package luar

import (
	"reflect"

	"github.com/yuin/gopher-lua"
)

func checkPtr(L *lua.LState, idx int) reflect.Value {
	ud := L.CheckUserData(idx)
	ref := reflect.ValueOf(ud.Value)
	if ref.Kind() != reflect.Ptr {
		L.ArgError(idx, "expecting ptr")
	}
	return ref
}

func ptrIndex(L *lua.LState) int {
	ref := checkPtr(L, 1)
	deref := ref.Elem()
	if deref.Kind() != reflect.Struct {
		L.RaiseError("cannot index non-struct pointer")
	}
	refType := ref.Type()

	// Check for method
	key := L.OptString(2, "")
	exKey := getExportedName(key)
	if key != "" {
		if method, ok := refType.MethodByName(exKey); ok {
			L.Push(New(L, method.Func.Interface()))
			return 1
		}
	}

	// Check for field
	if field := deref.FieldByName(exKey); field.IsValid() {
		if !field.CanInterface() {
			L.RaiseError("cannot interface field " + exKey)
		}
		if val := New(L, field.Interface()); val != nil {
			L.Push(val)
			return 1
		}
		L.RaiseError("could not convert field " + exKey)
	}

	if ret := metaFunction(L, luarIndexFunc, ref); ret >= 0 {
		return ret
	}
	return 0
}

func ptrNewIndex(L *lua.LState) int {
	ref := checkPtr(L, 1)
	deref := ref.Elem()

	if deref.Kind() != reflect.Struct {
		L.RaiseError("cannot new index non-struct pointer")
	}

	key := L.OptString(2, "")
	value := L.CheckAny(3)

	if key == "" {
		if ret := metaFunction(L, luarNewIndexFunc, ref); ret >= 0 {
			return ret
		}
		L.TypeError(2, lua.LTString)
		return 0
	}

	exKey := getExportedName(key)

	field := deref.FieldByName(exKey)
	if !field.IsValid() {
		if ret := metaFunction(L, luarNewIndexFunc, ref); ret >= 0 {
			return ret
		}
		L.ArgError(2, "unknown field "+exKey)
	}
	if !field.CanSet() {
		L.ArgError(2, "cannot set field "+exKey)
	}
	field.Set(lValueToReflect(value, field.Type()))

	return 0
}

func ptrPow(L *lua.LState) int {
	ref := checkPtr(L, 1)
	val := L.CheckAny(2)

	if ref.IsNil() {
		L.RaiseError("cannot dereference nil pointer")
	}
	elem := ref.Elem()
	if !elem.CanSet() {
		L.RaiseError("unable to set pointer value")
	}
	value := lValueToReflect(val, elem.Type())
	elem.Set(value)
	return 1
}

func ptrCall(L *lua.LState) int {
	ref := checkPtr(L, 1)
	if ret := metaFunction(L, luarCallFunc, ref); ret >= 0 {
		return ret
	}
	L.RaiseError("attempt to call a non-function object")
	return 0
}

func ptrUnm(L *lua.LState) int {
	ref := checkPtr(L, 1)
	elem := ref.Elem()
	if !elem.CanInterface() {
		L.RaiseError("cannot interface pointer type " + elem.String())
	}
	L.Push(New(L, elem.Interface()))
	return 1
}

func ptrEq(L *lua.LState) int {
	ptr1 := checkPtr(L, 1)
	ptr2 := checkPtr(L, 2)
	L.Push(lua.LBool(ptr1 == ptr2))
	return 1
}
