package muesli

import "fmt"
import "log"
import "runtime"
import "strings"
import "sort"

type LogTracer struct {
}

func (t LogTracer) Trace(vm VM, fmt string, args ... interface{}) bool {
	args = append([]interface{}{vm.BackTrace()}, args...)	
	log.Printf("%s: " + fmt,  args...)
	return false
}


type FmtTracer struct {
}

func (t FmtTracer) Trace(vm VM, fm string, args ... interface{}) bool {
	args = append([]interface{}{vm.BackTrace()}, args...)	
	fmt.Printf("%s: " + fm + "\n",  args...)
	return false
}

func printf(vm *VM, args ...Value) []Value {
	var format string
	rest, err := ParseArgs(args, &format)
	if err != nil {
		return Fail(err)
	}
	extra := ListFromList(rest)
	fmt.Printf(format, extra...)
	return None()
}

func println(vm *VM, args ...Value) []Value {
	var msg string
	_, err := ParseArgs(args, &msg)
	if err != nil {
		return Fail(err)
	} else {
		fmt.Println(msg)
	}
	return None()
}

func p(vm *VM, args ...Value) []Value {	
	for _, arg := range args {
		fmt.Printf("%v\n", arg)
	}	
	return None()
}

func trace(vm *VM, args ...Value) []Value {
	var b bool = true
	fmt.Printf("command trace: %v\n", args)
	_, err := ParseArgs(args, &b)
	if err != nil {
		fmt.Printf("Error: %s\n", err.Error())
		return Fail(err)
	}
	fmt.Printf("command trace: bool: %v\n", b)
	if b {
		vm.Tracer = FmtTracer{}
	} else {
		vm.Tracer = nil
	}
	return Ok(BoolValue(b))
}

func sumi(vm *VM, args ...Value) []Value {
	slice, err := ParseArgsToIntSlice(args)
	if err != nil {
		fmt.Printf("Error: %s\n", err.Error())
		return Fail(err)
	}
	res := int(0)
	for _, val := range slice {
		res += val
	} 
	return IntOk(res)
}

func sumf(vm *VM, args ...Value) []Value {
	slice, err := ParseArgsToFloat64Slice(args)
	if err != nil {
		fmt.Printf("Error: %s\n", err.Error())
		return Fail(err)
	}
	res := float64(0)
	for _, val := range slice {
		res += val
	} 
	return FloatOk(res)
}

func addi(vm *VM, args ...Value) []Value {
	var v1, v2 int
	ParseArgs(args, &v1, &v2)
    return IntOk(v1 + v2)
}


func addf(vm *VM, args ...Value) []Value {
	var v1, v2 float64
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return FloatOk(v1 + v2)
}

func subi(vm *VM, args ...Value) []Value {
	var v1, v2 int
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return IntOk(v1 - v2)
}


func subf(vm *VM, args ...Value) []Value {
	var v1, v2 float64
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return FloatOk(v1 - v2)
}

func muli(vm *VM, args ...Value) []Value {
	var v1, v2 int
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return IntOk(v1 * v2)
}


func mulf(vm *VM, args ...Value) []Value {
	var v1, v2 float64
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return FloatOk(v1 * v2)
}


func divi(vm *VM, args ...Value) []Value {
	var v1, v2 int
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return IntOk(v1 / v2)
}

func divf(vm *VM, args ...Value) []Value {
	var v1, v2 float64
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return FloatOk(v1 / v2)
}

func andb(vm * VM, args ...Value) []Value {
	var v1, v2 bool
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return BoolOk(v1 && v2)
}

func orb(vm * VM, args ...Value) []Value {
	var v1, v2 bool
	_, err := ParseArgs(args, &v1, &v2)
	if err != nil {
		return Fail(err)
	}	
	return BoolOk(v1 || v2)
}

func val(vm *VM, args ...Value) []Value {
	if len(args) < 1 {
		return []Value{NewErrorValuef("val requres at least one argument.")}
	}
	return args
}

