/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package compiler;

import API.Api;
import API.Instruction;
import API.Variaveis;
import IntermediaryCode.Code;
import IntermediaryCode.TacGenInterface;
import ast.AbstractSyntaxTree;
import ast.Semantic;
import common.ErrorReport;
import common.Log;
import grammar.IvannosysGrammarLexer;
import grammar.IvannosysGrammarParser;
import grammar.visitorAndWalkers.IvannosysListener;
import grammar.visitorAndWalkers.IvannosysVisitor;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.antlr.v4.gui.TreeViewer;
import org.antlr.v4.runtime.ANTLRFileStream;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.TokenStream;
import org.antlr.v4.runtime.tree.ParseTreeWalker;

/**
 *
 * @author Eugenio
 */
public class IvannosysCompiler {

    public static final int INPUT_FILE = 0;
    public static final int INPUT_STREAM = 1;
    protected String inputFile = "";
    protected String inputStream = "";
    protected String encType = "UTF-8";
    protected int inputType = 0;
    public ArrayList<String> path;
    public ArrayList<String> pathLibs;
    protected ParseTreeWalker walker;
    protected IvannosysListener listener;
    protected IvannosysGrammarParser.InitContext tree;
//    protected IvannosysVisitor iVisitor;
    protected HashMap<String, IvannosysTargetArch> targets = new HashMap<>();
//    protected HashMap<String, IvannosysVisitor> visitors;
    protected HashMap<String, AbstractSyntaxTree> asts;
    protected AbstractSyntaxTree ast;
    protected HashMap<String, String> settings;
    protected String ExtensionLibrary = ".go";

    protected String EnvPathVarName = "IVANPATH";
    protected String EnvBinVarName = "IVANBIN";
    protected String EnvRootVarName = "IVANROOT";
    protected TacGenInterface tac;

    IvannosysCompiler(String srcinputprogram, String enc, int inputType) throws Exception {
        settings = new HashMap<>();
        path = new ArrayList<>();
        pathLibs = new ArrayList<>();
//        visitors = new HashMap<>();
        asts = new HashMap<>();
        ast = AbstractSyntaxTree.getAbstractSyntaxTree();
        if (inputType == INPUT_STREAM) {
            inputStream = srcinputprogram;
        } else {
            inputFile = srcinputprogram;
        }
        encType = enc;

        inicializarSrcPath();
        inicializarSettings();
    }

    protected IvannosysCompiler RegisterTarget(String id, IvannosysTargetArch target) {
        targets.put(id, target);
        return this;
    }

    protected boolean HasTarget(String id) {
        return targets.containsKey(id);
    }

    protected void inicializarSrcPath() throws Exception {
        String[] variavies = new String[]{this.EnvPathVarName, this.EnvRootVarName};
        for (String variavel : variavies) {
            variavel = System.getenv(variavel);
            if (variavel == null) {
                throw new Exception(String.format("Não encontrou a variável de ambeinte %s.", variavel));
            }
            include(variavel);
        }
    }

    /**
     * StingS.put("clearCode", true);<br>
     * StingS.put("developement",falSe);<br>
     * StingS.put("diSplayASt", falSe);<br>
     * StingS.put("diSplayErroS", falSe);<br>
     * StingS.put("diSplaParSerTree", falSe);
     *
     * @return
     */
    public IvannosysCompiler inicializarSettings() {
        settings.put("clearCode", "true");
        settings.put("developement", "false");
        settings.put("displayAst", "false");
        settings.put("displayErros", "false");
        settings.put("displaParserTree", "false");
        return this;
    }

    public IvannosysCompiler setSetting(String prop, String valor) {
        settings.put(prop, valor);
        return this;
    }

    public IvannosysCompiler setLimparCodigo(String lc) {
        settings.put("clearCode", lc);
        return this;
    }

    public IvannosysListener getListener() throws Exception {
        if (null == listener) {
            listener = new IvannosysListener(this);
        }
        return listener;
    }

