package main

import (
	"bytes"
	"crypto/sha256"
	"encoding/base64"
	"flag"
	"fmt"
	"image"
	"image/color"
	"image/png"
	"os"
)

// OptimizedIdenticon with indexed colors for smaller file sizes
type OptimizedIdenticon struct {
	source []byte
	size   int
}

// NewOptimizedIdenticon creates a generator with indexed colors
func NewOptimizedIdenticon(source []byte) *OptimizedIdenticon {
	return &OptimizedIdenticon{
		source: source,
		size:   256,
	}
}

// NewOptimizedIdenticonWithSize creates a generator with custom size
func NewOptimizedIdenticonWithSize(source []byte, size int) *OptimizedIdenticon {
	return &OptimizedIdenticon{
		source: source,
		size:   size,
	}
}

// getBit returns the n-th bit (0-indexed) from source
func (identicon *OptimizedIdenticon) getBit(n int) bool {
	if len(identicon.source) == 0 || n < 0 {
		return false
	}
	byteIndex := n / 8
	bitIndex := n % 8
	if byteIndex >= len(identicon.source) {
		return false
	}
	return (identicon.source[byteIndex]>>bitIndex)&1 == 1
}

// getByte returns the n-th byte, wraps around if needed
func (identicon *OptimizedIdenticon) getByte(n int) byte {
	if len(identicon.source) == 0 {
		return 0
	}
	return identicon.source[n%len(identicon.source)]
}

// getColorIndices returns color indices for indexed version
func (identicon *OptimizedIdenticon) getColorIndices() (primaryIndex, secondaryIndex, bgIndex uint8) {
	if len(identicon.source) < 32 {
		return 0, 1, 2
	}

	// Primary color index (4 bits → 16 colors)
	primaryIndex = 0
	for i := 0; i < 4; i++ {
		if identicon.getBit(248 + i) {
			primaryIndex |= 1 << i
		}
	}
	primaryIndex %= 16

	// Secondary color index (4 bits → 16 colors)
	secondaryIndex = 0
	for i := 0; i < 4; i++ {
		if identicon.getBit(244 + i) {
			secondaryIndex |= 1 << i
		}
	}
	secondaryIndex %= 16

	// Background choice (2 bits → 4 options)
	bgChoice := 0
	for i := 0; i < 2; i++ {
		if identicon.getBit(252 + i) {
			bgChoice |= 1 << i
		}
	}
	bgIndex = uint8(bgChoice % 3) // 0, 1, or 2

	return primaryIndex, secondaryIndex, bgIndex
}

// generatePixelPattern generates 5x5 symmetric pixel grid
func (identicon *OptimizedIdenticon) generatePixelPattern() ([]bool, []bool) {
	primary := make([]bool, 25)
	secondary := make([]bool, 25)

	// Use bits 0-14 for primary pattern
	bitIndex := 0
	for row := 0; row < 5; row++ {
		for col := 0; col < 3; col++ {
			paint := identicon.getBit(bitIndex)
			bitIndex++

			ix := row*5 + col
			mirrorIx := row*5 + (4 - col)
			primary[ix] = paint
			primary[mirrorIx] = paint
		}
	}

	// Use bits 15-29 for secondary pattern
	for row := 0; row < 5; row++ {
		for col := 0; col < 3; col++ {
			paint := identicon.getBit(bitIndex)
			bitIndex++

			ix := row*5 + col
			mirrorIx := row*5 + (4 - col)
			secondary[ix] = paint
			secondary[mirrorIx] = paint
		}
	}

	return primary, secondary
}

