//   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.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.apache.commons.lang.SerializationUtils;
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.SOAPEventType;
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.console.TextConsole;

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

    private final static String OUTPUT_DIR = "target/tmp/test-outputs/";
    
    // 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.FINE);
    }

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

    @Test(expected = java.lang.RuntimeException.class)
    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();
                SOAPEventType eventType = (SOAPEventType) event.getType();
                if (!"TransportService".equals(eventType.getServiceName())) {
                    eventIter.remove();
                }
            }
            umlSequences.add(UMLUtils.createUMLTransitionSequence(sequence, stateMachine));
        }
    }

    @Test
    public void testConvertStateMachineToUsageProfile__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));

        // remove everything but transport from sequences
        for (List<Event> sequence : sequences) {
            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
                Event event = eventIter.next();
                SOAPEventType eventType = (SOAPEventType) event.getType();
                if (!"TransportService".equals(eventType.getServiceName())) {
                    eventIter.remove();
                }
            }
        }

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

        UMLUtils.convertStateMachineToUsageProfile(sequences, 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++;
        }
    }
    
    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(sequences);
        Collection<List<Event>> generatedSequences = createRandomSequences(usageProfile, properties);
        
        int i = 1;
        int[] lengths = new int[generatedSequences.size()];
        for (List<Event> sequence : generatedSequences) {
            UMLUtils.createInteractionFromEventSequence(sequence, model, properties.getProperty("testcases.prefix")+"_"+i,
                                                        properties.getProperty("test.context"));
            lengths[i - 1] = sequence.size();
            i++;
        }
        for (int j = 0; j < generatedSequences.size(); j++) {
            Interaction interaction =
                (Interaction) model.getPackagedElement(properties.getProperty("testcases.prefix") + j, true,
                                                       UMLPackage.Literals.INTERACTION, true);
            double usageScore = UMLUtils.calculateUsageScore(interaction, 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(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, null);

        //ModelUtils.writeModelToFile(model, OUTPUT_DIR + "testCreateScheduling_1_result.uml");
    }
    
    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
        for (List<Event> sequence : sequences) {
            SOAPUtils.removeNonSOAPEvents(sequence);
        }
        
        // 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 : sequences) {
            for (Iterator<Event> eventIter = sequence.iterator(); eventIter.hasNext();) {
                Event event = eventIter.next();
                SOAPEventType eventType = (SOAPEventType) event.getType();
                if (ignoredServices.contains(eventType.getServiceName())) {
                    eventIter.remove();
                }
            }
        }
        return sequences;
    }
    
    private IStochasticProcess createUsageProfile(Collection<List<Event>> sequences) throws Exception {
        Collection<List<Event>> simpleSOAPSequences = new LinkedList<>();
        for (List<Event> sequence : sequences) {
            simpleSOAPSequences.add(SOAPUtils.convertToSimpleSOAPEvent(sequence, true));
        }

        FirstOrderMarkovModel usageProfile = new FirstOrderMarkovModel(new Random(1));
        usageProfile.train(simpleSOAPSequences);
        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
            }
        }
    }

}
