package de.dhbwstuttgart.core;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;

import de.dhbwstuttgart.typeinference.Menge;
import de.dhbwstuttgart.logger.Logger;
import de.dhbwstuttgart.logger.LoggerConfiguration;
import de.dhbwstuttgart.logger.Section;
import de.dhbwstuttgart.parser.JavaParser;
import de.dhbwstuttgart.parser.Scanner;
import de.dhbwstuttgart.parser.JavaParser.yyException;
import de.dhbwstuttgart.syntaxtree.Class;
import de.dhbwstuttgart.syntaxtree.ClassBody;
import de.dhbwstuttgart.syntaxtree.FormalParameter;
import de.dhbwstuttgart.syntaxtree.ImportDeclarations;
import de.dhbwstuttgart.syntaxtree.Method;
import de.dhbwstuttgart.syntaxtree.ParameterList;
import de.dhbwstuttgart.syntaxtree.SourceFile;
import de.dhbwstuttgart.syntaxtree.misc.DeclId;
import de.dhbwstuttgart.syntaxtree.misc.UsedId;
import de.dhbwstuttgart.syntaxtree.type.GenericTypeVar;
import de.dhbwstuttgart.syntaxtree.type.IMatchable;
import de.dhbwstuttgart.syntaxtree.type.ITypeContainer;
import de.dhbwstuttgart.syntaxtree.type.RefType;
import de.dhbwstuttgart.syntaxtree.type.Type;
import de.dhbwstuttgart.syntaxtree.type.TypePlaceholder;
import de.dhbwstuttgart.typeinference.ByteCodeResult;
import de.dhbwstuttgart.typeinference.FunNInterface;
import de.dhbwstuttgart.typeinference.FunVoidNInterface;
import de.dhbwstuttgart.typeinference.Pair;
import de.dhbwstuttgart.typeinference.ResultSet;
import de.dhbwstuttgart.typeinference.TypeinferenceResultSet;
import de.dhbwstuttgart.typeinference.TypeinferenceResults;
import de.dhbwstuttgart.typeinference.assumptions.TypeAssumptions;
import de.dhbwstuttgart.typeinference.exceptions.DebugException;
import de.dhbwstuttgart.typeinference.exceptions.ParserError;
import de.dhbwstuttgart.typeinference.exceptions.TypeinferenceException;


public class MyCompiler implements MyCompilerAPI{
    // PL: Der Zusammenhang zwischen paralist und vParaOrg muesste
    // noch geklaert werden 05-01-07

    public static final int NO_LINENUMBER = -1;

    protected static Logger inferencelog = Logger.getLogger(MyCompiler.class.getName());

    protected String OutputDir = "";
    
    public Menge<Pair> testPair = null;
    
    /**
     * Author: J�rg B�uerle<br/>
     * Der private Konstruktor. Es soll von au�en kein Compiler angelegt werden
     * k�nnen, sondern nur eine API zur Verf�gung gestellt werden.
     * @param logger Konfiguration für Debug Ausgabe TODO
     */
    private MyCompiler(){
        this.init();
    }

    /**
     * Author: Jörg Bäuerle<br/>
     * Stellt eine neue Instanz der CompilerAPI zur Verf�gung.
     * Diese Methode sollte von der IDE aus aufgerufen werden,
     * um eine Quellcode-Datei zu kompilieren.
     * @return Die Compiler-API
     */
    public static MyCompilerAPI getAPI(LoggerConfiguration loggerConfig){
    	Logger.setStandardConfiguration(loggerConfig);
        return new MyCompiler();
    }
    