// createPalette creates an optimized palette with only necessary colors
func createPalette(primaryIdx, secondaryIdx, bgIdx uint8, darkMode bool) color.Palette {
	// Optimized color palettes - only 16 colors per palette
	primaryPalette := []color.Color{
		color.RGBA{0x00, 0xbf, 0x93, 0xff}, // turquoise
		color.RGBA{0x2d, 0xcc, 0x70, 0xff}, // mint
		color.RGBA{0x42, 0xe4, 0x53, 0xff}, // green
		color.RGBA{0xf1, 0xc4, 0x0f, 0xff}, // yellowOrange
		color.RGBA{0xe6, 0x7f, 0x22, 0xff}, // brown
		color.RGBA{0xff, 0x94, 0x4e, 0xff}, // orange
		color.RGBA{0xe8, 0x4c, 0x3d, 0xff}, // red
		color.RGBA{0x35, 0x98, 0xdb, 0xff}, // blue
		color.RGBA{0x9a, 0x59, 0xb5, 0xff}, // purple
		color.RGBA{0xef, 0x3e, 0x96, 0xff}, // magenta
		color.RGBA{0xdf, 0x21, 0xb9, 0xff}, // violet
		color.RGBA{0x7d, 0xc2, 0xd2, 0xff}, // lightBlue
		color.RGBA{0x16, 0xa0, 0x86, 0xff}, // turquoiseIntense
		color.RGBA{0x27, 0xae, 0x61, 0xff}, // mintIntense
		color.RGBA{0x24, 0xc3, 0x33, 0xff}, // greenIntense
		color.RGBA{0x1c, 0xab, 0xbb, 0xff}, // lightBlueIntense
	}

	secondaryPalette := []color.Color{
		color.RGBA{0x34, 0x49, 0x5e, 0xff}, // darkBlue
		color.RGBA{0x95, 0xa5, 0xa5, 0xff}, // grey
		color.RGBA{0xd2, 0x54, 0x00, 0xff}, // brownIntense
		color.RGBA{0xc1, 0x39, 0x2b, 0xff}, // redIntense
		color.RGBA{0x29, 0x7f, 0xb8, 0xff}, // blueIntense
		color.RGBA{0x8d, 0x44, 0xad, 0xff}, // purpleIntense
		color.RGBA{0xbe, 0x12, 0x7e, 0xff}, // violetIntense
		color.RGBA{0xe5, 0x23, 0x83, 0xff}, // magentaIntense
		color.RGBA{0x27, 0xae, 0x61, 0xff}, // mintIntense
		color.RGBA{0x24, 0xc3, 0x33, 0xff}, // greenIntense
		color.RGBA{0xd9, 0xd9, 0x21, 0xff}, // yellowIntense
		color.RGBA{0xf3, 0x9c, 0x11, 0xff}, // yellowOrangeIntense
		color.RGBA{0xff, 0x55, 0x00, 0xff}, // orangeIntense
		color.RGBA{0x1c, 0xab, 0xbb, 0xff}, // lightBlueIntense
		color.RGBA{0x23, 0x23, 0x23, 0xff}, // lightBlackIntense
		color.RGBA{0x7e, 0x8c, 0x8d, 0xff}, // greyIntense
	}

	// Background colors based on mode
	lightBackgrounds := []color.Color{
		color.RGBA{255, 255, 255, 255}, // white
		color.RGBA{243, 245, 247, 255}, // light gray 1
		color.RGBA{236, 240, 241, 255}, // light gray 2
		color.RGBA{0, 0, 0, 0},         // transparent (position 3)
	}

	darkBackgrounds := []color.Color{
		color.RGBA{30, 30, 30, 255},  // dark gray
		color.RGBA{45, 62, 80, 255},  // dark blue
		color.RGBA{57, 57, 57, 255},  // dark gray 2
		color.RGBA{0, 0, 0, 0},       // transparent (position 3)
	}

	// Palette in exact order:
	// 0: Background
	// 1: Primary color
	// 2: Secondary color
	// 3: Transparent (optional)
	palette := make(color.Palette, 0, 4)

	// Background first
	if darkMode {
		palette = append(palette, darkBackgrounds[bgIdx])
	} else {
		palette = append(palette, lightBackgrounds[bgIdx])
	}

	// Then primary and secondary colors
	palette = append(palette,
		primaryPalette[primaryIdx],
		secondaryPalette[secondaryIdx],
		color.RGBA{0, 0, 0, 0}, // transparent as last option
	)

	return palette
}

