/*
 * Copyright (C) 2005-2012 Alfresco Software Limited.
 *
 * This file is part of Alfresco
 *
 * 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 . 
 */
package org.alfresco.repo.avm;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.alfresco.config.JNDIConstants;
import org.alfresco.model.ContentModel;
import org.alfresco.model.WCMModel;
import org.alfresco.repo.action.ActionImpl;
import org.alfresco.repo.avm.actions.AVMRevertListAction;
import org.alfresco.repo.avm.actions.AVMRevertStoreAction;
import org.alfresco.repo.avm.actions.AVMRevertToVersionAction;
import org.alfresco.repo.avm.actions.AVMUndoSandboxListAction;
import org.alfresco.repo.avm.actions.SimpleAVMPromoteAction;
import org.alfresco.repo.avm.actions.SimpleAVMSubmitAction;
import org.alfresco.repo.avm.util.BulkLoader;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.domain.PropertyValue;
import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.cmr.avm.AVMBadArgumentException;
import org.alfresco.service.cmr.avm.AVMCycleException;
import org.alfresco.service.cmr.avm.AVMException;
import org.alfresco.service.cmr.avm.AVMExistsException;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMNotFoundException;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.avm.AVMStoreDescriptor;
import org.alfresco.service.cmr.avm.LayeringDescriptor;
import org.alfresco.service.cmr.avm.VersionDescriptor;
import org.alfresco.service.cmr.avmsync.AVMDifference;
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.remote.RepoRemote;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.CrossRepositoryCopyService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.search.ResultSet;
import org.alfresco.service.cmr.search.ResultSetRow;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.test_category.LegacyCategory;
import org.alfresco.util.GUID;
import org.alfresco.util.Pair;
import org.alfresco.wcm.sandbox.SandboxConstants;
import org.junit.experimental.categories.Category;
/**
 * Big test of AVM behavior.
 *
 * @author britt
 */
@Category(LegacyCategory.class)
public class AVMServiceTest extends AVMServiceTestBase
{
    public void testSetup() throws Exception
    {
        super.testSetup();
    }
    
    public void testSetupAndQueriesAgainstBasicTree() throws Exception
    {
        setupBasicTree();
        runQueriesAgainstBasicTree("main");
    }
    
