package main

import (
	/*
		#include <stdint.h>
	*/
	"C"
	"bytes"
	"fmt"
	"html/template"
	"image"
	_ "image/png"
	"os"
	"strconv"
	"strings"

	"embed"

	"github.com/fogleman/gg"
	"golang.org/x/image/font"
	"golang.org/x/image/font/opentype"
	"golang.org/x/net/html"

	"encoding/json"

	"github.com/kenshaw/escpos"
)

const (
	canvasWidth = 550
	padding     = 5
	lineGap     = 12
)

//go:embed static/*
var fontFs embed.FS

//export GenPNG
func GenPNG(outputPath *C.char, payload *C.char, tmpl *C.char) *C.char {
	goPath := C.GoString(outputPath)
	goPayload := C.GoString(payload)
	goTmpl := C.GoString(tmpl)

	data := make(map[string]interface{})
	err := json.Unmarshal([]byte(goPayload), &data)
	if err != nil {
		return NewErr(err)
	}

	htmlStr, err := renderTemplate(goTmpl, data)
	if err != nil {
		return NewErr(err)
	}

	htmlStr = uni2zg(htmlStr)
	root, err := html.Parse(strings.NewReader(htmlStr))
	if err != nil {
		return NewErr(err)
	}

	fontBytes, err := fontFs.ReadFile("static/Zawgyi-One.ttf")
	if err != nil {
		return NewErr(err)
	}
	ttfFont, err := opentype.Parse(fontBytes)
	if err != nil {
		return NewErr(err)
	}

	face, err := opentype.NewFace(ttfFont, &opentype.FaceOptions{
		Size:    24,
		DPI:     72,
		Hinting: font.HintingFull,
	})
	if err != nil {
		return NewErr(err)
	}

	// First pass: compute required height
	dummyDC := gg.NewContext(canvasWidth, 150)
	dummyDC.SetFontFace(face)
	bodyNode := findNode(root, "body")
	if bodyNode == nil {
		bodyNode = root
	}

	computedHeight := computeHeight(bodyNode, padding, dummyDC, face)

	// Second pass: actual rendering
	dc := gg.NewContext(canvasWidth, computedHeight+50)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	dc.SetFontFace(face)

	y := padding
	renderNode(dc, bodyNode, &y, face)

	err = dc.SavePNG(goPath)
	if err != nil {
		return NewErr(err)
	}

	// PrintReceipt(goPath)
	return NewOk(nil)
}

func renderTemplate(tmp string, data map[string]interface{}) (string, error) {
	tmpl := template.Must(template.New("mytemplate").Parse(tmp))
	var buf bytes.Buffer
	if err := tmpl.Execute(&buf, data); err != nil {
		return "", err
	}
	return buf.String(), nil
}

func renderNode(dc *gg.Context, n *html.Node, y *int, face font.Face) {
	if n.Type == html.ElementNode {
		switch n.Data {
		case "h1":
			drawTextBlock(dc, getText(n), 28, y, face)
		case "p":
			drawTextBlock(dc, getText(n), 24, y, face)
		case "img":
			drawImage(dc, n, y)
		case "table":
			renderTable(dc, n, y, face)
		}
	}

	for c := n.FirstChild; c != nil; c = c.NextSibling {
		renderNode(dc, c, y, face)
	}
}

func drawTextBlock(dc *gg.Context, text string, size float64, y *int, face font.Face) {
	dc.SetFontFace(face)
	lines := wordWrap(dc, text, canvasWidth-padding*2)
	for _, line := range lines {
		dc.SetRGB(0, 0, 0)
		dc.DrawString(line, padding, float64(*y))
		*y += int(size) + lineGap
	}
	*y += 5
}

func wordWrap(dc *gg.Context, text string, maxWidth int) []string {
	words := strings.Fields(text)
	if len(words) == 0 {
		return []string{}
	}
	var lines []string
	current := words[0]
	for _, w := range words[1:] {
		test := current + " " + w
		wWidth, _ := dc.MeasureString(test)
		if int(wWidth) > maxWidth {
			lines = append(lines, current)
			current = w
		} else {
			current = test
		}
	}
	lines = append(lines, current)
	return lines
}

