package gg

import (
	"crypto/md5"
	"flag"
	"fmt"
	"image/color"
	"math/rand"
	"testing"
)

var save bool

func init() {
	flag.BoolVar(&save, "save", false, "save PNG output for each test case")
	flag.Parse()
}

func hash(dc *Context) string {
	return fmt.Sprintf("%x", md5.Sum(dc.im.Pix))
}

func checkHash(t *testing.T, dc *Context, expected string) {
	actual := hash(dc)
	if actual != expected {
		t.Fatalf("expected hash: %s != actual hash: %s", expected, actual)
	}
}

func saveImage(dc *Context, name string) error {
	if save {
		return SavePNG(name+".png", dc.Image())
	}
	return nil
}

func TestBlank(t *testing.T) {
	dc := NewContext(100, 100)
	saveImage(dc, "TestBlank")
	checkHash(t, dc, "4e0a293a5b638f0aba2c4fe2c3418d0e")
}

func TestGrid(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	for i := 10; i < 100; i += 10 {
		x := float64(i) + 0.5
		dc.DrawLine(x, 0, x, 100)
		dc.DrawLine(0, x, 100, x)
	}
	dc.SetRGB(0, 0, 0)
	dc.Stroke()
	saveImage(dc, "TestGrid")
	checkHash(t, dc, "78606adda71d8abfbd8bb271087e4d69")
}

