package parser

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

type Parser struct {
  pos    int
  tokens []tokeniser.Token
}

func NewParser(tokens []tokeniser.Token) *Parser {
  return &Parser{
    pos: 0,
    tokens: tokens,
  }
}

func (p *Parser) Peek() tokeniser.Token {
  if p.pos >= len(p.tokens) || p.pos < 0 {
    return tokeniser.Token{Type: tokeniser.TOKEN_TYPE_EOF}
  }

  return p.tokens[p.pos]
}

func (p *Parser) Next() tokeniser.Token {
  pos := p.pos + 1

  if pos >= len(p.tokens) || pos < 0 {
    return tokeniser.Token{Type: tokeniser.TOKEN_TYPE_EOF}
  }

  return p.tokens[pos]
}

func (p *Parser) Inc() *Parser {
  p.pos++

  return p
}

func (p *Parser) Dec() *Parser {
  p.pos--

  return p
}

func (p *Parser) PrevLoc() shared.Location {
  l := p.Dec().Peek().Loc
  p.Inc()
  return l
}

func (p *Parser) CurrLoc() shared.Location {
  return p.Peek().Loc
}

func (p *Parser) Consume() tokeniser.Token {
  c := p.Peek()
  p.Inc()

  return c
}

func (p *Parser) Expect(expected tokeniser.TokenType) bool {
  tok := p.Consume()

  ret := tok.Type == expected

  return ret
}

func (p *Parser) ExpectGet(expected tokeniser.TokenType) (*tokeniser.Token, bool) {
  tok := p.Consume()

  if tok.Type != expected {
    return nil, false
  }

  return &tok, true
}

func (p *Parser) Match(ttypes ...tokeniser.TokenType) bool {
  ptype := p.Peek().Type

  for _, ttype := range ttypes {
    if ptype == ttype {
      return true
    }
  }

  return false
}

func (p *Parser) Parse() (*RootNode, error) {
  eofTok := p.tokens[len(p.tokens)-1]
  rootNode := &RootNode{
    Loc: shared.Location{
      LC: shared.LineCol{
        Line: 1,
        Col: 1,
      },
      FilePath: eofTok.Loc.FilePath,
    },
  }
  for !p.Match(tokeniser.TOKEN_TYPE_EOF) {
    for p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
      p.Inc()
    }
    e := shared.NewError(p.CurrLoc(), "expected function definition or import statement")
    if !p.Match(tokeniser.TOKEN_TYPE_KEYWORD) {
      return nil, e
    }

    switch p.Peek().Value {
    case "let":
      fnDef, err := p.ParseFunctionDefinition()
      if err != nil {
        return nil, err
      }
      rootNode.Body = append(rootNode.Body, fnDef)
    case "declare":
      fnDef, err := p.ParseExternalFunctionDefinition()
      if err != nil {
        return nil, err
      }
      rootNode.Body = append(rootNode.Body, fnDef)
    case "struct":
      str, err := p.ParseStructDefinition()
      if err != nil {
        return nil, err
      }
      rootNode.Body = append(rootNode.Body, str)
    case "import":
      imp, err := p.ParseImport()
      if err != nil {
        return nil, err
      }
      rootNode.Body = append(rootNode.Body, imp)
    default:
      return nil, e
    }

    if p.Match(tokeniser.TOKEN_TYPE_EOF) {
      break
    }

    if !p.Match(tokeniser.TOKEN_TYPE_NEWLINE, tokeniser.TOKEN_TYPE_SEMICOLON) {
      return nil, shared.NewError(p.CurrLoc(), "expected ';' or '\\n'")
    }

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

  return rootNode, nil
}