/*
 * Copyright (C) 2003-2009 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.services.wcm.extensions.publication.lifecycle.authoring.ui;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.exoplatform.ecm.utils.lock.LockUtil;
import org.exoplatform.ecm.webui.utils.JCRExceptionManager;
import org.exoplatform.portal.webui.util.Util;
import org.exoplatform.services.ecm.publication.PublicationPlugin;
import org.exoplatform.services.ecm.publication.PublicationService;
import org.exoplatform.services.jcr.access.AccessControlEntry;
import org.exoplatform.services.jcr.access.AccessControlList;
import org.exoplatform.services.jcr.access.PermissionType;
import org.exoplatform.services.jcr.impl.core.NodeImpl;
import org.exoplatform.services.security.Identity;
import org.exoplatform.services.security.IdentityRegistry;
import org.exoplatform.services.wcm.extensions.publication.PublicationManager;
import org.exoplatform.services.wcm.extensions.publication.lifecycle.authoring.AuthoringPublicationConstant;
import org.exoplatform.services.wcm.extensions.publication.lifecycle.impl.LifecyclesConfig.Lifecycle;
import org.exoplatform.services.wcm.extensions.publication.lifecycle.impl.LifecyclesConfig.State;
import org.exoplatform.services.wcm.publication.PublicationDefaultStates;
import org.exoplatform.services.wcm.publication.WCMPublicationService;
import org.exoplatform.services.wcm.publication.lifecycle.stageversion.ui.UIPublicationContainer;
import org.exoplatform.webui.config.annotation.ComponentConfig;
import org.exoplatform.webui.config.annotation.EventConfig;
import org.exoplatform.webui.core.UIApplication;
import org.exoplatform.webui.core.lifecycle.UIFormLifecycle;
import org.exoplatform.webui.event.Event;
import org.exoplatform.webui.event.EventListener;

import javax.jcr.Node;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;


/**
 * Created by The eXo Platform MEA Author : haikel.thamri@exoplatform.com
 */
@ComponentConfig(lifecycle = UIFormLifecycle.class,
template = "app:/groovy/webui/component/explorer/popup/action/UIPublicationPanel.gtmpl",
events = {
  @EventConfig(listeners = UIPublicationPanel.ChangeStateActionListener.class),
  @EventConfig(listeners = UIPublicationPanel.ChangeVersionActionListener.class),
  @EventConfig(listeners = UIPublicationPanel.PreviewVersionActionListener.class),
  @EventConfig(listeners = UIPublicationPanel.RestoreVersionActionListener.class),
  @EventConfig(listeners = UIPublicationPanel.SeeAllVersionActionListener.class)})

public class UIPublicationPanel extends org.exoplatform.services.wcm.publication.lifecycle.stageversion.ui.UIPublicationPanel {

  private static final Log    LOG               = LogFactory.getLog(UIPublicationPanel.class.getName());

  /**
   * Instantiates a new uI publication panel.
   *
   * @throws Exception the exception
   */
  public UIPublicationPanel() throws Exception {
  }

  public void init(Node node) throws Exception {
    String nodeVersionUUID = null;
    super.init(node);
    String currentState = node.getProperty(AuthoringPublicationConstant.CURRENT_STATE).getString();
    if (PublicationDefaultStates.PUBLISHED.equals(currentState) || PublicationDefaultStates.UNPUBLISHED.equals(currentState)
	    || PublicationDefaultStates.OBSOLETE.equals(currentState)) {
      if (node.hasProperty(AuthoringPublicationConstant.LIVE_REVISION_PROP)) {
        nodeVersionUUID = node.getProperty(AuthoringPublicationConstant.LIVE_REVISION_PROP).getString();
      }
      if (StringUtils.isNotEmpty(nodeVersionUUID)) {
        Node revision = this.getRevisionByUUID(nodeVersionUUID);
        this.setCurrentRevision(revision);
      }
    }
  }

  /**
   * The listener interface for receiving draftAction events. The class that is
   * interested in processing a draftAction event implements this interface, and
   * the object created with that class is registered with a component using the
   * component's <code>addDraftActionListener</code> method. When
   * the draftAction event occurs, that object's appropriate
   * method is invoked.
   */
  public static class ChangeStateActionListener extends EventListener<UIPublicationPanel> {