    /**
     * Parst den Quellcode und baut den abstrakten Syntaxbaum auf. Danach wird
     * automatisch der von Thomas Ott implementierte Algorithmus                       
     * <code>NewTVar(jclass)</code> (siehe Algorithmus 5.17 TRProg, Martin Pl�micke)   
     * aufgerufen.
     * <br/>Author: J�rg B�uerle
     * @param reader
     * @throws IOException
     * @throws JavaParser.yyException
     */
    private void parse_backup(Reader reader) throws IOException, JavaParser.yyException{
    	
    }
/////////////////////////////////////////////////////////////////////////////////////////////////   
//   Implementierte API-Methoden:
/////////////////////////////////////////////////////////////////////////////////////////////////
    /**
     * Author: J�rg B�uerle<br/>
     * Initialisiert den Compiler
     */
    public void init(){
        TypePlaceholder.deleteRegistry();  
    }
    /**
     * Author: J�rg B�uerle<br/>
     * Ruft die Parse-Methode.
     * @param file Die Quellcode-Datei
     * @throws FileNotFoundException Wenn die Quellcode-Datei nicht existiert.
     * @throws IOException Wenn was schief l�uft.
     * @throws JavaParser.yyException Wenn ein Fehler beim Parsen auftritt.
     */
    public SourceFile parse(File file) throws FileNotFoundException, IOException, JavaParser.yyException{
        FileReader fr = new FileReader(file);
        SourceFile ret = this.parse2SyntaxTree(fr);
        fr.close();
        return ret;
    }

    /**
     * Author: J�rg B�uerle<br/>
     * Ruft den Typrekonstruktionsalgorithmus auf.
     * @return Die Menge aller m�glichen Typkombinationen
     * @throws NullPointerException Wenn noch kein abstrakter Syntaxbaum vorhanden     
     * ist. @throws CTypeReconstructionException Wenn ein Fehler bei der               
     * Typrekonstruktion auftritt. 
     */
    public Menge<TypeinferenceResultSet> typeReconstruction(Menge<SourceFile> m_AbstractSyntaxTree) throws NullPointerException{
        inferencelog.info("##########################################", Section.TYPEINFERENCE);
        inferencelog.info("# TypeReconstruction-Algorithmus - START #", Section.TYPEINFERENCE);
        inferencelog.info("##########################################\n", Section.TYPEINFERENCE);
        
        TypeAssumptions globalAssumptions = makeFunNAssumptions();
        Menge<TypeinferenceResultSet> result = new Menge<TypeinferenceResultSet>();
        for(SourceFile srcFile : m_AbstractSyntaxTree){
        	result.addAll(srcFile.typeReconstruction(globalAssumptions));
            
        }
        
        inferencelog.info("#########################################", Section.TYPEINFERENCE);
        inferencelog.info("# TypeReconstruction-Algorithmus - ENDE #", Section.TYPEINFERENCE);
        inferencelog.info("#########################################\n", Section.TYPEINFERENCE);
        
        return result;
    }

    /**
     * Erstellt die FunN-Assumptions
     * Fun0-FunN (momentan für N = 6)
     * @return
     */
	public static TypeAssumptions makeFunNAssumptions(){
		TypeAssumptions ret = new TypeAssumptions();
		
		//Basic Assumptions für die FunN Interfaces:
		//TODO: Hier mehr als Fun1-Fun5 implementieren
		for(int i = 0; i<6; i++){
			FunNInterface funN = new FunNInterface(i);
			ret.add(funN.getPublicFieldAssumptions());
		}
		for(int i = 0; i<6; i++){
			FunVoidNInterface funN = new FunVoidNInterface(i);
			ret.add(funN.getPublicFieldAssumptions());
		}
		
		return ret;
	}

