package main

import (
	"fmt"
	"log"
	"math"
	"math/rand"
	"os"
	"time"

	"github.com/gen2brain/raylib-go/raylib"
	"github.com/ojrac/opensimplex-go"
)

const (
	targetWidth  = 1000
	targetHeight = 1000
	sourceScale  = 2
	snapshotsDir = "snapshots"
)

type TextureCam struct {
	SourceWidth  int32
	SourceHeight int32
	LookAt       rl.Vector2
	Zoom         float32
}

func main() {

	sourceWidth := int32(sourceScale * targetWidth)
	sourceHeight := int32(sourceScale * targetHeight)

	os.MkdirAll(snapshotsDir, 0755)
	log := log.New(os.Stdout, "", log.Ldate|log.Ltime|log.Lshortfile)
	storage, err := NewStorage(snapshotsDir)
	if err != nil {
		log.Printf("Error loading storage: %v\n", err)
		os.Exit(1)
	}

	rl.SetConfigFlags(rl.FlagMsaa4xHint)
	rl.InitWindow(targetWidth, targetHeight, "sumi sierpinski arrow")

	// point at source center
	// put source center at center of screen
	var camera = TextureCam{
		LookAt:       rl.Vector2{X: float32(sourceWidth) / 2.0, Y: float32(sourceHeight) / 2.0},
		Zoom:         1.0,
		SourceWidth:  sourceWidth,
		SourceHeight: sourceHeight,
	}

	rl.SetTargetFPS(60)
	t0 := time.Now()

	/*
	 */

	imageField := NewImageField("/home/d/Dropbox/art/passage/data/david.png")

	field :=
		TranslateField{
			x: -float32(sourceWidth / 2.0),
			y: -float32(sourceHeight / 2.0),
			field: &ScaleField{
				scale: 1.0,
				field: &imageField,
			},
		}
	rng := rand.New(rand.NewSource(0))

	contourLayer := NewContourLayer(rng, &field, sourceWidth, sourceHeight)

	sketch := NewSketch(sourceWidth, sourceHeight)
	//sketch.CreateLayer("testPattern", &TestPattern{}, int32(sourceWidth), int32(sourceHeight))
	//sketch.CreateLayer("actors", &contourLayer, int32(sourceWidth), int32(sourceHeight))
	//sketch.CreateLayer("field", &FieldLayer{field: &field, dirty: true}, int32(sourceWidth), int32(sourceHeight))
	sketch.CreateLayer("contours", &contourLayer, int32(sourceWidth), int32(sourceHeight))

	ports := MakePorts()
	ports["sierpinskiArrowAngle"] = Sine{
		Amp:  120,
		Bias: 100,
		Freq: 0.1,
	}

	for !rl.WindowShouldClose() {

		// begin drawing
		t := time.Since(t0).Seconds()

		// set up RenderCtx
		renderCtx := &RenderCtx{
			TargetWidth:  int32(targetWidth),
			TargetHeight: int32(targetHeight),
			SourceWidth:  int32(sourceWidth),
			SourceHeight: int32(sourceHeight),
			Time:         t,
			Ports:        ports.Eval(t),
			Cam:          &camera,
		}

		sketch.Update(renderCtx)

		/**
		 *	MAIN DRAWING
		 */
		rl.BeginDrawing()
		rl.ClearBackground(rl.Blank)

		sketch.Draw(renderCtx)

		if rl.IsKeyDown(rl.KeySpace) {
			capture := sketch.Capture()

			if _, err := storage.Save(capture); err != nil {
				log.Printf("Error saving snapshot: %v\n", err)
			}
		}

		rl.DrawText("Mouse right button drag to move, mouse wheel to zoom", 10, 10, 20, rl.White)

		rl.EndDrawing()

		for ch := rl.GetCharPressed(); ch != 0; ch = rl.GetCharPressed() {
			c := rune(ch)
			if c == 'c' {
				resetCamera(&camera)
			} else if c >= '1' && c <= '9' {
				zoom := 1 << int(ch-'0')
				camera.Zoom = float32(zoom)
			}
		}

		//rl.EndMode2D()

		// HUD
	}

	rl.CloseWindow()
}

func resetCamera(cam *TextureCam) {
	cam.LookAt = rl.Vector2{X: float32(cam.SourceWidth) / 2.0, Y: float32(cam.SourceHeight) / 2.0}
	cam.Zoom = 1.0
}

type FieldLayer struct {
	field Field
	dirty bool
}

func (s *FieldLayer) Update(ctx *RenderCtx) {
	;
}

func (s *FieldLayer) Draw(ctx *RenderCtx) {
	for x := range ctx.SourceWidth {
		for y := range ctx.SourceHeight {
			v := s.field.Get(float32(x), float32(y))
			clr := GrayCurve(v, 1.0)
			rl.DrawPixel(x, y, clr)
		}
	}
	s.dirty = false
}

func (s *FieldLayer) IsDirty() bool {
	return s.dirty
}

type ContourLayer struct {
	field                     Field
	actors                    []*Actor
	rng                       *rand.Rand
	sourceWidth int32
	sourceHeight int32
}

func NewContourLayer(rng *rand.Rand, field Field, sourceWidth int32, sourceHeight int32) ContourLayer {

	actors := make([]*Actor, 0)

	layer := ContourLayer {
		rng: rng,
		field: field,
		actors:       actors,
		sourceWidth:  sourceWidth,
		sourceHeight: sourceHeight,
	}

	layer.AddActors(1)

	return layer
}

