/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.exoplatform.applicationregistry.webui.component;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.exoplatform.commons.serialization.api.annotations.Converted;
import org.exoplatform.commons.serialization.api.annotations.Serialized;
import org.exoplatform.container.ExoContainer;
import org.exoplatform.container.ExoContainerContext;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.webui.application.WebuiRequestContext;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIContainer;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;
import org.gatein.common.i18n.LocalizedString;
import org.gatein.pc.api.Portlet;
import org.gatein.pc.api.PortletContext;
import org.gatein.pc.api.info.MetaInfo;
import org.gatein.pc.api.info.PortletInfo;
import org.gatein.pc.api.info.PreferencesInfo;
import org.gatein.pc.federation.FederatingPortletInvoker;

/**
 * Created by The eXo Platform SAS Author : Pham Thanh Tung thanhtungty@gmail.com Jun 24, 2008
 */

@ComponentConfig(template = "app:/groovy/applicationregistry/webui/component/UIPortletManagement.gtmpl", events = {
        @EventConfig(listeners = UIPortletManagement.SelectPortletActionListener.class),
        @EventConfig(listeners = UIPortletManagement.SelectPortletType.class) })
@Serialized
public class UIPortletManagement extends UIContainer {

    /** Should match WSRPPortletInfo.PRODUCER_NAME_META_INFO_KEY */
    private static final String PRODUCER_NAME_META_INFO_KEY = "producer-name";

    public static final String LOCAL = "local";

    public static final String REMOTE = "remote";

    private List<WebApp> webApps;

    private PortletExtra selectedPorlet;

    private String[] portletTypes = new String[] { LOCAL, REMOTE };

    private String selectedType;

    private static final Comparator<WebApp> WEB_APP_COMPARATOR = new Comparator<WebApp>() {
        public int compare(WebApp app1, WebApp app2) {
            return app1.getName().compareToIgnoreCase(app2.getName());
        }
    };

    private static final Comparator<PortletExtra> PORTLET_EXTRA_COMPARATOR = new Comparator<PortletExtra>() {
        public int compare(PortletExtra portlet1, PortletExtra portlet2) {
            return portlet1.getName().compareToIgnoreCase(portlet2.getName());
        }
    };

    public UIPortletManagement() throws Exception {
        setSelectedType(LOCAL);
    }

    private void initWebApps(String type) throws Exception {
        webApps = new LinkedList<WebApp>(); // LinkedList is more appropriate here since we add lots of elements and sort
        // julien : who said that LinkedList is appropriate for sorting ?
        ExoContainer manager = ExoContainerContext.getCurrentContainer();

        FederatingPortletInvoker portletInvoker = (FederatingPortletInvoker) manager
                .getComponentInstance(FederatingPortletInvoker.class);
        boolean remote = REMOTE.equals(type);

        Set<Portlet> portlets;
        if (!remote) {
            portlets = portletInvoker.getLocalPortlets();
        } else {
            portlets = portletInvoker.getRemotePortlets();
        }

        if (portlets == null || portlets.isEmpty()) {
            return;
        }

        for (Portlet portlet : portlets) {
            PortletInfo info = portlet.getInfo();

            // in the remote case, the "application name" will be the name of the remote invoker
            String appName;
            if (remote) {
                LocalizedString producerNameLS = info.getMeta().getMetaValue(PRODUCER_NAME_META_INFO_KEY);
                if (producerNameLS != null) {
                    appName = producerNameLS.getDefaultString();
                } else {
                    throw new IllegalStateException("Missing PortletInvoker id in remote portlet metadata");
                }
            } else {
                appName = info.getApplicationName();
            }

            WebApp webApp = getWebApp(appName);
            if (webApp == null) {
                webApp = new WebApp(appName);
                webApps.add(webApp);
            }
            webApp.addPortlet(new PortletExtra(portlet));
        }

        Collections.sort(webApps, WEB_APP_COMPARATOR);
        for (WebApp ele : webApps) {
            Collections.sort(ele.getPortlets(), PORTLET_EXTRA_COMPARATOR);
        }
    }

    public WebApp getWebApp(String name) {
        for (WebApp ele : webApps) {
            if (ele.getName().equals(name)) {
                return ele;
            }
        }
        return null;
    }

    public List<WebApp> getWebApps() {
        return webApps;
    }

    public String getSelectedType() {
        return selectedType;
    }

    public void setSelectedType(String type) throws Exception {
        selectedType = type;
        initWebApps(type);
        if (webApps != null && !webApps.isEmpty()) {
            List<PortletExtra> list = webApps.get(0).getPortlets();
            if (!list.isEmpty()) {
                setSelectedPortlet(list.get(0));
            }
        } else {
            setSelectedPortlet((PortletExtra) null);
        }
    }

    public String[] getPortletTypes() {
        return portletTypes;
    }

    public PortletExtra getSelectedPortlet() {
        return selectedPorlet;
    }

