/*
 * #%L
 * Alfresco Repository
 * %%
 * Copyright (C) 2005 - 2016 Alfresco Software Limited
 * %%
 * This file is part of the Alfresco software. 
 * If the software was purchased under a paid Alfresco license, the terms of 
 * the paid license agreement will prevail.  Otherwise, the software is 
 * provided under the following open source license terms:
 * 
 * Alfresco is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Alfresco 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 Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with Alfresco. If not, see .
 * #L%
 */
package org.alfresco.repo.virtual.bundle;
import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import org.alfresco.repo.version.Version2Model;
import org.alfresco.repo.version.VersionModel;
import org.alfresco.repo.version.VersionServicePolicies.CalculateVersionLabelPolicy;
import org.alfresco.repo.version.common.VersionImpl;
import org.alfresco.repo.version.traitextender.VersionServiceExtension;
import org.alfresco.repo.version.traitextender.VersionServiceTrait;
import org.alfresco.repo.virtual.ref.GetParentReferenceMethod;
import org.alfresco.repo.virtual.ref.NodeProtocol;
import org.alfresco.repo.virtual.ref.Reference;
import org.alfresco.repo.virtual.store.VirtualStore;
import org.alfresco.service.cmr.repository.AspectMissingException;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.version.ReservedVersionNameException;
import org.alfresco.service.cmr.version.Version;
import org.alfresco.service.cmr.version.VersionHistory;
import org.alfresco.service.namespace.QName;
import org.alfresco.traitextender.SpringBeanExtension;
public class VirtualVersionServiceExtension extends SpringBeanExtension
            implements VersionServiceExtension
{
    private VirtualStore smartStore;
    public class VirtualVersionHistory implements VersionHistory
    {
        /**
         * 
         */
        private static final long serialVersionUID = 2640439550254763191L;
        private Reference versionedReference;
        private VersionHistory actualHistory;
        public VirtualVersionHistory(Reference versionedReference, VersionHistory actualHistory)
        {
            super();
            this.versionedReference = versionedReference;
            this.actualHistory = actualHistory;
        }
        @Override
        public Version getRootVersion()
        {
            Version actualRootVersion = actualHistory.getRootVersion();
            return VirtualVersionServiceExtension.this.virtualizeVersion(versionedReference,
                                                                         actualRootVersion);
        }
        @Override
        public Version getHeadVersion()
        {
            Version actualHeadVersion = actualHistory.getRootVersion();
            return VirtualVersionServiceExtension.this.virtualizeVersion(versionedReference,
                                                                         actualHeadVersion);
        }
        @Override
        public Collection getAllVersions()
        {
            Collection allActualVersions = actualHistory.getAllVersions();
            return VirtualVersionServiceExtension.this.virtualizeVersions(versionedReference,
                                                                          allActualVersions);
        }
        @Override
        public Version getPredecessor(Version version)
        {
            Version actualVersion = VirtualVersionServiceExtension.this.materializeVersionIfReference(version);
            Version actualPredecesor = actualHistory.getPredecessor(actualVersion);
            return VirtualVersionServiceExtension.this.virtualizeVersion(versionedReference,
                                                                         actualPredecesor);
        }
        @Override
        public Collection getSuccessors(Version version)
        {
            Version actualVersion = VirtualVersionServiceExtension.this.materializeVersionIfReference(version);
            Collection actualSuccessors = actualHistory.getSuccessors(actualVersion);
            return VirtualVersionServiceExtension.this.virtualizeVersions(versionedReference,
                                                                          actualSuccessors);
        }
        @Override
        public Version getVersion(String versionLabel)
        {
            Version actualVersion = actualHistory.getVersion(versionLabel);
            return VirtualVersionServiceExtension.this.virtualizeVersion(versionedReference,
                                                                         actualVersion);
        }
    }
    public VirtualVersionServiceExtension()
    {
        super(VersionServiceTrait.class);
    }
    public void setSmartStore(VirtualStore smartStore)
    {
        this.smartStore = smartStore;
    }
    @Override
    public StoreRef getVersionStoreReference()
    {
        return getTrait().getVersionStoreReference();
    }
    @Override
    public boolean isAVersion(NodeRef nodeRef)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.isAVersion(nodeRef);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            return theTrait.isAVersion(materialNode);
        }
    }
    @Override
    public boolean isVersioned(NodeRef nodeRef)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.isVersioned(nodeRef);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            return theTrait.isVersioned(materialNode);
        }
    }
    private Version materializeVersionIfReference(Version virtualVersion)
    {
        NodeRef frozenStateNodeRef = virtualVersion.getFrozenStateNodeRef();
        StoreRef frozenStoreRef = frozenStateNodeRef.getStoreRef();
        NodeRef materialFrozenNodeRef = frozenStateNodeRef;
        if (Reference.isReference(frozenStateNodeRef))
        {
            Reference frozenReference = Reference.fromNodeRef(frozenStateNodeRef);
            materialFrozenNodeRef = smartStore.materialize(frozenReference);
        }
        Map virtualProperties = virtualVersion.getVersionProperties();
        Map actualProperties = new HashMap<>(virtualProperties);
        if (frozenStoreRef.getIdentifier().equals(Version2Model.STORE_ID))
        {
            // V2 version store (eg. workspace://version2Store)
            NodeRef propFrozenNode = (NodeRef) virtualProperties.get(Version2Model.PROP_FROZEN_NODE_REF);
            NodeRef propActualFrozenNode = propFrozenNode;
            if (Reference.isReference(propFrozenNode))
            {
                Reference propFrozenReference = Reference.fromNodeRef(propFrozenNode);
                propActualFrozenNode = smartStore.materialize(propFrozenReference);
            }
            actualProperties.put(Version2Model.PROP_FROZEN_NODE_REF,
                                 propActualFrozenNode);
        }
        else if (frozenStoreRef.getIdentifier().equals(VersionModel.STORE_ID))
        {
            // Deprecated V1 version store (eg.
            // workspace://lightWeightVersionStore)
            String frozenNodeStoreProtocol = (String) virtualProperties
                        .get(VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL);
            String frozenNodeStoreId = (String) virtualProperties.get(VersionModel.PROP_FROZEN_NODE_STORE_ID);
            String frozenNodeId = (String) virtualProperties.get(VersionModel.PROP_FROZEN_NODE_ID);
            NodeRef propFrozenNode = new NodeRef(frozenNodeStoreProtocol,
                                                 frozenNodeStoreId,
                                                 frozenNodeId);
            NodeRef propActualFrozenNode = propFrozenNode;
            if (Reference.isReference(propFrozenNode))
            {
                Reference propFrozenReference = Reference.fromNodeRef(propFrozenNode);
                propActualFrozenNode = smartStore.materialize(propFrozenReference);
            }
            StoreRef propActualStoreRef = propFrozenNode.getStoreRef();
            actualProperties.put(VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL,
                                 propActualStoreRef.getProtocol());
            actualProperties.put(VersionModel.PROP_FROZEN_NODE_STORE_ID,
                                 propActualStoreRef.getIdentifier());
            actualProperties.put(VersionModel.PROP_FROZEN_NODE_ID,
                                 propActualFrozenNode.getId());
        }
        Version actualVersion = new VersionImpl(actualProperties,
                                                materialFrozenNodeRef);
        return actualVersion;
    }
    private Version virtualizeVersion(Reference versionedReference, Version actualVersion)
    {
        if (actualVersion == null)
        {
            return null;
        }
        
        NodeRef frozenStateNodeRef = actualVersion.getFrozenStateNodeRef();
        StoreRef frozenStoreRef = frozenStateNodeRef.getStoreRef();
        Reference parentReference = versionedReference.execute(new GetParentReferenceMethod());
        Reference virtualFrozenReference = NodeProtocol.newReference(frozenStateNodeRef,
                                                                     parentReference);
        Map properties = actualVersion.getVersionProperties();
        Map virtualProperties = new HashMap(properties);
        // Switch VersionStore depending on configured impl
        if (frozenStoreRef.getIdentifier().equals(Version2Model.STORE_ID))
        {
            // V2 version store (eg. workspace://version2Store)
            NodeRef propFrozenNodeRef = (NodeRef) virtualProperties.get(Version2Model.PROP_FROZEN_NODE_REF);
            Reference virtualPropFrozenReference = NodeProtocol.newReference(propFrozenNodeRef,
                                                                             parentReference);
            virtualProperties.put(Version2Model.PROP_FROZEN_NODE_REF,
                                  virtualPropFrozenReference.toNodeRef(propFrozenNodeRef.getStoreRef()));
        }
        else if (frozenStoreRef.getIdentifier().equals(VersionModel.STORE_ID))
        {
            // Deprecated V1 version store (eg.
            // workspace://lightWeightVersionStore)
            String frozenNodeStoreProtocol = (String) virtualProperties
                        .get(VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL);
            String frozenNodeStoreId = (String) virtualProperties.get(VersionModel.PROP_FROZEN_NODE_STORE_ID);
            String frozenNodeId = (String) virtualProperties.get(VersionModel.PROP_FROZEN_NODE_ID);
            StoreRef propFrozenStoreRef = new StoreRef(frozenNodeStoreProtocol,
                                                       frozenNodeStoreId);
            NodeRef propFrozenNode = new NodeRef(propFrozenStoreRef,
                                                 frozenNodeId);
            Reference virtualPropFrozenReference = NodeProtocol.newReference(propFrozenNode,
                                                                             parentReference);
            NodeRef virtualPropFrozenNodeRef = virtualPropFrozenReference.toNodeRef(propFrozenStoreRef);
            virtualProperties.put(VersionModel.PROP_FROZEN_NODE_STORE_PROTOCOL,
                                  propFrozenStoreRef.getProtocol());
            virtualProperties.put(VersionModel.PROP_FROZEN_NODE_STORE_ID,
                                  propFrozenStoreRef.getIdentifier());
            virtualProperties.put(VersionModel.PROP_FROZEN_NODE_ID,
                                  virtualPropFrozenNodeRef.getId());
        }
        return new VersionImpl(virtualProperties,
                               virtualFrozenReference.toNodeRef(frozenStateNodeRef.getStoreRef()));
    }
    private Collection virtualizeVersions(Reference versionedReference, Collection actualVersions)
    {
        Collection virtualizedVersions = new LinkedList<>();
        for (Version actualVersion : actualVersions)
        {
            Version virtualizedVersion = virtualizeVersion(versionedReference,
                                                           actualVersion);
            virtualizedVersions.add(virtualizedVersion);
        }
        return virtualizedVersions;
    }
    @Override
    public Version createVersion(NodeRef nodeRef, Map versionProperties)
                throws ReservedVersionNameException, AspectMissingException
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.createVersion(nodeRef,
                                          versionProperties);
        }
        else
        {
            NodeRef materialNode = smartStore.materializeIfPossible(nodeRef);
            Version actualVersion = theTrait.createVersion(materialNode,
                                                           versionProperties);
            Reference reference = Reference.fromNodeRef(nodeRef);
            return virtualizeVersion(reference,
                                     actualVersion);
        }
    }
    @Override
    public Collection createVersion(NodeRef nodeRef, Map versionProperties,
                boolean versionChildren) throws ReservedVersionNameException, AspectMissingException
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.createVersion(nodeRef,
                                          versionProperties,
                                          versionChildren);
        }
        else
        {
            NodeRef materialNode = smartStore.materializeIfPossible(nodeRef);
            Collection actualVersions = theTrait.createVersion(materialNode,
                                                                        versionProperties,
                                                                        versionChildren);
            Reference reference = Reference.fromNodeRef(nodeRef);
            return virtualizeVersions(reference,
                                      actualVersions);
        }
    }
    @Override
    public Collection createVersion(Collection nodeRefs, Map versionProperties)
                throws ReservedVersionNameException, AspectMissingException
    {
        VersionServiceTrait theTrait = getTrait();
        Collection materialNodeRefs = new LinkedList<>();
        Map materializedNodeRefs = new HashMap<>();
        for (NodeRef nodeRef : nodeRefs)
        {
            if (!Reference.isReference(nodeRef))
            {
                materialNodeRefs.add(nodeRef);
            }
            else
            {
                NodeRef materialNode = smartStore.materializeIfPossible(nodeRef);
                materialNodeRefs.add(materialNode);
                materializedNodeRefs.put(materialNode,
                                         Reference.fromNodeRef(nodeRef));
            }
        }
        Collection versions = theTrait.createVersion(materialNodeRefs,
                                                              versionProperties);
        Collection virtualizedVersions = new LinkedList<>();
        for (Version version : versions)
        {
            NodeRef versionedNodeRef = version.getVersionedNodeRef();
            Reference reference = materializedNodeRefs.get(versionedNodeRef);
            if (reference != null)
            {
                Version virtualizedVersion = virtualizeVersion(reference,
                                                               version);
                virtualizedVersions.add(virtualizedVersion);
            }
            else
            {
                virtualizedVersions.add(version);
            }
        }
        return virtualizedVersions;
    }
    @Override
    public VersionHistory getVersionHistory(NodeRef nodeRef) throws AspectMissingException
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.getVersionHistory(nodeRef);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            VersionHistory actualVersionHistory = theTrait.getVersionHistory(materialNode);
            if (actualVersionHistory == null)
            {
                return null;
            }
            else
            {
                Reference versionedReference = Reference.fromNodeRef(nodeRef);
                return new VirtualVersionHistory(versionedReference,
                                                 actualVersionHistory);
            }
        }
    }
    @Override
    public Version getCurrentVersion(NodeRef nodeRef)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.getCurrentVersion(nodeRef);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            Reference versionedReference = Reference.fromNodeRef(nodeRef);
            Version actualVersion = theTrait.getCurrentVersion(materialNode);
            return virtualizeVersion(versionedReference,
                                     actualVersion);
        }
    }
    @Override
    public void revert(NodeRef nodeRef)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.revert(nodeRef);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            theTrait.revert(materialNode);
        }
    }
    @Override
    public void revert(NodeRef nodeRef, boolean deep)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.revert(nodeRef,
                            deep);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            theTrait.revert(materialNode,
                            deep);
        }
    }
    @Override
    public void revert(NodeRef nodeRef, Version version)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.revert(nodeRef,
                            version);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            Version actualVersion = VirtualVersionServiceExtension.this.materializeVersionIfReference(version);
            theTrait.revert(materialNode,
                            actualVersion);
        }
    }
    @Override
    public void revert(NodeRef nodeRef, Version version, boolean deep)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.revert(nodeRef,
                            version,
                            deep);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            Version actualVersion = VirtualVersionServiceExtension.this.materializeVersionIfReference(version);
            theTrait.revert(materialNode,
                            actualVersion,
                            deep);
        }
    }
    @Override
    public NodeRef restore(NodeRef nodeRef, NodeRef parentNodeRef, QName assocTypeQName, QName assocQName)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.restore(nodeRef,
                                    parentNodeRef,
                                    assocTypeQName,
                                    assocQName);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            return theTrait.restore(materialNode,
                                    parentNodeRef,
                                    assocTypeQName,
                                    assocQName);
        }
    }
    @Override
    public NodeRef restore(NodeRef nodeRef, NodeRef parentNodeRef, QName assocTypeQName, QName assocQName, boolean deep)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            return theTrait.restore(nodeRef,
                                    parentNodeRef,
                                    assocTypeQName,
                                    assocQName,
                                    deep);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            return theTrait.restore(materialNode,
                                    parentNodeRef,
                                    assocTypeQName,
                                    assocQName,
                                    deep);
        }
    }
    @Override
    public void deleteVersionHistory(NodeRef nodeRef) throws AspectMissingException
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.deleteVersionHistory(nodeRef);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            theTrait.deleteVersionHistory(materialNode);
        }
    }
    @Override
    public void deleteVersion(NodeRef nodeRef, Version version)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.deleteVersion(nodeRef,
                                   version);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            Version actualVersion = materializeVersionIfReference(version);
            theTrait.deleteVersion(materialNode,
                                   actualVersion);
        }
    }
    @Override
    public void ensureVersioningEnabled(NodeRef nodeRef, Map versionProperties)
    {
        VersionServiceTrait theTrait = getTrait();
        if (!Reference.isReference(nodeRef))
        {
            theTrait.ensureVersioningEnabled(nodeRef,
                                             versionProperties);
        }
        else
        {
            Reference reference = Reference.fromNodeRef(nodeRef);
            NodeRef materialNode = smartStore.materialize(reference);
            theTrait.ensureVersioningEnabled(materialNode,
                                             versionProperties);
        }
    }
    @Override
    public void registerVersionLabelPolicy(QName typeQName, CalculateVersionLabelPolicy policy)
    {
        getTrait().registerVersionLabelPolicy(typeQName,
                                              policy);
    }
}