package other

import scala.util.parsing.combinator._
import sun.java2d.pipe.SpanShapeRenderer.Simple
import other.Tudo.rotulos

class Arith extends JavaTokenParsers {
  //CONTROLE etc.
  val fazTudo = Tudo
  var nivelLexico = 0
  val rotStack = Tudo.proc.RotStack
  
  val procStack = collection.mutable.Stack[Entrada]()
  val paramCounterStack = collection.mutable.Stack[Int]()

  def eh_param = procStack match {
    case stack if ! stack.isEmpty => true
//      stack.top.isInstanceOf[Procedimento] || stack.top.isInstanceOf[Funcao]
    case _ => false
  }
  
  //PARSER
  object PascalParser {

    implicit class isolavel(s:String) {
      def iso = (s + "\\s").r ^^ { _.take(s.size) }
    }
    
    //PROGRAM
    def program  = {
      def programInOut = "(" ~> repsep(ident,",") <~ ")"
      fazTudo.iniciaPrograma
      "program".iso ~> ident ~ programInOut <~ ";" ~ bloco ~ "." ^^
        { _ => fazTudo.encerraPrograma }
    }
    
    def bloco:Parser[Any] =
        (opt(dec_rotulos) ~ opt(dec_vars) ^^ 
            { _ => 
              if (nivelLexico==0) Tudo.aplica("DSVS R00");
              else Tudo.aplica("DSVS "+Tudo.proc.RotStack.top)
              nivelLexico+=1 } ) ~
        (opt(dec_subrotinas) ^^ 
          { _ => 
            nivelLexico-=1
            if (nivelLexico==0) Tudo.aplica("R00: NADA") 
            else
              if (Tudo.MEPA.last == "DSVS "+Tudo.proc.RotStack.top) {
                //DSVS R003; R003: seguidos!
                Tudo.rotulos.--
                Tudo.proc.RotStack.pop()
                Tudo.MEPA.remove(Tudo.MEPA.size - 1)
              } else
                Tudo.aplica(Tudo.proc.RotStack.pop()+": NADA")
          }
        ) ~ comando_composto

    //ROTULOS
    def dec_rotulos = "label".iso ~> """\d+""".r ~ rep("," ~> """\d+""".r) <~ ";" ^^
      { case x~xs => fazTudo.insereRotulos(x::xs, nivelLexico) }
    
    //VARIAVEIS
    def dec_vars = "var".iso ~> dec_var ~ rep(";" ~> dec_var) <~ ";" ^^
      { case xs~xss => fazTudo.insereIntVars(xs ::: xss.flatten, nivelLexico) } 
    def dec_var  = lista_ident <~ ":" ~ tipo
    def lista_ident = ident ~ rep( "," ~> ident ) ^^ { mkList }
    def tipo = "integer"
    
    //SUB-ROTINAS
    def dec_subrotinas = rep( (dec_proc | dec_func) <~ bloco ~ ";" ^^ 
        {  e => fazTudo.proc.saiProcFunc(nivelLexico); e} )
    
    def dec_proc = "procedure".iso ~> ident ~ opt(params_formais) <~ ";" ^^
      { case id ~ lstOptIds => fazTudo.proc.entraProc(id, lstOptIds, nivelLexico) }
    def dec_func = "function".iso  ~> ident ~ opt(params_formais) <~ ":" ~ tipo ~ ";" ^^
      { case id ~ lstOptIds => fazTudo.proc.entraFunc(id, lstOptIds, nivelLexico) }
    
    def params_formais = "(" ~> params ~ rep(";" ~> params) <~ ")" ^^ { s_xs => mkList(s_xs).map{case opt~id=>(opt,id)}}
    def params = opt("var".iso) ~ lista_ident <~ ":" ~ tipo
    
    //COMANDOS
    def comando_composto:Parser[Any] = "begin".iso ~ comando ~ rep(";" ~ comando) ~ "end"
    def comando = opt("""\d+""".r <~ ":" ^^ { rot => fazTudo.entraRotulo(rot,nivelLexico) }) <~ comando_sem_rotulo
    def comando_sem_rotulo:Parser[Any] = desvio | atribuicao | repeticao |
        condicional | comando_composto | chamaId | expr_simples
        
    def atribuicao = ident <~ ":=" ~ expr_simples ^^
        { case id => fazTudo.atribuicao(id) }
    
    def desvio = "goto".iso ~> """\d+""".r ^^ { rot => fazTudo.gotoRotulo(rot,nivelLexico) }
    