func builtin_return(vm *VM, args ...Value) []Value {	
	vm.Frame.returned = true
	vm.Frame.results = args
	vm.Trace("Returning... %v", vm.Frame)
	return args
}

func to(vm *VM, args ...Value) []Value {
	var name string	
	rest, err := ParseArgs(args, &name)
	if err != nil {
		return Fail(err)
	}
	
	if len(rest) < 1 {
		return Fail(NewErrorValuef("Need at least 2 arguments: %v", args))
	}
	
	last := rest[len(rest)-1]
	block, isBlock := last.(*BlockValue)
	if ! isBlock {
		return Fail(NewErrorValuef("Not a block: %v", last))
	}
	param := rest[0:len(rest)-1]
	sign, err := NewSignatureWithNames(param...)
	if err != nil {
		return Fail(NewErrorValuef("Not a word value: %v", name))
	}
	
	// To must register one level up.
	return Ok(vm.RegisterDefined(name, sign, block, 1))
}

func cover(vm *VM, args ...Value) []Value {
	var name, target string
	rest, err := ParseArgs(args, &name, &target)
	if err != nil {
		return Fail(err)
	}
	types := []TypeValue{}
	for i, arg := range rest {
		if typ, ok := arg.(TypeValue) ; ok {
			types = append(types, typ)
		} else {
			return Fail(NewErrorValuef("Argument %d: not a type: %v %s", i+2, arg, arg.String()))
		}
	}	
	// Overload must be defined one scope up.
	err = vm.AddOverload(name, target, 1, types...)
	
	if err != nil {
		return Fail(err)
	}	
	return Ok()
}

func types(vm *VM, args ...Value) []Value {
	result := []Value{}
	for i, arg := range args {
		typ := arg.Type() 
		result = append(result, typ)
		fmt.Printf("Type %d: %s\n", i, typ.String())
	}
	return Ok(NewListValue(result...))
}

func set(vm *VM, val ...Value) []Value {
	if len(val) < 2 { 
		return Fail(fmt.Errorf("set needs at least 2 arguments: received %v", val)) 
	}
	target := val[0].String() 
	value := val[1]
	if target == "(" || target == "$" {
		if len(val) < 3 {
			return Fail(fmt.Errorf("indirect get needs results: received %v", val)) 
		}
		target = val[1].String() 
		value = val[2]
	}	
	vm.RegisterUp(target, value, 1)
	return Ok(value)
}

func get(vm *VM, val ...Value) []Value {
	if len(val) < 1 {
		return Fail(fmt.Errorf("get needs at least 1 argument")) 
	}
	target := val[0].String() 
	if target == "(" || target == "$" {
		if len(val) < 2 {
			return Fail(fmt.Errorf("indirect get needs results: received %v", val)) 
		}
		target = val[1].String()
	}	
	return Ok(vm.Lookup(target))
}

func fetchl(vm *VM, args ...Value) []Value {
	var index int
	var list *ListValue
	_, err := ParseArgs(args, &list, &index)
	if err != nil {
		return Fail(err)
	}
    if (index < 0) || (index >= len(list.List)) {
        return Fail(fmt.Errorf("index out of range: %d<->%d", index, len(list.List)))
    } 
	return Ok(list.List[index])	
}

func storel(vm *VM, args ...Value) []Value {
	var index int
	var list *ListValue
	rest, err := ParseArgs(args, &list, &index)
	if err != nil {
		return Fail(err)
	}
	if len(rest) < 1 {
		return Fail(fmt.Errorf("fetch: need 3 arguments")) 
	}
    if (index < 0) || (index >= len(list.List)) {
        return Fail(fmt.Errorf("index out of range: %d<->%d", index, len(list.List)))
    }
	list.List[index] = rest[0]
	return Ok(list.List[index])	
}


func fetchm(vm *VM, args ...Value) []Value {
	var index Value
	var hmap *MapValue
	_, err := ParseArgs(args, &hmap, &index)
	if err != nil {
		return Fail(err)
	}
	return Ok(hmap.Map[index])	
}

