package types

import (
	"fmt"
	"os"
	"regexp"
	"strings"
)

var Gogc bool
var Typesubs map[string]string

//super is a map recording which class is the parent of each other class
var super map[string]string

//wrapped is a map recording whether a given GoType is to be "wrapped" in a
//go struct.
var wrapped map[string]bool
var tdptr map[string]bool

func ShouldWrap(gt string) bool {
	return wrapped[gt]
}

func PtrShouldWrap(gt string) bool {
	return gt != "" && gt[0] == '*' && wrapped[gt[1:]]
}

func TypedefShouldWrap(gt string) bool {
	return tdptr[gt]
}

//goInterfaces records the names of top level Go interfaces.
var goInterfaces map[string]bool

func IsGoInterface(gt string) bool {
	return goInterfaces[gt]
}

func PtrIsGoInterface(gt string) bool {
	return gt != "" && gt[0] == '*' && goInterfaces[gt[1:]]
}

//TypeParameters maps, for each class, a TypedefName to a type, representing
//the Objective-C type parameters for that class
var TypeParameters map[string]map[string]string

//Typedefs maps from C types to the Type of a typedef with that name.
var typedefs map[string]*Type

func (t *Type) Typedef() *Type {
	return typedefs[t.CType()]
}

var (
	r_id           *regexp.Regexp
	r_instancename *regexp.Regexp
	r_instancetype *regexp.Regexp
)

func init() {
	super = make(map[string]string)
	wrapped = make(map[string]bool)
	tdptr = make(map[string]bool)
	goInterfaces = make(map[string]bool)
	TypeParameters = make(map[string]map[string]string)
	typedefs = make(map[string]*Type)

	r_id = regexp.MustCompile(`\bid\b`)
	r_instancename = regexp.MustCompile(`\binstancename\b`)
	r_instancetype = regexp.MustCompile(`\binstancetype\b`)
}

func Super(c string) string {
	return super[c]
}

func SetSuper(c, p string) {
	super[c] = p
}

func SetTypeParam(c, n, t string) {
	if TypeParameters[c] == nil {
		TypeParameters[c] = make(map[string]string)
	}
	TypeParameters[c][n] = t
}

func AddTypedef(n string, tp *Type) {
	//fmt.Printf("AddTypedef(): %s -> %s\n",n,t)
	typedefs[n] = tp
}

type Type struct {
	Node     *Node
	Class    string
	Variadic bool
}

func (t *Type) CloneToClass(c string) *Type {
	return &Type{
		Node:     t.Node,
		Class:    c,
		Variadic: t.Variadic,
	}
}

func clean(n *Node, c string) (*Node, bool) {
	if n == nil {
		return nil, false
	}
	ret := NewNode(n.Kind, n.Content)
	ret.Children = n.Children
	//fmt.Printf("clean(%s,%s)\n",n.CType(),c)
	recur := false
	if TypeParameters[c] != nil {
		for k, v := range TypeParameters[c] {
			recur = ret.renameTypedefs(k, v)
		}
	}
	if recur {
		clean(n, c)
		return ret, true
	}
	return n, false
}

func NewType(n *Node, c string) *Type {
	n2, _ := clean(n, c)
	return &Type{
		Node:  n2,
		Class: c,
	}
}

func NewTypeFromString(t, c string) *Type {
	//fmt.Printf("t/c: %s/%s\n",t,c)
	n, err := Parse(t)
	//fmt.Printf("%p %s",n,n.String())
	if err != nil {
		return &Type{}
	}
	if n2, ok := clean(n, c); ok {
		//found type parameters, re-parse
		return NewTypeFromString(n2.CType(), c)
	}
	return &Type{
		Node:  n,
		Class: c,
	}
}

func (t *Type) String() string {
	return t.Node.String()
}

func (t *Type) PointsTo() *Type {
	if td := t.Typedef(); td != nil {
		return td.PointsTo()
	}
	if pt := t.Node.PointsTo(); pt != nil {
		return NewType(t.Node.PointsTo(), t.Class)
	} else {
		return nil
	}
}

func Wrap(s string) {
	//fmt.Printf("wrapped[%s] = true\n",s)
	wrapped[s] = true
}

func (t *Type) BaseType() *Type {
	if t == nil {
		return nil
	}
	ret := NewType(
		t.Node.BaseType(),
		t.Class,
	)
	return ret
}

