// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package interpreter provides functions to evaluate parsed expressions with
// the option to augment the evaluation with inputs and functions supplied at
// evaluation time.
package interpreter

import (
	"errors"

	"github.com/google/cel-go/common/ast"
	"github.com/google/cel-go/common/containers"
	"github.com/google/cel-go/common/types"
	"github.com/google/cel-go/common/types/ref"
)

// PlannerOption configures the program plan options during interpretable setup.
type PlannerOption func(*planner) (*planner, error)

// Interpreter generates a new InterpretableV2 from a checked or unchecked expression.
type Interpreter interface {
	// NewInterpretable creates an InterpretableV2 from a checked expression and an
	// optional list of PlannerOption values.
	NewInterpretable(exprAST *ast.AST, opts ...PlannerOption) (InterpretableV2, error)
}

// EvalObserver is a functional interface that accepts an expression id and an observed value.
// The id identifies the expression that was evaluated, the programStep is the Interpretable or Qualifier that
// was evaluated and value is the result of the evaluation.
type EvalObserver func(vars Activation, id int64, programStep any, value ref.Val)

// StatefulObserver observes evaluation while tracking or utilizing stateful behavior.
type StatefulObserver interface {
	// InitState configures stateful metadata on the execution frame.
	InitState(*ExecutionFrame) (any, error)

	// GetState retrieves the stateful metadata from the execution frame.
	GetState(*ExecutionFrame) any

	// Observe passes the activation and relevant evaluation metadata to the observer.
	// The observe method is expected to do the equivalent of GetState(AsFrame(activation))
	// to find the metadata that needs to be updated upon invocation.
	Observe(Activation, int64, any, ref.Val)
}

// EvalCancelledError represents a cancelled program evaluation operation.
type EvalCancelledError struct {
	Message string
	// Type identifies the cause of the cancellation.
	Cause CancellationCause
}

func (e EvalCancelledError) Error() string {
	return e.Message
}

// CancellationCause enumerates the ways a program evaluation operation can be cancelled.
type CancellationCause int

const (
	// ContextCancelled indicates that the operation was cancelled in response to a Golang context cancellation.
	ContextCancelled CancellationCause = iota

	// CostLimitExceeded indicates that the operation was cancelled in response to the actual cost limit being
	// exceeded.
	CostLimitExceeded
)

// evalStateOption configures the evalStateFactory behavior.
type evalStateOption func(*evalStateFactory) *evalStateFactory

// EvalStateFactory configures the EvalState generator to be used by the EvalStateObserver.
func EvalStateFactory(factory func() EvalState) evalStateOption {
	return func(fac *evalStateFactory) *evalStateFactory {
		fac.factory = factory
		return fac
	}
}

// EvalStateObserver provides an observer which records the value associated with the given expression id.
// EvalState must be provided to the observer.
func EvalStateObserver(opts ...evalStateOption) PlannerOption {
	et := &evalStateFactory{factory: NewEvalState}
	for _, o := range opts {
		et = o(et)
	}
	return func(p *planner) (*planner, error) {
		if et.factory == nil {
			return nil, errors.New("eval state factory not configured")
		}
		p.observers = append(p.observers, et)
		p.decorators = append(p.decorators, decObserveEval(et.Observe))
		return p, nil
	}
}

// activationWrapper identifies an object carrying local variables which should not be exposed to the user
// Activations used for such purposes can be unwrapped to return the activation which omits local state.
type activationWrapper interface {
	// Unwrap returns the Activation which omits local state.
	Unwrap() Activation
}

// evalStateFactory holds a reference to a factory function that produces an EvalState instance.
type evalStateFactory struct {
	factory func() EvalState
}

// InitState produces an EvalState instance and bundles it into the ExecutionFrame in a way which is
// not visible to expression evaluation.
func (et *evalStateFactory) InitState(frame *ExecutionFrame) (any, error) {
	state := et.factory()
	if frame.ctx == nil {
		frame.ctx = evalContextPool.Get().(*evalContext)
	}
	frame.ctx.state = state
	return state, nil
}

// GetState extracts the EvalState from the Activation.
func (et *evalStateFactory) GetState(frame *ExecutionFrame) any {
	if frame.ctx == nil {
		return nil
	}
	return frame.ctx.state
}