    public IvannosysCompiler Compile() throws IOException, Exception {
        try {
            // create a CharStream that reads from standard input
            ANTLRInputStream input;
            if (inputType == INPUT_STREAM) {
                input = new ANTLRInputStream(inputStream);
            } else {
                input = new ANTLRFileStream(inputFile, encType);
            }

            SyntaxError se = new SyntaxError();
            IvannosysGrammarLexer lexer = new IvannosysGrammarLexer(input);

            lexer.removeErrorListeners();
            lexer.addErrorListener(se);

            CommonTokenStream tokens = new CommonTokenStream(lexer);
            IvannosysGrammarParser parser = new IvannosysGrammarParser((TokenStream) tokens);

            parser.removeErrorListeners();
            parser.addErrorListener(se);

            tree = parser.init(); // begin parsing at init rule

            if (se.HasError()) {
                abortarPorErro(se, "parser");
                return this;
            }

            Api.inicializar(this);

            listener = getListener();
            walker = new ParseTreeWalker();

            Log be = new IvannosysVisitor(tree, parser.getTokenNames(), this).Error();
            if (be.HasError()) {
                abortarPorErro(be, "visitor");
                return this;
            }
            /**
             * Etapa de geração da arvore sintatica e analise semantica
             */
            walker.walk(listener, tree);

            be = listener.Error();

            if (be.HasError()) {
                abortarPorErro(be, "listener");
                return this;
            }

            DisplayAST(parser);

            Variaveis.List();
            Semantic semantic = new Semantic(ast);
            if (semantic.Execute().HasError()) {
                abortarPorErro(semantic, "semantic");
            }
//            Funcoes.genCallTree(ast);
            /**
             * Etapa de geração de codigo de 3 enderecos
             */
            Log.PrintInfo("TAC", new Instruction().S("msg", "Gerando código de três endereços"));
            Code code = tac.Create(ast, settings);
//            return this;
            for (String arch : code.ast.getCompileParams().getList("target")) {
                Log.PrintInfo("TAC → TARGET", new Instruction().S("msg", "Gerando código " + arch));
                Generate(arch, code);
            }

        } catch (Exception e) {
            System.out.println(e.getMessage());
            if (settings.get("developement").equals("true")) {
                e.printStackTrace();
            }
        }
        return this;
    }

    public void Generate(String arch, Code code) throws Exception {
        if (!HasTarget(arch)) {
            throw new Exception(String.format("Arch '%s' não definida.", arch));
        }

        this.targets.get(arch)
                .SetTAC(code)
                .Compile()
                .Format()
                .Export();
    }

    protected void DisplayAST(IvannosysGrammarParser parser) {
        if (settings.get("displaParserTree").equals("true")) {
//            ((ParserRuleContext) tree).inspect(parser); 
            displayTree(parser);//mostra a arvore grafica
            System.out.println(tree.toStringTree(parser)); // print LISP-style tree
        }

        if (settings.get("displayAst").equals("true")) {
            System.out.println("...................................Abstract Syntax Tree");
            System.out.println(ast.stringfy());
        }
    }

    protected void displayTree(IvannosysGrammarParser parser) {
        JFrame frame = new JFrame("Antlr AST");
        JPanel panel = new JPanel();
        TreeViewer viewr = new TreeViewer(Arrays.asList(
                parser.getRuleNames()), tree);
        viewr.setScale(1.5);//scale a little
        panel.add(viewr);
        frame.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(200, 200);
        frame.setVisible(true);
    }

    protected void abortarPorErro(ErrorReport er, String onde) throws Exception {
        System.out.println("Foram encontrados erros durante a compilação[" + onde + "]:");
        er.PrintErros();
        throw new Exception("Compilação abortada");
    }

    public IvannosysCompiler LoadPackage(String alias, String path) throws IOException, Exception {
//        System.out.println("Carregando biblioteca ......................... " + path);
//        System.out.println("Load:" + alias + ":" + path);

        String absPath = GetFileInPath(path);

        if (absPath == null) {
            throw new Exception(String.format("O pacote '%s' não foi encontrado no path.", path));
        }
        File folder = new File(absPath);
        int inc = 0;
        for (File file : folder.listFiles()) {
            if (!file.isFile()) {
                continue;
            }
            inc++;

//            System.out.println("Read file " + file.getAbsolutePath());
            ANTLRInputStream input = new ANTLRFileStream(file.getAbsolutePath(), encType);
            IvannosysGrammarLexer lexer = new IvannosysGrammarLexer(input);
            CommonTokenStream tokens = new CommonTokenStream(lexer);
            IvannosysGrammarParser parser = new IvannosysGrammarParser((TokenStream) tokens);
            IvannosysGrammarParser.InitContext ltree = parser.init(); // begin parsing at init rule

            if (settings.get("displaParserTree").equals("true")) {
//            ((ParserRuleContext) ltree).inspect(parser); //mostra a arvore grafica
                System.out.println(ltree.toStringTree(parser)); // print LISP-style tree
                displayTree(parser);//mostra a arvore grafica
            }

            walker.walk(getListener(), ltree); // walk parse tree
        }
        if (inc == 0) {
            throw new Exception(String.format("Diretório '%s' não possui arquivos fonte.", absPath));
        }
        return this;
    }