func drawImage(dc *gg.Context, n *html.Node, y *int) {
	src := getAttr(n, "src")
	file, err := os.Open(src)
	if err != nil {
		return
	}
	defer file.Close()
	img, _, _ := image.Decode(file)
	widthStr := getAttr(n, "width")
	if widthStr != "" {
		w, _ := strconv.Atoi(widthStr)
		scale := float64(w) / float64(img.Bounds().Dx())
		dc.Push()
		dc.Scale(scale, scale)
		dc.DrawImage(img, padding, *y)
		dc.Pop()
		*y += int(float64(img.Bounds().Dy()) * scale)
	} else {
		dc.DrawImage(img, padding, *y)
		*y += img.Bounds().Dy()
	}
	*y += 10
}

func renderTable(dc *gg.Context, table *html.Node, y *int, face font.Face) {
	rows := extractRows(table)
	if len(rows) == 0 {
		return
	}
	colCount := len(rows[0])
	cellWidth := (canvasWidth - padding*2) / colCount
	dc.SetFontFace(face)
	for _, row := range rows {
		x := padding
		for _, cell := range row {
			dc.DrawRectangle(float64(x), float64(*y), float64(cellWidth), 30)
			dc.Stroke()
			dc.SetRGB(0, 0, 0)
			dc.DrawString(cell, float64(x+8), float64(*y+20))
			x += cellWidth
		}
		*y += 30
	}
	*y += 10
}

func extractRows(table *html.Node) [][]string {
	var rows [][]string
	var traverse func(*html.Node)
	traverse = func(n *html.Node) {
		if n.Type == html.ElementNode && n.Data == "tr" {
			var row []string
			for td := n.FirstChild; td != nil; td = td.NextSibling {
				if td.Type == html.ElementNode && (td.Data == "td" || td.Data == "th") {
					row = append(row, getText(td))
				}
			}
			if len(row) > 0 {
				rows = append(rows, row)
			}
		}
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			traverse(c)
		}
	}
	traverse(table)
	return rows
}

func getText(n *html.Node) string {
	if n == nil {
		return ""
	}
	if n.Type == html.TextNode {
		return strings.TrimSpace(n.Data)
	}
	var buf strings.Builder
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		buf.WriteString(getText(c))
	}
	return buf.String()
}

func getAttr(n *html.Node, key string) string {
	for _, a := range n.Attr {
		if a.Key == key {
			return a.Val
		}
	}
	return ""
}

func findNode(n *html.Node, tag string) *html.Node {
	if n.Type == html.ElementNode && n.Data == tag {
		return n
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		if res := findNode(c, tag); res != nil {
			return res
		}
	}
	return nil
}

func computeHeight(n *html.Node, currentY int, dc *gg.Context, face font.Face) int {
	y := currentY
	if n.Type == html.ElementNode {
		switch n.Data {
		case "h1":
			dc.SetFontFace(face)
			lines := wordWrap(dc, getText(n), canvasWidth-padding*2)
			y += len(lines)*28 + len(lines)*lineGap + 5
		case "p":
			dc.SetFontFace(face)
			lines := wordWrap(dc, getText(n), canvasWidth-padding*2)
			y += len(lines)*18 + len(lines)*lineGap + 5
		case "table":
			rows := extractRows(n)
			y += len(rows)*30 + 10
		case "img":
			widthStr := getAttr(n, "width")
			h := 100
			if widthStr != "" {
				h = 100
			}
			y += h + 10
		}
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		y = computeHeight(c, y, dc, face)
	}
	return y
}

func printImg(prt *escpos.Escpos, imgPath string) error {
	imgFile, err := os.Open(imgPath)
	if err != nil {
		fmt.Println(err)
		return err
	}

	img, _, err := image.Decode(imgFile)
	defer imgFile.Close()
	if err != nil {
		fmt.Println(err)
		return err
	}

	gray := toMonochrome(img)
	data := escposRaster(gray)

	_, err = prt.WriteRaw(data)
	return err
}
