package edu.cmu.cs.l3.translate;

import edu.cmu.cs.l3.frame.*;
import edu.cmu.cs.l3.tree.*;
import edu.cmu.cs.l3.semant.*;
import edu.cmu.cs.l3.temp.*;
import edu.cmu.cs.l3.general.*;
import edu.cmu.cs.l3.errormsg.*;

public class TranslateMemoryIR
    extends TranslateMemory {

    public TranslateMemoryIR() {
    }

    public IRExpression pointerInfo(Frame frame, IRExpression ptr,
                                    int operation) {
        if (operation == Translate.OFFSET)
            return thinPointerAccess(frame, ptr, new IRConst(1));
        else if (operation == Translate.SIZE)
            return thinPointerAccess(frame, ptr, new IRConst(2));
        else {
            System.out.println("WARNING: Unknown Op!");
            return null;
        }
    }

    public IRExpression addressToFatPointer(Frame frame, IRExpression iaddress) {
        IRExpression fatptrexp = allocateHeapMemory(frame,
            (new IRConst(3)), false);
        IRExpression fatptr = new IRTemp(frame.allocLocalTemp());
        IRExpression atemp = new IRTemp(frame.allocLocalTemp());

        IRStatement amove = new IRMove(atemp, iaddress);
        IRStatement fatfill =
            new IRSeq(new IRMove(fatptr, fatptrexp),
                      new IRSeq(new IRMove(thinPointerAccess(frame, fatptr,
            new IRConst(0)),
                                           atemp),
                                new IRSeq(new IRMove(thinPointerAccess(frame,
            fatptr, new IRConst(1)),
            new IRConst(0)),
                                          new IRMove(thinPointerAccess(frame,
            fatptr, new IRConst(2)), new IRConst(1)))));

        return new IRESeq(new IRSeq(amove, fatfill), fatptr);
    }

    public IRExpression unsafeFatHeapPointer(Frame frame, IRExpression ptr) {
        return new IRMem(new IRMem(ptr));
    }

    public IRExpression pointerAddition(Frame frame, IRExpression iptr,
                                        IRExpression additive, boolean addition) {
        IRExpression fatptrexp = allocateHeapMemory(frame, new IRConst(3), false);
        IRTemp ptrtemp = new IRTemp(frame.allocLocalTemp());
        IRTemp addtemp = new IRTemp(frame.allocLocalTemp());

        IRStatement ptrmove = new IRMove(ptrtemp, iptr);
        IRStatement addmove = new IRMove(addtemp, additive);
        IRExpression padd, fatptr = new IRTemp(frame.allocLocalTemp());
        if (addition)
            padd = new IRBinop(IRBinop.PLUS,
                               thinPointerAccess(frame, ptrtemp, new IRConst(1)),
                               addtemp);
        else
            padd = new IRBinop(IRBinop.MINUS,
                               thinPointerAccess(frame, ptrtemp, new IRConst(1)),
                               addtemp);

        IRLabel nullsuccess = new IRLabel(new Label());
        IRLabel nullfail = new IRLabel(new Label());
        IRStatement nulltest = new IRCJump(
            IRCJump.EQ, thinPointerAccess(frame, ptrtemp, new IRConst(0)),
            new IRConst(0),
            nullfail, nullsuccess);

        IRLabel done = new IRLabel(new Label());
        IRTemp addresult = new IRTemp(frame.allocLocalTemp());
        IRStatement doaddition = new IRSeq(
            nulltest, new IRSeq(
            new IRSeq(nullsuccess,
                      new IRSeq(new IRMove(addresult, padd), new IRJump(done))),
            new IRSeq(nullfail, new IRSeq(new IRMove(addresult, new IRConst(0)), done))));

        IRStatement fatfill =
            new IRSeq(new IRMove(fatptr, fatptrexp),
                      new IRSeq(new IRMove(thinPointerAccess(frame, fatptr,
            new IRConst(0)),
                                           thinPointerAccess(frame, ptrtemp,
            new IRConst(0))),
                                new IRSeq(new IRMove(thinPointerAccess(frame,
            fatptr, new IRConst(1)), addresult),
                                          new IRMove(thinPointerAccess(frame,
            fatptr, new IRConst(2)),
            thinPointerAccess(frame, ptrtemp, new IRConst(2))))));

        return new IRESeq(new IRSeq(new IRSeq(addmove, ptrmove),
                                    new IRSeq(doaddition, fatfill)), fatptr);

    }

    public IRExpression checkFatHeapPointer(Frame frame, IRExpression ptrexp,
                                            int pos) throws TranslateException {
        //Check for fptr[1] < 0
        IRLabel zerosuccess, zerofail;
        IRLabel sizesuccess, sizefail;
        IRLabel nullsuccess, nullfail;
        IRStatement zptrerror, sptrerror, nptrerror;
        IRExpressionList params;
        IRExpression ptrtemp;
        IRStatement bcomment, ecomment;

        bcomment = new IRComment("Begin checkFatHeapPointer() Line: " +
                                 m_errorMsg.getLine(pos));
        ecomment = new IRComment("End checkFatHeapPointer() Line: " +
                                 m_errorMsg.getLine(pos));
        ptrtemp = new IRTemp(frame.allocLocalTemp());
        zerosuccess = new IRLabel(new Label());
        zerofail = new IRLabel(new Label());
        IRStatement zerotest = new IRCJump(
            IRCJump.LT, thinPointerAccess(frame, ptrtemp, new IRConst(1)),
            new IRConst(0),
            zerofail, zerosuccess);

        params = new IRExpressionList(Translator.getFileLabel(m_errorMsg.
            getFile(pos)));
        params = params.append(new IRConst(m_errorMsg.getLine(pos)));
        params = params.append(Translator.getErrorLabel(Translator.PTRNEGINDEX));
        zptrerror = new IRStatementExpression(
            new IRCall(new IRName(Globals.extFunctionName("_l3_error")),
                       params));

        sizesuccess = new IRLabel(new Label());
        sizefail = new IRLabel(new Label());
        IRStatement sizetest = new IRCJump(
            IRCJump.GE, thinPointerAccess(frame, ptrtemp, new IRConst(1)),
            thinPointerAccess(frame, ptrtemp, new IRConst(2)),
            sizefail, sizesuccess);
        params = new IRExpressionList(Translator.getFileLabel(m_errorMsg.
            getFile(pos)));
        params = params.append(new IRConst(m_errorMsg.getLine(pos)));
        params = params.append(Translator.getErrorLabel(Translator.PTRBOUNDS));
        sptrerror = new IRStatementExpression(
            new IRCall(new IRName(Globals.extFunctionName("_l3_error")),
                       params));

        nullsuccess = new IRLabel(new Label());
        nullfail = new IRLabel(new Label());
        IRStatement nulltest = new IRCJump(
            IRCJump.EQ, thinPointerAccess(frame, ptrtemp, new IRConst(0)),
            new IRConst(0),
            nullfail, nullsuccess);
        params = new IRExpressionList(Translator.getFileLabel(m_errorMsg.
            getFile(pos)));
        params = params.append(new IRConst(m_errorMsg.getLine(pos)));
        params = params.append(Translator.getErrorLabel(Translator.PTRNULL));
        nptrerror = new IRStatementExpression(
            new IRCall(new IRName(Globals.extFunctionName("_l3_error")),
                       params));

        IRExpression ptraddress = new IRTemp(frame.allocLocalTemp());
        IRStatement getptraddr =
            new IRMove(ptraddress,
                       new IRBinop(IRBinop.PLUS, new IRMem(ptrtemp),
                                   new IRBinop(IRBinop.MUL,
                                               new IRConst(frame.getWordSize()),
                                               thinPointerAccess(frame, ptrtemp,
            new IRConst(1)))));

        IRLabel succ = new IRLabel(new Label());
        IRStatement dochecks =
            new IRSeq(
            new IRSeq(
            zerotest, new IRSeq(
            new IRSeq(
            zerosuccess, new IRSeq(
            sizetest, new IRSeq(
            new IRSeq(
            sizesuccess, new IRSeq(
            nulltest, new IRSeq(
            new IRSeq(
            nullfail, nptrerror), new IRSeq(
            nullsuccess, new IRJump(succ))))),
            new IRSeq(sizefail, sptrerror)))), new IRSeq(zerofail, zptrerror))),
            succ);

        return new IRESeq(new IRSeq(bcomment,
                                    new IRSeq(new IRMove(ptrtemp, ptrexp),
                                              new IRSeq(dochecks,
            new IRSeq(getptraddr, ecomment)))),
                          ptraddress);

    }

    public IRExpression safeDereference(Frame frame, IRExpression pointer,
                                        int pos) throws TranslateException {
        IRExpression accessloc = checkFatHeapPointer(frame, pointer, pos);
        return new IRMem(accessloc);
    }

    public IRExpression pointerEquality(Frame frame, IRExpression ileft,
                                        IRExpression iright, int equal) {

        IRLabel eq = new IRLabel(new Label()), neq = new IRLabel(new Label());
        IRLabel adrsuccess = new IRLabel(new Label());
        IRLabel offlbl = new IRLabel(new Label());
        IRStatement check, getretval;
        IRExpression retval = new IRTemp(frame.allocLocalTemp());
        IRExpression rtemp = new IRTemp(frame.allocLocalTemp());
        IRExpression ltemp = new IRTemp(frame.allocLocalTemp());
        IRStatement bcomment = new IRComment("Begin pointerEquality()");
        IRStatement ecomment = new IRComment("End pointerEquality()");

        IRStatement lmove = new IRMove(ltemp, ileft);
        IRStatement rmove = new IRMove(rtemp, iright);
        IRStatement adrtest =
            new IRCJump(IRCJump.EQ,
                        thinPointerAccess(frame, ltemp, new IRConst(0)),
                        thinPointerAccess(frame, rtemp, new IRConst(0)),
                        adrsuccess, neq);
        IRStatement offtest =
            new IRCJump(IRCJump.EQ,
                        thinPointerAccess(frame, ltemp, new IRConst(1)),
                        thinPointerAccess(frame, rtemp, new IRConst(1)), eq,
                        neq);
        IRStatement nulltest =
            new IRCJump(IRCJump.EQ,
                        thinPointerAccess(frame, ltemp, new IRConst(0)),
                        new IRConst(0), eq,
                        offlbl);

        check = new IRSeq(adrtest,
                          new IRSeq(adrsuccess,
                                    new IRSeq(nulltest,
                                              new IRSeq(offlbl, offtest))));
        IRLabel succ = new IRLabel(new Label());
        if (equal == 0) {
            getretval =
                new IRSeq(new IRSeq(check, new IRSeq(
                new IRSeq(eq,
                          new IRSeq(new IRMove(retval, new IRConst(0)),
                                    new IRJump(succ))),
                new IRSeq(neq, new IRMove(retval, new IRConst(1))))), succ);
        }
        else {
            getretval =
                new IRSeq(new IRSeq(check, new IRSeq(
                new IRSeq(eq,
                          new IRSeq(new IRMove(retval, new IRConst(1)),
                                    new IRJump(succ))),
                new IRSeq(neq, new IRMove(retval, new IRConst(0))))), succ);
        }

        return new IRESeq(new IRSeq(bcomment,
                                    new IRSeq(lmove, new IRSeq(rmove,
            new IRSeq(getretval, ecomment)))), retval);

    }

    public IRExpression copyFatPointer(Frame frame, IRExpression iptr) {
        IRExpression fptrexp = allocateHeapMemory(frame,
                                                  new IRConst(3), false);
        IRExpression fptr = new IRTemp(frame.allocLocalTemp());

        IRExpression ptrtemp = new IRTemp(frame.allocLocalTemp());
        IRStatement ptrmove = new IRMove(ptrtemp, iptr);
        IRStatement fatfill = new IRSeq(
            new IRMove(thinPointerAccess(frame, fptr, new IRConst(0)),
                       thinPointerAccess(frame, ptrtemp, new IRConst(0))),
            new IRSeq(
            new IRMove(thinPointerAccess(frame, fptr, new IRConst(1)),
                       thinPointerAccess(frame, ptrtemp, new IRConst(1))),
            new IRMove(thinPointerAccess(frame, fptr, new IRConst(2)),
                       thinPointerAccess(frame, ptrtemp, new IRConst(2)))));

        return new IRESeq(new IRSeq(ptrmove,
                                    new IRSeq(new IRMove(fptr, fptrexp),
                                              fatfill)), fptr);
    }

    public IRStatement copyExpression(Frame frame, IRExpression left,
                                      IRExpression right, Ty rty) throws
        TranslateException {
        return new IRMove(left, right);
    }

    public IRExpression addressOf(Frame frame, IRExpression exp) throws
        TranslateException {
        IRExpression address;

        if (exp instanceof IRMem) {
            address = ( (IRMem) exp).getExpression();
        }
        else if (exp instanceof IRESeq) {
            IRExpression deref = ( (IRESeq) exp).getExpression();
            if (deref instanceof IRMem) {
                address = ( (IRMem) deref).getExpression();
            }
            else {
                throw new TranslateException("Address Of on Non Memory");
            }
        }
        else if (exp instanceof IRTemp) {
            IRExpression fakefptr = allocateHeapMemory(frame, new IRConst(1), false);
            IRTemp fptrtemp = new IRTemp(frame.allocLocalTemp());
            address = new IRESeq(new IRSeq(new IRMove(fptrtemp, fakefptr),
                                           new IRMove(new IRMem(fptrtemp), exp)),
                                 fptrtemp);
        }
        else {
            throw new TranslateException("Address Of on Non Memory");
        }

        return addressToFatPointer(frame, address);
    }

    public IRExpression unsafeArrayAccess(Frame frame, IRExpression array,
                                          IRExpression offset) throws
        TranslateException {
        if (offset instanceof IRConst && ( (IRConst) offset).getValue() == 0) {
            return new IRMem(new IRMem(array));
        }
        else if (offset instanceof IRConst) {
            return new IRMem(new IRBinop(IRBinop.PLUS, new IRMem(array),
                                         new IRConst(frame.getWordSize() *
                ( (IRConst) offset).getValue())));
        }
        else {
            return new IRMem(new IRBinop(IRBinop.PLUS, new IRMem(array),
                                         new IRBinop(IRBinop.MUL, offset,
                new IRConst(frame.getWordSize()))));
        }
    }

    public IRExpression thinPointerAccess(Frame frame, IRExpression array,
                                          IRExpression offset) {
        if (offset instanceof IRConst && ( (IRConst) offset).getValue() == 0) {
            return new IRMem(array);
        }
        else if (offset instanceof IRConst) {
            return new IRMem(new IRBinop(IRBinop.PLUS, array,
                                         new IRConst(frame.getWordSize() *
                ( (IRConst) offset).getValue())));
        }
        else
            return new IRMem(new IRBinop(IRBinop.PLUS, array,
                                         new IRBinop(IRBinop.MUL, offset,
                new IRConst(frame.getWordSize()))));
    }

    public IRExpression pointerIncrement(Frame frame, IRExpression ptr) {
        IRExpression ptemp = new IRTemp(frame.allocLocalTemp());
        IRStatement pmove = new IRMove(ptemp, ptr);

        IRExpression padd = new IRBinop(IRBinop.PLUS,
                                        thinPointerAccess(frame, ptemp,
            new IRConst(1)),
                                        new IRConst(1));
        IRStatement offmove = new IRMove(thinPointerAccess(frame, ptemp,
            new IRConst(1)), padd);

        return new IRESeq(new IRSeq(pmove, offmove), ptemp);
    }

    public IRExpression pointerDecrement(Frame frame, IRExpression ptr) {
        IRExpression ptemp = new IRTemp(frame.allocLocalTemp());
        IRStatement pmove = new IRMove(ptemp, ptr);

        IRExpression padd = new IRBinop(IRBinop.PLUS,
                                        thinPointerAccess(frame, ptemp,
            new IRConst(1)),
                                        new IRConst( -1));
        IRStatement offmove = new IRMove(thinPointerAccess(frame, ptemp,
            new IRConst(1)), padd);

        return new IRESeq(new IRSeq(pmove, offmove), ptemp);
    }

    public IRExpression allocateHeapMemory(Frame frame, IRExpression size,
                                           boolean createpointer) {
        IRExpressionList params;
        IRStatement bcomment = new IRComment("Begin allocateHeapMemory()");
        IRStatement ecomment = new IRComment("End allocateHeapMemory()");
        if (! (size instanceof IRConst))
            params = new IRExpressionList(new IRBinop(IRBinop.MUL,
                new IRConst(frame.getWordSize()),
                size));
        else
            params = new IRExpressionList(new IRConst(frame.getWordSize() *
                ( (IRConst) size).getValue()));

        IRExpression memexp = new IRCall(new IRName(Globals.extFunctionName(
            "_alloc")), params);
        IRExpression mem = new IRTemp(frame.allocLocalTemp());
        IRStatement memmove = new IRMove(mem, memexp);
        if (createpointer) {
            params = new IRExpressionList(new IRConst(3 * frame.getWordSize()));
            IRExpression fptrexp = new IRCall(new IRName(Globals.
                extFunctionName("_alloc")), params);
            IRExpression fptr = new IRTemp(frame.allocLocalTemp());
            IRStatement fatfill = new IRSeq(memmove,
                                            new IRSeq(new IRMove(fptr, fptrexp),
                new IRSeq(
                new IRMove(thinPointerAccess(frame, fptr, new IRConst(0)), mem),
                new IRSeq(
                new IRMove(thinPointerAccess(frame, fptr, new IRConst(1)),
                           new IRConst(0)),
                new IRMove(thinPointerAccess(frame, fptr, new IRConst(2)), size)))));

            return new IRESeq(new IRSeq(bcomment, new IRSeq(fatfill, ecomment)),
                              fptr);
        }
        else
            return new IRESeq(new IRSeq(bcomment, new IRSeq(memmove, ecomment)),
                              mem);
    }
}