package yqlib

import (
	"container/list"
	"fmt"
)

func getVariableOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
	variableName := expressionNode.Operation.StringValue
	log.Debugf("getVariableOperator %v", variableName)
	result := context.GetVariable(variableName)
	if result == nil {
		result = list.New()
	}
	return context.ChildContext(result), nil
}

type assignVarPreferences struct {
	IsReference bool
}

func useWithPipe(_ *dataTreeNavigator, _ Context, _ *ExpressionNode) (Context, error) {
	return Context{}, fmt.Errorf("must use variable with a pipe, e.g. `exp as $x | ...`")
}

// variables are like loops in jq
// https://stedolan.github.io/jq/manual/#Variable
func variableLoop(d *dataTreeNavigator, context Context, originalExp *ExpressionNode) (Context, error) {
	log.Debug("variable loop!")
	results := list.New()
	var evaluateAllTogether = true
	for matchEl := context.MatchingNodes.Front(); matchEl != nil; matchEl = matchEl.Next() {
		evaluateAllTogether = evaluateAllTogether && matchEl.Value.(*CandidateNode).EvaluateTogether
		if !evaluateAllTogether {
			break
		}
	}
	if evaluateAllTogether {
		return variableLoopSingleChild(d, context, originalExp)
	}

	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		result, err := variableLoopSingleChild(d, context.SingleChildContext(el.Value.(*CandidateNode)), originalExp)
		if err != nil {
			return Context{}, err
		}
		results.PushBackList(result.MatchingNodes)
	}
	return context.ChildContext(results), nil
}

func variableLoopSingleChild(d *dataTreeNavigator, context Context, originalExp *ExpressionNode) (Context, error) {

	variableExp := originalExp.LHS
	lhs, err := d.GetMatchingNodes(context.ReadOnlyClone(), variableExp.LHS)
	if err != nil {
		return Context{}, err
	}
	if variableExp.RHS.Operation.OperationType.Type != "GET_VARIABLE" {
		return Context{}, fmt.Errorf("RHS of 'as' operator must be a variable name e.g. $foo")
	}
	variableName := variableExp.RHS.Operation.StringValue

	prefs := variableExp.Operation.Preferences.(assignVarPreferences)

	results := list.New()

	// now we loop over lhs, set variable to each result and calculate originalExp.Rhs
	for el := lhs.MatchingNodes.Front(); el != nil; el = el.Next() {
		log.Debugf("PROCESSING VARIABLE: %v", NodeToString(el.Value.(*CandidateNode)))
		var variableValue = list.New()
		if prefs.IsReference {
			variableValue.PushBack(el.Value)
		} else {
			candidateCopy := el.Value.(*CandidateNode).Copy()
			variableValue.PushBack(candidateCopy)
		}
		newContext := context.ChildContext(context.MatchingNodes)
		newContext.SetVariable(variableName, variableValue)

		rhs, err := d.GetMatchingNodes(newContext, originalExp.RHS)

		if err != nil {
			return Context{}, err
		}
		log.Debugf("PROCESSING VARIABLE DONE, got back: %v", rhs.MatchingNodes.Len())
		results.PushBackList(rhs.MatchingNodes)
	}

	// if there is no LHS - then I guess we just calculate originalExp.Rhs
	if lhs.MatchingNodes.Len() == 0 {
		return d.GetMatchingNodes(context, originalExp.RHS)
	}

	return context.ChildContext(results), nil

}
