// compiles Abacus programs directly to Javabyte code

import scala.sys.process._
import lib._
import abacus._
import recs._
import comp2._


def compile_aprog(p: AProg) : String = {

  def compile_inst(i: AInst, l: Int) : List[String] = {
    ("L" + l.toString + ":") :: (i match {
      case Inc(n) => List("iinc " + n.toString + " 1")
      case Dec(n, l) => List("iload " + n.toString, "ifeq L" + l.toString, "iinc " + n.toString + " -1")
      case Goto(l) => List("goto L" + l.toString)
    })
  }
  
  val code = for ((i, l) <- p.zipWithIndex) 
    yield compile_inst(i, l).mkString("", "\n", "\n") 

  code.mkString
}

def init_regs(ns: List[Int]) : String = {
  
  val code = for ((n, i) <- ns.zipWithIndex)
    yield List("bipush " + n.toString, "istore " + i.toString).mkString("", "\n", "\n") 
  
  code.mkString
}

def print_result(l: Int, r: Int, class_name: String) : String = {
  List("L" + l.toString + ":", 
       "iload " + r.toString,
       "invokestatic " + class_name + "/" + class_name + "/write(I)V").mkString("", "\n", "\n")
}
  
// compiler preludes
def beginning(class_name: String) : String = { 
"\n.class public " + class_name + "." + class_name + """
.super java/lang/Object

.method public <init>()V
   aload_0
   invokenonvirtual java/lang/Object/<init>()V
   return
.end method

.method public static write(I)V 
    .limit locals 5 
    .limit stack 5 
    iload 0 
    getstatic java/lang/System/out Ljava/io/PrintStream; 
    swap 
    invokevirtual java/io/PrintStream/println(I)V 
    return 
.end method


.method public static main([Ljava/lang/String;)V
   .limit locals 200
   .limit stack 200

""" }

val ending = """

   return

.end method
"""


def compile(f: Rec, ns: List[Int]) : Unit = {
  val class_name = "Prog" + ns.mkString  // name of the class and program
  val (aprog, res, max) = compile_rec(f)

  val init_code = init_regs(ns.padTo(max, 0))  // initialising registers with input data
  val main_code = compile_aprog(aprog.p) 
  val end_code = print_result(aprog.p.length, res, class_name)
  val code = beginning(class_name) + init_code + main_code + end_code + ending
  
  val fw = new java.io.FileWriter(class_name + ".j") // temporary file
  fw.write(code) 
  fw.close()

  val _ = ("java -jar jvm/jasmin-2.4/jasmin.jar " + class_name + ".j").!!
  val start = System.nanoTime()
  val result = ("java " + class_name + "/" + class_name).!!
  val end = System.nanoTime()
  println("Result: " + result + "  Time: " + (end - start) / 1.0e9)
}

print("Add(69, 30)   "); compile(Add, List(69, 30))
print("Mult(13, 9)   "); compile(recs.Mult, List(13, 9))
print("Power(3, 4)   "); compile(Power, List(3, 4))
print("Strt:  ");  compile(Strt(2), List(2,3))

println("FACTORIAL")

for (i <- 7 to 9) {
  println("Input:  " + i)
  compile(Fact, List(i))
}


println("PRIME TEST")

for (i <- 10 to 20) {
  println("Input:  " + i)
  compile(Prime, List(i))
}



