//go:build ignore
// +build ignore

// Generate runewidth_table.go from data at https://unicode.org/

package main

import (
	"bufio"
	"bytes"
	"fmt"
	"go/format"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"strings"
)

type rrange struct {
	lo rune
	hi rune
}

func generate(out io.Writer, v string, arr []rrange) {
	fmt.Fprintf(out, "var %s = table{\n\t", v)
	for i := 0; i < len(arr); i++ {
		fmt.Fprintf(out, "{0x%04X, 0x%04X},", arr[i].lo, arr[i].hi)
		if i < len(arr)-1 {
			if i%3 == 2 {
				fmt.Fprint(out, "\n\t")
			} else {
				fmt.Fprint(out, " ")
			}
		}
	}
	fmt.Fprintln(out, "\n}")
}

func shapeup(p *[]rrange) {
	arr := *p
	for i := 0; i < len(arr)-1; i++ {
		if arr[i].hi+1 == arr[i+1].lo {
			lo := arr[i].lo
			arr = append(arr[:i], arr[i+1:]...)
			arr[i].lo = lo
			i--
		}
	}
	*p = arr
}

func eastasian(out io.Writer, in io.Reader) error {
	scanner := bufio.NewScanner(in)

	dbl := []rrange{}
	amb := []rrange{}
	cmb := []rrange{}
	na := []rrange{}
	nu := []rrange{}
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "#") {
			continue
		}
		var r1, r2 rune
		var ss string
		n, err := fmt.Sscanf(line, "%x..%x ; %s", &r1, &r2, &ss)
		if err != nil || n == 2 {
			n, err = fmt.Sscanf(line, "%x ; %s", &r1, &ss)
			if err != nil || n != 2 {
				continue
			}
			r2 = r1
		}

		if strings.Index(line, "COMBINING") != -1 {
			cmb = append(cmb, rrange{
				lo: r1,
				hi: r2,
			})
		}

		switch ss {
		case "W", "F":
			dbl = append(dbl, rrange{
				lo: r1,
				hi: r2,
			})
		case "A":
			amb = append(amb, rrange{
				lo: r1,
				hi: r2,
			})
		case "Na":
			na = append(na, rrange{
				lo: r1,
				hi: r2,
			})
		case "N":
			nu = append(nu, rrange{
				lo: r1,
				hi: r2,
			})
		}
	}

	shapeup(&cmb)
	generate(out, "combining", cmb)
	fmt.Fprintln(out)

	shapeup(&dbl)
	generate(out, "doublewidth", dbl)
	fmt.Fprintln(out)

	shapeup(&amb)
	generate(out, "ambiguous", amb)
	fmt.Fprint(out)

	shapeup(&na)
	generate(out, "narrow", na)
	fmt.Fprintln(out)

	shapeup(&nu)
	generate(out, "neutral", nu)
	fmt.Fprintln(out)

	return nil
}

func emoji(out io.Writer, in io.Reader) error {
	scanner := bufio.NewScanner(in)

	for scanner.Scan() {
		line := scanner.Text()
		if strings.Index(line, "Extended_Pictographic=No") != -1 {
			break
		}
	}

	if scanner.Err() != nil {
		return scanner.Err()
	}

	arr := []rrange{}
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "#") {
			continue
		}
		var r1, r2 rune
		n, err := fmt.Sscanf(line, "%x..%x ", &r1, &r2)
		if err != nil || n == 1 {
			n, err = fmt.Sscanf(line, "%x ", &r1)
			if err != nil || n != 1 {
				continue
			}
			r2 = r1
		}
		if r2 < 0xFF {
			continue
		}

		arr = append(arr, rrange{
			lo: r1,
			hi: r2,
		})
	}

	shapeup(&arr)
	generate(out, "emoji", arr)

	return nil
}

func main() {
	var buf bytes.Buffer
	f := &buf
	fmt.Fprint(f, "// Code generated by script/generate.go. DO NOT EDIT.\n\n")

	fmt.Fprint(f, "package runewidth\n\n")

	resp, err := http.Get("https://unicode.org/Public/15.1.0/ucd/EastAsianWidth.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	eastasian(f, resp.Body)

	resp, err = http.Get("https://unicode.org/Public/15.1.0/ucd/emoji/emoji-data.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer resp.Body.Close()

	emoji(f, resp.Body)

	out, err := format.Source(f.Bytes())
	if err != nil {
		log.Fatal(err)
	}
	err = ioutil.WriteFile("runewidth_table.go", out, 0666)
	if err != nil {
		log.Fatal(err)
	}
}
