// 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"
	"slices"
	"strconv"
	"strings"
	"time"

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

func isQuiet(where []string) bool {
	if len(pth) == 0 {
		return false
	}
	if len(pth) > len(where) {
		return true
	}
	return !slices.Equal(where[:len(pth)], pth)
}

func prindent(depth int) {
	fmt.Print(strings.Repeat("  ", depth))
}

func hexenc(b []byte) string {
	return strings.ToUpper(hex.EncodeToString(b))
}

func printbin(s []byte) {
	sLen := len(s)
	var dots string
	if sLen > int(*MaxStrLen) {
		s = s[:int(*MaxStrLen)]
		dots = "..."
	}
	if *onlyV {
		fmt.Println(hexenc(s))
	} else {
		fmt.Printf("%s%d:%s%s%s\n", Magenta, sLen, Reset, hexenc(s), dots)
	}
}

func printer(iter *keks.Iterator, where []string, count int, inList, inMap bool) {
	var quiet bool
	for i := range count {
		if !iter.Next() {
			panic("unexpected")
		}
		depth := iter.Depth
		var key string
		if inMap {
			key = iter.Str()
			if !iter.Next() {
				panic("unexpected")
			}
		}
		if inList {
			where[len(where)-1] = strconv.Itoa(i)
		} else if inMap {
			where[len(where)-1] = key
		}
		quiet = isQuiet(where)
		if !quiet && !*onlyV {
			fmt.Print(Blue + strconv.Itoa(depth) + Reset)
			fmt.Printf(" %s%03d%s ", Red, iter.Offset(), Reset)
			prindent(depth)
			if inList {
				fmt.Printf("%s%d:%s ", Yellow, i, Reset)
			} else if inMap {
				fmt.Printf("%s%s:%s ", Green, key, Reset)
			}
		}
		var listOrMap bool
		switch iter.T {
		case types.List:
			listOrMap = true
			if !quiet && !*onlyV {
				fmt.Printf("[ %s%d%s\n", Cyan, iter.Len(), Reset)
			}
			printer(iter, append(where, "0"), iter.Len(), true, false)
			if !quiet && !*onlyV {
				prindent(depth)
				fmt.Println("     " + " ]")
			}
		case types.Map:
			listOrMap = true
			if !quiet && !*onlyV {
				fmt.Printf("{ %s%d%s\n", Cyan, iter.Len(), Reset)
			}
			printer(iter, append(where, key), iter.Len(), false, true)
			if !quiet && !*onlyV {
				prindent(depth)
				fmt.Println("     " + " }")
			}
		}
		if listOrMap || quiet {
			continue
		}
		switch iter.T {
		case types.NIL:
			fmt.Println("NIL")
		case types.Bool:
			if iter.Bool() {
				fmt.Println("TRUE")
			} else {
				fmt.Println("FALSE")
			}
		case types.Hexlet:
			h := iter.Hexlet()
			if *onlyV {
				fmt.Println(h.UUID())
			} else {
				fmt.Println(h.UUID(), h.IP())
			}
		case types.UInt:
			fmt.Println(iter.UInt())
		case types.Int:
			fmt.Println(iter.Int())
		case types.BigInt:
			fmt.Println(iter.BigInt())
		case types.Float:
			f := iter.Float()
			if f.NaN {
				fmt.Println("NaN")
			} else if f.Pinf {
				fmt.Println("+inf")
			} else if f.Ninf {
				fmt.Println("-inf")
			} else {
				fmt.Printf("%s (m=%d e=%d)\n", f.BigFloat().String(), f.M, f.E)
			}
		case types.Blob:
			blob := iter.Blob()
			if *onlyV {
				v, err := io.ReadAll(blob.Reader())
				if err != nil {
					log.Fatal(err)
				}
				fmt.Println(hex.EncodeToString(v))
				break
			}
			fmt.Printf("BLOB[ %s%d l=%d%s\n",
				Cyan, len(blob.Chunks), blob.ChunkLen, Reset)
			off := iter.Offset() + 1 + 8
			for i, chunk := range blob.Chunks {
				fmt.Print(Blue + strconv.Itoa(depth+1) + Reset)
				fmt.Printf(" %s%03d%s ", Red, off, Reset)
				prindent(depth + 1)
				fmt.Printf("%s%d:%s ", Yellow, i, Reset)
				printbin([]byte(chunk))
				off += int64(len(chunk)) + 1
			}
			prindent(depth)
			fmt.Println("     " + " ]")
		case types.TAI64:
			t := iter.TAI64().Time()
			utc, isLeap := tai64n.Leapsecs.Sub(t)
			if isLeap {
				fmt.Printf("TAI64(%s TAI, leap)\n", t.Format(time.DateTime))
			} else {
				fmt.Printf("TAI64(%s TAI, %s UTC)\n",
					t.Format(time.DateTime), utc.Format(time.DateTime))
			}
		case types.TAI64N:
			t := iter.TAI64N().Time()
			utc, isLeap := tai64n.Leapsecs.Sub(t)
			if isLeap {
				fmt.Printf("TAI64N(%s TAI, leap)\n",
					t.Format(time.DateTime+".000000000"))
			} else {
				fmt.Printf("TAI64N(%s TAI, %s UTC)\n",
					t.Format(time.DateTime+".000000000"),
					utc.Format(time.DateTime+".000000000"))
			}
		case types.TAI64NA:
			t := iter.TAI64NA()
			fmt.Printf("TAI64NA(%s)\n", hexenc(t[:]))
		case types.Magic:
			var builder strings.Builder
			for _, b := range []byte(iter.Magic()) {
				if strconv.IsPrint(rune(b)) {
					builder.WriteByte(b)
				} else {
					builder.WriteString(fmt.Sprintf("\\x%02x", b))
				}
			}
			fmt.Printf("Magic(%s)\n", builder.String())
		case types.Bin:
			printbin(iter.Bin())
		case types.Str:
			if !*onlyV {
				fmt.Print(`"`)
			}
			s := iter.Str()
			sLen := len(s)
			if sLen > int(*MaxStrLen) {
				fmt.Print(s[:int(*MaxStrLen)])
				fmt.Print("...")
			} else {
				fmt.Print(s)
			}
			if !*onlyV {
				fmt.Print(`"`)
			}
			fmt.Println("")
		case types.Raw:
			fmt.Printf("(l=%d v=%s)\n", len(iter.Raw()), hexenc(iter.Raw()))
		default:
			fmt.Println("???")
		}
	}
}
