package main

import (
	"math"

	"github.com/ryomak/p5go"
)

func main() {
	p5go.Run("#canvas-detail",
		p5go.Setup(setup),
		p5go.Draw(draw),
	)
	select {}
}

func setup(p *p5go.Canvas) {
	p.CreateCanvas(400, 400)
	p.FrameRate(30)
}

func draw(p *p5go.Canvas) {
	p.Background("#0A0A2E")

	(&keyblade{x: 200, y: 224, angle: math.Pi / 5}).draw(p)
	(&keyblade{x: 200, y: 224, angle: -math.Pi / 5}).draw(p)

	(&heart{x: 200, y: 176, size: 44, color: "#1E6FD9"}).draw(p)

	(&crown{x: 200, y: 128, size: 64, color: "#FFD700"}).draw(p)
}

// ==================== heart ====================

type heart struct {
	x, y  float64
	size  float64
	color string
}

func (h *heart) draw(p *p5go.Canvas) {
	h.drawBody(p)
	h.drawScrolls(p)
}

func (h *heart) drawBody(p *p5go.Canvas) {
	p.Push()
	p.Translate(h.x, h.y)
	p.NoStroke()

	p.Fill("#0D3B8C")
	h.drawShape(p, h.size*1.08)

	p.Fill(h.color)
	h.drawShape(p, h.size)

	p.Fill("#3A8FFF")
	h.drawShape(p, h.size*0.6)

	p.Fill("#6BB3FF")
	h.drawShape(p, h.size*0.25)

	p.Pop()
}

func (h *heart) drawShape(p *p5go.Canvas, size float64) {
	w := size * 0.55
	ht := size * 0.5

	p.BeginShape()
	p.Vertex(0, ht*1.4)
	p.BezierVertex(w*0.3, ht*0.9, w*1.15, ht*0.2, w*1.0, -ht*0.2)
	p.BezierVertex(w*0.85, -ht*0.7, w*0.2, -ht*0.85, 0, -ht*0.5)
	p.BezierVertex(-w*0.2, -ht*0.85, -w*0.85, -ht*0.7, -w*1.0, -ht*0.2)
	p.BezierVertex(-w*1.15, ht*0.2, -w*0.3, ht*0.9, 0, ht*1.4)
	p.EndShape(p5go.CLOSE)
}

func (h *heart) drawScrolls(p *p5go.Canvas) {
	p.Push()
	p.Translate(h.x, h.y)

	for _, isLeft := range []bool{true, false} {
		p.NoFill()
		p.StrokeCap(p5go.ROUND)

		p.Stroke("#0D3B8C")
		p.StrokeWeight(h.size * 0.14)
		h.drawScroll(p, isLeft)

		p.Stroke(h.color)
		p.StrokeWeight(h.size * 0.09)
		h.drawScroll(p, isLeft)
	}

	p.Pop()
}

func (h *heart) drawScroll(p *p5go.Canvas, isLeft bool) {
	s := h.size

	var centerX, startAngle, angleDir float64
	if isLeft {
		centerX = -s * 0.55
		startAngle = 1.25
		angleDir = 1.0
	} else {
		centerX = s * 0.55
		startAngle = math.Pi - 1.25
		angleDir = -1.0
	}

	centerY := -s * 0.55
	initialR := s * 0.29
	decay := 0.18

	p.BeginShape()
	for t := 0.0; t <= math.Pi*3.0; t += 0.05 {
		r := initialR * math.Exp(-decay*t)
		angle := startAngle + angleDir*t
		x := centerX + r*math.Cos(angle)
		y := centerY + r*math.Sin(angle)
		p.Vertex(x, y)
	}
	p.EndShape()
}

// ==================== crown ====================

type crown struct {
	x, y  float64
	size  float64
	color string
}

