package protocbase

import (
	"bytes"
	"fmt"
	"text/template"
	"time"

	"github.com/Masterminds/sprig"
	"github.com/golang/protobuf/proto"
	"github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway/descriptor"
	"golang.org/x/tools/imports"
)

var extensions = map[string]*proto.ExtensionDesc{}

type BaseTemplateData struct {
	Now  time.Time
	File *descriptor.File

	registry *descriptor.Registry
}

var templateFuncs = map[string]interface{}{
	"option": getOption,
}

func RegisterTmplFunc(name string, f interface{}) {
	if _, found := templateFuncs[name]; found {
		panic(fmt.Sprintf("func with name %v is already registered", name))
	}

	templateFuncs[name] = f
}

func RegisterExtension(ext *proto.ExtensionDesc) {
	extensions[ext.Name] = ext
}

func GetBaseTemplateData(registry *descriptor.Registry, file *descriptor.File) *BaseTemplateData {
	return &BaseTemplateData{
		Now:      time.Now().UTC(),
		File:     file,
		registry: registry,
	}
}

func getOption(opts proto.Message, extName string) interface{} {
	extDesc := extensions[extName]

	if !proto.HasExtension(opts, extDesc) {
		return nil
	}

	ext, err := proto.GetExtension(opts, extDesc)
	if err != nil {
		panic(err)
	}

	return ext
}

func (data *BaseTemplateData) ResolveMsgType(msgType string) string {
	msg, err := data.registry.LookupMsg(data.File.GetPackage(), msgType)
	if err != nil {
		panic(err)
	}

	return msg.GoType(data.File.GoPkg.Path)
}

func (data *BaseTemplateData) ResolveFile(fileName string) *descriptor.File {
	file, err := data.registry.LookupFile(fileName)
	if err != nil {
		panic(err)
	}

	return file
}

func LoadTemplate(templateData []byte, err error) *template.Template {
	if err != nil {
		panic(err)
	}

	return template.Must(template.New("").
		Funcs(sprig.TxtFuncMap()).
		Funcs(templateFuncs).
		Parse(string(templateData)))
}

func GenerateFromTemplate(tmpl *template.Template, data interface{}) (string, error) {
	var tpl bytes.Buffer
	err := tmpl.Execute(&tpl, data)
	if err != nil {
		return "", err
	}

	tmplResult := tpl.Bytes()
	tmplResult, err = imports.Process(".", tmplResult, nil)
	return string(tmplResult), err
}

func GenerateFromBaseTemplate(tmpl *template.Template, registry *descriptor.Registry, file *descriptor.File) (string, error) {
	return GenerateFromTemplate(tmpl, GetBaseTemplateData(registry, file))
}