import java.io.*;
import java.util.*;
import java.awt.Color;

/**
 * A main class for test visualizer.
 * @author Maxim Buzdalov
 */ 
public class Base implements Graphics {
    PrintWriter texWriter;
    PrintWriter mpWriter;

    Random random = new Random();
    String pathName = getRandom();

    double minX, maxX, minY, maxY;

    final double maxW = 150;

    void reset() {
        script.clear();
        minX = minY = Double.POSITIVE_INFINITY;
        maxX = maxY = Double.NEGATIVE_INFINITY;
    }

    double convertX(double arg) {
        arg -= minX;
        double quot = maxW / Math.max(maxX - minX, maxY - minY);
        double maxx = quot * (maxX - minX);
        arg *= quot;
        arg += (maxW - maxx) / 2;
        return arg;
    }

    double convertY(double arg) {
        arg -= minY;
        double quot = maxW / Math.max(maxX - minX, maxY - minY);
        double maxy = quot * (maxY - minY);
        arg *= quot;
        arg += (maxW - maxy) / 2;
        return arg;
    }

    void add(double x, double y) {
        minX = Math.min(minX, x);
        maxX = Math.max(maxX, x);
        minY = Math.min(minY, y);
        maxY = Math.max(maxY, y);
    }

    static interface ScriptObject {
        void execute();
    }

    class PutPixelScript implements ScriptObject {
        double x, y;
        PutPixelScript(double xx, double yy) {
            x = xx;
            y = yy;
            add(x, y);
        }
        public void execute() {
            mpWriter.printf("drawdot (%fu, %fu);\n", convertX(x), convertY(y));
        }
    }

    class PutPixelScript2 implements ScriptObject {
        double x, y;
        Color color;
        PutPixelScript2(double xx, double yy, Color c) {
            x = xx;
            y = yy;
            color = c;
            add(x, y);
        }
        public void execute() {
            mpWriter.printf("drawdot (%fu, %fu) withcolor \n", convertX(x), convertY(y));
            printColor(color);
            mpWriter.println(";");
        }
    }

    class PutLineScript implements ScriptObject {
        double[] xs, ys;
        boolean closed, curved;
        PutLineScript(double[] xs, double[] ys, boolean closed, boolean curved) {
            this.xs = xs.clone();
            this.ys = ys.clone();
            this.closed = closed;
            this.curved = curved;
            for (int i = 0; i < xs.length; i++) {
                add(xs[i], ys[i]);
            }
        }
        public void execute() {
            for (int i = 0; i < xs.length; i++) {
                xs[i] = convertX(xs[i]);
                ys[i] = convertY(ys[i]);
            }
            String path = createPath(xs, ys, closed, curved);
            mpWriter.printf("draw %s;\n", path);
        }
    }

    class PutLineScript2 implements ScriptObject {
        double[] xs, ys;
        boolean closed, curved;
        Color color;

        PutLineScript2(double[] xs, double[] ys, boolean closed, boolean curved, Color color) {
            this.xs = xs.clone();
            this.ys = ys.clone();
            this.closed = closed;
            this.curved = curved;
            this.color = color;
            for (int i = 0; i < xs.length; i++) {
                add(xs[i], ys[i]);
            }
        }
        public void execute() {
            for (int i = 0; i < xs.length; i++) {
                xs[i] = convertX(xs[i]);
                ys[i] = convertY(ys[i]);
            }
            String path = createPath(xs, ys, closed, curved);
            mpWriter.printf("draw %s withcolor ", path);
            printColor(color);
            mpWriter.println(";");
        }
    }

    class FillLineScript implements ScriptObject {
        double[] xs, ys;
        boolean curved;
        FillLineScript(double[] xs, double[] ys, boolean curved) {
            this.xs = xs.clone();
            this.ys = ys.clone();
            this.curved = curved;
            for (int i = 0; i < xs.length; i++) {
                add(xs[i], ys[i]);
            }
        }
        public void execute() {
            for (int i = 0; i < xs.length; i++) {
                xs[i] = convertX(xs[i]);
                ys[i] = convertY(ys[i]);
            }
            String path = createPath(xs, ys, true, curved);
            mpWriter.printf("fill %s;\n", path);
        }
    }

    class FillLineScript2 implements ScriptObject {
        double[] xs, ys;
        boolean curved;
        Color color;

        FillLineScript2(double[] xs, double[] ys, boolean curved, Color color) {
            this.xs = xs.clone();
            this.ys = ys.clone();
            this.curved = curved;
            this.color = color;
            for (int i = 0; i < xs.length; i++) {
                add(xs[i], ys[i]);
            }
        }
        public void execute() {
            for (int i = 0; i < xs.length; i++) {
                xs[i] = convertX(xs[i]);
                ys[i] = convertY(ys[i]);
            }
            String path = createPath(xs, ys, true, curved);
            mpWriter.printf("fill %s withcolor ", path);
            printColor(color);
            mpWriter.println(";");
        }
    }

    ArrayList<ScriptObject> script = new ArrayList<ScriptObject>();

    void executeScript() {
        for (ScriptObject o : script) {
            o.execute();
        }
    }

    /* Graphics implementation */
    public void putPixel(double x, double y) {
        script.add(new PutPixelScript(x, y));
    }

    public void putPixel(double x, double y, Color color) {
        script.add(new PutPixelScript2(x, y, color));
    }

