/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This 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 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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 this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.patching.tests;

import static org.jboss.as.patching.HashUtils.hashFile;
import static org.jboss.as.patching.runner.TestUtils.dump;
import static org.jboss.as.patching.runner.TestUtils.randomString;
import static org.jboss.as.patching.runner.TestUtils.touch;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import org.jboss.as.patching.PatchingException;
import org.jboss.as.patching.installation.Layer;
import org.jboss.as.patching.validation.PatchingGarbageLocator;
import org.junit.Test;


/**
 * @author Alexey Loubyansky
 *
 */
public class LocatingUnusedArtifactsUnitTestCase extends AbstractPatchingTest {

    static final String[] FILE_ONE = {"bin", "standalone.sh"};
    static final String[] FILE_TWO = {"bin", "standalone.conf"};
    static final String[] FILE_EXISTING = {"bin", "test"};
    private static final String CP_1_ID = "cp1";
    private static final String ONE_OFF_1_ID = "oneOff1";
    private static final String ONE_OFF_2_ID = "oneOff2";

    @Test
    public void testUnpatchedValidation() throws Exception {
        assertNoGarbage();
    }

    @Test
    public void testOneCPValidation() throws Exception {

        final PatchingTestBuilder builder = createDefaultBuilder();
        final byte[] standaloneHash = new byte[20];
        final byte[] moduleHash = new byte[20];

        // Create a file
        final File existing = builder.getFile(FILE_EXISTING);
        touch(existing);
        dump(existing, randomString());

        final byte[] existingHash = hashFile(existing);
        final byte[] initialHash = Arrays.copyOf(existingHash, existingHash.length);

        final PatchingTestStepBuilder cp1 = builder.createStepBuilder();
        final String cp1Id = "CP1";
        cp1.setPatchId(cp1Id)
                .upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
                .upgradeElement("base-CP1", "base", false)
                .addModuleWithRandomContent("org.jboss.test", moduleHash)
                .getParent()
                .addFileWithRandomContent(standaloneHash, FILE_ONE)
                .updateFileWithRandomContent(initialHash, existingHash, FILE_EXISTING);

        // Apply CP1
        apply(cp1);

        assertNoGarbage();
    }

    @Test
    public void testOneOffAndCPValidation() throws Exception {

        final PatchingTestBuilder builder = createDefaultBuilder();
        final byte[] standaloneHash = new byte[20];
        final byte[] moduleHash = new byte[20];

        // Create a file
        final File existing = builder.getFile(FILE_EXISTING);
        touch(existing);
        dump(existing, randomString());

        final byte[] existingHash = hashFile(existing);
        final byte[] initialHash = Arrays.copyOf(existingHash, existingHash.length);

        final PatchingTestStepBuilder cp1 = builder.createStepBuilder();
        final String cp1Id = "CP1";
        cp1.setPatchId(cp1Id)
                .upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
                .upgradeElement("base-CP1", "base", false)
                .addModuleWithRandomContent("org.jboss.test", moduleHash)
                .getParent()
                .addFileWithRandomContent(standaloneHash, FILE_ONE)
                .updateFileWithRandomContent(initialHash, existingHash, FILE_EXISTING);

        // Apply CP1
        apply(cp1);

        final PatchingTestStepBuilder oneOff1 = builder.createStepBuilder();
        final String oneOff1Id = "oneOff1";
        oneOff1.setPatchId(oneOff1Id)
                .oneOffPatchIdentity(PRODUCT_VERSION)
                .oneOffPatchElement("base-oneOff1", "base", false)
                .updateModuleWithRandomContent("org.jboss.test", moduleHash, null)
                .getParent()
                .updateFileWithRandomContent(standaloneHash, null, FILE_ONE)
                .updateFileWithRandomContent(Arrays.copyOf(existingHash, existingHash.length), existingHash, FILE_EXISTING);

        // Apply oneOff1
        apply(oneOff1);

        assertNoGarbage();
    }

    @Test
    public void testOneOffCPOneOffValidation() throws Exception {
        installOneOffCpOneOff();
        assertNoGarbage();
    }

    @Test
    public void testRemovedOneOffRollbackXml() throws Exception {
        installOneOffCpOneOff();
        removeRollbackXml(ONE_OFF_1_ID);
        assertNoGarbage();
    }

    @Test
    public void testRemovedCPRollbackXml() throws Exception {

        installOneOffCpOneOff();
        removeRollbackXml(CP_1_ID);

        final PatchingGarbageLocator garbageLocator = PatchingGarbageLocator.getIninitialized(updateInstallationManager().getDefaultIdentity());
        final List<File> inactiveHistory = garbageLocator.getInactiveHistory();
        assertEquals(1, inactiveHistory.size());
        assertEquals(getExpectedHistoryDir(ONE_OFF_1_ID), inactiveHistory.get(0).getAbsolutePath());

        final List<File> inactiveOverlays = garbageLocator.getInactiveOverlays();
        assertEquals(1, inactiveOverlays.size());
        assertEquals(getExpectedOverlayDir("base", ONE_OFF_1_ID), inactiveOverlays.get(0).getAbsolutePath());
    }

