/*
 * 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.services.wcm.skin;

import java.util.Iterator;
import java.util.List;

import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.ValueFormatException;
import javax.jcr.query.Query;
import javax.jcr.query.QueryManager;
import javax.jcr.query.QueryResult;
import javax.servlet.ServletContext;

import org.apache.commons.lang.StringUtils;
import org.exoplatform.portal.resource.SkinService;
import org.exoplatform.services.jcr.RepositoryService;
import org.exoplatform.services.jcr.core.ManageableRepository;
import org.exoplatform.services.jcr.ext.common.SessionProvider;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.exoplatform.services.wcm.core.NodeLocation;
import org.exoplatform.services.wcm.core.NodetypeConstant;
import org.exoplatform.services.wcm.core.WCMConfigurationService;
import org.exoplatform.services.wcm.core.WebSchemaConfigService;
import org.exoplatform.services.wcm.portal.LivePortalManagerService;
import org.exoplatform.services.wcm.portal.PortalFolderSchemaHandler;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.picocontainer.Startable;

/**
 * Created by The eXo Platform SAS Author : Hoa.Pham hoa.pham@exoplatform.com
 * Apr 9, 2008
 */
public class XSkinService implements Startable {

  /** The SHARE d_ cs s_ query. */
  private static String           SHARED_CSS_QUERY     = "select * from exo:cssFile where jcr:path like '{path}/%' "
                                                           + "and exo:active='true' and exo:sharedCSS='true' "
                                                           + "and jcr:mixinTypes <> 'exo:restoreLocation' "
                                                           + "order by exo:priority ASC".intern();

  private static String           WEBCONTENT_CSS_QUERY = "select * from exo:cssFile where jcr:path like '{path}/%' "
                                                           + "and exo:active='true' "
                                                           + "order by exo:priority ASC".intern();

  /** The Constant SKIN_PATH_REGEXP. */
  public final static String      SKIN_PATH_REGEXP     = "/(.*)/css/jcr/(.*)/(.*)/(.*).css".intern();

  /** The Constant SKIN_PATH_PATTERN. */
  private final static String     SKIN_PATH_PATTERN    = "/{docBase}/css/jcr/(.*)/(.*)/Stylesheet.css".intern();

  /** The log. */
  private static Log              log                  = ExoLogger.getLogger("wcm:XSkinService");

  /** The schema config service. */
  private WebSchemaConfigService schemaConfigService;

  /** The configuration service. */
  private WCMConfigurationService configurationService;

  private RepositoryService repositoryService;

  /** The skin service. */
  private SkinService skinService ;

  /** The servlet context. */
  private ServletContext servletContext;

  /**
   * Instantiates a new extended skin service to manage skin for web content.
   *
   * @param skinService the skin service
   * @param initializerService the content initializer service. this param makes
   *          sure that the service started after the content initializer
   *          service is started
   * @param schemaConfigService the schema config service
   * @param configurationService the configuration service
   * @param servletContext the servlet context
   * @param repositoryService the repository service
   * @throws Exception the exception
   */
  public XSkinService() throws Exception {
    this.skinService = WCMCoreUtils.getService(SkinService.class);
    this.skinService.addResourceResolver(new WCMSkinResourceResolver(this.skinService));
    this.configurationService = WCMCoreUtils.getService(WCMConfigurationService.class);
    this.schemaConfigService = WCMCoreUtils.getService(WebSchemaConfigService.class);
    this.servletContext = WCMCoreUtils.getService(ServletContext.class);
    this.repositoryService = WCMCoreUtils.getService(RepositoryService.class);
  }