// Observe records the evaluation state for a given expression node and program step.
func (et *evalStateFactory) Observe(vars Activation, id int64, programStep any, val ref.Val) {
	frame := AsFrame(vars)
	if frame.ctx == nil || frame.ctx.state == nil {
		return
	}
	frame.ctx.state.SetValue(id, val)
}

// CustomDecorator configures a custom interpretable decorator for the program.
func CustomDecorator(dec InterpretableDecorator) PlannerOption {
	return func(p *planner) (*planner, error) {
		dec2 := func(i InterpretableV2) (InterpretableV2, error) {
			legacy, err := dec(i)
			if err != nil {
				return nil, err
			}
			return adaptToV2(legacy), nil
		}
		p.decorators = append(p.decorators, dec2)
		return p, nil
	}
}

// CustomDecoratorV2 configures a custom V2 interpretable decorator for the program.
func CustomDecoratorV2(dec InterpretableDecoratorV2) PlannerOption {
	return func(p *planner) (*planner, error) {
		p.decorators = append(p.decorators, dec)
		return p, nil
	}
}

// ExhaustiveEval replaces operations that short-circuit with versions that evaluate
// expressions and couples this behavior with the TrackState() decorator to provide
// insight into the evaluation state of the entire expression. EvalState must be
// provided to the decorator. This decorator is not thread-safe, and the EvalState
// must be reset between Eval() calls.
func ExhaustiveEval() PlannerOption {
	return CustomDecoratorV2(decDisableShortcircuits())
}

// InterruptableEval annotates comprehension loops with information that indicates they
// should check the `#interrupted` state within a custom Activation.
//
// The custom activation is currently managed higher up in the stack within the 'cel' package
// and should not require any custom support on behalf of callers.
func InterruptableEval() PlannerOption {
	return CustomDecoratorV2(decInterruptFolds())
}

// Optimize will pre-compute operations such as list and map construction and optimize
// call arguments to set membership tests. The set of optimizations will increase over time.
func Optimize() PlannerOption {
	return CustomDecoratorV2(decOptimize())
}

// RegexOptimization provides a way to replace an InterpretableCall for a regex function when the
// RegexIndex argument is a string constant. Typically, the Factory would compile the regex pattern at
// RegexIndex and report any errors (at program creation time) and then use the compiled regex for
// all regex function invocations.
type RegexOptimization struct {
	// Function is the name of the function to optimize.
	Function string
	// OverloadID is the ID of the overload to optimize.
	OverloadID string
	// RegexIndex is the index position of the regex pattern argument. Only calls to the function where this argument is
	// a string constant will be delegated to this optimizer.
	RegexIndex int
	// Factory constructs a replacement InterpretableCall node that optimizes the regex function call. Factory is
	// provided with the unoptimized regex call and the string constant at the RegexIndex argument.
	// The Factory may compile the regex for use across all invocations of the call, return any errors and
	// return an interpreter.NewCall with the desired regex optimized function impl.
	Factory func(call InterpretableCall, regexPattern string) (InterpretableCall, error)
}

// CompileRegexConstants compiles regex pattern string constants at program creation time and reports any regex pattern
// compile errors.
func CompileRegexConstants(regexOptimizations ...*RegexOptimization) PlannerOption {
	return CustomDecoratorV2(decRegexOptimizer(regexOptimizations...))
}

type exprInterpreter struct {
	dispatcher  Dispatcher
	container   *containers.Container
	provider    types.Provider
	adapter     types.Adapter
	attrFactory AttributeFactory
}

// NewInterpreter builds an Interpreter from a Dispatcher and TypeProvider which will be used
// throughout the Eval of all Interpretable instances generated from it.
func NewInterpreter(dispatcher Dispatcher,
	container *containers.Container,
	provider types.Provider,
	adapter types.Adapter,
	attrFactory AttributeFactory) Interpreter {
	return &exprInterpreter{
		dispatcher:  dispatcher,
		container:   container,
		provider:    provider,
		adapter:     adapter,
		attrFactory: attrFactory}
}

// NewInterpretable implements the Interpreter interface method.
func (i *exprInterpreter) NewInterpretable(
	checked *ast.AST,
	opts ...PlannerOption) (InterpretableV2, error) {
	p := newPlanner(i.dispatcher, i.provider, i.adapter, i.attrFactory, i.container, checked)
	var err error
	for _, o := range opts {
		p, err = o(p)
		if err != nil {
			return nil, err
		}
	}
	return p.Plan(checked.Expr())
}
