package be.iminds.ilabt.jfed.rspec.parser;

import be.iminds.ilabt.jfed.rspec.model.*;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXModelRspec;
import be.iminds.ilabt.jfed.rspec.model.javafx_impl.FXRspecFactory;
import org.testng.Assert;
import org.testng.annotations.Test;

import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;

/**
 * TestRspecAndBenchMark
 */
public class TestRspecAndBenchMark {
    private static RspecFactory rspecFactory = FXRspecFactory.getInstance();

    @Test
    public void testPleAdvertisement() throws Exception {
        doTestAndBenchMark(readResouceRspec("ple_advertisement_example.rspec"), false);
    }

    @Test
    public void test10() throws Exception {
        doTestAndBenchMark(generateBigRequest(10, 1));
    }

    @Test
    public void test100() throws Exception {
        doTestAndBenchMark(generateBigRequest(100, 10));
    }

    @Test
    public void test1000() throws Exception {
        doTestAndBenchMark(generateBigRequest(1000, 100));
    }

    //disabled to make compiles on Jenkins not wait for this
//    @Test
//    public void test10000() throws Exception {
//        doTestAndBenchMark(generateBigRequest(10000, 1000));
//    }

    //takes way too long
//    @Test
//    public void test100000() throws Exception {
//        doTestAndBenchMark(generateBigRequest(100000, 10000));
//    }

    private static void doTestAndBenchMark(String rspecString) throws Exception {
        doTestAndBenchMark(rspecString, true);
    }
    private static void doTestAndBenchMark(String rspecString, boolean check) throws Exception {
        System.gc();
        Date startParse = new Date();
        ModelRspec rspec = new StaxRspecParser(rspecString, rspecFactory).call();
        Date endParse = new Date();

        System.gc();
        Date startWrite = new Date();
        String reconstructedRspec = new StaxRspecWriter(rspec).call();
        Date endWrite = new Date();

        long nodeCount = rspec.getNodes().size();
        long linkCount = rspec.getLinks().size();
        //ModelRspec is not needed anymore now. We'll set it to null explicitly.
        rspec = null;
        System.gc();
        Date startIsSimilar = new Date();
//        if (check)
//            Assert.assertTrue(TestRspecRecontructable.testSameXml(rspecString, reconstructedRspec));
        Date endIsSimilar = new Date();

        System.gc();
        Date startRspecCompare = new Date();
        RspecCompare compare = new RspecCompare(rspecString, reconstructedRspec);
        if (check)
            Assert.assertTrue(compare.isEqual(), compare.getDifferences().toString());
        Date endRspecCompare = new Date();

        long parseMs = endParse.getTime() - startParse.getTime();
        long writeMs = endWrite.getTime() - startWrite.getTime();
        long similarMs = endIsSimilar.getTime() - startIsSimilar.getTime();
        long rspecCompareMs = endRspecCompare.getTime() - startRspecCompare.getTime();

        System.out.printf("Benchmarked RSpec      chars     nodes     links    Parsed(ms)    Written(ms)   testSameXml(ms)   RspecCompare(ms)\n");
//        System.out.printf("Benchmarked RSpec of %7d chars (with %5d nodes and %5d links). Parsed in %5d ms. Written in %5d ms. Ran testSameXml in %5d ms. Did RspecCompare in %5d ms.",
        System.out.printf("                  %10d%10d%10d%14d%15d%18d%19d\n",
                rspecString.length(), nodeCount, linkCount,
                parseMs, writeMs, similarMs, rspecCompareMs);
        System.out.flush();
    }

    private static String readResouceRspec(String name) {
        try {
            URL url = TestRspecReconstructable.class.getResource(name);
            Path resPath = java.nio.file.Paths.get(url.toURI());
            return new String(java.nio.file.Files.readAllBytes(resPath), "UTF8");
        } catch (Exception e) {
            throw new RuntimeException("Failed to load resource");
        }
    }
    private static String generateBigRequest(int nodeCount, int linkCount) {
        final String TEST_COMP_MAN_URN = "urn:publicid:IDN+example.com+authority+cm";
        final String TEST_COMP_MAN_URN2 = "urn:publicid:IDN+example.net+authority+cm";

        RspecFactory rspecFactory = FXRspecFactory.getInstance();
        ModelRspec testRspec = new FXModelRspec("request");

        List<RspecNode> nodes = new ArrayList<>();
        for (int i = 0; i < nodeCount; i++) {
            RspecNode n = rspecFactory.createNodeWithClientId(testRspec, "node"+i);
            n.setComponentManagerId(TEST_COMP_MAN_URN);
            if ((i % 2) == 0) {
                n.setExclusive(true);
                n.setSliverType(new SliverTypeBuilder("raw-pc").addDiskImage("DUMMY").build());
                if ((i % 5) == 0)
                    n.setComponentId(TEST_COMP_MAN_URN2);
            } else {
                n.setExclusive(false);
                n.setSliverType(new SliverTypeBuilder("emulab-xen").addDiskImage("DUMMY").build());
            }
            testRspec.addNode(n);
            nodes.add(n);
        }

        //pass is used to start skiping nodes once all nodes have been used. This is useful to get links connecting different nodes if there are many links.
        int pass = 0;

        ListIterator<RspecNode> nodeIt = nodes.listIterator();
        for (int i = 0; i < linkCount; i++) {
            RspecLink l = rspecFactory.createLinkWithClientId(testRspec, "link" + i, "lan");

            for (int p = 0; p < pass; p++) {
                if (!nodeIt.hasNext()) nodeIt = nodes.listIterator(); assert nodeIt.hasNext();
                nodeIt.next(); //skip a node
            }
            if (!nodeIt.hasNext()) { nodeIt = nodes.listIterator(); pass++; } assert nodeIt.hasNext();
            RspecNode n1 = nodeIt.next();
            if (!nodeIt.hasNext()) { nodeIt = nodes.listIterator(); pass++; } assert nodeIt.hasNext();
            RspecNode n2 = nodeIt.next();

            RspecInterface i1 = rspecFactory.createInterface(n1, l, testRspec.nextIfaceName(n1));
            RspecInterface i2 = rspecFactory.createInterface(n2, l, testRspec.nextIfaceName(n2));

            testRspec.addLink(l);
        }

        nodes.clear();

        return testRspec.toGeni3Rspec();
    }
}
