package ast

import (
	"encoding/json"
	"fmt"
	"sort"
	"strings"
	"time"
)

// MakeSushi generates a JSON string for an ast Table, which is compatible
// with BurntSushi's TOML testing tool (https://github.com/BurntSushi/toml-test)
func (t Table) MakeSushi() string {
	return MakeSushi(NewValue(TypeTable, t))
}

// MakeSushi generates a JSON string for an ast Value, which is compatible
// with BurntSushi's TOML testing tool (https://github.com/BurntSushi/toml-test)
func MakeSushi(value *Value) string {
	switch value.Type {
	case TypeString:
		return renderValue("string", value.Data[0].(string))
	case TypeInteger:
		return renderValue("integer", fmt.Sprintf("%d", value.Data[0].(int64)))
	case TypeFloat:
		return renderValue("float", fmt.Sprintf("%v", value.Data[0].(float64)))
	case TypeBool:
		return renderValue("bool", fmt.Sprintf("%t", value.Data[0].(bool)))
	case TypeOffsetDateTime:
		return renderValue("datetime", value.Data[0].(time.Time).Format(time.RFC3339Nano))
	case TypeLocalDateTime:
		return renderValue("local_datetime", value.Data[0].(time.Time).Format("2006-01-02 15:04:05.999999999"))
	case TypeLocalDate:
		return renderValue("local_date", value.Data[0].(time.Time).Format("2006-01-02"))
	case TypeLocalTime:
		return renderValue("local_time", value.Data[0].(time.Time).Format("15:04:05.999999999"))
	case TypeArrayOfTables:
		fallthrough
	case TypeArray:
		// BurntSushi's tests sees [ {inline: "table"}, {array: "definitions"} ]
		// as an array of tables, so here we accomodate for that situation
		// by checking for that case and render such inline array definition
		// as if it were an [[array.of.tables]].
		arr := value.Data[0].(*Array)
		values := make([]string, 0, arr.Length)
		for i := arr.First; i != nil; i = i.Next {
			values = append(values, MakeSushi(i.Value))
		}
		if arr.ItemType == TypeTable {
			return fmt.Sprintf("[%s]", strings.Join(values, ", "))
		}
		return fmt.Sprintf(`{"type": "array", "value": [%s]}`, strings.Join(values, ", "))
	case TypeImplicitTable:
		fallthrough
	case TypeTable:
		pairs := value.Data[0].(Table)
		keys := make([]string, len(pairs))
		i := 0
		for k := range pairs {
			keys[i] = k
			i++
		}
		sort.Strings(keys)
		values := make([]string, len(pairs))
		for i, k := range keys {
			values[i] = fmt.Sprintf("%q: %s", k, MakeSushi(pairs[k]))
		}
		return fmt.Sprintf("{%s}", strings.Join(values, ", "))
	default:
		panic(fmt.Sprintf("Unhandled data type: %s", value.Type))
	}
}

func renderValue(t string, v string) string {
	return fmt.Sprintf("{%q: %q, %q: %s}", "type", t, "value", toJSON(v))
}

func toJSON(s string) string {
	j, err := json.Marshal(s)
	if err != nil {
		panic(fmt.Sprintf("unable to JSON encode %q: %s", s, err))
	}
	return string(j)
}