    def condicional = 
      ("if".iso ~ boolexpr ^^ { e =>
        val r = Tudo.rotulos.geraRotulo
        Tudo.aplica("DSVF "+r)
        rotStack.push(r)
        e
      }) ~ 
      "then".iso ~
      (comando_sem_rotulo ^^ { _ => Tudo.aplica(rotStack.pop+": NADA") }) ~
      opt(
          ("else".iso ^^ { _ =>
            val gotoElse = Tudo.MEPA.remove(Tudo.MEPA.size - 1)//.takeWhile {_!=':'}
            //rotStack.push(fim_if)
            rotStack.push(rotulos.geraRotulo)
            Tudo.aplica("DSVS "+rotStack.top) //Vai pro fim do if
            Tudo.aplica(gotoElse)
          }) 
          ~ (comando_sem_rotulo ^^ { _ => Tudo.aplica(rotStack.pop+": NADA") })
      )
       
    def repeticao = 
      (("while" ^^ { _=> 
          val rotLoop = rotulos.geraRotulo
          Tudo.aplica(rotLoop+": NADA")
          rotStack.push(rotLoop)
      }) ~ boolexpr ~ "do" ^^ { _ => 
          val rotFim = rotulos.geraRotulo
          Tudo.aplica("DSVF "+rotFim)
          rotStack.push(rotFim)
      }) ~ comando_sem_rotulo ^^ { _=>
          val rotFim = rotStack.pop
          val rotLoop = rotStack.pop
          Tudo.aplica("DSVS "+rotLoop)
          Tudo.aplica(rotFim+": NADA")
      }
    
    def amem1ForFunctions(id:String) {
      val opt = TabelaSimb.getById(id)
      opt match {
        case Some(fun) if fun.isInstanceOf[Funcao] => Tudo.aplica("AMEM 1")
        case _ => //Dont care
      }
    }
    
    //EXPRESSOES BOOLEANAS
    def boolexpr:Parser[Unit] =
      (
        "(" ~> boolexpr_simples ~> rep( ("and"|"or") <~ boolexpr_simples ) <~ ")" |
        boolexpr_simples ~> rep ( ("and"|"or") <~ boolexpr )
      ) ^^
      { lst => lst.reverse.foreach { case "and" => fazTudo.aplica("CONJ")
                                     case "or"  => fazTudo.aplica("DISJ")}
      }
    
    def boolexpr_simples =
      (
        expr_simples ~> relacao <~ expr_simples | 
        "(" ~ expr_simples ~> relacao <~ expr_simples ~ ")"
      ) ^^
          {
            case "="  => fazTudo.aplica("CMIG")
            case "<>" => fazTudo.aplica("CMDG")
            case "<=" => fazTudo.aplica("CMEG")
            case ">=" => fazTudo.aplica("CMAG")
            case "<"  => fazTudo.aplica("CMME")
            case ">"  => fazTudo.aplica("CMMA")
          }
    def relacao = "<="|">="|"="|"<>"|"<"|">"
    
    //EXPRESSOES
    
    def lista_expr = 
      (expr_simples ^^ { foreachExpr }) ~
        rep("," ~> (expr_simples ^^ { foreachExpr })) ^^ { mkList }
    
    def foreachExpr(o:Object) = {
      procStack.top.id  match {
        case "write" => fazTudo.aplica("IMPR")
        case "read"  => //ok
        case _ =>
      }
      val isByRef = Tudo.isAtArgByRef()
      inc_pCount
      // Check for byRef args
      o match {
        case s:String => //Okey
        case o => 
          if ( procStack.top.id=="read" )
            assert(false, "Função read recebendo uma expressão!")
          else if (isByRef) 
            assert(false, "Parametro por referencia recebendo valor! Funcão/procedimento: " + procStack.top.id)
          //else println("Okey")
      }
      o
    }
    
    def inc_pCount {
      paramCounterStack.push(1+paramCounterStack.pop)
    }
    
    def expr_simples:Parser[Object] = termo ~ rep( ("+"|"-") ~ termo ) ^^
    {
      case t ~ lst if ! lst.isEmpty =>
        lst.reverse.foreach {
          case ("+" ~ t) => fazTudo.aplica("SOMA")
          case ("-" ~ t) => fazTudo.aplica("SUBT")
          case _ => println("op???")
        }
        new AnyRef
      case t ~ Nil => t  
    }
    
    def termo = fator ~ rep( ("*"|"div"|"/") ~ fator ) ^^
      { case fat~lst if ! lst.isEmpty =>
          lst.map{ case ~(str,_) => str }.reverse.foreach {
            case "*" => fazTudo.aplica("MULT")
            case "div"|"/" => fazTudo.aplica("DIVI")
          }
          new AnyRef
        case fat ~ Nil => fat 
      }
    def fator = ( "("~expr_simples~")" | chamaId | numero ) ^^ { case p:String => p; case _ => new AnyRef}
    
