//   Copyright 2012 Georg-August-Universität Göttingen, Germany
//
//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at
//
//       http://www.apache.org/licenses/LICENSE-2.0
//
//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.

package de.ugoe.cs.autoquest.plugin.uml;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.logging.Level;

import static org.junit.Assert.*;

import org.eclipse.uml2.uml.Interaction;
import org.eclipse.uml2.uml.Model;
import org.eclipse.uml2.uml.StateMachine;
import org.eclipse.uml2.uml.UMLPackage;
import org.junit.After;
import org.junit.BeforeClass;
import org.junit.Test;

import de.fraunhofer.fokus.testing.ModelUtils;
import de.ugoe.cs.autoquest.eventcore.Event;
import de.ugoe.cs.autoquest.plugin.http.HTTPLogParser;
import de.ugoe.cs.autoquest.plugin.http.SOAPUtils;
import de.ugoe.cs.autoquest.plugin.http.eventcore.SimpleSOAPEventType;
import de.ugoe.cs.autoquest.testgeneration.RandomWalkGenerator;
import de.ugoe.cs.autoquest.usageprofiles.HighOrderMarkovModel;
import de.ugoe.cs.autoquest.usageprofiles.IStochasticProcess;
import de.ugoe.cs.autoquest.usageprofiles.TrieBasedModel;
import de.ugoe.cs.util.SerializationUtils;
import de.ugoe.cs.util.console.TextConsole;

/**
 * <p>
 * Tests for AutoQUESTs UMLUtils
 * </p>
 * 
 * @author Steffen Herbold
 */
public class UMLUtilsTest {

    private final static String OUTPUT_DIR = "target/tmp/test-outputs/";

    private final static boolean DELETE_OUTPUTS = false;

    // for RLUS
    private final static TestData deda_1 = new TestData("deda_rlus_properties.prop",
                                                        "deda_usagejournal.log",
                                                        "deda_rlus_usageprofile.dat",
                                                        "deda_model.uml",
                                                        "deda_rlus_model_testsuite.uml",
                                                        "deda_rlus_model_scheduling.uml");

    // for IXS
    private final static TestData deda_2 = new TestData("deda_ixs_properties.prop",
                                                        "deda_usagejournal.log",
                                                        "deda_ixs_usageprofile.dat",
                                                        "deda_model.uml",
                                                        "deda_ixs_model_testsuite.uml",
                                                        "deda_ixs_model_scheduling.uml");

    private final static TestData ita_1 = new TestData("ita_imported_properties.prop",
                                                       "ita_imported_usagejournal.log",
                                                       "ita_imported_usageprofile.dat",
                                                       "ita_imported_model.uml",
                                                       "ita_imported_model_testsuite.uml",
                                                       "ita_imported_model_scheduling.uml");

    private static class TestData {
        public final String propertiesFile;
        public final String usageJournalFile;
        public final String usageProfileFile;
        public final String dslModelFile;
        public final String testSuiteFile;
        public final String schedulingFile;

        public TestData(String propertiesFile,
                        String usageJournalFile,
                        String usageProfileFile,
                        String dslModelFile,
                        String testSuiteFile,
                        String schedulingFile)
        {
            this.propertiesFile = propertiesFile;
            this.usageJournalFile = usageJournalFile;
            this.usageProfileFile = usageProfileFile;
            this.dslModelFile = dslModelFile;
            this.testSuiteFile = testSuiteFile;
            this.schedulingFile = schedulingFile;

        }

        @Override
        public String toString() {
            StringBuilder strBld = new StringBuilder();
            strBld.append("Properties    " + propertiesFile + "\n");
            strBld.append("Usage Journal " + usageJournalFile + "\n");
            strBld.append("Usage Profile " + usageProfileFile + "\n");
            strBld.append("DSL Model     " + dslModelFile + "\n");
            strBld.append("Test Suite    " + testSuiteFile + "\n");
            strBld.append("Scheduling    " + schedulingFile + "\n");
            return strBld.toString();
        }
    }

    @BeforeClass
    public static void setUpBeforeClass() throws Exception {
        new TextConsole(Level.FINE);
        File outdir = new File(OUTPUT_DIR);
        if( !outdir.exists() ) {
            outdir.mkdirs();
        }
    }

