// GoAWK parser - abstract syntax tree structs

package ast

import (
	"fmt"
	"strconv"
	"strings"

	. "github.com/benhoyt/goawk/lexer"
)

// Program is a parsed AWK program.
type Program struct {
	Begin     []Stmts
	Actions   []*Action
	End       []Stmts
	Functions []*Function
}

// String returns an indented, pretty-printed version of the parsed
// program.
func (p *Program) String() string {
	parts := []string{}
	for _, ss := range p.Begin {
		parts = append(parts, "BEGIN {\n"+ss.String()+"}")
	}
	for _, a := range p.Actions {
		parts = append(parts, a.String())
	}
	for _, ss := range p.End {
		parts = append(parts, "END {\n"+ss.String()+"}")
	}
	for _, function := range p.Functions {
		parts = append(parts, function.String())
	}
	return strings.Join(parts, "\n\n")
}

// Stmts is a block containing multiple statements.
type Stmts []Stmt

func (ss Stmts) String() string {
	lines := []string{}
	for _, s := range ss {
		subLines := strings.Split(s.String(), "\n")
		for _, sl := range subLines {
			lines = append(lines, "    "+sl+"\n")
		}
	}
	return strings.Join(lines, "")
}

// Action is pattern-action section of a program.
type Action struct {
	Pattern []Expr
	Stmts   Stmts
}

func (a *Action) String() string {
	patterns := make([]string, len(a.Pattern))
	for i, p := range a.Pattern {
		patterns[i] = p.String()
	}
	sep := ""
	if len(patterns) > 0 && a.Stmts != nil {
		sep = " "
	}
	stmtsStr := ""
	if a.Stmts != nil {
		stmtsStr = "{\n" + a.Stmts.String() + "}"
	}
	return strings.Join(patterns, ", ") + sep + stmtsStr
}

// Node is an interface to be satisfied by all AST elements.
// We need it to be able to work with AST in a generic way, like in ast.Walk().
type Node interface {
	node()
}

// All these types implement the Node interface.
func (p *Program) node()        {}
func (a *Action) node()         {}
func (f *Function) node()       {}
func (e *FieldExpr) node()      {}
func (e *NamedFieldExpr) node() {}
func (e *UnaryExpr) node()      {}
func (e *BinaryExpr) node()     {}
func (e *InExpr) node()         {}
func (e *CondExpr) node()       {}
func (e *NumExpr) node()        {}
func (e *StrExpr) node()        {}
func (e *RegExpr) node()        {}
func (e *VarExpr) node()        {}
func (e *IndexExpr) node()      {}
func (e *AssignExpr) node()     {}
func (e *AugAssignExpr) node()  {}
func (e *IncrExpr) node()       {}
func (e *CallExpr) node()       {}
func (e *UserCallExpr) node()   {}
func (e *MultiExpr) node()      {}
func (e *GetlineExpr) node()    {}
func (e *GroupingExpr) node()   {}
func (s *PrintStmt) node()      {}
func (s *PrintfStmt) node()     {}
func (s *ExprStmt) node()       {}
func (s *IfStmt) node()         {}
func (s *ForStmt) node()        {}
func (s *ForInStmt) node()      {}
func (s *WhileStmt) node()      {}
func (s *DoWhileStmt) node()    {}
func (s *BreakStmt) node()      {}
func (s *ContinueStmt) node()   {}
func (s *NextStmt) node()       {}
func (s *NextfileStmt) node()   {}
func (s *ExitStmt) node()       {}
func (s *DeleteStmt) node()     {}
func (s *ReturnStmt) node()     {}
func (s *BlockStmt) node()      {}

// Expr is the abstract syntax tree for any AWK expression.
type Expr interface {
	Node
	precedence() int
	String() string
}

// Table of operator precedence, lowest to highest
const (
	precAssign = iota
	precCond
	precOr
	precAnd
	precIn
	precMatch
	precCompare
	precConcat
	precAdd
	precMul
	precUnary
	precPower
	precPreIncr
	precPostIncr
	precField
	precPrimary
	precGrouping
)

