package tpp

import (
	"github.com/d5/tengo/v2"
	"fmt"
	"context"
	"bytes"
)

type Script = tengo.Script
type Compiled = tengo.Compiled

type Tengo struct {
	head string
	compiledScripts map[string] *Compiled
	// Functions to modify
	// preprocessor for
	// more specific purposes.
	preCode func(context.Context) []byte
	postCode func(context.Context) []byte
	preCompile func(context.Context, *Script)
}

// Returns the new Tengo preprocessor
// with 
func NewTengo() *Tengo {
	ret := &Tengo{}
	return ret
}

func (tengo *Tengo) SetPreCode(fn func(context.Context) []byte) *Tengo {
	tengo.preCode = fn
	return tengo
}

func (tengo *Tengo) SetPostCode(fn func(context.Context) []byte) *Tengo {
	tengo.postCode = fn
	return tengo
}

func (tengo *Tengo) SetPreCompile(fn func(context.Context, *Script)) *Tengo {
	tengo.preCompile = fn
	return tengo
}

// Simple Evaler implementation for the Tengo language
func (pp *Tengo) Eval(
	ctx context.Context,
	recompile bool,
	filePath string,
	preCodes [][]byte,
	codes [][]byte,
) ([][]byte, error) {
	var fullCodeBuf bytes.Buffer
	const retHead = `
		context := {}
		__ret_one__ := bytes("")
		pp := immutable({
			filepath: __filepath__,
			printf : func(format, ...vals) {
				__ret_one__ += __sprintf__(format, vals...)
			},

			print : func(...vals) {
				__ret_one__ += __sprint__(vals...)
			},
			println : func(...vals) {
				__ret_one__ += __sprintln__(vals...)
			},
			write_raw : func(bts) {
				__ret_one__ += bytes(bts)
			}
		})
	`

	const retSeparator = `
		__separate__(__ret_one__)
		__ret_one__ = ""
	`
	rets := [][]byte{}


	fmt.Fprint(&fullCodeBuf, retHead)
	for _, preCode := range preCodes {
		fmt.Fprintln(&fullCodeBuf, "\n" + string(preCode) + retSeparator)
	}
	if pp.preCode != nil {
		fullCodeBuf.Write(pp.preCode(ctx))
	}
	for _, code := range codes {
		fmt.Fprintln(&fullCodeBuf, "\n" + string(code) + retSeparator)
	}
	if pp.postCode != nil {
		fullCodeBuf.Write(pp.postCode(ctx))
	}

	script := tengo.NewScript(fullCodeBuf.Bytes())
	if pp.preCompile != nil {
		pp.preCompile(ctx, script)
	}

	script.Add("__filepath__", filePath)
	script.Add("__sprintf__", &tengo.UserFunction{
		Value: func(args ...tengo.Object) (tengo.Object, error){
			if len(args) < 1 {
				return nil, tengo.ErrWrongNumArguments
			}
			format, ok := tengo.ToString(args[0])
			if !ok {
				return nil, tengo.ErrInvalidArgumentType{
					Expected: "string",
				}
			}
			gargs := make([]any, len(args) - 1)
			for i := range gargs {
				gargs[i] = tengo.ToInterface(args[i+1])
				//fmt.Printf("shit: %q\n", gargs[i])
			}
			str := fmt.Sprintf(format, gargs...)
			return tengo.FromInterface([]byte(str))
		},
	})
	script.Add("__sprint__", &tengo.UserFunction{
		Value: func(args ...tengo.Object) (tengo.Object, error){
			gargs := make([]any, len(args))
			for i := range gargs {
				gargs[i] = tengo.ToInterface(args[i])
			}
			str := fmt.Sprint(gargs...)
			return tengo.FromInterface([]byte(str))
		},
	})
	script.Add("__sprintln__", &tengo.UserFunction{
		Value: func(args ...tengo.Object) (tengo.Object, error){
			gargs := make([]any, len(args))
			for i := range gargs {
				gargs[i] = tengo.ToInterface(args[i])
			}
			str := fmt.Sprintln(gargs...)
			return tengo.FromInterface([]byte(str))
		},
	})
	script.Add("__separate__", &tengo.UserFunction{
		Value: func(args ...tengo.Object) (tengo.Object, error){
			if len(args) < 1 {
				return nil, tengo.ErrWrongNumArguments
			}
			bts, ok := tengo.ToByteSlice(args[0])
			if !ok {
				return nil, tengo.ErrInvalidArgumentType{
				}
			}
			rets = append(rets, bts)
			return nil, nil
		},
	})

	_, err := script.RunContext(ctx)
	if err != nil {
		return nil, err
	}

	return rets, nil
}