package edu.cmu.cs.l3.translate;

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

public class TranslateMemoryRT
    extends TranslateMemory {

    public TranslateMemoryRT() {
    }

    // Allocates memory on the heap, optionally creating a fat pointer to it
    // The fat pointer is also on the heap. Reason: Global scope has no stack.
    // Size is # of machine words, not bytes!
    public IRExpression allocateHeapMemory(Frame frame, IRExpression size,
                                           boolean createpointer) {

        if (createpointer) {
            return new IRCall(new IRName(Globals.extFunctionName(
                "_allocHeapWithPointer")), new IRExpressionList(size));
        }
        else {
            return new IRCall(new IRName(Globals.extFunctionName("_allocHeap")),
                              new IRExpressionList(size));
        }

    }

    public IRExpression addressToFatPointer(Frame frame, IRExpression address) {
        return new IRCall(new IRName(Globals.extFunctionName(
            "_addressToFatPointer")), new IRExpressionList(address));
    }

    // Makes sure the fat pointer is within the bounds originally set out
    public IRExpression checkFatHeapPointer(Frame frame, IRExpression pointer,
                                            int pos) throws TranslateException {

        IRExpressionList params = new IRExpressionList(pointer);
        params = params.append(Translator.getFileLabel(m_errorMsg.getFile(pos)));
        params = params.append(new IRConst(m_errorMsg.getLine(pos)));
        return new IRESeq(new IRLabel(new Label()),
                          new IRCall(new IRName(Globals.extFunctionName(
            "_checkFatHeapPointer")), params));
    }

    // Unsafe heap dereference
    public IRExpression unsafeFatHeapPointer(Frame frame, IRExpression ptr) {

        return new IRMem(new IRMem(ptr));

    }

    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 copyFatPointer(Frame frame, IRExpression ptr) {
        IRExpressionList params = new IRExpressionList(ptr);
        return new IRCall(new IRName(Globals.extFunctionName("_copyFatPointer")),
                          params);
    }

    public IRExpression safeDereference(Frame frame, IRExpression pointer,
                                        int pos) throws TranslateException {

        IRExpression accessloc = checkFatHeapPointer(frame, pointer, pos);

        return new IRMem(accessloc);
    }

    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 {
            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);
        }
        return new IRMem(new IRBinop(IRBinop.PLUS, array,
                                     new IRBinop(IRBinop.MUL, offset,
                                                 new IRConst(frame.getWordSize()))));
    }

    public IRExpression pointerAddition(Frame frame, IRExpression iptr,
                                        IRExpression additive, boolean addition) {

        IRTemp ptmp = new IRTemp(frame.allocLocalTemp());
        IRTemp ptrtemp = new IRTemp(frame.allocLocalTemp());
        IRStatement ptrmove = new IRMove(ptrtemp, iptr);
        IRExpressionList params = new IRExpressionList(ptrtemp);
        params = params.append(additive);
        if (addition) {
            params = params.append(new IRConst(1));
        }
        else {
            params = params.append(new IRConst(0));
        }
        IRExpression nptr = new IRCall(new IRName(Globals.extFunctionName(
            "_pointerAddition")), params);

        return new IRESeq(new IRSeq(ptrmove, new IRMove(ptmp, nptr)), ptmp);
    }

    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 pointerInfo(Frame frame, IRExpression iptr,
                                    int operation) {
        IRTemp ptrtemp = new IRTemp(frame.allocLocalTemp());
        IRStatement ptrmove = new IRMove(ptrtemp, iptr);
        IRExpressionList params = new IRExpressionList(ptrtemp);
        params = params.append(new IRConst(operation));
        return new IRESeq(new IRMove(ptrtemp, iptr),
                          new IRCall(new IRName(Globals.extFunctionName(
            "_pointerInfo")), params));
    }

    public IRExpression pointerEquality(Frame frame, IRExpression left,
                                        IRExpression right, int equal) {
        IRExpressionList params = new IRExpressionList(left);
        params = params.append(right);
        params = params.append(new IRConst(equal));

        return new IRCall(new IRName(Globals.extFunctionName("_pointerEquality")),
                          params);
    }

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

}