    /*
     * (non-Javadoc)
     * @see org.exoplatform.webui.event.EventListener#execute(org.exoplatform
     * .webui.event.Event)
     */
    public void execute(Event<UIPublicationPanel> event) throws Exception {
      UIPublicationPanel publicationPanel = event.getSource();
      String state = event.getRequestContext().getRequestParameter(OBJECTID) ;
      Node currentNode = publicationPanel.getCurrentNode();     

      PublicationService publicationService = publicationPanel.getApplicationComponent(PublicationService.class);
      WCMPublicationService wcmPublicationService = publicationPanel.getApplicationComponent(WCMPublicationService.class);
      PublicationPlugin publicationPlugin = publicationService.getPublicationPlugins()
          .get(AuthoringPublicationConstant.LIFECYCLE_NAME);
      HashMap<String, String> context = new HashMap<String, String>();
      Node currentRevision = publicationPanel.getCurrentRevision();
      if (currentRevision != null) {
        context.put(AuthoringPublicationConstant.CURRENT_REVISION_NAME, currentRevision.getName());
      }
      try {
        if(currentNode.isLocked()) {
          currentNode.getSession().addLockToken(LockUtil.getLockToken(currentNode));
        }

        publicationPlugin.changeState(currentNode, state, context);
        currentNode.setProperty("publication:lastUser", event.getRequestContext().getRemoteUser());

        String nodeVersionUUID = null;
        String currentState = currentNode.getProperty(AuthoringPublicationConstant.CURRENT_STATE).getString();
        if (PublicationDefaultStates.PUBLISHED.equals(currentState) || PublicationDefaultStates.UNPUBLISHED.equals(currentState)
	        || PublicationDefaultStates.OBSOLETE.equals(currentState)) {
          if(currentNode.hasProperty(AuthoringPublicationConstant.LIVE_REVISION_PROP)){
            nodeVersionUUID = currentNode.getProperty(AuthoringPublicationConstant.LIVE_REVISION_PROP).getString();
          }
          if (nodeVersionUUID != null && !nodeVersionUUID.isEmpty()) {
            publicationPanel.setCurrentRevision(publicationPanel.getRevisionByUUID(nodeVersionUUID));
          }
        }
        String siteName = Util.getPortalRequestContext().getPortalOwner();
        String remoteUser = Util.getPortalRequestContext().getRemoteUser();
        wcmPublicationService.updateLifecyleOnChangeContent(currentNode, siteName, remoteUser, state);
        publicationPanel.updatePanel();
      } catch (Exception e) {
        UIApplication uiApp = publicationPanel.getAncestorOfType(UIApplication.class);
        JCRExceptionManager.process(uiApp, e);
      }
      UIPublicationContainer publicationContainer = publicationPanel.getAncestorOfType(UIPublicationContainer.class);
      publicationContainer.setActiveTab(publicationPanel, event.getRequestContext());
    }
  }

  public List<State> getStates(Node cNode) throws Exception {
    List<State> states = new ArrayList<State>();
    String lifecycleName = getLifeCycle(cNode);
    PublicationManager publicationManagerImpl = getApplicationComponent(PublicationManager.class);
    Lifecycle lifecycle = publicationManagerImpl.getLifecycle(lifecycleName);
    states = lifecycle.getStates();
    return states;
  }

  private String getLifeCycle(Node cNode) throws Exception {
    String lifecycleName = null;
    try {
      lifecycleName = cNode.getProperty("publication:lifecycle").getString();
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Failed to get States for node " + cNode, e);
      }
    }
    return lifecycleName;
  }

  /**
   * Check if a user is authorized to reach the given state of a given  node.
   * The user must satisfy the constraints defined by state (memberships or role)
   * @param state
   * @param remoteUser
   * @param node
   * @return
   */
  public boolean canReachState(State state, String remoteUser, NodeImpl node) {

    IdentityRegistry identityRegistry = getApplicationComponent(IdentityRegistry.class);
    Identity currentUser = identityRegistry.getIdentity(remoteUser);

    if (isAuthorizedByMembership(state, currentUser)) {
      return true;
    }

    if (isAuthorizedByRole(state, currentUser, node)) {
      return true;
    }

    return false;
  }

  /**
   * Check if the user has the memberships defined in the state
   * @param state
   * @param currentUser
   * @return
   */
  boolean isAuthorizedByMembership(State state, Identity currentUser) {
    String membership = state.getMembership();
    List<String> memberships = new ArrayList<String>();
    if (membership != null) {
      memberships.add(membership);
    }
    if (state.getMemberships() != null) {
      memberships.addAll(state.getMemberships());
    }

    for (String membership_ : memberships) {
      String[] membershipTab = membership_.split(":");
      String expectedRole = membershipTab[0];
      String expectedGroup = membershipTab[1];
      if (currentUser.isMemberOf(expectedGroup, expectedRole)) {
        return true;
      }
    }
    return false;
  }
 
  /**
   * Check if a user is authorized to reach the state based on the state's role.
   * The user must have the role
   * @param state
   * @param currentUser
   * @param node
   * @return
   */
  boolean isAuthorizedByRole(State state, Identity currentUser, NodeImpl node) {
    try {
      String role_ = state.getRole();

      List<String> roles = new ArrayList<String>();
      if (role_ != null) {
        roles.add(role_);
      }
      if (state.getRoles() != null) {
        roles.addAll(state.getRoles());
      }
      for (String role : roles) {

        AccessControlList acl = node.getACL();
        if (acl.hasPermissions()) {
          List<AccessControlEntry> entries = acl.getPermissionEntries();
          for (AccessControlEntry accessControlEntry : entries) {
            String identity = accessControlEntry.getIdentity();
            if (identity.indexOf(':') > 0) {
              // write access on node is defined by 'set_property' in exo JCR
              if (PermissionType.SET_PROPERTY.equals(accessControlEntry.getPermission())) {
                String authorizedGroup = identity.split(":")[1];
                // user must have the configured role in one of the node's
                // authorized groups
                if (currentUser.isMemberOf(authorizedGroup, role)) {
                  return true;
                }
              }
            }
          }
        }

      }

    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Failed to extract node permissions", e);
      }
    }
    return false;
  }

}