    public void setSelectedPortlet(PortletExtra portlet) throws Exception {
        selectedPorlet = portlet;
        if (selectedPorlet == null) {
            getChildren().clear();
            UIMessageBoard uiMessage = addChild(UIMessageBoard.class, null, null);
            uiMessage.setMessage(new ApplicationMessage("UIPortletManagement.msg.noPortlet", null));
            return;
        }
        UIPortletInfo uiPortletInfo = getChild(UIPortletInfo.class);
        if (uiPortletInfo == null) {
            getChildren().clear();
            uiPortletInfo = addChild(UIPortletInfo.class, null, null);
        }
        uiPortletInfo.setPortlet(selectedPorlet);
        uiPortletInfo.getChild(UICategorySelector.class).setRendered(false);
    }

    public void setSelectedPortlet(String id) throws Exception {
        String webAppName;
        String portletName;
        if (LOCAL.equals(selectedType)) {
            String[] fragments = id.split("/");
            webAppName = fragments[0];
            portletName = fragments[1];
        } else {
            // extract PortletInvoker id to use as WebApp name
            final int separatorIndex = id.indexOf('.');
            webAppName = id.substring(0, separatorIndex);
            portletName = id.substring(separatorIndex + 1);
        }

        WebApp webApp = getWebApp(webAppName);
        for (PortletExtra ele : webApp.getPortlets()) {
            if (ele.getName().equals(portletName)) {
                setSelectedPortlet(ele);
                break;
            }
        }
    }

    public void processRender(WebuiRequestContext context) throws Exception {
        super.processRender(context);
    }

    public static class SelectPortletType extends EventListener<UIPortletManagement> {

        public void execute(Event<UIPortletManagement> event) throws Exception {
            UIPortletManagement uiManagement = event.getSource();
            String type = event.getRequestContext().getRequestParameter(OBJECTID);
            uiManagement.setSelectedType(type);
            event.getRequestContext().addUIComponentToUpdateByAjax(uiManagement);
        }

    }

    public static class SelectPortletActionListener extends EventListener<UIPortletManagement> {

        public void execute(Event<UIPortletManagement> event) throws Exception {
            UIPortletManagement uiManagement = event.getSource();
            String portletId = event.getRequestContext().getRequestParameter(OBJECTID);
            uiManagement.setSelectedPortlet(portletId);
            event.getRequestContext().addUIComponentToUpdateByAjax(uiManagement);
        }

    }

    @Converted(PortletExtraSerializer.class)
    public static class PortletExtra {

        private String id_;

        private String name_;

        private String group_;

        private String type_;

        private PortletInfo portletInfo_;

        final PortletContext context;

        public PortletExtra(Portlet portlet) {
            PortletInfo info = portlet.getInfo();

            context = portlet.getContext();

            String appName = info.getApplicationName();
            boolean remote = portlet.isRemote();

            String portletId;
            if (remote) {
                portletId = context.getId();
            } else {
                portletId = info.getApplicationName() + "/" + info.getName();
            }

            String type = remote ? REMOTE : LOCAL;

            //
            id_ = portletId;
            group_ = appName;
            name_ = info.getName();
            type_ = type;
            portletInfo_ = info;

        }

        public String getId() {
            return id_;
        }

        public String getName() {
            return name_;
        }

        public String getPortletGroup() {
            return group_;
        }

        public String getType() {
            return type_;
        }

        public String getDisplayName() {
            try {
                final String displayName = getMetaValue(MetaInfo.DISPLAY_NAME, name_);
                return isRemote() ? displayName + " (remote)" : displayName;
            } catch (Exception ex) {
                return "COULD NOT GET DISPLAY NAME OF THE PORTLET";
            }
        }

        public String getDescription() {
            try {
                return getMetaValue(MetaInfo.DESCRIPTION, name_);
            } catch (Exception ex) {
                return "COULD NOT GET DESCRIPTION OF THE PORTLET";
            }
        }

        public PreferencesInfo getPortletPreferences() {
            try {
                return portletInfo_.getPreferences();
            } catch (Exception ex) {
                return null;
            }
        }

        private String getMetaValue(String metaKey, String defaultValue) {
            LocalizedString metaValue = portletInfo_.getMeta().getMetaValue(metaKey);
            if (metaValue == null || metaValue.getDefaultString() == null) {
                return defaultValue;
            }
            return metaValue.getDefaultString();
        }

        public boolean isRemote() {
            return REMOTE.equals(type_);
        }
    }

    @Serialized
    public static class WebApp {

        String name_;

        List<PortletExtra> portlets_;

        public WebApp() {
        }

        public WebApp(String name) {
            name_ = name;
        }

        public WebApp(String name, List<PortletExtra> portlets) {
            name_ = name;
            portlets_ = portlets;
        }

        public String getName() {
            return name_;
        }

        public void setName(String name) {
            name_ = name;
        }

        public List<PortletExtra> getPortlets() {
            return portlets_;
        }

        public void setPortlets(List<PortletExtra> portlets) {
            portlets_ = portlets;
        }

        public void addPortlet(PortletExtra portlet) {
            if (portlets_ == null) {
                portlets_ = new ArrayList<PortletExtra>();
            }
            portlets_.add(portlet);
        }

    }
}
