package parser

import (
	"regexp"
	"strconv"
	"strings"
	"time"

	"github.com/influxdata/ifql/ast"
)

func toIfaceSlice(v interface{}) []interface{} {
	if v == nil {
		return nil
	}
	return v.([]interface{})
}

func program(body interface{}, text []byte, pos position) (*ast.Program, error) {
	return &ast.Program{
		Body:     body.([]ast.Statement),
		BaseNode: base(text, pos),
	}, nil
}

func srcElems(head, tails interface{}) ([]ast.Statement, error) {
	elems := []ast.Statement{head.(ast.Statement)}
	for _, tail := range toIfaceSlice(tails) {
		elem := toIfaceSlice(tail)[1] // Skip whitespace
		elems = append(elems, elem.(ast.Statement))
	}
	return elems, nil
}

func blockstmt(body interface{}, text []byte, pos position) (*ast.BlockStatement, error) {
	bodySlice := toIfaceSlice(body)
	statements := make([]ast.Statement, len(bodySlice))
	for i, s := range bodySlice {
		stmt := toIfaceSlice(s)[1] // Skip whitespace
		statements[i] = stmt.(ast.Statement)
	}
	return &ast.BlockStatement{
		BaseNode: base(text, pos),
		Body:     statements,
	}, nil
}

func varstmt(declaration interface{}, text []byte, pos position) (*ast.VariableDeclaration, error) {
	return &ast.VariableDeclaration{
		Declarations: []*ast.VariableDeclarator{declaration.(*ast.VariableDeclarator)},
		BaseNode:     base(text, pos),
	}, nil
}

func vardecl(id, initializer interface{}, text []byte, pos position) (*ast.VariableDeclarator, error) {
	return &ast.VariableDeclarator{
		ID:   id.(*ast.Identifier),
		Init: initializer.(ast.Expression),
	}, nil
}

func exprstmt(expr interface{}, text []byte, pos position) (*ast.ExpressionStatement, error) {
	return &ast.ExpressionStatement{
		Expression: expr.(ast.Expression),
		BaseNode:   base(text, pos),
	}, nil
}

func returnstmt(argument interface{}, text []byte, pos position) (*ast.ReturnStatement, error) {
	return &ast.ReturnStatement{
		BaseNode: base(text, pos),
		Argument: argument.(ast.Expression),
	}, nil
}

func pipeExprs(head, tail interface{}, text []byte, pos position) (*ast.PipeExpression, error) {
	var arg ast.Expression
	arg = head.(ast.Expression)

	var pe *ast.PipeExpression
	for _, t := range toIfaceSlice(tail) {
		pe = toIfaceSlice(t)[1].(*ast.PipeExpression)
		pe.Argument = arg
		arg = pe
	}
	return pe, nil
}

func incompletePipeExpr(call interface{}, text []byte, pos position) (*ast.PipeExpression, error) {
	return &ast.PipeExpression{
		Call:     call.(*ast.CallExpression),
		BaseNode: base(text, pos),
	}, nil
}

func memberexprs(head, tail interface{}, text []byte, pos position) (ast.Expression, error) {
	res := head.(ast.Expression)
	for _, prop := range toIfaceSlice(tail) {
		res = &ast.MemberExpression{
			Object:   res,
			Property: prop.(ast.Expression),
			BaseNode: base(text, pos),
		}
	}
	return res, nil
}

func memberexpr(object, property interface{}, text []byte, pos position) (*ast.MemberExpression, error) {
	m := &ast.MemberExpression{
		BaseNode: base(text, pos),
	}

	if object != nil {
		m.Object = object.(ast.Expression)
	}

	if property != nil {
		m.Property = property.(*ast.Identifier)
	}

	return m, nil
}

func callexpr(callee, args interface{}, text []byte, pos position) (*ast.CallExpression, error) {
	c := &ast.CallExpression{
		BaseNode: base(text, pos),
	}

	if callee != nil {
		c.Callee = callee.(ast.Expression)
	}

	if args != nil {
		c.Arguments = []ast.Expression{args.(*ast.ObjectExpression)}
	}
	return c, nil
}

func callexprs(head, tail interface{}, text []byte, pos position) (ast.Expression, error) {
	expr := head.(ast.Expression)
	for _, i := range toIfaceSlice(tail) {
		switch elem := i.(type) {
		case *ast.CallExpression:
			elem.Callee = expr
			expr = elem
		case *ast.MemberExpression:
			elem.Object = expr
			expr = elem
		}
	}
	return expr, nil
}

func arrowfunc(params interface{}, body interface{}, text []byte, pos position) *ast.ArrowFunctionExpression {
	paramsSlice := toIfaceSlice(params)
	paramsList := make([]*ast.Property, len(paramsSlice))
	for i, p := range paramsSlice {
		paramsList[i] = p.(*ast.Property)
	}
	return &ast.ArrowFunctionExpression{
		BaseNode: base(text, pos),
		Params:   paramsList,
		Body:     body.(ast.Node),
	}
}

func objectexpr(first, rest interface{}, text []byte, pos position) (*ast.ObjectExpression, error) {
	props := []*ast.Property{first.(*ast.Property)}
	if rest != nil {
		for _, prop := range toIfaceSlice(rest) {
			props = append(props, prop.(*ast.Property))
		}
	}

	return &ast.ObjectExpression{
		Properties: props,
		BaseNode:   base(text, pos),
	}, nil
}

