package planner

import (
	"github.com/featurebasedb/featurebase/v3/sql3/planner/types"
)

// PlanVisitor visits nodes in the plan.
type PlanVisitor interface {
	// VisitOperator method is invoked for each node during PlanWalk. If the
	// resulting PlanVisitor is not nil, PlanWalk visits each of the children of
	// the node with that visitor, followed by a call of VisitOperator(nil) to
	// the returned visitor.
	VisitOperator(op types.PlanOperator) PlanVisitor
}

// PlanWalk traverses the plan depth-first. It starts by calling
// v.VisitOperator; node must not be nil. If the result returned by
// v.VisitOperator is not nil, PlanWalk is invoked recursively with the returned
// result for each of the children of the plan operator, followed by a call of
// v.VisitOperator(nil) to the returned result. If v.VisitOperator(op) returns
// non-nil, then all children are walked, even if one of them returns nil.
func PlanWalk(v PlanVisitor, op types.PlanOperator) {
	if v = v.VisitOperator(op); v == nil {
		return
	}

	for _, child := range op.Children() {
		PlanWalk(v, child)
	}

	v.VisitOperator(nil)
}

type planInspectionFunction func(types.PlanOperator) bool

func (f planInspectionFunction) VisitOperator(op types.PlanOperator) PlanVisitor {
	if f(op) {
		return f
	}
	return nil
}

// InspectPlan traverses the plan op graph depth-first order
// if f(op) returns true, InspectPlan invokes f recursively for each of the children of op,
// followed by a call of f(nil).
func InspectPlan(op types.PlanOperator, f planInspectionFunction) {
	PlanWalk(f, op)
}

//-----------------------------------------------------------------------------

// ExprVisitor visits expressions in an expression tree.
type ExprVisitor interface {
	// VisitExpr method is invoked for each expr encountered by ExprWalk.
	// If the result is not nil, ExprWalk visits each of the children
	// of the expr, followed by a call of VisitExpr(nil) to the returned result.
	VisitExpr(expr types.PlanExpression) ExprVisitor
}

func ExprWalk(v ExprVisitor, expr types.PlanExpression) {
	if v = v.VisitExpr(expr); v == nil {
		return
	}

	for _, child := range expr.Children() {
		ExprWalk(v, child)
	}

	v.VisitExpr(nil)
}

type exprInspector func(types.PlanExpression) bool

func (f exprInspector) VisitExpr(e types.PlanExpression) ExprVisitor {
	if f(e) {
		return f
	}
	return nil
}

// walkExpressions traverses the plan and calls ExprWalk on any expression it finds
func walkExpressions(v ExprVisitor, op types.PlanOperator) {
	InspectPlan(op, func(operator types.PlanOperator) bool {
		if n, ok := operator.(types.ContainsExpressions); ok {
			for _, e := range n.Expressions() {
				ExprWalk(v, e)
			}
		}
		return true
	})
}

// InspectOperatorExpressions traverses the plan and calls WalkExpressions on any
// expression it finds.
func InspectOperatorExpressions(op types.PlanOperator, f exprInspector) {
	walkExpressions(f, op)
}

// InspectExpression traverses expressions in depth-first order
func InspectExpression(expr types.PlanExpression, f func(expr types.PlanExpression) bool) {
	ExprWalk(exprInspector(f), expr)
}

//-----------------------------------------------------------------------------

// PlanOpExprVisitor visits expressions in an expression tree. Like ExprVisitor, but with the added context of the plan op in which
// an expression is embedded.
type PlanOpExprVisitor interface {
	// VisitPlanOpExpr method is invoked for each expr encountered by Walk. If the result Visitor is not nil, Walk visits each of
	// the children of the expr with that visitor, followed by a call of VisitPlanOpExpr(nil, nil) to the returned visitor.
	VisitPlanOpExpr(op types.PlanOperator, expression types.PlanExpression) PlanOpExprVisitor
}