    @After
    public void tearDown() throws Exception {
        if (DELETE_OUTPUTS) {
            deleteFiles(new File(OUTPUT_DIR));
        }
        
    }

    @Test
    public void testCreateUMLTransitionSequence_ITA_1() throws Exception {
        TestData testdata = ita_1;

        /*
         * Properties properties = loadProperties(testdata); //Collection<List<Event>> sequences =
         * loadAndPreprocessUsageJournal(testdata, properties);
         * 
         * Model model = ModelUtils.loadModel(new
         * File(ClassLoader.getSystemResource(testdata.dslModelFile).getFile()));
         * 
         * StateMachine stateMachine = (StateMachine)
         * model.getPackagedElement("StateMachineTransportService", true,
         * UMLPackage.Literals.STATE_MACHINE, true);
         * 
         * 
         * Collection<List<Event>> umlSequences = new LinkedList<>();
         * 
         * // remove everything but transport from sequences for (List<Event> sequence : sequences)
         * { for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) { Event
         * event = eventIter.next(); if
         * (!"TransportService".equals(SOAPUtils.getServiceNameFromEvent(event))) {
         * eventIter.remove(); } } umlSequences.add(UMLUtils.createUMLTransitionSequence(sequence,
         * stateMachine)); }
         */

        Model model =
            ModelUtils.loadModel(new File(ClassLoader.getSystemResource(testdata.dslModelFile)
                .getFile()));

        StateMachine stateMachine =
            (StateMachine) model.getPackagedElement("StateMachineTransportService", true,
                                                    UMLPackage.Literals.STATE_MACHINE, true);

        List<Event> manuallyCreatedSequence = new LinkedList<>();
        manuallyCreatedSequence
            .add(new Event(new SimpleSOAPEventType("transportInstructionRequest",
                                                   "TransportService", "Logistics_Environment",
                                                   null, null, null)));
        manuallyCreatedSequence
            .add(new Event(new SimpleSOAPEventType("transportInstructionConfirmationRequest",
                                                   "materialSupplierService",
                                                   "Logistics_Environment", null, null, null)));

        // TODO make test case run
        // UMLUtils.createUMLTransitionSequence(manuallyCreatedSequence, stateMachine);
    }

    @Test
    public void testConvertStateMachineToUsageProfile__ITA_1() throws Exception {
        // TODO make test run
        /*
         * TestData testdata = ita_1;
         * 
         * assertTrue("test currently not working", false);
         * 
         * Properties properties = loadProperties(testdata); Collection<List<Event>> sequences =
         * loadAndPreprocessUsageJournal(testdata, properties); Model model =
         * ModelUtils.loadModel(ClassLoader.getSystemResourceAsStream(testdata.dslModelFile));
         * StateMachine stateMachine = (StateMachine)
         * model.getPackagedElement("StateMachineTransportService", true,
         * UMLPackage.Literals.STATE_MACHINE, true);
         * 
         * Collection<List<Event>> umlSequences = new LinkedList<>();
         * 
         * // remove everything but transport from sequences for (List<Event> sequence : sequences)
         * { for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) { Event
         * event = eventIter.next(); if
         * (!"TransportService".equals(SOAPUtils.getServiceNameFromEvent(event))) {
         * eventIter.remove(); } } umlSequences.add(UMLUtils.createUMLTransitionSequence(sequence,
         * stateMachine)); }
         * 
         * UMLUtils.convertStateMachineToUsageProfile(umlSequences, stateMachine);
         * 
         * ModelUtils.writeModelToFile(model, OUTPUT_DIR + "ita_v2_result.uml");
         */
    }

    @Test
    public void testCreateInteractionFromEventSequence_DEDA_1() throws Exception {
        createInteractionFromEventSequenceWorkflow(deda_1);
    }

    @Test
    public void testCreateInteractionFromEventSequence_DEDA_2() throws Exception {
        createInteractionFromEventSequenceWorkflow(deda_2);
    }

    @Test
    public void testCreateInteractionFromEventSequence_ITA_1() throws Exception {
        createInteractionFromEventSequenceWorkflow(ita_1);
    }

