// $Id: Qedeq2Latex.java,v 1.8 2004/09/10 16:41:54 m31 Exp $
//
// This file is part of the program suite "Principia Mathematica II"
// which is a working prototype for the main project:
// "Hilbert II" - http://www.qedeq.org
//
// Copyright 2001-2004  Michael Meyling <mime@qedeq.org>.
//
// "Principia Mathematica II" is free software; you can redistribute
// it and/or modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

package com.meyling.principia.latex;


import com.meyling.principia.io.*;

import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;


/**
 * This class provides static methods for converting module files
 * into LaTex files.
 *
 * @version $Revision: 1.8 $
 * @author  Michael Meyling
 */
public final class Qedeq2Latex {


    private final static Map convert = new HashMap();


    /**
     * Make attribut initialization.
     */
    static {
        // fill hash map
        convert.put("MODULE",
            new Operator(
                "MODULE",
                "\\documentclass[draft]{amsart}\n"
                + "\\usepackage{amsmath,amsthm,amsfonts}\n",
                "%%% ====================================================================\n"
                + "%%% @LaTeX-file generated by " + Qedeq2Latex.class.getName() + "\n"
                + "%%% ====================================================================\n\n"
//                + "\\documentclass[draft]{amsart}\n"
                + "\\documentclass{amsart}\n"
                + "\\usepackage{amsmath,amsthm,amsfonts}\n"
                + "\\usepackage[]{color}\n"
                + "\\usepackage[letterpaper,colorlinks,breaklinks,backref]{hyperref}\n"
                + "\\newtheorem{thm}{Theorem}[section]\n"
                + "\\newtheorem{cor}[thm]{Corollary}\n"
                + "\\newtheorem{prop}{Proposition}\n"
                + "\\newtheorem{lem}[thm]{Lemma}\n"
                + "\\theoremstyle{remark}\n"
                + "\\newtheorem*{rmk}{Remark}\n"
                + "\\sloppy"
                + "\n",
//                + "\\begin{document}\n",
                "",
                "\n\\cite{THE}\n"
                + "\\bibliographystyle{unsrt}\n"
                + "\\bibliography{testurl}\n"
                + "\\end{document}\n"));

        convert.put("HEADER",
            new Operator(
                "HEADER",
                "",
                "",
                "\n",
                "\n") {
                String spec = "";
                // Q & D: exchange first and second argument
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 0) {
                        spec = argument;
                        return "";
                    } else if (i == 1) {
                        return argument + spec;
                    } else if (i == 3) {
                        return "Module admin: "+ argument + "\n";
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });
        convert.put("SPEC",
            new Operator(
                "SPEC",
                "\n",
                "\n"
                + "\\begin{tabular}[t]{ll}\n",
                "",
                "\\end{tabular}\n\n") {
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 0) {
                        return "Name: & {\\bf " + argument + "} \\\\\n";
                    } else if (i == 1) {
                        return "Module version: & {\\bf " + argument + "} \\\\\n";
                    } else if (i == 2) {
                        return "Rule version: & {\\bf " + argument + "} \\\\\n";
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });
        convert.put("NAME",
            new Operator(
                "NAME",
                "",
                "",
                "",
                ""
            ));
        convert.put("VERSION",
            new Operator(
                "VERSION",
                "",
                "",
                "",
                ""
            ));
        convert.put("LOCATIONS",
            new Operator(
                "LOCATIONS",
                "Locations: &",
                "Locations: & {\\bf ",
                "",
                "} \\\\\n"
            ));
        convert.put("LOCATION",
            new Operator(
                "LOCATION",
                "",
                "",
                "",
                ""
            ));
        convert.put("HEADLINE",
            new Operator(
                "HEADLINE",
                "\\title",
                "\\title{",
                "",
                "}\n\n\\begin{document}\n\\maketitle\n"
            ));
        convert.put("DESCRIPTION",
            new Operator(
                "DESCRIPTION",
                "",
                "\\begin{abstract}\n"
                + "This \\LaTeX file is part of the project "
                + "{\\bf Principia Mathematica II} which is an "
                + "open source project that wants to present mathematical "
                + "knowledge in a formal correct form.\n"
                + "It includes a proof verifier which checks a mathematical "
                + "proof written in a certain formal language and converters "
                + "to other formats (HTML, LaTeX).\n"
                + "See "
                + "\\url{http://www.meyling.com/principia/principia.html}"
                + " for details about this project.\n\n\\medskip\n\n",
                "",
                "\n\\end{abstract}\n"
            ));
        convert.put("EMAIL",
            new Operator(
                "EMAIL",
                "",
                "",
                "",
                ""
            ));
        convert.put("AUTHORS",
            new Operator(
                "AUTHORS",
                "author(s): ",
                "author(s): ",
                "",
                ""
            ));
        convert.put("AUTHOR",
            new Operator(
                "AUTHOR",
                "\\author{}",
                "\\author{",
                "}"
            ));
        convert.put("IMPORTS",
            new Operator(
                "IMPORTS",
                "",
                "\nNeeds following modules:\n\n",
                "\n",
                "\n"
            ));
        convert.put("IMPORT",
            new Operator(
                "IMPORT",
                "",
                "",
                "",
                "") {
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 1) {
                        return "";  // Q & D: throw away label
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });
        convert.put("USEDBY",
            new Operator(
                "USEDBY",
                "",
                "\nIs used by following modules:\n\n",
                "\n",
                "\n"
            ));
        convert.put("PARAGRAPHS",
            new Operator(
                "PARAGRAPHS",
                "\n",
                "\n\n\\bigskip\n",
                "",
                ""
            ));
        convert.put("PARAGRAPH",
            new Operator(
                "PARAGRAPH",
                "\n",
                "\n",
                "\n",
                "") {
                String label = "";

                // Q & D
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 0) {
                        label = argument;
                        return "";
                    } else if (!arguments[i].isAtom()) {
                        int pos = argument.indexOf("\n");
                        if (pos >= 0 ) {
                            return argument.substring(0, pos)
                                + "\n" + label + "\n" + argument.substring(pos);
                        } else {
                            return label + "\n" + argument + "\n";
                        }
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });
        convert.put("PROPOSITION",
            new Operator(
                "PROPOSITION",
                "\\begin{thm}\n\\end{thm}\n",
                "\\begin{thm}\n",
                "",
                "\\end{thm}\n"
            ));
        convert.put("SENTENCE",
            new Operator( "SENTENCE",
                "\\begin{displaymath}\n\\end{displaymath}\n",
                "\\begin{displaymath}\n",
                "\n\\end{displaymath}\n"
            ));
        convert.put("PROOF",
            new Operator(
                "PROOF",
                "\\begin{proof} \\end{proof}\n",
                "\\begin{proof}\n\\mbox{}\\\\\n\\begin{tabular}{rp{5cm}p{4cm}r}\n",
                "\\\\\n",
                "\\\\ & & & \\qedhere\n\\end{tabular}\n\\end{proof}\n") {
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    return "  $" + (i + 1) + "$ & " + argument;
                }
            });
        convert.put("LINE",
            new Operator("LINE",
                "\\\\\n",
                "", " & ",   " \\\\\n"){
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 0) {
                        return "$" + argument + "$";
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });

