import java.io.*;
import java.util.*;

class SparcGen {

  static class SparcGenException extends Exception {
    SparcGenException(String text) {
      super(text);
    }
  }

  // Global counters
  static int nextProc = 0;
  static int nextStringLabel = 0;

  // Information about current procedure
  static int argBuildSize; 
  static int localSize;

  static void genProgram(IR.Body body) throws SparcGenException {
    nextProc = 0;
    nextStringLabel = 0;
    Sparc.emitSegment(1);
    Sparc.emit(".global main");
    Sparc.emit(".align 4");
    Sparc.emit(".proc 4");
    Sparc.emit("main:");
    Sparc.emit0("save %sp,-main_FRAMESIZE,%sp");
    int framesize = gen(body,nextProc++,1,Env.empty);
    Sparc.emit("main_FRAMESIZE = " + framesize);
  }

  // Generate code for a body.
  // Args: instance = unique instance # for enclosing procedure.
  //       level = level of enclosing procedure
  //       env = environment describing in-scope procedures and variables.
  // Returns: frame size for this body.
  static int gen(IR.Body body, int instance, int level, Env env) throws SparcGenException {
    // ...
    Map registerMap = allocateRegisters(body);
    // recursively generate code for sub-procedures (and extend environment)
    for (int i = 0; i < body.decls.length; i++) 
      env = gen(body.decls[i],instance,level, registerMap, env);
    gen(body.bodyCode,instance,level,registerMap,env);
    return 96;  // ... really calculate it here!
  }

  // Generate code for an IR.Code sequence
  // Args: registerMap = allocation map for temporaries in enclosing procedure
  //       others as above.
  static void gen(IR.Code code, int instance, int level, Map registerMap, Env env) {
    Sparc.emitSegment(level);
    for (int i = code.low; i <= code.high; i++)
      gen(code.codeLines[i],instance,level,registerMap,env);
  }

  // Generate code for a declaration.
  // Args: as above.
  static Env gen(IR.Decl decl,int instance, int level, Map registerMap, Env env) throws SparcGenException {
    if (decl instanceof IR.Var) {
      IR.Var v = (IR.Var) decl;
      // ...
      env = new VarEnv(v,level,0 /* ... must fix this!! ... */,env);
      gen(v.initializer,instance,level,registerMap,env);
      return env;
    } else { // decl instanceof IR.Procs 
      IR.Proc[] procs = ((IR.Procs) decl).procs; 
      // add (potentially recursive) procs to environment
      for (int i = 0; i < procs.length; i++) 
	env = new ProcEnv(procs[i],level,nextProc++,env);
      // ...
      return env;
    }
  }      


  // Generate code to evalute an arbitrary operand.
  // Args:
  // immOK = True iff it's ok to return 13-bit immediate value for this operand.
  // scratch = register to use as a scratch register if needed in evaluating operand (possibly returned!) 
  // others as above.
  static Sparc.Operand gen(IR.Operand rand, boolean immOK, int level, Map registerMap, Env env, Sparc.Reg scratch) {
    if (rand instanceof IR.IntLit) {
      int i = ((IR.IntLit)rand).i;
      if (Sparc.shortoff(i)) 
	if (i == 0)
	  return Sparc.G0;
	else if (immOK) 
	  return new Sparc.Imm13(i);
	else 
	  Sparc.emit2("mov",new Sparc.Imm13(i),scratch);
      else
	Sparc.emit2("set", new Sparc.Imm32(i),scratch);
      return scratch;
    } else
      return genAddrRand(rand, level, registerMap,env,scratch);
  }

  // Generate code to evaluate an address operand.
  // Args: as above.
  static Sparc.Operand genAddrRand(IR.Operand rand, int level, Map registerMap, Env env, Sparc.Reg scratch) {
    if (rand instanceof IR.AddrString) {
      Sparc.Label lab = new Sparc.Label("S" + nextStringLabel++);
      Sparc.emitSegment(0);
      Sparc.emitLabel(lab);
      Sparc.emitString(((IR.AddrString)rand).s);
      Sparc.emitSegment(level);
      Sparc.emit2("set",lab,scratch);
      return scratch;
    } else if (rand instanceof IR.AddrArg) {
      // ...
      return null;  // fix this!!
    } else if (rand instanceof IR.AddrName) {
      // ... genLink ...
      return null; // fix this!!
    } else if (rand instanceof IR.AddrNull) 
      return Sparc.G0;
    else
      return genRegRand(rand, registerMap);
  }

