package de.visone.nlpext.util;

import java.awt.Color;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;

/**
 * Experimental class for plotting several time series' to a SVG file.
 * 
 * @author <a href="mailto:endele@inf.uni-konstanz.de">Simon Endele</a>
 */
public class SVGCurver {

    private static Logger logger = Logger.getLogger(SVGCurver.class);

    public static final int stepWidth = 20; // 20
    public static final int plotHeight = 500; // 200
    public static final double RADIUS_SCALE = 1.0; // 0.5

    public static final double curveFactor = 0.4;

    public static final Vect globalOffset = new Vect(stepWidth, 0);
    public static final Vect plotDim = new Vect(66 * stepWidth, plotHeight + 40)
            .add(globalOffset);

    public static final String CSS_CONTENT = "polygon, circle {\n"
            + "\tstroke-width:0.0;\n"
            + "\tstroke:#000000;\n"
            + "}\n"
            + "polygon {\n"
            +
            // "\tfill:#ff0000;\n"+
            "\tfill-opacity:0.2;\n" + "}\n" + "polygon.opaquer {\n"
            + "\tfill-opacity:0.7;\n" + "}\n" + "circle {\n"
            + "\tfill-opacity:1.0;\n" + "}\n" + "text {\n"
            + "\tfont-size:10px;\n" + "}\n" + "text.legend {\n"
            + "\tfont-size:16px;\n" + "\tfont-weight:bold;\n" + "}\n"
            + ".color1 {\n\tfill:#ff0000;\n}\n"
            + ".color2 {\n\tfill:#00ff00;\n}\n"
            + ".color3 {\n\tfill:#0000ff;\n}\n"
            + ".color4 {\n\tfill:#ECAC00;\n}\n" + "line.grey {\n"
            + "\tstroke:#999999;\n" +
            // "\tstroke-width:1.0px;\n"+
            // "\tstroke-opacity:0.2;\n"+
            "}\n" + "line.thick {\n" + "\tstroke-width:2.0px;\n" + "}\n";

    public static final String CSS_DEFS = "<defs>\n"
            + "<style type=\"text/css\"><![CDATA[\n" + CSS_CONTENT
            + "]]></style>\n" + "</defs>\n";

    public static final String SVG_HEADER = "<?xml version='1.0' standalone='no'?>\n"
            +
            // "<?xml-stylesheet href='style.css' type='text/css'?>\n"+
            "<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>\n"
            + "<svg width='"
            + plotDim.x()
            + "' height='"
            + plotDim.y()
            + "' version='1.1' xmlns='http://www.w3.org/2000/svg'>\n";

    public static final String SVG_FOOTER = "</svg>";

    public static final String propertiesPath = "/de/visone/nlp/resource/plotomat.properties";
    private static Properties props;

    private static void loadPlotProperties() {
        try {
            props = new Properties();
            System.out.println(SVGCurver.class);
            System.out.println(SVGCurver.class.getResource(propertiesPath));
            props.load(SVGCurver.class.getResource(propertiesPath).openStream());
        } catch (IOException e) {
            System.out.println("Plot properties could not be loaded: " + e); // //////////////////////////
        }
    }

    private static String getProp(String key) {
        if (props == null || props.isEmpty()) {
            loadPlotProperties();
        }
        return props.getProperty(key);
    }

    private SVGCurver() {
    }