func property(key, value interface{}, text []byte, pos position) (*ast.Property, error) {
	var v ast.Expression
	if value != nil {
		v = value.(ast.Expression)
	}
	return &ast.Property{
		Key:      key.(*ast.Identifier),
		Value:    v,
		BaseNode: base(text, pos),
	}, nil
}

func identifier(text []byte, pos position) (*ast.Identifier, error) {
	return &ast.Identifier{
		Name:     string(text),
		BaseNode: base(text, pos),
	}, nil
}

func array(first, rest interface{}, text []byte, pos position) *ast.ArrayExpression {
	var elements []ast.Expression
	if first != nil {
		elements = append(elements, first.(ast.Expression))
	}
	if rest != nil {
		for _, el := range rest.([]interface{}) {
			elements = append(elements, el.(ast.Expression))
		}
	}
	return &ast.ArrayExpression{
		Elements: elements,
		BaseNode: base(text, pos),
	}
}

func logicalExpression(head, tails interface{}, text []byte, pos position) (ast.Expression, error) {
	res := head.(ast.Expression)
	for _, tail := range toIfaceSlice(tails) {
		right := toIfaceSlice(tail)
		res = &ast.LogicalExpression{
			Left:     res,
			Right:    right[3].(ast.Expression),
			Operator: right[1].(ast.LogicalOperatorKind),
			BaseNode: base(text, pos),
		}
	}
	return res, nil
}

func logicalOp(text []byte) (ast.LogicalOperatorKind, error) {
	return ast.LogicalOperatorLookup(strings.ToLower(string(text))), nil
}

func binaryExpression(head, tails interface{}, text []byte, pos position) (ast.Expression, error) {
	res := head.(ast.Expression)
	for _, tail := range toIfaceSlice(tails) {
		right := toIfaceSlice(tail)
		res = &ast.BinaryExpression{
			Left:     res,
			Right:    right[3].(ast.Expression),
			Operator: right[1].(ast.OperatorKind),
			BaseNode: base(text, pos),
		}
	}
	return res, nil
}

func unaryExpression(op, argument interface{}, text []byte, pos position) (*ast.UnaryExpression, error) {
	return &ast.UnaryExpression{
		Operator: op.(ast.OperatorKind),
		Argument: argument.(ast.Expression),
		BaseNode: base(text, pos),
	}, nil
}

func operator(text []byte) (ast.OperatorKind, error) {
	return ast.OperatorLookup(strings.ToLower(string(text))), nil
}

func stringLiteral(text []byte, pos position) (*ast.StringLiteral, error) {
	s, err := strconv.Unquote(string(text))
	if err != nil {
		return nil, err
	}
	return &ast.StringLiteral{
		BaseNode: base(text, pos),
		Value:    s,
	}, nil
}

func pipeLiteral(text []byte, pos position) *ast.PipeLiteral {
	return &ast.PipeLiteral{
		BaseNode: base(text, pos),
	}
}

func booleanLiteral(b bool, text []byte, pos position) (*ast.BooleanLiteral, error) {
	return &ast.BooleanLiteral{
		BaseNode: base(text, pos),
		Value:    b,
	}, nil
}

func integerLiteral(text []byte, pos position) (*ast.IntegerLiteral, error) {
	n, err := strconv.ParseInt(string(text), 10, 64)
	if err != nil {
		return nil, err
	}
	return &ast.IntegerLiteral{
		BaseNode: base(text, pos),
		Value:    n,
	}, nil
}

func numberLiteral(text []byte, pos position) (*ast.FloatLiteral, error) {
	n, err := strconv.ParseFloat(string(text), 64)
	if err != nil {
		return nil, err
	}
	return &ast.FloatLiteral{
		BaseNode: base(text, pos),
		Value:    n,
	}, nil
}

func regexLiteral(chars interface{}, text []byte, pos position) (*ast.RegexpLiteral, error) {
	b := new(strings.Builder)
	for _, char := range toIfaceSlice(chars) {
		b.Write(char.([]byte))
	}

	r, err := regexp.Compile(b.String())
	if err != nil {
		return nil, err
	}
	return &ast.RegexpLiteral{
		BaseNode: base(text, pos),
		Value:    r,
	}, nil
}

func durationLiteral(text []byte, pos position) (*ast.DurationLiteral, error) {
	d, err := time.ParseDuration(string(text))
	if err != nil {
		return nil, err
	}
	return &ast.DurationLiteral{
		BaseNode: base(text, pos),
		Value:    d,
	}, nil
}

func datetime(text []byte, pos position) (*ast.DateTimeLiteral, error) {
	t, err := time.Parse(time.RFC3339Nano, string(text))
	if err != nil {
		return nil, err
	}
	return &ast.DateTimeLiteral{
		BaseNode: base(text, pos),
		Value:    t,
	}, nil
}

func base(text []byte, pos position) *ast.BaseNode {
	return &ast.BaseNode{
		Loc: &ast.SourceLocation{
			Start: ast.Position{
				Line:   pos.line,
				Column: pos.col,
			},
			End: ast.Position{
				Line:   pos.line,
				Column: pos.col + len(text),
			},
			Source: source(text),
		},
	}
}

func source(text []byte) *string {
	str := string(text)
	return &str
}
