// pp -- KEKS pretty printer
// Copyright (C) 2024-2025 Sergey Matveev <stargrave@stargrave.org>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as
// published by the Free Software Foundation, version 3 of the License.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this program.  If not, see <http://www.gnu.org/licenses/>.

package main

import (
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"strconv"
	"time"

	"go.cypherpunks.su/keks"
	"go.cypherpunks.su/keks/be"
	"go.cypherpunks.su/keks/types"
	"go.cypherpunks.su/tai64n/v4"
)

func tclTAI(t time.Time, ns, as int) {
	utc, isLeap := tai64n.Leapsecs.Sub(t)
	year := utc.Year()
	if year < 0 {
		year = -year
	}
	if isLeap || year > 9999 {
		fmt.Printf("TAI64 %d %d %d", t.Unix(), ns, as)
	} else {
		s := `TAI64 [ToTAI [ISOToSec "` + utc.Format("2006-01-02 15:04:05") + `"]]`
		if ns > 0 {
			s += " " + strconv.Itoa(ns)
		}
		if as > 0 {
			s += " " + strconv.Itoa(as)
		}
		fmt.Print(s)
	}
}

func tcl(iter *keks.Iterator, count int, inList, inMap bool) {
	for range count {
		if !iter.Next() {
			panic("unexpected")
		}
		depth := iter.Depth
		var key string
		if inMap {
			key = iter.Str()
			if !iter.Next() {
				panic("unexpected")
			}
		}
		prindent(depth)
		if inMap {
			fmt.Print(key, " ")
		}
		if inList || inMap {
			fmt.Print("{")
		}
		var listOrMap bool
		switch iter.T {
		case types.List:
			listOrMap = true
			fmt.Println("LIST {")
			tcl(iter, iter.Len(), true, false)
			prindent(depth)
			fmt.Print("}")
		case types.Map:
			listOrMap = true
			fmt.Println("MAP {")
			tcl(iter, iter.Len(), false, true)
			prindent(depth)
			fmt.Print("}")
		}
		if !listOrMap {
			switch iter.T {
			case types.NIL:
				fmt.Print("NIL")
			case types.Bool:
				if iter.Bool() {
					fmt.Print("TRUE")
				} else {
					fmt.Print("FALSE")
				}
			case types.Hexlet:
				h := iter.Hexlet()
				fmt.Printf("HEXLET %s", h.UUID())
			case types.UInt:
				fmt.Printf("INT %d", iter.UInt())
			case types.Int:
				fmt.Printf("INT %d", iter.Int())
			case types.BigInt:
				fmt.Printf("INT %d", iter.BigInt())
			case types.Float:
				f := iter.Float()
				if f.NaN {
					fmt.Printf("FLOAT nan")
				} else if f.Pinf {
					fmt.Printf("FLOAT +inf")
				} else if f.Ninf {
					fmt.Printf("FLOAT -inf")
				} else {
					fmt.Printf("FLOAT {%d %d}", f.M, f.E)
				}
			case types.Blob:
				blob := iter.Blob()
				v, err := io.ReadAll(blob.Reader())
				if err != nil {
					log.Fatal(err)
				}
				fmt.Printf("BLOB %d [binary decode hex \"%s\"]",
					blob.ChunkLen, hex.EncodeToString(v))
			case types.TAI64:
				t := iter.TAI64().Time()
				tclTAI(t, 0, 0)
			case types.TAI64N:
				t := iter.TAI64N().Time()
				tclTAI(t, t.Nanosecond(), 0)
			case types.TAI64NA:
				tai := iter.TAI64NA()
				var n tai64n.TAI64N
				copy(n[:], tai[:])
				t := n.Time()
				tclTAI(t, t.Nanosecond(), int(be.Get(tai[12:])))
			case types.Magic:
				allPrintable := true
				magic := iter.Magic()
				for _, b := range []byte(magic) {
					if !strconv.IsPrint(rune(b)) {
						allPrintable = false
					}
				}
				if allPrintable {
					fmt.Printf("MAGIC \"%s\"", magic)
				} else {
					fmt.Printf("MAGIC [binary decode hex \"%s\"]", []byte(magic))
				}
			case types.Bin:
				fmt.Printf("BIN [binary decode hex \"%s\"]",
					hex.EncodeToString(iter.Bin()))
			case types.Str:
				fmt.Printf("STR \"%s\"", iter.Str())
			case types.Raw:
				fmt.Printf("RAW [binary decode hex \"%s\"]",
					hex.EncodeToString(iter.Raw()))
			default:
				fmt.Print("???")
			}
		}
		if inList || inMap {
			fmt.Print("}")
		}
		fmt.Println("")
	}
}
