package transformers

import (
	"fmt"
	"os"
	"regexp"
	"strings"

	"github.com/johnkerl/miller/v6/pkg/cli"
	"github.com/johnkerl/miller/v6/pkg/lib"
	"github.com/johnkerl/miller/v6/pkg/types"
)

const verbNameRename = "rename"

var renameOptions = []OptionSpec{
	{
		Flag: "-r",
		Type: "bool",
		Desc: "Treat old field names as regular expressions. \"ab\", \"a.*b\" will match any field name containing the substring \"ab\" or matching \"a.*b\", respectively; anchors of the form \"^ab$\", \"^a.*b$\" may be used. New field names may be plain strings, or may contain capture groups of the form \"\\1\" through \"\\9\". Wrapping the regex in double quotes is optional, but is required if you wish to follow it with 'i' to indicate case-insensitivity.",
	},
	{
		Flag: "-g",
		Type: "bool",
		Desc: "Do global replacement within each field name rather than first-match replacement.",
	},
}

var RenameSetup = TransformerSetup{
	Verb:         verbNameRename,
	UsageFunc:    transformerRenameUsage,
	ParseCLIFunc: transformerRenameParseCLI,
	IgnoresInput: false,
	Options:      renameOptions,
}

func transformerRenameUsage(
	o *os.File,
) {
	exeName := "mlr"
	verb := verbNameRename

	fmt.Fprintf(o, "Usage: %s %s [options] {old1,new1,old2,new2,...}\n", "mlr", verbNameRename)
	fmt.Fprintf(o, "Renames specified fields.\n")
	WriteVerbOptions(o, renameOptions)
	fmt.Fprintf(o, "Examples:\n")
	fmt.Fprintf(o, "%s %s old_name,new_name\n", exeName, verb)
	fmt.Fprintf(o, "%s %s old_name_1,new_name_1,old_name_2,new_name_2\n", exeName, verb)
	fmt.Fprintf(o, "%s %s -r 'Date_[0-9]+,Date'  Rename all such fields to be \"Date\"\n", exeName, verb)
	fmt.Fprintf(o, "%s %s -r '\"Date_[0-9]+\",Date' Same\n", exeName, verb)
	fmt.Fprintf(o, "%s %s -r 'Date_([0-9]+).*,\\1' Rename all such fields to be of the form 20151015\n", exeName, verb)
	fmt.Fprintf(o, "%s %s -r '\"name\"i,Name'       Rename \"name\", \"Name\", \"NAME\", etc. to \"Name\"\n", exeName, verb)
}

func transformerRenameParseCLI(
	pargi *int,
	argc int,
	args []string,
	_ *cli.TOptions,
	doConstruct bool, // false for first pass of CLI-parse, true for second pass
) (RecordTransformer, error) {

	// Skip the verb name from the current spot in the mlr command line
	argi := *pargi
	argi++

	doRegexes := false
	doGsub := false

	for argi < argc /* variable increment: 1 or 2 depending on flag */ {
		opt := args[argi]
		if !strings.HasPrefix(opt, "-") {
			break // No more flag options to process
		}
		if args[argi] == "--" {
			break // All transformers must do this so main-flags can follow verb-flags
		}
		argi++

		switch opt {
		case "-h", "--help":
			transformerRenameUsage(os.Stdout)
			return nil, cli.ErrHelpRequested

		case "-r":
			doRegexes = true

		case "-g":
			doGsub = true

		default:
			return nil, cli.VerbErrorf(verbNameRename, "option \"%s\" not recognized", opt)
		}
	}

	if doGsub {
		doRegexes = true
	}

	// Get the rename field names from the command line
	if argi >= argc {
		return nil, cli.VerbErrorf(verbNameRename, "old1,new1,old2,new2,... required")
	}
	names := lib.SplitString(args[argi], ",")
	if len(names)%2 != 0 {
		return nil, cli.VerbErrorf(verbNameRename, "names string must have even length")
	}
	argi++

	*pargi = argi
	if !doConstruct { // All transformers must do this for main command-line parsing
		return nil, nil
	}

	transformer, err := NewTransformerRename(
		names,
		doRegexes,
		doGsub,
	)
	if err != nil {
		return nil, err
	}

	return transformer, nil
}

type tRegexAndReplacement struct {
	regex                    *regexp.Regexp
	replacement              string
	replacementCaptureMatrix [][]int // TODO: comment
}