    @Test
    public void testCalculateUsageScore_DEDA_1() throws Exception {
        calculateUsageScoreWorkflow(deda_1);
    }

    @Test
    public void testCalculateUsageScore_DEDA_2() throws Exception {
        calculateUsageScoreWorkflow(deda_2);
    }

    @Test
    public void testCalculateUsageScore_ITA_1() throws Exception {
        calculateUsageScoreWorkflow(ita_1);
    }

    @Test
    public void testCreateScheduling_DEDA_1() throws Exception {
        createSchedulingWorkflow(deda_1);
    }

    @Test
    public void testCreateScheduling_DEDA_2() throws Exception {
        createSchedulingWorkflow(deda_2);
    }

    @Test
    public void testCreateScheduling_ITA_1() throws Exception {
        createSchedulingWorkflow(ita_1);
    }

    @Test
    public void testValidateModelWithLog_DEDA_1() throws Exception {
        validateModelWithLogWorkflow(deda_1);
    }

    @Test
    public void testValidateModelWithLog_DEDA_2() throws Exception {
        validateModelWithLogWorkflow(deda_2);
    }

    @Test
    public void testValidateModelWithLog_ITA_1() throws Exception {
        validateModelWithLogWorkflow(ita_1);
    }
    
    @Test
    public void testSerialization_ITA_1() throws Exception {
        TestData testdata = ita_1;
        Properties properties = loadProperties(testdata);
        Collection<List<Event>> sequences = loadAndPreprocessUsageJournal(testdata, properties);
        IStochasticProcess model = createUsageProfile(testdata, properties, sequences);
        byte[] serialized = SerializationUtils.serialize(model);
        SerializationUtils.deserialize(serialized);
    }

    private void validateModelWithLogWorkflow(TestData testdata) throws Exception {
        Properties properties = loadProperties(testdata);
        Collection<List<Event>> sequences = loadAndPreprocessUsageJournal(testdata, properties);
        Model model =
            ModelUtils.loadModel(new File(ClassLoader.getSystemResource(testdata.dslModelFile)
                .getFile()));

        // run validation
        int violations =
            UMLUtils.validateModelWithLog(sequences, model, properties.getProperty("test.context"));
        if (violations == 0) {
            System.out.println("No problems found.");
        }
        else {
            System.out.println(violations + " violations found.");
        }
    }

    private void createInteractionFromEventSequenceWorkflow(TestData testdata) throws Exception {
        Properties properties = loadProperties(testdata);
        Collection<List<Event>> sequences = loadAndPreprocessUsageJournal(testdata, properties);
        Model model =
            ModelUtils.loadModel(new File(ClassLoader.getSystemResource(testdata.dslModelFile)
                .getFile()));

        // create a test case for each observed sequence
        UMLUtils.createInteractionFromEventSequence(sequences, model,  properties.getProperty("testcases.prefix"), properties.getProperty("test.context"), false);

        ModelUtils.writeModelToFile(model, OUTPUT_DIR + testdata.testSuiteFile);
    }

    private void calculateUsageScoreWorkflow(TestData testdata) throws Exception {
        Properties properties = loadProperties(testdata);
        Collection<List<Event>> sequences = loadAndPreprocessUsageJournal(testdata, properties);
        Model model =
            ModelUtils.loadModel(new File(ClassLoader.getSystemResource(testdata.dslModelFile)
                .getFile()));
        IStochasticProcess usageProfile = createUsageProfile(testdata, properties, sequences);
        Collection<List<Event>> generatedSequences =
            createRandomSequences(usageProfile, properties);

        int i = 1;
        List<Interaction> interactions = UMLUtils.createInteractionFromEventSequence(generatedSequences, model, properties.getProperty("testcases.prefix"), properties.getProperty("test.context"), Boolean.parseBoolean(properties.getProperty("testcases.data.random", "false")));
        int[] lengths = new int[generatedSequences.size()];
        for (List<Event> sequence : generatedSequences) {
            lengths[i - 1] = sequence.size();
            i++;
        }
        for (int j = 0; j < interactions.size(); j++) {
            double usageScore = UMLUtils.calculateUsageScore(interactions.get(j), usageProfile);
            System.out.format("usage score %02d: %.2f \t %d\n", j + 1, usageScore, lengths[j]);
        }
    }