// ExprWithPlanOpWalk traverses the expression tree in depth-first order. It starts by calling v.VisitPlanOpExpr(op, expr); expr must
// not be nil. If the visitor returned by v.VisitPlanOpExpr(op, expr) is not nil, Walk is invoked recursively with the returned
// visitor for each children of the expr, followed by a call of v.VisitPlanOpExpr(nil, nil) to the returned visitor.
func ExprWithPlanOpWalk(v PlanOpExprVisitor, op types.PlanOperator, expr types.PlanExpression) {
	if v = v.VisitPlanOpExpr(op, expr); v == nil {
		return
	}

	for _, child := range expr.Children() {
		ExprWithPlanOpWalk(v, op, child)
	}

	v.VisitPlanOpExpr(nil, nil)
}

type exprWithNodeInspector func(types.PlanOperator, types.PlanExpression) bool

func (f exprWithNodeInspector) VisitPlanOpExpr(op types.PlanOperator, expr types.PlanExpression) PlanOpExprVisitor {
	if f(op, expr) {
		return f
	}
	return nil
}

// WalkExpressionsWithPlanOp traverses the plan and calls ExprWithPlanOpWalk on any expression it finds.
func WalkExpressionsWithPlanOp(v PlanOpExprVisitor, op types.PlanOperator) {
	InspectPlan(op, func(operator types.PlanOperator) bool {
		if expressioner, ok := operator.(types.ContainsExpressions); ok {
			for _, e := range expressioner.Expressions() {
				ExprWithPlanOpWalk(v, operator, e)
			}
		}
		return true
	})
}

// InspectExpressionsWithPlanOp traverses the plan and calls f on any expression it finds.
func InspectExpressionsWithPlanOp(op types.PlanOperator, f exprWithNodeInspector) {
	WalkExpressionsWithPlanOp(f, op)
}

// PlanOpTransformFunc is a function that given a plan op will return either a transformed plan op or the original plan op.
// If there was a transformation, the bool will be true, and an error if there was an error
type PlanOpTransformFunc func(op types.PlanOperator) (types.PlanOperator, bool, error)

// TransformPlanOp applies a depth first transformation function to the given plan op
// It returns a tuple that is the result of the transformation; the new PlanOperator, a bool that
// is true if the resultant PlanOperator has not been transformed or an error.
// If the TransformPlanOp has children it will iterate the children and call the transformation
// function on each of them in turn. If those operators are transformed, it will create a new operator
// with those children. The last step is to call the transformation on the passed PlanOperator
func TransformPlanOp(op types.PlanOperator, f PlanOpTransformFunc) (types.PlanOperator, bool, error) {
	thisOperator := op

	children := thisOperator.Children()
	if len(children) == 0 {
		return f(thisOperator)
	}

	var newChildren []types.PlanOperator

	for i := range children {
		child := children[i]
		child, same, err := TransformPlanOp(child, f)
		if err != nil {
			return nil, true, err
		}
		if !same {
			if newChildren == nil {
				newChildren = make([]types.PlanOperator, len(children))
				copy(newChildren, children)
			}
			newChildren[i] = child
		}
	}

	var err error
	sameChildren := true
	if len(newChildren) > 0 {
		sameChildren = false
		thisOperator, err = thisOperator.WithChildren(newChildren...)
		if err != nil {
			return nil, true, err
		}
	}

	resultOperator, sameOperator, err := f(thisOperator)
	if err != nil {
		return nil, true, err
	}
	return resultOperator, sameChildren && sameOperator, nil
}

// ParentContext is a struct that enables transformation functions to include a parent operator
type ParentContext struct {
	Operator   types.PlanOperator
	Parent     types.PlanOperator
	ChildCount int
}

type ParentContextFunc func(c ParentContext) (types.PlanOperator, bool, error)

type ParentSelectorFunc func(c ParentContext) bool

// TransformPlanOpWithParent applies a transformation function to a plan operator in the context that plan operators parent
func TransformPlanOpWithParent(op types.PlanOperator, s ParentSelectorFunc, f ParentContextFunc) (types.PlanOperator, bool, error) {
	return planOpWithParentHelper(ParentContext{op, nil, -1}, s, f)
}

