package semantic

import (
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"time"

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

type Node interface {
	node()
	NodeType() string
	Copy() Node

	json.Marshaler
}

func (*Program) node() {}

func (*BlockStatement) node()              {}
func (*ExpressionStatement) node()         {}
func (*ReturnStatement) node()             {}
func (*NativeVariableDeclaration) node()   {}
func (*ExternalVariableDeclaration) node() {}

func (*ArrayExpression) node()       {}
func (*FunctionExpression) node()    {}
func (*BinaryExpression) node()      {}
func (*CallExpression) node()        {}
func (*ConditionalExpression) node() {}
func (*IdentifierExpression) node()  {}
func (*LogicalExpression) node()     {}
func (*MemberExpression) node()      {}
func (*ObjectExpression) node()      {}
func (*UnaryExpression) node()       {}

func (*Identifier) node()    {}
func (*Property) node()      {}
func (*FunctionParam) node() {}

func (*BooleanLiteral) node()         {}
func (*DateTimeLiteral) node()        {}
func (*DurationLiteral) node()        {}
func (*FloatLiteral) node()           {}
func (*IntegerLiteral) node()         {}
func (*StringLiteral) node()          {}
func (*RegexpLiteral) node()          {}
func (*UnsignedIntegerLiteral) node() {}

type Statement interface {
	Node
	stmt()
}

func (*BlockStatement) stmt()              {}
func (*ExpressionStatement) stmt()         {}
func (*ReturnStatement) stmt()             {}
func (*NativeVariableDeclaration) stmt()   {}
func (*ExternalVariableDeclaration) stmt() {}

type Expression interface {
	Node
	Type() Type
	expression()
}

func (*ArrayExpression) expression()        {}
func (*BinaryExpression) expression()       {}
func (*BooleanLiteral) expression()         {}
func (*CallExpression) expression()         {}
func (*ConditionalExpression) expression()  {}
func (*DateTimeLiteral) expression()        {}
func (*DurationLiteral) expression()        {}
func (*FloatLiteral) expression()           {}
func (*FunctionExpression) expression()     {}
func (*IdentifierExpression) expression()   {}
func (*IntegerLiteral) expression()         {}
func (*LogicalExpression) expression()      {}
func (*MemberExpression) expression()       {}
func (*ObjectExpression) expression()       {}
func (*RegexpLiteral) expression()          {}
func (*StringLiteral) expression()          {}
func (*UnaryExpression) expression()        {}
func (*UnsignedIntegerLiteral) expression() {}

type Literal interface {
	Expression
	literal()
}

func (*BooleanLiteral) literal()         {}
func (*DateTimeLiteral) literal()        {}
func (*DurationLiteral) literal()        {}
func (*FloatLiteral) literal()           {}
func (*IntegerLiteral) literal()         {}
func (*RegexpLiteral) literal()          {}
func (*StringLiteral) literal()          {}
func (*UnsignedIntegerLiteral) literal() {}

type Program struct {
	Body []Statement `json:"body"`
}

func (*Program) NodeType() string { return "Program" }

func (p *Program) Copy() Node {
	if p == nil {
		return p
	}
	np := new(Program)
	*np = *p

	if len(p.Body) > 0 {
		np.Body = make([]Statement, len(p.Body))
		for i, s := range p.Body {
			np.Body[i] = s.Copy().(Statement)
		}
	}

	return np
}

type BlockStatement struct {
	Body []Statement `json:"body"`
}

func (*BlockStatement) NodeType() string { return "BlockStatement" }

func (s *BlockStatement) ReturnStatement() *ReturnStatement {
	return s.Body[len(s.Body)-1].(*ReturnStatement)
}

func (s *BlockStatement) Copy() Node {
	if s == nil {
		return s
	}
	ns := new(BlockStatement)
	*ns = *s

	if len(s.Body) > 0 {
		ns.Body = make([]Statement, len(s.Body))
		for i, stmt := range s.Body {
			ns.Body[i] = stmt.Copy().(Statement)
		}
	}

	return ns
}

type ExpressionStatement struct {
	Expression Expression `json:"expression"`
}

func (*ExpressionStatement) NodeType() string { return "ExpressionStatement" }

func (s *ExpressionStatement) Copy() Node {
	if s == nil {
		return s
	}
	ns := new(ExpressionStatement)
	*ns = *s

	ns.Expression = s.Expression.Copy().(Expression)

	return ns
}

type ReturnStatement struct {
	Argument Expression `json:"argument"`
}

func (*ReturnStatement) NodeType() string { return "ReturnStatement" }

func (s *ReturnStatement) Copy() Node {
	if s == nil {
		return s
	}
	ns := new(ReturnStatement)
	*ns = *s

	ns.Argument = s.Argument.Copy().(Expression)

	return ns
}

type VariableDeclaration interface {
	Node
	ID() *Identifier
	InitType() Type
}

type NativeVariableDeclaration struct {
	Identifier *Identifier `json:"identifier"`
	Init       Expression  `json:"init"`
}

func (d *NativeVariableDeclaration) ID() *Identifier {
	return d.Identifier
}
func (d *NativeVariableDeclaration) InitType() Type {
	return d.Init.Type()
}

func (*NativeVariableDeclaration) NodeType() string { return "NativeVariableDeclaration" }

func (s *NativeVariableDeclaration) Copy() Node {
	if s == nil {
		return s
	}
	ns := new(NativeVariableDeclaration)
	*ns = *s

	ns.Identifier = s.Identifier.Copy().(*Identifier)

	if s.Init != nil {
		ns.Init = s.Init.Copy().(Expression)
	}

	return ns
}

type ExternalVariableDeclaration struct {
	Identifier *Identifier `json:"identifier"`
	Type       Type        `json:"type"`
}

func NewExternalVariableDeclaration(name string, typ Type) *ExternalVariableDeclaration {
	return &ExternalVariableDeclaration{
		Identifier: &Identifier{Name: name},
		Type:       typ,
	}
}

func (d *ExternalVariableDeclaration) ID() *Identifier {
	return d.Identifier
}
func (d *ExternalVariableDeclaration) InitType() Type {
	return d.Type
}

func (*ExternalVariableDeclaration) NodeType() string { return "ExternalVariableDeclaration" }

func (s *ExternalVariableDeclaration) Copy() Node {
	if s == nil {
		return s
	}
	ns := new(ExternalVariableDeclaration)
	*ns = *s

	ns.Identifier = s.Identifier.Copy().(*Identifier)

	return ns
}

type ArrayExpression struct {
	Elements []Expression `json:"elements"`
	typ      Type
}

func (*ArrayExpression) NodeType() string { return "ArrayExpression" }
func (e *ArrayExpression) Type() Type {
	if e.typ == nil {
		e.typ = arrayTypeOf(e)
	}
	return e.typ
}

func (e *ArrayExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(ArrayExpression)
	*ne = *e

	if len(e.Elements) > 0 {
		ne.Elements = make([]Expression, len(e.Elements))
		for i, elem := range e.Elements {
			ne.Elements[i] = elem.Copy().(Expression)
		}
	}

	return ne
}

type FunctionExpression struct {
	Params []*FunctionParam `json:"params"`
	Body   Node             `json:"body"`
	typ    Type
}

func (*FunctionExpression) NodeType() string { return "ArrowFunctionExpression" }
func (e *FunctionExpression) Type() Type {
	if e.typ == nil {
		e.typ = functionTypeOf(e)
	}
	return e.typ

}

func (e *FunctionExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(FunctionExpression)
	*ne = *e

	if len(e.Params) > 0 {
		ne.Params = make([]*FunctionParam, len(e.Params))
		for i, p := range e.Params {
			ne.Params[i] = p.Copy().(*FunctionParam)
		}
	}
	ne.Body = e.Body.Copy()

	return ne
}

type FunctionParam struct {
	Key         *Identifier `json:"key"`
	Default     Expression  `json:"default"`
	Piped       bool        `json:"piped,omitempty"`
	declaration VariableDeclaration
}

func (*FunctionParam) NodeType() string { return "FunctionParam" }

func (f *FunctionParam) Type() Type {
	if f.declaration == nil {
		if f.Default != nil {
			f.declaration = &NativeVariableDeclaration{
				Identifier: f.Key,
				Init:       f.Default,
			}
		} else {
			return Invalid
		}
	}
	return f.declaration.InitType()
}

func (p *FunctionParam) Copy() Node {
	if p == nil {
		return p
	}
	np := new(FunctionParam)
	*np = *p

	np.Key = p.Key.Copy().(*Identifier)
	if np.Default != nil {
		np.Default = p.Default.Copy().(Expression)
	}

	return np
}

type BinaryExpression struct {
	Operator ast.OperatorKind `json:"operator"`
	Left     Expression       `json:"left"`
	Right    Expression       `json:"right"`
}

func (*BinaryExpression) NodeType() string { return "BinaryExpression" }
func (e *BinaryExpression) Type() Type {
	return binaryTypesLookup[binarySignature{
		operator: e.Operator,
		left:     e.Left.Type().Kind(),
		right:    e.Right.Type().Kind(),
	}]
}

func (e *BinaryExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(BinaryExpression)
	*ne = *e

	ne.Left = e.Left.Copy().(Expression)
	ne.Right = e.Right.Copy().(Expression)

	return ne
}

type CallExpression struct {
	Callee    Expression        `json:"callee"`
	Arguments *ObjectExpression `json:"arguments"`
}

func (*CallExpression) NodeType() string { return "CallExpression" }
func (e *CallExpression) Type() Type {
	return e.Callee.Type()
}

func (e *CallExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(CallExpression)
	*ne = *e

	ne.Callee = e.Callee.Copy().(Expression)
	ne.Arguments = e.Arguments.Copy().(*ObjectExpression)

	return ne
}

type ConditionalExpression struct {
	Test       Expression `json:"test"`
	Alternate  Expression `json:"alternate"`
	Consequent Expression `json:"consequent"`
}

func (*ConditionalExpression) NodeType() string { return "ConditionalExpression" }

func (e *ConditionalExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(ConditionalExpression)
	*ne = *e

	ne.Test = e.Test.Copy().(Expression)
	ne.Alternate = e.Alternate.Copy().(Expression)
	ne.Consequent = e.Consequent.Copy().(Expression)

	return ne
}

type LogicalExpression struct {
	Operator ast.LogicalOperatorKind `json:"operator"`
	Left     Expression              `json:"left"`
	Right    Expression              `json:"right"`
}

func (*LogicalExpression) NodeType() string { return "LogicalExpression" }
func (*LogicalExpression) Type() Type       { return Bool }

func (e *LogicalExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(LogicalExpression)
	*ne = *e

	ne.Left = e.Left.Copy().(Expression)
	ne.Right = e.Right.Copy().(Expression)

	return ne
}

type MemberExpression struct {
	Object   Expression `json:"object"`
	Property string     `json:"property"`
}

func (*MemberExpression) NodeType() string { return "MemberExpression" }

func (e *MemberExpression) Type() Type {
	t := e.Object.Type()
	if t.Kind() != Object {
		return Invalid
	}
	return e.Object.Type().PropertyType(e.Property)
}

func (e *MemberExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(MemberExpression)
	*ne = *e

	ne.Object = e.Object.Copy().(Expression)

	return ne
}

type ObjectExpression struct {
	Properties []*Property `json:"properties"`
	typ        Type
}

func (*ObjectExpression) NodeType() string { return "ObjectExpression" }
func (e *ObjectExpression) Type() Type {
	if e.typ == nil {
		e.typ = objectTypeOf(e)
	}
	return e.typ
}

func (e *ObjectExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(ObjectExpression)
	*ne = *e

	if len(e.Properties) > 0 {
		ne.Properties = make([]*Property, len(e.Properties))
		for i, prop := range e.Properties {
			ne.Properties[i] = prop.Copy().(*Property)
		}
	}

	return ne
}

type UnaryExpression struct {
	Operator ast.OperatorKind `json:"operator"`
	Argument Expression       `json:"argument"`
}

func (*UnaryExpression) NodeType() string { return "UnaryExpression" }
func (e *UnaryExpression) Type() Type {
	return e.Argument.Type()
}

func (e *UnaryExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(UnaryExpression)
	*ne = *e

	ne.Argument = e.Argument.Copy().(Expression)

	return ne
}

type Property struct {
	Key   *Identifier `json:"key"`
	Value Expression  `json:"value"`
}

func (*Property) NodeType() string { return "Property" }

func (p *Property) Copy() Node {
	if p == nil {
		return p
	}
	np := new(Property)
	*np = *p

	np.Value = p.Value.Copy().(Expression)

	return np
}

type IdentifierExpression struct {
	Name string `json:"name"`
	// declaration is the node that declares this identifier
	declaration VariableDeclaration
}

func (*IdentifierExpression) NodeType() string { return "IdentifierExpression" }

func (e *IdentifierExpression) Type() Type {
	if e.declaration == nil {
		return Invalid
	}
	return e.declaration.InitType()
}

func (e *IdentifierExpression) Copy() Node {
	if e == nil {
		return e
	}
	ne := new(IdentifierExpression)
	*ne = *e

	if ne.declaration != nil {
		ne.declaration = e.declaration.Copy().(VariableDeclaration)
	}

	return ne
}

type Identifier struct {
	Name string `json:"name"`
}

func (*Identifier) NodeType() string { return "Identifier" }

func (i *Identifier) Copy() Node {
	if i == nil {
		return i
	}
	ni := new(Identifier)
	*ni = *i

	return ni
}

type BooleanLiteral struct {
	Value bool `json:"value"`
}

func (*BooleanLiteral) NodeType() string { return "BooleanLiteral" }
func (*BooleanLiteral) Type() Type       { return Bool }

func (l *BooleanLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(BooleanLiteral)
	*nl = *l

	return nl
}

type DateTimeLiteral struct {
	Value time.Time `json:"value"`
}

func (*DateTimeLiteral) NodeType() string { return "DateTimeLiteral" }
func (*DateTimeLiteral) Type() Type       { return Time }

func (l *DateTimeLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(DateTimeLiteral)
	*nl = *l

	return nl
}

type DurationLiteral struct {
	Value time.Duration `json:"value"`
}

func (*DurationLiteral) NodeType() string { return "DurationLiteral" }
func (*DurationLiteral) Type() Type       { return Duration }

func (l *DurationLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(DurationLiteral)
	*nl = *l

	return nl
}

type IntegerLiteral struct {
	Value int64 `json:"value"`
}

func (*IntegerLiteral) NodeType() string { return "IntegerLiteral" }
func (*IntegerLiteral) Type() Type       { return Int }

func (l *IntegerLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(IntegerLiteral)
	*nl = *l

	return nl
}

type FloatLiteral struct {
	Value float64 `json:"value"`
}

func (*FloatLiteral) NodeType() string { return "FloatLiteral" }
func (*FloatLiteral) Type() Type       { return Float }

func (l *FloatLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(FloatLiteral)
	*nl = *l

	return nl
}

type RegexpLiteral struct {
	Value *regexp.Regexp `json:"value"`
}

func (*RegexpLiteral) NodeType() string { return "RegexpLiteral" }
func (*RegexpLiteral) Type() Type       { return Regexp }

func (l *RegexpLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(RegexpLiteral)
	*nl = *l

	nl.Value = l.Value.Copy()

	return nl
}

type StringLiteral struct {
	Value string `json:"value"`
}

func (*StringLiteral) NodeType() string { return "StringLiteral" }
func (*StringLiteral) Type() Type       { return String }

func (l *StringLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(StringLiteral)
	*nl = *l

	return nl
}

type UnsignedIntegerLiteral struct {
	Value uint64 `json:"value"`
}

func (*UnsignedIntegerLiteral) NodeType() string { return "UnsignedIntegerLiteral" }
func (*UnsignedIntegerLiteral) Type() Type       { return UInt }

func (l *UnsignedIntegerLiteral) Copy() Node {
	if l == nil {
		return l
	}
	nl := new(UnsignedIntegerLiteral)
	*nl = *l

	return nl
}

// New creates a semantic graph from the provided AST and builtin declarations
// The declarations will be modified for any variable declaration found in the program.
func New(prog *ast.Program, declarations map[string]VariableDeclaration) (*Program, error) {
	if declarations == nil {
		// NOTE: Calls to New may expect modifications to declarations to persist outside the function.
		// The check is against nil instead of len(declarations) == 0 for this reason.
		declarations = make(map[string]VariableDeclaration)
	}
	return analyzeProgram(prog, DeclarationScope(declarations))
}

type DeclarationScope map[string]VariableDeclaration

func (s DeclarationScope) Copy() DeclarationScope {
	cpy := make(DeclarationScope, len(s))
	for k, v := range s {
		cpy[k] = v
	}
	return cpy
}

func analyzeProgram(prog *ast.Program, declarations DeclarationScope) (*Program, error) {
	p := &Program{
		Body: make([]Statement, len(prog.Body)),
	}
	for i, s := range prog.Body {
		n, err := analyzeStatment(s, declarations)
		if err != nil {
			return nil, err
		}
		p.Body[i] = n
	}
	return p, nil
}

func analyzeNode(n ast.Node, declarations DeclarationScope) (Node, error) {
	switch n := n.(type) {
	case ast.Statement:
		return analyzeStatment(n, declarations)
	case ast.Expression:
		return analyzeExpression(n, declarations)
	default:
		return nil, fmt.Errorf("unsupported node %T", n)
	}
}

func analyzeStatment(s ast.Statement, declarations DeclarationScope) (Statement, error) {
	switch s := s.(type) {
	case *ast.BlockStatement:
		return analyzeBlockStatement(s, declarations)
	case *ast.ExpressionStatement:
		return analyzeExpressionStatement(s, declarations)
	case *ast.ReturnStatement:
		return analyzeReturnStatement(s, declarations)
	case *ast.VariableDeclaration:
		// Expect a single declaration
		if len(s.Declarations) != 1 {
			return nil, fmt.Errorf("only single variable declarations are supported, found %d declarations", len(s.Declarations))
		}
		return analyzeVariableDeclaration(s.Declarations[0], declarations)
	default:
		return nil, fmt.Errorf("unsupported statement %T", s)
	}
}

func analyzeBlockStatement(block *ast.BlockStatement, declarations DeclarationScope) (*BlockStatement, error) {
	declarations = declarations.Copy()
	b := &BlockStatement{
		Body: make([]Statement, len(block.Body)),
	}
	for i, s := range block.Body {
		n, err := analyzeStatment(s, declarations)
		if err != nil {
			return nil, err
		}
		b.Body[i] = n
	}
	last := len(b.Body) - 1
	if _, ok := b.Body[last].(*ReturnStatement); !ok {
		return nil, errors.New("missing return statement in block")
	}
	return b, nil
}

func analyzeExpressionStatement(expr *ast.ExpressionStatement, declarations DeclarationScope) (*ExpressionStatement, error) {
	e, err := analyzeExpression(expr.Expression, declarations)
	if err != nil {
		return nil, err
	}
	return &ExpressionStatement{
		Expression: e,
	}, nil
}

func analyzeReturnStatement(ret *ast.ReturnStatement, declarations DeclarationScope) (*ReturnStatement, error) {
	arg, err := analyzeExpression(ret.Argument, declarations)
	if err != nil {
		return nil, err
	}
	return &ReturnStatement{
		Argument: arg,
	}, nil
}

func analyzeVariableDeclaration(decl *ast.VariableDeclarator, declarations DeclarationScope) (*NativeVariableDeclaration, error) {
	id, err := analyzeIdentifier(decl.ID, declarations)
	if err != nil {
		return nil, err
	}
	init, err := analyzeExpression(decl.Init, declarations)
	if err != nil {
		return nil, err
	}
	vd := &NativeVariableDeclaration{
		Identifier: id,
		Init:       init,
	}
	declarations[vd.Identifier.Name] = vd
	return vd, nil
}

func analyzeExpression(expr ast.Expression, declarations DeclarationScope) (Expression, error) {
	switch expr := expr.(type) {
	case *ast.ArrowFunctionExpression:
		return analyzeArrowFunctionExpression(expr, declarations)
	case *ast.CallExpression:
		return analyzeCallExpression(expr, declarations)
	case *ast.MemberExpression:
		return analyzeMemberExpression(expr, declarations)
	case *ast.PipeExpression:
		return analyzePipeExpression(expr, declarations)
	case *ast.BinaryExpression:
		return analyzeBinaryExpression(expr, declarations)
	case *ast.UnaryExpression:
		return analyzeUnaryExpression(expr, declarations)
	case *ast.LogicalExpression:
		return analyzeLogicalExpression(expr, declarations)
	case *ast.ObjectExpression:
		return analyzeObjectExpression(expr, declarations)
	case *ast.ArrayExpression:
		return analyzeArrayExpression(expr, declarations)
	case *ast.Identifier:
		return analyzeIdentifierExpression(expr, declarations)
	case ast.Literal:
		return analyzeLiteral(expr, declarations)
	default:
		return nil, fmt.Errorf("unsupported expression %T", expr)
	}
}

func analyzeLiteral(lit ast.Literal, declarations DeclarationScope) (Literal, error) {
	switch lit := lit.(type) {
	case *ast.StringLiteral:
		return analyzeStringLiteral(lit, declarations)
	case *ast.BooleanLiteral:
		return analyzeBooleanLiteral(lit, declarations)
	case *ast.FloatLiteral:
		return analyzeFloatLiteral(lit, declarations)
	case *ast.IntegerLiteral:
		return analyzeIntegerLiteral(lit, declarations)
	case *ast.UnsignedIntegerLiteral:
		return analyzeUnsignedIntegerLiteral(lit, declarations)
	case *ast.RegexpLiteral:
		return analyzeRegexpLiteral(lit, declarations)
	case *ast.DurationLiteral:
		return analyzeDurationLiteral(lit, declarations)
	case *ast.DateTimeLiteral:
		return analyzeDateTimeLiteral(lit, declarations)
	case *ast.PipeLiteral:
		return nil, errors.New("a pipe literal may only be used as a default value for an argument in a function definition")
	default:
		return nil, fmt.Errorf("unsupported literal %T", lit)
	}
}

func analyzeArrowFunctionExpression(arrow *ast.ArrowFunctionExpression, declarations DeclarationScope) (*FunctionExpression, error) {
	declarations = declarations.Copy()
	f := &FunctionExpression{
		Params: make([]*FunctionParam, len(arrow.Params)),
	}
	pipedCount := 0
	for i, p := range arrow.Params {
		key, err := analyzeIdentifier(p.Key, declarations)
		if err != nil {
			return nil, err
		}

		var (
			def         Expression
			declaration VariableDeclaration
			piped       bool
		)
		if p.Value != nil {
			if _, ok := p.Value.(*ast.PipeLiteral); ok {
				// Special case the PipeLiteral
				piped = true
				pipedCount++
				if pipedCount > 1 {
					return nil, errors.New("only a single argument may be piped")
				}
			} else {
				d, err := analyzeExpression(p.Value, declarations)
				if err != nil {
					return nil, err
				}
				def = d
				declaration = &NativeVariableDeclaration{
					Identifier: key,
					Init:       def,
				}
				declarations[key.Name] = declaration
			}
		}

		f.Params[i] = &FunctionParam{
			Key:         key,
			Default:     def,
			Piped:       piped,
			declaration: declaration,
		}

	}

	b, err := analyzeNode(arrow.Body, declarations)
	if err != nil {
		return nil, err
	}
	f.Body = b

	return f, nil
}

func analyzeCallExpression(call *ast.CallExpression, declarations DeclarationScope) (*CallExpression, error) {
	callee, err := analyzeExpression(call.Callee, declarations)
	if err != nil {
		return nil, err
	}
	var args *ObjectExpression
	if l := len(call.Arguments); l > 1 {
		return nil, fmt.Errorf("arguments are not a single object expression %v", args)
	} else if l == 1 {
		obj, ok := call.Arguments[0].(*ast.ObjectExpression)
		if !ok {
			return nil, fmt.Errorf("arguments not an object expression")
		}
		var err error
		args, err = analyzeObjectExpression(obj, declarations)
		if err != nil {
			return nil, err
		}
	} else {
		args = new(ObjectExpression)
	}

	expr := &CallExpression{
		Callee:    callee,
		Arguments: args,
	}

	declarations = declarations.Copy()
	for _, arg := range args.Properties {
		declarations[arg.Key.Name] = &NativeVariableDeclaration{
			Identifier: arg.Key,
			Init:       arg.Value,
		}
	}

	ApplyNewDeclarations(expr.Callee, declarations)
	return expr, nil
}

func ApplyNewDeclarations(n Node, declarations map[string]VariableDeclaration) {
	v := &applyDeclarationsVisitor{
		declarations: declarations,
	}
	Walk(v, n)
}

type applyDeclarationsVisitor struct {
	declarations DeclarationScope
}

func (v *applyDeclarationsVisitor) Visit(n Node) Visitor {
	switch n := n.(type) {
	case *IdentifierExpression:
		if n.declaration == nil {
			n.declaration = v.declarations[n.Name]
		}
		// No need to walk further down this branch
		return nil
	case *FunctionExpression:
		// Remove type information since we may have changed it.
		n.typ = nil
	case *FunctionParam:
		if n.declaration == nil {
			n.declaration = v.declarations[n.Key.Name]
		}
		// No need to walk further down this branch
		return nil
	}
	return v
}
func (v *applyDeclarationsVisitor) Done() {}

func analyzeMemberExpression(member *ast.MemberExpression, declarations DeclarationScope) (*MemberExpression, error) {
	obj, err := analyzeExpression(member.Object, declarations)
	if err != nil {
		return nil, err
	}

	var propertyName string
	switch p := member.Property.(type) {
	case *ast.Identifier:
		propertyName = p.Name
	case *ast.StringLiteral:
		propertyName = p.Value
	case *ast.IntegerLiteral:
		propertyName = strconv.FormatInt(p.Value, 10)
	default:
		return nil, fmt.Errorf("unsupported member property expression of type %T", member.Property)
	}

	return &MemberExpression{
		Object:   obj,
		Property: propertyName,
	}, nil
}

func analyzePipeExpression(pipe *ast.PipeExpression, declarations DeclarationScope) (*CallExpression, error) {
	call, err := analyzeCallExpression(pipe.Call, declarations)
	if err != nil {
		return nil, err
	}

	decl, err := resolveDeclaration(call.Callee)
	if err != nil {
		return nil, err
	}
	fnTyp := decl.InitType()
	if fnTyp.Kind() != Function {
		return nil, fmt.Errorf("cannot pipe into non function %q", fnTyp.Kind())
	}
	key := fnTyp.PipeArgument()
	if key == "" {
		return nil, fmt.Errorf("function %q does not have a pipe argument", decl.ID().Name)
	}

	value, err := analyzeExpression(pipe.Argument, declarations)
	if err != nil {
		return nil, err
	}
	property := &Property{
		Key:   &Identifier{Name: key},
		Value: value,
	}

	found := false
	for i, p := range call.Arguments.Properties {
		if key == p.Key.Name {
			found = true
			call.Arguments.Properties[i] = property
			break
		}
	}
	if !found {
		call.Arguments.Properties = append(call.Arguments.Properties, property)
	}
	return call, nil
}

// resolveDeclaration traverse the expression until a variable declaration is found for the expression.
func resolveDeclaration(n Node) (VariableDeclaration, error) {
	switch n := n.(type) {
	case *IdentifierExpression:
		if n.declaration == nil {
			return nil, fmt.Errorf("identifier expression %q has no declaration", n.Name)
		}
		return resolveDeclaration(n.declaration)
	case *ExternalVariableDeclaration:
		return n, nil
	case *NativeVariableDeclaration:
		if n.Init == nil {
			return nil, fmt.Errorf("variable declaration %v has no init", n.Identifier)
		}
		if i, ok := n.Init.(*IdentifierExpression); ok {
			return resolveDeclaration(i)
		}
		return n, nil
	}
	return nil, errors.New("no declaration found")
}

func analyzeBinaryExpression(binary *ast.BinaryExpression, declarations DeclarationScope) (*BinaryExpression, error) {
	left, err := analyzeExpression(binary.Left, declarations)
	if err != nil {
		return nil, err
	}
	right, err := analyzeExpression(binary.Right, declarations)
	if err != nil {
		return nil, err
	}
	return &BinaryExpression{
		Operator: binary.Operator,
		Left:     left,
		Right:    right,
	}, nil
}

func analyzeUnaryExpression(unary *ast.UnaryExpression, declarations DeclarationScope) (*UnaryExpression, error) {
	arg, err := analyzeExpression(unary.Argument, declarations)
	if err != nil {
		return nil, err
	}
	// TODO(nathanielc): validate operand type once we have type inference working with functions.
	//k := arg.Type().Kind()
	//if k != Bool && k != Int && k != Float && k != Duration {
	//	return nil, fmt.Errorf("invalid unary operator %v on type %v", unary.Operator, k)
	//}
	return &UnaryExpression{
		Operator: unary.Operator,
		Argument: arg,
	}, nil
}
func analyzeLogicalExpression(logical *ast.LogicalExpression, declarations DeclarationScope) (*LogicalExpression, error) {
	left, err := analyzeExpression(logical.Left, declarations)
	if err != nil {
		return nil, err
	}
	// TODO(nathanielc): Validate operand types once we have type inference working with functions.
	//if k := left.Type().Kind(); k != Bool {
	//	return nil, fmt.Errorf("left operand to logical expression is not a boolean, got kind %v", k)
	//}
	right, err := analyzeExpression(logical.Right, declarations)
	if err != nil {
		return nil, err
	}
	//if k := right.Type().Kind(); k != Bool {
	//	return nil, fmt.Errorf("right operand to logical expression is not a boolean, got kind %v", k)
	//}
	return &LogicalExpression{
		Operator: logical.Operator,
		Left:     left,
		Right:    right,
	}, nil
}
func analyzeObjectExpression(obj *ast.ObjectExpression, declarations DeclarationScope) (*ObjectExpression, error) {
	o := &ObjectExpression{
		Properties: make([]*Property, len(obj.Properties)),
	}
	for i, p := range obj.Properties {
		n, err := analyzeProperty(p, declarations)
		if err != nil {
			return nil, err
		}
		o.Properties[i] = n
	}
	return o, nil
}
func analyzeArrayExpression(array *ast.ArrayExpression, declarations DeclarationScope) (*ArrayExpression, error) {
	a := &ArrayExpression{
		Elements: make([]Expression, len(array.Elements)),
	}
	for i, e := range array.Elements {
		n, err := analyzeExpression(e, declarations)
		if err != nil {
			return nil, err
		}
		a.Elements[i] = n
	}
	return a, nil
}

func analyzeIdentifier(ident *ast.Identifier, declarations DeclarationScope) (*Identifier, error) {
	return &Identifier{
		Name: ident.Name,
	}, nil
}

func analyzeIdentifierExpression(ident *ast.Identifier, declarations DeclarationScope) (*IdentifierExpression, error) {
	return &IdentifierExpression{
		Name:        ident.Name,
		declaration: declarations[ident.Name],
	}, nil
}

func analyzeProperty(property *ast.Property, declarations DeclarationScope) (*Property, error) {
	key, err := analyzeIdentifier(property.Key, declarations)
	if err != nil {
		return nil, err
	}
	value, err := analyzeExpression(property.Value, declarations)
	if err != nil {
		return nil, err
	}
	return &Property{
		Key:   key,
		Value: value,
	}, nil
}

func analyzeDateTimeLiteral(lit *ast.DateTimeLiteral, declarations DeclarationScope) (*DateTimeLiteral, error) {
	return &DateTimeLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeDurationLiteral(lit *ast.DurationLiteral, declarations DeclarationScope) (*DurationLiteral, error) {
	return &DurationLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeFloatLiteral(lit *ast.FloatLiteral, declarations DeclarationScope) (*FloatLiteral, error) {
	return &FloatLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeIntegerLiteral(lit *ast.IntegerLiteral, declarations DeclarationScope) (*IntegerLiteral, error) {
	return &IntegerLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeUnsignedIntegerLiteral(lit *ast.UnsignedIntegerLiteral, declarations DeclarationScope) (*UnsignedIntegerLiteral, error) {
	return &UnsignedIntegerLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeStringLiteral(lit *ast.StringLiteral, declarations DeclarationScope) (*StringLiteral, error) {
	return &StringLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeBooleanLiteral(lit *ast.BooleanLiteral, declarations DeclarationScope) (*BooleanLiteral, error) {
	return &BooleanLiteral{
		Value: lit.Value,
	}, nil
}
func analyzeRegexpLiteral(lit *ast.RegexpLiteral, declarations DeclarationScope) (*RegexpLiteral, error) {
	return &RegexpLiteral{
		Value: lit.Value,
	}, nil
}