    public static void plotRoundedCurve(PrintWriter out,
            List< ? extends Map<String, Double>> centralitiesList,
            String... terms) throws IOException {

        double max = HashMapReader.max(centralitiesList, terms);
        double scale = plotHeight / max;
        int numMeasures = centralitiesList.size();

        double scaleU = scale * 1.2; // scale up
        double scaleD = scale * 0.8; // scale down

        String term = terms[0]; // //////////////////////////
        double[] centralities = HashMapReader.extractAsArray(centralitiesList,
                term);

        for (int i = 1; i < numMeasures; i++) {
            // if (i!=14) continue;

            double lastCentr = centralities[i - 1];
            double centr = centralities[i];
            double preCentr = (i - 2 < 0) ? lastCentr : centralities[i - 2];
            double postCentr = (i + 1 > 0) ? centr : centralities[i + 1];
            int x1 = (i - 1) * stepWidth;
            int x2 = i * stepWidth;
            int y1a = (int) (plotHeight - lastCentr * scaleU);
            int y1b = (int) (plotHeight - lastCentr * scaleD);
            int y2a = (int) (plotHeight - centr * scaleU);
            int y2b = (int) (plotHeight - centr * scaleD);

            int y0a = (int) (plotHeight - preCentr * scaleU);
            int y0b = (int) (plotHeight - preCentr * scaleD);
            int y3a = (int) (plotHeight - postCentr * scaleU);
            int y3b = (int) (plotHeight - postCentr * scaleD);

            // int xSa = (int)(x1+(x2-x1)*curveFactor);
            // int xTa = (int)(x1+(x2-x1)*(1-curveFactor));
            int xS = (int) (stepWidth * curveFactor);
            int xT = (int) (stepWidth * (1 - curveFactor));
            int ySa = (int) ((y2a - y0a) / 2.0 * curveFactor);
            int yTa = (int) ((y2a - y1a) + ((y1a - y3a) / 2.0 * (curveFactor)));
            int ySb = (int) ((y0b - y2b) / 2.0 * curveFactor);
            int yTb = (int) ((y1b - y2b) + ((y1b - y3b) / 2.0 * (curveFactor)));
            // int ySa = (int)(plotHeight- (lastCentr*scaleU+
            // (preCentr*scaleU)));

            System.out.println("ySa: " + ySa + ",  yTa: " + yTa);

            out.println("\t<path d='M"
                    + x1
                    + " "
                    + y1a
                    + " "
                    +
                    // "S"+x1+","+y1a+" "+
                    "c"
                    + xS
                    + " "
                    + ySa
                    + " "
                    + xT
                    + " "
                    + yTa
                    + " "
                    + (x2 - x1)
                    + " "
                    + (y2a - y1a)
                    + " "
                    + "L"
                    + x2
                    + " "
                    + y2b
                    + " "
                    +
                    // "c"+ xS+" "+ySb+" "+ xT+" "+yTb+" "+
                    // (x1-x2)+" "+(y1b-y2b)+" "+
                    "L"
                    + x1
                    + " "
                    + y1b
                    + " "
                    + "Z' style='fill:red; stroke:blue; stroke-width:1; stroke-opacity:0.5; fill-opacity:0.5;'/>");

            out.println("\t<circle cx='" + (x1 + xS) + "' cy='" + (y1a + ySa)
                    + "' r='1' fill='none' stroke='blue' stroke-width='1' />");
            out.println("\t<circle cx='" + (x1 + xT) + "' cy='" + (y1a + yTa)
                    + "' r='1' fill='none' stroke='black' stroke-width='1' />");
            out.println("\t<circle cx='" + (x2) + "' cy='" + (y1a)
                    + "' r='1' fill='none' stroke='green' stroke-width='1' />");

            /*
             * out.println("\t<polygon points='"+ x1+","+y1a+" "+
             * x1+","+y1b+" "+ x2+","+y2b+" "+ x2+","+y2a+
             * "' style='fill:green; stroke:green; stroke-width:1; stroke-opacity:0.5; fill-opacity:0.5;'/>"
             * );
             */
        }
    }