func swapstars(s string) string {
	for i := len(s) - 1; i > 0 && s[i] == '*'; {
		s = "*" + s[:i]
	}
	return strings.TrimSpace(s)
}

func (t *Type) CGoType() string {
	ct := swapstars("C." + t.CType())
	ct = strings.ReplaceAll(ct, "unsigned ", "u")
	ct = strings.ReplaceAll(ct, "signed ", "u")
	ct = strings.ReplaceAll(ct, "long ", "long")
	ct = strings.ReplaceAll(ct, "complex ", "complex")
	ct = strings.ReplaceAll(ct, " ", "_")
	return ct
}

func (t *Type) GoType() string {
	return _goType(t.CType())
}

func typeSub(gt string) string {
	if Typesubs == nil {
		return gt
	}
	i := 0
	for ; i < len(gt); i++ {
		if gt[i] != '*' {
			break
		}
	}
	gtp := gt[:i]
	gte := gt[i:]
	for k, v := range Typesubs {
		if gte == k {
			return gtp + v
		}
	}
	return gt
}

func _goType(ct string) string {
	ct = strings.Title(ct)
	ct = strings.ReplaceAll(ct, " ", "")
	ct = strings.TrimPrefix(ct, "Struct")
	ct = swapstars(ct)
	if len(ct) > 0 && ct[0] == '*' && IsGoInterface(ct[1:]) {
		return typeSub(ct)
	}

	if ct == "Id" {
		ct = "*Id"
	}
	if ShouldWrap(ct) {
		return typeSub(ct)
	}
	if len(ct) > 4 && ct[len(ct)-4:len(ct)] == "Void" {
		ct = ct[:len(ct)-5] + "unsafe.Pointer"
	}
	return typeSub(ct)
}

func (t *Type) CType() string {
	return t._CType(false)
}

func (t *Type) CTypeAttrib() string {
	return t._CType(true)
}

func (t *Type) _CType(attrib bool) string {
	if t == nil {
		//fmt.Println("nil sent to _CType()")
		return ""
	}
	var ct string
	if attrib {
		ignore := map[string]bool{"GenericList": true}
		ct = t.Node._CType(ignore)
	} else {
		ct = t.Node.CTypeSimplified()
	}
	ct = r_id.ReplaceAllString(ct, "NSObject*")
	ct = r_instancename.ReplaceAllString(ct, t.Class)
	ct = r_instancetype.ReplaceAllString(ct, t.Class+"*")
	return ct
}

func (t *Type) GoTypeDecl(fin bool) string {
	gt := t.GoType()
	if wrapped[gt] {
		return t.GoInterfaceDecl(fin)
	}
	if t.Node.IsId() {
		return ""
	}
	if td := t.Typedef(); td != nil {
		tdgt := td.GoType()
		if ShouldWrap(tdgt) || PtrShouldWrap(tdgt) { // type alias
			if PtrIsGoInterface(tdgt) {
				tdgt = "*Id"
			}
			if IsGoInterface(tdgt) {
				tdgt = "Id"
			}
			Wrap(gt)
			tdptr[gt] = true
			return fmt.Sprintf(`
type %s = %s
`, gt, tdgt)
		}
		cgt := td.CGoType()
		eq := ""
		if len(cgt) > 9 && cgt[:9] == "C.struct_" {
			//cgt = "C." + cgt[9:]
			eq = "= "
		}
		return fmt.Sprintf(`
type %s %s%s
`, gt, eq, cgt)
	}
	if Debug {
		fmt.Printf("  writing GoTypeDecl for %s\n", gt)
	}
	switch gt {
	case "", "Void":
		return ""
	default:
		cgt := t.CGoType()
		eq := ""
		if len(cgt) > 9 && cgt[:9] == "C.struct_" {
			//cgt = "C." + cgt[9:]
			eq = "= "
		}
		return fmt.Sprintf(`
type %s %s%s
`, gt, eq, cgt)
	}
}