func storem(vm *VM, args ...Value) []Value {
	var index Value
	var hmap *MapValue
	rest, err := ParseArgs(args, &hmap, &index)
	if err != nil {
		return Fail(err)
	}
	if len (rest) < 1 {
		return Fail(fmt.Errorf("fetch: need 3 arguments")) 
	}	
	hmap.Map[index] = rest[0]
	return Ok(hmap.Map[index])	
}

func newmap(vm *VM, args ...Value) []Value {
	result := make(map[Value] Value)
	for i := 1; i < len(args) ; i+=2 {
		result[args[i-1]] = args[i]
	}
	return Ok(NewMapValue(result))
}



func help(vm *VM, val ...Value) [] Value {
	if len(val) < 1 {
		fmt.Printf("help <callable> will display help on the callable\n\nThe following commands are available:\n")
		helpers := vm.DefinedHelpers()
		sort.SliceStable(helpers, func(i, j int) bool {
			return helpers[i].HelperName() < helpers[j].HelperName()
		})
		for _, helper := range helpers {
			fl := strings.SplitN(helper.Help(),"\n", 2)
			fmt.Printf("%s %s: \n", helper.HelperName(), fl[0])
		}
		return Ok()
	}
	targetName := val[0].String() 	
	target := vm.Lookup(targetName)
	if target == nil {
		fmt.Printf("help: %s not found.\n", targetName)
	}
	
	if helper, isHelper := target.(Helper) ; isHelper {
		help := helper.Help()
		if call, isCall := target.(Callable); isCall { 
			fmt.Printf("%s %s: %s.\n", targetName, call.Signature().String(), help)
		} else {
			fmt.Printf("%s: %s.\n", targetName, help)
		}
		return Ok(StringValue(help))
	}
	
	return Ok()
}

func explain(vm *VM, val ...Value) [] Value {
	var target, help string
	_, err := ParseArgs(val, &target, &help)
	if err != nil {
		return Fail(err)
	}	
	err = vm.SetHelp(target, help)
	if err != nil {
		return Fail(err)
	}		
	return Ok(StringValue(help))
}

func exit(vm *VM, val ...Value) [] Value {
	var code int
	_, err := ParseArgs(val, &code)
	if err != nil {
		runtime.Goexit()
	}	
	vm.ExitStatus = code
	runtime.Goexit()
	return Ok()
}

type Door struct {
	name string
	locked bool
	opened bool
}

// Implement callable ... 
func (door * Door) Position() *Position {
	return &Position{"builtin.go", 427, 8}
}


func (door * Door) Call(vm *VM, args...Value) []Value {
	return Redispatch(door, vm, args...)
}

var doorSignature = NewSignature(WordType) 

func (door * Door) Signature() Signature {
    return doorSignature
}

var _ Callable = &Door{}

const DoorType = TypeValue("Door")

func (door * Door) String() string {
	return fmt.Sprintf("Door: %s %v %v", door.name, door.locked, door.opened)
}


func (*Door) Type() TypeValue {
	return DoorType
}

func (from * Door) Convert(to interface{}) error {
	switch toPtr := to.(type) {
		case **Door:
			(*toPtr) = from
		case *Door:
			(*toPtr) = *from
		case *Value:
			(*toPtr) = from
		default:
			return NewErrorValuef("Cannot convert DoorValue value %v to %v", from, to)
	}
	return nil
}

func door(vm *VM, val ...Value) [] Value {
	var name string
	var locked bool
	_, err := ParseOptArgs(val, 1, &name, &locked)
	if err != nil {
		return Fail(err)
	}
	return Ok(&Door{name: name, locked: locked, opened:false})
}

func (door * Door) Open() {
	door.opened = true
}

func openDoor(vm *VM, val ...Value) [] Value {
	var door *Door
	_, err := ParseArgs(val, &door)
	if err != nil {
		return Fail(err)
	}
	if door == nil {
		return Fail(NewErrorValuef("Door may not be nil."))
	}	
	
	fmt.Printf("Door: %v %s", door, door)
	door.Open()
	return Ok(door)
}