  /**
   * Gets the active style sheet of the specified web content node
   * 
   * @param webcontent the web content node to get style sheet
   * @return the active style sheet
   * @throws Exception the exception
   */
  public String getActiveStylesheet(Node webcontent) throws Exception {
    StringBuffer buffer = new StringBuffer();
    String cssQuery = StringUtils.replaceOnce(WEBCONTENT_CSS_QUERY, "{path}", webcontent.getPath());
    // Need re-login to get session because this node is get from template and the session is not live anymore.
    // If node is version (which is stored in system workspace) we have to login to system workspace to get data
    NodeLocation webcontentLocation = NodeLocation.getNodeLocationByNode(webcontent);
    ManageableRepository repository = repositoryService.getCurrentRepository();
    Session session = null;
    try {
      if (webcontentLocation.getPath().startsWith("/jcr:system"))
        session = repository.getSystemSession(repository.getConfiguration().getSystemWorkspaceName());
      else {
        session = repository.getSystemSession(webcontentLocation.getWorkspace());
      }

      QueryManager queryManager = session.getWorkspace().getQueryManager();
      Query query = queryManager.createQuery(cssQuery, Query.SQL);
      QueryResult queryResult = query.execute();
      NodeIterator iterator = queryResult.getNodes();
      while (iterator.hasNext()) {
        Node registeredCSSFile = iterator.nextNode();
        buffer.append(registeredCSSFile.getNode(NodetypeConstant.JCR_CONTENT)
                                       .getProperty(NodetypeConstant.JCR_DATA)
                                       .getString());
      }
    } catch(Exception e) {
      log.error("Unexpected problem happen when active stylesheet", e);
    } finally {
      if(session != null) session.logout();
    }
    return buffer.toString();
  }

  /**
   * Update portal skin on modify.
   *
   * @param portal the portal
   * @param cssFile the css file
   *
   * @throws Exception the exception
   */
  public void updatePortalSkinOnModify(Node portal, Node cssFile) throws Exception {
    String repository = ((ManageableRepository) portal.getSession().getRepository()).getConfiguration()
                                                                                    .getName();
    String sharedPortalName = configurationService.getSharedPortalName(repository);
    if (sharedPortalName.equals(portal.getName())) {
      addSharedPortalSkin(portal, cssFile, false);
    } else {
      addPortalSkin(portal, cssFile, false);
    }
  }

  /**
   * Update portal skin on remove.
   *
   * @param portal the portal
   * @param cssFile the css file
   *
   * @throws Exception the exception
   */
  public void updatePortalSkinOnRemove(Node portal, Node cssFile) throws Exception {
    String repository = ((ManageableRepository) portal.getSession().getRepository()).getConfiguration()
                                                                                    .getName();
    String sharedPortalName = configurationService.getSharedPortalName(repository);
    if (sharedPortalName.equals(portal.getName())) {
      addSharedPortalSkin(portal, cssFile, false);
    } else {
      addPortalSkin(portal, cssFile, false);
    }
  }

  /**
   * Adds the portal skin.
   *
   * @param portalNode the portal
   * @param cssFile the css file
   * @param isStartup the flag to decide whether this situation is startup or not
   *
   * @throws Exception the exception
   */
  private void addPortalSkin(Node portalNode, Node cssFile, boolean isStartup) throws Exception {
    String cssData = mergeCSSData(portalNode, cssFile, isStartup);
    String skinPath = StringUtils.replaceOnce(SKIN_PATH_PATTERN, "(.*)", portalNode.getName())
                                 .replaceFirst("\\{docBase\\}",
                                               servletContext.getServletContextName());
    Iterator<String> iterator = skinService.getAvailableSkinNames().iterator();
    if (iterator.hasNext() == false) {
      skinPath = StringUtils.replaceOnce(skinPath,"(.*)", "Default");
      skinService.addSkin(portalNode.getName(), "Default", skinPath, cssData);
    } else {
      while (iterator.hasNext()) {
        String skinName = iterator.next();
        skinPath = StringUtils.replaceOnce(skinPath,"(.*)",skinName);
        skinService.addSkin(portalNode.getName(), skinName, skinPath, cssData);
      }
    }
  }

  /**
   * Adds the shared portal skin.
   *
   * @param portalNode the portal
   * @param cssFile the css file
   * @param isAddNew flag to decide whether this situation is startup or not
   *
   * @throws Exception the exception
   */
  private void addSharedPortalSkin(Node portalNode, Node cssFile, boolean isAddNew) throws Exception {
    String cssData = mergeCSSData(portalNode, cssFile, isAddNew);
    String skinPath = StringUtils.replaceOnce(SKIN_PATH_PATTERN, "(.*)", portalNode.getName())
                                 .replaceFirst("\\{docBase\\}",
                                               servletContext.getServletContextName());
    for (Iterator<String> iterator = skinService.getAvailableSkinNames().iterator(); iterator.hasNext();) {
      String skinName = iterator.next();
      skinPath = StringUtils.replaceOnce(skinPath, "(.*)", skinName);
      skinService.addPortalSkin(portalNode.getName(), skinName, skinPath, cssData);
    }
  }

