package yqlib

import (
	"bufio"
	"bytes"
	"container/list"
	"errors"
	"regexp"
	"strings"
)

func configureEncoder(format *Format, indent int) Encoder {

	switch format {
	case JSONFormat:
		prefs := ConfiguredJSONPreferences.Copy()
		prefs.Indent = indent
		prefs.ColorsEnabled = false
		prefs.UnwrapScalar = false
		return NewJSONEncoder(prefs)
	case YamlFormat:
		var prefs = ConfiguredYamlPreferences.Copy()
		prefs.Indent = indent
		prefs.ColorsEnabled = false
		return NewYamlEncoder(prefs)
	case XMLFormat:
		var xmlPrefs = ConfiguredXMLPreferences.Copy()
		xmlPrefs.Indent = indent
		return NewXMLEncoder(xmlPrefs)
	}
	return format.EncoderFactory()
}

func encodeToString(candidate *CandidateNode, prefs encoderPreferences) (string, error) {
	var output bytes.Buffer
	log.Debug("printing with indent: %v", prefs.indent)

	encoder := configureEncoder(prefs.format, prefs.indent)
	if encoder == nil {
		return "", errors.New("no support for output format")
	}

	printer := NewPrinter(encoder, NewSinglePrinterWriter(bufio.NewWriter(&output)))
	err := printer.PrintResults(candidate.AsList())
	return output.String(), err
}

type encoderPreferences struct {
	format *Format
	indent int
}

/* encodes object as yaml string */
var chomper = regexp.MustCompile("\n+$")

func encodeOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
	preferences := expressionNode.Operation.Preferences.(encoderPreferences)
	var results = list.New()

	hasOnlyOneNewLine := regexp.MustCompile("[^\n].*\n$")
	endWithNewLine := regexp.MustCompile(".*\n$")

	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		candidate := el.Value.(*CandidateNode)
		stringValue, err := encodeToString(candidate, preferences)

		if err != nil {
			return Context{}, err
		}

		// remove trailing newlines if needed.
		// check if we originally decoded this path, and the original thing had a single line.
		originalList := context.GetVariable("decoded: " + candidate.GetKey())
		if originalList != nil && originalList.Len() > 0 && hasOnlyOneNewLine.MatchString(stringValue) {

			original := originalList.Front().Value.(*CandidateNode)
			// original block did not have a newline at the end, get rid of this one too
			if !endWithNewLine.MatchString(original.Value) {
				stringValue = chomper.ReplaceAllString(stringValue, "")
			}
		}

		// dont print a newline when printing json on a single line.
		if (preferences.format == JSONFormat && preferences.indent == 0) ||
			preferences.format == CSVFormat ||
			preferences.format == TSVFormat {
			stringValue = chomper.ReplaceAllString(stringValue, "")
		}

		results.PushBack(candidate.CreateReplacement(ScalarNode, "!!str", stringValue))
	}
	return context.ChildContext(results), nil
}

type decoderPreferences struct {
	format *Format
}

/* takes a string and decodes it back into an object */
func decodeOperator(_ *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {

	preferences := expressionNode.Operation.Preferences.(decoderPreferences)

	decoder := preferences.format.DecoderFactory()
	if decoder == nil {
		return Context{}, errors.New("no support for input format")
	}

	var results = list.New()
	for el := context.MatchingNodes.Front(); el != nil; el = el.Next() {
		candidate := el.Value.(*CandidateNode)

		context.SetVariable("decoded: "+candidate.GetKey(), candidate.AsList())

		log.Debugf("got: [%v]", candidate.Value)

		err := decoder.Init(strings.NewReader(candidate.Value))
		if err != nil {
			return Context{}, err
		}

		node, errorReading := decoder.Decode()
		if errorReading != nil {
			return Context{}, errorReading
		}
		node.Key = candidate.Key
		node.Parent = candidate.Parent

		results.PushBack(node)
	}
	return context.ChildContext(results), nil
}