    @Test
    public void testRemovedLastOneOffRollbackXml() throws Exception {

        installOneOffCpOneOff();
        removeRollbackXml(ONE_OFF_2_ID);

        PatchingGarbageLocator garbageLocator = PatchingGarbageLocator.getIninitialized(updateInstallationManager().getDefaultIdentity());
        final List<File> inactiveHistory = garbageLocator.getInactiveHistory();

        List<String> inactivePaths = Arrays.asList(new String[] { getExpectedHistoryDir(ONE_OFF_1_ID) });
        assertEqualPaths(inactivePaths, inactiveHistory);

        final List<File> inactiveOverlays = garbageLocator.getInactiveOverlays();
        inactivePaths = Arrays.asList(new String[]{getExpectedOverlayDir("base", ONE_OFF_1_ID)});
        assertEqualPaths(inactivePaths, inactiveOverlays);

        // test cleaning
        garbageLocator.deleteInactiveContent();
        garbageLocator.reset();
        assertTrue(garbageLocator.getInactiveHistory().isEmpty());
        assertTrue(garbageLocator.getInactiveOverlays().isEmpty());
        assertTrue(new File(getExpectedOverlayDir("base", ONE_OFF_2_ID)).exists());
    }

    @Test
    public void testMultipleLayers() throws Exception {

        final PatchingTestBuilder builder = createDefaultBuilder("layer2", "layer1", "base");

        final byte[] standaloneHash = new byte[20];
        final byte[] moduleHash = new byte[20];

        final PatchingTestStepBuilder cp1 = builder.createStepBuilder();
        cp1.setPatchId("CP1")
                .upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
                .upgradeElement("base-CP1", "base", false)
                .addModuleWithRandomContent("org.jboss.test", moduleHash)
                .getParent()
                .addFileWithRandomContent(standaloneHash, FILE_ONE);
        // Apply CP1
        apply(cp1);

        final PatchingTestStepBuilder cp2 = builder.createStepBuilder();
        cp2.setPatchId("CP2")
                .upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
                .upgradeElement("layer2-CP2", "layer2", false)
                    .addModuleWithRandomContent("org.jboss.test", null)
                    .getParent()
                .upgradeElement("layer1-CP2", "layer1", false)
                    .addModuleWithRandomContent("org.jboss.test", null)
                    .getParent()
                .updateFileWithRandomContent(Arrays.copyOf(standaloneHash, standaloneHash.length), standaloneHash, FILE_ONE);
        // Apply CP2
        apply(cp2);

        final PatchingTestStepBuilder cp3 = builder.createStepBuilder();
        cp3.setPatchId("CP3")
                .upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
                .removeFile(Arrays.copyOf(standaloneHash, standaloneHash.length), FILE_ONE);
        // Apply CP3
        apply(cp3);

        removeRollbackXml("CP3");

        PatchingGarbageLocator garbageLocator = PatchingGarbageLocator.getIninitialized(updateInstallationManager().getDefaultIdentity());
        final List<File> inactiveHistory = garbageLocator.getInactiveHistory();
        List<String> inactivePaths = Arrays.asList(new String[]{getExpectedHistoryDir("CP1"), getExpectedHistoryDir("CP2")});
        assertEqualPaths(inactivePaths, inactiveHistory);

        final List<File> inactiveOverlays = garbageLocator.getInactiveOverlays();
        inactivePaths = Arrays.asList(new String[]{getExpectedOverlayDir("layer2", "CP2"),
                getExpectedOverlayDir( "layer1", "CP2"), getExpectedOverlayDir("base", "CP1")});
        assertEqualPaths(inactivePaths, inactiveOverlays);

        // test cleaning
        garbageLocator.deleteInactiveContent();
        garbageLocator.reset();
        assertTrue(garbageLocator.getInactiveHistory().isEmpty());
        assertTrue(garbageLocator.getInactiveOverlays().isEmpty());

    }

    @Test
    public void testUnpatchedWitGarbage() throws Exception {

        createDefaultBuilder("layer2", "layer1", "base");

        final List<String> historyGarbage = Arrays.asList(new String[]{getExpectedHistoryDir("CP1"), getExpectedHistoryDir("CP2")});
        for(int i = 0; i < historyGarbage.size(); ++i) {
            new File(historyGarbage.get(i)).mkdirs();
        }

        final List<String> overlayGarbage = Arrays.asList(new String[]{getExpectedOverlayDir("layer2", "CP2"),
                getExpectedOverlayDir("layer1", "CP2"), getExpectedOverlayDir("base", "CP1")});
        for(int i = 0; i < overlayGarbage.size(); ++i) {
            new File(overlayGarbage.get(i)).mkdirs();
        }

        PatchingGarbageLocator garbageLocator = PatchingGarbageLocator.getIninitialized(loadInstallationManager().getDefaultIdentity());
        final List<File> inactiveHistory = garbageLocator.getInactiveHistory();
        assertEqualPaths(historyGarbage, inactiveHistory);

        final List<File> inactiveOverlays = garbageLocator.getInactiveOverlays();
        assertEqualPaths(overlayGarbage, inactiveOverlays);

        garbageLocator.deleteInactiveContent();
        garbageLocator.reset();
        assertTrue(garbageLocator.getInactiveHistory().isEmpty());
        assertTrue(garbageLocator.getInactiveOverlays().isEmpty());
    }