func planOpWithParentHelper(c ParentContext, s ParentSelectorFunc, f ParentContextFunc) (types.PlanOperator, bool, error) {
	operator := c.Operator

	children := operator.Children()
	if len(children) == 0 {
		return f(c)
	}

	var (
		newChildren []types.PlanOperator
		err         error
	)
	for i := range children {
		child := children[i]
		cc := ParentContext{child, operator, i}
		if s == nil || s(cc) {
			child, same, err := planOpWithParentHelper(cc, s, f)
			if err != nil {
				return nil, true, err
			}
			if !same {
				if newChildren == nil {
					newChildren = make([]types.PlanOperator, len(children))
					copy(newChildren, children)
				}
				newChildren[i] = child
			}
		}
	}

	sameChildren := true
	if len(newChildren) > 0 {
		sameChildren = false
		operator, err = operator.WithChildren(newChildren...)
		if err != nil {
			return nil, true, err
		}
	}

	resultOperator, sameOperator, err := f(ParentContext{operator, c.Parent, c.ChildCount})
	if err != nil {
		return nil, true, err
	}
	return resultOperator, sameChildren && sameOperator, nil
}

// ExprWithPlanOpFunc is a function that given an expression and the node
// that contains it, will return that expression as is or transformed
// along with an error, if any.
type ExprWithPlanOpFunc func(op types.PlanOperator, expr types.PlanExpression) (types.PlanExpression, bool, error)

// ExprFunc is a function that given an expression will return that
// expression as is or transformed, or bool to indicate
// whether the expression was modified, and an error or nil.
type ExprFunc func(expr types.PlanExpression) (types.PlanExpression, bool, error)

// ExprExprSelectorFunc is a function that can be used as a filter selector during
// expression transformation - it is called before calling a transformation function on
// a child expression; if it returns false, the child is skipped
type ExprSelectorFunc func(parentExpr, childExpr types.PlanExpression) bool

// TransformSinglePlanOpExpressions applies a transformation function to all expressions on the given plan operator
func TransformSinglePlanOpExpressions(op types.PlanOperator, f ExprFunc, selector ExprSelectorFunc) (types.PlanOperator, bool, error) {
	e, ok := op.(types.ContainsExpressions)
	if !ok {
		return op, true, nil
	}

	exprs := e.Expressions()
	if len(exprs) == 0 {
		return op, true, nil
	}

	var newExprs []types.PlanExpression
	for i := range exprs {
		expr := exprs[i]
		expr, same, err := TransformExpr(expr, f, selector)
		if err != nil {
			return nil, true, err
		}
		if !same {
			if newExprs == nil {
				newExprs = make([]types.PlanExpression, len(exprs))
				copy(newExprs, exprs)
			}
			newExprs[i] = expr
		}
	}
	if len(newExprs) > 0 {
		n, err := e.WithUpdatedExpressions(newExprs...)
		if err != nil {
			return nil, true, err
		}
		return n, false, nil
	}
	return op, true, nil
}

// TransformExpr applies a  depth first transformation function to an expression
func TransformExpr(expr types.PlanExpression, transformFunction ExprFunc, selector ExprSelectorFunc) (types.PlanExpression, bool, error) {
	thisExpr := expr

	children := expr.Children()
	if len(children) == 0 {
		return transformFunction(thisExpr)
	}

	var (
		newChildren []types.PlanExpression
		err         error
	)

	for i := 0; i < len(children); i++ {
		child := children[i]
		if selector(expr, child) {
			c, same, err := TransformExpr(child, transformFunction, selector)
			if err != nil {
				return nil, true, err
			}
			if !same {
				if newChildren == nil {
					newChildren = make([]types.PlanExpression, len(children))
					copy(newChildren, children)
				}
				newChildren[i] = c
			}
		}
	}

	sameChildren := true
	if len(newChildren) > 0 {
		sameChildren = false
		thisExpr, err = thisExpr.WithChildren(newChildren...)
		if err != nil {
			return nil, true, err
		}
	}

	resultExpr, sameExpr, err := transformFunction(thisExpr)
	if err != nil {
		return nil, true, err
	}
	return resultExpr, sameChildren && sameExpr, nil
}