func (vm *VM) RegisterBuiltins() {
	vm.RegisterBuiltinWithHelp("addi", addi, `adds two integers together`).Takes(IntType, IntType).Returns(BoolType)
	vm.RegisterBuiltinWithHelp("addf", addf, `adds two floats together`).Takes(IntType, IntType).Returns(IntType)
	vm.RegisterBuiltinWithHelp("andb", andb, `returns true if all it's arguments are true`).Takes(BoolType, BoolType).Returns(BoolType)
	vm.RegisterBuiltin("cover", cover)
	vm.RegisterBuiltin("fetchl", fetchl)
	vm.RegisterBuiltin("fetchm", fetchm)
	vm.RegisterBuiltin("sumi", sumi).Takes(IntType, IntType).Returns(IntType)
	vm.RegisterBuiltin("sumf", sumf).Takes(FloatType, FloatType).Returns(FloatType)
	vm.RegisterBuiltin("subi", subi).Takes(IntType, IntType).Returns(IntType)
	vm.RegisterBuiltin("subf", subf).Takes(FloatType, FloatType).Returns(FloatType)
	vm.RegisterBuiltin("divi", divi).Takes(IntType, IntType).Returns(IntType)
	vm.RegisterBuiltin("divf", divf).Takes(FloatType, FloatType).Returns(FloatType)
	vm.RegisterBuiltin("map", newmap)
	vm.RegisterBuiltin("muli", muli).Takes(IntType, IntType).Returns(IntType)
	vm.RegisterBuiltin("mulf", mulf).Takes(FloatType, FloatType).Returns(FloatType)
	vm.RegisterBuiltinWithHelp("orb", orb, `[Bool Bool] -> Bool: returns true if on of it's arguments is true`)
	// vm.RegisterCover("add")
	vm.RegisterBuiltin("p", p)
	vm.RegisterBuiltin("println", println)
	vm.RegisterBuiltin("printf", printf)	
	vm.RegisterBuiltin("storel", storel)
	vm.RegisterBuiltin("storem", storem)
	vm.RegisterBuiltin("trace", trace)
	vm.RegisterBuiltin("to", to)
	vm.RegisterBuiltin("types", types)
	vm.RegisterBuiltin("return", builtin_return)
	vm.RegisterBuiltin("val", val)
	vm.RegisterBuiltin("set", set)
	vm.RegisterBuiltin("get", get)
	vm.RegisterBuiltin("help", help)
	vm.RegisterBuiltin("explain", explain)
	vm.RegisterBuiltin("exit", exit)
	vm.RegisterBuiltin("door", door)
	vm.RegisterBuiltin("openDoor", openDoor)
    vm.Register("Door", DoorType)
	vm.AddOverloads("open", Over("openDoor", 0, DoorType))
	
	vm.AddOverloads("mul", 
			Over("mulf", 0, FloatType, FloatType),
			Over("muli", 0, IntType, IntType),
			Over("mulf", 0, FloatType, IntType),
			Over("mulf", 0, IntType, FloatType))
	
	vm.AddOverloads("add", 
			Over("addf", 0, FloatType, FloatType),
			Over("addi", 0, IntType, IntType),
			Over("addf", 0, FloatType, IntType),
			Over("addf", 0, IntType, FloatType))
	
			
	vm.SetHelp("mul", "	 Num: Multiplies two numbers. Cover for muli and mulf.")	
	vm.AddOverloads("fetch", 
			Over("fetchl", 0, ListType, IntType),
			Over("fetchm", 0, MapType, AnyType),
		)			
	vm.SetHelp("fetch", " storage, index. Fetch value in storage at given index.")
	/*
	vm.AddOverloads("store", 
			Over("storel", ListType, IntType, AnyType),
			Over("storem", MapType, AnyType, AnyType),
		)
	vm.SetHelp("store", " storage, index, value. Store value in storage at given index.")
	*/
}