// gen_blocks.go generates block information.

//+build ignore

package main

import (
	"encoding/json"
	"fmt"
	"go/ast"
	"go/format"
	"go/token"
	"net/http"
	"os"
	"reflect"
	"strconv"

	"github.com/iancoleman/strcase"
)

const (
	infoURL = "https://raw.githubusercontent.com/PrismarineJS/minecraft-data/master/data/pc/1.16.2/items.json"
)

type Item struct {
	ID          uint32 `json:"id"`
	DisplayName string `json:"displayName"`
	Name        string `json:"name"`
	StackSize   uint   `json:"stackSize"`
}

func downloadInfo() ([]Item, error) {
	resp, err := http.Get(infoURL)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	var data []Item
	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
		return nil, err
	}
	return data, nil
}

func makeItemDeclaration(blocks []Item) *ast.DeclStmt {
	out := &ast.DeclStmt{Decl: &ast.GenDecl{Tok: token.VAR}}

	for _, b := range blocks {
		t := reflect.TypeOf(b)
		fields := make([]ast.Expr, t.NumField())

		for i := 0; i < t.NumField(); i++ {
			ft := t.Field(i)

			var val ast.Expr
			switch ft.Type.Kind() {
			case reflect.Uint32, reflect.Int, reflect.Uint:
				val = &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i))}
			case reflect.Float64:
				val = &ast.BasicLit{Kind: token.FLOAT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i))}
			case reflect.String:
				val = &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(reflect.ValueOf(b).Field(i).String())}
			case reflect.Bool:
				val = &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(reflect.ValueOf(b).Field(i).Bool())}

			case reflect.Slice:
				val = &ast.CompositeLit{
					Type: &ast.ArrayType{
						Elt: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
					},
				}
				v := reflect.ValueOf(b).Field(i)
				switch ft.Type.Elem().Kind() {
				case reflect.Uint32, reflect.Int:
					for x := 0; x < v.Len(); x++ {
						val.(*ast.CompositeLit).Elts = append(val.(*ast.CompositeLit).Elts, &ast.BasicLit{
							Kind:  token.INT,
							Value: fmt.Sprint(v.Index(x)),
						})
					}
				}

			case reflect.Map:
				// Must be the NeedsTools map of type map[uint32]bool.
				m := &ast.CompositeLit{
					Type: &ast.MapType{
						Key:   &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Key().Name()},
						Value: &ast.BasicLit{Kind: token.IDENT, Value: ft.Type.Elem().Name()},
					},
				}
				iter := reflect.ValueOf(b).Field(i).MapRange()
				for iter.Next() {
					m.Elts = append(m.Elts, &ast.KeyValueExpr{
						Key:   &ast.BasicLit{Kind: token.INT, Value: fmt.Sprint(iter.Key().Uint())},
						Value: &ast.BasicLit{Kind: token.IDENT, Value: fmt.Sprint(iter.Value().Bool())},
					})
				}

				val = m
			}

			fields[i] = &ast.KeyValueExpr{
				Key:   &ast.Ident{Name: ft.Name},
				Value: val,
			}
		}

		out.Decl.(*ast.GenDecl).Specs = append(out.Decl.(*ast.GenDecl).Specs, &ast.ValueSpec{
			Names: []*ast.Ident{{Name: strcase.ToCamel(b.Name)}},
			Values: []ast.Expr{
				&ast.CompositeLit{
					Type: &ast.Ident{Name: reflect.TypeOf(b).Name()},
					Elts: fields,
				},
			},
		})
	}

	return out
}

func main() {
	items, err := downloadInfo()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(1)
	}

	fmt.Println(`// Package item stores information about items in Minecraft.
package item

import (
	"math"
)

// ID describes the numeric ID of an item.
type ID uint32

// Item describes information about a type of item.
type Item struct {
	ID          ID
	DisplayName string
	Name        string
	StackSize   uint
}

`)
	format.Node(os.Stdout, token.NewFileSet(), makeItemDeclaration(items))

	fmt.Println()
	fmt.Println()
	fmt.Println("// ByID is an index of minecraft items by their ID.")
	fmt.Println("var ByID = map[ID]*Item{")
	for _, i := range items {
		fmt.Printf("  %d: &%s,\n", i.ID, strcase.ToCamel(i.Name))
	}
	fmt.Println("}")
	fmt.Println()

	fmt.Println("// ByName is an index of minecraft items by their name.")
	fmt.Println("var ByName = map[string]*Item{")
	for _, i := range items {
		fmt.Printf("  %q: &%s,\n", i.Name, strcase.ToCamel(i.Name))
	}
	fmt.Println("}")
	fmt.Println()
}
