/*
 * 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.javascript;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

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.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.portal.LivePortalManagerService;
import org.exoplatform.services.wcm.utils.WCMCoreUtils;
import org.exoplatform.web.application.javascript.Javascript;
import org.exoplatform.web.application.javascript.JavascriptConfigService;
import org.exoplatform.web.application.javascript.Javascript.PortalJScript;
import org.picocontainer.Startable;

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

  private static String           WEBCONTENT_JS_QUERY = "select * from exo:jsFile "
                                                          + "where jcr:path like '{path}/%' and exo:active='true' "
                                                          + "order by exo:priority ASC";

  /** The MODUL e_ name. */
  final private String MODULE_NAME = "eXo.WCM.Live";

  public final static String JS_PATH_REGEXP = "/(.*)/javascript/eXo/(.*)/live";

  /** The PATH. */
  final private String PATH = "/javascript/eXo/{portalName}/live";

  /** The js config service. */
  private JavascriptConfigService jsConfigService ;

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

  private LivePortalManagerService livePortalManagerService_;

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

  /** The log. */
  private static final Log LOG = ExoLogger.getLogger(XJavascriptService.class.getName());

  private Set<String> loadedJSModule = new HashSet<String>();
  private Set<String> loadedSharedJSModule = new HashSet<String>();

  /**
   * Instantiates a new x javascript service.
   *
   * @param repositoryService the repository service
   * @param jsConfigService the js config service
   * @param servletContext the servlet context
   * @param configurationService the configuration service
   * @param contentInitializerService the content initializer service
   *
   * @throws Exception the exception
   */
  public XJavascriptService(LivePortalManagerService livePortalService) throws Exception{
    this.livePortalManagerService_ = livePortalService;
    this.jsConfigService = WCMCoreUtils.getService(JavascriptConfigService.class);
    this.jsConfigService.addResourceResolver(
      new WCMJavascriptResourceResolver(livePortalManagerService_, jsConfigService));
    this.configurationService = WCMCoreUtils.getService(WCMConfigurationService.class);
    this.servletContext = WCMCoreUtils.getService(ServletContext.class);
  }

  /**
   * Get active java script.
   *
   * @param webcontent the webcontent's node
   * @return Code of all js file in home node.
   * @throws Exception the exception
   */
  public String getActiveJavaScript(Node webcontent) throws Exception {
    StringBuffer buffer = new StringBuffer();
    String jsQuery = StringUtils.replaceOnce(WEBCONTENT_JS_QUERY, "{path}", webcontent.getPath());

    // Need re-login to get session because this node is get from template and the session is not live anymore.
    NodeLocation webcontentLocation = NodeLocation.getNodeLocationByNode(webcontent);
    RepositoryService repositoryService = WCMCoreUtils.getService(RepositoryService.class);
    ManageableRepository repository = repositoryService.getCurrentRepository();
    Session session = null;
    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(jsQuery, Query.SQL);
    QueryResult queryResult = query.execute();
    NodeIterator iterator = queryResult.getNodes();
    while(iterator.hasNext()) {
      Node registeredJSFile = iterator.nextNode();
      buffer.append(getActivedJSData(registeredJSFile));
    }
    session.logout();
    return buffer.toString();
  }

  /**
   * Update and merged all Java Script in all portal when content of js file is modified.
   *
   * @param jsFile the js file
   * @param sessionProvider the session provider
   *
   * @throws Exception the exception
   */
  public void updatePortalJSOnModify(Node portalNode, Node jsFile) throws Exception {
    String repository = ((ManageableRepository) portalNode.getSession().getRepository()).getConfiguration()
                                                                                        .getName();
    String sharedPortalName = configurationService.getSharedPortalName(repository);
    if(sharedPortalName.equals(portalNode.getName())) {
      addSharedPortalJavascript(portalNode, jsFile, false);
    }else {
      addPortalJavascript(portalNode, jsFile, false);
    }
  }

  /**
   * Update and merged all Java Script in all portal when content of js file is modified.
   *
   * @param jsFile the js file
   * @param sessionProvider the session provider
   *
   * @throws Exception the exception
   */
  public void updatePortalJSOnRemove(Node portalNode, Node jsFile) throws Exception {
    String repository = ((ManageableRepository) portalNode.getSession().getRepository()).getConfiguration()
                                                                                        .getName();
    String sharedPortalName = configurationService.getSharedPortalName(repository);
    if(sharedPortalName.equals(portalNode.getName())) {
      addSharedPortalJavascript(portalNode, jsFile, false);
    }else {
      addPortalJavascript(portalNode, jsFile, false);
    }
  }

  /**
   * Adds the javascript.
   *
   * @param jsData the js data
   */
  private void addPortalJavascript(Node portalNode, Node jsFile, boolean isStartup) throws Exception {
    String javascriptPath = StringUtils.replaceOnce(PATH, "{portalName}", portalNode.getName());

    String moduleName = MODULE_NAME + '.' + portalNode.getName();
    jsConfigService.invalidateCachedJScript("/" + servletContext.getServletContextName() +
                                            javascriptPath);
    if (!loadedJSModule.contains(moduleName)) {
      loadedJSModule.add(moduleName);
      jsConfigService.addPortalJScript(
        new PortalJScript(moduleName, javascriptPath,"/" + servletContext.getServletContextName(),
                          10, portalNode.getName()));
    }
  }

  /**
   * Adds the javascript.
   *
   * @param jsData the js data
   */
  private void addSharedPortalJavascript(Node portalNode, Node jsFile, boolean isStartup) throws Exception {
    String javascriptPath = StringUtils.replaceOnce(PATH, "{portalName}", portalNode.getName());
    String moduleName = MODULE_NAME + '.' + portalNode.getName();
    jsConfigService.invalidateMergedCommonJScripts();
    jsConfigService.invalidateCachedJScript("/" + servletContext.getServletContextName() +
                                            javascriptPath);
    if (!loadedSharedJSModule.contains(moduleName)) {
      loadedSharedJSModule.add(moduleName);
      jsConfigService.addCommonJScript(
         new Javascript(moduleName, javascriptPath,"/" + servletContext.getServletContextName(), 10));
    }
  }

  private String getActivedJSData(Node jsFile) throws ValueFormatException,
                                          RepositoryException,
                                          PathNotFoundException {
    if (jsFile != null && !jsFile.isNodeType("exo:restoreLocation")
        && jsFile.hasNode(NodetypeConstant.JCR_CONTENT)
        && jsFile.getNode(NodetypeConstant.JCR_CONTENT).hasProperty(NodetypeConstant.JCR_DATA)
        && jsFile.hasProperty(NodetypeConstant.EXO_ACTIVE)
        && jsFile.getProperty(NodetypeConstant.EXO_ACTIVE).getBoolean() == true) {

      return jsFile.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);
      List<Node> livePortals = livePortalManagerService.getLivePortals(sessionProvider);
      for(Node portal: livePortals) {
        addPortalJavascript(portal, null, true);
      }
      Node sharedPortal = livePortalManagerService.getLiveSharedPortal(sessionProvider);
      addSharedPortalJavascript(sharedPortal, null, true);
    } catch (PathNotFoundException e) {
      if (LOG.isWarnEnabled()) {
        LOG.warn("Exception when merging inside Portal : WCM init is not completed.");
      }
    } catch (Exception e) {
      if (LOG.isErrorEnabled()) {
        LOG.error("Exception when start XJavascriptService");
      }
    } finally {
      sessionProvider.close();
    }
  }

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