// Generate48x48ForFace creates a 48x48 pixel image specifically for Face headers
func (identicon *OptimizedIdenticon) Generate48x48ForFace(transparent bool) *image.Paletted {
	const (
		size       = 48
		spriteSize = 5
		pixelSize  = 6                             // 48/8 = 6
		margin     = (size - pixelSize*spriteSize) / 2 // = 9
	)

	// Determine color indices
	primaryIdx, secondaryIdx, bgIdx := identicon.getColorIndices()

	// For transparent background, set bgIdx to 3 (transparent)
	if transparent {
		bgIdx = 3
	}

	// Palette for export (always light mode for better compatibility)
	palette := createPalette(primaryIdx, secondaryIdx, bgIdx, false)

	// Create image
	img := image.NewPaletted(image.Rect(0, 0, size, size), palette)

	// Fill background
	bgIndex := uint8(0)
	if transparent {
		bgIndex = 3 // transparent
	}

	for i := 0; i < size; i++ {
		for j := 0; j < size; j++ {
			img.SetColorIndex(j, i, bgIndex)
		}
	}

	primaryPixels, secondaryPixels := identicon.generatePixelPattern()

	// Secondary pixels (index 2)
	for row := 0; row < spriteSize; row++ {
		for col := 0; col < spriteSize; col++ {
			if secondaryPixels[row*spriteSize+col] {
				x := col*pixelSize + margin
				y := row*pixelSize + margin
				for py := y; py < y+pixelSize; py++ {
					for px := x; px < x+pixelSize; px++ {
						if px < size && py < size {
							img.SetColorIndex(px, py, 2)
						}
					}
				}
			}
		}
	}

	// Primary pixels (index 1)
	for row := 0; row < spriteSize; row++ {
		for col := 0; col < spriteSize; col++ {
			if primaryPixels[row*spriteSize+col] {
				x := col*pixelSize + margin
				y := row*pixelSize + margin
				for py := y; py < y+pixelSize; py++ {
					for px := x; px < x+pixelSize; px++ {
						if px < size && py < size {
							img.SetColorIndex(px, py, 1)
						}
					}
				}
			}
		}
	}

	return img
}

// GenerateForExportOptimized for indexed export (256x256)
func (identicon *OptimizedIdenticon) GenerateForExportOptimized(transparent bool) *image.Paletted {
	const (
		spriteSize = 5
	)

	pixelSize := identicon.size / 8
	margin := (identicon.size - pixelSize*spriteSize) / 2

	// Determine color indices
	primaryIdx, secondaryIdx, bgIdx := identicon.getColorIndices()

	// For transparent background, set bgIdx to 3 (transparent)
	if transparent {
		bgIdx = 3
	}

	// Palette for export (always light mode for better compatibility)
	palette := createPalette(primaryIdx, secondaryIdx, bgIdx, false)

	// Create image
	img := image.NewPaletted(image.Rect(0, 0, identicon.size, identicon.size), palette)

	// Fill background
	bgIndex := uint8(0)
	if transparent {
		bgIndex = 3 // transparent
	}

	for i := 0; i < identicon.size; i++ {
		for j := 0; j < identicon.size; j++ {
			img.SetColorIndex(j, i, bgIndex)
		}
	}

	primaryPixels, secondaryPixels := identicon.generatePixelPattern()

	// Secondary pixels (index 2)
	for row := 0; row < spriteSize; row++ {
		for col := 0; col < spriteSize; col++ {
			if secondaryPixels[row*spriteSize+col] {
				x := col*pixelSize + margin
				y := row*pixelSize + margin
				for py := y; py < y+pixelSize; py++ {
					for px := x; px < x+pixelSize; px++ {
						if px < identicon.size && py < identicon.size {
							img.SetColorIndex(px, py, 2)
						}
					}
				}
			}
		}
	}

	// Primary pixels (index 1)
	for row := 0; row < spriteSize; row++ {
		for col := 0; col < spriteSize; col++ {
			if primaryPixels[row*spriteSize+col] {
				x := col*pixelSize + margin
				y := row*pixelSize + margin
				for py := y; py < y+pixelSize; py++ {
					for px := x; px < x+pixelSize; px++ {
						if px < identicon.size && py < identicon.size {
							img.SetColorIndex(px, py, 1)
						}
					}
				}
			}
		}
	}

	return img
}

