/*
 * 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.BuildParams;
import API.Target;
import IntermediaryCode.IRGenInterface;
import ast.AbstractSyntaxTree;
import ast.Semantic;
import common.Code;
import common.ErrorReport;
import common.Instruction;
import common.IvannosysTargetArch;
import common.Log;
import common.SyntaxError;
import frontend.Ivannosys.IvannosysListener;
import frontend.Ivannosys.IvannosysVisitor;
import grammar.IvannosysGrammarLexer;
import grammar.IvannosysGrammarParser;
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 final class IvannosysCompiler {

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

    IvannosysCompiler(String enc, int inputType) throws Exception {
        ast = AbstractSyntaxTree.getAbstractSyntaxTree();
        asts = new HashMap<>();
        path = new ArrayList<>();
        encType = enc;
        pathLibs = new ArrayList<>();

    }

    public IvannosysCompiler IRGenerator(IRGenInterface ir) {
        this.IR = ir;
        return this;
    }

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

    public void Compile(String input) throws Exception {
        this.userInput = input;
        Compile();
    }

    protected IvannosysCompiler Compile() throws IOException, Exception {
        try {
            // create a CharStream that reads from standard input
            ANTLRInputStream input;

            if (inputType == INPUT_STREAM) {
                input = new ANTLRInputStream(userInput);
            } else {
                input = new ANTLRFileStream(userInput, 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;
            }

            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);

            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 = IR.Create(ast);

            for (String arch : BuildParams.Get("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 (BuildParams.GetFirst("mode").equals("developement")) {
                e.printStackTrace();
            }
        }
        return this;
    }

    public void Generate(String arch, Code code) throws Exception {

        Target.Get(arch)
                .SetTAC(code)
                .Compile()
                .Format()
                .Export();
    }

    protected void DisplayAST(IvannosysGrammarParser parser) throws Exception {
        if (BuildParams.GetFirst("display.PARSETREE").equals("true")) {
//            ((ParserRuleContext) tree).inspect(parser); 
            displayTree(parser);//mostra a arvore grafica
            System.out.println(tree.toStringTree(parser)); // print LISP-style tree
        }

        if (BuildParams.GetFirst("display.AST").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 (BuildParams.GetFirst("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 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;
    }
}
