/*
 * Aipo is a groupware program developed by Aimluck,Inc.
 * Copyright (C) 2004-2011 Aimluck,Inc.
 * http://www.aipo.com
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.aimluck.eip.modules.screens;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.json.JSONArray;

import org.apache.jetspeed.om.profile.Control;
import org.apache.jetspeed.om.profile.Controller;
import org.apache.jetspeed.om.profile.Entry;
import org.apache.jetspeed.om.profile.IdentityElement;
import org.apache.jetspeed.om.profile.Layout;
import org.apache.jetspeed.om.profile.MetaInfo;
import org.apache.jetspeed.om.profile.Parameter;
import org.apache.jetspeed.om.profile.Portlets;
import org.apache.jetspeed.om.profile.Profile;
import org.apache.jetspeed.om.profile.Reference;
import org.apache.jetspeed.om.profile.psml.PsmlControl;
import org.apache.jetspeed.om.profile.psml.PsmlController;
import org.apache.jetspeed.om.profile.psml.PsmlEntry;
import org.apache.jetspeed.om.profile.psml.PsmlLayout;
import org.apache.jetspeed.om.profile.psml.PsmlParameter;
import org.apache.jetspeed.om.registry.PortletEntry;
import org.apache.jetspeed.portal.PortletController;
import org.apache.jetspeed.portal.security.portlets.PortletWrapper;
import org.apache.jetspeed.services.PortalToolkit;
import org.apache.jetspeed.services.PortletFactory;
import org.apache.jetspeed.services.Registry;
import org.apache.jetspeed.services.idgenerator.JetspeedIdGenerator;
import org.apache.jetspeed.services.logging.JetspeedLogFactoryService;
import org.apache.jetspeed.services.logging.JetspeedLogger;
import org.apache.jetspeed.services.rundata.JetspeedRunData;
import org.apache.jetspeed.services.statemanager.SessionState;
import org.apache.jetspeed.util.PortletSessionState;
import org.apache.turbine.services.localization.Localization;
import org.apache.turbine.util.RunData;
import org.apache.velocity.context.Context;

import com.aimluck.eip.common.ALApplication;
import com.aimluck.eip.services.accessctl.ALAccessControlConstants;
import com.aimluck.eip.services.social.ALApplicationService;
import com.aimluck.eip.services.social.model.ALApplicationGetRequest;
import com.aimluck.eip.util.ALEipUtils;
import com.aimluck.eip.util.CustomizeUtils;

/**
 * ToDoをJSONデータとして出力するクラスです。 <br />
 * 
 */
public class CustomizeFormJSONScreen extends ALJSONScreen {

  /** logger */
  private static final JetspeedLogger logger = JetspeedLogFactoryService
    .getLogger(CustomizeFormJSONScreen.class.getName());

  @Override
  protected String getJSONString(RunData rundata, Context context)
      throws Exception {
    String result = new JSONArray().toString();
    try {
      String mode = rundata.getParameters().get("mode");
      if ("add".equals(mode)) {
        doAdd(rundata, context);
      }
      if ("delete".equals(mode)) {
        doDelete(rundata, context);
      }
      if ("layout".equals(mode)) {
        doLayout(rundata, context);
      }
    } catch (Exception e) {
      logger.error("[CustomizeFormJSONScreen]", e);
    }
    return result;
  }