func TestLines(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(0.5, 0.5, 0.5)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 100; i++ {
		x1 := rnd.Float64() * 100
		y1 := rnd.Float64() * 100
		x2 := rnd.Float64() * 100
		y2 := rnd.Float64() * 100
		dc.DrawLine(x1, y1, x2, y2)
		dc.SetLineWidth(rnd.Float64() * 3)
		dc.SetRGB(rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.Stroke()
	}
	saveImage(dc, "TestLines")
	checkHash(t, dc, "036bd220e2529955cc48425dd72bb686")
}

func TestCircles(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 10; i++ {
		x := rnd.Float64() * 100
		y := rnd.Float64() * 100
		r := rnd.Float64()*10 + 5
		dc.DrawCircle(x, y, r)
		dc.SetRGB(rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.FillPreserve()
		dc.SetRGB(rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.SetLineWidth(rnd.Float64() * 3)
		dc.Stroke()
	}
	saveImage(dc, "TestCircles")
	checkHash(t, dc, "c52698000df96fabafe7863701afe922")
}

func TestQuadratic(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(0.25, 0.25, 0.25)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 100; i++ {
		x1 := rnd.Float64() * 100
		y1 := rnd.Float64() * 100
		x2 := rnd.Float64() * 100
		y2 := rnd.Float64() * 100
		x3 := rnd.Float64() * 100
		y3 := rnd.Float64() * 100
		dc.MoveTo(x1, y1)
		dc.QuadraticTo(x2, y2, x3, y3)
		dc.SetLineWidth(rnd.Float64() * 3)
		dc.SetRGB(rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.Stroke()
	}
	saveImage(dc, "TestQuadratic")
	checkHash(t, dc, "56b842d814aee94b52495addae764a77")
}

func TestCubic(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(0.75, 0.75, 0.75)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 100; i++ {
		x1 := rnd.Float64() * 100
		y1 := rnd.Float64() * 100
		x2 := rnd.Float64() * 100
		y2 := rnd.Float64() * 100
		x3 := rnd.Float64() * 100
		y3 := rnd.Float64() * 100
		x4 := rnd.Float64() * 100
		y4 := rnd.Float64() * 100
		dc.MoveTo(x1, y1)
		dc.CubicTo(x2, y2, x3, y3, x4, y4)
		dc.SetLineWidth(rnd.Float64() * 3)
		dc.SetRGB(rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.Stroke()
	}
	saveImage(dc, "TestCubic")
	checkHash(t, dc, "4a7960fc4eaaa33ce74131c5ce0afca8")
}

func TestFill(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 10; i++ {
		dc.NewSubPath()
		for j := 0; j < 10; j++ {
			x := rnd.Float64() * 100
			y := rnd.Float64() * 100
			dc.LineTo(x, y)
		}
		dc.ClosePath()
		dc.SetRGBA(rnd.Float64(), rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.Fill()
	}
	saveImage(dc, "TestFill")
	checkHash(t, dc, "7ccb3a2443906a825e57ab94db785467")
}

func TestClip(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	dc.DrawCircle(50, 50, 40)
	dc.Clip()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 1000; i++ {
		x := rnd.Float64() * 100
		y := rnd.Float64() * 100
		r := rnd.Float64()*10 + 5
		dc.DrawCircle(x, y, r)
		dc.SetRGBA(rnd.Float64(), rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.Fill()
	}
	saveImage(dc, "TestClip")
	checkHash(t, dc, "762c32374d529fd45ffa038b05be7865")
}

func TestPushPop(t *testing.T) {
	const S = 100
	dc := NewContext(S, S)
	dc.SetRGBA(0, 0, 0, 0.1)
	for i := 0; i < 360; i += 15 {
		dc.Push()
		dc.RotateAbout(Radians(float64(i)), S/2, S/2)
		dc.DrawEllipse(S/2, S/2, S*7/16, S/8)
		dc.Fill()
		dc.Pop()
	}
	saveImage(dc, "TestPushPop")
	checkHash(t, dc, "e0d6724eb72033577811a55f7a642f6a")
}

func TestDrawStringWrapped(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	dc.SetRGB(0, 0, 0)
	dc.DrawStringWrapped("Hello, world! How are you?", 50, 50, 0.5, 0.5, 90, 1.5, AlignCenter)
	saveImage(dc, "TestDrawStringWrapped")
	checkHash(t, dc, "8d92f6aae9e8b38563f171abd00893f8")
}

func TestDrawImage(t *testing.T) {
	src := NewContext(100, 100)
	src.SetRGB(1, 1, 1)
	src.Clear()
	for i := 10; i < 100; i += 10 {
		x := float64(i) + 0.5
		src.DrawLine(x, 0, x, 100)
		src.DrawLine(0, x, 100, x)
	}
	src.SetRGB(0, 0, 0)
	src.Stroke()

	dc := NewContext(200, 200)
	dc.SetRGB(0, 0, 0)
	dc.Clear()
	dc.DrawImage(src.Image(), 50, 50)
	saveImage(dc, "TestDrawImage")
	checkHash(t, dc, "282afbc134676722960b6bec21305b15")
}

func TestSetPixel(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(0, 0, 0)
	dc.Clear()
	dc.SetRGB(0, 1, 0)
	i := 0
	for y := 0; y < 100; y++ {
		for x := 0; x < 100; x++ {
			if i%31 == 0 {
				dc.SetPixel(x, y)
			}
			i++
		}
	}
	saveImage(dc, "TestSetPixel")
	checkHash(t, dc, "27dda6b4b1d94f061018825b11982793")
}

func TestDrawPoint(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(0, 0, 0)
	dc.Clear()
	dc.SetRGB(0, 1, 0)
	dc.Scale(10, 10)
	for y := 0; y <= 10; y++ {
		for x := 0; x <= 10; x++ {
			dc.DrawPoint(float64(x), float64(y), 3)
			dc.Fill()
		}
	}
	saveImage(dc, "TestDrawPoint")
	checkHash(t, dc, "55af8874531947ea6eeb62222fb33e0e")
}

func TestLinearGradient(t *testing.T) {
	dc := NewContext(100, 100)
	g := NewLinearGradient(0, 0, 100, 100)
	g.AddColorStop(0, color.RGBA{0, 255, 0, 255})
	g.AddColorStop(1, color.RGBA{0, 0, 255, 255})
	g.AddColorStop(0.5, color.RGBA{255, 0, 0, 255})
	dc.SetFillStyle(g)
	dc.DrawRectangle(0, 0, 100, 100)
	dc.Fill()
	saveImage(dc, "TestLinearGradient")
	checkHash(t, dc, "75eb9385c1219b1d5bb6f4c961802c7a")
}

func TestRadialGradient(t *testing.T) {
	dc := NewContext(100, 100)
	g := NewRadialGradient(30, 50, 0, 70, 50, 50)
	g.AddColorStop(0, color.RGBA{0, 255, 0, 255})
	g.AddColorStop(1, color.RGBA{0, 0, 255, 255})
	g.AddColorStop(0.5, color.RGBA{255, 0, 0, 255})
	dc.SetFillStyle(g)
	dc.DrawRectangle(0, 0, 100, 100)
	dc.Fill()
	saveImage(dc, "TestRadialGradient")
	checkHash(t, dc, "f170f39c3f35c29de11e00428532489d")
}

func TestDashes(t *testing.T) {
	dc := NewContext(100, 100)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < 100; i++ {
		x1 := rnd.Float64() * 100
		y1 := rnd.Float64() * 100
		x2 := rnd.Float64() * 100
		y2 := rnd.Float64() * 100
		dc.SetDash(rnd.Float64()*3+1, rnd.Float64()*3+3)
		dc.DrawLine(x1, y1, x2, y2)
		dc.SetLineWidth(rnd.Float64() * 3)
		dc.SetRGB(rnd.Float64(), rnd.Float64(), rnd.Float64())
		dc.Stroke()
	}
	saveImage(dc, "TestDashes")
	checkHash(t, dc, "d188069c69dcc3970edfac80f552b53c")
}

func BenchmarkCircles(b *testing.B) {
	dc := NewContext(1000, 1000)
	dc.SetRGB(1, 1, 1)
	dc.Clear()
	rnd := rand.New(rand.NewSource(99))
	for i := 0; i < b.N; i++ {
		x := rnd.Float64() * 1000
		y := rnd.Float64() * 1000
		dc.DrawCircle(x, y, 10)
		if i%2 == 0 {
			dc.SetRGB(0, 0, 0)
		} else {
			dc.SetRGB(1, 1, 1)
		}
		dc.Fill()
	}
}