    public void putLine(double[] xs, double[] ys, boolean closed, boolean curved) {
        script.add(new PutLineScript(xs, ys, closed, curved));
    }

    public void putLine(double[] xs, double[] ys, boolean closed, boolean curved, Color color) {
        script.add(new PutLineScript2(xs, ys, closed, curved, color));
    }

    public void fillLine(double[] xs, double[] ys, boolean curved) {
        script.add(new FillLineScript(xs, ys, curved));
    }

    public void fillLine(double[] xs, double[] ys, boolean curved, Color color) {
        script.add(new FillLineScript2(xs, ys, curved, color));
    }

    private String createPath(double[] xs, double[] ys, boolean closed, boolean curved) {
        String div = curved ? ".." : "--";

        StringWriter sw = new StringWriter();

        for (int i = 0; i < xs.length; i++) {
            sw.write(String.format("(%fu, %fu)", xs[i], ys[i]));
            if (i != xs.length - 1) {
                sw.write(div);
            }
        }
        if (closed) {
            sw.write(div + "cycle");
        }
        return sw.toString();
    }

    private String getRandom() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 8; i++) {
            sb.append((char)(random.nextInt(26) + 'a'));
        }
        return sb.toString();
    }

    private void printColor(Color color) {
        mpWriter.printf("(%f red + %f green + %f blue)", color.getRed() / 255.0, color.getGreen() / 255.0, color.getBlue() / 255.0);
    }

    private void visualize(Visualizer v, File test, int number) throws IOException {
        System.err.print("Visualizing " + test.getName() + "...");

        mpWriter.printf("beginfig(%d)\n", number);

        Scanner in = new Scanner(test);

        reset();
        v.parseTest(in, this);
        executeScript();

        mpWriter.println("endfig;");

        texWriter.println("\\begin{center}");
        texWriter.println("\\end{center}");
        texWriter.printf("\\includegraphics{tests.%d}", number);
        texWriter.println("\\newpage");

        System.err.println(" done.");
    }

    private Base(Visualizer v, File srcDir, File destDir, String fileName) throws IOException {
        destDir.mkdir();

        File mpFile = new File(destDir, "tests.mp");
        File texFile = new File(destDir, "tests.tex");

        mpWriter = new PrintWriter(mpFile);
        texWriter = new PrintWriter(texFile);

        mpWriter.println("numeric u; u = 1mm;");
        mpWriter.println("pickup pencircle scaled 0.1u;");

        if (fileName != null) {
            visualize(v, new File(srcDir, fileName), 1);
        } else {
            File f;
            for (int i = 1; i < 100 && (f = new File(srcDir, String.format("%02d", i))).exists(); i++) {
                visualize(v, f, i);
            }
        }

        mpWriter.println("end.");
        mpWriter.close();
        texWriter.close();

        PrintWriter runner = new PrintWriter(new File(destDir, "r.cmd"));
        runner.println("@echo off");
        runner.println("mp tests.mp");
        runner.println("latex -terminal=oem vizi.tex");
        runner.println("latex -terminal=oem vizi.tex");
        runner.println("dvips vizi.dvi");
        runner.println("dvipdfm -p a4 vizi.dvi");
        runner.close();

        PrintWriter runnerUnix = new PrintWriter(new File(destDir, "r.sh"));
        runnerUnix.println("#!/bin/sh");
        runnerUnix.println("mpost tests.mp");
        runnerUnix.println("latex vizi.tex");
        runnerUnix.println("latex vizi.tex");
        runnerUnix.println("dvips vizi.dvi -o vizi.ps");
        runnerUnix.println("ps2pdf vizi.ps");
        runnerUnix.close();

        PrintWriter cleaner = new PrintWriter(new File(destDir, "clean.cmd"));
        cleaner.println("del tests.??");
        cleaner.println("del *.dvi");
        cleaner.println("del *.log");
        cleaner.println("del *.pdf");
        cleaner.println("del *.ps");
        cleaner.println("del *.aux");
        cleaner.println("del *.tex");
        cleaner.close();

        PrintWriter viziTeX = new PrintWriter(new File(destDir, "vizi.tex"));
        viziTeX.println("\\documentclass[11pt,a4paper,oneside]{article}");
        viziTeX.println("\\usepackage[T2A]{fontenc}");
        viziTeX.println("\\usepackage[cp1251]{inputenc}");
        viziTeX.println("\\usepackage[english,russian]{babel}");
        viziTeX.println("\\usepackage[russian]{olymp}");
        viziTeX.println("\\usepackage[dvips]{graphicx}");
        viziTeX.println("\\usepackage{amsmath}");
        viziTeX.println("\\renewcommand{\\contestname}{Визуализация тестов}");
        viziTeX.println("\\binoppenalty=10000");
        viziTeX.println("\\relpenalty=10000");
        viziTeX.println("\\exhyphenpenalty=10000");
        viziTeX.println("\\begin{document}");
        viziTeX.println("\\input tests.tex");
        viziTeX.println("\\end{document}");
        viziTeX.close();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        Locale.setDefault(Locale.US);
        if (args.length < 3 || args.length > 4) {
            System.err.println("Usage: <visualizer class name> <input directory> <output directory> [<name of test>]");
        } else {
            String visualizerName = args[0];
            Visualizer v = (Visualizer) (Class.forName(visualizerName).newInstance());
            File srcDir = new File(args[1]);
            File destDir = new File(args[2]);

            new Base(v, srcDir, destDir, args.length == 4 ? args[3] : null);
        }
    }
}