        convert.put("LABEL",  new Operator("LABEL",     "\\label{}", "\\label{", "} "));
        convert.put("LINK",   new Operator("LINK",      "\\ref{}",   "\\ref{",   "} "));

        convert.put("NOT",    new Operator("NOT",       "\\neg",             "\\neg "));
        convert.put("AND",    new Operator("AND",       "\\wedge",           "(", "\\ \\wedge \\ ", ")"));
        convert.put("OR",     new Operator("OR",        "\\vee",             "(", "\\ \\vee \\ ",   ")"));
        convert.put("IMPL",   new Operator("IMPL",      "\\rightarrow",      "(", "\\ \\rightarrow \\ ",   ")"));
        convert.put("EQUI",   new Operator("EQUI",      "\\leftrightarrow",  "(", "\\ \\leftrightarrow \\ ",   ")"));
        convert.put("FORALL", new Operator("FORALL",    "\\forall",          "\\forall" , "(", ")"));
        convert.put("EXISTS", new Operator("EXISTS",    "\\exists",          "\\exists" , "(", ")"));
        convert.put("PROP",
            new Operator("PROP",      "P") {
                String getPraefix() {
                    return "";
                }

                String getArgument(final int i, final String argument, final Function[] arguments) {
                    try {
                        final int value = Integer.parseInt(argument);
                        switch (value) {
                        case 1: return "P";
                        case 2: return "Q";
                        case 3: return "A";
                        default:
                            if (value > 0) {
                                return "P_" + (value - 3);
                            } else {
                                return "P_{" + value + "}";
                            }
                        }
                    } catch (Exception e) {
                        return "P_{" + argument + "}";
                    }
                }

                String getPostfix() {
                    return " ";
                }
            });

        convert.put("ADDAXIOM",
            new Operator(
                "ADDAXIOM",
                "axiom added",
                "axiom " ,
                "",
                " added"
            ));