    public static void plotPolygonCurve(PrintWriter out,
            List< ? extends Map<String, Double>> centralitiesList1,
            List< ? extends Map<String, Double>> centralitiesList2,
            String... terms) throws IOException {

        double max1 = HashMapReader.max(centralitiesList1, terms);
        double max2 = HashMapReader.max(centralitiesList2, terms);
        // double scale = plotHeight/max;

        for (int j = 0; j < terms.length; j++) {
            String term = terms[j];
            // double[] centralities =
            // HashMapReader.extractAsArray(centralitiesList, term);

            Vect centrality1 = new Vect(HashMapReader.extractAsArray(
                    centralitiesList1, term));
            Vect centrality2 = new Vect(HashMapReader.extractAsArray(
                    centralitiesList2, term));

            double maxThickness = stepWidth * 1.0; // maximal thickness of all
                                                   // polygons
            centrality1.mult((plotHeight - maxThickness) / max1).neg()
                    .add(plotHeight);
            // centrality2.mult(maxThickness/centrality2.max());
            centrality2.mult(maxThickness / max2);

            Vect p1 = null, p2 = null;
            for (int i = 0; i < centrality1.size(); i++) {
                Vect mid = new Vect(i * stepWidth, centrality1.get(i));
                Vect q1 = new Vect(0, -centrality2.get(i)).add(mid);
                Vect q2 = new Vect(0, centrality2.get(i)).add(mid);
                if (i == 0) {
                    p1 = q1;
                    p2 = q2;
                    continue;
                }
                out.println("\t<polygon class='opaquer color" + (j + 1)
                        + "' points='" + p1.copy().add(globalOffset) + " "
                        + p2.copy().add(globalOffset) + " "
                        + q2.copy().add(globalOffset) + " "
                        + q1.copy().add(globalOffset) + "'/>");
                p1 = q1;
                p2 = q2;
            }
        }
    }

    public static void plotBoppel(PrintWriter out,
            List< ? extends Map<String, Double>> centralitiesList1,
            List< ? extends Map<String, Double>> centralitiesList2,
            String... terms) throws IOException {

        double max1 = HashMapReader.max(centralitiesList1, terms);
        double max2 = HashMapReader.max(centralitiesList2, terms);
        double scale = plotHeight / max1;

        int colID = 0; // color ID
        for (String term : terms) {
            colID++;
            Vect centrality1 = new Vect(HashMapReader.extractAsArray(
                    centralitiesList1, term));
            Vect centrality2 = new Vect(HashMapReader.extractAsArray(
                    centralitiesList2, term));
            double maxRad = stepWidth * RADIUS_SCALE; // maximal radius of all
                                                      // circles
            Vect y = centrality1.copy().mult((plotHeight - maxRad) / max1)
                    .neg().add(plotHeight);
            centrality2 = centrality2.apply("sqrt"); // sqrt all entries in
                                                     // centr2 -> radius of
                                                     // circles with area ~
                                                     // centr2
            // Vect r = centrality2.copy().mult(maxRad/centrality2.max());
            Vect r = centrality2.copy().mult(maxRad / Math.sqrt(max2));

            Vect mid = null;
            for (int i = 0; i < y.size(); i++) {
                Vect newMid = new Vect(i * stepWidth, y.get(i));
                double ri = Math.max(0.0, r.get(i)); // max(1.0, ...)
                if (i >= 1) {
                    Vect line = newMid.copy().sub(mid);
                    double dist = line.length();
                    double angle = Math.PI
                            - Math.acos((r.get(i) - r.get(i - 1)) / dist);
                    Vect offsetB = line.rotate(angle).norm();
                    Vect offsetA = line.rotate(-angle).norm();
                    double ri1 = Math.max(0.0, r.get(i - 1)); // max(1.0, ...)
                    out.println("\t<polygon class='color"
                            + colID
                            + "' points='"
                            + mid.copy().add(offsetB.copy().mult(ri1))
                                    .add(globalOffset)
                            + " "
                            + newMid.copy().add(offsetB.copy().mult(ri))
                                    .add(globalOffset)
                            + " "
                            + newMid.copy().add(offsetA.copy().mult(ri))
                                    .add(globalOffset)
                            + " "
                            + mid.copy().add(offsetA.copy().mult(ri1))
                                    .add(globalOffset) + "'/>");
                }
                mid = newMid;
                Vect circleCenter = new Vect(i * stepWidth, y.get(i))
                        .add(globalOffset);
                out.println("\t<circle class='color" + colID + "' cx='"
                        + circleCenter.x() + "' cy='" + circleCenter.y()
                        + "' r='" + ri + "' />");
                // out.println("\t<circle class='col"+colID+"' cx='"+i*stepWidth+"' cy='"+y.get(i)+"' r='1' style='fill:#000000;'/>");
            }
        }
    }

