package codegen

import (
  "fmt"
  "strconv"
  "strings"

  "github.com/marzeq/quokka/parser"
  "github.com/marzeq/quokka/shared"
  "github.com/marzeq/quokka/typechecker"
)

type CodeGen struct {
  tmpVarId  uint
  tmpLblId  uint
  cnstId    uint
  rootNode  *parser.RootNode
  funcSigs  map[string]*typechecker.FunctionSig
  typeTable shared.TypeTable
}

func NewCodeGen(rootNode *parser.RootNode, funcSigs map[string]*typechecker.FunctionSig, typeTable shared.TypeTable) *CodeGen {
  return &CodeGen{
    tmpVarId: 0,
    tmpLblId: 0,
    cnstId: 0,
    rootNode: rootNode,
    funcSigs: funcSigs,
    typeTable: typeTable,
  }
}

func (cg *CodeGen) GetTmpVar() string {
  val := fmt.Sprintf("%%___tmpvar%d", cg.tmpVarId)
  cg.tmpVarId++
  return val
}

func (cg *CodeGen) GetTmpLbl() string {
  val := fmt.Sprintf("@___tmplbl%d", cg.tmpLblId)
  cg.tmpLblId++
  return val
}

func (cg *CodeGen) GetCnst() string {
  val := fmt.Sprintf("$___cnst%d", cg.cnstId)
  cg.cnstId++
  return val
}

func (cg *CodeGen) EmitIR() (string, error) {
  ir := ""
  gsetups := []string{}

  for _, n := range cg.rootNode.Body {
    switch node := n.(type) {
    case *parser.StructDefNode:
      continue
    case *parser.FunctionDefNode:
      if node.Body == nil {
        continue
      }

      funcIr, gstps, err := cg.GenerateFuncIR(node)
      if err != nil {
        return "", err
      }

      ir += funcIr
      gsetups = append(gsetups, gstps...)
    default:
      return "", shared.NewError(node.GetLoc(), "unexpected node")
    }
  }

  for name, tpe := range cg.typeTable {
    if !tpe.IsStruct() {
      continue
    }

    ir += fmt.Sprintf("type :%s = {", name)
    for _, field := range tpe.(shared.Struct).Fields {
      ir += fmt.Sprintf(" %s,", mapTypeToIRType(field.R))
    }
    ir += "}\n"
  }

  for _, gsetup := range gsetups {
    ir = gsetup + "\n" + ir
  }

  return ir, nil
}

func (cg *CodeGen) GenerateFuncIR(funcNode *parser.FunctionDefNode) (string, []string, error) {
  cg.tmpVarId = 0
  cg.tmpLblId = 0
  prologue := ""
  body := ""
  epilogue := ""
  gsetups := []string{}

  fsig := cg.funcSigs[funcNode.Name]

  if fsig.Name == "main" {
    prologue += "export function w $main() {\n@start"
  } else {
    prologue += fmt.Sprintf("function %s $%s(", mapTypeToIRType(fsig.RetType), fsig.Name)
    after := ""
    for i, arg := range fsig.ArgTypes {
      tnm := cg.GetTmpVar()
      tpe := mapTypeToIRType(arg.R)
      prologue += fmt.Sprintf("%s %s", tpe, tnm)
      if i != len(fsig.ArgTypes)-1 {
        prologue += ", "
      }

      after += fmt.Sprintf("%%%s =l %s\n", arg.L, emitAllocForType(arg.R, 1))
      after += fmt.Sprintf("store%s %s, %%%s", tpe, tnm, arg.L)
    }

    prologue += ") {\n@start\n"
    prologue += after

    if fsig.ImplicitReturn {
      epilogue = "\nret"
    }
  }

  epilogue += "\n}\n"


  switch funcBody := funcNode.Body.(type) {
  case *parser.BlockNode:
    b, gstps, _, err := cg.GenerateBlockIR(funcBody, "", "")
    if err != nil {
      return "", nil, err
    }
    body = b

    if len(funcBody.Body) > 1 {
      switch funcBody.Body[len(funcBody.Body)-1].(type) {
      case *parser.IfNode:
        if fsig.RetType != shared.PRIMITIVE_VOID {
          epilogue = "ret 0" + epilogue
        }
      }
    }
    gsetups = append(gsetups, gstps...)
  default:
    if expr, ok := funcNode.Body.(parser.ExpressionNode); ok {
      val, setups, gstps, _, err := cg.GenerateExprIR(expr)
      if err != nil {
        return "", nil, err
      }
      for _, setup := range setups {
        body += "\n" + setup
      }
      body += "\nret " + val
      gsetups = append(gsetups, gstps...)
    }
  }

  return prologue + body + epilogue, gsetups, nil
}

