package graphics

import (
	"fmt"
	"math/rand"
	"image/color"
	rl "github.com/gen2brain/raylib-go/raylib"
)

type Graphics struct {
	Style Style
	Bounds Rect
}

func CreateGraphics(bounds Rect) *Graphics {
	return &Graphics {
		Bounds: bounds,
		Style: Style {
			Fill:         false,
			FillColor:    rl.RayWhite,
			Stroke:       true,
			StrokeColor:  rl.Black,
			StrokeWeight: 1.0,
		},
	}
}

type Style struct {
	StrokeColor, FillColor color.RGBA
	StrokeWeight float32
	Stroke, Fill bool
}

func BeginDrawing() {
	rl.BeginDrawing()
}

func EndDrawing() {
	rl.EndDrawing()
}

func (g *Graphics) Center() Point {
	return g.Bounds.Center()
}

func (g *Graphics) Begin() {
	g.PushMatrix()
	g.BeginClip(g.Bounds)
	g.Translate(g.Bounds.UL())
}

func (g *Graphics) BeginPremultiplyBlend() {
	rl.BeginBlendMode(rl.BlendAlphaPremultiply)
}

func (g *Graphics) EndPremultiplyBlend() {
	rl.EndBlendMode()
}

func (g *Graphics) End() {
	g.PopMatrix()
	g.EndClip()
}

func (g *Graphics) Clear() {
	rl.ClearBackground(rl.Blank)
}

func (g *Graphics) Background(c color.RGBA) {
	rl.ClearBackground(c)
}

func (g *Graphics) Width() float32 {
	return g.Bounds.Width
}

func (g *Graphics) Height() float32 {
	return g.Bounds.Height
}

func (g *Graphics) WidthInt32() int32 {
	return int32(g.Bounds.Width)
}

func (g *Graphics) HeightInt32() int32 {
	return int32(g.Bounds.Height)
}


func (g *Graphics) PushStyle() {
}

func (g *Graphics) PopStyle() {
}

func (g *Graphics) SetStrokeColor(c color.RGBA) {
	g.Style.StrokeColor = color.RGBA { R: c.R, G: c.G, B: c.B, A: c.A }
}

func (g *Graphics) SetFillColor(c color.RGBA) {
	g.Style.FillColor = color.RGBA { R: c.R, G: c.G, B: c.B, A: c.A }
}

func (g *Graphics) SetStrokeWeight(w float32) {
	g.Style.StrokeWeight = w
}

func (g *Graphics) SetStroke(b bool) {
	g.Style.Stroke = b
}

func (g *Graphics) SetFill(b bool) {
	g.Style.Fill = b
}

func (g *Graphics) PushMatrix() {
	rl.PushMatrix()
}

func (g *Graphics) Translate(p Point) {
	rl.Translatef(p.X, p.Y, 0)
}

func (g *Graphics) PopMatrix() {
	rl.PopMatrix()
}

func (g *Graphics) BeginClip(r Rect) {
	rlRect := r.ToRL()
	rint := (&rlRect).ToInt32()
	rl.BeginScissorMode(rint.X, rint.Y, rint.Width, rint.Height)
}

func (g *Graphics) EndClip() {
	rl.EndScissorMode()
}

func (g *Graphics) BeginAdditiveBlend() {
	rl.BeginBlendMode(rl.BlendAdditive)
}

func (g *Graphics) EndBlend() {
	rl.EndBlendMode()
}

func (g *Graphics) BeginTexture(t rl.RenderTexture2D) {
	rl.BeginTextureMode(t)
}

func (g *Graphics) DrawTexture(t rl.Texture2D, p Point, c color.RGBA) {
	rl.DrawTexture(t, int32(p.X), int32(p.Y), c)
}

func (g *Graphics) TransferTexture(t rl.Texture2D, src Rect, dst Rect, tint color.RGBA) {
	// flip source since textures are upside down on GPU
	//src.Y += src.Height
	src.Height = -src.Height
	rl.DrawTexturePro(t, src.ToRL(), dst.ToRL(), rl.Vector2{}, 0, tint)
}

func (g *Graphics) EndTexture() {
	rl.EndTextureMode()
}

func (g *Graphics) DrawRect(r Rect) {
	if g.Style.Fill {
		rl.DrawRectangleRec(r.ToRL(), g.Style.FillColor)
	}
	if g.Style.Stroke {
		saveLineWidth := rl.GetLineWidth()
		rl.SetLineWidth(g.Style.StrokeWeight)
		rl.DrawRectangleLines(
			int32(r.X), int32(r.Y),
			int32(r.Width), int32(r.Height),
			g.Style.StrokeColor,
		)
		rl.SetLineWidth(saveLineWidth)
	}
}

