package webcontroller

import (
	"fmt"
	"math"
)

type Color interface {
	CSS
	HSL() HSL
	RGB() RGB
}

type CSS interface {
	CSS() string
}

// Raw CSS
type RawCSS string

func (c RawCSS) CSS() string { return string(c) }

// HSL color
type HSL struct {
	Hue        int
	Saturation float64
	Lightness  float64
}

var _ Color = HSL{} // Confirm interface compliance

func (hsl HSL) CSS() string { return hsl.RGB().CSS() }
func (hsl HSL) HSL() HSL    { return hsl }
func (hsl HSL) RGB() RGB {
	var r, g, b, q, p float64
	var h, s, l = float64(hsl.Hue) / 360, hsl.Saturation, hsl.Lightness

	if s == 0 {
		r, g, b = l, l, l
	} else {
		var hue2rgb = func(p, q, t float64) float64 {
			if t < 0 {
				t++
			}
			if t > 1 {
				t--
			}

			if t < 1.0/6.0 {
				return p + (q-p)*6*t
			} else if t < 1.0/2.0 {
				return q
			} else if t < 2.0/3.0 {
				return p + (q-p)*(2.0/3.0-t)*6
			}
			return p
		}

		if l < 0.5 {
			q = l * (1 + s)
		} else {
			q = l + s - l*s
		}

		p = 2*l - q
		r = hue2rgb(p, q, h+1.0/3.0)
		g = hue2rgb(p, q, h)
		b = hue2rgb(p, q, h-1.0/3.0)
	}
	return RGB{R: uint8(r * 255), G: uint8(g * 255), B: uint8(b * 255)}
}

// Add returns a NEW HSL struct, it doesn't modify the current one
func (hsl HSL) Add(hue int, saturation float64, lightness float64) HSL {
	var new = HSL{
		hsl.Hue + hue,
		hsl.Saturation + saturation,
		hsl.Lightness + lightness,
	}
	// Hue bounds correction
	if new.Hue < 0 {
		new.Hue += 360
	} else if new.Hue > 360 {
		new.Hue -= 360
	}
	// Saturation bounds check
	if new.Saturation < 0 {
		new.Saturation = 0
	} else if new.Saturation > 1 {
		new.Saturation = 1
	}
	// Lightness bounds check
	if new.Lightness < 0 {
		new.Lightness = 0
	} else if new.Lightness > 1 {
		new.Lightness = 1
	}

	return new
}

func (hsl HSL) Darken(percent float64) HSL {
	hsl.Lightness = hsl.Lightness * percent
	hsl.Saturation = hsl.Saturation * percent
	return hsl
}

func (hsl HSL) WithAlpha(alpha float64) HSLA {
	return HSLA{hsl.Hue, hsl.Saturation, hsl.Lightness, alpha}
}

type HSLA struct {
	Hue        int
	Saturation float64
	Lightness  float64
	Alpha      float64
}

var _ Color = HSLA{}

func (hsla HSLA) CSS() string {
	return fmt.Sprintf(
		"hsla(%d, %.2f%%, %.2f%%, %.2f)",
		hsla.Hue, hsla.Saturation*100, hsla.Lightness*100, hsla.Alpha,
	)
}
func (hsla HSLA) HSL() HSL { return HSL{hsla.Hue, hsla.Saturation, hsla.Lightness} }
func (hsla HSLA) RGB() RGB { return hsla.HSL().RGB() }

type RGB struct {
	R uint8
	G uint8
	B uint8
}

var _ Color = RGB{}

func (rgb RGB) CSS() string { return fmt.Sprintf("#%02x%02x%02x", rgb.R, rgb.G, rgb.B) }
func (rgb RGB) HSL() HSL {
	var r, g, b = float64(rgb.R), float64(rgb.G), float64(rgb.B)
	var h, s, l float64

	max := math.Max(math.Max(r, g), b)
	min := math.Min(math.Min(r, g), b)

	// Luminosity is the average of the max and min rgb color intensities.
	l = (max + min) / 2

	// saturation
	delta := max - min
	if delta == 0 {
		// it's gray
		return HSL{0, 0, l}
	}

	// it's not gray
	if l < 0.5 {
		s = delta / (max + min)
	} else {
		s = delta / (2 - max - min)
	}

	// hue
	r2 := (((max - r) / 6) + (delta / 2)) / delta
	g2 := (((max - g) / 6) + (delta / 2)) / delta
	b2 := (((max - b) / 6) + (delta / 2)) / delta
	switch {
	case r == max:
		h = b2 - g2
	case g == max:
		h = (1.0 / 3.0) + r2 - b2
	case b == max:
		h = (2.0 / 3.0) + g2 - r2
	}

	if h < 0 {
		h += 1
	} else if h > 1 {
		h -= 1
	}

	return HSL{int(h), s, l}
}
func (rgb RGB) RGB() RGB { return rgb }

type RGBA struct {
	R uint8
	G uint8
	B uint8
	A float64
}

var _ Color = RGBA{}

func (rgba RGBA) CSS() string {
	return fmt.Sprintf("rgba(%d, %d, %d, %f)", rgba.R, rgba.G, rgba.B, rgba.A)
}

func (rgba RGBA) HSL() HSL { return rgba.RGB().HSL() }
func (rgba RGBA) RGB() RGB { return RGB{rgba.R, rgba.G, rgba.B} }

type Gradient struct {
	Angle  int
	Colors []Color
}

var _ CSS = Gradient{}

func NewGradient(angle int, colors ...Color) Gradient {
	return Gradient{angle, colors}
}

func (g Gradient) CSS() string {
	var colors string
	for i, color := range g.Colors {
		if i != 0 {
			colors += ", "
		}
		colors += color.CSS()
	}

	return fmt.Sprintf("linear-gradient(%ddeg, %s)", g.Angle, colors)
}
