package main

import (
	"fmt"
	"math/rand"
	"sort"
	"time"

	"github.com/ryomak/p5go"
)

const (
	cellSize    = 20
	canvasWidth = 300
	canvasHeight = 300
)

var (
	columnCount   int
	rowCount      int
	currentCells  [][]int
	nextCells     [][]int
	shapeColors   map[string][3]uint8 // 形状ごとの色を管理するマップ
)

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

func setup(p *p5go.Canvas) {
	p.FrameRate(10)
	p.CreateCanvas(canvasWidth, canvasHeight)

	columnCount = canvasWidth / cellSize
	rowCount = canvasHeight / cellSize

	// 配列を初期化
	currentCells = make2DArray(columnCount, rowCount)
	nextCells = make2DArray(columnCount, rowCount)

	shapeColors = make(map[string][3]uint8)

	// ボードをランダム化
	randomizeBoard()
}

// 2次元配列を作成する関数
func make2DArray(cols, rows int) [][]int {
	arr := make([][]int, cols)
	for i := range arr {
		arr[i] = make([]int, rows)
	}
	return arr
}

// ボードをランダム化(初期密度を制御)
func randomizeBoard() {
	rand.Seed(time.Now().UnixNano())
	for column := 0; column < columnCount; column++ {
		for row := 0; row < rowCount; row++ {
			// 初期密度を25%に設定
			if rand.Float64() < 0.25 {
				currentCells[column][row] = 1
			} else {
				currentCells[column][row] = 0
			}
		}
	}
}

func draw(p *p5go.Canvas) {
	p.Background(255)

	// 領域の ID と形状キーを割り当て
	regions, shapeKeys := assignRegions()

	// グリッドを描画
	for column := 0; column < columnCount; column++ {
		for row := 0; row < rowCount; row++ {
			cell := currentCells[column][row]
			if cell == 1 {
				shapeKey := shapeKeys[regions[column][row]]
				color := getColorForShape(shapeKey)
				p.Fill(color[0], color[1], color[2], 255)
			} else {
				// 死んだセルは薄いグレー
				p.Fill(220, 220, 220, 255)
			}
			p.Rect(float64(column*cellSize), float64(row*cellSize), float64(cellSize), float64(cellSize))
		}
	}

	// 次世代を生成
	generate()
}

func mousePressed(p *p5go.Canvas) {
	randomizeBoard()
}

// 次世代を生成
func generate() {
	for column := 0; column < columnCount; column++ {
		for row := 0; row < rowCount; row++ {
			// 周囲の生存セルを数える
			neighbours := countNeighbours(column, row)

			// ライフゲームのルールを適用
			if currentCells[column][row] == 1 { // 現在生存中のセル
				if neighbours < 2 || neighbours > 3 {
					nextCells[column][row] = 0 // 過疎または過密で死滅
				} else {
					nextCells[column][row] = 1 // 生存を維持
				}
			} else { // 現在死んでいるセル
				if neighbours == 3 {
					nextCells[column][row] = 1 // 誕生
				} else {
					nextCells[column][row] = 0 // 死のまま維持
				}
			}
		}
	}

	// 配列をスワップ
	currentCells, nextCells = nextCells, currentCells
}

// 周囲の生存セルを数える
func countNeighbours(column, row int) int {
	neighbours := 0
	for x := -1; x <= 1; x++ {
		for y := -1; y <= 1; y++ {
			if x == 0 && y == 0 {
				continue // 自分自身はカウントしない
			}
			col := (column + x + columnCount) % columnCount
			r := (row + y + rowCount) % rowCount
			neighbours += currentCells[col][r]
		}
	}
	return neighbours
}

// 領域を割り当てる
func assignRegions() ([][]int, map[int]string) {
	regionID := 0
	regions := make2DArray(columnCount, rowCount)
	shapeKeys := make(map[int]string)

	visited := make2DArray(columnCount, rowCount)

	// Flood Fill を使って領域を識別
	for column := 0; column < columnCount; column++ {
		for row := 0; row < rowCount; row++ {
			if currentCells[column][row] == 1 && regions[column][row] == 0 {
				regionID++
				shape := []string{}
				floodFill(column, row, regionID, regions, visited, &shape)
				shapeKeys[regionID] = calculateShapeKey(shape)
			}
		}
	}
	return regions, shapeKeys
}

// Flood Fill アルゴリズムで領域を塗りつぶし、形状を記録
func floodFill(column, row, regionID int, regions, visited [][]int, shape *[]string) {
	if column < 0 || column >= columnCount || row < 0 || row >= rowCount {
		return
	}
	if visited[column][row] == 1 || currentCells[column][row] == 0 {
		return
	}

	visited[column][row] = 1
	regions[column][row] = regionID

	// 形状の相対位置を記録
	*shape = append(*shape, fmt.Sprintf("%d,%d", column, row))

	// 周囲8方向に対して再帰的に処理
	directions := [][2]int{
		{-1, -1}, {0, -1}, {1, -1},
		{-1, 0}, {1, 0},
		{-1, 1}, {0, 1}, {1, 1},
	}
	for _, dir := range directions {
		floodFill(column+dir[0], row+dir[1], regionID, regions, visited, shape)
	}
}

// 形状キーを計算
func calculateShapeKey(shape []string) string {
	sort.Strings(shape) // 形状を一意にするためソート
	return fmt.Sprintf("%v", shape)
}

// 形状ごとの色を取得
func getColorForShape(shapeKey string) [3]uint8 {
	if color, exists := shapeColors[shapeKey]; exists {
		return color
	}
	newColor := randomColor()
	shapeColors[shapeKey] = newColor
	return newColor
}

// ランダムな色を生成
func randomColor() [3]uint8 {
	return [3]uint8{
		uint8(rand.Intn(200) + 50), // R: 明るい色
		uint8(rand.Intn(200) + 50), // G
		uint8(rand.Intn(200) + 50), // B
	}
}