    def getParamsCount(e:Entrada) = e match {
      case f:Funcao => f.paramInfo.size
      case p:Procedimento => p.paramInfo.size
    }
    
  
  def ehProcFun(id:String) = {
    TabelaSimb.getById(id) match {
      case Some(e) => 
        e.isInstanceOf[Procedimento] || e.isInstanceOf[Funcao]
      case None => false
    }
  }
  
  def assert(bool: => Boolean, errMsg:String) {
    if ( ! bool ) {
      println("Erro: " + errMsg)
      sys.exit(1)
    }
  }
    
  def entraId(id:String) 
  {
    if (ehProcFun(id)) {
      if ( TabelaSimb.getById(id).get.isInstanceOf[Funcao] )
        Tudo.aplica("AMEM 1")
      procStack.push(TabelaSimb.getById(id).get)
      paramCounterStack.push(0)
      
    } else if (id=="write" || id=="read") {
      
      procStack.push(new Entrada(id))
      paramCounterStack.push(0)
      id match {
        case "write" =>
        case "read"  =>
      }
      
    }
  }
  
  def saiId(id:String)
  {
    
    if (id=="write" || id=="read") { 
      procStack.pop
      return
    }
    if (!procStack.isEmpty && procStack.top.id == "read"){
      fazTudo.aplica("LEIT")
      if (id != "read") fazTudo.atribuicao(id)
      return
    }

      
    if ( ehProcFun(id) ) {
      val proc = procStack.pop
      val nParams = paramCounterStack.pop
      assert(nParams == getParamsCount(proc), 
          "Número de parametros incorretos na chamada da função/procedimento "+id)
    }

    fazTudo.geraMepaDoId(id, nivelLexico)
  }
  
  def chamaId =
   (ident ^^ { id => entraId(id); id }) <~ opt("(" ~ lista_expr ~ ")") ^^ 
   { id => saiId(id); id }
    
  def numero = wholeNumber ^^ { case x => fazTudo.literal(x.toInt) }
    
  }
}

object MyParser extends Arith {
  
  def main(args: Array[String]) {
//    if (args.size != 2) {
//      println("./compilador [ArqEntrada] [NomeArquivoSaida]")
//      sys.exit(1)
//    }
//    val pascalSource = io.Source.fromFile(args(0)).getLines().mkString("\n")
    val pascalSource = ArqExemplos.exemplo8_10
    println(parseAll(PascalParser.program, pascalSource))
    Tudo.MEPA foreach println
//    val mp = Tudo.MEPA
//    val pw = new java.io.PrintWriter(args(1))
//    mp foreach pw.println
  }
  

//TODO: function return as parameter/atribution


  
val noloopPascalProgram = 
"""program exemplo12 (input,output);
  var x: integer;
  function p(a:integer; var t: integer):integer;
  label 200;
  var a,b:integer;
      c:integer;
      procedure q(var a:integer; y:integer);
      begin
        q(a,c+b+a+20);
        write(b,a);
        read(a,b)
      end;
  begin
    p := p(20,a);
    t := (23 div (x/2)) + b
  end;
begin 
  x := p(x,x);
  write(x)
end.
"""
  
  
val loopPascalProgram = 
"""program exemplo12 (input,output);
  var x: integer;
  function p(var t: integer):integer;
  label 200;
  var a,b:integer;
      c:integer;

  begin
    while x>2 do begin
      x := x+1;
      p := p(t-10)
    end;
    t := x - 20*4 - (23 div (x/2)) + b
  end;
begin 
  x := p(x);
  write(x)
end.
"""
  
val simplePascalProgram = 
"""program exemplo12 (input,output);
  var x: integer;
  function p(var t: integer):integer;
  label 200;
  var a,b,c:integer;
  begin
    200: if t>3 and (p(x)<4 or t=-1) and t>=1 then
      write(t);
    read(x);
    goto 200;
    t := x - 20*4 - (23 div (x/2)) + b;
    p := t
  end;
begin 
  x := p(x);
  write(x)
end.
"""
val complexPascalProgram = 
"""program exemplo12 (input,output);
    var x: integer;
    procedure p(var t: integer);
      label 100,200;
      var s : integer;
      function f(z:integer):integer;
        begin
          if z=1 then z:=2;
          if z<0 and 10>2 then goto 200
          else if z=0 then f:=2
          else f:=f(z-2)*z+1
        end;
   begin
     100: s:=f(t); t:=s;
     if t<x then goto 100;
     200: x:=x-1
   end;
   procedure r;
     procedure q;
       var y:integer;
       begin read(y); p(y); write(y) end;
     begin q end;
   begin read(x); r end.
"""

}