    public void testDiffOrder() throws IOException
    {
        try
        {
            fService.createStore("Bottom");
            fService.createStore("Top");
            fService.createDirectory("Bottom:/", "www");
            fService.createLayeredDirectory("Bottom:/www", "Top:/", "www");
            fService.createFile("Bottom:/www", "newInBottom").close();
            fService.createSnapshot("Bottom", null, null);
            fService.createFile("Top:/www", "newInTop").close();
            fService.createSnapshot("Top", null, null);
            fService.createFile("Bottom:/www", "file").close();
            fService.createSnapshot("Bottom", null, null);
            fService.forceCopy("Top:/www/file");
            fService.createSnapshot("Top", null, null);
            fService.forceCopy("Bottom:/www/file");
            fService.createSnapshot("Bottom", null, null);
           
            List diffs = fSyncService.compare(-1, "Top:/", -1, "Bottom:/", null);
            assertEquals(
                    2, diffs.size());
            Collections.sort(diffs);
            AVMDifference last = null;
            for(AVMDifference current : diffs)
            {
                if(last != null)
                {
                    assert(last.getOrderValue() < current.getOrderValue());
                }
                last = current;
            }
            
            diffs.add(new AVMDifference(1, null, -1, null, 0));
            diffs.add(new AVMDifference(1, null, -1, null, 1));
            diffs.add(new AVMDifference(1, null, -1, null, 2));
            diffs.add(new AVMDifference(1, null, -1, null, 3));
            diffs.add(new AVMDifference(1, null, -1, null, 4));
            diffs.add(new AVMDifference(1, null, -1, null, 5));
            diffs.add(new AVMDifference(1, null, -1, null, 6));
            
            Collections.sort(diffs);
            last = null;
            for(AVMDifference current : diffs)
            {
                if(last != null)
                {
                    assert(last.getOrderValue() < current.getOrderValue());
                }
                last = current;
            }
        }
        finally
        {
            fService.purgeStore("Bottom");
            fService.purgeStore("Top");
        }
    }
    private enum DiffActionEnum
    {
        CREATION, MODIFICATION, DELETION, DELETION_AND_MODIFICATION
    }
    /**
     * Test is related to ALF-4098
     * 
     * @throws Exception
     */
    public void testDiffOfNewItems() throws Exception
    {
        try
        {
            performeDiffTesting(DiffActionEnum.CREATION);
        }
        finally
        {
            fService.purgeStore("testStore");
            fService.purgeStore("submitStore");
        }
    }
    /**
     * Test is related to ALF-4098
     * 
     * @throws Exception
     */
    public void testDiffOfModifiedItems() throws Exception
    {
        try
        {
            performeDiffTesting(DiffActionEnum.MODIFICATION);
        }
        finally
        {
            fService.purgeStore("testStore");
            fService.purgeStore("submitStore");
        }
    }
    /**
     * Test is related to ALF-4098
     * 
     * @throws Exception
     */
    public void testDiffOfDeletedItems() throws Exception
    {
        try
        {
            performeDiffTesting(DiffActionEnum.DELETION);
        }
        finally
        {
            fService.purgeStore("testStore");
            fService.purgeStore("submitStore");
        }
    }
    public void testDiffOfDeletedItemsInModifiedDirectory() throws Exception
    {
        try
        {
            performeDiffTesting(DiffActionEnum.DELETION_AND_MODIFICATION);
        }
        finally
        {
            fService.purgeStore("testStore");
            fService.purgeStore("submitStore");
        }
    }
    private void performeDiffTesting(DiffActionEnum action) throws IOException
    {
        fService.createStore("testStore");
        fService.createStore("submitStore");
        fService.createDirectory("submitStore:/", "root");
        fService.createLayeredDirectory("submitStore:/root", "testStore:/", "root");
        fService.createSnapshot("testStore", null, null);
        fService.createDirectory("testStore:/root", "test");
        for (int i = 0; i < 10; i++)
        {
            fService.createFile("testStore:/root/test", ("testFileN" + i + ".txt")).close();
        }
        List diffs = fSyncService.compare(-1, "testStore:/root/", -1, "submitStore:/root/", null);
        if (DiffActionEnum.CREATION != action)
        {
            fSyncService.update(diffs, null, true, true, false, true, null, null);
            fSyncService.flatten("testStore:/root/", "submitStore:/root/");
            diffs = fSyncService.compare(-1, "testStore:/root/", -1, "submitStore:/root/", null);
        }
        List actual = fSyncService.compare(-1, "testStore:/root/", -1, "submitStore:/root/", null, true);
        if (DiffActionEnum.CREATION == action)
        {
            assertEquals(11, actual.size());
            assertEquals(1, diffs.size());
            List newDiff = fSyncService.compare(-1, "testStore:/root/", -1, "submitStore:/root/", null, false);
            assertEquals(diffs.toString(), newDiff.toString());
        }
        else
        {
            assertEquals(0, actual.size());
            assertEquals(0, diffs.size());
        }
        String parentPath = "testStore:/root/test/";
        if (DiffActionEnum.CREATION == action)
        {
            fService.createDirectory(parentPath, "inner");
            parentPath += "inner/";
        }
        int start = (DiffActionEnum.DELETION == action) ? (1) : (0);
        int incrementingStep = start + 1;
        for (int i = start; i < 10; i += incrementingStep)
        {
            String name = "testFileN" + i + ".txt";
            String path = parentPath + name;
            switch (action)
            {
            case CREATION:
            {
                fService.createFile(parentPath, name).close();
                break;
            }
            case MODIFICATION:
            {
                fService.setNodeProperty(path, WCMModel.PROP_REVERTED_ID, new PropertyValue(WCMModel.PROP_REVERTED_ID, null));
                break;
            }
            default:
            {
                fService.removeNode(path);
            }
            }
        }
        int actualModificationsCount = (DiffActionEnum.DELETION == action) ? (5) : ((DiffActionEnum.CREATION == action) ? (22) : (10));
        int diffModificationsCount = (DiffActionEnum.DELETION == action) ? (5) : ((DiffActionEnum.CREATION == action) ? (1) : (10));
        actual = fSyncService.compare(-1, "testStore:/root", -1, "submitStore:/root", null, true);
        diffs = fSyncService.compare(-1, "testStore:/root", -1, "submitStore:/root", null);
        assertEquals(actualModificationsCount, actual.size());
        assertEquals(diffModificationsCount, diffs.size());
        if (DiffActionEnum.CREATION != action)
        {
            assertDiffsList(AVMDifference.NEWER, diffs);
            assertEquals(diffs.toString(), actual.toString());
        }
        if (DiffActionEnum.CREATION != action)
        {
            if (DiffActionEnum.DELETION == action)
            {
                fService.removeNode("testStore:/root/test");
            }
            else
            {
                fService.setNodeProperty("testStore:/root/test", WCMModel.PROP_REVERTED_ID, new PropertyValue(WCMModel.PROP_REVERTED_ID, null));
            }
            actual = fSyncService.compare(-1, "testStore:/root/", -1, "submitStore:/root/", null, true);
            diffs = fSyncService.compare(-1, "testStore:/root/", -1, "submitStore:/root/", null);
            actualModificationsCount = (DiffActionEnum.DELETION == action) ? (1) : (actualModificationsCount + 1);
            assertEquals(actualModificationsCount, actual.size());
            assertEquals(1, diffs.size());
            assertDiffsList(AVMDifference.NEWER, actual);
            assertDiffsList(AVMDifference.NEWER, diffs);
        }
    }
    private void assertDiffsList(int expectedCode, List actual)
    {
        for (AVMDifference diff : actual)
        {
            assertNotNull(diff);
            assertTrue(diff.isValid());
            assertEquals(expectedCode, diff.getDifferenceCode());
        }
    }
    public void test_ETWOTWO_570() throws Exception
    {
        // Check that read-write methods are properly intercepted
        RetryingTransactionCallback