package main

import (
	"math"
	"math/rand"

	"github.com/ryomak/p5go"
)

const (
	canvasWidth  = 400
	canvasHeight = 400
	frameRate    = 30

	maxCells  = 30   // 細胞の最大数
	minRadius = 5    // 細胞がこれ以下になったらリセットする
	maxSize   = 50.0 // 成長フェーズの上限サイズ。これ以上になると縮小フェーズに移行

	// 個体ごとのパラメータの設定範囲
	divisionThresholdMin = 2.0 // 分裂閾値の下限(秒)
	divisionThresholdMax = 4.0 // 分裂閾値の上限(秒)
	growthRateMin        = 2.0 // 成長速度の下限
	growthRateMax        = 3.0 // 成長速度の上限
	shrinkRateMin        = 2.0 // 縮小速度の下限
	shrinkRateMax        = 3.0 // 縮小速度の上限
	speedFactorMin       = 2.0 // 移動速度係数の下限
	speedFactorMax       = 4.0 // 移動速度係数の上限

	// 細胞間相互作用のパラメータ
	repulsionConstant  = 0.5 // 近すぎる場合の反発力
	attractionConstant = 0.5 // わずかな引力(重力的な効果として強めに設定)
)

// Cell は細胞の状態を表します。
type Cell struct {
	x, y              float64  // 位置(中心)
	r                 float64  // 半径
	age               float64  // 経過時間(秒)
	divisionThreshold float64  // 分裂までの時間(秒)
	vx, vy            float64  // 速度
	color             [3]uint8 // 細胞の色
	isShrinking       bool     // false: 成長フェーズ, true: 縮小フェーズ

	growthRate  float64 // 個体ごとの成長速度
	shrinkRate  float64 // 個体ごとの縮小速度
	speedFactor float64 // 個体ごとの移動速度係数
}

// mutateColor は、与えられた色に±20のランダムな変動を加えた色を返します。
func mutateColor(c [3]uint8) [3]uint8 {
	delta := 20
	r := int(c[0]) + rand.Intn(delta*2+1) - delta
	g := int(c[1]) + rand.Intn(delta*2+1) - delta
	b := int(c[2]) + rand.Intn(delta*2+1) - delta
	if r < 0 {
		r = 0
	} else if r > 255 {
		r = 255
	}
	if g < 0 {
		g = 0
	} else if g > 255 {
		g = 255
	}
	if b < 0 {
		b = 0
	} else if b > 255 {
		b = 255
	}
	return [3]uint8{uint8(r), uint8(g), uint8(b)}
}

// Update は、dt秒分だけセルの状態を更新します。
// 分裂・縮小・成長・移動などを処理し、必要ならシミュレーション全体に新たな細胞を追加します。
func (c *Cell) Update(dt float64, sim *Simulation) {
	// 年齢更新
	c.age += dt

	// 成長フェーズか縮小フェーズかで、半径の更新方法を切り替え
	if !c.isShrinking {
		c.r += c.growthRate * dt
	} else {
		c.r -= c.shrinkRate * dt
	}

	// 位置更新(速度に個体ごとの speedFactor をかける)
	c.x += c.vx * dt * c.speedFactor
	c.y += c.vy * dt * c.speedFactor

	// 画面端で反射
	if c.x < c.r {
		c.x = c.r
		c.vx = -c.vx
	} else if c.x > canvasWidth-c.r {
		c.x = canvasWidth - c.r
		c.vx = -c.vx
	}
	if c.y < c.r {
		c.y = c.r
		c.vy = -c.vy
	} else if c.y > canvasHeight-c.r {
		c.y = canvasHeight - c.r
		c.vy = -c.vy
	}

	// 成長フェーズ中で、最大サイズに達したら縮小フェーズへ
	if !c.isShrinking && c.r >= maxSize {
		c.isShrinking = true
	}

	// 分裂イベント:年齢が閾値を超え、かつ全体細胞数が maxCells 未満なら分裂
	if c.age >= c.divisionThreshold && len(sim.cells) < maxCells {
		// 分裂時、元の細胞はリセット(半径70%に縮小、年齢リセット、成長フェーズに戻す)
		c.age = 0
		c.r *= 0.7
		c.divisionThreshold = randomInRange(divisionThresholdMin, divisionThresholdMax)
		c.isShrinking = false

		// 新たな細胞を元の細胞の近傍に生成
		newCell := Cell{
			x:                 c.x + (rand.Float64()*2-1)*c.r,
			y:                 c.y + (rand.Float64()*2-1)*c.r,
			r:                 c.r,
			age:               0,
			divisionThreshold: randomInRange(divisionThresholdMin, divisionThresholdMax),
			vx:                (rand.Float64()*2 - 1) * 40,
			vy:                (rand.Float64()*2 - 1) * 40,
			color:             mutateColor(c.color),
			isShrinking:       false,
			growthRate:        randomInRange(growthRateMin, growthRateMax),
			shrinkRate:        randomInRange(shrinkRateMin, shrinkRateMax),
			speedFactor:       randomInRange(speedFactorMin, speedFactorMax),
		}
		sim.cells = append(sim.cells, newCell)
	}

	// もし縮小フェーズ中で半径が minRadius 以下になったら、リセットして再び成長フェーズに戻す
	if c.isShrinking && c.r < minRadius {
		c.r = 20
		c.age = 0
		c.isShrinking = false
		c.divisionThreshold = randomInRange(divisionThresholdMin, divisionThresholdMax)
		c.vx = (rand.Float64()*2 - 1) * 40
		c.vy = (rand.Float64()*2 - 1) * 40
		c.growthRate = randomInRange(growthRateMin, growthRateMax)
		c.shrinkRate = randomInRange(shrinkRateMin, shrinkRateMax)
		c.speedFactor = randomInRange(speedFactorMin, speedFactorMax)
		// 色はそのまま
	}
}