  /** Add new portlets in the customized set */
  public void doAdd(RunData rundata, Context context) throws Exception {
    maintainUserSelections(rundata);
    Map<String, PortletEntry> userSelections =
      CustomizeUtils.getUserSelections(rundata);
    String[] pnames = new String[userSelections.size()];
    userSelections.keySet().toArray(pnames);

    // Create a ClearPortletControl
    Control ctrl = new PsmlControl();
    ctrl.setName("ClearPortletControl");

    JetspeedRunData jdata = (JetspeedRunData) rundata;
    Profile profile = jdata.getProfile();

    String pid = rundata.getParameters().get("portlet_id");
    Portlets portlets = profile.getDocument().getPortletsById(pid);

    setController(rundata, context, portlets);

    if (pnames != null) {

      boolean addIt;
      for (int i = 0; i < pnames.length; i++) {
        String pname = pnames[i];
        PortletEntry entry = null;
        boolean isGadgets = false;
        ALApplication app = null;
        if (pname.startsWith("GadgetsTemplate::")) {
          String[] split = pname.split("::");
          String appId = split[1];
          app =
            ALApplicationService.get(new ALApplicationGetRequest()
              .withAppId(appId));
          entry =
            (PortletEntry) Registry.getEntry(
              Registry.PORTLET,
              "GadgetsTemplate");
          isGadgets = true;
        } else {
          entry = (PortletEntry) Registry.getEntry(Registry.PORTLET, pnames[i]);
        }

        // add only new portlets!
        if ((entry != null) && (portlets != null)) {
          addIt = true;
          if (addIt) {
            Entry p = new PsmlEntry();
            p.setParent(isGadgets ? "GadgetsTemplate" : pnames[i]);
            p.setId(JetspeedIdGenerator.getNextPeid());

            if (isGadgets) {
              p.setTitle(app.getTitle().getValue());
              Parameter p1 = new PsmlParameter();
              p1.setName("aid");
              p1.setValue(app.getAppId().getValue());
              p.addParameter(p1);
              Parameter p2 = new PsmlParameter();
              p2.setName("url");
              p2.setValue(app.getUrl().getValue());
              p.addParameter(p2);
              Parameter p3 = new PsmlParameter();
              p3.setName("mid");
              String moduleId =
                String.valueOf(ALApplicationService.getNextModuleId());
              p3.setValue(moduleId);
              p.addParameter(p3);
              p.setId(moduleId);
            }
            portlets.addEntry(p);
          }
        }
      }
    }
    // --------------------------------------------------------------------------

    // 理由等 ：追加したポートレットを PSML に保存する．
    doSaveAddAction(rundata, context, portlets);

    SessionState customizationState =
      ((JetspeedRunData) rundata).getPageSessionState();
    customizationState.setAttribute("customize-mode", "layout");
  }

  public void doDelete(RunData rundata, Context context) {
    JetspeedRunData jdata = (JetspeedRunData) rundata;

    // アクセス権限のチェック
    ALEipUtils.CheckAclPermissionForCustomize(
      rundata,
      context,
      ALAccessControlConstants.VALUE_ACL_DELETE);

    String portletId = rundata.getParameters().get("js_peid");
    Portlets portlets = jdata.getProfile().getDocument().getPortlets();
    Portlets[] portletList = portlets.getPortletsArray();
    if (portletList == null) {
      return;
    }

    Profile profile = jdata.getProfile();
    List<PortletEntry> normalPortlets =
      CustomizeUtils.buildAllPortletList(
        rundata,
        profile.getMediaType(),
        new ArrayList<PortletEntry>());
    List<String> editablePortlets = new ArrayList<String>();
    for (PortletEntry entry : normalPortlets) {
      editablePortlets.add(entry.getName());
    }

    try {
      int length = portletList.length;

      search: for (int i = 0; i < length; i++) {
        Entry[] entries = portletList[i].getEntriesArray();
        if (entries == null || entries.length <= 0) {
          continue;
        }

        int ent_length = entries.length;
        for (int j = 0; j < ent_length; j++) {
          String parent = entries[j].getParent();
          if (entries[j].getId().equals(portletId)
            && (editablePortlets.contains(entries[j].getParent()) || parent
              .startsWith("GadgetsTemplate"))) {
            PortletWrapper wrapper =
              (PortletWrapper) PortletFactory.getPortlet(entries[j]);
            if (wrapper != null) {
              portletList[i].removeEntry(j);
              break search;
            }
          }
        }
      }
      profile.store();
    } catch (Exception e) {
      // probably got wrong coordinates
      logger.error(
        "MultiColumnControllerAction: Probably got wrong coordinates",
        e);
    }
  }

  public void doLayout(RunData rundata, Context context) {

    JetspeedRunData jdata = (JetspeedRunData) rundata;

    // アクセス権限のチェック
    ALEipUtils.CheckAclPermissionForCustomize(
      rundata,
      context,
      ALAccessControlConstants.VALUE_ACL_UPDATE);

    Portlets portlets = jdata.getProfile().getDocument().getPortlets();
    Portlets[] portletList = portlets.getPortletsArray();
    if (portletList == null) {
      return;
    }

    Profile profile = jdata.getProfile();
    List<PortletEntry> normalPortlets =
      CustomizeUtils.buildAllPortletList(
        rundata,
        profile.getMediaType(),
        new ArrayList<PortletEntry>());
    List<String> editablePortlets = new ArrayList<String>();
    for (PortletEntry entry : normalPortlets) {
      editablePortlets.add(entry.getName());
    }

    try {
      int length = portletList.length;

      for (int i = 0; i < length; i++) {
        Entry[] entries = portletList[i].getEntriesArray();
        if (entries == null || entries.length <= 0) {
          continue;
        }

        int ent_length = entries.length;
        for (int j = 0; j < ent_length; j++) {
          String parent = entries[j].getParent();
          if (editablePortlets.contains(parent)
            || parent.startsWith("GadgetsTemplate")) {
            String pid = entries[j].getId();
            int col = rundata.getParameters().getInt(pid + "_col", -1);
            int row = rundata.getParameters().getInt(pid + "_row", -1);
            if (col != -1 && row != -1) {
              setPosition(entries[j], col, row);
            }
          }
        }
      }
      profile.store();
    } catch (Exception e) {
      // probably got wrong coordinates
      logger.error(
        "MultiColumnControllerAction: Probably got wrong coordinates",
        e);
    }
  }

