package edu.cmu.cs.l3.translate;

import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import edu.cmu.cs.l3.absyn.ASDeclarationList;
import edu.cmu.cs.l3.canon.Canonicalizer;
import edu.cmu.cs.l3.errormsg.ErrorMsg;
import edu.cmu.cs.l3.frame.Frame;
import edu.cmu.cs.l3.general.Globals;
import edu.cmu.cs.l3.parse.Parse;
import edu.cmu.cs.l3.semant.Environment;
import edu.cmu.cs.l3.semant.TypeChecker;
import edu.cmu.cs.l3.temp.Label;
import edu.cmu.cs.l3.temp.Temp;
import edu.cmu.cs.l3.tree.IRCall;
import edu.cmu.cs.l3.tree.IRConst;
import edu.cmu.cs.l3.tree.IRExpressionList;
import edu.cmu.cs.l3.tree.IRName;
import edu.cmu.cs.l3.tree.IRStatement;
import edu.cmu.cs.l3.tree.IRStatementExpression;
import edu.cmu.cs.l3.tree.IRStatementList;
import edu.cmu.cs.l3.tree.IRTemp;

public class Translator {

    private IRStatementList m_initCounters;
	//private HashMap m_counterAddr;
	private IRStatementList m_writeCounters;
	private Label m_traceFile;

	public Translator(Frame frame) {
        if (m_initfrag == null) {
            m_initfrag = new FragmentCode(frame.newFrame(0,1),"simpleinit");
        }
        m_frame = frame;
    }

    public static final int DIVZERO=0, NEGALLOC=1, PTRNULL=2, PTRBOUNDS=3,
        PTRNEGINDEX=4;

    public List translate(String filename, boolean canon) {

	Parse parser = null;
	Canonicalizer canoner = null;
	Translate progtran=null;
        TypeChecker tychecker;
	IRStatement canonized=null;
        ErrorMsg errormsg=null;
        int i,errors;

        m_fragments.clear();

	try {

	    parser = new Parse();
	    canoner = new Canonicalizer();

	    Globals.verbosePrintln("--- Initiating Translation Stage");
	    Globals.verbosePrintln(" --- Parsing");

            parser.parse(filename);
            errormsg = parser.getErrorHandler();
            tychecker = new TypeChecker(errormsg, new Environment());
            Globals.verbosePrintln(" --- Type Checking");
            if (parser.getProgram() != null) {
                tychecker.setupProcedures((ASDeclarationList)parser.getProgram());
                parser.getProgram().visit(tychecker);
            }

            Globals.verbosePrintln(" --- Creating Static Data");
            TranslateMemory.createNullPointer(m_frame);
            dumpFiles(m_frame, errormsg);
            dumpErrors(m_frame);

	    Globals.verbosePrintln(" --- Translating Abstract Syntax");
	    progtran = new TranslateDeclaration(m_frame,errormsg,new TranslateEnvironment());
            parser.getProgram().visit(progtran);

            errors = tychecker.getNumErrors() + progtran.getNumErrors();
            if (errors > 0) {
                System.out.println("("+errors+") errors. Compilation aborted.");
                System.exit(-1);
            }

	} catch (Exception e) {
	    e.printStackTrace();
	    System.out.println("Compilation aborted due to errors.");
	    System.exit(-1);
	}

        if (canon) {
            Globals.verbosePrintln(" --- Canonicalizing IR Code");
            
	    //Canonicalizer traceCanoner = new Canonicalizer();
            //prepareTracing(errormsg.getFile(1));
            
    	    //FIRST PASS: TRACING - CHANGE ME
            // added for trace instrumentation
            prepareTracing();
            int f = 0; // code fragment index
            canoner.setSourceFileName(errormsg.getFile(1));
            // trace instrumentation end

            for (i = 0; i < m_fragments.size(); i++) {
                Fragment frag = (Fragment) m_fragments.get(i);
                if (frag instanceof FragmentCode) {
                    FragmentCode cfrag = (FragmentCode) frag;
                    
                    // added for trace instrumentation - CHANGE ME
                    canoner.setCounterIndex(f);
                    if(cfrag.getName() == "main") {
                    	canoner.setPrelude(m_initCounters);
                    	canoner.setPostlude(m_writeCounters);
                    }
                    else {
                    	canoner.setPrelude(null);
                    	canoner.setPostlude(null);
                    }
                    // trace instrumentation end

                    cfrag.setCode(canoner.canonicalize(cfrag.getCode(), cfrag.getFrame()));
                    
                    // added for trace instrumentation - CHANGE ME
                    ++f;
                    // end trace instrumentation
                }
            }
            // added for trace instrumentation
            canoner.storePathGraphs();
            // end trace instrumentation

        }

	return m_fragments;
    }
    