  // Generate code to evaluate a register operand.
  // Args: as above.
  static Sparc.Operand genRegRand(IR.Operand rand, Map registerMap) {
    if (rand instanceof IR.RegTemp) 
      return (Sparc.Reg) (registerMap.get(rand));
    else if (rand instanceof IR.RegO0)
      return Sparc.O0;
    else if (rand instanceof IR.RegI0)
      return Sparc.I0;
    else 
      throw new Error ("Impossible IR.Operand:" + rand);
  }

  // Generate code to calculate a static link.
  // Args: targetLevel = level of link to calculate
  //       currentLevel = level of enclosing proceudre
  //       scratch = register to use for scratch storage (possibly returned!)
  static Sparc.Reg genLink(int targetLevel, int currentLevel, Sparc.Reg scratch) {
    int k = currentLevel - targetLevel;
    if (k > 0) {
      Sparc.emitLd(new Sparc.Addr(Sparc.FP,Sparc.FIRST_ARG_OFFSET),scratch);
      while (--k > 0)
	Sparc.emitLd(new Sparc.Addr(scratch,Sparc.FIRST_ARG_OFFSET),scratch);
      return scratch;
    } else
      return Sparc.FP;
  }

  static Sparc.Label genLabel(int instance, IR.Operand rand) {
    if (rand instanceof IR.Label)
      return new Sparc.Label("L" + instance + "_" + ((IR.Label)rand).i);
    else
      throw new Error ("Impossible IR.Label:" + rand);
  }

  // Generate code corresponding to CodeLine cl.
  // Args: as above.
  static void gen(IR.CodeLine cl, int instance, int level, Map registerMap, Env env) {
    if (cl instanceof IR.LabelDec) {
      // ...
    } else { // (cl intanceof Inst) 
      IR.Inst inst = (IR.Inst) cl;
      switch (inst.op) {
      case IR.NO_OP: 
	Sparc.emit0("nop");
	break;
      case IR.LD_OP:
	{
	  Sparc.Operand r0 = genAddrRand(inst.rands[0],level,registerMap,env,Sparc.G1);  
	  Sparc.Operand r1 = genRegRand(inst.rands[1],registerMap);
	  Sparc.emitLd(r0,r1);
	  break;
	}
      case IR.ST_OP: 
	{ 
	  // ...
	  break;
	}
      case IR.RET_OP:
	{
	  Sparc.emit0("ret");
	  Sparc.emit0("restore");
	  break;
	}
      case IR.CALL_OP:
	{
	  String procName = ((IR.AddrName)(inst.rands[0])).s;
	  ProcEnv pe = (ProcEnv) find(procName,env);
	  String qname = procName + "_" + pe.instance;
	  Sparc.Reg u = genLink(pe.level,level,Sparc.G1);
	  Sparc.emitSt(u,new Sparc.Addr(Sparc.SP,Sparc.FIRST_ARG_OFFSET));
	  Sparc.emit0("call " + qname);
	  Sparc.emit0("nop");
	  break;
	}
      case IR.SCALL_OP:
	{
	  String funcName = ((IR.AddrName)(inst.rands[0])).s;
	  if (funcName.equals("alloc")) { // scale arg from word count to byte count
	    Sparc.Operand u = gen(inst.rands[1],false,level,registerMap,env,Sparc.O0);
	    Sparc.emit1("call", new Sparc.AddrName("alloc"));
	    Sparc.emit3("sll",u,new Sparc.Imm13(Sparc.WORDSIZE_BITS),Sparc.O0);  // in delay slot
	  } else {  // others don't require anything special
	    // ...
	  }
	  break;
	}
      case IR.BA_OP: 
      case IR.BG_OP: 
      case IR.BL_OP: 
      case IR.BE_OP: 
      case IR.BGE_OP:
      case IR.BLE_OP: 
      case IR.BNE_OP: 
      case IR.BGU_OP: 
      case IR.BLU_OP: 
      case IR.BGEU_OP:
      case IR.BLEU_OP:
	{
	  Sparc.Label lab = genLabel(instance,inst.rands[0]); 
	  Sparc.emit1(IR.opToString(inst.op), lab);   // happily the IR and Sparc names are the same!
	  Sparc.emit0("nop");
	  break;
	}
      case IR.CMP_OP: 
	{
	  // ...
	  break;
	}
      case IR.MOV_OP: 
	{
	  // ...
	  break;
	}
      case IR.ADD_OP:
	{
	  Sparc.Operand r0 = gen(inst.rands[0],false,level,registerMap,env,Sparc.G1);
	  Sparc.Operand r1 = gen(inst.rands[1],true,level,registerMap,env,Sparc.G2); 
	  Sparc.Operand r2 = genRegRand(inst.rands[2],registerMap);
	  String opname = IR.opToString(inst.op);   // happily the IR and Sparc opnames are the same
	  Sparc.emit3(opname, r0, r1, r2);  
	  break;
	}
      case IR.SUB_OP: 
	{
	  // ... (note: there may be better options than cut-and-paste) ...
	  break;
	}
      case IR.SMUL_OP: 
	{ 
	  // ...;
	  break;
	}
      case IR.SDIV_OP:
	{ 
	  // ...:
	  break;
	}
      case IR.ADDA_OP: 
	{
	  // ...
	  break;
	}
      default: 
	throw new Error("Impossible op code:" + inst.op);
      }
    }
  }


