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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.jcr.AccessDeniedException;
import javax.jcr.Credentials;
import javax.jcr.InvalidItemStateException;
import javax.jcr.InvalidSerializedDataException;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.LoginException;
import javax.jcr.NamespaceException;
import javax.jcr.Node;
import javax.jcr.PathNotFoundException;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.ValueFactory;
import javax.jcr.Workspace;
import javax.jcr.lock.LockException;
import javax.jcr.nodetype.ConstraintViolationException;
import javax.jcr.nodetype.NoSuchNodeTypeException;
import javax.jcr.version.VersionException;

import org.exoplatform.services.jcr.core.ExtendedSession;
import org.exoplatform.services.jcr.core.NamespaceAccessor;
import org.exoplatform.services.jcr.core.SessionLifecycleListener;
import org.exoplatform.services.jcr.impl.core.LocationFactory;
import org.exoplatform.services.log.ExoLogger;
import org.exoplatform.services.log.Log;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

/**
 * Created by The eXo Platform SAS
 * Author : eXoPlatform
 *          nicolas.filotto@exoplatform.com
 * 4 avr. 2009  
 */
public class SessionLinkAware implements ExtendedSession, NamespaceAccessor {

  /**
   * Logger.
   */
  private static final Log LOG  = ExoLogger.getLogger("services.cms.link.SessionLinkAware");
  
  private ItemLinkAware itemLA;
  
  private final ExtendedSession originalSession;
  private final ExtendedSession currentNodeSession;
  
  private volatile ExtendedSession targetNodeSession;  
  private volatile ExtendedSession[] sessions;
  
  SessionLinkAware (ItemLinkAware itemLA) throws RepositoryException {
    this.itemLA = itemLA;
    this.originalSession = (ExtendedSession) itemLA.originalSession;
    this.currentNodeSession = (ExtendedSession) itemLA.item.getSession();
  }
  
  private ExtendedSession[] getSessions() throws RepositoryException {
    if (sessions == null) {
      synchronized (this) {
        if (sessions == null) {
          Set<ExtendedSession> sSessions = new HashSet<ExtendedSession>(3, 1f);
          sSessions.add(originalSession);
          sSessions.add(currentNodeSession);
          sSessions.add(getTargetSession());
          sessions = (ExtendedSession[]) sSessions.toArray(new ExtendedSession[sSessions.size()]);
        }
      }
    }
    return sessions;
  }
  
  private ExtendedSession getTargetSession() throws RepositoryException {
    if (targetNodeSession == null) {
      synchronized (this) {
        if (targetNodeSession == null) {
          targetNodeSession = getTargetSession(itemLA);
          itemLA = null;
        }
      }
    }
    return targetNodeSession;
  }
  
  private ExtendedSession getTargetSession(String absPath, Item item) throws RepositoryException {
    return getTargetSession(ItemLinkAware.newInstance(originalSession, absPath, item));
  }
  
  private ExtendedSession getTargetSession(ItemLinkAware itemLA) throws RepositoryException {
    if (itemLA instanceof NodeLinkAware) {
      return (ExtendedSession) ((NodeLinkAware) itemLA).getTargetNode().getRealNode().getSession();
    } else {
      return (ExtendedSession) itemLA.item.getSession();
    }
  }
  
  private ExtendedSession getTargetSession(String absPath) throws RepositoryException {
    Item item = getItem(absPath);    
    return getTargetSession(absPath, item);
  }
  
  /**
   * {@inheritDoc}
   */  
  public void addLockToken(String lt) {
    try {
      getTargetSession().addLockToken(lt);
    } catch (RepositoryException e) {
      LOG.error(e);
    }
  }

  /**
   * {@inheritDoc}
   */  
  public void checkPermission(String absPath, String actions) throws AccessControlException,
                                                       RepositoryException {
    getTargetSession(absPath).checkPermission(absPath, actions);
  }

  /**
   * {@inheritDoc}
   */  
  public void exportDocumentView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws PathNotFoundException,
                                                                                              SAXException,
                                                                                              RepositoryException {
    getTargetSession(absPath).exportDocumentView(absPath, contentHandler, skipBinary, noRecurse);
  }

  /**
   * {@inheritDoc}
   */  
  public void exportDocumentView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException,
                                                                                            PathNotFoundException,
                                                                                            RepositoryException {
    getTargetSession(absPath).exportDocumentView(absPath, out, skipBinary, noRecurse);
  }

