//
// Copyright (c) 2009, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
//   11 Sep 09  Andy Frank  Creation
//

using compiler

**
** JsStmt
**
abstract class JsStmt : JsNode
{
  new make(JsCompilerSupport s, Stmt? stmt := null) : super(s, stmt)
  {
  }

  static JsStmt makeFor(JsCompilerSupport s, Stmt stmt)
  {
    switch (stmt.id)
    {
      case StmtId.nop:          return JsNoOpStmt(s)
      case StmtId.expr:         return JsExprStmt(s, stmt)
      case StmtId.localDef:     return JsLocalDefStmt(s, stmt)
      case StmtId.ifStmt:       return JsIfStmt(s, stmt)
      case StmtId.returnStmt:   return JsReturnStmt(s, stmt)
      case StmtId.throwStmt:    return JsThrowStmt(s, stmt)
      case StmtId.forStmt:      return JsForStmt(s, stmt)
      case StmtId.whileStmt:    return JsWhileStmt(s, stmt)
      case StmtId.breakStmt:    return JsBreakStmt(s)
      case StmtId.continueStmt: return JsContinueStmt(s)
      case StmtId.tryStmt:      return JsTryStmt(s, stmt)
      case StmtId.switchStmt:   return JsSwitchStmt(s, stmt)
      default: throw s.err("Unknown StmtId: $stmt.id", stmt.loc)
    }
  }
}

**************************************************************************
** JsNoOpStmt
**************************************************************************

class JsNoOpStmt : JsStmt
{
  new make(JsCompilerSupport s) : super(s) {}
  override Void write(JsWriter out) {}
}

**************************************************************************
** JsExprStmt
**************************************************************************

class JsExprStmt : JsStmt
{
  new make(JsCompilerSupport s, ExprStmt stmt) : super(s, stmt)
  {
    this.expr = JsExpr.makeFor(s, stmt.expr)
  }
  override Void write(JsWriter out)
  {
    expr.write(out)
  }
  JsExpr expr
}

**************************************************************************
** JsLocalDefStmt
**************************************************************************

class JsLocalDefStmt : JsStmt
{
  new make(JsCompilerSupport s, LocalDefStmt lds) : super(s, lds)
  {
    this.lds  = lds
    this.name = lds.name
    this.init = (lds.init != null) ? JsExpr.makeFor(s, lds.init) : null
    if (init != null) init.isLocalDefStmt = true
    JsType.checkJsSafety(lds.ctype, s, lds.loc)
  }
  override Void write(JsWriter out)
  {
    out.w("var ", lds.loc)
    if (init == null) out.w(name, lds.loc)
    else init.write(out)
  }
  LocalDefStmt lds
  Str name
  JsExpr? init
}

**************************************************************************
** JsIfStmt
**************************************************************************

class JsIfStmt : JsStmt
{
  new make(JsCompilerSupport s, IfStmt fs) : super(s)
  {
    this.cond = JsExpr.makeFor(s, fs.condition)
    this.trueBlock  = JsBlock(s, fs.trueBlock)
    this.falseBlock = (fs.falseBlock != null) ? JsBlock(s, fs.falseBlock) : null
  }

  override Void write(JsWriter out)
  {
    out.w("if ("); cond.write(out); out.w(")").nl
    out.w("{").nl
    out.indent
    trueBlock.write(out)
    out.unindent
    out.w("}").nl
    if (falseBlock != null)
    {
      out.w("else").nl
      out.w("{").nl
      out.indent
      falseBlock.write(out)
      out.unindent
      out.w("}").nl
    }
  }

  JsExpr cond
  JsBlock trueBlock
  JsBlock? falseBlock
}

**************************************************************************
** JsReturnStmt
**************************************************************************

class JsReturnStmt : JsStmt
{
  new make(JsCompilerSupport s, ReturnStmt rs) : super(s)
  {
    expr = (rs.expr != null) ? JsExpr.makeFor(s, rs.expr) : null
  }
  override Void write(JsWriter out)
  {
    out.w("return")
    if (expr != null)
    {
      out.w(" ")
      expr.write(out)
    }
  }
  JsExpr? expr
}

**************************************************************************
** JsThrowStmt
**************************************************************************

class JsThrowStmt : JsStmt
{
  new make(JsCompilerSupport s, ThrowStmt ts) : super(s)
  {
    this.expr = JsExpr.makeFor(s, ts.exception)
  }
  override Void write(JsWriter out)
  {
    out.w("throw ")
    expr.write(out)
  }
  JsExpr? expr
}

**************************************************************************
** JsForStmt
**************************************************************************

class JsForStmt : JsStmt
{
  new make(JsCompilerSupport s, ForStmt fs) : super(s)
  {
    this.init   = (fs.init != null) ? JsStmt.makeFor(s, fs.init) : null
    this.cond   = (fs.condition != null) ? JsExpr.makeFor(s, fs.condition) : null
    this.update = (fs.update != null) ? JsExpr.makeFor(s, fs.update) : null
    this.block  = (fs.block != null) ? JsBlock(s, fs.block) : null
  }

  override Void write(JsWriter out)
  {
    out.w("for ("); init?.write(out); out.w("; ")
      cond?.write(out); out.w("; ")
      update?.write(out); out.w(")").nl
    out.w("{").nl
    out.indent
    block?.write(out)
    out.unindent
    out.w("}").nl
  }

  JsStmt? init
  JsExpr? cond
  JsExpr? update
  JsBlock? block
}

**************************************************************************
** JsWhileStmt
**************************************************************************

