package main

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

func main() {
	if err := main1(); err != nil {
		os.Stderr.WriteString(err.Error() + "\n")
		os.Exit(1)
	}
}

func main1() error {
	if !cdIconVGRootDirectory() {
		return fmt.Errorf("main: could not find or chdir to the IconVG root directory")
	}

	buf := &bytes.Buffer{}
	remaining, err := os.ReadFile(filepath.FromSlash("src/c/aaa_package.h"))
	if err != nil {
		return err
	}
	for len(remaining) > 0 {
		line := remaining
		remaining = nil
		if i := bytes.IndexByte(line, '\n'); i >= 0 {
			line, remaining = line[:i+1], line[i+1:]
		}

		if !bytes.HasPrefix(line, hashIncludeDQ) {
			buf.Write(line)
		} else if err := expand(buf, line); err != nil {
			return err
		} else {
			buf.WriteByte('\n')
		}
	}

	version := "unsupported-snapshot"
	outFilename := filepath.FromSlash("release/c/iconvg-" + version + ".c")
	return os.WriteFile(outFilename, buf.Bytes(), 0o644)
}

var (
	copyright = []byte(`// Copyright `)
	dquoteNL  = []byte("\"\n")
	nl        = []byte("\n")
	nlNL      = []byte("\n\n")

	hashEndif     = []byte(`#endif `)
	hashIfndef    = []byte(`#ifndef `)
	hashIncludeAB = []byte(`#include <`)   
	hashIncludeDQ = []byte(`#include "./`) 
	includeGuard  = []byte(`_INCLUDE_GUARD`)

	publicAPIIndex = []byte("// Public API Index (generated by the amalgamation script)\n\n")

	closeCurly          = []byte("} ")
	closeCurlyNamespace = []byte("}  
	extern              = []byte("extern ")
	namespace           = []byte("namespace ")
	slashSlash          = []byte("//")
	slashSlashPilcrow   = []byte("// ¶")
	typedef             = []byte("typedef ")
	typedefEnum         = []byte("typedef enum ")
	typedefStruct       = []byte("typedef struct ")
)

func cdIconVGRootDirectory() bool {
	prevWD, err := os.Getwd()
	if err != nil {
		return false
	}
	for {
		if _, err := os.Stat("iconvg-root-directory.txt"); err == nil {
			break
		}
		if err := os.Chdir(".."); err != nil {
			return false
		}
		if wd, err := os.Getwd(); (err != nil) || (prevWD == wd) {
			return false
		} else {
			prevWD = wd
		}
	}
	return true
}

func expand(w io.Writer, line []byte) error {
	if !bytes.HasSuffix(line, dquoteNL) {
		return fmt.Errorf("main: unexpected line %q", line)
	}
	filename := string(line[len(hashIncludeDQ) : len(line)-len(dquoteNL)])
	if _, err := fmt.Fprintf(w, "// -------------------------------- %s\n", line); err != nil {
		return err
	}

	src, err := os.ReadFile(filepath.FromSlash("src/c/" + filename))
	if err != nil {
		return err
	}

	src = skipIncludeGuards(src)
	if !bytes.HasPrefix(src, copyright) {
		return fmt.Errorf("main: %q did not start with the expected copyright header", filename)
	} else if i := bytes.Index(src, nlNL); i < 0 {
		return fmt.Errorf("main: %q did not start with the expected copyright header", filename)
	} else {
		src = src[i+1:]
	}

	if filename == "aaa_public.h" {
		if src, err = processPublicAPI(src); err != nil {
			return err
		}
	}

	sawHashIncludeAB := false
	for len(src) > 0 {
		if src[0] == '\n' {
			src = src[1:]
		} else if src[0] != '#' {
			break
		}

		if bytes.HasPrefix(src, hashIncludeAB) {
			if i := bytes.IndexByte(src, '\n'); i < 0 {
				return fmt.Errorf("main: %q had unsupported #include", filename)
			} else {
				if _, err := w.Write(src[:i+1]); err != nil {
					return err
				}
				src = src[i+1:]
				sawHashIncludeAB = true
			}
		} else if bytes.HasPrefix(src, hashIncludeDQ) {
			if i := bytes.IndexByte(src, '\n'); i < 0 {
				return fmt.Errorf("main: %q had unsupported #include", filename)
			} else {
				src = src[i+1:]
			}
		} else {
			break
		}
	}

	if sawHashIncludeAB {
		if _, err := w.Write(nl); err != nil {
			return err
		}
	}
	_, err = w.Write(src)
	return err
}

func skipIncludeGuards(src []byte) []byte {
	unchanged := src
	if !bytes.HasPrefix(src, hashIfndef) {
		return unchanged
	}

	if i := bytes.IndexByte(src, '\n'); i < 0 {
		return unchanged
	} else if !bytes.HasSuffix(src[:i], includeGuard) {
		return unchanged
	} else {
		src = src[i+1:]
	}

	if i := bytes.IndexByte(src, '\n'); i < 0 {
		return unchanged
	} else if !bytes.HasSuffix(src[:i], includeGuard) {
		return unchanged
	} else {
		src = src[i+1:]
	}

	for (len(src) > 0) && (src[0] == '\n') {
		src = src[1:]
	}
	for (len(src) > 0) && (src[len(src)-1] == '\n') {
		src = src[:len(src)-1]
	}

	if !bytes.HasSuffix(src, includeGuard) {
		return unchanged
	} else if i := bytes.LastIndexByte(src, '\n'); i < 0 {
		return unchanged
	} else if !bytes.HasPrefix(src[i+1:], hashEndif) {
		return unchanged
	} else {
		src = src[:i]
	}

	return src
}

func processPublicAPI(src []byte) ([]byte, error) {
	ret := []byte(nil)
	if i := bytes.Index(src, publicAPIIndex); i >= 0 {
		prefix := src[:i]
		suffix := src[i+len(publicAPIIndex):]
		index, err := buildPublicAPIIndex(suffix)
		if err != nil {
			return nil, err
		}
		ret = append(ret, prefix...)
		ret = append(ret, index...)
		ret = append(ret, suffix...)
	}
	return ret, nil
}

func buildPublicAPIIndex(src []byte) ([]byte, error) {
	funks := []string(nil)
	strukts := []string(nil)
	enums := []string(nil)
	others := []string(nil)
	pendingEnumValues := []string(nil)
	ctorMap := map[string][]string{}
	enumMap := map[string][]string{}
	methMap := map[string][]string{}
	typedefIsEnum := false

	ns := ""
	for remaining := src; len(remaining) > 0; {
		line := remaining
		remaining = nil
		if i := bytes.IndexByte(line, '\n'); i >= 0 {
			line, remaining = line[:i+1], line[i+1:]
		}

		if bytes.HasPrefix(line, namespace) {
			line = line[len(namespace):]
			if i := bytes.IndexByte(line, ' '); i >= 0 {
				ns = string(line[:i]) + "::"
			}
			continue
		} else if bytes.HasPrefix(line, closeCurlyNamespace) {
			ns = ""
			continue
		}

		if bytes.HasPrefix(line, typedef) {
			typedefIsEnum = bytes.HasPrefix(line, typedefEnum)
			if !typedefIsEnum {

			} else if len(pendingEnumValues) != 0 {
				return nil, fmt.Errorf("main: no matching enum type for values %q", pendingEnumValues)
			} else {
				pendingEnumValues = []string(nil)
			}
		}

		if i := bytes.Index(line, slashSlashPilcrow); i < 0 {
			continue
		} else {
			line = bytes.TrimSpace(line[:i])
		}

		for (len(line) > 0) && (line[0] <= ' ') {
			line = line[1:]
		}
		if (len(line) == 0) || (line[0] == '/') {
			continue
		}

		if funkName := looksLikePublicAPIFunction(line); funkName != "" {
			funkName = ns + funkName
			if i := strings.Index(funkName, "__make"); i >= 0 {
				typName := strings.Replace(funkName[:i], "::", "_", -1)
				ctorMap[typName] = append(ctorMap[typName], funkName)
				continue
			} else if i := strings.Index(funkName, "__"); i >= 0 {
				typName := funkName[:i]
				methMap[typName] = append(methMap[typName], funkName)
				continue
			}
			funks = append(funks, funkName)
			continue
		} else if typName := looksLikePublicAPIType(line); typName != "" {
			typName = ns + typName
			if typedefIsEnum {
				enums = append(enums, typName)
				enumMap[typName] = pendingEnumValues
				pendingEnumValues = []string(nil)
			} else {
				strukts = append(strukts, typName)
			}
			continue
		}

		if i := bytes.IndexByte(line, '='); i >= 0 {
			line = bytes.TrimSpace(line[:i])
			if !typedefIsEnum {
				return nil, fmt.Errorf("main: %q not within an enum", line)
			}
			pendingEnumValues = append(pendingEnumValues, string(line))
			continue
		}

		if bytes.HasPrefix(line, extern) {
			line = line[1+bytes.LastIndexByte(line, ' '):]
			line = bytes.Trim(line, "[];")
			others = append(others, string(line))
			continue
		}

		return nil, fmt.Errorf("main: unrecognized public API line %q", line)
	}

	sort.Strings(funks)
	sort.Strings(strukts)
	sort.Strings(enums)
	sort.Strings(others)

	b := &bytes.Buffer{}
	b.WriteString("// Public API Index.\n")

	if len(funks) > 0 {
		b.WriteString("//\n
		for _, funk := range funks {
			fmt.Fprintf(b, "//   - %s\n", funk)
		}
	}

	if len(strukts) > 0 {
		b.WriteString("//\n
		for _, strukt := range strukts {
			fmt.Fprintf(b, "//   - %s\n", strukt)

			ctors := ctorMap[strukt]
			sort.Strings(ctors)
			for _, ctor := range ctors {
				fmt.Fprintf(b, "//           * %s\n", ctor)
			}
			delete(ctorMap, strukt)

			meths := methMap[strukt]
			sort.Strings(meths)
			for _, meth := range meths {
				fmt.Fprintf(b, "//       + %s\n", meth)
			}
			delete(methMap, strukt)
		}
	}

	if len(enums) > 0 {
		b.WriteString("//\n
		for _, enum := range enums {
			fmt.Fprintf(b, "//   - %s\n", enum)

			ctors := ctorMap[enum]
			sort.Strings(ctors)
			for _, ctor := range ctors {
				fmt.Fprintf(b, "//           * %s\n", ctor)
			}
			delete(ctorMap, enum)

			values := enumMap[enum]
			sort.Strings(values)
			for _, value := range values {
				fmt.Fprintf(b, "//       = %s\n", value)
			}
			delete(enumMap, enum)
		}
	}

	if len(others) > 0 {
		b.WriteString("//\n
		for _, other := range others {
			fmt.Fprintf(b, "//   - %s\n", other)
		}
	}

	if len(ctorMap) != 0 {
		k, v := biggestKey(ctorMap)
		return nil, fmt.Errorf("main: no matching return type %q for constructors %q", k, v)
	}
	if len(enumMap) != 0 {
		k, v := biggestKey(enumMap)
		return nil, fmt.Errorf("main: no matching enum type %q for values %q", k, v)
	}
	if len(methMap) != 0 {
		k, v := biggestKey(methMap)
		return nil, fmt.Errorf("main: no matching receiver type %q for methods %q", k, v)
	}
	if len(pendingEnumValues) != 0 {
		return nil, fmt.Errorf("main: no matching enum type for values %q", pendingEnumValues)
	}
	b.WriteString("\n")
	return b.Bytes(), nil
}

func looksLikePublicAPIFunction(src []byte) (name string) {
	if i := bytes.IndexByte(src, '('); i < 0 {
		return ""
	} else {
		src = src[:i]
	}
	if (len(src) == 0) || (bytes.IndexByte(src, ' ') >= 0) {
		return ""
	}
	return string(src)
}

func looksLikePublicAPIType(src []byte) (name string) {
	if bytes.HasPrefix(src, closeCurly) {
		src = src[len(closeCurly):]
	} else if bytes.HasPrefix(src, typedefStruct) {
		src = src[len(typedefStruct):]
	} else {
		return ""
	}

	if i := bytes.IndexByte(src, ';'); i < 0 {
		return ""
	} else {
		src = src[:i]
	}

	i := 1 + bytes.LastIndexByte(src, ' ')
	return string(src[i:])
}

func biggestKey(m map[string][]string) (key string, val []string) {
	for k, v := range m {
		if k >= key {
			key, val = k, v
		}
	}
	return key, val
}
// Copyright 2021 The IconVG Authors.
//
// Licensed 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
//
//    https://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.

//go:build ignore

package main

// gen-release-c.go amalgamates src/c/* into a single release/c/foo.c file.

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"sort"
	"strings"
)

func main() {
	if err := main1(); err != nil {
		os.Stderr.WriteString(err.Error() + "\n")
		os.Exit(1)
	}
}

func main1() error {
	if !cdIconVGRootDirectory() {
		return fmt.Errorf("main: could not find or chdir to the IconVG root directory")
	}

	buf := &bytes.Buffer{}
	remaining, err := os.ReadFile(filepath.FromSlash("src/c/aaa_package.h"))
	if err != nil {
		return err
	}
	for len(remaining) > 0 {
		line := remaining
		remaining = nil
		if i := bytes.IndexByte(line, '\n'); i >= 0 {
			line, remaining = line[:i+1], line[i+1:]
		}

		if !bytes.HasPrefix(line, hashIncludeDQ) {
			buf.Write(line)
		} else if err := expand(buf, line); err != nil {
			return err
		} else {
			buf.WriteByte('\n')
		}
	}

	version := "unsupported-snapshot"
	outFilename := filepath.FromSlash("release/c/iconvg-" + version + ".c")
	return os.WriteFile(outFilename, buf.Bytes(), 0o644)
}

var (
	copyright = []byte(`// Copyright `)
	dquoteNL  = []byte("\"\n")
	nl        = []byte("\n")
	nlNL      = []byte("\n\n")

	hashEndif     = []byte(`#endif `)
	hashIfndef    = []byte(`#ifndef `)
	hashIncludeAB = []byte(`#include <`)   // AB = Angle Bracket.
	hashIncludeDQ = []byte(`#include "./`) // DQ = Double Quote.
	includeGuard  = []byte(`_INCLUDE_GUARD`)

	publicAPIIndex = []byte("// Public API Index (generated by the amalgamation script)\n\n")

	closeCurly          = []byte("} ")
	closeCurlyNamespace = []byte("}  // namespace ")
	extern              = []byte("extern ")
	namespace           = []byte("namespace ")
	slashSlash          = []byte("//")
	slashSlashPilcrow   = []byte("// ¶")
	typedef             = []byte("typedef ")
	typedefEnum         = []byte("typedef enum ")
	typedefStruct       = []byte("typedef struct ")
)

func cdIconVGRootDirectory() bool {
	prevWD, err := os.Getwd()
	if err != nil {
		return false
	}
	for {
		if _, err := os.Stat("iconvg-root-directory.txt"); err == nil {
			break
		}
		if err := os.Chdir(".."); err != nil {
			return false
		}
		if wd, err := os.Getwd(); (err != nil) || (prevWD == wd) {
			return false
		} else {
			prevWD = wd
		}
	}
	return true
}

func expand(w io.Writer, line []byte) error {
	if !bytes.HasSuffix(line, dquoteNL) {
		return fmt.Errorf("main: unexpected line %q", line)
	}
	filename := string(line[len(hashIncludeDQ) : len(line)-len(dquoteNL)])
	if _, err := fmt.Fprintf(w, "// -------------------------------- %s\n", line); err != nil {
		return err
	}

	src, err := os.ReadFile(filepath.FromSlash("src/c/" + filename))
	if err != nil {
		return err
	}

	// Skip the `#ifndef ETC_INCLUDE_GUARD` and `// Copyright etc` lines.
	src = skipIncludeGuards(src)
	if !bytes.HasPrefix(src, copyright) {
		return fmt.Errorf("main: %q did not start with the expected copyright header", filename)
	} else if i := bytes.Index(src, nlNL); i < 0 {
		return fmt.Errorf("main: %q did not start with the expected copyright header", filename)
	} else {
		src = src[i+1:]
	}

	// Process public API.
	if filename == "aaa_public.h" {
		if src, err = processPublicAPI(src); err != nil {
			return err
		}
	}

	// Skip the `#include "./etc.h"` lines but not `#include <etc.h>`.
	sawHashIncludeAB := false
	for len(src) > 0 {
		if src[0] == '\n' {
			src = src[1:]
		} else if src[0] != '#' {
			break
		}

		if bytes.HasPrefix(src, hashIncludeAB) {
			if i := bytes.IndexByte(src, '\n'); i < 0 {
				return fmt.Errorf("main: %q had unsupported #include", filename)
			} else {
				if _, err := w.Write(src[:i+1]); err != nil {
					return err
				}
				src = src[i+1:]
				sawHashIncludeAB = true
			}
		} else if bytes.HasPrefix(src, hashIncludeDQ) {
			if i := bytes.IndexByte(src, '\n'); i < 0 {
				return fmt.Errorf("main: %q had unsupported #include", filename)
			} else {
				src = src[i+1:]
			}
		} else {
			break
		}
	}

	if sawHashIncludeAB {
		if _, err := w.Write(nl); err != nil {
			return err
		}
	}
	_, err = w.Write(src)
	return err
}

func skipIncludeGuards(src []byte) []byte {
	unchanged := src
	if !bytes.HasPrefix(src, hashIfndef) {
		return unchanged
	}

	// Remove leading `#ifndef FOO_INCLUDE_GUARD`.
	if i := bytes.IndexByte(src, '\n'); i < 0 {
		return unchanged
	} else if !bytes.HasSuffix(src[:i], includeGuard) {
		return unchanged
	} else {
		src = src[i+1:]
	}

	// Remove leading `#define FOO_INCLUDE_GUARD`.
	if i := bytes.IndexByte(src, '\n'); i < 0 {
		return unchanged
	} else if !bytes.HasSuffix(src[:i], includeGuard) {
		return unchanged
	} else {
		src = src[i+1:]
	}

	// Trim leading and trailing blank lines.
	for (len(src) > 0) && (src[0] == '\n') {
		src = src[1:]
	}
	for (len(src) > 0) && (src[len(src)-1] == '\n') {
		src = src[:len(src)-1]
	}

	// Remove trailing `#endif  // FOO_INCLUDE_GUARD`.
	if !bytes.HasSuffix(src, includeGuard) {
		return unchanged
	} else if i := bytes.LastIndexByte(src, '\n'); i < 0 {
		return unchanged
	} else if !bytes.HasPrefix(src[i+1:], hashEndif) {
		return unchanged
	} else {
		src = src[:i]
	}

	return src
}

func processPublicAPI(src []byte) ([]byte, error) {
	ret := []byte(nil)
	if i := bytes.Index(src, publicAPIIndex); i >= 0 {
		prefix := src[:i]
		suffix := src[i+len(publicAPIIndex):]
		index, err := buildPublicAPIIndex(suffix)
		if err != nil {
			return nil, err
		}
		ret = append(ret, prefix...)
		ret = append(ret, index...)
		ret = append(ret, suffix...)
	}
	return ret, nil
}

func buildPublicAPIIndex(src []byte) ([]byte, error) {
	funks := []string(nil)
	strukts := []string(nil)
	enums := []string(nil)
	others := []string(nil)
	pendingEnumValues := []string(nil)
	ctorMap := map[string][]string{}
	enumMap := map[string][]string{}
	methMap := map[string][]string{}
	typedefIsEnum := false

	ns := ""
	for remaining := src; len(remaining) > 0; {
		line := remaining
		remaining = nil
		if i := bytes.IndexByte(line, '\n'); i >= 0 {
			line, remaining = line[:i+1], line[i+1:]
		}

		if bytes.HasPrefix(line, namespace) {
			line = line[len(namespace):]
			if i := bytes.IndexByte(line, ' '); i >= 0 {
				ns = string(line[:i]) + "::"
			}
			continue
		} else if bytes.HasPrefix(line, closeCurlyNamespace) {
			ns = ""
			continue
		}

		if bytes.HasPrefix(line, typedef) {
			typedefIsEnum = bytes.HasPrefix(line, typedefEnum)
			if !typedefIsEnum {
				// No-op.
			} else if len(pendingEnumValues) != 0 {
				return nil, fmt.Errorf("main: no matching enum type for values %q", pendingEnumValues)
			} else {
				pendingEnumValues = []string(nil)
			}
		}

		if i := bytes.Index(line, slashSlashPilcrow); i < 0 {
			continue
		} else {
			line = bytes.TrimSpace(line[:i])
		}

		for (len(line) > 0) && (line[0] <= ' ') {
			line = line[1:]
		}
		if (len(line) == 0) || (line[0] == '/') {
			continue
		}

		if funkName := looksLikePublicAPIFunction(line); funkName != "" {
			funkName = ns + funkName
			if i := strings.Index(funkName, "__make"); i >= 0 {
				typName := strings.Replace(funkName[:i], "::", "_", -1)
				ctorMap[typName] = append(ctorMap[typName], funkName)
				continue
			} else if i := strings.Index(funkName, "__"); i >= 0 {
				typName := funkName[:i]
				methMap[typName] = append(methMap[typName], funkName)
				continue
			}
			funks = append(funks, funkName)
			continue
		} else if typName := looksLikePublicAPIType(line); typName != "" {
			typName = ns + typName
			if typedefIsEnum {
				enums = append(enums, typName)
				enumMap[typName] = pendingEnumValues
				pendingEnumValues = []string(nil)
			} else {
				strukts = append(strukts, typName)
			}
			continue
		}

		if i := bytes.IndexByte(line, '='); i >= 0 {
			line = bytes.TrimSpace(line[:i])
			if !typedefIsEnum {
				return nil, fmt.Errorf("main: %q not within an enum", line)
			}
			pendingEnumValues = append(pendingEnumValues, string(line))
			continue
		}

		if bytes.HasPrefix(line, extern) {
			line = line[1+bytes.LastIndexByte(line, ' '):]
			line = bytes.Trim(line, "[];")
			others = append(others, string(line))
			continue
		}

		return nil, fmt.Errorf("main: unrecognized public API line %q", line)
	}

	sort.Strings(funks)
	sort.Strings(strukts)
	sort.Strings(enums)
	sort.Strings(others)

	b := &bytes.Buffer{}
	b.WriteString("// Public API Index.\n")

	if len(funks) > 0 {
		b.WriteString("//\n// Functions (-):\n")
		for _, funk := range funks {
			fmt.Fprintf(b, "//   - %s\n", funk)
		}
	}

	if len(strukts) > 0 {
		b.WriteString("//\n// Data structures (-), their constructors (*) and their methods (+):\n")
		for _, strukt := range strukts {
			fmt.Fprintf(b, "//   - %s\n", strukt)

			ctors := ctorMap[strukt]
			sort.Strings(ctors)
			for _, ctor := range ctors {
				fmt.Fprintf(b, "//           * %s\n", ctor)
			}
			delete(ctorMap, strukt)

			meths := methMap[strukt]
			sort.Strings(meths)
			for _, meth := range meths {
				fmt.Fprintf(b, "//       + %s\n", meth)
			}
			delete(methMap, strukt)
		}
	}

	if len(enums) > 0 {
		b.WriteString("//\n// Enumerations (-), their constructors (*) and their values (=):\n")
		for _, enum := range enums {
			fmt.Fprintf(b, "//   - %s\n", enum)

			ctors := ctorMap[enum]
			sort.Strings(ctors)
			for _, ctor := range ctors {
				fmt.Fprintf(b, "//           * %s\n", ctor)
			}
			delete(ctorMap, enum)

			values := enumMap[enum]
			sort.Strings(values)
			for _, value := range values {
				fmt.Fprintf(b, "//       = %s\n", value)
			}
			delete(enumMap, enum)
		}
	}

	if len(others) > 0 {
		b.WriteString("//\n// Other globals (-):\n")
		for _, other := range others {
			fmt.Fprintf(b, "//   - %s\n", other)
		}
	}

	if len(ctorMap) != 0 {
		k, v := biggestKey(ctorMap)
		return nil, fmt.Errorf("main: no matching return type %q for constructors %q", k, v)
	}
	if len(enumMap) != 0 {
		k, v := biggestKey(enumMap)
		return nil, fmt.Errorf("main: no matching enum type %q for values %q", k, v)
	}
	if len(methMap) != 0 {
		k, v := biggestKey(methMap)
		return nil, fmt.Errorf("main: no matching receiver type %q for methods %q", k, v)
	}
	if len(pendingEnumValues) != 0 {
		return nil, fmt.Errorf("main: no matching enum type for values %q", pendingEnumValues)
	}
	b.WriteString("\n")
	return b.Bytes(), nil
}

func looksLikePublicAPIFunction(src []byte) (name string) {
	if i := bytes.IndexByte(src, '('); i < 0 {
		return ""
	} else {
		src = src[:i]
	}
	if (len(src) == 0) || (bytes.IndexByte(src, ' ') >= 0) {
		return ""
	}
	return string(src)
}

func looksLikePublicAPIType(src []byte) (name string) {
	if bytes.HasPrefix(src, closeCurly) {
		src = src[len(closeCurly):]
	} else if bytes.HasPrefix(src, typedefStruct) {
		src = src[len(typedefStruct):]
	} else {
		return ""
	}

	if i := bytes.IndexByte(src, ';'); i < 0 {
		return ""
	} else {
		src = src[:i]
	}

	i := 1 + bytes.LastIndexByte(src, ' ')
	return string(src[i:])
}

// biggestKey returns an element of m, deterministically (as map iteration
// order is non-deterministic).
func biggestKey(m map[string][]string) (key string, val []string) {
	for k, v := range m {
		if k >= key {
			key, val = k, v
		}
	}
	return key, val
}
