// 15 april 2015
package main

import (
	"fmt"
	"os"
	"flag"
	"github.com/andlabs/pgidl"
)

var extern = flag.String("extern", "extern", "name for extern")
var guard = flag.String("guard", "", "#define name for include guards")

var pkgtypes = map[string]string{}

func typedecl(t *pgidl.Type, name string) string {
	if t == nil {
		return "void " + name
	}
	if t.IsFuncPtr {
		return cfuncptrdecl(t.FuncType, name)
	}
	s := t.Name + " "
	if pkgtypes[t.Name] != "" {
		s = pkgtypes[t.Name] + " "
	}
	for i := uint(0); i < t.NumPtrs; i++ {
		s += "*"
	}
	return s + name
}

func arglist(a []*pgidl.Arg) string {
	if len(a) == 0 {
		return "void"
	}
	s := typedecl(a[0].Type, a[0].Name)
	for i := 1; i < len(a); i++ {
		s += ", " + typedecl(a[i].Type, a[i].Name)
	}
	return s
}

func cfuncdecl(f *pgidl.Func, name string) string {
	fd := name + "(" + arglist(f.Args) + ")"
	return *extern + " " + typedecl(f.Ret, fd) + ";"
}

func cfuncptrdecl(f *pgidl.Func, name string) string {
	name = "(*" + name + ")"
	fd := name + "(" + arglist(f.Args) + ")"
	return typedecl(f.Ret, fd)
}

func cmethodmacro(f *pgidl.Func, typename string) string {
	s := "#define " + typename + f.Name + "("
	first := true
	for _, a := range f.Args {
		if !first {
			s += ", "
		}
		s += a.Name
		first = false
	}
	s += ") ("
	s += "(*((this)->" + f.Name + "))"
	s += "("
	first = true
	for _, a := range f.Args {
		if !first {
			s += ", "
		}
		s += "(" + a.Name + ")"
		first = false
	}
	s += ")"
	s += ")"
	return s
}

func genpkgfunc(f *pgidl.Func, prefix string) {
	fmt.Printf("%s\n", cfuncdecl(f, prefix + f.Name))
}

func genstruct(s *pgidl.Struct, prefix string) {
	fmt.Printf("struct %s%s {\n", prefix, s.Name)
	for _, f := range s.Fields {
		fmt.Printf("\t%s;\n", typedecl(f.Type, f.Name))
	}
	fmt.Printf("};\n")
}

func geniface(i *pgidl.Interface, prefix string) {
	fmt.Printf("struct %s%s {\n", prefix, i.Name)
	if i.From != "" {
		fmt.Printf("\t%s%s base;\n", prefix, i.From)
	} else {
		fmt.Printf("\tuintmax_t Type;\n")
	}
	for _, f := range i.Fields {
		fmt.Printf("\t%s;\n", typedecl(f.Type, f.Name))
	}
	for _, m := range i.Methods {
		// hack our this pointer in
		m.Args = append([]*pgidl.Arg{
			&pgidl.Arg{
				Name:	"this",
				Type:	&pgidl.Type{
					Name:	prefix + i.Name,
					NumPtrs:	1,
				},
			},
		}, m.Args...)
		fmt.Printf("\t%s;\n", cfuncptrdecl(m, m.Name))
		fmt.Printf("%s\n", cmethodmacro(m, prefix + i.Name))
	}
	fmt.Printf("};\n")
	fmt.Printf("%s uintmax_t %sType%s(void);\n",
		*extern,
		prefix, i.Name)
	fmt.Printf("#define %s%s(this) ((%s%s *) %sIsA((this), %sType%s(), 1))\n",
		prefix, i.Name,
		prefix, i.Name,
		prefix,
		prefix, i.Name)
	fmt.Printf("#define %sIs%s(this) (%sIsA((this), %sType%s(), 0) != NULL)\n",
		prefix, i.Name,
		prefix,
		prefix, i.Name)
}

func genenum(e *pgidl.Enum, prefix string) {
	fmt.Printf("enum %s%s {\n", prefix, e.Name)
	for _, m := range e.Members {
		fmt.Printf("\t%s%s%s,\n", prefix, e.Name, m)
	}
	fmt.Printf("};\n")
}

func genpkg(p *pgidl.Package) {
	for _, s := range p.Structs {
		fmt.Printf("typedef struct %s%s %s%s;\n",
			p.Name, s.Name,
			p.Name, s.Name)
		pkgtypes[s.Name] = p.Name + s.Name
	}
	for _, i := range p.Interfaces {
		fmt.Printf("typedef struct %s%s %s%s;\n",
			p.Name, i.Name,
			p.Name, i.Name)
		pkgtypes[i.Name] = p.Name + i.Name
	}
	for _, e := range p.Enums {
		fmt.Printf("typedef enum %s%s %s%s;\n",
			p.Name, e.Name,
			p.Name, e.Name)
		pkgtypes[e.Name] = p.Name + e.Name
	}
	// apparently we have to fully define C enumerations before we can use them...
	for _, e := range p.Enums {
		genenum(e, p.Name)
	}
	for _, o := range p.Order {
		switch o.Which {
		case pgidl.Funcs:
			genpkgfunc(p.Funcs[o.Index], p.Name)
		case pgidl.Structs:
			genstruct(p.Structs[o.Index], p.Name)
		case pgidl.Interfaces:
			geniface(p.Interfaces[o.Index], p.Name)
		case pgidl.Raws:
			fmt.Printf("%s\n", p.Raws[o.Index])
		case pgidl.Enums:
			// we did them already; see above
		}
	}
}

func main() {
	flag.Parse()
	idl, errs := pgidl.Parse(os.Stdin, "<stdin>")
	if len(errs) != 0 {
		for _, e := range errs {
			fmt.Fprintf(os.Stderr, "%s\n", e)
		}
		os.Exit(1)
	}
	fmt.Printf("// generated by idl2h; do not edit\n")
	if *guard != "" {
		fmt.Printf("#ifndef %s\n", *guard)
		fmt.Printf("#define %s\n", *guard)
	}
	for _, p := range idl {
		genpkg(p)
	}
	if *guard != "" {
		fmt.Printf("#endif\n")
	}
}