package main

import (
	"bytes"
	"fmt"
	"go/format"
	"reflect"
	"strings"
	"text/template"
	. "time"
)

// Keep sorted.
var types = []interface{}{
	nil,
	new(func() Duration),
	new(func() Month),
	new(func() Time),
	new(func() Weekday),
	new(func() []byte),
	new(func() []interface{}),
	new(func() bool),
	new(func() byte),
	new(func() float64),
	new(func() int),
	new(func() int64),
	new(func() interface{}),
	new(func() map[string]interface{}),
	new(func() rune),
	new(func() string),
	new(func() uint),
	new(func() uint64),
	new(func(Duration) Duration),
	new(func(Duration) Time),
	new(func(Time) Duration),
	new(func(Time) bool),
	new(func([]interface{}, string) string),
	new(func([]string, string) string),
	new(func(bool) bool),
	new(func(bool) float64),
	new(func(bool) int),
	new(func(bool) string),
	new(func(float64) bool),
	new(func(float64) float64),
	new(func(float64) int),
	new(func(float64) string),
	new(func(int) bool),
	new(func(int) float64),
	new(func(int) int),
	new(func(int) string),
	new(func(int, int) int),
	new(func(int, int) string),
	new(func(int64) Time),
	new(func(string) []string),
	new(func(string) bool),
	new(func(string) float64),
	new(func(string) int),
	new(func(string) string),
	new(func(string, byte) int),
	new(func(string, int) int),
	new(func(string, rune) int),
	new(func(string, string) bool),
	new(func(string, string) string),
	new(func(interface{}) bool),
	new(func(interface{}) float64),
	new(func(interface{}) int),
	new(func(interface{}) string),
	new(func(interface{}) interface{}),
	new(func(interface{}) []interface{}),
	new(func(interface{}) map[string]interface{}),
	new(func([]interface{}) interface{}),
	new(func([]interface{}) []interface{}),
	new(func([]interface{}) map[string]interface{}),
	new(func(interface{}, interface{}) bool),
	new(func(interface{}, interface{}) string),
	new(func(interface{}, interface{}) interface{}),
	new(func(interface{}, interface{}) []interface{}),
}

func main() {
	data := struct {
		Index string
		Code  string
	}{}

	for i, t := range types {
		if i == 0 {
			continue
		}
		fn := reflect.ValueOf(t).Elem().Type()
		data.Index += fmt.Sprintf("%v: new(%v),\n", i, fn)
		data.Code += fmt.Sprintf("case %d:\n", i)
		args := make([]string, fn.NumIn())
		for j := fn.NumIn() - 1; j >= 0; j-- {
			cast := fmt.Sprintf(".(%v)", fn.In(j))
			if fn.In(j).Kind() == reflect.Interface {
				cast = ""
			}
			data.Code += fmt.Sprintf("arg%v := vm.pop()%v\n", j+1, cast)
			args[j] = fmt.Sprintf("arg%v", j+1)
		}
		data.Code += fmt.Sprintf("return fn.(%v)(%v)\n", fn, strings.Join(args, ", "))
	}

	var b bytes.Buffer
	err := template.Must(
		template.New("func_types").
			Parse(source),
	).Execute(&b, data)
	if err != nil {
		panic(err)
	}

	formatted, err := format.Source(b.Bytes())
	if err != nil {
		panic(err)
	}
	fmt.Print(string(formatted))
}

const source = `// Code generated by vm/func_types/main.go. DO NOT EDIT.

package vm

import (
	"fmt"
	"time"
)

var FuncTypes = []interface{}{
	{{ .Index }}
}

func (vm *VM) call(fn interface{}, kind int) interface{} {
	switch kind {
	{{ .Code }}
	}
	panic(fmt.Sprintf("unknown function kind (%v)", kind))
}
`