func (cg *CodeGen) GenerateBlockIR(blockNode *parser.BlockNode, loopBegin, loopEnd string) (string, []string, bool, error) {
  body := ""
  endsWithTerminator := false
  gsetups := []string{}
  for i, node := range blockNode.Body {
    ir, setups, gstps, err := cg.GenerateStmtIR(node, i == len(blockNode.Body)-1, loopBegin, loopEnd)
    if err != nil {
      return "", nil, false, err
    }

    for _, setup := range setups {
      body += "\n" + setup
    }
    body += "\n" + ir

    if IsTerminatorInstruction(ir) {
      endsWithTerminator = true
    } else {
      endsWithTerminator = false
    }
    gsetups = append(gsetups, gstps...)
  }

  return body, gsetups, endsWithTerminator, nil
}

func IsTerminatorInstruction(ir string) bool {
  return strings.HasPrefix(ir, "ret") ||
  strings.HasPrefix(ir, "jmp") ||
  strings.HasPrefix(ir, "jnz")
}

func (cg *CodeGen) GenerateStmtIR(stmtNd parser.Node, last bool, loopBegin, loopEnd string) (string, []string, []string, error) {
  line := ""
  setups := []string{}
  gsetups := []string{}
  switch stmtNode := stmtNd.(type) {
  case *parser.ControlKeywordNode:
    switch stmtNode.Keyword {
    case parser.KEYWORD_TYPE_RETURN:
      line = "ret"
      if stmtNode.ReturnValue != nil {
        line += " "
        val, setps, gsetps, _, err := cg.GenerateExprIR(stmtNode.ReturnValue)
        if err != nil {
          return "", nil, nil, err
        }
        line += val
        setups = append(setups, setps...)
        gsetups = append(gsetups, gsetps...)
      }
    case parser.KEYWORD_TYPE_BREAK:
      if loopEnd == "" {
        return "", nil, nil, shared.NewError(stmtNode.GetLoc(), "used 'break' keyword outside loop")
      }
      line = fmt.Sprintf("jmp %s", loopEnd)
    case parser.KEYWORD_TYPE_CONTINUE:
      if loopBegin == "" {
        return "", nil, nil, shared.NewError(stmtNode.GetLoc(), "used 'continue' keyword outside loop")
      }
      line = fmt.Sprintf("jmp %s", loopBegin)
    }

  case *parser.DeclarationNode:
    nme := stmtNode.Name
    setups = append(setups, fmt.Sprintf("%%%s =l %s", nme, emitAllocForType(stmtNode.Value.GetType(), 1)))

    val, setps, gsetps, _, err := cg.GenerateExprIR(stmtNode.Value)
    if err != nil {
      return "", nil, nil, err
    }

    line += fmt.Sprintf("store%s %s, %%%s", mapTypeToIRType(stmtNode.Value.GetType()), val, nme)
    setups = append(setups, setps...)
    gsetups = append(gsetups, gsetps...)
  case *parser.AssignmentNode:
    switch assignee := stmtNode.Assignee.(type) {
    case *parser.IdentifierNode:
      val, setps, gsetps, _, err := cg.GenerateExprIR(stmtNode.Value)
      if err != nil {
        return "", nil, nil, err
      }

      line = fmt.Sprintf("store%s %s, %%%s", mapTypeToIRType(stmtNode.Value.GetType()), val, assignee.Name)
      setups = append(setups, setps...)
      gsetups = append(gsetups, gsetps...)
    case *parser.UnaryOpNode:
      val, setps, gsetps, _, err := cg.GenerateExprIR(stmtNode.Value)
      if err != nil {
        return "", nil, nil, err
      }
      ptrVal, ptrSetps, ptrGSetps, _, err := cg.GenerateExprIR(assignee.Operand)
      if err != nil {
        return "", nil, nil, err
      }
      line = fmt.Sprintf("store%s %s, %s", mapTypeToIRType(stmtNode.Value.GetType()), val, ptrVal)
      setups = append(setups, ptrSetps...)
      setups = append(setups, setps...)
      gsetups = append(gsetups, ptrGSetps...)
      gsetups = append(gsetups, gsetps...)
    }
  case *parser.FunctionCallNode:
    fsig := cg.funcSigs[stmtNode.Name.Name]

    line = fmt.Sprintf("call $%s(", fsig.Name)
    for i, arg := range stmtNode.Args {
      if i == len(fsig.ArgTypes) {
        line += "..., "
      }

      argType := ""

      val, setps, gsetps, exprTpe, err := cg.GenerateExprIR(arg)
      if err != nil {
        return "", nil, nil, err
      }
      if i < len(fsig.ArgTypes) {
        argType = mapTypeToIRType(fsig.ArgTypes[i].R)
      } else {
        argType = exprTpe
      }
      line += fmt.Sprintf("%s %s", argType, val)
      if i != len(stmtNode.Args)-1 {
        line += ", "
      }

      setups = append(setups, setps...)
      gsetups = append(gsetups, gsetps...)
    }

    line += ")"

  case *parser.IfNode:
    endLabel := cg.GetTmpLbl()

    condVal, condSetups, condGSetups, _, err := cg.GenerateExprIR(stmtNode.IfBranch.Condition)
    if err != nil {
      return "", nil, nil, err
    }
    ifLabel := cg.GetTmpLbl()
    elseCheckLabel := cg.GetTmpLbl()

    setups = append(setups, condSetups...)
    gsetups = append(gsetups, condGSetups...)
    setups = append(setups, fmt.Sprintf("jnz %s, %s, %s", condVal, ifLabel, elseCheckLabel))

    ifBlockIR, blockGSetups, ifTerminated, err := cg.GenerateBlockIR(stmtNode.IfBranch.Node, loopBegin, loopEnd)
    if err != nil {
      return "", nil, nil, err
    }
    setups = append(setups, fmt.Sprintf("%s", ifLabel))
    setups = append(setups, ifBlockIR)
    if !ifTerminated {
      setups = append(setups, fmt.Sprintf("jmp %s", endLabel))
    }
    gsetups = append(gsetups, blockGSetups...)

    currentCheckLabel := elseCheckLabel

    for _, elseIf := range stmtNode.ElseIfBranches {
      elseIfCondVal, elseIfCondSetups, elseIfCondGSetups, _, err := cg.GenerateExprIR(elseIf.Condition)
      if err != nil {
        return "", nil, nil, err
      }

      elseIfLabel := cg.GetTmpLbl()
      nextCheckLabel := cg.GetTmpLbl()

      setups = append(setups, fmt.Sprintf("%s", currentCheckLabel))
      setups = append(setups, elseIfCondSetups...)
      gsetups = append(gsetups, elseIfCondGSetups...)
      setups = append(setups, fmt.Sprintf("jnz %s, %s, %s", elseIfCondVal, elseIfLabel, nextCheckLabel))

      elseIfBlockIR, blockGSetups, elseIfTerminated, err := cg.GenerateBlockIR(elseIf.Node, loopBegin, loopEnd)
      if err != nil {
        return "", nil, nil, err
      }
      setups = append(setups, fmt.Sprintf("%s", elseIfLabel))
      setups = append(setups, elseIfBlockIR)
      if !elseIfTerminated {
        setups = append(setups, fmt.Sprintf("jmp %s", endLabel))
      }
      gsetups = append(gsetups, blockGSetups...)

      currentCheckLabel = nextCheckLabel
    }

    if stmtNode.ElseBranch != nil {
      elseLabel := cg.GetTmpLbl()
      setups = append(setups, fmt.Sprintf("%s", currentCheckLabel))
      setups = append(setups, fmt.Sprintf("jmp %s", elseLabel))

      elseBlockIR, blockGSetups, elseTerminated, err := cg.GenerateBlockIR(stmtNode.ElseBranch, loopBegin, loopEnd)
      if err != nil {
        return "", nil, nil, err
      }
      setups = append(setups, fmt.Sprintf("%s", elseLabel))
      setups = append(setups, elseBlockIR)
      if !elseTerminated {
        setups = append(setups, fmt.Sprintf("jmp %s", endLabel))
      }
      gsetups = append(gsetups, blockGSetups...)
    } else {
      setups = append(setups, fmt.Sprintf("%s", currentCheckLabel))
      setups = append(setups, fmt.Sprintf("jmp %s", endLabel))
    }

    setups = append(setups, fmt.Sprintf("%s", endLabel))

  case *parser.ForNode:
    beginLbl := cg.GetTmpLbl()
    bodyLbl := cg.GetTmpLbl()
    endLbl := cg.GetTmpLbl()
    line = beginLbl + "\n"
    body, blockGSetups, _, err := cg.GenerateBlockIR(stmtNode.Body, beginLbl, endLbl)
    if err != nil {
      return "", nil, nil, err
    }

    if len(stmtNode.ExprsOrStmts) == 1 {
      if condExpr, ok := stmtNode.ExprsOrStmts[0].(parser.ExpressionNode); ok {
        cond, setps, gsetps, _, err := cg.GenerateExprIR(condExpr)
        if err != nil {
          return "", nil, nil, err
        }

        for _, stp := range setps {
          line += stp + "\n"
        }
        gsetups = append(gsetups, gsetps...)
        line += fmt.Sprintf("jnz %s %s, %s\n", cond, bodyLbl, endLbl)
        line += bodyLbl + "\n" + body + "\n"
        line += fmt.Sprintf("jmp %s\n", beginLbl)
      } else {
        return "", nil, nil, shared.NewError(stmtNode.GetLoc(), "unexpected statement in for loop")
      }
    } else if len(stmtNode.ExprsOrStmts) == 3 {
      l, setps, gsetps, err := cg.GenerateStmtIR(stmtNode.ExprsOrStmts[0], last, beginLbl, endLbl)
      if err != nil {
        return "", nil, nil, err
      }

      setups = append(setups, setps...)
      setups = append(setups, l)
      gsetups = append(gsetups, gsetps...)

      if condExpr, ok := stmtNode.ExprsOrStmts[1].(parser.ExpressionNode); ok {
        cond, setps, gsetps, _, err := cg.GenerateExprIR(condExpr)
        if err != nil {
          return "", nil, nil, err
        }

        for _, stp := range setps {
          line += stp + "\n"
        }
        gsetups = append(gsetups, gsetps...)
        line += fmt.Sprintf("jnz %s, %s, %s\n", cond, bodyLbl, endLbl)
        line += bodyLbl + "\n" + body + "\n"
      } else {
        return "", nil, nil, shared.NewError(stmtNode.GetLoc(), "unexpected statement in for loop")
      }

      reass, setps, gsetps, err := cg.GenerateStmtIR(stmtNode.ExprsOrStmts[2], last, beginLbl, endLbl)
      if err != nil {
        return "", nil, nil, err
      }

      for _, stp := range setps {
        line += stp + "\n"
      }
      gsetups = append(gsetups, gsetps...)
      line += reass + "\n"
      line += fmt.Sprintf("jmp %s\n", beginLbl)
    } else {
      line += bodyLbl + "\n" + body + "\n"
    }

    line += endLbl
    gsetups = append(gsetups, blockGSetups...)
  default:
    return "", nil, nil, shared.NewError(stmtNode.GetLoc(), "unexpected statement")
  }

  return line, setups, gsetups, nil
}

