package tengo

import (
	"fmt"

	"github.com/d5/tengo/compiler"
	"github.com/d5/tengo/objects"
	"github.com/d5/tengo/parser"
	"github.com/d5/tengo/scanner"
	"github.com/d5/tengo/vm"
)

type Variable struct {
	name  string
	value *objects.Object
}

func (v *Variable) Name() string {
	return v.name
}

func (v *Variable) Value() interface{} {
	return nil
}

type Script struct {
	variables map[string]*objects.Object
	input     []byte
}

func NewScript(input []byte) *Script {
	return &Script{
		variables: make(map[string]*objects.Object),
		input:     input,
	}
}

func (s *Script) Add(name string, value interface{}) error {
	obj, err := objects.FromValue(value)
	if err != nil {
		return err
	}

	s.variables[name] = &obj

	return nil
}

func (s *Script) Remove(name string) bool {
	if _, ok := s.variables[name]; !ok {
		return false
	}

	delete(s.variables, name)

	return true
}

func (s *Script) Compile() (*CompiledScript, error) {
	symbolTable, globals := s.prepCompile()

	fileSet := scanner.NewFileSet()

	p := parser.NewParser(fileSet.AddFile("", -1, len(s.input)), s.input, nil)
	file, err := p.ParseFile()
	if err != nil {
		return nil, fmt.Errorf("parse error: %s", err.Error())
	}

	c := compiler.NewCompiler(symbolTable, nil)
	if err := c.Compile(file); err != nil {
		return nil, err
	}

	return &CompiledScript{
		bytecode:    c.Bytecode(),
		symbolTable: symbolTable,
		globals:     globals,
	}, nil
}

func (s *Script) prepCompile() (symbolTable *compiler.SymbolTable, globals []*objects.Object) {
	var names []string
	for name := range s.variables {
		names = append(names, name)
	}

	symbolTable = compiler.NewSymbolTable()
	globals = make([]*objects.Object, len(names), len(names))

	for idx, name := range names {
		symbol := symbolTable.Define(name)
		if symbol.Index != idx {
			panic(fmt.Errorf("wrong symbol index: %d != %d", idx, symbol.Index))
		}

		globals[symbol.Index] = s.variables[name]
	}

	return
}

type CompiledScript struct {
	bytecode    *compiler.Bytecode
	symbolTable *compiler.SymbolTable
	globals     []*objects.Object
}

func (c *CompiledScript) Run() error {
	v := vm.NewVM(c.bytecode, c.globals)

	return v.Run()
}

func (c *CompiledScript) Update(name string, value interface{}) error {
	symbol, _, ok := c.symbolTable.Resolve(name)
	if !ok {
		return fmt.Errorf("name not found: %s", name)
	}

	updated, err := objects.FromValue(value)
	if err != nil {
		return err
	}

	c.globals[symbol.Index] = &updated

	return nil
}