package name.panitz.gm;
import java.io.*;
import java.util.*;

import name.panitz.util.*;

public class WriteC {

  static public void writeC(GmState st,String fileName) 
                                                   throws Exception{
    writeC(st,new FileWriter(fileName+".c"));
  }

  static public void writeC(GmState st,Writer out) throws Exception{
    Map<String,Integer> globs=new HashMap<String,Integer>();

    int pc=1;
    List<Pair<Integer,Instruction>> code
      = new ArrayList<Pair<Integer,Instruction>>();

    for (Instruction i:st.code){
      code.add(new Pair<Integer,Instruction>(pc,i));
      pc=pc+size(i);
    }

    out.write("#include \"constants.h\"\n");
    out.write("#include \"gm.h\"\n");
    out.write("#include <stdio.h>\n\n");


    out.write("char* constructors [] \n ={");
    boolean first = true;
    for (String c : st.constructors){
      if (first){first=false;}else {out.write("\n  ,");};
      out.write("\""+c+"\"");
    }

    out.write("\n  };\n\n");
    out.write("void init(){");
    
    GmGlobals globals = st.globals;
    int i =0;
    for (Map.Entry<String,Integer> glob:st.globals.entrySet()){
      out.write("\n  nglb("
      +((NGlobal)st.heap.get(glob.getValue())).argc+","
      +code.get(((NGlobal)st.heap.get(glob.getValue())).i).e1+");");
      globs.put(glob.getKey(),i);
      i=i+3;
    }
    out.write("\n  int i="+globs.size()+";");
    out.write("\n  numberOfGlobals=&i;");
    out.write("\n}\n\n");
 
    out.write("int code [] =\n  {NOP");
    int nr=0;
    for (Pair<Integer,Instruction> p:code){
      writeInstruction(p,code,globs,nr,out);
      nr=nr+1;
    }

    out.write("\n  };\n\n");
    out.write("\nint main()");
    out.write("\n{");
    out.write("\n    initGM();");
    out.write("\n    init();");
    out.write("\n    pc="+code.get(st.pc).e1+";");
    out.write("\n    eval();");
    out.write("\n    return (0);");
    out.write("\n}\n");

    out.flush();out.close();
  }

  static int size(Instruction i){
    if (i instanceof End)return 1;
    if (i instanceof Unwind)return 1;
    if (i instanceof Eval)return 1;
    if (i instanceof Print)return 1;   
    if (i instanceof PrintString)return 1;
    if (i instanceof MkAp)return 1;    
    if (i instanceof JavaCall)return 1;  
    if (i instanceof StaticJavaCall)return 1;
    if (i instanceof GetRuntime)return 1;
    if (i instanceof Add)return 1;   
    if (i instanceof Sub)return 1;   
    if (i instanceof Mult)return 1;  
    if (i instanceof Div)return 1;   
    if (i instanceof Lt)return 1;    
    if (i instanceof Gt)return 1;    
    if (i instanceof Le)return 1;    
    if (i instanceof Ge)return 1;    
    if (i instanceof Eq)return 1;    
    if (i instanceof Output)return 1;    

    if (i instanceof Push)return 2;
    if (i instanceof Pop)return 2;
    if (i instanceof Jump)return 2;
    if (i instanceof PushInt)return 2;
    if (i instanceof Slide)return 2;
    if (i instanceof Update)return 2;
    if (i instanceof Split)return 2;
    if (i instanceof PushGlobal)return 2;
    if (i instanceof PushChar)return 2;
    if (i instanceof Pack)return 3;
    if (i instanceof CaseJump)
      return 2+((CaseJump)i).entrySet().size()*2;
    return 1;
  }

  static void writeInstruction
                (Pair<Integer,Instruction> pi
                ,List<Pair<Integer,Instruction>> is
                ,Map<String,Integer> globs
                ,int nr
                ,Writer out)throws Exception{
    out.write("\n  ,");  
    Instruction i=pi.e2;
    if (i instanceof End){out.write("END");}
    else if (i instanceof Output){out.write("NOP");}
    else if (i instanceof Unwind){out.write("UNWIND");}
    else if (i instanceof Eval){out.write("EVAL");}
    else if (i instanceof Print){out.write("PRINT");}
    else if (i instanceof PrintString){out.write("PRINTSTRING");}
    else if (i instanceof MkAp){out.write("MKAP");}
    else if (i instanceof JavaCall){out.write("JAVACALL");}
    else if (i instanceof StaticJavaCall){
      out.write("STATICJAVACALL");}
    else if (i instanceof GetRuntime){out.write("GETRUNTIME");}
    else if (i instanceof Add){out.write("ADD");}
    else if (i instanceof Sub){out.write("SUB");}
    else if (i instanceof Mult){out.write("MULT");}
    else if (i instanceof Div){out.write("DIV");}
    else if (i instanceof Lt){out.write("LT");}
    else if (i instanceof Gt){out.write("GT");}
    else if (i instanceof Le){out.write("LE");}
    else if (i instanceof Ge){out.write("GE");}
    else if (i instanceof Eq){out.write("EQ");}

    else if (i instanceof Push){
      out.write("PUSH,");out.write(""+((Push)i).n);}
    else if (i instanceof Pop){
      out.write("POP,");out.write(""+((Pop)i).n);}
    else if (i instanceof Jump){//to do
      out.write("JUMP,");
      out.write(""+newJump(is,nr,((Jump)i).n));
    }
    else if (i instanceof PushInt){
      out.write("PUSHINT,");
      out.write(""+((PushInt)i).n);
    }
    else if (i instanceof Slide){
      out.write("SLIDE,");out.write(""+((Slide)i).n);}
    else if (i instanceof Update){
      out.write("UPDATE,");out.write(""+((Update)i).n);}
    else if (i instanceof Split){
      out.write("SPLIT,");out.write(""+((Split)i).argc);}
    else if (i instanceof PushGlobal){
      out.write("PUSHGLOBAL");
      out.write(","+globs.get(((PushGlobal)i).name));
    }
    else if (i instanceof PushChar){
      out.write("PUSHCHAR,");
      out.write("\'"+((PushChar)i).n+"\'");
    }

    else if (i instanceof Pack){
      out.write("PACK");
      out.write(","+((Pack)i).name);
      out.write(","+((Pack)i).argc);
    }
    else if (i instanceof CaseJump){
      out.write("CASEJUMP");
      out.write(","+((CaseJump)i).size());
      for (Map.Entry<Integer,Integer> jump:((CaseJump)i).entrySet()){
        out.write(","+jump.getKey());
        out.write(","+newJump(is,nr,jump.getValue()));
      }
    }
    out.write(" // "+pi.e1);
  }

  static int newJump
        (List<Pair<Integer,Instruction>> code,int from, int step){
    Integer fr = code.get(from).e1;
    Integer to = code.get(from+step+1).e1;
    return to-fr;
  }

  public static void main(String[] args)throws Exception{
    GmState st
     = ReadGm.readGMCode
         (new DataInputStream
             (new FileInputStream(args[0]+".gmc")));
    writeC(st,args[0]);
  }
}