// All these types implement the Expr interface.
func (e *FieldExpr) precedence() int      { return precField }
func (e *NamedFieldExpr) precedence() int { return precField }
func (e *UnaryExpr) precedence() int      { return precUnary }
func (e *InExpr) precedence() int         { return precIn }
func (e *CondExpr) precedence() int       { return precCond }
func (e *NumExpr) precedence() int        { return precPrimary }
func (e *StrExpr) precedence() int        { return precPrimary }
func (e *RegExpr) precedence() int        { return precPrimary }
func (e *VarExpr) precedence() int        { return precPrimary }
func (e *IndexExpr) precedence() int      { return precPrimary }
func (e *AssignExpr) precedence() int     { return precAssign }
func (e *AugAssignExpr) precedence() int  { return precAssign }
func (e *CallExpr) precedence() int       { return precPrimary }
func (e *UserCallExpr) precedence() int   { return precPrimary }
func (e *MultiExpr) precedence() int      { return precPrimary }
func (e *GetlineExpr) precedence() int    { return precPrimary }
func (e *GroupingExpr) precedence() int   { return precGrouping }

func (e *IncrExpr) precedence() int {
	if e.Pre {
		return precPreIncr
	}
	return precPostIncr
}

func (e *BinaryExpr) precedence() int {
	switch e.Op {
	case AND:
		return precAnd
	case OR:
		return precOr
	case CONCAT:
		return precConcat
	case ADD, SUB:
		return precAdd
	case MUL, DIV, MOD:
		return precMul
	case EQUALS, LESS, LTE, GREATER, GTE, NOT_EQUALS:
		return precCompare
	case MATCH, NOT_MATCH:
		return precMatch
	case POW:
		return precPower
	default:
		return precPrimary
	}
}

// parenthesize returns the string version of e, surrounding it in
// parentheses if e's precedence is lower than that of other.
func parenthesize(e, other Expr) string {
	if e.precedence() < other.precedence() {
		return "(" + e.String() + ")"
	}
	return e.String()
}

// FieldExpr is an expression like $0.
type FieldExpr struct {
	Index Expr
}

func (e *FieldExpr) String() string {
	return "$" + parenthesize(e.Index, e)
}

// NamedFieldExpr is an expression like @"name".
type NamedFieldExpr struct {
	Field Expr
}

func (e *NamedFieldExpr) String() string {
	return "@" + parenthesize(e.Field, e)
}

// UnaryExpr is an expression like -1234.
type UnaryExpr struct {
	Op    Token
	Value Expr
}

func (e *UnaryExpr) String() string {
	return e.Op.String() + parenthesize(e.Value, e)
}

// BinaryExpr is an expression like 1 + 2.
type BinaryExpr struct {
	Left  Expr
	Op    Token
	Right Expr
}

func (e *BinaryExpr) String() string {
	var op string
	if e.Op == CONCAT {
		op = " "
	} else {
		op = " " + e.Op.String() + " "
	}
	return parenthesize(e.Left, e) + op + parenthesize(e.Right, e)
}

// InExpr is an expression like (index in array).
type InExpr struct {
	Index    []Expr
	Array    string
	ArrayPos Position
}

func (e *InExpr) String() string {
	if len(e.Index) == 1 {
		return parenthesize(e.Index[0], e) + " in " + e.Array
	}
	indices := make([]string, len(e.Index))
	for i, index := range e.Index {
		indices[i] = index.String()
	}
	return "(" + strings.Join(indices, ", ") + ") in " + e.Array
}

// CondExpr is an expression like cond ? 1 : 0.
type CondExpr struct {
	Cond  Expr
	True  Expr
	False Expr
}

func (e *CondExpr) String() string {
	return parenthesize(e.Cond, e) + " ? " + parenthesize(e.True, e) + " : " + parenthesize(e.False, e)
}

// NumExpr is a literal number like 1234.
type NumExpr struct {
	Value float64
}

func (e *NumExpr) String() string {
	if e.Value == float64(int64(e.Value)) {
		return strconv.FormatInt(int64(e.Value), 10)
	} else {
		return fmt.Sprintf("%.6g", e.Value)
	}
}

// StrExpr is a literal string like "foo" or a regex constant like /foo/.
type StrExpr struct {
	Value string
	Regex bool
}

func (e *StrExpr) String() string {
	if e.Regex {
		return formatRegex(e.Value)
	}
	return strconv.Quote(e.Value)
}

// RegExpr is a stand-alone regex expression, equivalent to:
// $0 ~ /regex/.
type RegExpr struct {
	Regex string
}

func (e *RegExpr) String() string {
	return formatRegex(e.Regex)
}

// VarExpr is a variable reference (special var, global, or local).
type VarExpr struct {
	Name string
	Pos  Position
}