  /**
   * Merge the registered css and new css file
   * 
   * @param portalNode the portal
   * @param newCSSFile new css file
   * @param isStartup flag to decide whether this situation is startup or not
   * @return the merged css data as result of registered css and new css
   * @throws Exception the exception
   */
  private String mergeCSSData(Node portalNode, Node newCSSFile, boolean isStartup) throws Exception {
    StringBuffer buffer = new StringBuffer();

    // Get all css by query
    Node cssFolder = schemaConfigService.getWebSchemaHandlerByType(PortalFolderSchemaHandler.class)
                                        .getCSSFolder(portalNode);
    String statement = StringUtils.replaceOnce(SHARED_CSS_QUERY, "{path}", cssFolder.getPath());
    SessionProvider sessionProvider = SessionProvider.createSystemProvider();
    NodeLocation portalNodeLocation = NodeLocation.getNodeLocationByNode(portalNode);
    ManageableRepository repository = repositoryService.getCurrentRepository();
    Session session = sessionProvider.getSession(portalNodeLocation.getWorkspace(), repository);
    try {
      QueryManager queryManager = session.getWorkspace().getQueryManager();
      Query query = queryManager.createQuery(statement, Query.SQL);
      QueryResult queryResult = query.execute();
      NodeIterator iterator = queryResult.getNodes();

      if (isStartup) {
        while (iterator.hasNext()) {
          Node registeredCSSFile = iterator.nextNode();
          buffer.append(getActivedCSSData(registeredCSSFile));
        }
      } else if (!iterator.hasNext()) {
        buffer.append(getActivedCSSData(newCSSFile));
      } else {      
        boolean isApplied = false;
        while(iterator.hasNext()) {
          
          Node registeredCSSFile = iterator.nextNode();
          if (newCSSFile != null) {
            // Modify
            if (!isApplied && newCSSFile.getPath().equals(registeredCSSFile.getPath())) {
              buffer.append(getActivedCSSData(newCSSFile));
              isApplied = true;
              continue;
            }
            
            // Add new
            long newCSSFilePriority = newCSSFile.getProperty(NodetypeConstant.EXO_PRIORITY).getLong();
            long registeredCSSFilePriority = registeredCSSFile.getProperty(NodetypeConstant.EXO_PRIORITY).getLong();
            if (!isApplied && newCSSFilePriority < registeredCSSFilePriority) {
              buffer.append(getActivedCSSData(newCSSFile));
              isApplied = true;
            }
          }
          buffer.append(getActivedCSSData(registeredCSSFile));
        }     
        if (!isApplied) {
          buffer.append(getActivedCSSData(newCSSFile));
        }
      }
    } catch(Exception e) {
      log.error("Unexpected problem happen when merge CSS data", e);
    } finally {
      sessionProvider.close();
    }   
    return buffer.toString();
  }
  
  private String getActivedCSSData(Node cssFile) throws ValueFormatException,
                                              RepositoryException,
                                              PathNotFoundException {
    if (!cssFile.isNodeType("exo:restoreLocation") && cssFile.hasNode(NodetypeConstant.JCR_CONTENT)
        && cssFile.getNode(NodetypeConstant.JCR_CONTENT).hasProperty(NodetypeConstant.JCR_DATA)
        && cssFile.hasProperty(NodetypeConstant.EXO_ACTIVE)
        && cssFile.getProperty(NodetypeConstant.EXO_ACTIVE).getBoolean() == true) {

      return cssFile.getNode(NodetypeConstant.JCR_CONTENT)
                   .getProperty(NodetypeConstant.JCR_DATA)
                   .getString();
    }
    return "";
  }

  /* (non-Javadoc)
   * @see org.picocontainer.Startable#start()
   */
  public void start() {
    SessionProvider sessionProvider = SessionProvider.createSystemProvider();
    try {
      LivePortalManagerService livePortalManagerService = WCMCoreUtils.getService(LivePortalManagerService.class);
      Node sharedPortal = livePortalManagerService.getLiveSharedPortal(sessionProvider);
      addSharedPortalSkin(sharedPortal, null, true);
      List<Node> livePortals = livePortalManagerService.getLivePortals(sessionProvider);
      for (Node portal : livePortals) {
        addPortalSkin(portal, null, true);
      }
    } catch (Exception e) {
      log.error("Exception when start XSkinService", e);
    } finally {
      sessionProvider.close();
    }
  }

  /* (non-Javadoc)
   * @see org.picocontainer.Startable#stop()
   */
  public void stop() {
  }

}