    private void createSchedulingWorkflow(TestData testdata) throws Exception {
        Properties properties = loadProperties(testdata);
        Collection<List<Event>> sequences = loadAndPreprocessUsageJournal(testdata, properties);
        Model model =
            ModelUtils.loadModel(new File(ClassLoader.getSystemResource(testdata.dslModelFile)
                .getFile()));
        IStochasticProcess usageProfile = createUsageProfile(testdata, properties, sequences);
        Collection<List<Event>> generatedSequences =
            createRandomSequences(usageProfile, properties);
        UMLUtils.createInteractionFromEventSequence(generatedSequences, model, properties.getProperty("testcases.prefix"), properties.getProperty("test.context"), Boolean.parseBoolean(properties.getProperty("testcases.data.random", "false")));
        
        UMLUtils.createScheduling(model, usageProfile, properties.getProperty("test.context"));

        ModelUtils.writeModelToFile(model, OUTPUT_DIR + testdata.schedulingFile);
    }

    private Properties loadProperties(TestData testdata) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream(ClassLoader.getSystemResource(testdata.propertiesFile)
            .getFile()));
        return properties;
    }

    private Collection<List<Event>> loadAndPreprocessUsageJournal(TestData testdata,
                                                                  Properties properties)
        throws Exception
    {
        // load usage journal
        HTTPLogParser parser =
            new HTTPLogParser(new File(ClassLoader.getSystemResource(testdata.propertiesFile)
                .getFile()));
        parser.parseFile(new File(ClassLoader.getSystemResource(testdata.usageJournalFile)
            .getFile()));
        Collection<List<Event>> sequences = parser.getSequences();

        sequences = SOAPUtils.removeNonSOAPEvents(sequences);
        sequences = SOAPUtils.sortAndConvertSequences(sequences, true, true);
        sequences = SOAPUtils.normalizeOperationNames(sequences, properties
                    .getProperty("methodName.prefixToRemove"), properties
                    .getProperty("methodName.suffixToRemove"));
        sequences = SOAPUtils.removeCallsToIgnoredServices(sequences, properties.getProperty("test.ignored.services"));
        
        return sequences;
    }

    private IStochasticProcess createUsageProfile(TestData testdata, Properties properties,
                                                  Collection<List<Event>> sequences)
        throws Exception
    {
        TrieBasedModel usageProfile = new HighOrderMarkovModel(Integer.parseInt(properties.getProperty("usageprofile.markovorder", "1")), new Random(1));
        usageProfile.train(sequences);
        FileOutputStream fos = new FileOutputStream(OUTPUT_DIR + testdata.usageProfileFile);
        SerializationUtils.serialize(usageProfile, fos);
        fos.close();
        return usageProfile;
    }

    private Collection<List<Event>> createRandomSequences(IStochasticProcess usageProfile,
                                                          Properties properties) throws Exception
    {
        int numberOfTestCases = Integer.parseInt(properties.getProperty("testcases.number"));
        int testCaseMinLength = Integer.parseInt(properties.getProperty("testcases.minlength", "1"));
        long maxIterValidSequence = Long.parseLong(properties.getProperty("testcases.maxIterValid", "100"));
        int testCaseMaxLength =
            Integer.parseInt(properties.getProperty("testcases.maxlength", "100"));
        int maxIter = numberOfTestCases * 100;
        RandomWalkGenerator testGenerator =
            new RandomWalkGenerator(numberOfTestCases, testCaseMinLength, testCaseMaxLength, true,
                                    maxIter, maxIterValidSequence);
        return SOAPUtils.dropInvalidResponseRequestPairs(testGenerator.generateTestSuite(usageProfile));
    }

    private void deleteFiles(File file) {
        if (file.exists()) {
            if (file.isDirectory()) {
            	File[] children = file.listFiles();
            	if (children != null) {
                    for (File child : children) {
                        deleteFiles(child);
                    }
                }
            }

            try {
                file.delete();
            }
            catch (Exception e) {
                // ignore and delete as much as possible
            }
        }
    }

}