func (c *crown) draw(p *p5go.Canvas) {
	p.Push()
	p.Translate(c.x, c.y)

	halfW := c.size * 0.55
	baseH := c.size * 0.3
	spikeH := c.size * 0.55

	p.Fill(c.color)
	p.NoStroke()
	p.BeginShape()
	p.Vertex(-halfW, 0)
	p.Vertex(-halfW*0.7, -baseH)
	p.Vertex(-halfW*0.5, -baseH-spikeH*0.65)
	p.Vertex(-halfW*0.2, -baseH*0.7)
	p.Vertex(0, -baseH-spikeH)
	p.Vertex(halfW*0.2, -baseH*0.7)
	p.Vertex(halfW*0.5, -baseH-spikeH*0.65)
	p.Vertex(halfW*0.7, -baseH)
	p.Vertex(halfW, 0)
	p.EndShape(p5go.CLOSE)

	p.Pop()
}

// ==================== keyblade ====================

type keyblade struct {
	x, y  float64
	angle float64
}

func (k *keyblade) draw(p *p5go.Canvas) {
	p.Push()
	p.Translate(k.x, k.y)
	p.Rotate(k.angle)

	halfLen := 96.0
	shaftW := 8.0

	p.Fill("#A8A8A8")
	p.Stroke("#808080")
	p.StrokeWeight(1)
	p.Rect(-shaftW/2, -halfLen, shaftW, halfLen*2)

	p.Fill("#D0D0D0")
	p.NoStroke()
	p.Rect(-1.6, -halfLen, 3.2, halfLen*2)

	k.drawTeeth(p, 0, halfLen)
	k.drawGuard(p, 0, -halfLen)
	k.drawChain(p, 0, -halfLen-6)

	p.Pop()
}

func (k *keyblade) drawTeeth(p *p5go.Canvas, cx, cy float64) {
	p.Push()
	p.Translate(cx, cy)

	p.Fill("#A8A8A8")
	p.Stroke("#808080")
	p.StrokeWeight(1)

	barW := 35.0
	barH := 6.4
	p.Rect(-barW/2, 0, barW, barH)

	prongW := 7.2
	prongH := 22.4
	gap := 3.2

	p.Rect(-barW/2, barH, prongW, prongH)
	p.Rect(-barW/2+prongW+gap, barH, prongW, prongH*0.65)
	p.Rect(barW/2-2*prongW-gap, barH, prongW, prongH*0.65)
	p.Rect(barW/2-prongW, barH, prongW, prongH)

	p.Fill("#D0D0D0")
	p.NoStroke()
	p.Rect(-barW/2+1.6, 1.6, barW-3.2, barH-3.2)

	p.Pop()
}

func (k *keyblade) drawGuard(p *p5go.Canvas, cx, cy float64) {
	p.Push()
	p.Translate(cx, cy)

	wingW := 14.4
	wingH := 25.6

	p.Fill("#FFD700")
	p.Stroke("#DAA520")
	p.StrokeWeight(1)

	p.Rect(-wingW-9.6, -wingH/2, wingW, wingH)
	p.Rect(9.6, -wingH/2, wingW, wingH)

	p.Rect(-wingW-12, -wingH/2-4.8, wingW+4.8, 6.4)
	p.Rect(7.2, -wingH/2-4.8, wingW+4.8, 6.4)

	p.Fill("#FF3333")
	p.NoStroke()
	p.Circle(0, 0, 6.4)
	p.Fill("#FF7777")
	p.Circle(-0.8, -0.8, 2.4)

	p.Pop()
}

func (k *keyblade) drawChain(p *p5go.Canvas, cx, cy float64) {
	p.Push()
	p.Translate(cx, cy)

	p.NoFill()
	p.Stroke("#B0B0B0")
	p.StrokeWeight(1.2)
	for i := 0.0; i < 14.4; i += 4.8 {
		p.Ellipse(0, -i-2.4, 3.2, 4.8)
	}

	my := -19.2
	p.Fill("#FFD700")
	p.NoStroke()
	p.Circle(0, my, 8.8)
	p.Circle(-4.4, my-4.8, 5.6)
	p.Circle(4.4, my-4.8, 5.6)

	p.Pop()
}