func (g *Graphics) DrawLine(a, b Point) {
	saveLineWidth := rl.GetLineWidth()
	rl.SetLineWidth(g.Style.StrokeWeight)
	rl.DrawLineV(a.ToRL(), b.ToRL(), g.Style.StrokeColor)
	rl.SetLineWidth(saveLineWidth)
}

type HSBA struct {
	H uint
	S, B float32
	A uint8
}

var Origin = Point { X: 0, Y: 0, Z: 0 }

type Point rl.Vector3
type Vec rl.Vector3
type Rect rl.Rectangle

func (r *Rect) Center() Point {
	return Point {
		X: r.X + r.Width / 2,
		Y: r.Y + r.Height / 2,
	}
}

func (r *Rect) ContractByAbs(px float32) Rect {
	return Rect {
		X: r.X + px,
		Y: r.Y + px,
		Width: r.Width - 2*px,
		Height: r.Height - 2*px,
	}
}

func (p *Point) Add(v Vec) Point {
	return Point { X: p.X + v.X, Y: p.Y + v.Y, Z: p.Z + v.Z }
}

func (p Point) ToRL() rl.Vector2 {
	return rl.Vector2 { X: p.X, Y: p.Y }
}

func (p Point) ToRL3() rl.Vector3 {
	return rl.Vector3 { X: p.X, Y: p.Y, Z: p.Z }
}

/**
 * scale the given rect down to the target rect
 * maintaining the aspect ratio of the original rect
 */

func (r Rect) UL() Point {
	return Point { X: r.X, Y: r.Y }
}


func (r Rect) ToRL() rl.Rectangle {
	return rl.Rectangle { X: r.X, Y: r.Y, Width: r.Width, Height: r.Height }
}

func (r Rect) ScaleTo(tgt Rect) Rect {

	outputWidth := tgt.Width
	outputHeight := tgt.Height

	aspect := r.Width / r.Height
	tgtAspect := outputWidth / outputHeight

	if aspect < tgtAspect {
		// source is relatively taller than the target
		// so we set the output height to the target height
		// and calculate the width based on source aspect and center
		outputWidth = float32(outputHeight) * aspect
	} else {
		// source is relatively wider than the target
		// so we set the output width to the target width
		// and calculate the height based on source aspect and center
		outputHeight = float32(outputWidth) / aspect
	}

	// output width and height are correct -- center within TargetBounds
	x := tgt.X + tgt.Width / 2.0 - outputWidth / 2.0 
	y := tgt.Y + tgt.Height / 2.0 - outputHeight / 2.0 

	return Rect {
		X: x, Y: y,
		Width: outputWidth,
		Height: outputHeight,
	}
}

var (
	FlourescentHues = []float32{
		0,   // hot magenta-red
		30,  // neon orange
		60,  // acid yellow
		120, // laser green
		180, // cyan
		210, // electric blue
		270, // ultraviolet purple
	}
	FlourescentColors = makeFlourescentColors()
)

func Clamp(c color.RGBA, min uint8, max uint8) rl.Color {
	return rl.NewColor(
		uint8(rl.Clamp(float32(c.R), float32(min), float32(max))),
		uint8(rl.Clamp(float32(c.G), float32(min), float32(max))),
		uint8(rl.Clamp(float32(c.B), float32(min), float32(max))),
		uint8(rl.Clamp(float32(c.A), float32(min), float32(max))),
	)
}

func makeFlourescentColors() []rl.Color {
	fc := make([]rl.Color, len(FlourescentHues))
	for i, hue := range(FlourescentHues) {
		fc[i] = rl.ColorFromHSV(hue, 1.0, 1.0)
	}
	fmt.Printf("flourescent colors --> %v\n", fc)

	return fc
}

type ColorCycle interface {
	Next() rl.Color
}

type ArrayBackedColorCycle struct {
	colors []rl.Color
	index int
}

func NewFixedColorCycle(colors []rl.Color) *ArrayBackedColorCycle {
	return &ArrayBackedColorCycle {
		colors: colors,
		index: 0,
	}
}

func (c ArrayBackedColorCycle) Shuffle(seed int64) *ArrayBackedColorCycle {
	r := rand.New(rand.NewSource(seed))
	cprime := &ArrayBackedColorCycle {
		colors: make([]rl.Color, len(c.colors)),
		index: 0,
	}
	copy(cprime.colors, c.colors)
	r.Shuffle(len(c.colors), func(i, j int) {
		cprime.colors[i], cprime.colors[j] = cprime.colors[j], cprime.colors[i]
	})
	fmt.Printf("shuffled colors --> %v\n", cprime.colors)
	return cprime
}

func (c *ArrayBackedColorCycle) Next() rl.Color {
	color := c.colors[c.index]
	c.index = (c.index + 1) % len(c.colors)
	return color
}