    public static void plotGrid(PrintWriter out, int numMeasures) {
        out.println("\t<!-- Grid and axis labels -->");
        GregorianCalendar calendar = new GregorianCalendar(2001, 9 - 1, 11); // month
                                                                             // is
                                                                             // 0-based
        for (int i = 0; i < numMeasures; i++) {
            int x = i * stepWidth;
            String netwID = (i + 1 < 10) ? "0" + (i + 1) : "" + (i + 1);
            int idTextY = plotHeight + 12;
            int dateTextY = idTextY + 3 + ((i % 2 == 0) ? 12 : 22);
            int dayOfMonth = calendar.get(Calendar.DAY_OF_MONTH);
            int month = calendar.get(Calendar.MONTH) + 1; // month is 0-based
            String sunday = (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY) ? "thick "
                    : "";
            // String day = month+"/"+dayOfMonth; //english
            String day = dayOfMonth + "." + month + "."; // german
            Vect p1 = new Vect(x, 0).add(globalOffset);
            Vect p2 = new Vect(x, plotHeight).add(globalOffset);
            out.println("\t<line class='" + sunday + "grey' x1='" + p1.x()
                    + "' y1='" + p1.y() + "' x2='" + p2.x() + "' y2='" + p2.y()
                    + "'/>");

            Vect idTextPos = new Vect(x - 5, idTextY).add(globalOffset);
            out.println("\t<text x='" + idTextPos.x() + "' y='" + idTextPos.y()
                    + "'>" + netwID + "</text>");
            Vect dateTextPos = new Vect(x - 10, dateTextY).add(globalOffset);
            out.println("\t<text x='" + dateTextPos.x() + "' y='"
                    + dateTextPos.y() + "'>" + day + "</text>");
            calendar.roll(Calendar.DAY_OF_YEAR, true);
        }
        // top line and x-axis
        Vect q1 = new Vect(-stepWidth / 2.0, 0).add(globalOffset);
        Vect q2 = new Vect(stepWidth * numMeasures - stepWidth / 2.0, 0)
                .add(globalOffset);
        out.println("\t<line class='grey' x1='" + q1.x() + "' y1='" + q1.y()
                + "' x2='" + q2.x() + "' y2='" + q2.y() + "'/>");
        q1.add(new Vect(0, plotHeight));
        q2.add(new Vect(0, plotHeight));
        out.println("\t<line class='grey' x1='" + q1.x() + "' y1='" + q1.y()
                + "' x2='" + q2.x() + "' y2='" + q2.y() + "'/>");
    }

    private static final Vect legendOffset = new Vect(30, 30);
    private static final int legendRadius = 6;
    private static final int legendLength = 20;
    private static final int legendRowHeight = 22;

    public static void plotLegend(PrintWriter out, String[] terms, int style) {
        out.println("\t<!-- Legend -->");
        int legendLength = (style == 0) ? SVGCurver.legendLength
                : SVGCurver.legendLength + 2 * legendRadius;
        for (int i = 0; i < terms.length; i++) {
            int colorID = i + 1;
            Vect vect = new Vect(0, i * legendRowHeight).add(legendOffset);
            if (style == 0)
                out.println("\t<circle class='color" + colorID + "' cx='"
                        + vect.x() + "' cy='" + vect.y() + "' r='"
                        + legendRadius + "' />");
            if (style == 1)
                vect.sub(new Vect(legendRadius, 0));
            String polyStyle = (style == 0) ? "" : "opaquer ";
            out.println("\t<polygon class='" + polyStyle + "color" + colorID
                    + "' points='" + vect.copy().add(new Vect(0, legendRadius))
                    + " " + vect.copy().add(new Vect(0, -legendRadius)) + " "
                    + vect.copy().add(new Vect(legendLength, -legendRadius))
                    + " "
                    + vect.copy().add(new Vect(legendLength, legendRadius))
                    + "'/>");
            vect.add(new Vect(legendLength, 0));
            if (style == 0)
                out.println("\t<circle class='color" + colorID + "' cx='"
                        + vect.x() + "' cy='" + vect.y() + "' r='"
                        + legendRadius + "' />");
            if (style == 0)
                vect.add(new Vect(legendRadius, 0));
            vect.add(new Vect(5, legendRadius));
            out.println("\t<text class='legend color" + colorID + "' x='"
                    + vect.x() + "' y='" + vect.y() + "'>" + terms[i]
                    + "</text>");
        }
    }