type TransformerRename struct {
	oldToNewNames          *lib.OrderedMap[string]
	regexesAndReplacements []*tRegexAndReplacement
	doGsub                 bool
	recordTransformerFunc  RecordTransformerFunc
}

func NewTransformerRename(
	names []string,
	doRegexes bool,
	doGsub bool,
) (*TransformerRename, error) {
	if len(names)%2 != 0 {
		return nil, fmt.Errorf("mlr rename: names string must have even length")
	}

	oldToNewNames := lib.NewOrderedMap[string]()
	n := len(names)
	for i := 0; i < n; i += 2 {
		oldName := names[i]
		newName := names[i+1]
		oldToNewNames.Put(oldName, newName)
	}

	tr := &TransformerRename{}

	if !doRegexes {
		tr.oldToNewNames = oldToNewNames
		tr.doGsub = false
		tr.recordTransformerFunc = tr.transformWithoutRegexes
	} else {
		tr.regexesAndReplacements = []*tRegexAndReplacement{}
		for pe := oldToNewNames.Head; pe != nil; pe = pe.Next {
			regexString := pe.Key
			regex, err := lib.CompileMillerRegex(regexString)
			if err != nil {
				return nil, err
			}
			replacement := pe.Value
			_, replacementCaptureMatrix := lib.ReplacementHasCaptures(replacement)
			regexAndReplacement := tRegexAndReplacement{
				regex:                    regex,
				replacement:              replacement,
				replacementCaptureMatrix: replacementCaptureMatrix,
			}
			tr.regexesAndReplacements = append(tr.regexesAndReplacements, &regexAndReplacement)
		}
		tr.doGsub = doGsub
		tr.recordTransformerFunc = tr.transformWithRegexes
	}

	return tr, nil
}

func (tr *TransformerRename) Transform(
	inrecAndContext *types.RecordAndContext,
	outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext
	inputDownstreamDoneChannel <-chan bool,
	outputDownstreamDoneChannel chan<- bool,
) error {
	HandleDefaultDownstreamDone(inputDownstreamDoneChannel, outputDownstreamDoneChannel)
	return tr.recordTransformerFunc(inrecAndContext, outputRecordsAndContexts, inputDownstreamDoneChannel, outputDownstreamDoneChannel)
}

func (tr *TransformerRename) transformWithoutRegexes(
	inrecAndContext *types.RecordAndContext,
	outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext
	inputDownstreamDoneChannel <-chan bool,
	outputDownstreamDoneChannel chan<- bool,
) error {
	if !inrecAndContext.EndOfStream {
		inrec := inrecAndContext.Record

		for pe := inrec.Head; pe != nil; pe = pe.Next {
			if tr.oldToNewNames.Has(pe.Key) {
				newName := tr.oldToNewNames.Get(pe.Key)
				inrec.Rename(pe.Key, newName)
			}

		}
	}
	*outputRecordsAndContexts = append(*outputRecordsAndContexts, inrecAndContext) // including end-of-stream marker
	return nil
}

func (tr *TransformerRename) transformWithRegexes(
	inrecAndContext *types.RecordAndContext,
	outputRecordsAndContexts *[]*types.RecordAndContext, // list of *types.RecordAndContext
	inputDownstreamDoneChannel <-chan bool,
	outputDownstreamDoneChannel chan<- bool,
) error {
	if !inrecAndContext.EndOfStream {
		inrec := inrecAndContext.Record

		for _, regexAndReplacement := range tr.regexesAndReplacements {
			regex := regexAndReplacement.regex
			replacement := regexAndReplacement.replacement
			replacementCaptureMatrix := regexAndReplacement.replacementCaptureMatrix

			for pe := inrec.Head; pe != nil; pe = pe.Next {
				oldName := pe.Key
				if tr.doGsub {
					newName := regex.ReplaceAllString(oldName, replacement)
					if newName != oldName {
						inrec.Rename(oldName, newName)
					}
				} else {
					newName := lib.RegexCompiledSub(oldName, regex, replacement, replacementCaptureMatrix)
					if newName != oldName {
						inrec.Rename(oldName, newName)
					}
				}
			}
		}

		*outputRecordsAndContexts = append(*outputRecordsAndContexts, inrecAndContext)
	} else {
		*outputRecordsAndContexts = append(*outputRecordsAndContexts, inrecAndContext) // including end-of-stream marker
	}
	return nil
}