    public IvannosysCompiler setDisplayParserTree(boolean b) {
        settings.put("displaParserTree", b ? "true" : "false");
        return this;
    }

    public IvannosysCompiler setDisplayErros(boolean b) {
        settings.put("displayErros", b ? "true" : "false");
        return this;
    }

    public IvannosysCompiler include(String srcinput) {
        if (!path.contains(srcinput)) {
            path.add(srcinput);
        }
        return this;
    }

    public IvannosysCompiler includeLib(String srcinput) {
        if (!pathLibs.contains(srcinput)) {
            pathLibs.add(srcinput);
        }
        return this;
    }

    public String GetFileInPath(String file) {
        this.path.add(0, ".");
        for (String x : this.path) {
            File f = new File(x + "\\" + file);
            if (f.exists() && f.isDirectory()) {
                return f.getAbsolutePath();
            }
        }
        return null;
    }

    public String getExtensionLibrary() {
        return ExtensionLibrary;
    }

    public IvannosysCompiler setExtensionLibrary(String ExtensionLibrary) {
        this.ExtensionLibrary = ExtensionLibrary;
        return this;
    }

    public IvannosysCompiler EnvPathVar(String ivanpath) {
        this.EnvPathVarName = ivanpath;
        return this;
    }

    public IvannosysCompiler TacGen(TacGenInterface tac) {
        this.tac = tac;
        return this;
    }

}

//    protected MifFactory execEtapaGeracaoMif(MipsCodeGen mc) throws Exception {
//        System.out.println("...................Executando Conversão para binario");
//        Mif mif = null;
//
//        MifFactory mf = new MifFactory(mc);
//        mc.getBlocks();
////        mf.criarTraps(true);
//        HashMap<String, ArrayList<Instruction>> blocos = mc.getBlocks();
//        ArrayList<Instruction> funcoes = new ArrayList<>();
//        for (Map.Entry<String, ArrayList<Instruction>> bloco : blocos.entrySet()) {
//            String nomeBloco = bloco.getKey();
//            ArrayList<Instruction> b = bloco.getValue();
//            if (nomeBloco.equals("main")) {
//                mf.gerarMif(b, new Mif(1024), "mifs\\main");
//            } else {
//                funcoes.addAll(b);
//            }
//        }
//        mf.gerarMif(funcoes, new Mif(1024), "mifs\\funcoes");
//        mif = mf.criarDataStream();
//        mif.save("mifs\\data");
//
//        System.out.println("..................................................ok\n");
//        return mf;
//    }
//    protected void loadInputStream(String path) throws IOException, Exception {
//        System.out.println("Carregando biblioteca ......................... " + path);
//
//        ANTLRInputStream input = new ANTLRFileStream(path, encType);
//        IvannosysGrammarLexer lexer = new IvannosysGrammarLexer(input);
//        CommonTokenStream tokens = new CommonTokenStream(lexer);
//        IvannosysGrammarParser parser = new IvannosysGrammarParser((TokenStream) tokens);
//        IvannosysGrammarParser.InitContext ltree = parser.init(); // begin parsing at init rule
//
//        if (settings.G("displaParserTree").equals("true")) {
////            ((ParserRuleContext) ltree).inspect(parser); //mostra a arvore grafica
//            displayTree(parser);//mostra a arvore grafica
//            System.out.println(ltree.toStringTree(parser)); // print LISP-style tree
//        }
//        walker.walk(getListener(), ltree); // walk parse tree
//    }