func (cg *CodeGen) GenerateExprIR(eNode parser.ExpressionNode) (string, []string, []string, string, error) {
  val := ""
  setups := []string{}
  gsetups := []string{}
  tpe := mapTypeToIRType(eNode.GetType())
  switch exprNode := eNode.(type) {
  case *parser.IdentifierNode:
    irt := mapTypeToIRType(exprNode.ExprType)
    tnme := cg.GetTmpVar()
    setups = append(setups, fmt.Sprintf("%s =%s load%s %%%s", tnme, irt, irt, exprNode.Name))
    val = tnme

  case *parser.NumberLiteralNode:
    val = exprNode.Value

  case *parser.CharLiteralNode:
    val = strconv.Itoa(int(exprNode.Value))

  case *parser.StringLiteralNode:
    nme := cg.GetCnst()
    val = nme
    gsetups = append(gsetups, fmt.Sprintf("data %s = { b \"%s\", b 0 }", nme, strings.NewReplacer("\\", `\\`,
      "\"", `\"`,
      "\n", `\n`,
      "\r", `\r`,
      "\t", `\t`,
      "\b", `\b`,
      "\f", `\f`,
      "\v", `\v`,
      "\a", `\a`,
      string(rune(0)), `\0`,
      ).Replace(exprNode.Value)))

  case *parser.BoolLiteralNode:
    if exprNode.Value == "true" {
      val = "1"
    } else {
      val = "0"
    }

  case *parser.FunctionCallNode:
    fsig := cg.funcSigs[exprNode.Name.Name]
    val = cg.GetTmpVar()
    tpe = mapTypeToIRType(fsig.RetType)
    setup := fmt.Sprintf("%s =%s call $%s(", val, tpe, exprNode.Name.Name)
    for i, arg := range exprNode.Args {
      if i == len(fsig.ArgTypes) {
        setup += "..., "
      }

      argType := ""

      v, st, gst, exprTpe, err := cg.GenerateExprIR(arg)
      if err != nil {
        return "", nil, nil, "", err
      }
      if i < len(fsig.ArgTypes) {
        argType = mapTypeToIRType(fsig.ArgTypes[i].R)
      } else {
        argType = exprTpe
      }
      setup += fmt.Sprintf("%s %s", argType, v)
      if i != len(exprNode.Args)-1 {
        setup += ", "
      }

      setups = append(setups, st...)
      gsetups = append(gsetups, gst...)
    }
    setup += ")"
    setups = append(setups, setup)

  case *parser.BinaryOpNode:
    tpe = mapTypeToIRType(exprNode.Operand1.GetType())

    op := ""
    switch exprNode.Op {
    case parser.BINARY_OP_ADD:
      op = "add"
    case parser.BINARY_OP_SUBTRACT:
      op = "sub"
    case parser.BINARY_OP_MULTIPLY:
      op = "mul"
    case parser.BINARY_OP_DIVIDE:
      if shared.IsUnsignedType(exprNode.Operand1.GetType()) {
        op = "udiv"
      } else {
        op = "div"
      }
    case parser.BINARY_OP_MODULO:
      if shared.IsUnsignedType(exprNode.Operand1.GetType()) {
        op = "urem"
      } else {
        op = "rem"
      }
    case parser.BINARY_OP_EQUAL:
      op = "ceq" + tpe
    case parser.BINARY_OP_NOT_EQUAL:
      op = "cne" + tpe
    case parser.BINARY_OP_LESS:
      if shared.IsUnsignedType(exprNode.Operand1.GetType()) {
        op = "cult" + tpe
      } else {
        op = "cslt" + tpe
      }
    case parser.BINARY_OP_GREATER:
      if shared.IsUnsignedType(exprNode.Operand1.GetType()) {
        op = "cugt" + tpe
      } else {
        op = "csgt" + tpe
      }
    case parser.BINARY_OP_LESS_EQUAL:
      if shared.IsUnsignedType(exprNode.Operand1.GetType()) {
        op = "cule" + tpe
      } else {
        op = "csle" + tpe
      }
    case parser.BINARY_OP_GREATER_EQUAL:
      if shared.IsUnsignedType(exprNode.Operand1.GetType()) {
        op = "cuge" + tpe
      } else {
        op = "csge" + tpe
      }
    }

    nm := cg.GetTmpVar()
    val = fmt.Sprintf("%s", nm)
    v1, st1, gst1, _, err := cg.GenerateExprIR(exprNode.Operand1)
    if err != nil {
      return "", nil, nil, "", err
    }
    v2, st2, gst2, _, err := cg.GenerateExprIR(exprNode.Operand2)
    if err != nil {
      return "", nil, nil, "", err
    }
    setup := fmt.Sprintf("%s =%s %s %s, %s", val, tpe, op, v1, v2)
    setups = append(setups, st1...)
    setups = append(setups, st2...)
    gsetups = append(gsetups, gst1...)
    gsetups = append(gsetups, gst2...)
    setups = append(setups, setup)

  case *parser.UnaryOpNode:
    nm := cg.GetTmpVar()
    val = fmt.Sprintf("%s", nm)

    switch exprNode.Op {
    case parser.UNARY_OP_REFERENCE:
      switch identNode := exprNode.Operand.(type) {
      case *parser.IdentifierNode:
        setups = append(setups, fmt.Sprintf("%s =l copy %%%s", val, identNode.Name))
      default:
        return "", nil, nil, "", shared.NewError(exprNode.Loc, "cannot take address of non-variable expression")
      }
    case parser.UNARY_OP_DEREFERENCE:
      switch operand := exprNode.Operand.(type) {
      case *parser.IdentifierNode:
        nm2 := cg.GetTmpVar()
        val2 := fmt.Sprintf("%s", nm2)
        setups = append(setups, fmt.Sprintf("%s =l loadl %%%s", val2, operand.Name))
        setups = append(setups, fmt.Sprintf("%s =%s load%s %s", val, mapTypeToIRType(exprNode.ExprType), mapTypeToIRType(exprNode.ExprType), val2))
      default:
        return "", nil, nil, "", shared.NewError(exprNode.Loc, "cannot dereference non-variable expression")
      }
    default:
      setup := ""
      v, st, gst, _, err := cg.GenerateExprIR(exprNode.Operand)
      if err != nil {
        return "", nil, nil, "", err
      }
      switch exprNode.Op {
      case parser.UNARY_OP_NEGATE:
        setup = fmt.Sprintf("%s =%s neg %s", val, mapTypeToIRType(exprNode.Operand.GetType()), v)
      case parser.UNARY_OP_LOGICAL_NOT:
        tpe := mapTypeToIRType(exprNode.Operand.GetType())
        setup = fmt.Sprintf("%s =%s cne%s %s, 1", val, tpe, tpe, v)
      }

      setups = append(setups, st...)
      gsetups = append(gsetups, gst...)
      setups = append(setups, setup)
    }

  case *parser.IfExprNode:
    endLabel := cg.GetTmpLbl()
    nm := cg.GetTmpVar()
    val = fmt.Sprintf("%s", nm)
    tpe := mapTypeToIRType(exprNode.ExprType)

    condVal, condSetups, condGSetups, _, err := cg.GenerateExprIR(exprNode.IfBranch.Condition)
    if err != nil {
      return "", nil, nil, "", err
    }

    ifLabel := cg.GetTmpLbl()
    elseCheckLabel := cg.GetTmpLbl()

    setups = append(setups, condSetups...)
    gsetups = append(gsetups, condGSetups...)
    setups = append(setups, fmt.Sprintf("jnz %s, %s, %s", condVal, ifLabel, elseCheckLabel))

    ifVal, ifSetups, ifGSetups, _, err := cg.GenerateExprIR(exprNode.IfBranch.Node)
    if err != nil {
      return "", nil, nil, "", err
    }
    ifBlockCode := fmt.Sprintf("%s", ifLabel)
    for _, s := range ifSetups {
      ifBlockCode += "\n" + s
    }
    gsetups = append(gsetups, ifGSetups...)
    ifBlockCode += fmt.Sprintf("\n%s =%s copy %s", nm, tpe, ifVal)
    ifBlockCode += fmt.Sprintf("\njmp %s", endLabel)
    setups = append(setups, ifBlockCode)

    currentCheckLabel := elseCheckLabel

    for _, elseIf := range exprNode.ElseIfBranches {
      elseIfCondVal, elseIfCondSetups, elseIfCondGSetups, _, err := cg.GenerateExprIR(elseIf.Condition)
      if err != nil {
        return "", nil, nil, "", err
      }

      elseIfLabel := cg.GetTmpLbl()
      nextCheckLabel := cg.GetTmpLbl()

      setups = append(setups, fmt.Sprintf("%s", currentCheckLabel))
      setups = append(setups, elseIfCondSetups...)
      gsetups = append(gsetups, elseIfCondGSetups...)
      setups = append(setups, fmt.Sprintf("jnz %s, %s, %s", elseIfCondVal, elseIfLabel, nextCheckLabel))

      elseIfVal, elseIfSetups, elseIfGSetups, _, err := cg.GenerateExprIR(elseIf.Node)
      if err != nil {
        return "", nil, nil, "", err
      }

      elseIfBlockCode := fmt.Sprintf("%s", elseIfLabel)
      for _, s := range elseIfSetups {
        elseIfBlockCode += "\n" + s
      }
      gsetups = append(gsetups, elseIfGSetups...)
      elseIfBlockCode += fmt.Sprintf("\n%s =%s copy %s", nm, tpe, elseIfVal)
      elseIfBlockCode += fmt.Sprintf("\njmp %s", endLabel)
      setups = append(setups, elseIfBlockCode)

      currentCheckLabel = nextCheckLabel
    }

    elseLabel := cg.GetTmpLbl()
    setups = append(setups, fmt.Sprintf("%s", currentCheckLabel))
    setups = append(setups, fmt.Sprintf("jmp %s", elseLabel))

    elseVal, elseSetups, elseGSetups, _, err := cg.GenerateExprIR(exprNode.ElseBranch)
    if err != nil {
      return "", nil, nil, "", err
    }
    elseBlockCode := fmt.Sprintf("%s", elseLabel)
    for _, s := range elseSetups {
      elseBlockCode += "\n" + s
    }
    gsetups = append(gsetups, elseGSetups...)
    elseBlockCode += fmt.Sprintf("\n%s =%s copy %s", nm, tpe, elseVal)
    elseBlockCode += fmt.Sprintf("\njmp %s", endLabel)
    setups = append(setups, elseBlockCode)

    setups = append(setups, fmt.Sprintf("%s", endLabel))

  case *parser.CastNode:
    targetType, _ := cg.typeTable.Lookup(exprNode.ToType.Name)
    targetIRType := mapTypeToIRType(targetType)
    sourceType := exprNode.Operand.GetType()
    sourceIRType := mapTypeToIRType(sourceType)

    sourceVal, sourceSetups, sourceGSetups, _, err := cg.GenerateExprIR(exprNode.Operand)
    if err != nil {
      return "", nil, nil, "", err
    }
    setups = append(setups, sourceSetups...)
    gsetups = append(gsetups, sourceGSetups...)

    currentVal := sourceVal

    if sourceIRType != targetIRType {
      tmpVar := cg.GetTmpVar()

      if sourceIRType == "w" && targetIRType == "l" {
        if shared.IsUnsignedType(sourceType) {
          setups = append(setups, fmt.Sprintf("%s =l extuw %s", tmpVar, currentVal))
        } else {
          setups = append(setups, fmt.Sprintf("%s =l extsw %s", tmpVar, currentVal))
        }
        currentVal = tmpVar
      } else if sourceIRType == "l" && targetIRType == "w" {
        setups = append(setups, fmt.Sprintf("%s =w copy %s", tmpVar, currentVal))
        currentVal = tmpVar
      } else {
        return "", nil, nil, "", shared.NewError(exprNode.Loc, "unsupported cast from %s to %s", sourceIRType, targetIRType)
      }
    }

    mask, ext := getTruncateMaskAndExt(targetType)
    if mask != 0 {
      tmpTrunc := cg.GetTmpVar()
      setups = append(setups, fmt.Sprintf("%s =w and %s, %d", tmpTrunc, currentVal, mask))
      currentVal = tmpTrunc

      if ext != "" {
        tmpExt := cg.GetTmpVar()
        setups = append(setups, fmt.Sprintf("%s =w %s %s", tmpExt, ext, currentVal))
        currentVal = tmpExt
      }
    }

    val = currentVal

  case *parser.StructLiteralNode:
    nm := cg.GetTmpVar()
    val = fmt.Sprintf("%s", nm)
    setups = append(setups, fmt.Sprintf("%s =l %s", val, emitAllocForType(eNode.GetType(), 1)))

  default:
    return "", nil, nil, "", shared.NewError(exprNode.GetLoc(), "unexpected expression")
  }

  mask, ext := getTruncateMaskAndExt(eNode.GetType())
  if mask != 0 {
    truncTmp := cg.GetTmpVar()
    setups = append(setups, fmt.Sprintf("%s =w and %s, %d", truncTmp, val, mask))
    val = truncTmp
    if ext != "" {
      extTmp := cg.GetTmpVar()
      setups = append(setups, fmt.Sprintf("%s =w %s %s", extTmp, ext, val))
      val = extTmp
    }
  }

  return val, setups, gsetups, tpe, nil
}