    /**
     * Creates prelude and postlude statements for main to initialize and flush the trace buffer.
     */
    private void prepareTracing() {
		m_initCounters = new IRStatementList();
		m_writeCounters = new IRStatementList();
		
		m_initCounters.append_imp(createInitBuffer());
		m_writeCounters.append_imp(createFlush());
    }

	/**
	 * @return
	 */
	private IRStatement createInitBuffer() {
		IRStatement initBuffer = new IRStatementExpression(new IRCall(new IRName("_init_trace_buffer"), 
				new IRExpressionList()));
		return initBuffer;
	}

    /**
	 * @return
	 */
	private IRStatement createFlush() {
		IRStatement flushBuffer = new IRStatementExpression(new IRCall(new IRName("_flush_trace_buffer"), 
				new IRExpressionList()));
		return flushBuffer;
	}

	/**
	 * @deprecated Use {@link #prepareTracing()} instead.
	 */
	private void prepareTracing(String filename) {
		m_traceFile = new Label();
        FragmentString traceFileFrag = new FragmentString(m_frame, filename + ".trace", m_traceFile);
        Translator.addFragment(traceFileFrag);
		
		m_initCounters = new IRStatementList();
		m_writeCounters = new IRStatementList();
		//m_counterAddr = new HashMap();
		int f = 0;
        for (int i = 0; i < m_fragments.size(); i++) {
            Fragment frag = (Fragment) m_fragments.get(i);
            if (frag instanceof FragmentCode) {
                FragmentCode cfrag = (FragmentCode) frag;
                //Temp counterAddr = createGlobalAddrTemp(m_frame);
                //m_counterAddr.put(cfrag, counterAddr);
                m_initCounters.append_imp(prepareInitCounter(f));
                m_writeCounters.append_imp(prepareWriteCounter(f));
                ++f;
            }
        }		
		m_initCounters.insertStatement(0, createInitCounterArray(f));
	}

	/**
	 * @param counters
	 * @return
	 * @deprecated
	 */
	private IRStatement createInitCounterArray(int counters) {
		IRStatement initCounters = new IRStatementExpression(new IRCall(new IRName("_init_counts"), 
				new IRExpressionList(new IRConst(counters))));
		return initCounters;
	}

	/**
	 * @param counterAddr
	 * @deprecated
	 */
	private IRStatement prepareWriteCounter(int counterIndex) {
		// prepare call with dummy array length 0 (2nd arg--size not known yet)
		//IRExpressionList writeCallArgs = new IRExpressionList(new IRName(m_traceFile),
		//		new IRExpressionList(new IRTemp(counterAddr), new IRExpressionList(new IRConst(0))));
		//IRCall writeCall = new IRCall(new IRName(COUNTER_WRITER), writeCallArgs);
		//IRStatementExpression writeCounter = new IRStatementExpression(writeCall);
		IRExpressionList writeCallArgs = new IRExpressionList(new IRName(m_traceFile), 
				new IRExpressionList(new IRConst(counterIndex),
				new IRExpressionList(new IRConst(0))));
		IRCall writeCall = new IRCall(new IRName("_write_counter"), writeCallArgs);
		IRStatement writeCounter = new IRStatementExpression(writeCall);
		return writeCounter;
	}
	
