// +build tools

// Openapigen is a script to take the OpenAPI YAML file, turn it into a JSON
// document, and embed it into a source file for easy deployment.
package main

import (
	"crypto/sha256"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/format"
	"go/parser"
	"go/token"
	"log"
	"os"
	"path/filepath"
	"strings"

	"gopkg.in/yaml.v3"
)

const template = `
// Code generated by openapigen.go DO NOT EDIT.

package main

const (
	_openapiJSON     = ""
	_openapiJSONEtag = ""
)
`

func main() {
	inFile := flag.String("in", "../openapi.yaml", "input YAML file")
	outFile := flag.String("out", "discoveryhandler_gen.go", "output go file")
	pkgName := flag.String("name", "httptransport", "package name for generated file")
	flag.Parse()

	inF, err := os.Open(*inFile)
	if inF != nil {
		defer inF.Close()
	}
	if err != nil {
		log.Fatal(err)
	}

	tmp := map[interface{}]interface{}{}
	if err := yaml.NewDecoder(inF).Decode(&tmp); err != nil {
		log.Fatal(err)
	}
	embed, err := json.Marshal(convert(tmp))
	if err != nil {
		log.Fatal(err)
	}
	ck := sha256.Sum256(embed)

	fs := token.NewFileSet()
	file, err := parser.ParseFile(fs, filepath.Base(*outFile), strings.NewReader(template), parser.ParseComments)
	if err != nil {
		log.Fatal(err)
	}
	ast.Inspect(file, func(n ast.Node) bool {
		switch x := n.(type) {
		case *ast.File:
			x.Name = ast.NewIdent(*pkgName)
		case *ast.ValueSpec:
			id := x.Names[0]
			var v string
			switch id.Name {
			case "_openapiJSON":
				v = fmt.Sprintf("%#q", string(embed))
			case "_openapiJSONEtag":
				v = fmt.Sprintf("`\"%x\"`", ck)
			default:
				return true
			}
			x.Values = []ast.Expr{&ast.BasicLit{
				Kind:  token.STRING,
				Value: v,
			}}
		}
		return true
	})

	outF, err := os.OpenFile(*outFile, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer outF.Close()
	if err := format.Node(outF, fs, file); err != nil {
		log.Fatal(err)
	}
}

// Convert yoinked from:
// https://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct/40737676#40737676
func convert(i interface{}) interface{} {
	switch x := i.(type) {
	case map[interface{}]interface{}:
		m2 := map[string]interface{}{}
		for k, v := range x {
			m2[fmt.Sprint(k)] = convert(v)
		}
		return m2
	case []interface{}:
		for i, v := range x {
			x[i] = convert(v)
		}
	case map[string]interface{}:
		for k, v := range x {
			x[k] = convert(v)
		}
	}
	return i
}