func mapTypeToIRType(t shared.Type) string {
  switch t {
  case shared.PRIMITIVE_VOID:
    return ""
  case shared.PRIMITIVE_BOOL:
    return "w"
  case shared.PRIMITIVE_CHAR:
    return "w"
  case shared.PRIMITIVE_CSTRING:
    return "l"
  case shared.PRIMITIVE_I8, shared.PRIMITIVE_U8:
    return "w"
  case shared.PRIMITIVE_I16, shared.PRIMITIVE_U16:
    return "w"
  case shared.PRIMITIVE_I32, shared.PRIMITIVE_U32:
    return "w"
  case shared.PRIMITIVE_I64, shared.PRIMITIVE_U64:
    return "l"
  case shared.PRIMITIVE_UNTYPED_INT:
    return "l"
  default:
    if t.IsStruct() {
      return "l"
    }
    if t.IsPointer() {
      return "l"
    }
    panic("unknown type")
  }
}

func emitAllocForType(t shared.Type, count int) string {
  align := shared.GetAlignOfType(t)
  size := shared.GetSizeOfType(t) * count

  switch {
  case align <= 4:
    return fmt.Sprintf("alloc4 %d", size)
  case align <= 8:
    return fmt.Sprintf("alloc8 %d", size)
  case align <= 16:
    return fmt.Sprintf("alloc16 %d", size)
  default:
    panic(fmt.Sprintf("unsupported alignment %d", align))
  }
}

func getTruncateMaskAndExt(t shared.Type) (mask int, extOp string) {
  switch t {
  case shared.PRIMITIVE_U8:
    return 0xFF, ""
  case shared.PRIMITIVE_I8:
    return 0xFF, "extsb"
  case shared.PRIMITIVE_U16:
    return 0xFFFF, ""
  case shared.PRIMITIVE_I16:
    return 0xFFFF, "extsh"
  default:
    return 0, ""
  }
}