// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
// All rights reserved.

// commands.go
package main

import (
	"fmt"
	"io"
	"os"
	"strings"

	"git.portale-stac.it/go-pkg/expr"
	"git.portale-stac.it/go-pkg/expr/kern"
	"git.portale-stac.it/go-pkg/utils"
)

type commandFunction func(opt *Options, ctx kern.ExprContext, args []string) (err error)

type command struct {
	name        string
	description string
	code        commandFunction
}

func (cmd *command) exec(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	return cmd.code(opt, ctx, args)
}

type commandHandler struct {
	cmdIndex []string
	commands map[string]*command
}

func NewCommandHandler() *commandHandler {
	return &commandHandler{
		cmdIndex: make([]string, 0, 20),
		commands: make(map[string]*command),
	}
}

// func (h *commandHandler) setContext(ctx expr.ExprContext) {
// 	h.ctx = ctx
// }

func (h *commandHandler) add(name, description string, f commandFunction) {
	h.cmdIndex = append(h.cmdIndex, name)
	h.commands[name] = &command{name: name, description: description, code: f}
}

func (h *commandHandler) get(cmdLine string) (cmd *command, args []string) {
	if len(cmdLine) > 0 {
		tokens := strings.Split(cmdLine, " ")
		name := tokens[0]
		args = make([]string, 0, len(tokens)-1)
		if cmd = h.commands[name]; cmd != nil && len(tokens) > 1 {
			for _, tk := range tokens[1:] {
				if tk != "" {
					args = append(args, tk)
				}
			}
		}
	}
	return
}

// ------
var cmdHandler *commandHandler

func (h *commandHandler) help() {
	fmt.Fprintln(os.Stderr, `--- REPL commands:`)
	for _, name := range h.cmdIndex {
		cmd := h.commands[name]
		fmt.Fprintf(os.Stderr, "%12s -- %s\n", cmd.name, cmd.description)
	}
	fmt.Fprint(os.Stderr, `
--- Command line options:
    -b <builtin>       Import builtin modules.
                       <builtin> can be a list of module names or a glob-pattern.
                       Use the special value 'all' or the pattern '*' to import all modules.
    -B, --list-builtins  List all builtin module names
    -e <expression>    Evaluate <expression> instead of standard-input
    -i                 Force REPL operation when all -e occurences have been processed
    -h, --help, help   Show this help menu
    -m, --modules      List all builtin modules
    --noout            Disable printing of expression results
    -p                 Print prefix form
    -t                 Print tree form
    -v, --version      Show program version
`)

}

// --------

func cmdExit(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	return io.EOF
}

func cmdHelp(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	cmdHandler.help()
	return
}

func cmdMultiLine(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	if opt.formOpt&kern.MultiLine == 0 {
		opt.formOpt |= kern.MultiLine
	} else {
		opt.formOpt &= ^kern.MultiLine
	}
	return
}

func cmdTty(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	if opt.formOpt&kern.TTY == 0 {
		opt.formOpt |= kern.TTY
	} else {
		opt.formOpt &= ^kern.TTY
	}
	return
}

func execFile(opt *Options, ctx kern.ExprContext, fileName string) (err error) {
	var fh *os.File
	if fh, err = os.Open(fileName); err == nil {
		goBatch(opt, ctx, fh, false)
		fh.Close()
	}
	return
}

func cmdSource(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	var target string
	for _, arg := range args {
		if len(arg) == 0 {
			continue
		}
		// TODO migliorare questa parte: eventualmente valutare un'espressione
		if target, err = checkStringLiteral(arg); err != nil {
			break
		}

		if target, err = utils.ExpandPath(target); err != nil {
			break
		}

		if isPattern(target) {
			var fileNames []string
			if fileNames, err = matchPathPattern(target); err == nil {
				for _, fileName := range fileNames {
					if err = execFile(opt, ctx, fileName); err != nil {
						break
					}
				}
			}
		} else {
			err = execFile(opt, ctx, target)
		}

		if err != nil {
			break
		}
	}
	return
}

func cmdModules(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	expr.IterateBuiltinModules(func(name, description string, imported bool) bool {
		var check rune = ' '
		if imported {
			check = '*'
		}
		fmt.Printf("%c %20q: %s\n", check, name, description)
		return true
	})
	return
}

func cmdBase(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	if len(args) == 0 {
		fmt.Println(opt.base)
	} else if args[0] == "2" {
		opt.baseVerb = "0b%b"
		opt.base = 2
	} else if args[0] == "8" {
		opt.baseVerb = "0o%o"
		opt.base = 8
	} else if args[0] == "10" {
		opt.baseVerb = "%d"
		opt.base = 10
	} else if args[0] == "16" {
		opt.baseVerb = "0x%x"
		opt.base = 16
	} else {
		err = fmt.Errorf("invalid number base %s", args[0])
	}
	return
}

func cmdOutput(opt *Options, ctx kern.ExprContext, args []string) (err error) {
	var outputArg string
	if len(args) == 0 {
		outputArg = "status"
	} else {
		outputArg = strings.ToLower(args[0])
	}
	switch outputArg {
	case "on":
		opt.output = true
	case "off":
		opt.output = false
	case "status":
		if opt.output {
			fmt.Println("on")
		} else {
			fmt.Println("off")
		}
	default:
		err = fmt.Errorf("output: unknown option %q", outputArg)
	}
	return
}

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

func setupCommands() {
	cmdHandler = NewCommandHandler()
	cmdHandler.add("base", "Set the integer output base: 2, 8, 10, or 16", cmdBase)
	cmdHandler.add("exit", "Exit the program", cmdExit)
	cmdHandler.add("help", "Show command list", cmdHelp)
	cmdHandler.add("ml", "Enable/Disable multi-line output", cmdMultiLine)
	cmdHandler.add("mods", "List builtin modules", cmdModules)
	cmdHandler.add("output", "Enable/Disable printing expression results. Options 'on', 'off', 'status'", cmdOutput)
	cmdHandler.add("source", "Load a file as input", cmdSource)
	cmdHandler.add("tty", "Enable/Disable ansi output", cmdTty)
}