  static class Env {
    IR.Binding binding;
    int level;  // static nesting depth
    Env next;

    /** Empty environment is just a null Env. */
    static final Env empty = null;

    /** Creates new Env by extending an existing Env with a new declaration binding.
     */
    Env(IR.Binding binding, int level, Env next) {
      this.binding = binding; this.level = level; this.next = next;
    }

    public String toString() {
      String r = "[ ";
      for (Env env = this; env != null; env = env.next) {
	r += env.binding.id + " " + level + "\n";
      }
      r += "]";
      return r;
    }
  }

  static class ProcEnv extends Env {
    int instance;  // unique instance number
    ProcEnv(IR.Proc binding, int level, int instance, Env next) {
      super(binding,level,next);
      this.instance = instance;
    }
  }
  
  static class VarEnv extends Env {
    int offset; // offset from FP 
    VarEnv(IR.Var binding, int level, int offset, Env next) {
      super(binding,level,next);
      this.offset = offset;
    }
    VarEnv(IR.Formal binding, int level, int offset, Env next) {
      super(binding,level,next);
      this.offset = offset;
    }
  }


  static Env find(String id,Env env) {
    for (; env != null; env = env.next)
      if (env.binding.id.equals(id))
	return env;
    return null;
  }

  // returns Map with Key = RegTemp, values = Sparc.Reg
  static Map allocateRegisters(IR.Body body) throws SparcGenException {
    Map liveIntervals;
    try {
      liveIntervals = Liveness.calculateLiveIntervals(body);
    } catch (Liveness.LivenessException exn) {
      throw new SparcGenException("Error calculating liveness:" + exn.getMessage());
    }
    Map allocation = new HashMap();        // keys are RegTemp; values are Sparc.Reg
    // Extremely feeble version:
    // Calculate the live interval mapping, but just to get the set of temp registers; 
    //   ignore the interval info.
    // Just assign each %t in the map to a unique physical register.
    // We'll run out quickly!
    //
    // ... do something much smarter here! ...
    int r = 0;
    for (Iterator it = liveIntervals.keySet().iterator(); it.hasNext();) {
      IR.RegTemp t = (IR.RegTemp) (it.next());
      while (!Sparc.regUsable[r++]) {
	if (r >= Sparc.REGS)
	  throw new SparcGenException ("Out of registers");
      }
      allocation.put(t,new Sparc.Reg(r));
    }
    return allocation;
  }

}
