package renderer

import (
	"bytes"
	"image"
	"image/color"
	"image/draw"
	"image/png"
	"math"
	"strconv"
	"strings"
	"sync"

	"github.com/kevin/go-jdenticon/internal/engine"
)

// PNGRenderer implements the Renderer interface for PNG output
type PNGRenderer struct {
	*BaseRenderer
	img           *image.RGBA
	currentColor  color.RGBA
	background    color.RGBA
	hasBackground bool
	mu            sync.RWMutex // For thread safety in concurrent generation
}

// bufferPool provides buffer pooling for efficient PNG generation
var bufferPool = sync.Pool{
	New: func() interface{} {
		return &bytes.Buffer{}
	},
}

// NewPNGRenderer creates a new PNG renderer with the specified icon size
func NewPNGRenderer(iconSize int) *PNGRenderer {
	return &PNGRenderer{
		BaseRenderer: NewBaseRenderer(iconSize),
		img:          image.NewRGBA(image.Rect(0, 0, iconSize, iconSize)),
	}
}

// SetBackground sets the background color and opacity
func (r *PNGRenderer) SetBackground(fillColor string, opacity float64) {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.BaseRenderer.SetBackground(fillColor, opacity)
	r.background = parseColor(fillColor, opacity)
	r.hasBackground = opacity > 0

	if r.hasBackground {
		// Fill the entire image with background color
		draw.Draw(r.img, r.img.Bounds(), &image.Uniform{r.background}, image.Point{}, draw.Src)
	}
}

// BeginShape marks the beginning of a new shape with the specified color
func (r *PNGRenderer) BeginShape(fillColor string) {
	r.mu.Lock()
	defer r.mu.Unlock()

	r.BaseRenderer.BeginShape(fillColor)
	r.currentColor = parseColor(fillColor, 1.0)
}

// EndShape marks the end of the currently drawn shape
func (r *PNGRenderer) EndShape() {
	// No action needed for PNG - shapes are drawn immediately
}

// AddPolygon adds a polygon with the current fill color to the image
func (r *PNGRenderer) AddPolygon(points []engine.Point) {
	if len(points) == 0 {
		return
	}

	r.mu.Lock()
	defer r.mu.Unlock()

	// Convert engine.Point to image coordinates
	imagePoints := make([]image.Point, len(points))
	for i, p := range points {
		imagePoints[i] = image.Point{
			X: int(math.Round(p.X)),
			Y: int(math.Round(p.Y)),
		}
	}

	// Fill polygon using scanline algorithm
	r.fillPolygon(imagePoints)
}

// AddCircle adds a circle with the current fill color to the image
func (r *PNGRenderer) AddCircle(topLeft engine.Point, size float64, invert bool) {
	r.mu.Lock()
	defer r.mu.Unlock()

	radius := size / 2
	centerX := int(math.Round(topLeft.X + radius))
	centerY := int(math.Round(topLeft.Y + radius))
	radiusInt := int(math.Round(radius))

	// Use Bresenham's circle algorithm for anti-aliased circle drawing
	r.drawCircle(centerX, centerY, radiusInt, invert)
}

// ToPNG generates the final PNG image data
func (r *PNGRenderer) ToPNG() []byte {
	r.mu.RLock()
	defer r.mu.RUnlock()

	buf := bufferPool.Get().(*bytes.Buffer)
	buf.Reset()
	defer bufferPool.Put(buf)

	// Encode to PNG with compression
	encoder := &png.Encoder{
		CompressionLevel: png.BestCompression,
	}

	if err := encoder.Encode(buf, r.img); err != nil {
		return nil
	}

	// Return a copy of the buffer data
	result := make([]byte, buf.Len())
	copy(result, buf.Bytes())
	return result
}