func main() {
	// CLI flags
	input := flag.String("input", "", "Input text (username|email|pubkey)")
	size := flag.Int("size", 48, "Image size (48 or 256)")
	transparent := flag.Bool("transparent", true, "Transparent background")
	outputFormat := flag.String("format", "base64", "Output format: base64, dataurl, or png")
	outputFile := flag.String("output", "", "Output file (for png format)")
	hash := flag.String("hash", "", "Direct SHA256 hash (hex) instead of input")

	flag.Parse()

	// Validate input
	if *input == "" && *hash == "" {
		fmt.Fprintln(os.Stderr, "Error: -input or -hash required")
		fmt.Fprintln(os.Stderr, "Usage: identicons-cli -input 'username|email|pubkey' [-size 48|256] [-transparent] [-format base64|dataurl|png] [-output file.png]")
		os.Exit(1)
	}

	// Generate hash
	var hashBytes []byte
	if *hash != "" {
		// Use provided hash (hex string)
		fmt.Sscanf(*hash, "%x", &hashBytes)
		if len(hashBytes) != 32 {
			fmt.Fprintln(os.Stderr, "Error: hash must be 32 bytes (64 hex chars)")
			os.Exit(1)
		}
	} else {
		// Hash the input
		h := sha256.Sum256([]byte(*input))
		hashBytes = h[:]
	}

	// Generate identicon
	var img *image.Paletted
	if *size == 48 {
		identicon := NewOptimizedIdenticonWithSize(hashBytes, 48)
		img = identicon.Generate48x48ForFace(*transparent)
	} else if *size == 256 {
		identicon := NewOptimizedIdenticonWithSize(hashBytes, 256)
		img = identicon.GenerateForExportOptimized(*transparent)
	} else {
		fmt.Fprintln(os.Stderr, "Error: size must be 48 or 256")
		os.Exit(1)
	}

	// Encode to PNG
	var buf bytes.Buffer
	encoder := png.Encoder{
		CompressionLevel: png.BestCompression,
	}

	if err := encoder.Encode(&buf, img); err != nil {
		fmt.Fprintln(os.Stderr, "Error encoding PNG:", err)
		os.Exit(1)
	}

	// Output based on format
	switch *outputFormat {
	case "base64":
		// Output base64 only (for Face header)
		b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
		fmt.Print(b64)

	case "dataurl":
		// Output data URL (for web)
		b64 := base64.StdEncoding.EncodeToString(buf.Bytes())
		fmt.Printf("data:image/png;base64,%s", b64)

	case "png":
		// Output PNG file
		if *outputFile == "" {
			fmt.Fprintln(os.Stderr, "Error: -output required for png format")
			os.Exit(1)
		}
		if err := os.WriteFile(*outputFile, buf.Bytes(), 0644); err != nil {
			fmt.Fprintln(os.Stderr, "Error writing PNG:", err)
			os.Exit(1)
		}
		fmt.Fprintf(os.Stderr, "PNG saved to %s (%d bytes)\n", *outputFile, buf.Len())

	default:
		fmt.Fprintln(os.Stderr, "Error: format must be base64, dataurl, or png")
		os.Exit(1)
	}
}
