//   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.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
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.FirstOrderMarkovModel;
import de.ugoe.cs.autoquest.usageprofiles.IStochasticProcess;
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_properties.prop",
                                                       "ita_usagejournal.log",
                                                       "ita_usageprofile.dat", "ita_model.uml",
                                                       "ita_model_testsuite.uml",
                                                       "ita_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.SEVERE);
    }

    @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(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)); }
         */

        Model model =
            ModelUtils.loadModel(ClassLoader.getSystemResourceAsStream(testdata.dslModelFile));

        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)));
        manuallyCreatedSequence
            .add(new Event(new SimpleSOAPEventType("transportInstructionConfirmationRequest",
                                                   "materialSupplierService",
                                                   "Logistics_Environment", 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() 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);
    }

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

        // 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(ClassLoader.getSystemResourceAsStream(testdata.dslModelFile));

        // create a test case for each observed sequence
        int i = 0;
        for (List<Event> sequence : sequences) {
            UMLUtils.createInteractionFromEventSequence(sequence, model,
                                                        properties.getProperty("testcases.prefix") +
                                                            "_" + i,
                                                        properties.getProperty("test.context"));
            i++;
        }

        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(ClassLoader.getSystemResourceAsStream(testdata.dslModelFile));
        IStochasticProcess usageProfile = createUsageProfile(testdata, sequences);
        Collection<List<Event>> generatedSequences =
            createRandomSequences(usageProfile, properties);

        int i = 1;
        List<Interaction> interactions = new LinkedList<>();
        int[] lengths = new int[generatedSequences.size()];
        for (List<Event> sequence : generatedSequences) {
            interactions.add(UMLUtils
                .createInteractionFromEventSequence(sequence, model,
                                                    properties.getProperty("testcases.prefix") +
                                                        "_" + i,
                                                    properties.getProperty("test.context")));
            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(ClassLoader.getSystemResourceAsStream(testdata.dslModelFile));
        IStochasticProcess usageProfile = createUsageProfile(testdata, sequences);
        Collection<List<Event>> generatedSequences =
            createRandomSequences(usageProfile, properties);
        int i = 1;
        for (List<Event> sequence : generatedSequences) {
            UMLUtils.createInteractionFromEventSequence(sequence, model,
                                                        properties.getProperty("testcases.prefix") +
                                                            "_" + i,
                                                        properties.getProperty("test.context"));
            i++;
        }

        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();

        // remove non SOAP events and convert to SimpleSOAPEventType
        Collection<List<Event>> simpleSOAPSequences = new LinkedList<>();
        for (List<Event> sequence : sequences) {
            simpleSOAPSequences.add(SOAPUtils.convertToSimpleSOAPEvent(sequence, true));
        }

        // remove calls to ingored services
        Set<String> ignoredServices = new HashSet<>();
        String ignoredServicesString = properties.getProperty("test.ignored.services");
        if (ignoredServicesString != null) {
            for (String service : ignoredServicesString.split(",")) {
                ignoredServices.add(service.trim());
            }
        }

        for (List<Event> sequence : simpleSOAPSequences) {
            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
                Event event = eventIter.next();
                SimpleSOAPEventType eventType = (SimpleSOAPEventType) event.getType();
                if (ignoredServices.contains(eventType.getServiceName())) {
                    eventIter.remove();
                }
            }
        }
        return simpleSOAPSequences;
    }

    private IStochasticProcess createUsageProfile(TestData testdata,
                                                  Collection<List<Event>> sequences)
        throws Exception
    {
        FirstOrderMarkovModel usageProfile = new FirstOrderMarkovModel(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.minlenth", "1"));
        int testCaseMaxLength =
            Integer.parseInt(properties.getProperty("testcases.maxlenth", "100"));
        int maxIter = numberOfTestCases * 100;
        RandomWalkGenerator testGenerator =
            new RandomWalkGenerator(numberOfTestCases, testCaseMinLength, testCaseMaxLength, true,
                                    maxIter);
        return testGenerator.generateTestSuite(usageProfile);
    }

    private void deleteFiles(File file) {
        if (file.exists()) {
            if (file.isDirectory()) {
                for (File child : file.listFiles()) {
                    deleteFiles(child);
                }
            }

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

}