// Simulation はシミュレーション全体の状態を管理します。
type Simulation struct {
	cells []Cell
}

// NewSimulation は、中央に1個の初期細胞を配置して Simulation を生成します。
func NewSimulation() *Simulation {
	sim := &Simulation{
		cells: make([]Cell, 0),
	}
	initialCell := Cell{
		x:                 canvasWidth / 2,
		y:                 canvasHeight / 2,
		r:                 20,
		age:               0,
		divisionThreshold: randomInRange(divisionThresholdMin, divisionThresholdMax),
		vx:                (rand.Float64()*2 - 1) * 40,
		vy:                (rand.Float64()*2 - 1) * 40,
		color:             [3]uint8{uint8(rand.Intn(256)), uint8(rand.Intn(256)), uint8(rand.Intn(256))},
		isShrinking:       false,
		growthRate:        randomInRange(growthRateMin, growthRateMax),
		shrinkRate:        randomInRange(shrinkRateMin, shrinkRateMax),
		speedFactor:       randomInRange(speedFactorMin, speedFactorMax),
	}
	sim.cells = append(sim.cells, initialCell)
	return sim
}

// Update は、dt秒分だけシミュレーション全体の状態を更新します。
// まず、細胞間の相互作用(反発・引力)を計算し、各細胞の速度に反映した後、各細胞の Update を呼び出します。
func (sim *Simulation) Update(dt float64) {
	n := len(sim.cells)
	// 1. 細胞間相互作用の力計算
	type force struct {
		fx, fy float64
	}
	forces := make([]force, n)
	for i := 0; i < n; i++ {
		forces[i] = force{0, 0}
	}
	for i := 0; i < n; i++ {
		for j := i + 1; j < n; j++ {
			dx := sim.cells[i].x - sim.cells[j].x
			dy := sim.cells[i].y - sim.cells[j].y
			d := math.Hypot(dx, dy)
			if d < 0.001 {
				continue
			}
			desired := sim.cells[i].r + sim.cells[j].r
			if d < desired { // 重なっているなら反発
				overlap := (desired - d)
				f := overlap * repulsionConstant
				fx := (dx / d) * f
				fy := (dy / d) * f
				forces[i].fx += fx
				forces[i].fy += fy
				forces[j].fx -= fx
				forces[j].fy -= fy
			} else if d < desired*2 { // わずかに離れているなら引力
				f := (d - desired) * attractionConstant
				fx := (dx / d) * f
				fy := (dy / d) * f
				forces[i].fx -= fx
				forces[i].fy -= fy
				forces[j].fx += fx
				forces[j].fy += fy
			}
		}
	}
	for i := 0; i < n; i++ {
		sim.cells[i].vx += forces[i].fx * dt
		sim.cells[i].vy += forces[i].fy * dt
	}

	// 2. 各細胞の Update を呼び出す
	for i := 0; i < n; i++ {
		sim.cells[i].Update(dt, sim)
	}
}

// Draw は、各細胞をキャンバスに描画します。
func (sim *Simulation) Draw(p *p5go.Canvas) {
	for _, cell := range sim.cells {
		p.Fill(float64(cell.color[0]), float64(cell.color[1]), float64(cell.color[2]), 200)
		p.NoStroke()
		p.Ellipse(cell.x, cell.y, cell.r*2, cell.r*2)
	}
}

// randomInRange は、min以上max未満のランダムな値を返します。
func randomInRange(min, max float64) float64 {
	return min + rand.Float64()*(max-min)
}

func main() {
	sim := NewSimulation()
	p5go.Run("#canvas-detail",
		p5go.Setup(func(p *p5go.Canvas) {
			p.CreateCanvas(canvasWidth, canvasHeight)
			p.FrameRate(frameRate)
		}),
		p5go.Draw(func(p *p5go.Canvas) {
			dt := 1.0 / float64(frameRate)
			sim.Update(dt)
			p.Background(0)
			sim.Draw(p)
		}),
	)
	select {}
}