package parser

import (
  "github.com/marzeq/quokka/shared"
  "github.com/marzeq/quokka/tokeniser"
)

func (p *Parser) ParseExpression() (*Node, error) {
  return p.ParseLogicalOr()
}

func (p *Parser) ParseLogicalOr() (*Node, error) {
  beginLoc := p.CurrLoc()
  left, err := p.ParseLogicalAnd()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "or" {
    p.Inc()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    right, err := p.ParseLogicalAnd()
    if err != nil {
      return nil, err
    }
    left = &Node{
      Type:  NODE_TYPE_BINARY_OP,
      Value: "or",
      Left:  left,
      Right: right,
      Loc:   beginLoc,
    }
  }

  return left, nil
}

func (p *Parser) ParseLogicalAnd() (*Node, error) {
  beginLoc := p.CurrLoc()
  left, err := p.ParseLogicalNot()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "and" {
    p.Inc()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    right, err := p.ParseLogicalNot()
    if err != nil {
      return nil, err
    }
    left = &Node{
      Type:  NODE_TYPE_BINARY_OP,
      Value: "and",
      Left:  left,
      Right: right,
      Loc:   beginLoc,
    }
  }

  return left, nil
}

func (p *Parser) ParseLogicalNot() (*Node, error) {
  beginLoc := p.CurrLoc()
  if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "not" {
    p.Inc()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    expr, err := p.ParseLogicalNot()
    if err != nil {
      return nil, err
    }

    return &Node{
      Type:  NODE_TYPE_UNARY_OP,
      Value: "not",
      Right: expr,
      Loc:   beginLoc,
    }, nil
  }

  return p.ParseComparison()
}

func (p *Parser) ParseUnary() (*Node, error) {
  beginLoc := p.CurrLoc()
  if p.Match(tokeniser.TOKEN_TYPE_MINUS, tokeniser.TOKEN_TYPE_ASTERISK, tokeniser.TOKEN_TYPE_AMPERSAND) {
    op := p.Consume()
    val := ""
    switch op.Type {
    case tokeniser.TOKEN_TYPE_MINUS:
      val = "-"
    case tokeniser.TOKEN_TYPE_ASTERISK:
      val = "*"
    case tokeniser.TOKEN_TYPE_AMPERSAND:
      val = "&"
    }
    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }
    expr, err := p.ParseUnary()
    if err != nil {
      return nil, err
    }
    return &Node{
      Type:  NODE_TYPE_UNARY_OP,
      Value: val,
      Right: expr,
      Loc:   beginLoc,
    }, nil
  }

  return p.ParseTerm()
}

func (p *Parser) ParseComparison() (*Node, error) {
  beginLoc := p.CurrLoc()
  left, err := p.ParseAddSub()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_EQUALS_EQUALS, tokeniser.TOKEN_TYPE_NOT_EQUALS, tokeniser.TOKEN_TYPE_LESS, tokeniser.TOKEN_TYPE_GREATER, tokeniser.TOKEN_TYPE_LESS_EQUALS, tokeniser.TOKEN_TYPE_GREATER_EQUALS) {
    op := p.Consume()
    val := ""
    switch op.Type {
    case tokeniser.TOKEN_TYPE_EQUALS_EQUALS:
      val = "=="
    case tokeniser.TOKEN_TYPE_NOT_EQUALS:
      val = "!="
    case tokeniser.TOKEN_TYPE_LESS:
      val = "<"
    case tokeniser.TOKEN_TYPE_GREATER:
      val = ">"
    case tokeniser.TOKEN_TYPE_LESS_EQUALS:
      val = "<="
    case tokeniser.TOKEN_TYPE_GREATER_EQUALS:
      val = ">="
    }
    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    right, err := p.ParseAddSub()
    if err != nil {
      return nil, err
    }
    left = &Node{
      Type:  NODE_TYPE_BINARY_OP,
      Value: val,
      Left:  left,
      Right: right,
      Loc:   beginLoc,
    }
  }

  return left, nil
}

func (p *Parser) ParseAddSub() (*Node, error) {
  beginLoc := p.CurrLoc()
  left, err := p.ParseMulDiv()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_PLUS, tokeniser.TOKEN_TYPE_MINUS) {
    op := p.Consume()
    val := ""
    if op.Type == tokeniser.TOKEN_TYPE_PLUS {
      val = "+"
    } else {
      val = "-"
    }

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    right, err := p.ParseMulDiv()
    if err != nil {
      return nil, err
    }
    left = &Node{
      Type:  NODE_TYPE_BINARY_OP,
      Value: val,
      Left:  left,
      Right: right,
      Loc:   beginLoc,
    }
  }

  return left, nil
}