func (s *ContourLayer) AddActors(n int) {
	for range n {
		x := s.rng.Int31() % s.sourceWidth
		y := s.rng.Int31() % s.sourceHeight
		newActor :=
			&Actor {
				position: rl.Vector2{X: float32(x), Y: float32(y)},
				field:    s.field,
				stepSize: 1,
				color:    rl.NewColor(11, 35, 176, 100),
			}
		s.actors = append(s.actors, newActor)
	}
}

func (s *ContourLayer) Update(ctx *RenderCtx) {
	s.AddActors(100) // parameterize
}

func (s *ContourLayer) Draw(ctx *RenderCtx) {
	rl.BeginBlendMode(rl.BlendAdditive)
	for _, actor := range s.actors {
		actor.Draw()
	}
	rl.EndBlendMode()
}

func (s *ContourLayer) IsDirty() bool {
	return true
}

type Actor struct {
	position rl.Vector2
	field    Field
	stepSize float32
	color    rl.Color
}

func (a *Actor) Draw() {
	v := a.field.Get(a.position.X, a.position.Y)
	rad := rl.Remap(v, 0, 1, 0, 3*math.Pi)
	nextPosition := rl.Vector2{X: a.position.X + a.stepSize*float32(math.Cos(float64(rad))), Y: a.position.Y + a.stepSize*float32(math.Sin(float64(rad)))}
	rl.DrawLineV(a.position, nextPosition, a.color)
	//fmt.Printf("position %v -> nextPosition %v \n", a.position, nextPosition)
	a.position = nextPosition
}

func RandRadialVec(rng *rand.Rand, minRadius float32, maxRadius float32, loAngle float32, hiAngle float32) rl.Vector2 {
	r := float64(rl.Remap(rng.Float32(), 0, 1, minRadius, maxRadius))
	deg := float64(rl.Remap(rng.Float32(), 0, 1, loAngle, hiAngle))
	rad := rl.Deg2rad * deg
	return rl.Vector2{X: float32(r * math.Cos(rad)), Y: float32(r * math.Sin(rad))}
}

type Worm struct {
	position   rl.Vector2
	angles     []float32
	angleIndex int
	stepSize   int
	renderPct  float32
}

func (w *Worm) Draw(ctx *RenderCtx) {
	rl.PushMatrix()
	rl.Translatef(w.position.X, w.position.Y, 0)
	lastAngle := float32(0.0)
	stepCount := 0
	nudged := false
	for i := range w.angles {
		ii := (i + w.angleIndex) % len(w.angles)
		angle := w.angles[ii]
		deltaAngle := angle - lastAngle
		if !nudged {
			rad := float64(deltaAngle * math.Pi / 180.0)
			nudge := rl.Vector2{X: float32(w.stepSize) * float32(math.Cos(rad)), Y: float32(w.stepSize) * float32(math.Sin(rad))}
			w.position = rl.Vector2Add(w.position, nudge)
			nudged = true
		}
		rl.Rotatef(deltaAngle, 0, 0, 1)
		rl.DrawLine(0, 0, int32(w.stepSize), 0, rl.NewColor(184, 187, 38, 50))
		rl.Translatef(float32(w.stepSize), 0, 0)
		lastAngle = angle
		stepCount++
		if stepCount > int(float32(len(w.angles))*w.renderPct) {
			break
		}
	}
	rl.PopMatrix()
	w.angleIndex = (w.angleIndex + 1) % len(w.angles)
}

type SierpinskiArrow struct{}

func (s *SierpinskiArrow) Draw(ctx *RenderCtx) {
	sierpinskiArrow(ctx, int(ctx.Ports["sierpinskiArrowDepth"]), ctx.Ports["sierpinskiArrowLength"])
}

func sierpinskiArrow(ctx *RenderCtx, order int, length float64) {
	if order == 0 {
		curve(ctx, order, length, ctx.Ports["sierpinskiArrowAngle"])
	} else {
		rl.Rotatef(float32(ctx.Ports["sierpinskiArrowAngle"]), 0, 0, 1)
		curve(ctx, order, length, -ctx.Ports["sierpinskiArrowAngle"])
	}
}

func curve(ctx *RenderCtx, order int, length float64, angle float64) {
	if order == 0 {
		len := int32(length)
		rl.DrawLine(0, 0, len, 0, rl.Black)
		rl.Translatef(float32(length), 0, 0)
	} else {
		curve(ctx, order-1, length/2, -angle)
		rl.Rotatef(float32(angle), 0, 0, 1)
		curve(ctx, order-1, length/2, angle)
		rl.Rotatef(float32(angle), 0, 0, 1)
		curve(ctx, order-1, length/2, -angle)
	}
}

func main2() {
	angles := make([]float32, 1000)
	noise := opensimplex.NewNormalized(0)
	for i := range len(angles) {
		angles[i] = float32(noise.Eval2(float64(i)*0.05, 0.00))*0.1 - 0.05
	}
	frameNum := 0
	for !rl.WindowShouldClose() {
		frameNum++
		// initial transform by halfway again through angle array
		angleIndex := (frameNum / 10) % len(angles)
		angle := angles[angleIndex]
		initAngle := angles[(angleIndex+len(angles)/2)%len(angles)]
		rl.Rotatef(2500*initAngle, 0, 0, 1)
		rl.Translatef(100*initAngle, 100*initAngle, 0)
		fmt.Printf("%.3f", angle)
		rl.EndMode2D()
	}
}
