/*
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.
//

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"}
	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)
	}
}

func mixedCase(s string) string {
	result := ""
	for _, w := range strings.Split(s, "_") {
		if w != "" {
			result = result + strings.ToUpper(w[0:1]) + strings.ToLower(w[1:])
		}
	}
	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
}

func genEnum(out io.Writer, name string, values []string) {
	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"
}
`)
}

var (
	reSpace = regexp.MustCompile("\\s+")
)

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_push")
)

// 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)
	}
}

var notStruct = map[string]bool{
	"EventType":        true,
	"SndSettleMode":    true,
	"RcvSettleMode":    true,
	"TerminusType":     true,
	"State":            true,
	"Durability":       true,
	"ExpiryPolicy":     true,
	"DistributionMode": true,
}

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

	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) }
	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 !notStruct[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 ""
	}
	switch api + "." + fname {
	case "link.get_drain":
		return "IsDrain"
	default:
		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")
	}
}
