/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/

// Code generator to generate a thin Go wrapper API around the C proton API.
//
// Not run automatically, generated sources are checked in. To update the
// generated sources run `go run genwrap.go` in this directory.
//
package main

import (
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"regexp"
	"strings"
	"text/template"
)

var includeProton = "../../include/proton"
var outpath = "src/qpid.apache.org/proton/wrappers_gen.go"

func main() {
	flag.Parse()
	out, err := os.Create(outpath)
	panicIf(err)
	defer out.Close()

	apis := []string{"session", "link", "delivery", "disposition", "condition", "terminus", "connection", "transport", "sasl"}
	fmt.Fprintln(out, copyright)
	fmt.Fprint(out, `
package proton

import (
	"time"
  "unsafe"
)

// #include <proton/types.h>
// #include <proton/error.h>
// #include <proton/condition.h>
// #include <proton/event.h>
// #include <stdlib.h>
`)
	for _, api := range apis {
		fmt.Fprintf(out, "// #include <proton/%s.h>\n", api)
	}
	fmt.Fprintln(out, `import "C"`)

	event(out)

	for _, api := range apis {
		fmt.Fprintf(out, "// Wrappers for declarations in %s.h\n\n", api)
		header := readHeader(api)
		enums := findEnums(header)
		for _, e := range enums {
			genEnum(out, e.Name, e.Values)
		}
		apiWrapFns(api, header, out)
	}
	out.Close()

	// Run gofmt.
	cmd := exec.Command("gofmt", "-w", outpath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err = cmd.Run()
	if err != nil {
		fmt.Fprintf(os.Stderr, "gofmt: %s", err)
		os.Exit(1)
	}
}

// Identify acronyms that should be uppercase not Mixedcase
var acronym = regexp.MustCompile("(?i)SASL|AMQP")

func mixedCase(s string) string {
	result := ""
	for _, w := range strings.Split(s, "_") {
		if acronym.MatchString(w) {
			w = strings.ToUpper(w)
		} else {
			w = strings.ToUpper(w[0:1]) + strings.ToLower(w[1:])
		}
		result = result + w
	}
	return result
}

func mixedCaseTrim(s, prefix string) string {
	return mixedCase(strings.TrimPrefix(s, prefix))
}

var templateFuncs = template.FuncMap{"mixedCase": mixedCase, "mixedCaseTrim": mixedCaseTrim}

func doTemplate(out io.Writer, data interface{}, tmpl string) {
	panicIf(template.Must(template.New("").Funcs(templateFuncs).Parse(tmpl)).Execute(out, data))
}

type enumType struct {
	Name   string
	Values []string
}

// Find enums in a header file return map of enum name to values.
func findEnums(header string) (enums []enumType) {
	for _, enum := range enumDefRe.FindAllStringSubmatch(header, -1) {
		enums = append(enums, enumType{enum[2], enumValRe.FindAllString(enum[1], -1)})
	}
	return enums
}

// Types that are integral, not wrappers. Enums are added automatically.
var simpleType = map[string]bool{
	"State": true, // integral typedef
}

func genEnum(out io.Writer, name string, values []string) {
	simpleType[mixedCase(name)] = true
	doTemplate(out, []interface{}{name, values}, `
{{$enumName := index . 0}}{{$values := index . 1}}
type {{mixedCase $enumName}} C.pn_{{$enumName}}_t
const ({{range $values}}
	{{mixedCaseTrim . "PN_"}} {{mixedCase $enumName}} = C.{{.}} {{end}}
)

func (e {{mixedCase $enumName}}) String() string {
	switch e {
{{range $values}}
	case C.{{.}}: return "{{mixedCaseTrim . "PN_"}}" {{end}}
	}
	return "unknown"
}
`)
}

func panicIf(err error) {
	if err != nil {
		panic(err)
	}
}

func readHeader(name string) string {
	file, err := os.Open(path.Join(includeProton, name+".h"))
	panicIf(err)
	defer file.Close()
	s, err := ioutil.ReadAll(file)
	panicIf(err)
	return string(s)
}

var copyright string = `/*
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements.  See the NOTICE file
distributed with this work for additional information
regarding copyright ownership.  The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License.  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.  See the License for the
specific language governing permissions and limitations
under the License.
*/

//
// NOTE: DO NOT EDIT. This file was generated by genwrap.go from the proton header files.
// Update the generator and re-run if you need to modify this code.
//
`

type eventType struct {
	// C, function and interface names for the event
	Name, Cname, Fname, Iname string
}

func newEventType(cName string) eventType {
	var etype eventType
	etype.Cname = cName
	etype.Name = mixedCaseTrim(cName, "PN_")
	etype.Fname = "On" + etype.Name
	etype.Iname = etype.Fname + "Interface"
	return etype
}

var (
	enumDefRe   = regexp.MustCompile("typedef enum {([^}]*)} pn_([a-z_]+)_t;")
	enumValRe   = regexp.MustCompile("PN_[A-Z_]+")
	skipEventRe = regexp.MustCompile("EVENT_NONE|REACTOR|SELECTABLE|TIMER")
	skipFnRe    = regexp.MustCompile("attach|context|class|collect|link_recv|link_send|transport_.*logf$|transport_.*trace|transport_head|transport_tail|transport_push|connection_set_password|link_get_drain")
)

// Generate event wrappers.
func event(out io.Writer) {
	event_h := readHeader("event")

	// Event is implented by hand in wrappers.go

	// Get all the pn_event_type_t enum values
	var etypes []eventType
	enums := findEnums(event_h)
	for _, e := range enums[0].Values {
		if skipEventRe.FindStringSubmatch(e) == nil {
			etypes = append(etypes, newEventType(e))
		}
	}

	doTemplate(out, etypes, `
type EventType int
const ({{range .}}
	 E{{.Name}} EventType = C.{{.Cname}}{{end}}
)
`)

	doTemplate(out, etypes, `
func (e EventType) String() string {
	switch e {
{{range .}}
	case C.{{.Cname}}: return "{{.Name}}"{{end}}
	}
	return "Unknown"
}
`)
}

type genType struct {
	Ctype, Gotype string
	ToGo          func(value string) string
	ToC           func(value string) string
	Assign        func(value string) string
}

func (g genType) printBody(out io.Writer, value string) {
	if g.Gotype != "" {
		fmt.Fprintf(out, "return %s", g.ToGo(value))
	} else {
		fmt.Fprintf(out, "%s", value)
	}
}

func (g genType) goLiteral(value string) string {
	return fmt.Sprintf("%s{%s}", g.Gotype, value)
}

func (g genType) goConvert(value string) string {
	switch g.Gotype {
	case "string":
		return fmt.Sprintf("C.GoString(%s)", value)
	case "Event":
		return fmt.Sprintf("makeEvent(%s)", value)
	default:
		return fmt.Sprintf("%s(%s)", g.Gotype, value)
	}
}

func mapType(ctype string) (g genType) {
	g.Ctype = "C." + strings.Trim(ctype, " \n")

	// Special-case mappings for C types, default: is the general wrapper type case.
	switch g.Ctype {
	case "C.void":
		g.Gotype = ""
	case "C.size_t":
		g.Gotype = "uint"
	case "C.int":
		g.Gotype = "int"
	case "C.void *":
		g.Gotype = "unsafe.Pointer"
		g.Ctype = "unsafe.Pointer"
	case "C.bool":
		g.Gotype = "bool"
	case "C.ssize_t":
		g.Gotype = "int"
	case "C.uint64_t":
		g.Gotype = "uint64"
	case "C.uint32_t":
		g.Gotype = "uint16"
	case "C.uint16_t":
		g.Gotype = "uint32"
	case "C.const char *":
		fallthrough
	case "C.char *":
		g.Gotype = "string"
		g.Ctype = "C.CString"
		g.ToC = func(v string) string { return fmt.Sprintf("%sC", v) }
		g.Assign = func(v string) string {
			return fmt.Sprintf("%sC := C.CString(%s)\n defer C.free(unsafe.Pointer(%sC))\n", v, v, v)
		}
	case "C.pn_seconds_t":
		g.Gotype = "time.Duration"
		g.ToGo = func(v string) string { return fmt.Sprintf("(time.Duration(%s) * time.Second)", v) }
	case "C.pn_millis_t":
		g.Gotype = "time.Duration"
		g.ToGo = func(v string) string { return fmt.Sprintf("(time.Duration(%s) * time.Millisecond)", v) }
		g.ToC = func(v string) string { return fmt.Sprintf("C.pn_millis_t(%s/time.Millisecond)", v) }
	case "C.pn_timestamp_t":
		g.Gotype = "time.Time"
		g.ToC = func(v string) string { return fmt.Sprintf("pnTime(%s)", v) }
		g.ToGo = func(v string) string { return fmt.Sprintf("goTime(%s)", v) }
	case "C.pn_error_t *":
		g.Gotype = "error"
		g.ToGo = func(v string) string { return fmt.Sprintf("PnError(%s)", v) }
	default:
		pnId := regexp.MustCompile(" *pn_([a-z_]+)_t *\\*? *")
		match := pnId.FindStringSubmatch(g.Ctype)
		if match == nil {
			panic(fmt.Errorf("unknown C type %#v", g.Ctype))
		}
		g.Gotype = mixedCase(match[1])
		if !simpleType[g.Gotype] {
			g.ToGo = g.goLiteral
			g.ToC = func(v string) string { return v + ".pn" }
		}
	}
	if g.ToGo == nil {
		g.ToGo = g.goConvert // Use conversion by default.
	}
	if g.ToC == nil {
		g.ToC = func(v string) string { return fmt.Sprintf("%s(%s)", g.Ctype, v) }
	}
	return
}

type genArg struct {
	Name string
	genType
}

var typeNameRe = regexp.MustCompile("^(.*( |\\*))([^ *]+)$")

func splitArgs(argstr string) []genArg {
	argstr = strings.Trim(argstr, " \n")
	if argstr == "" {
		return []genArg{}
	}
	args := make([]genArg, 0)
	for _, item := range strings.Split(argstr, ",") {
		item = strings.Trim(item, " \n")
		typeName := typeNameRe.FindStringSubmatch(item)
		if typeName == nil {
			panic(fmt.Errorf("Can't split argument type/name %#v", item))
		}
		cType := strings.Trim(typeName[1], " \n")
		name := strings.Trim(typeName[3], " \n")
		if name == "type" {
			name = "type_"
		}
		args = append(args, genArg{name, mapType(cType)})
	}
	return args
}

func goArgs(args []genArg) string {
	l := ""
	for i, arg := range args {
		if i != 0 {
			l += ", "
		}
		l += arg.Name + " " + arg.Gotype
	}
	return l
}

func cArgs(args []genArg) string {
	l := ""
	for _, arg := range args {
		l += fmt.Sprintf(", %s", arg.ToC(arg.Name))
	}
	return l
}

func cAssigns(args []genArg) string {
	l := "\n"
	for _, arg := range args {
		if arg.Assign != nil {
			l += fmt.Sprintf("%s\n", arg.Assign(arg.Name))
		}
	}
	return l
}

// Return the go name of the function or "" to skip the function.
func goFnName(api, fname string) string {
	// Skip class, context and attachment functions.
	if skipFnRe.FindStringSubmatch(api+"_"+fname) != nil {
		return ""
	}
	return mixedCaseTrim(fname, "get_")
}

func apiWrapFns(api, header string, out io.Writer) {
	fmt.Fprintf(out, "type %s struct{pn *C.pn_%s_t}\n", mixedCase(api), api)
	fmt.Fprintf(out, "func (%c %s) IsNil() bool { return %c.pn == nil }\n", api[0], mixedCase(api), api[0])
	fmt.Fprintf(out, "func (%c %s) CPtr() unsafe.Pointer { return unsafe.Pointer(%c.pn) }\n", api[0], mixedCase(api), api[0])
	fn := regexp.MustCompile(fmt.Sprintf(`PN_EXTERN ([a-z0-9_ ]+ *\*?) *pn_%s_([a-z_]+)\(pn_%s_t *\*[a-z_]+ *,? *([^)]*)\)`, api, api))
	for _, m := range fn.FindAllStringSubmatch(header, -1) {
		rtype, fname, argstr := mapType(m[1]), m[2], m[3]
		gname := goFnName(api, fname)
		if gname == "" { // Skip
			continue
		}
		args := splitArgs(argstr)
		fmt.Fprintf(out, "func (%c %s) %s", api[0], mixedCase(api), gname)
		fmt.Fprintf(out, "(%s) %s { ", goArgs(args), rtype.Gotype)
		fmt.Fprint(out, cAssigns(args))
		rtype.printBody(out, fmt.Sprintf("C.pn_%s_%s(%c.pn%s)", api, fname, api[0], cArgs(args)))
		fmt.Fprintf(out, "}\n")
	}
}