func (e *VarExpr) String() string {
	return e.Name
}

// IndexExpr is an expression like a[k] (rvalue or lvalue).
type IndexExpr struct {
	Array    string
	ArrayPos Position
	Index    []Expr
}

func (e *IndexExpr) String() string {
	indices := make([]string, len(e.Index))
	for i, index := range e.Index {
		indices[i] = index.String()
	}
	return e.Array + "[" + strings.Join(indices, ", ") + "]"
}

// AssignExpr is an expression like x = 1234.
type AssignExpr struct {
	Left  Expr // can be one of: var, array[x], $n
	Right Expr
}

func (e *AssignExpr) String() string {
	return parenthesize(e.Left, e) + " = " + parenthesize(e.Right, e)
}

// AugAssignExpr is an assignment expression like x += 5.
type AugAssignExpr struct {
	Left  Expr // can be one of: var, array[x], $n
	Op    Token
	Right Expr
}

func (e *AugAssignExpr) String() string {
	return parenthesize(e.Left, e) + " " + e.Op.String() + "= " + parenthesize(e.Right, e)
}

// IncrExpr is an increment or decrement expression like x++ or --y.
type IncrExpr struct {
	Expr Expr
	Op   Token
	Pre  bool
}

func (e *IncrExpr) String() string {
	if e.Pre {
		return e.Op.String() + parenthesize(e.Expr, e)
	} else {
		return parenthesize(e.Expr, e) + e.Op.String()
	}
}

// CallExpr is a builtin function call like length($1).
type CallExpr struct {
	Func Token
	Args []Expr
}

func (e *CallExpr) String() string {
	args := make([]string, len(e.Args))
	for i, a := range e.Args {
		args[i] = a.String()
	}
	return e.Func.String() + "(" + strings.Join(args, ", ") + ")"
}

// UserCallExpr is a user-defined function call like my_func(1, 2, 3),
// where my_func is either AWK-defined or a native Go function.
type UserCallExpr struct {
	Name string
	Args []Expr
	Pos  Position
}

func (e *UserCallExpr) String() string {
	args := make([]string, len(e.Args))
	for i, a := range e.Args {
		args[i] = a.String()
	}
	return e.Name + "(" + strings.Join(args, ", ") + ")"
}

// MultiExpr isn't an interpretable expression, but it's used as a
// pseudo-expression for print[f] parsing.
type MultiExpr struct {
	Exprs []Expr
}

func (e *MultiExpr) String() string {
	exprs := make([]string, len(e.Exprs))
	for i, e := range e.Exprs {
		exprs[i] = e.String()
	}
	return "(" + strings.Join(exprs, ", ") + ")"
}

// GetlineExpr is an expression read from file or pipe input.
type GetlineExpr struct {
	Command Expr
	Target  Expr
	File    Expr
}

func (e *GetlineExpr) String() string {
	s := ""
	if e.Command != nil {
		s += parenthesize(e.Command, e) + " |"
	}
	s += "getline"
	if e.Target != nil {
		s += " " + e.Target.String()
	}
	if e.File != nil {
		s += " <" + parenthesize(e.File, e)
	}
	return s
}

// GroupingExpr is a parenthesized grouping expression.
type GroupingExpr struct {
	Expr Expr
}

func (e *GroupingExpr) String() string {
	return "(" + e.Expr.String() + ")"
}

// IsLValue returns true if the given expression can be used as an
// lvalue (on the left-hand side of an assignment, in a ++ or --
// operation, or as the third argument to sub or gsub).
func IsLValue(expr Expr) bool {
	switch expr.(type) {
	case *VarExpr, *IndexExpr, *FieldExpr:
		return true
	default:
		return false
	}
}

// formatRegex formats the regex string r.
func formatRegex(r string) string {
	escaped := strings.ReplaceAll(r, "/", `\/`)
	return "/" + escaped + "/"
}

// Stmt is the abstract syntax tree for any AWK statement.
type Stmt interface {
	Node
	StartPos() Position // position of first character belonging to the node
	EndPos() Position   // position of first character immediately after the node
	stmt()
	String() string
}