func (t *Type) GoInterfaceDecl(fin bool) string {
	ct := t.CType()
	gt := t.GoType()
	if Debug {
		fmt.Printf("  writing GoInterfaceDecl for %s\n", gt)
	}
	if gt[0] == '*' {
		gt = gt[1:] // dereference wrapped types
		ct = ct[:len(ct)-1]
		//fmt.Printf("  dereferenced %s\n", gt)
	}
	super := Super(ct)
	if super == "" {
		//fmt.Printf("goInterfaces[%s] = true\n",gt)
		goInterfaces[gt] = true
		return fmt.Sprintf(`
type %s interface {
	Ptr() unsafe.Pointer
}
`, gt)
	}
	if IsGoInterface(super) {
		super = "Id"
	}
	return fmt.Sprintf(`
type %s struct { %s }
func (o *%s) Ptr() unsafe.Pointer { if o == nil { return nil }; return o.ptr }
func (o *Id) %s() *%s {
	return (*%s)(unsafe.Pointer(o))
}
`, gt, super, gt, gt, gt, gt)
}

func (t *Type) IsFunctionPtr() bool {
	if t == nil {
		return false
	}
	if td := t.Typedef(); td != nil {
		return td.IsFunctionPtr()
	}
	if pt := t.PointsTo(); pt != nil {
		return pt.IsFunction()
	}
	return false
}

func (t *Type) IsFunction() bool {
	if t == nil {
		//fmt.Println("nil sent to IsFunction()")
		return false
	}
	if td := t.Typedef(); td != nil {
		return td.IsFunction()
	}
	return t.Node.IsFunction()
}

func (t *Type) IsValist() bool {
	if t == nil {
		return false
	}
	if t.GoType() == "__va_list_tag" { // OS dependent
		return true
	}
	return false
}

func (t *Type) ReturnType() *Type {
	if td := t.Typedef(); td != nil {
		return td.ReturnType()
	}
	if rt := t.Node.ReturnType(); rt != nil {
		return NewType(rt, t.Class)
	}
	return nil
}

func (t *Type) IsPointer() bool {
	if t == nil {
		return false
	}
	if td := t.Typedef(); td != nil {
		return td.IsPointer()
	}
	return t.Node.IsPointer()
}

// cast C value to CGo
func (t *Type) CToGo(cval string) string {
	if t.IsPointer() {
		cval = "unsafe.Pointer(" + cval + ")"
	}
	return fmt.Sprintf("(%s)(%s)", t.GoType(), cval)
}