    public static void createCSSFile(File dir) throws IOException {
        File outputFile = new File(dir, "style.css");
        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
                outputFile)));

        out.println(CSS_CONTENT);
        out.flush();
        out.close();
    }

    public static String getXMLColorString(Color c) {
        int i = c.getRGB() | 0xff000000;
        return "#" + String.format("%6h", i).substring(2, 8);
    }

    public static class CSVFileFilter implements FilenameFilter {
        public boolean accept(File dir, String name) {
            return name.endsWith(".csv");
        }
    }

    public static void main(String[] args) throws IOException {
        // System.out.println(getXMLColorString(Color.GREEN));
        if (args.length < 3) {
            System.out
                    .println("Usage is:: java SVGCurver <csvDir1> <csvDir2> <style[0,1]> <term...>");
            System.exit(-1);
        }
        String csvDir1 = args[0];
        String csvDir2 = args[1];
        int style = Integer.parseInt(args[2]);
        String[] terms = new String[args.length - 3];
        System.arraycopy(args, 3, terms, 0, args.length - 3); // args[2].split(" ");

        File[] files1 = new File(csvDir1).listFiles(new CSVFileFilter());
        Arrays.sort(files1);
        File[] files2 = new File(csvDir2).listFiles(new CSVFileFilter());
        Arrays.sort(files2);
        if (files1.length != files2.length) {
            System.out
                    .println("InputDirs do not contain the same number of cvs-files: "
                            + files1.length + ", " + files2.length);
            return;
        } else {
            System.out.println("Found " + files1.length
                    + " files in inputDirs respectively.");
        }

        String outputName = terms[0];
        for (int i = 1; i < terms.length; i++) {
            outputName = outputName + "-" + terms[i];
        }
        File output = new File(outputName + ".svg");
        System.out.println("output: " + output);

        List<LinkedHashMap<String, Double>> betweennessList = HashMapReader
                .readFiles(files1, "Betweenness");
        List<LinkedHashMap<String, Double>> degreeList = HashMapReader
                .readFiles(files2, "Degree");
        // List<LinkedHashMap<String, Double>> centralitiesList =
        // getTestList(args[0]);
        int numMeasures = betweennessList.size();

        PrintWriter out = new PrintWriter(new BufferedWriter(new FileWriter(
                output)));
        out.println(SVG_HEADER);
        out.println(CSS_DEFS);
        plotGrid(out, numMeasures);
        plotLegend(out, terms, style);

        if (style == 0) {
            plotBoppel(out, betweennessList, degreeList, terms);
        } else {
            plotPolygonCurve(out, betweennessList, degreeList, terms);
        }

        out.println(SVG_FOOTER);
        out.flush();
        out.close();

        // createCSSFile(output.getParentFile());

        System.out.println("Done.");
    }

    /*
     * public static List<LinkedHashMap<String, Double>> getTestList(String key)
     * { LinkedHashMap<String, Double> map1 = new LinkedHashMap<String,
     * Double>(); LinkedHashMap<String, Double> map2 = new LinkedHashMap<String,
     * Double>(); map1.put(key, 0.1); map2.put(key, 1.0);
     * 
     * List<LinkedHashMap<String, Double>> list = new
     * LinkedList<LinkedHashMap<String, Double>>(); list.add(new
     * LinkedHashMap<String, Double>()); list.add(map2); list.add(map1); return
     * list; }
     */
}