  /**
   * {@inheritDoc}
   */  
  public void exportSystemView(String absPath, ContentHandler contentHandler, boolean skipBinary, boolean noRecurse) throws PathNotFoundException,
                                                                                            SAXException,
                                                                                            RepositoryException {
    getTargetSession(absPath).exportSystemView(absPath, contentHandler, skipBinary, noRecurse);
  }

  /**
   * {@inheritDoc}
   */  
  public void exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse) throws IOException,
                                                                                          PathNotFoundException,
                                                                                          RepositoryException {
    getTargetSession(absPath).exportSystemView(absPath, out, skipBinary, noRecurse);
  }

  /**
   * {@inheritDoc}
   */  
  public Object getAttribute(String name) {
    try {
      return getTargetSession().getAttribute(name);
    } catch (RepositoryException e) {
      LOG.error(e);
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */  
  public String[] getAttributeNames() {
    try {
      return getTargetSession().getAttributeNames();
    } catch (RepositoryException e) {
      LOG.error(e);
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */  
  public ContentHandler getImportContentHandler(String parentAbsPath, int uuidBehavior) throws PathNotFoundException,
                                                                      ConstraintViolationException,
                                                                      VersionException,
                                                                      LockException,
                                                                      RepositoryException {
    return getTargetSession(parentAbsPath).getImportContentHandler(parentAbsPath, uuidBehavior);
  }

  /**
   * {@inheritDoc}
   */  
  public Item getItem(String absPath) throws PathNotFoundException, RepositoryException {
    NodeFinder nodeFinder = LinkUtils.getNodeFinder();
    return nodeFinder.getItem(originalSession, absPath);
  }

  /**
   * {@inheritDoc}
   */  
  public String[] getLockTokens() {
    try {
      return getTargetSession().getLockTokens();
    } catch (RepositoryException e) {
      LOG.error(e);
    }
    return null;
  }

  /**
   * {@inheritDoc}
   */  
  public String getNamespacePrefix(String uri) throws NamespaceException, RepositoryException {
    return getTargetSession().getNamespacePrefix(uri);
  }

  /**
   * {@inheritDoc}
   */  
  public String[] getNamespacePrefixes() throws RepositoryException {
    return getTargetSession().getNamespacePrefixes();
  }

  /**
   * {@inheritDoc}
   */  
  public String getNamespaceURI(String prefix) throws NamespaceException, RepositoryException {
    return getTargetSession().getNamespaceURI(prefix);
  }

  /**
   * {@inheritDoc}
   */  
  public Node getNodeByUUID(String uuid) throws ItemNotFoundException, RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      try {
        return session.getNodeByUUID(uuid);
      } catch (ItemNotFoundException e) {
        // do nothing      
      }
    }
    throw new ItemNotFoundException("No node with uuid ='" + uuid + "' can be found");
  }

  /**
   * {@inheritDoc}
   */  
  public Repository getRepository() {
    return originalSession.getRepository();
  }

  /**
   * {@inheritDoc}
   */  
  public Node getRootNode() throws RepositoryException {
    return originalSession.getRootNode();
  }

  /**
   * {@inheritDoc}
   */  
  public String getUserID() {
    return originalSession.getUserID();
  }

  /**
   * {@inheritDoc}
   */  
  public ValueFactory getValueFactory() throws UnsupportedRepositoryOperationException,
                                       RepositoryException {
    return originalSession.getValueFactory();
  }

  /**
   * {@inheritDoc}
   */  
  public Workspace getWorkspace() {
    return originalSession.getWorkspace();
  }

  /**
   * {@inheritDoc}
   */  
  public boolean hasPendingChanges() throws RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      if (session.hasPendingChanges()) {
        return true;
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */  
  public Session impersonate(Credentials credentials) throws LoginException, RepositoryException {
    return originalSession.impersonate(credentials);
  }

  /**
   * {@inheritDoc}
   */  
  public void importXML(String parentAbsPath, InputStream in, int uuidBehavior) throws IOException,
                                                                PathNotFoundException,
                                                                ItemExistsException,
                                                                ConstraintViolationException,
                                                                VersionException,
                                                                InvalidSerializedDataException,
                                                                LockException,
                                                                RepositoryException {
    getTargetSession(parentAbsPath).importXML(parentAbsPath, in, uuidBehavior);
  }

  /**
   * {@inheritDoc}
   */  
  public boolean isLive() {
    ExtendedSession[] sessions;
    try {
      sessions = getSessions();
    } catch (RepositoryException e) {
      LOG.error(e);
      return false;
    }
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      if (session.isLive()) {
        return true;
      }
    }
    return false;
  }

  /**
   * {@inheritDoc}
   */  
  public boolean itemExists(String absPath) throws RepositoryException {
    NodeFinder nodeFinder = LinkUtils.getNodeFinder();
    return nodeFinder.itemExists(originalSession, absPath);
  }

  /**
   * {@inheritDoc}
   */  
  public void logout() {
    ExtendedSession[] sessions;
    try {
      sessions = getSessions();
    } catch (RepositoryException e) {
      LOG.error(e);
      return;
    }
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      session.logout();
    }
  }

  /**
   * {@inheritDoc}
   */  
  public void move(String srcAbsPath, String destAbsPath) throws ItemExistsException,
                                            PathNotFoundException,
                                            VersionException,
                                            ConstraintViolationException,
                                            LockException,
                                            RepositoryException {
    Item srcItem = getItem(srcAbsPath);
    Session srcSession = getTargetSession(srcAbsPath, srcItem);
    Session destParentSession = getTargetSession(LinkUtils.getParentPath(destAbsPath));
    if (srcSession.getWorkspace().equals(destParentSession.getWorkspace())) {
      srcSession.move(srcAbsPath, srcAbsPath);
    } else {
      destParentSession.getWorkspace().clone(srcSession.getWorkspace().getName(), srcAbsPath, destAbsPath, false);
    }
  }

  /**
   * {@inheritDoc}
   */  
  public void refresh(boolean keepChanges) throws RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      session.refresh(keepChanges);
    }
  }

  /**
   * {@inheritDoc}
   */  
  public void removeLockToken(String lt) {
    try {
      getTargetSession().removeLockToken(lt);
    } catch (RepositoryException e) {
      LOG.error(e);
    }    
  }

  /**
   * {@inheritDoc}
   */  
  public void save() throws AccessDeniedException,
                    ItemExistsException,
                    ConstraintViolationException,
                    InvalidItemStateException,
                    VersionException,
                    LockException,
                    NoSuchNodeTypeException,
                    RepositoryException {
    ExtendedSession[] sessions = getSessions();
    for (int i = 0, length = sessions.length; i < length; i++) {
      Session session = sessions[i];
      session.save();
    }
  }

  /**
   * {@inheritDoc}
   */  
  public void setNamespacePrefix(String prefix, String uri) throws NamespaceException,
                                                          RepositoryException {
    getTargetSession().setNamespacePrefix(prefix, uri);
  }

  /**
   * {@inheritDoc}
   */  
  public String getId() {
    try {
      return getTargetSession().getId();
    } catch (RepositoryException e) {
      LOG.error(e);
    }
    return null;    
  }

  /**
   * {@inheritDoc}
   */  
  public LocationFactory getLocationFactory() {
    try {
      return getTargetSession().getLocationFactory();
    } catch (RepositoryException e) {
      LOG.error(e);
    }
    return null;    
  }

  /**
   * {@inheritDoc}
   */  
  public void importXML(String parentAbsPath,
                        InputStream in,
                        int uuidBehavior,
                        Map<String, Object> context) throws IOException,
                                                    PathNotFoundException,
                                                    ItemExistsException,
                                                    ConstraintViolationException,
                                                    InvalidSerializedDataException,
                                                    RepositoryException {
    getTargetSession(parentAbsPath).importXML(parentAbsPath, in, uuidBehavior, context);
  }

  /**
   * {@inheritDoc}
   */  
  public void registerLifecycleListener(SessionLifecycleListener listener) {
    try {
      getTargetSession().registerLifecycleListener(listener);
    } catch (RepositoryException e) {
      LOG.error(e);
    }
  }

  /**
   * {@inheritDoc}
   */  
  public String[] getAllNamespacePrefixes() throws RepositoryException {
    return ((NamespaceAccessor) getTargetSession()).getAllNamespacePrefixes();
  }

  /**
   * {@inheritDoc}
   */  
  public String getNamespacePrefixByURI(String uri) throws NamespaceException, RepositoryException {
    return ((NamespaceAccessor) getTargetSession()).getNamespacePrefixByURI(uri);
  }

  /**
   * {@inheritDoc}
   */  
  public String getNamespaceURIByPrefix(String prefix) throws NamespaceException,
                                                      RepositoryException {
    return ((NamespaceAccessor) getTargetSession()).getNamespaceURIByPrefix(prefix);
  }

public Node getNodeByIdentifier(String identifier)
		throws ItemNotFoundException, RepositoryException {
	// TODO Auto-generated method stub
	return null;
}
}