    protected void assertEqualPaths(List<String> expected, final List<File> actual) {
        assertEquals(expected.size(), actual.size());
        for(int i = 0; i < expected.size(); ++i) {
            assertTrue(expected.contains(actual.get(i).getAbsolutePath()));
        }
    }

    protected void removeRollbackXml(String patchId) throws IOException {
        final File oneOff1History = updateInstallationManager().getInstalledImage().getPatchHistoryDir(patchId);
        assertTrue(oneOff1History.exists());
        final File oneOff1RollbackXml = new File(oneOff1History, "rollback.xml");
        assertTrue(oneOff1RollbackXml.exists());
        assertTrue(oneOff1RollbackXml.delete());
        assertFalse(oneOff1RollbackXml.exists());
    }

    protected String getExpectedOverlayDir(String layerName, final String patchId) throws IOException {
        final Layer layer = updateInstallationManager().getDefaultIdentity().getLayer(layerName);
        if(layer == null) {
            fail("No layer " + layerName);
        }
        return layer.getDirectoryStructure().getModulePatchDirectory(layerName + "-" + patchId).getAbsolutePath();
    }

    protected String getExpectedHistoryDir(final String patchId) throws IOException {
        return updateInstallationManager().getInstalledImage().getPatchesDir().getAbsolutePath()
                + File.separator + patchId;
    }

    protected void installOneOffCpOneOff()
            throws IOException, PatchingException {
        final PatchingTestBuilder builder = createDefaultBuilder();
        final byte[] standaloneHash = new byte[20];
        final byte[] moduleHash = new byte[20];

        // Create a file
        final File existing = builder.getFile(FILE_EXISTING);
        touch(existing);
        dump(existing, randomString());

        final byte[] existingHash = hashFile(existing);
        final byte[] initialHash = Arrays.copyOf(existingHash, existingHash.length);

        final PatchingTestStepBuilder oneOff1 = builder.createStepBuilder();
        oneOff1.setPatchId(ONE_OFF_1_ID)
                .oneOffPatchIdentity(PRODUCT_VERSION)
                .oneOffPatchElement("base-" + ONE_OFF_1_ID, "base", false)
                .addModuleWithRandomContent("org.jboss.test", moduleHash)
                .getParent()
                .addFileWithRandomContent(standaloneHash, FILE_ONE)
                .updateFileWithRandomContent(Arrays.copyOf(existingHash, existingHash.length), existingHash, FILE_EXISTING);

        // Apply oneOff1
        apply(oneOff1);

        final PatchingTestStepBuilder cp1 = builder.createStepBuilder();
        cp1.setPatchId(CP_1_ID)
                .upgradeIdentity(PRODUCT_VERSION, PRODUCT_VERSION)
                .upgradeElement("base-" + CP_1_ID, "base", false)
                .updateModuleWithRandomContent("org.jboss.test", Arrays.copyOf(moduleHash, moduleHash.length), moduleHash)
                .getParent()
                .updateFileWithRandomContent(Arrays.copyOf(standaloneHash, standaloneHash.length), standaloneHash, FILE_ONE)
                .updateFileWithRandomContent(initialHash, existingHash, FILE_EXISTING);

        // Apply CP1
        apply(cp1);

        final PatchingTestStepBuilder oneOff2 = builder.createStepBuilder();
        oneOff2.setPatchId(ONE_OFF_2_ID)
                .oneOffPatchIdentity(PRODUCT_VERSION)
                .oneOffPatchElement("base-" + ONE_OFF_2_ID, "base", false)
                .updateModuleWithRandomContent("org.jboss.test", moduleHash, null)
                .getParent()
                .updateFileWithRandomContent(standaloneHash, null, FILE_ONE)
                .updateFileWithRandomContent(Arrays.copyOf(existingHash, existingHash.length), existingHash, FILE_EXISTING);

        // Apply oneOff1
        apply(oneOff2);
    }

    protected void assertNoGarbage() throws Exception {
        final PatchingGarbageLocator garbageLocator = PatchingGarbageLocator.getIninitialized(loadInstallationManager().getDefaultIdentity());
        List<File> inactiveHistory = garbageLocator.getInactiveHistory();
        assertTrue(inactiveHistory.toString(), inactiveHistory.isEmpty());
        assertTrue(garbageLocator.getInactiveOverlays().toString(), garbageLocator.getInactiveOverlays().isEmpty());
    }

}