func (p *Parser) ParseMulDiv() (*Node, error) {
  beginLoc := p.CurrLoc()
  left, err := p.ParseUnary()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_ASTERISK, tokeniser.TOKEN_TYPE_SLASH, tokeniser.TOKEN_TYPE_PERCENT) {
    op := p.Consume()
    val := ""
    switch op.Type {
    case tokeniser.TOKEN_TYPE_ASTERISK:
      val = "*"
    case tokeniser.TOKEN_TYPE_SLASH:
      val = "/"
    default:
      val = "%"
    }
    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    right, err := p.ParseUnary()
    if err != nil {
      return nil, err
    }
    left = &Node{
      Type:  NODE_TYPE_BINARY_OP,
      Value: val,
      Left:  left,
      Right: right,
      Loc:   beginLoc,
    }
  }

  return left, nil
}

func (p *Parser) ParseTerm() (*Node, error) {
  beginLoc := p.CurrLoc()
  if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "if" {
    expr, err := p.ParseIfExpression()
    if err != nil {
      return nil, err
    }

    return expr, nil
  }

  if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "cast" {
    expr, err := p.ParseCast()
    if err != nil {
      return nil, err
    }

    return expr, nil
  }

  if p.Match(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
    p.Consume()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    expr, err := p.ParseExpression()
    if err != nil {
      return nil, err
    }

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
      return nil, shared.NewError(p.PrevLoc(), "expected ')'")
    }

    return expr, nil
  }

  if p.Match(tokeniser.TOKEN_TYPE_IDENT) {
    p.Inc()
    if p.Match(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
      p.Dec()
      return p.ParseFunctionCall()
    }

    if p.Match(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
      p.Dec()
      return p.ParseStructLiteral()
    }

    p.Dec()
    return p.ParseIdent()
  }

  if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "true" || p.Peek().Value == "false" {
    bLit := p.Consume()
    return &Node{
      Type:  NODE_TYPE_BOOL_LITERAL,
      Value: bLit.Value,
      Loc:   beginLoc,
    }, nil
  }

  if p.Match(tokeniser.TOKEN_TYPE_NUMBER) {
    nLit := p.Consume()
    return &Node{
      Type:  NODE_TYPE_NUMBER_LITERAL,
      Value: nLit.Value,
      Loc:   beginLoc,
    }, nil
  }

  if p.Match(tokeniser.TOKEN_TYPE_CHAR) {
    cLit := p.Consume()
    return &Node{
      Type:  NODE_TYPE_CHAR_LITERAL,
      Value: []byte(cLit.Value)[0],
      Loc:   beginLoc,
    }, nil
  }

  if p.Match(tokeniser.TOKEN_TYPE_STRING) {
    sLit := p.Consume()
    return &Node{
      Type:  NODE_TYPE_STRING_LITERAL,
      Value: sLit.Value,
      Loc:   beginLoc,
    }, nil
  }

  return nil, shared.NewError(p.CurrLoc(), "unexpected token %s", p.Peek())
}

func (p *Parser) ParseCast() (*Node, error) {
  beginLoc := p.CurrLoc()

  if t, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_KEYWORD); !ok || t.Value != "cast" {
    return nil, shared.NewError(beginLoc, "expected 'cast' keyword")
  }

  if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_PAREN) {
    return nil, shared.NewError(p.PrevLoc(), "expected '(")
  }
  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  tpe, err := p.ParseIdent()
  if err != nil {
    return nil, err
  }

  if !p.Expect(tokeniser.TOKEN_TYPE_COMMA) {
    return nil, shared.NewError(p.PrevLoc(), "expected ','")
  }
  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  expr, err := p.ParseExpression()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }
  if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
    return nil, shared.NewError(p.PrevLoc(), "expected ')")
  }

  return &Node{
    Type:  NODE_TYPE_CAST,
    Left:  &Node{Type: NODE_TYPE_IDENTIFIER, Value: tpe.Value},
    Right: expr,
    Loc:   beginLoc,
  }, nil
}

func (p *Parser) ParseFunctionCall() (*Node, error) {
  beginLoc := p.CurrLoc()
  name, err := p.ParseIdent()
  if err != nil {
    return nil, err
  }
  var args []*Node

  p.Consume()

  if !p.Match(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
    for {
      arg, err := p.ParseExpression()
      if err != nil {
        return nil, err
      }
      args = append(args, arg)

      if !p.Match(tokeniser.TOKEN_TYPE_COMMA) {
        break
      }
      p.Inc()

      for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
        p.Inc()
      }
    }
  }

  if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_PAREN) {
    return nil, shared.NewError(p.PrevLoc(), "expected '('")
  }

  return &Node{
    Type:     NODE_TYPE_FUNCTION_CALL,
    Value:    &Node{Type: NODE_TYPE_IDENTIFIER, Value: name.Value},
    Children: args,
    Loc:      beginLoc,
  }, nil
}