  protected void maintainUserSelections(RunData rundata) throws Exception {
    int size = rundata.getParameters().getInt("size", 0);
    int previous = rundata.getParameters().getInt("previous", -1);

    @SuppressWarnings("unused")
    int start = 0;
    if (previous >= 0) {
      start = previous + size;
    }

    String[] pnames = rundata.getParameters().getStrings("pname");
    Map<String, PortletEntry> userSelections =
      CustomizeUtils.getUserSelections(rundata);
    @SuppressWarnings("unchecked")
    List<PortletEntry> portlets =
      (List<PortletEntry>) PortletSessionState.getAttribute(
        rundata,
        CustomizeUtils.PORTLET_LIST,
        null);

    if (portlets == null) {
      throw new Exception("Master Portlet List is null!");
    }

    if (pnames != null) {
      for (String pname : pnames) {
        for (PortletEntry entry : portlets) {
          String name = entry.getName();
          if (name.equals(pname)) {
            userSelections.put(pname, userSelections.get(pname));
            break;
          }
        }
      }
    }
    PortletSessionState.setAttribute(
      rundata,
      CustomizeUtils.USER_SELECTIONS,
      userSelections);
  }

  public void doSaveAddAction(RunData data, Context context, Portlets portlets) {
    setPageLayout(data, context, portlets);
    // String REFERENCES_REMOVED = "references-removed";
    // get the customization state for this page
    SessionState customizationState =
      ((JetspeedRunData) data).getPageSessionState();
    // update the changes made here to the profile being edited
    List<?>[] columns =
      (List[]) customizationState.getAttribute("customize-columns");
    for (int col = 0; col < columns.length; col++) {
      for (int row = 0; row < columns[col].size(); row++) {
        setPosition((IdentityElement) columns[col].get(row), col, row);
      }
    }
    // save the edit profile and make it current
    try {
      ((JetspeedRunData) data).getProfile().store();

      // // Because of the way references are stored in memory, we have to
      // completely refresh
      // // the profile after a references is removed (otherwise it will
      // continue being displayed)
      // String referencesRemoved =
      // (String) customizationState.getAttribute(REFERENCES_REMOVED);
      // if (referencesRemoved != null && referencesRemoved.equals("true")) {
      // PsmlManager.refresh(((JetspeedRunData) data).getCustomizedProfile());
      // }
    } catch (Exception e) {
      logger.error("Unable to save profile ", e);
    }
  }

  protected static void setPosition(IdentityElement identityElement, int col,
      int row) {
    boolean colFound = false;
    boolean rowFound = false;

    if (identityElement != null) {
      Layout layout = identityElement.getLayout();

      if (layout == null) {
        layout = new PsmlLayout();
        identityElement.setLayout(layout);
      }

      for (int i = 0; i < layout.getParameterCount(); i++) {
        Parameter p = layout.getParameter(i);

        if (p.getName().equals("column")) {
          p.setValue(String.valueOf(col));
          colFound = true;
        } else if (p.getName().equals("row")) {
          p.setValue(String.valueOf(row));
          rowFound = true;
        }
      }

      if (!colFound) {
        Parameter p = new PsmlParameter();
        p.setName("column");
        p.setValue(String.valueOf(col));
        layout.addParameter(p);
      }

      if (!rowFound) {
        Parameter p = new PsmlParameter();
        p.setName("row");
        p.setValue(String.valueOf(row));
        layout.addParameter(p);
      }
    }
  }