// Call a C function from Go with a given return type and parameter types
func GoToC(sname, name string, pnames, snames []string, rtype *Type, ptypes []*Type, fun, fin bool, cm bool, goImports map[string]bool) string {
	if rtype == nil {
		//fmt.Println("nil sent to GoToC")
		return ""
	}
	var ret strings.Builder
	rt := rtype.CType()
	rtgt := rtype.GoType()
	//fmt.Printf("GoToC(%s): rtgt == %s\n",name,rtgt)
	if PtrIsGoInterface(rtgt) {
		//fmt.Printf("  PtrIsGoInterface(%s) = true\n",rtgt)
		rtgt = "*Id"
	}
	sw := PtrShouldWrap(rtgt) || rtgt == "*Id" || TypedefShouldWrap(rtgt)
	isptr := rtype.IsPointer()
	if rt != "void" {
		switch {
		case PtrShouldWrap(rtgt), rtgt == "*Id":
			isptr = true
			if PtrIsGoInterface(rtgt) {
				rtgt = "*Id"
			}
			ret.WriteString(fmt.Sprintf(
				`ret := &%s{}
	ret.ptr = unsafe.Pointer(`, rtgt[1:]))
		case TypedefShouldWrap(rtgt):
			isptr = true
			rtgt := rtype.Typedef().GoType()
			if PtrIsGoInterface(rtgt) {
				rtgt = "*Id"
			}
			ret.WriteString(fmt.Sprintf(
				`ret := &%s{}
	ret.ptr = unsafe.Pointer(`, rtgt[1:]))
		default:
			if rtgt == "BOOL" {
				ret.WriteString("ret := (")
				rtgt = "bool"
			} else {
				ret.WriteString("ret := (" + rtgt + ")(")
			}
			if isptr {
				ret.WriteString("unsafe.Pointer(")
			}
		}
	}
	ret.WriteString("C." + name + "(")
	parms := []string{}
	for i := 0; i < len(pnames); i++ {
		pn, pt := pnames[i], ptypes[i]
		ptgt := pt.GoType()
		p := pn
		switch {
		case (PtrShouldWrap(ptgt) || PtrIsGoInterface(ptgt)) && !pt.Variadic:
			p = pn + ".Ptr()"
		case TypedefShouldWrap(ptgt) && !pt.Variadic && fun:
			p = "(" + pt.CGoType() + ")(" + pn + ".Ptr())"
		case TypedefShouldWrap(ptgt) && !pt.Variadic && !fun:
			p = pn + ".Ptr()"
		case snames[i] != "":
			cast := pt.CGoType()
			if len(ptgt) > 2 && ptgt[:1] == "*" && PtrShouldWrap(ptgt[1:]) {
				cast = "*unsafe.Pointer"
			}
			p = fmt.Sprintf("(%s)(unsafe.Pointer(&%s[0]))", cast, snames[i])
		case pt.Variadic:
			p = "unsafe.Pointer(&" + p + ")"
		case pt.IsPointer() && !fun:
			p = "unsafe.Pointer(" + pn + ")"
		case pt.IsPointer() && fun && pt.BaseType().CType() == "void":
			p = pn
		default:
			p = "(" + pt.CGoType() + ")(" + pn + ")"
		}
		parms = append(parms, p)
	}
	ret.WriteString(strings.Join(parms, ", "))
	ret.WriteString(")")
	if rt != "void" && !sw {
		ret.WriteString(")")
	}
	if isptr {
		ret.WriteString(")")
	}
	if rt == "BOOL" {
		ret.WriteString(" != 0")
	}
	for i, sname := range snames {
		if sname == "" {
			continue
		}
		ptgt := ptypes[i].GoType()
		if !(len(ptgt) > 2 && ptgt[:1] == "*" && PtrShouldWrap(ptgt[1:])) {
			continue
		}
		if len(ptgt) < 2 {
			fmt.Printf("Error in function translation -- argument %s to %s should be pointer to pointer\n", pnames[i], name)
			os.Exit(-1)
		}
		ptgt = ptgt[2:]
		if IsGoInterface(ptgt) {
			ptgt = "Id"
		}
		dogc := ""
		if Gogc {
			goImports["runtime"] = true
			dogc = fmt.Sprintf(`
			runtime.SetFinalizer((*%s)[i], func(o *%s) {
				o.Release()
			})`, pnames[i], ptgt)
		}
		ret.WriteString(fmt.Sprintf(`
	(*%s) = (*%s)[:cap(*%s)]
	for i := 0; i < len(*%s); i++ {
		if %s[i] == nil {
			(*%s) = (*%s)[:i]
			break
		}
		if (*%s)[i] == nil {
			(*%s)[i] = &%s{}%s
		}
		(*%s)[i].ptr = %s[i]
	}`, pnames[i], pnames[i], pnames[i], pnames[i], sname, pnames[i], pnames[i], pnames[i], pnames[i], ptgt, dogc, pnames[i], sname))
	}
	if rt != "void" {
		cmp := ""
		if sw {
			ka := ""
			if !cm {
				goImports["runtime"] = true
				ka = "runtime.KeepAlive(o); "
			}
			if !cm && sname != "copy" && sname != "mutableCopy" {
				cmp = fmt.Sprintf(`
	if ret.ptr == o.ptr { %sreturn (%s)(unsafe.Pointer(o)) }`, ka, rtgt)
			}
			ret.WriteString(fmt.Sprintf(`
	if ret.ptr == nil { %sreturn ret }%s`, ka, cmp))
		}
		if fin {
			dbg := ""
			dbg2 := ""
			if Debug {
				dbg = fmt.Sprintf(`fmt.Printf("Setting finalizer (%s): %%p -> %%p\n", ret, ret.ptr)
	`, rtgt)
				dbg2 = fmt.Sprintf(`fmt.Printf("Finalizer (%s): release %%p -> %%p\n", o, o.ptr)
	`, rtgt)
			}
			goImports["runtime"] = true
			ret.WriteString(fmt.Sprintf(`
	%sruntime.SetFinalizer(ret, func(o %s) {
		%so.Release()
	})`, dbg, rtgt, dbg2))
		}
		if !cm {
			goImports["runtime"] = true
			ret.WriteString(`
	runtime.KeepAlive(o)`)
		}
		ret.WriteString(`
	return ret`)
	} else {
		if !cm {
			goImports["runtime"] = true
			ret.WriteString(`
	runtime.KeepAlive(o)`)
		}
	}
	return ret.String()
}
