/*
 * Copyright (C) 2003-2008 eXo Platform SAS.
 *
 * 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see<http://www.gnu.org/licenses/>.
 */
package org.exoplatform.wcm.webui.dialog.permission;

import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jcr.AccessDeniedException;
import javax.jcr.Node;
import javax.jcr.Session;

import org.exoplatform.commons.utils.LazyPageList;
import org.exoplatform.commons.utils.ListAccess;
import org.exoplatform.commons.utils.ListAccessImpl;
import org.exoplatform.ecm.webui.form.UIFormInputSetWithAction;
import org.exoplatform.ecm.webui.selector.UIGroupMemberSelector;
import org.exoplatform.ecm.webui.selector.UISelectable;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.core.ExtendedNode;
import org.exoplatform.services.security.IdentityConstants;
import org.exoplatform.services.wcm.core.NodeLocation;
import org.exoplatform.wcm.webui.Utils;
import org.exoplatform.wcm.webui.dialog.UIContentDialogForm;
import org.exoplatform.wcm.webui.selector.account.UIUserContainer;
import org.exoplatform.web.application.ApplicationMessage;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIGrid;
import org.exoplatform.webui.core.UIPopupContainer;
import org.exoplatform.webui.core.lifecycle.UIFormLifecycle;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;
import org.exoplatform.webui.form.UIForm;
import org.exoplatform.webui.form.UIFormStringInput;
import org.exoplatform.webui.form.input.UICheckBoxInput;

/**
 * Created by The eXo Platform SAS
 * Author : Phan Le Thanh Chuong
 *          chuong.phan@exoplatform.com, phan.le.thanh.chuong@gmail.com
 * Oct 29, 2009
 */
@ComponentConfig (
    lifecycle = UIFormLifecycle.class,
    template = "classpath:groovy/wcm/webui/dialog/permission/UIPermissionManager.gtmpl",
    events = {
      @EventConfig(listeners = UIPermissionManager.DeleteActionListener.class,
                   confirm = "UIPermissionManagerGrid.msg.confirm-delete-permission"),
      @EventConfig(listeners = UIPermissionManager.EditActionListener.class),
      @EventConfig(listeners = UIPermissionManager.SaveActionListener.class),
      @EventConfig(listeners = UIPermissionManager.ClearActionListener.class),
      @EventConfig(listeners = UIPermissionManager.SelectUserActionListener.class),
      @EventConfig(listeners = UIPermissionManager.SelectMemberActionListener.class),
      @EventConfig(listeners = UIPermissionManager.AddAnyActionListener.class)
    }
)
public class UIPermissionManager extends UIForm implements UISelectable {

  /** The Constant PERMISSION_MANAGER_GRID. */
  public static final String PERMISSION_MANAGER_GRID     = "UIPermissionManagerGrid";

  /** The Constant PERMISSION_INPUT_SET. */
  public static final String PERMISSION_INPUT_SET        = "UIPermissionInputSetWithAction";

  /** The Constant PERMISSION_STRING_INPUT. */
  public static final String PERMISSION_STRING_INPUT     = "UIPermissionStringInput";

  /** The Constant ACCESSIBLE_CHECKBOX_INPUT. */
  public static final String ACCESSIBLE_CHECKBOX_INPUT   = "UIAccessibleCheckboxInput";

  /** The Constant EDITABLE_CHECKBOX_INPUT. */
  public static final String EDITABLE_CHECKBOX_INPUT     = "UIEditableCheckboxInput";

  public static final String USER_SELECTOR_POPUP_WINDOW  = "UIUserSelectorPopupWindow";

  public static final String GROUP_SELECTOR_POPUP_WINDOW = "UIGroupSelectorPopupWindow";

  private String popupId;

  public String getPopupId() {
    return popupId;
  }

  public void setPopupId(String popupId) {
    this.popupId = popupId;
  }