// All these types implement the Stmt interface.
func (s *PrintStmt) stmt()    {}
func (s *PrintfStmt) stmt()   {}
func (s *ExprStmt) stmt()     {}
func (s *IfStmt) stmt()       {}
func (s *ForStmt) stmt()      {}
func (s *ForInStmt) stmt()    {}
func (s *WhileStmt) stmt()    {}
func (s *DoWhileStmt) stmt()  {}
func (s *BreakStmt) stmt()    {}
func (s *ContinueStmt) stmt() {}
func (s *NextStmt) stmt()     {}
func (s *NextfileStmt) stmt() {}
func (s *ExitStmt) stmt()     {}
func (s *DeleteStmt) stmt()   {}
func (s *ReturnStmt) stmt()   {}
func (s *BlockStmt) stmt()    {}

func (s *PrintStmt) StartPos() Position    { return s.Start }
func (s *PrintfStmt) StartPos() Position   { return s.Start }
func (s *ExprStmt) StartPos() Position     { return s.Start }
func (s *IfStmt) StartPos() Position       { return s.Start }
func (s *ForStmt) StartPos() Position      { return s.Start }
func (s *ForInStmt) StartPos() Position    { return s.Start }
func (s *WhileStmt) StartPos() Position    { return s.Start }
func (s *DoWhileStmt) StartPos() Position  { return s.Start }
func (s *BreakStmt) StartPos() Position    { return s.Start }
func (s *ContinueStmt) StartPos() Position { return s.Start }
func (s *NextStmt) StartPos() Position     { return s.Start }
func (s *NextfileStmt) StartPos() Position { return s.Start }
func (s *ExitStmt) StartPos() Position     { return s.Start }
func (s *DeleteStmt) StartPos() Position   { return s.Start }
func (s *ReturnStmt) StartPos() Position   { return s.Start }
func (s *BlockStmt) StartPos() Position    { return s.Start }

func (s *PrintStmt) EndPos() Position    { return s.End }
func (s *PrintfStmt) EndPos() Position   { return s.End }
func (s *ExprStmt) EndPos() Position     { return s.End }
func (s *IfStmt) EndPos() Position       { return s.End }
func (s *ForStmt) EndPos() Position      { return s.End }
func (s *ForInStmt) EndPos() Position    { return s.End }
func (s *WhileStmt) EndPos() Position    { return s.End }
func (s *DoWhileStmt) EndPos() Position  { return s.End }
func (s *BreakStmt) EndPos() Position    { return s.End }
func (s *ContinueStmt) EndPos() Position { return s.End }
func (s *NextStmt) EndPos() Position     { return s.End }
func (s *NextfileStmt) EndPos() Position { return s.End }
func (s *ExitStmt) EndPos() Position     { return s.End }
func (s *DeleteStmt) EndPos() Position   { return s.End }
func (s *ReturnStmt) EndPos() Position   { return s.End }
func (s *BlockStmt) EndPos() Position    { return s.End }

// PrintStmt is a statement like print $1, $3.
type PrintStmt struct {
	Args     []Expr
	Redirect Token
	Dest     Expr
	Start    Position
	End      Position
}

func (s *PrintStmt) String() string {
	return printString("print", s.Args, s.Redirect, s.Dest)
}

func printString(f string, args []Expr, redirect Token, dest Expr) string {
	parts := make([]string, len(args))
	for i, a := range args {
		parts[i] = a.String()
	}
	str := f + " " + strings.Join(parts, ", ")
	if dest != nil {
		str += " " + redirect.String() + dest.String()
	}
	return str
}

// PrintfStmt is a statement like printf "%3d", 1234.
type PrintfStmt struct {
	Args     []Expr
	Redirect Token
	Dest     Expr
	Start    Position
	End      Position
}

func (s *PrintfStmt) String() string {
	return printString("printf", s.Args, s.Redirect, s.Dest)
}

// ExprStmt is statement like a bare function call: my_func(x).
type ExprStmt struct {
	Expr  Expr
	Start Position
	End   Position
}

func (s *ExprStmt) String() string {
	return s.Expr.String()
}

// IfStmt is an if or if-else statement.
type IfStmt struct {
	Cond      Expr
	BodyStart Position
	Body      Stmts
	Else      Stmts
	Start     Position
	End       Position
}

func (s *IfStmt) String() string {
	str := "if (" + s.Cond.String() + ") {\n" + s.Body.String() + "}"
	if len(s.Else) > 0 {
		str += " else {\n" + s.Else.String() + "}"
	}
	return str
}

// ForStmt is a C-like for loop: for (i=0; i<10; i++) print i.
type ForStmt struct {
	Pre       Stmt
	Cond      Expr
	Post      Stmt
	BodyStart Position
	Body      Stmts
	Start     Position
	End       Position
}