class JsWhileStmt : JsStmt
{
  new make(JsCompilerSupport s, WhileStmt ws) : super(s)
  {
    this.cond  = JsExpr.makeFor(s, ws.condition)
    this.block = JsBlock(s, ws.block)
  }

  override Void write(JsWriter out)
  {
    out.w("while ("); cond.write(out); out.w(")").nl
    out.w("{").nl
    out.indent
    block.write(out)
    out.unindent
    out.w("}").nl
  }

  JsExpr cond
  JsBlock block
}

**************************************************************************
** JsBreakStmt
**************************************************************************

class JsBreakStmt : JsStmt
{
  new make(JsCompilerSupport s) : super(s) {}
  override Void write(JsWriter out) { out.w("break") }
}

**************************************************************************
** JsContinueStmt
**************************************************************************

class JsContinueStmt : JsStmt
{
  new make(JsCompilerSupport s) : super(s) {}
  override Void write(JsWriter out) { out.w("continue") }
}

**************************************************************************
** JsTryStmt
**************************************************************************

class JsTryStmt : JsStmt
{
  new make(JsCompilerSupport s, TryStmt ts) : super(s)
  {
    this.block  = (ts.block != null) ? JsBlock(s, ts.block) : null
    this.catches = ts.catches.map |c->JsCatch| { JsCatch(s, c) }
    this.finallyBlock = (ts.finallyBlock != null) ? JsBlock(s, ts.finallyBlock) : null
  }

  override Void write(JsWriter out)
  {
    out.w("try").nl
    out.w("{").nl
    out.indent
    block?.write(out)
    out.unindent
    out.w("}").nl

    if (!catches.isEmpty) writeCatches(out)

    if (finallyBlock != null)
    {
      out.w("finally").nl
      out.w("{").nl
      out.indent
      finallyBlock.write(out)
      out.unindent
      out.w("}").nl
    }
  }

  private Void writeCatches(JsWriter out)
  {
    var := support.unique
    hasTyped    := catches.any |c| { c.qname != null }
    hasCatchAll := catches.any |c| { c.qname == null }

    out.w("catch ($var)").nl
    out.w("{").nl
    out.indent
    if (hasTyped) out.w("$var = fan.sys.Err.make($var);").nl

    doElse := false
    catches.each |c|
    {
      if (c.qname != null)
      {
        if (doElse) out.w("else ")
        else doElse = true

        out.w("if ($var instanceof $c.qname)").nl
        out.w("{").nl
        out.indent
        out.w("var $c.var = $var;").nl
        c.write(out)
        out.unindent
        out.w("}").nl
      }
      else
      {
        hasElse := catches.size > 1
        if (hasElse)
        {
          out.w("else").nl
          out.w("{").nl
          out.indent
        }
        c.write(out)
        if (hasElse)
        {
          out.unindent
          out.w("}").nl
        }
      }
    }

    if (!hasCatchAll)
    {
      out.w("else").nl
      out.w("{").nl
      out.indent
      out.w("throw $var;").nl
      out.unindent
      out.w("}").nl
    }
    out.unindent
    out.w("}").nl
  }

  JsBlock? block         // try block
  JsCatch[] catches      // catch blocks
  JsBlock? finallyBlock  // finally block
}

**************************************************************************
** JsCatch
**************************************************************************

class JsCatch : JsNode
{
  new make(JsCompilerSupport s, Catch c) : super(s)
  {
    this.var   = c.errVariable ?: support.unique
    this.qname = (c.errType != null) ? qnameToJs(c.errType) : null
    this.block = (c.block != null) ? JsBlock(s, c.block) : null
  }
  override Void write(JsWriter out)
  {
    block?.write(out)
  }
  Str var          // name of expection variable
  Str? qname       // qname of err type
  JsBlock? block   // catch block
}

**************************************************************************
** JsSwitchStmt
**************************************************************************

class JsSwitchStmt : JsStmt
{
  new make(JsCompilerSupport s, SwitchStmt ss) : super(s)
  {
    this.cond  = JsExpr.makeFor(s, ss.condition)
    this.cases = ss.cases.map |c->JsCase| { JsCase(s, c) }
    this.defBlock = (ss.defaultBlock != null) ? JsBlock(s, ss.defaultBlock) : null
  }

  override Void write(JsWriter out)
  {
    var := support.unique
    out.w("var $var = "); cond.write(out); out.w(";").nl
    cases.each |c, i|
    {
      if (i > 0) out.w("else ")
      out.w("if (")
      c.cases.each |e, j|
      {
        if (j > 0) out.w(" || ")
        out.w("fan.sys.ObjUtil.equals($var,"); e.write(out); out.w(")")
      }
      out.w(")").nl
      out.w("{").nl
      out.indent
      c.block?.write(out)
      out.unindent
      out.w("}").nl
    }
    if (defBlock != null)
    {
      if (!cases.isEmpty)
      {
        out.w("else").nl
        out.w("{").nl
        out.indent
        defBlock.write(out)
        out.unindent
        out.w("}").nl
      }
      else { defBlock.write(out) }
    }
  }

  JsExpr cond         // switch condition
  JsCase[] cases      // case stmts
  JsBlock? defBlock   // default case

}

**************************************************************************
** JsCase
**************************************************************************

class JsCase : JsNode
{
  new make(JsCompilerSupport s, Case c) : super(s)
  {
    this.cases = c.cases.map |ex->JsExpr| { JsExpr.makeFor(s, ex) }
    this.block = (c.block != null) ? JsBlock(s, c.block) : null
  }
  override Void write(JsWriter out)
  {
    block?.write(out)
  }
  JsExpr[] cases
  JsBlock? block
}