  @SuppressWarnings("deprecation")
  private void setPageLayout(RunData rundata, Context context, Portlets portlets) {
    JetspeedRunData jdata = (JetspeedRunData) rundata;

    // get the customization state for this page
    SessionState customizationState = jdata.getPageSessionState();

    List<?>[] columns =
      (List[]) customizationState.getAttribute("customize-columns");
    String controllerName = portlets.getController().getName();
    int colNum = 2;
    if (controllerName.startsWith("One")) {
      colNum = 1;
    } else if (controllerName.startsWith("Three")) {
      colNum = 3;
    }
    Portlets set = portlets;

    if (logger.isDebugEnabled()) {
      logger.debug("MultiCol: columns "
        + Arrays.toString(columns)
        + " set "
        + set);
    }
    if ((columns != null) && (columns.length == colNum)) {
      int eCount = 0;
      for (int i = 0; i < columns.length; i++) {
        eCount += columns[i].size();
      }

      if (logger.isDebugEnabled()) {
        logger.debug("MultiCol: eCount "
          + eCount
          + " setCount"
          + set.getEntryCount()
          + set.getPortletsCount());
      }
      if (eCount != set.getEntryCount() + set.getPortletsCount()) {
        if (logger.isDebugEnabled()) {
          logger.debug("MultiCol: rebuilding columns ");
        }
        columns = CustomizeUtils.buildColumns(set, colNum);
      }

    } else {
      if (logger.isDebugEnabled()) {
        logger.debug("MultiCol: rebuilding columns ");
      }
      columns = CustomizeUtils.buildColumns(set, colNum);
    }
    customizationState.setAttribute("customize-columns", columns);
    context.put("portlets", columns);

    Map<String, String> titles = new HashMap<String, String>();
    for (int col = 0; col < columns.length; col++) {
      for (int row = 0; row < columns[col].size(); row++) {
        IdentityElement identityElement =
          (IdentityElement) columns[col].get(row);
        MetaInfo metaInfo = identityElement.getMetaInfo();
        if ((metaInfo != null) && (metaInfo.getTitle() != null)) {
          titles.put(identityElement.getId(), metaInfo.getTitle());
          continue;
        }

        if (identityElement instanceof Entry) {
          Entry entry = (Entry) identityElement;
          PortletEntry pentry =
            (PortletEntry) Registry.getEntry(Registry.PORTLET, entry
              .getParent());
          if ((pentry != null) && (pentry.getTitle() != null)) {
            titles.put(entry.getId(), pentry.getTitle());
            continue;
          }

          titles.put(entry.getId(), entry.getParent());
          continue;
        }

        if (identityElement instanceof Reference) {
          titles.put(identityElement.getId(), Localization.getString(
            rundata,
            "CUSTOMIZER_REF_DEFAULTTITLE"));
          continue;
        }

        // Let's make sure their is a title
        titles.put(identityElement.getId(), Localization.getString(
          rundata,
          "CUSTOMIZER_NOTITLESET"));
      }
    }

    Map<String, String> descriptions = new HashMap<String, String>();
    for (int col = 0; col < columns.length; col++) {
      for (int row = 0; row < columns[col].size(); row++) {
        IdentityElement identityElement =
          (IdentityElement) columns[col].get(row);
        MetaInfo metaInfo = identityElement.getMetaInfo();
        if ((metaInfo != null) && (metaInfo.getDescription() != null)) {
          descriptions.put(identityElement.getId(), metaInfo.getDescription());
          continue;
        }

        if (identityElement instanceof Entry) {
          Entry entry = (Entry) identityElement;
          PortletEntry pentry =
            (PortletEntry) Registry.getEntry(Registry.PORTLET, entry
              .getParent());
          if ((pentry != null) && (pentry.getDescription() != null)) {
            descriptions.put(entry.getId(), pentry.getDescription());
            continue;
          }

          descriptions.put(entry.getId(), entry.getParent());
          continue;
        }
      }
    }
  }

  /** Updates the customized portlet entry */
  private void setController(RunData rundata, Context context, Portlets portlets) {
    ALEipUtils.CheckAclPermissionForCustomize(
      rundata,
      context,
      ALAccessControlConstants.VALUE_ACL_UPDATE);

    try {
      String controller = rundata.getParameters().getString("controller");

      if (controller != null) {
        PortletController pc = PortalToolkit.getController(controller);
        if (pc != null) {

          Controller c = portlets.getController();
          if (c == null) {
            c = new PsmlController();
          }
          c.setName(controller);
          portlets.setController(c);

          String linkedControl = pc.getConfig().getInitParameter("control");

          if (linkedControl != null) {
            Control ctl = new PsmlControl();
            ctl.setName(linkedControl);
            portlets.setControl(ctl);
          } else {
            portlets.setControl(null);
          }
        }
      }
    } catch (Exception e) {
      logger.error("Exception", e);
    }
  }
}