  /**
   * Instantiates a new uI permission info.
   *
   * @throws Exception the exception
   */
  public UIPermissionManager() throws Exception {
    UIGrid uiGrid = createUIComponent(UIGrid.class, null, PERMISSION_MANAGER_GRID);
    uiGrid.setLabel(PERMISSION_MANAGER_GRID);
    uiGrid.configure("owner", new String[] {"owner", "accessible", "editable"}, new String[] {"Edit", "Delete"});
    addChild(uiGrid);

    UIFormInputSetWithAction permissionInputSet = new UIFormInputSetWithAction(PERMISSION_INPUT_SET);
    UIFormStringInput formStringInput = new UIFormStringInput(PERMISSION_STRING_INPUT, PERMISSION_STRING_INPUT, null);
    formStringInput.setReadOnly(true);
    permissionInputSet.addChild(formStringInput);
    permissionInputSet.setActionInfo(PERMISSION_STRING_INPUT, new String[] {"SelectUser", "SelectMember", "AddAny"});
    permissionInputSet.showActionInfo(true);
    addChild(permissionInputSet);
    addChild(new UICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT, ACCESSIBLE_CHECKBOX_INPUT, null));
    addChild(new UICheckBoxInput(EDITABLE_CHECKBOX_INPUT, EDITABLE_CHECKBOX_INPUT, null));
    setActions(new String[] {"Save", "Clear"});
  }

  /**
   * Update grid.
   *
   * @throws Exception the exception
   */
  public void updateGrid() throws Exception {
    // Get node
    UIContentDialogForm contentDialogForm = getAncestorOfType(UIPopupContainer.class).getChild(UIContentDialogForm.class);
    NodeLocation webcontentNodeLocation = contentDialogForm.getWebcontentNodeLocation();
    Node node = NodeLocation.getNodeByLocation(webcontentNodeLocation);
    ExtendedNode webcontent = (ExtendedNode) node;

    // Convert permission entries to map
    List<UIPermissionConfig> permissionConfigs = new ArrayList<UIPermissionConfig>();
    Map<String, List<String>> permissionMap = new HashMap<String, List<String>>();
    List<AccessControlEntry> accessControlEntries = webcontent.getACL().getPermissionEntries();
    for (AccessControlEntry accessControlEntry : accessControlEntries) {
      String identity = accessControlEntry.getIdentity();
      String permission = accessControlEntry.getPermission();
      List<String> currentPermissions = permissionMap.get(identity);
      if (!permissionMap.containsKey(identity)) {
        permissionMap.put(identity, null);
      }
      if (currentPermissions == null)
        currentPermissions = new ArrayList<String>();
      if (!currentPermissions.contains(permission)) {
        currentPermissions.add(permission);
      }
      permissionMap.put(identity, currentPermissions);
    }

    // Add owner's permission
    String owner = IdentityConstants.SYSTEM;
    if (webcontent.hasProperty("exo:owner"))
      owner = webcontent.getProperty("exo:owner").getString();
    UIPermissionConfig permissionConfig = new UIPermissionConfig();
    if (!permissionMap.containsKey(owner)) {
      permissionConfig.setOwner(owner);
      permissionConfig.setAccessible(true);
      permissionConfig.setEditable(true);
      permissionConfigs.add(permissionConfig);
    }

    // Add node's permission
    Iterator<String> permissionIterator = permissionMap.keySet().iterator();
    while (permissionIterator.hasNext()) {
      String identity = (String) permissionIterator.next();
      List<String> userPermissions = permissionMap.get(identity);
      UIPermissionConfig permBean = new UIPermissionConfig();
      permBean.setOwner(identity);
      int numberPermission = 0;
      for (String p : PermissionType.ALL) {
        if (!userPermissions.contains(p)) break;
        numberPermission++;
      }
      if (numberPermission == PermissionType.ALL.length) {
        permBean.setEditable(true);
        permBean.setAccessible(true);
      } else {
        permBean.setAccessible(true);
      }
      permissionConfigs.add(permBean);
    }
    ListAccess<UIPermissionConfig> permConfigList = new ListAccessImpl<UIPermissionConfig>(UIPermissionConfig.class,
                                                                                           permissionConfigs);
    LazyPageList<UIPermissionConfig> dataPageList = new LazyPageList<UIPermissionConfig>(permConfigList,
                                                                                         10);
    UIGrid uiGrid = getChildById(PERMISSION_MANAGER_GRID);
    uiGrid.getUIPageIterator().setPageList(dataPageList);
  }

  /* (non-Javadoc)
   * @see org.exoplatform.ecm.webui.selector.UISelectable#doSelect(java.lang.String, java.lang.Object)
   */
  public void doSelect(String selectField, Object value) throws Exception {
    UIFormInputSetWithAction permissionInputSet = getChildById(PERMISSION_INPUT_SET);
    permissionInputSet.getUIStringInput(PERMISSION_STRING_INPUT).setValue(value.toString());
    Utils.closePopupWindow(this, popupId);
  }

  /**
  * Checks for change permission right.
  *
  * @param node the node
  *
  * @return true, if successful
  *
  * @throws Exception the exception
  */
  private boolean hasChangePermissionRight(ExtendedNode node) throws Exception {
   try {
     node.checkPermission(PermissionType.ADD_NODE);
     node.checkPermission(PermissionType.REMOVE);
     node.checkPermission(PermissionType.SET_PROPERTY);
     return true;
   } catch (AccessControlException e) {
     return false;
   }
  }

  /**
   * The listener interface for receiving deleteAction events.
   * The class that is interested in processing a deleteAction
   * event implements this interface, and the object created
   * with that class is registered with a component using the
   * component's <code>addDeleteActionListener<code> method. When
   * the deleteAction event occurs, that object's appropriate
   * method is invoked.
   *
   * @see DeleteActionEvent
   */
  public static class DeleteActionListener extends EventListener<UIPermissionManager> {

    /* (non-Javadoc)
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
     */
    public void execute(Event<UIPermissionManager> event) throws Exception {
      UIPermissionManager permissionManager = event.getSource();
      UIContentDialogForm contentDialogForm = permissionManager.getAncestorOfType(UIPopupContainer.class)
                                                               .getChild(UIContentDialogForm.class);
      NodeLocation webcontentNodeLocation = contentDialogForm.getWebcontentNodeLocation();
      Node node = NodeLocation.getNodeByLocation(webcontentNodeLocation);
      ExtendedNode webcontent = (ExtendedNode) node;
      Session session = webcontent.getSession();

      String name = event.getRequestContext().getRequestParameter(OBJECTID);
      String nodeOwner = webcontent.getProperty("exo:owner").getString();
      if (name.equals(nodeOwner)) {
        Utils.createPopupMessage(permissionManager,
                                 "UIPermissionManagerGrid.msg.no-permission-remove",
                                 null,
                                 ApplicationMessage.WARNING);
        return;
      }
      if (permissionManager.hasChangePermissionRight(webcontent)) {
        if (webcontent.canAddMixin("exo:privilegeable")) {
          webcontent.addMixin("exo:privilegeable");
          webcontent.setPermission(nodeOwner, PermissionType.ALL);
        }
        try {
          webcontent.removePermission(name);
          session.save();
          permissionManager.updateGrid();
        } catch (AccessControlException e) {
          Object[] args = {webcontent.getPath()};
          Utils.createPopupMessage(permissionManager,
                                   "UIPermissionManagerGrid.msg.node-locked",
                                   args,
                                   ApplicationMessage.WARNING);
          return;
        } catch (AccessDeniedException ace) {
          Utils.createPopupMessage(permissionManager,
                                   "UIPermissionManagerGrid.msg.access-denied",
                                   null,
                                   ApplicationMessage.WARNING);
          return;
        }
      }
    }
  }

  /**
   * The listener interface for receiving editAction events.
   * The class that is interested in processing a editAction
   * event implements this interface, and the object created
   * with that class is registered with a component using the
   * component's <code>addEditActionListener<code> method. When
   * the editAction event occurs, that object's appropriate
   * method is invoked.
   *
   * @see EditActionEvent
   */
  public static class EditActionListener extends EventListener<UIPermissionManager> {

    /* (non-Javadoc)
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
     */
    public void execute(Event<UIPermissionManager> event) throws Exception {
      UIPermissionManager permissionManager = event.getSource();
      UIContentDialogForm contentDialogForm = permissionManager.getAncestorOfType(UIPopupContainer.class)
                                                               .getChild(UIContentDialogForm.class);
      NodeLocation webcontentNodeLocation = contentDialogForm.getWebcontentNodeLocation();
      Node node = NodeLocation.getNodeByLocation(webcontentNodeLocation);
      ExtendedNode webcontent = (ExtendedNode) node;
      String name = event.getRequestContext().getRequestParameter(OBJECTID);

      UIFormInputSetWithAction permissionInputSet = permissionManager.getChildById(PERMISSION_INPUT_SET);
      permissionInputSet.getUIStringInput(PERMISSION_STRING_INPUT).setValue(name);
      String owner = node.getProperty("exo:owner").getString();
      if (name.equals(owner)) {
        permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).setChecked(true);
        permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).setChecked(true);
        permissionManager.setActions(new String[] {"Clear"});
        permissionInputSet.setActionInfo(PERMISSION_STRING_INPUT, null);
      } else {
        List<AccessControlEntry> permsList = webcontent.getACL().getPermissionEntries();
        StringBuilder userPermission = new StringBuilder();
        for (AccessControlEntry accessControlEntry : permsList) {
          if (name.equals(accessControlEntry.getIdentity())) {
            userPermission.append(accessControlEntry.getPermission()).append(" ");
          }
        }
        int numPermission = 0;
        for (String perm : PermissionType.ALL) {
          if (userPermission.toString().contains(perm))
            numPermission++;
        }
        if (numPermission == PermissionType.ALL.length) {
          permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).setChecked(true);
          permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).setChecked(true);
        } else {
          permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).setChecked(true);
          permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).setChecked(false);
        }
        permissionManager.setActions(new String[] {"Save", "Clear"});
        permissionInputSet.setActionInfo(PERMISSION_STRING_INPUT, new String[] { "SelectUser",
            "SelectMember", "AddAny" });
      }
    }
  }

  /**
  * The listener interface for receiving saveAction events.
  * The class that is interested in processing a saveAction
  * event implements this interface, and the object created
  * with that class is registered with a component using the
  * component's <code>addSaveActionListener<code> method. When
  * the saveAction event occurs, that object's appropriate
  * method is invoked.
  *
  * @see SaveActionEvent
  */
  public static class SaveActionListener extends EventListener<UIPermissionManager> {

   /* (non-Javadoc)
    * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
    */
   public void execute(Event<UIPermissionManager> event) throws Exception {
     UIPermissionManager permissionManager = event.getSource();
      UIContentDialogForm contentDialogForm = permissionManager.getAncestorOfType(UIPopupContainer.class)
                                                               .getChild(UIContentDialogForm.class);
     NodeLocation webcontentNodeLocation = contentDialogForm.getWebcontentNodeLocation();
     Node node = NodeLocation.getNodeByLocation(webcontentNodeLocation);
     ExtendedNode webcontent = (ExtendedNode) node;
     Session session = webcontent.getSession();

     UIFormInputSetWithAction formInputSet = permissionManager.getChildById(PERMISSION_INPUT_SET);
     String identity = ((UIFormStringInput) formInputSet.getChildById(PERMISSION_STRING_INPUT)).getValue();
     List<String> permsList = new ArrayList<String>();
     if (!webcontent.isCheckedOut()) {
        Utils.createPopupMessage(permissionManager,
                                 "UIPermissionManagerGrid.msg.node-checkedin",
                                 null,
                                 ApplicationMessage.WARNING);
       return;
     }
     if (permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).isChecked()) {
       permsList.clear();
       permsList.add(PermissionType.READ);
     }
     if (permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).isChecked()) {
       permsList.clear();
       for (String perm : PermissionType.ALL)
         permsList.add(perm);
     }
     if (identity == null || identity.trim().length() == 0) {
        Utils.createPopupMessage(permissionManager,
                                 "UIPermissionManagerGrid.msg.userOrGroup-required",
                                 null,
                                 ApplicationMessage.WARNING);
       return;
     }
     if (permsList.size() == 0) {
        Utils.createPopupMessage(permissionManager,
                                 "UIPermissionManagerGrid.msg.checkbox-require",
                                 null,
                                 ApplicationMessage.WARNING);
       return;
     }
     String[] permsArray = permsList.toArray(new String[permsList.size()]);
     if (webcontent.canAddMixin("exo:privilegeable")) {
       webcontent.addMixin("exo:privilegeable");
       webcontent.setPermission(webcontent.getProperty("exo:owner").getString(), PermissionType.ALL);
     }
     try {
      webcontent.setPermission(identity, permsArray);
    } catch (AccessControlException e) {
      Object[] args = {webcontent.getPath()};
        Utils.createPopupMessage(permissionManager,
                                 "UIPermissionManagerGrid.msg.node-locked",
                                 args,
                                 ApplicationMessage.WARNING);
      return;
    }
     session.save();

     permissionManager.updateGrid();
     UIFormInputSetWithAction permissionInputSet = permissionManager.getChildById(PERMISSION_INPUT_SET);
     ((UIFormStringInput) permissionInputSet.getChildById(PERMISSION_STRING_INPUT)).setValue("");
     permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).setChecked(false);
     permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).setChecked(false);
   }
  }


  /**
  * The listener interface for receiving resetAction events.
  * The class that is interested in processing a resetAction
  * event implements this interface, and the object created
  * with that class is registered with a component using the
  * component's <code>addResetActionListener<code> method. When
  * the resetAction event occurs, that object's appropriate
  * method is invoked.
  *
  * @see ResetActionEvent
  */
  public static class ClearActionListener extends EventListener<UIPermissionManager> {

   /* (non-Javadoc)
    * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
    */
   public void execute(Event<UIPermissionManager> event) throws Exception {
     UIPermissionManager permissionManager = event.getSource();
     UIFormInputSetWithAction permissionInputSet = permissionManager.getChildById(PERMISSION_INPUT_SET);
     ((UIFormStringInput) permissionInputSet.getChildById(PERMISSION_STRING_INPUT)).setValue("");
     permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).setChecked(false);
     permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).setChecked(false);
     permissionManager.setActions(new String[] {"Save", "Clear"});
      permissionInputSet.setActionInfo(PERMISSION_STRING_INPUT, new String[] { "SelectUser",
          "SelectMember", "AddAny" });
   }
  }

  /**
  * The listener interface for receiving selectUserAction events.
  * The class that is interested in processing a selectUserAction
  * event implements this interface, and the object created
  * with that class is registered with a component using the
  * component's <code>addSelectUserActionListener<code> method. When
  * the selectUserAction event occurs, that object's appropriate
  * method is invoked.
  *
  * @see SelectUserActionEvent
  */
  public static class SelectUserActionListener extends EventListener<UIPermissionManager> {

   /* (non-Javadoc)
    * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
    */
   public void execute(Event<UIPermissionManager> event) throws Exception {
     UIPermissionManager permissionManager = event.getSource();
     UIUserContainer userContainer = permissionManager.createUIComponent(UIUserContainer.class, null, null);
     userContainer.setSelectable(permissionManager);
     userContainer.setSourceComponent(PERMISSION_STRING_INPUT);
     Utils.createPopupWindow(permissionManager, userContainer, USER_SELECTOR_POPUP_WINDOW, 740);
     permissionManager.setPopupId(USER_SELECTOR_POPUP_WINDOW);
   }
  }

  /**
  * The listener interface for receiving selectMemberAction events.
  * The class that is interested in processing a selectMemberAction
  * event implements this interface, and the object created
  * with that class is registered with a component using the
  * component's <code>addSelectMemberActionListener<code> method. When
  * the selectMemberAction event occurs, that object's appropriate
  * method is invoked.
  *
  * @see SelectMemberActionEvent
  */
  public static class SelectMemberActionListener extends EventListener<UIPermissionManager> {

    /* (non-Javadoc)
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
     */
    public void execute(Event<UIPermissionManager> event) throws Exception {
      UIPermissionManager permissionManager = event.getSource();
      UIGroupMemberSelector groupContainer = permissionManager.createUIComponent(UIGroupMemberSelector.class, null, null);
      groupContainer.setShowAnyPermission(false);
      groupContainer.setSourceComponent(permissionManager, new String[] {PERMISSION_STRING_INPUT});
      Utils.createPopupWindow(permissionManager, groupContainer, GROUP_SELECTOR_POPUP_WINDOW, 600);
      permissionManager.setPopupId(GROUP_SELECTOR_POPUP_WINDOW);
    }
  }

  /**
   * The listener interface for receiving addAnyAction events.
   * The class that is interested in processing a addAnyAction
   * event implements this interface, and the object created
   * with that class is registered with a component using the
   * component's <code>addAddAnyActionListener<code> method. When
   * the addAnyAction event occurs, that object's appropriate
   * method is invoked.
   *
   * @see AddAnyActionEvent
   */
  public static class AddAnyActionListener extends EventListener<UIPermissionManager> {

    /* (non-Javadoc)
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform.webui.event.Event)
     */
    public void execute(Event<UIPermissionManager> event) throws Exception {
      UIPermissionManager permissionManager = event.getSource();
      UIFormInputSetWithAction permisionInputSet = permissionManager.getChildById(PERMISSION_INPUT_SET);
      ((UIFormStringInput) permisionInputSet.getChildById(PERMISSION_STRING_INPUT)).setValue(IdentityConstants.ANY);
      permissionManager.getUICheckBoxInput(ACCESSIBLE_CHECKBOX_INPUT).setChecked(true);
      permissionManager.getUICheckBoxInput(EDITABLE_CHECKBOX_INPUT).setChecked(false);
    }
  }

}