func (s *ForStmt) String() string {
	preStr := ""
	if s.Pre != nil {
		preStr = s.Pre.String()
	}
	condStr := ""
	if s.Cond != nil {
		condStr = " " + s.Cond.String()
	}
	postStr := ""
	if s.Post != nil {
		postStr = " " + s.Post.String()
	}
	return "for (" + preStr + ";" + condStr + ";" + postStr + ") {\n" + s.Body.String() + "}"
}

// ForInStmt is a for loop like for (k in a) print k, a[k].
type ForInStmt struct {
	Var       string
	VarPos    Position
	Array     string
	ArrayPos  Position
	BodyStart Position
	Body      Stmts
	Start     Position
	End       Position
}

func (s *ForInStmt) String() string {
	return "for (" + s.Var + " in " + s.Array + ") {\n" + s.Body.String() + "}"
}

// WhileStmt is a while loop.
type WhileStmt struct {
	Cond      Expr
	BodyStart Position
	Body      Stmts
	Start     Position
	End       Position
}

func (s *WhileStmt) String() string {
	return "while (" + s.Cond.String() + ") {\n" + s.Body.String() + "}"
}

// DoWhileStmt is a do-while loop.
type DoWhileStmt struct {
	Body  Stmts
	Cond  Expr
	Start Position
	End   Position
}

func (s *DoWhileStmt) String() string {
	return "do {\n" + s.Body.String() + "} while (" + s.Cond.String() + ")"
}

// BreakStmt is a break statement.
type BreakStmt struct {
	Start Position
	End   Position
}

func (s *BreakStmt) String() string {
	return "break"
}

// ContinueStmt is a continue statement.
type ContinueStmt struct {
	Start Position
	End   Position
}

func (s *ContinueStmt) String() string {
	return "continue"
}

// NextStmt is a next statement.
type NextStmt struct {
	Start Position
	End   Position
}

func (s *NextStmt) String() string {
	return "next"
}

// NextfileStmt is a nextfile statement.
type NextfileStmt struct {
	Start Position
	End   Position
}

func (s *NextfileStmt) String() string {
	return "nextfile"
}

// ExitStmt is an exit statement.
type ExitStmt struct {
	Status Expr
	Start  Position
	End    Position
}

func (s *ExitStmt) String() string {
	var statusStr string
	if s.Status != nil {
		statusStr = " " + s.Status.String()
	}
	return "exit" + statusStr
}

// DeleteStmt is a statement like delete a[k].
type DeleteStmt struct {
	Array    string
	ArrayPos Position
	Index    []Expr
	Start    Position
	End      Position
}

func (s *DeleteStmt) String() string {
	if len(s.Index) == 0 {
		return "delete " + s.Array
	}
	indices := make([]string, len(s.Index))
	for i, index := range s.Index {
		indices[i] = index.String()
	}
	return "delete " + s.Array + "[" + strings.Join(indices, ", ") + "]"
}

// ReturnStmt is a return statement.
type ReturnStmt struct {
	Value Expr
	Start Position
	End   Position
}

func (s *ReturnStmt) String() string {
	var valueStr string
	if s.Value != nil {
		valueStr = " " + s.Value.String()
	}
	return "return" + valueStr
}

// BlockStmt is a stand-alone block like { print "x" }.
type BlockStmt struct {
	Body  Stmts
	Start Position
	End   Position
}

func (s *BlockStmt) String() string {
	return "{\n" + s.Body.String() + "}"
}

// Function is the AST for a user-defined function.
type Function struct {
	Name   string
	Params []string
	Body   Stmts
	Pos    Position
}

func (f *Function) String() string {
	return "function " + f.Name + "(" + strings.Join(f.Params, ", ") + ") {\n" +
		f.Body.String() + "}"
}

// PositionError represents an error bound to specific position in source.
type PositionError struct {
	// Source line/column position where the error occurred.
	Position Position
	// Error message.
	Message string
}

// PosErrorf like fmt.Errorf, but with an explicit position.
func PosErrorf(pos Position, format string, args ...any) error {
	message := fmt.Sprintf(format, args...)
	return &PositionError{pos, message}
}

// Error returns a formatted version of the error, including the line
// and column numbers.
func (e *PositionError) Error() string {
	return fmt.Sprintf("parse error at %d:%d: %s", e.Position.Line, e.Position.Column, e.Message)
}