func (p *Parser) ParseIfExpression() (*Node, error) {
  beginLoc := p.CurrLoc()
  if !p.Expect(tokeniser.TOKEN_TYPE_KEYWORD) {
    return nil, shared.NewError(p.PrevLoc(), "expected 'if' keyword")
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  condition, err := p.ParseExpression()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  thenBlock, err := p.ParseBlockExpression()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  ifBranch := &IfBranch{
    Condition: condition,
    Node:      thenBlock,
  }

  ifNodeValue := &IfNodeValue{
    IfBranch:       ifBranch,
    ElseIfBranches: []*IfBranch{},
  }

  for p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "else" {
    p.Consume()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    if p.Match(tokeniser.TOKEN_TYPE_KEYWORD) && p.Peek().Value == "if" {
      p.Consume()

      for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
        p.Inc()
      }

      elseifCondition, err := p.ParseExpression()
      if err != nil {
        return nil, err
      }

      for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
        p.Inc()
      }

      elseifBlock, err := p.ParseBlockExpression()
      if err != nil {
        return nil, err
      }

      elseIfBranch := &IfBranch{
        Condition: elseifCondition,
        Node:      elseifBlock,
      }
      ifNodeValue.ElseIfBranches = append(ifNodeValue.ElseIfBranches, elseIfBranch)
    } else {
      elseBlock, err := p.ParseBlockExpression()
      if err != nil {
        return nil, err
      }

      ifNodeValue.ElseBranch = &IfBranch{
        Condition: nil,
        Node:      elseBlock,
      }
      break
    }
  }

  return &Node{
    Type:  NODE_TYPE_IF_EXPR,
    Value: ifNodeValue,
    Loc:   beginLoc,
  }, nil
}

func (p *Parser) ParseBlockExpression() (*Node, error) {
  if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
    return nil, shared.NewError(p.PrevLoc(), "expected '{' to start block")
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  blockExpression, err := p.ParseExpression()
  if err != nil {
    return nil, err
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
    return nil, shared.NewError(p.PrevLoc(), "expected '}' to end block")
  }

  return blockExpression, err
}

func (p *Parser) ParseStructLiteral() (*Node, error) {
  beginLoc := p.CurrLoc()
  name, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
  if !ok {
    return nil, shared.NewError(p.PrevLoc(), "expected struct name")
  }

  if !p.Expect(tokeniser.TOKEN_TYPE_OPEN_CURLY) {
    return nil, shared.NewError(p.PrevLoc(), "expected '{' to start struct literal")
  }

  for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
    p.Inc()
  }

  value := &StructLiteralValue{}
  for {
    if p.Match(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
      p.Inc()
      break
    }

    fieldName, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
    if !ok {
      return nil, shared.NewError(p.PrevLoc(), "expected field name")
    }

    if !p.Expect(tokeniser.TOKEN_TYPE_EQUALS) {
      return nil, shared.NewError(p.PrevLoc(), "expected '='")
    }

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    fieldValue, err := p.ParseExpression()
    if err != nil {
      return nil, err
    }

    value.Fields = append(value.Fields, shared.Pair[*Node, *Node]{L: &Node{Type: NODE_TYPE_IDENTIFIER, Value: fieldName.Value}, R: fieldValue})

    if !p.Match(tokeniser.TOKEN_TYPE_COMMA, tokeniser.TOKEN_TYPE_NEWLINE) {
      break
    }
    p.Inc()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }
  }

  if !p.Expect(tokeniser.TOKEN_TYPE_CLOSE_CURLY) {
    return nil, shared.NewError(p.PrevLoc(), "expected '}' to end struct literal")
  }

  return &Node{
    Type:  NODE_TYPE_STRUCT_LITERAL,
    Value: value,
    Left:  &Node{Type: NODE_TYPE_IDENTIFIER, Value: name.Value},
    Loc:   beginLoc,
  }, nil
}

func (p *Parser) ParseIdent() (*Node, error) {
  beginLoc := p.CurrLoc()
  firstIdent, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
  if !ok {
    return nil, shared.NewError(p.PrevLoc(), "expected identifier")
  }
  node := &Node{
    Type:  NODE_TYPE_IDENTIFIER,
    Value: firstIdent.Value,
    Loc:   beginLoc,
  }
  currnode := node

  for p.Match(tokeniser.TOKEN_TYPE_DOT) {
    p.Inc()

    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE) {
      p.Inc()
    }

    loc := p.CurrLoc()
    nextIdent, ok := p.ExpectGet(tokeniser.TOKEN_TYPE_IDENT)
    if !ok {
      return nil, shared.NewError(p.PrevLoc(), "expected identifier")
    }
    currnode.Right = &Node{
      Type:  NODE_TYPE_IDENTIFIER,
      Value: nextIdent.Value,
      Loc:   loc,
    }
    currnode = currnode.Right
  }

  return node, nil
}