// parseColor converts a hex color string to RGBA color
func parseColor(hexColor string, opacity float64) color.RGBA {
	// Remove # prefix if present
	hexColor = strings.TrimPrefix(hexColor, "#")

	// Default to black if parsing fails
	var r, g, b uint8 = 0, 0, 0

	switch len(hexColor) {
	case 3:
		// Short form: #RGB -> #RRGGBB
		if val, err := strconv.ParseUint(hexColor, 16, 12); err == nil {
			r = uint8((val >> 8 & 0xF) * 17)
			g = uint8((val >> 4 & 0xF) * 17)
			b = uint8((val & 0xF) * 17)
		}
	case 6:
		// Full form: #RRGGBB
		if val, err := strconv.ParseUint(hexColor, 16, 24); err == nil {
			r = uint8(val >> 16)
			g = uint8(val >> 8)
			b = uint8(val)
		}
	case 8:
		// With alpha: #RRGGBBAA
		if val, err := strconv.ParseUint(hexColor, 16, 32); err == nil {
			r = uint8(val >> 24)
			g = uint8(val >> 16)
			b = uint8(val >> 8)
			// Override opacity with alpha from color
			opacity = float64(uint8(val)) / 255.0
		}
	}

	alpha := uint8(math.Round(opacity * 255))
	return color.RGBA{R: r, G: g, B: b, A: alpha}
}

// fillPolygon fills a polygon using a scanline algorithm
func (r *PNGRenderer) fillPolygon(points []image.Point) {
	if len(points) < 3 {
		return
	}

	// Find bounding box
	minY, maxY := points[0].Y, points[0].Y
	for _, p := range points[1:] {
		if p.Y < minY {
			minY = p.Y
		}
		if p.Y > maxY {
			maxY = p.Y
		}
	}

	// Ensure bounds are within image
	bounds := r.img.Bounds()
	if minY < bounds.Min.Y {
		minY = bounds.Min.Y
	}
	if maxY >= bounds.Max.Y {
		maxY = bounds.Max.Y - 1
	}

	// For each scanline, find intersections and fill
	for y := minY; y <= maxY; y++ {
		intersections := r.getIntersections(points, y)
		if len(intersections) < 2 {
			continue
		}

		// Sort intersections and fill between pairs
		for i := 0; i < len(intersections); i += 2 {
			if i+1 < len(intersections) {
				x1, x2 := intersections[i], intersections[i+1]
				if x1 > x2 {
					x1, x2 = x2, x1
				}

				// Clamp to image bounds
				if x1 < bounds.Min.X {
					x1 = bounds.Min.X
				}
				if x2 >= bounds.Max.X {
					x2 = bounds.Max.X - 1
				}

				// Fill the horizontal line
				for x := x1; x <= x2; x++ {
					r.img.SetRGBA(x, y, r.currentColor)
				}
			}
		}
	}
}

// getIntersections finds x-coordinates where a horizontal line intersects polygon edges
func (r *PNGRenderer) getIntersections(points []image.Point, y int) []int {
	var intersections []int
	n := len(points)

	for i := 0; i < n; i++ {
		j := (i + 1) % n
		p1, p2 := points[i], points[j]

		// Check if the edge crosses the scanline
		if (p1.Y <= y && p2.Y > y) || (p2.Y <= y && p1.Y > y) {
			// Calculate intersection x-coordinate
			x := p1.X + (y-p1.Y)*(p2.X-p1.X)/(p2.Y-p1.Y)
			intersections = append(intersections, x)
		}
	}

	// Sort intersections
	for i := 0; i < len(intersections)-1; i++ {
		for j := i + 1; j < len(intersections); j++ {
			if intersections[i] > intersections[j] {
				intersections[i], intersections[j] = intersections[j], intersections[i]
			}
		}
	}

	return intersections
}

// drawCircle draws a filled circle using Bresenham's algorithm
func (r *PNGRenderer) drawCircle(centerX, centerY, radius int, invert bool) {
	bounds := r.img.Bounds()

	// For filled circle, we'll draw it by filling horizontal lines
	for y := -radius; y <= radius; y++ {
		actualY := centerY + y
		if actualY < bounds.Min.Y || actualY >= bounds.Max.Y {
			continue
		}

		// Calculate x extent for this y
		x := int(math.Sqrt(float64(radius*radius - y*y)))

		x1, x2 := centerX-x, centerX+x

		// Clamp to image bounds
		if x1 < bounds.Min.X {
			x1 = bounds.Min.X
		}
		if x2 >= bounds.Max.X {
			x2 = bounds.Max.X - 1
		}

		// Fill the horizontal line
		for x := x1; x <= x2; x++ {
			if invert {
				// For inverted circles, we need to punch a hole
				// This would typically be handled by a compositing mode
				// For now, we'll set to transparent
				r.img.SetRGBA(x, actualY, color.RGBA{0, 0, 0, 0})
			} else {
				r.img.SetRGBA(x, actualY, r.currentColor)
			}
		}
	}
}