    /**
     * Die Main-Funktion, �ber die der Compiler auch per Konsole gestartet
     * werden kann.
     * @param args Klassendatei
     */
    public static void main(String[] args){
        MyCompilerAPI compiler = MyCompiler.getAPI(new LoggerConfiguration());
        
        // Hier koennten ggf. Aenderungen der Ausgabeeinstellungen
        // (Debuginfos) vorgenommen werden -> LOG4J
    
        try {
        	compiler.parse(new File(args[0]));
        } catch (FileNotFoundException e) {
            System.err.println("Die Datei \""+args[0]+"\" konnte nicht gefunden werden.");
            System.exit(0);
        } catch (IOException e) {
            System.err.println("Fehler beim Parsen:");
            System.err.println(e);
            System.exit(0);
        } catch (yyException e) {
            System.err.println("Fehler beim Parsen:");
            System.err.println(e);
            System.exit(0);
        }
    }

    public void setOutputDir(String dir){
        char c = dir.charAt(dir.length()-1);
        if (c != '/' & c != '\\') dir = dir + "/";
        OutputDir = dir;
        
        // Verzeichnis(se) ggf. anlegen
        File f = new File(dir);
        f.mkdirs();
    }

    public String getOutputDir(){
        return OutputDir;
    }
    
    /**
     * Parst den Inhalt einer Datei zu einem Syntaxbaum.
     */
    private SourceFile parse2SyntaxTree(Reader fileContent) throws ParserError{
    
    	//StringReader reader = new StringReader(fileContent);
        //////////////////////////////////////
        // Scanner und Parser erzeugen:
        //////////////////////////////////////
        Scanner scanner = new Scanner(fileContent);
        JavaParser parser = new JavaParser();
        
        //////////////////////////////////////
        // Parsen ==> Ergebnis: srcFile
        //////////////////////////////////////
        SourceFile srcFile = null;
		try {
			srcFile = (SourceFile) parser.yyparse( scanner );
		} catch (IOException | yyException e) {
			e.printStackTrace();
			if(e instanceof yyException)throw new ParserError((yyException)e);
		}
		//////////////////////////////////////
		// Postprocessing:
		//////////////////////////////////////
		srcFile.parserPostProcessing(null); //Muss mit null aufgerufen werden.
		//Fertig:
    	return srcFile;
    }
    
    /**
     * Diese Funktion nimmt einen Menge von Dateinamen. Alle diese Dateien werden zu einem SyntaxBaum geparst.
     * @return 
     */
    public Menge<SourceFile> parse(Menge<String> filenames) throws ParserError {
    	Menge<SourceFile> m_AbstractSyntaxTree = new Menge<SourceFile>();
		
		for(String filename : filenames){
				StringBuffer fileData = new StringBuffer();
		        BufferedReader reader;
				try {
					reader = new BufferedReader(
					        new FileReader(filename));
				} catch (FileNotFoundException e) {
					throw new DebugException("Die Datei "+ filename+" konnte nicht gelesen werden.");
				}
		        char[] buf = new char[1024];
		        int numRead=0;
		        try {
					while((numRead=reader.read(buf)) != -1){
					    String readData = String.valueOf(buf, 0, numRead);
					    fileData.append(readData);
					}
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}

		        StringReader srcreader = new StringReader(fileData.toString());
		        //Den aus der Datei ausgelesenen Quellcode zu einem Syntaxbaum parsen:
		        m_AbstractSyntaxTree.add(parse2SyntaxTree(srcreader)); // Alle Dateien nacheinander hintereinander anhängen...
		}
		
		return m_AbstractSyntaxTree;
	}

	@Override
	public SourceFile parse(String sourceCode) {
		return parse2SyntaxTree(new StringReader(sourceCode));
	}

	@Override
	public Menge<ByteCodeResult> generateBytecode(Menge<SourceFile> m_AbstractSyntaxTree, TypeinferenceResults typeinferenceResults) {
		//SourceFile parsedFile = this.m_AbstractSyntaxTree.firstElement();
		//Class parsedClass = parsedFile.KlassenVektor.firstElement();
		Menge<ByteCodeResult> ret = new Menge<>();
		for(SourceFile sf : m_AbstractSyntaxTree){
			ret.addAll(sf.generateBytecode(typeinferenceResults));
		}
		return ret;
	}
}