        convert.put("ADDSENTENCE",
            new Operator(
                "ADDSENTENCE",
                "proposition added",
                "proposition " ,
                "",
                " added"
            ));

        convert.put("REPLACEPROP",
            new Operator(
                "REPLACEPROP",
                "replace ",
                "in ",
                "",
                "") {
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 0) {
                        return  "$" + argument + "$";
                    } else if (i == 1) {
                        return " replace $" + argument + "$";
                    } else if (i == 2) {
                        return " by $" + argument + "$";
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });

        convert.put("REVERSEABBREVIATION",
            new Operator(
                "REVERSEABBREVIATION",
                "reverse ",
                "in ",
                "",
                "") {
                String getArgument(final int i, final String argument, final Function[] arguments) {
                    if (i == 0) {
                        return  "$" + argument + "$";
                    } else if (i == 1) {
                        return " reverse " + argument;
                    } else if (i == 2) {
                        return " at occurrence $" + argument + "$";
                    } else {
                        return super.getArgument(i, argument, arguments);
                    }
                }
            });
        convert.put("MODUSPONENS",
            new Operator(
                "MODUSPONENS",
                "modus ponens",
                "modus ponens of $",
                "$ with $",
                "$") {
            });
    }


    /**
     * Takes the given file names (without ".tex") and converts them
     * into html. The resulting files are at the same position but
     * have the ending ".html".
     *
     * @param   argument    list of LaTex files to convert
     */
    public static final String convert (final String qedeq) {
        TextInput input = null;
        StringBuffer in = new StringBuffer(qedeq);
        StringBuffer out = new StringBuffer();
        input = new TextInput(in, "", "");
        Output output = new Output(out);
        readAll(input, output);
        return out.toString();
    }




    /**
     * Takes the given file names (without ".tex") and converts them
     * into html. The resulting files are at the same position but
     * have the ending ".html".
     *
     * @param   argument    list of LaTex files to convert
     */
    public static final void main (String[] argument) {
        System.out.println("Converting into LaTex...");
        for (int i = 0; i < argument.length; i++) {
            TextInput input = null;
            try {
                StringBuffer in = new StringBuffer(
                    Utility.loadFile(argument[i] + ".qedeq"));
                StringBuffer out = new StringBuffer();
                input = new TextInput(in, argument[i] + ".qedeq",
                    argument[i] + ".txt");
                Output output = new Output(out);
                readAll(input, output);
                Utility.saveFile(argument[i] + ".tex", new String(out));
            } catch (Exception e) {
                e.printStackTrace();
                System.err.println(argument[i] + ".qedeq"
                    + ":" + input.getRow() + ":"
                    + input.getColumn() + ":");
                System.err.println(e.getMessage());
                System.err.println(input.getLine());
                final StringBuffer pointer = Utility.getSpaces(input.getColumn());
                for (int j = 0; j < input.getColumn() - 1; j++) {
                    pointer.append(' ');   // TODO Q & D
                }
                pointer.append("^");
                System.err.println(pointer);
            }
        }
        System.out.println();
        System.out.println("... converting done!");
    }


    public final static void readAll(final TextInput input, final Output output) {
        while (!input.isEmpty()) {
            try {
                final Function function = readArgument(input);
                output.write(function.getLatex());
            } catch (IllegalArgumentException e) {
                System.out.println(e);
                // ignore

            } catch (NullPointerException e) {  // TODO
                System.out.println(e);
                // ignore
                System.out.println(input.showLinePosition());   // TODO
//                throw e;

            }
            dealWithWhitespace(input);
            while (!input.isEmpty()) {
                output.write(input.readChar());
            }
        }
    }


    public final static Function readArgument(final TextInput input) {

        String key = "";
        dealWithWhitespace(input);
        if (!input.isEmpty()) {
            final char c = input.getChar();
            if (c == ')') {
                return null;
            } else if (Character.isLetter(c)) {
                key = input.readLetterDigitString();
//                System.out.print(key);
                if (convert.containsKey(key)) {
                    return readArgumentList(input, (Operator) convert.get(key));
                } else {
                    return readArgumentList(input, new Operator(key, key, key + " (", ") "));
                }
            } else if (c == '\"') {
                key = input.readQuoted();
//                System.out.print(Utility.quote(key));
//                output.write(key);
                return new Function(new Value(key, true));
            } else if (Character.isDigit(c)) {
                while (Character.isDigit(input.getChar())) {
                    key += input.readChar();
                }
//                System.out.print(key);
//                output.write(key);
                return new Function(new Value(key, false));
            }
        }
        return null;
    }


    private final static Function readArgumentList(final TextInput input,
            final Operator operator)
            throws IllegalArgumentException {

        final List arguments = new ArrayList();

        dealWithWhitespace(input);
        if (input.isEmpty() || input.getChar() != '(') {
            return new Function(operator);
        }
        input.readChar();       // read '('
        int i = 0;
        while (!input.isEmpty() && ')' != input.getChar()) {
            final Function function = readArgument(input);
            if (function != null) {
                arguments.add(function);
            }
            dealWithWhitespace(input);
            if (input.getChar() == ',') {
                input.readChar();
            }
            i++;
        }
        dealWithWhitespace(input);
        if (!input.isEmpty()) {
            input.readChar();       // read ')'
        }
        return new Function(operator, (Function[]) arguments.toArray( new Function[0]));
    }



    private final static void dealWithWhitespace(final TextInput input) {
        while (!input.isEmpty() && Character.isWhitespace(input.getChar())) {
            input.readChar();
        }
    }



    private static class Operator {

        final String qedeq;

        final String latex;

        final String praefix;

        final String infix;

        final String postfix;


        Operator(final String qedeq,
                 final String latex) {
            this.qedeq = qedeq;
            this.latex = latex;
            this.praefix = latex;
            this.infix = ", ";
            this.postfix = "";
        }

        Operator(final String qedeq,
                 final String latex,
                 final String praefix) {
            this.qedeq = qedeq;
            this.latex = latex;
            this.praefix = praefix;
            this.infix = ", ";
            this.postfix = "";
        }

        Operator(final String qedeq,
                 final String latex,
                 final String praefix,
                 final String postfix) {
            this.qedeq = qedeq;
            this.latex = latex;
            this.praefix = praefix;
            this.infix = ", ";
            this.postfix = postfix;
        }


        Operator(final String qedeq,
                 final String latex,
                 final String praefix,
                 final String infix,
                 final String postfix) {
            this.qedeq = qedeq;
            this.latex = latex;
            this.praefix = praefix;
            this.infix = infix;
            this.postfix = postfix;
        }

        String getQedeq() {
            return this.qedeq;
        }

        String getLatex() {
            return this.latex;
        }

        String getPraefix() {
            return this.praefix;
        }

        String getArgument(final int i, final String argument, final Function[] arguments) {
            if (i > 0) {
                return this.infix + argument;
            } else {
                return argument;
            }
        }

        String getPostfix() {
            return this.postfix;
        }

    }

    private static class Value {

        final String qedeq;
        final String latex;

        Value(final String value, final boolean quote) {
            if (quote) {
                this.qedeq = Utility.quote(value);
                this.latex = value;
            } else {
                this.qedeq = value;
                this.latex = value;
            }
        }

        String getQedeq() {
            return this.qedeq;
        }

        String getLatex() {
            return this.latex;
        }

    }


    private static class Function {

        final Operator operator;

        final Function[] arguments;

        final Value atom;


        Function(final Value atom) {
            this.operator = null;
            this.arguments = new Function[0];
            this.atom = atom;
        }


        Function(final Operator operator) {
            this.operator = operator;
            this.arguments = new Function[0];
            this.atom = null;
        }

        Function(Operator operator, Function[] arguments) {
            this.operator = operator;
            this.arguments = arguments;
            this.atom = null;
        }


        public boolean isAtom() {
            return operator == null;
        }

        public String getQedeq() {
            if (operator == null) {
                return atom.getQedeq();
            } else {
                if (arguments.length == 0) {
                    return operator.getQedeq();
                } else {
                    final StringBuffer buffer = new StringBuffer();
                    buffer.append(operator.getQedeq()).append("(");
                    for (int i = 0; i < arguments.length; i++) {
                        if (i > 0) {
                            buffer.append(",");
                        }
                        buffer.append(arguments[i].getQedeq());
                    }
                    buffer.append(")");
                    return buffer.toString();
                }
            }
        }

        public String getLatex() {
            if (operator == null) {
                return atom.getLatex();
            } else {
                if (arguments.length == 0) {
                    return operator.getLatex();
                } else {
                    final StringBuffer buffer = new StringBuffer();
                    buffer.append(operator.getPraefix());
                    for (int i = 0; i < arguments.length; i++) {
                        buffer.append(operator.getArgument(i,
                            arguments[i].getLatex(), arguments));
                    }
                    buffer.append(operator.getPostfix());
                    return buffer.toString();
                }
            }
        }

    }

}