	/** @deprecated */
	private void finishWriteCounter(IRStatement writeCounter, int arrayWidth) {
		((IRCall) ((IRStatementExpression) writeCounter).getExpression()).getParams().setExpression(2, new IRConst(arrayWidth));
	}

	/**
	 * @param counterAddr
	 * @deprecated
	 */
	private IRStatement prepareInitCounter(int counterIndex) {
		// prepare init counter expression with dummy size (size is not known yet)
		//IRMove initCounter = new IRMove(new IRTemp(counterAddr), new IRConst(4));
		IRExpressionList initCountArgs = new IRExpressionList(new IRConst(counterIndex), 
				new IRExpressionList(new IRConst(0)));
		IRStatementExpression initCounter = new IRStatementExpression(new IRCall(new IRName("_init_count"), initCountArgs));
		return initCounter;
	}
	
	/** @deprecated */
	private void finishInitCounter(IRStatement initCounter, int arrayWidth) {
		//IRConst allocSize = new IRConst(4 * arrayWidth);
		//IRCall allocCall = new IRCall(new IRName("_alloc"), new IRExpressionList(allocSize));
		//initCounter.setSource(allocCall);
		((IRCall) ((IRStatementExpression) initCounter).getExpression()).getParams().setExpression(1, new IRConst(arrayWidth));
		
	}

	public static void addFragment(Fragment f) {
        m_fragments.addElement(f);
    }

    public static FragmentCode getInitFragment() {
        return m_initfrag;
    }

    public static IRName getFileLabel(String file) {
        return (IRName) m_filemap.get(file);
    }

    public static IRName getErrorLabel(int type) {
        return (IRName) m_errormsgmap.get(new Integer(type));
    }
    
    public static Temp createGlobalAddrTemp(Frame frame) {
    	Temp t;
    	t = new Temp();
        t.setGlobal(true);
        Translator.addFragment(
            new FragmentGlobal(frame, new IRTemp(t), false));
        return t;
    }
    

    private static void dumpErrors(Frame frame) {
        IRName elabel = new IRName(new Label());
        FragmentString emsg = new FragmentString(frame, "Illegal division by zero", elabel.getLabel());
        Translator.addFragment(emsg);
        m_errormsgmap.put(new Integer(DIVZERO), elabel);
        elabel = new IRName(new Label());
        emsg = new FragmentString(frame, "Size for alloc <= 0", elabel.getLabel());
        Translator.addFragment(emsg);
        m_errormsgmap.put(new Integer(NEGALLOC), elabel);
        elabel = new IRName(new Label());
        emsg = new FragmentString(frame, "Pointer index < 0", elabel.getLabel());
        Translator.addFragment(emsg);
        m_errormsgmap.put(new Integer(PTRNEGINDEX), elabel);
        elabel = new IRName(new Label());
        emsg = new FragmentString(frame, "Pointer index out of bounds", elabel.getLabel());
        Translator.addFragment(emsg);
        m_errormsgmap.put(new Integer(PTRBOUNDS), elabel);
        elabel = new IRName(new Label());
        emsg = new FragmentString(frame, "Attempted to dereference NULL pointer", elabel.getLabel());
        Translator.addFragment(emsg);
        m_errormsgmap.put(new Integer(PTRNULL), elabel);

    }

    private static void dumpFiles(Frame frame, ErrorMsg errorMsg) {
        int i;
        List files;

        files = errorMsg.getAllFiles();
        for (i = 0; i < files.size(); i++) {
            IRName srcfilelbl = new IRName(new Label());
            String file = (String) files.get(i);
            FragmentString srcfile = new FragmentString(frame, file, srcfilelbl.getLabel());
            Translator.addFragment(srcfile);
            m_filemap.put(file, srcfilelbl);
        }
    }

    private static Dictionary m_filemap = new Hashtable();
    private static Dictionary m_errormsgmap = new Hashtable();
    private static Vector m_fragments = new Vector();
    private static FragmentCode m_initfrag=null;
    private Frame m_frame;
}


