// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build ignore

// This program generates table.go from
// https://material.google.com/style/color.html
package main

import (
	"bytes"
	"fmt"
	"go/format"
	"image/color"
	"io"
	"log"
	"net/http"
	"strings"

	"golang.org/x/net/html"
	"golang.org/x/net/html/atom"
)

type matchFunc func(*html.Node) bool

func appendAll(dst []*html.Node, n *html.Node, mf matchFunc) []*html.Node {
	if mf(n) {
		dst = append(dst, n)
	}
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		dst = appendAll(dst, c, mf)
	}
	return dst
}

func match(a atom.Atom, namespace, key, value string) matchFunc {
	return func(n *html.Node) bool {
		return n.DataAtom == a && strings.HasPrefix(getAttr(n, namespace, key), value)
	}
}

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

func getText(n *html.Node) string {
	if n.FirstChild == nil || n.FirstChild.Type != html.TextNode {
		return ""
	}
	return n.FirstChild.Data
}

var unhex = [256]byte{
	'0': 0,
	'1': 1,
	'2': 2,
	'3': 3,
	'4': 4,
	'5': 5,
	'6': 6,
	'7': 7,
	'8': 8,
	'9': 9,
	'a': 10,
	'b': 11,
	'c': 12,
	'd': 13,
	'e': 14,
	'f': 15,
}

func parseRGB(s string) color.RGBA {
	if len(s) != 7 || s[0] != '#' {
		return color.RGBA{}
	}
	return color.RGBA{
		R: unhex[s[1]]<<4 | unhex[s[2]],
		G: unhex[s[3]]<<4 | unhex[s[4]],
		B: unhex[s[5]]<<4 | unhex[s[6]],
		A: 0xff,
	}
}

type entry struct {
	name string
	rgba color.RGBA
}

func extractColors(tree *html.Node) (ret []entry) {
	for _, table := range appendAll(nil, tree, match(atom.Section, "", "class", "color-group")) {
		name := ""
		for _, nameNode := range appendAll(nil, table, match(atom.Span, "", "class", "name")) {
			name = strings.Replace(getText(nameNode), " ", "", -1)
			break
		}
		shades := appendAll(nil, table, match(atom.Span, "", "class", "shade"))
		hexes := appendAll(nil, table, match(atom.Span, "", "class", "hex"))
		if len(shades) != len(hexes) || len(shades) == 0 {
			continue
		}
		// Remove the duplicated 500 shade at the start of the list.
		if getText(shades[0]) == "500" {
			shades, hexes = shades[1:], hexes[1:]
		}
		for i, shade := range shades {
			ret = append(ret, entry{
				name + getText(shade),
				parseRGB(getText(hexes[i])),
			})
		}
	}
	return ret
}

const preamble = `// generated by go generate; DO NOT EDIT.

package colornames

import "image/color"

`

func writeColorNames(w io.Writer, entries []entry) {
	fmt.Fprintln(w, preamble)
	fmt.Fprintln(w, "// Map contains named colors defined in the Material Design style guide.")
	fmt.Fprintln(w, "var Map = map[string]color.RGBA{")
	for _, e := range entries {
		c := e.rgba
		fmt.Fprintf(w, "%q:color.RGBA{%#02x, %#02x, %#02x, %#02x}, // rgb(%d, %d, %d)\n",
			e.name, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
	}
	fmt.Fprintln(w, "}\n")
	fmt.Fprintln(w, "// Names contains the color names defined in the Material Design style guide.")
	fmt.Fprintln(w, "var Names = []string{")
	for _, e := range entries {
		fmt.Fprintf(w, "%q,\n", e.name)
	}
	fmt.Fprintln(w, "}\n")
	fmt.Fprintln(w, "var (")
	for _, e := range entries {
		c := e.rgba
		fmt.Fprintf(w, "%s=color.RGBA{%#02x, %#02x, %#02x, %#02x} // rgb(%d, %d, %d)\n",
			e.name, c.R, c.G, c.B, c.A, c.R, c.G, c.B)
	}
	fmt.Fprintln(w, ")")
}

const url = "https://material.google.com/style/color.html"

func main() {
	res, err := http.Get(url)
	if err != nil {
		log.Fatalf("Couldn't read from %s: %s\n", url, err)
	}
	defer res.Body.Close()

	tree, err := html.Parse(res.Body)
	if err != nil {
		log.Fatalf("Couldn't parse %s: %s\n", url, err)
	}

	buf := &bytes.Buffer{}
	writeColorNames(buf, extractColors(tree))
	fmted, err := format.Source(buf.Bytes())
	if err != nil {
		log.Fatalf("Error while formatting code: %s\n", err)
	}

	if err := os.WriteFile("table.go", fmted, 0644); err != nil {
		log.Fatalf("Error writing table.go: %s\n", err)
	}
}
