package main

import (
	"embed"
	"encoding/json"
	"fmt"
	"io/fs"
	"log"
	"net/http"
	"time"
)

// Teris rules https://tetris.fandom.com/wiki/Tetris_Guideline
// Tetrimino types
const (
	EMPTY = iota
	I_PIECE
	O_PIECE
	T_PIECE
	S_PIECE
	Z_PIECE
	J_PIECE
	L_PIECE
)

// Tetrimino shapes (4 rotations each)
var tetriminoShapes = map[int][4][4][4]int{
	I_PIECE: {
		{{0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}, {0, 0, 1, 0}},
		{{0, 0, 0, 0}, {0, 0, 0, 0}, {1, 1, 1, 1}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}},
	},
	O_PIECE: {
		{{0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 1, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
	},
	T_PIECE: {
		{{0, 1, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
		{{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
	},
	S_PIECE: {
		{{0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}},
		{{0, 0, 0, 0}, {0, 1, 1, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}},
		{{1, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
	},
	Z_PIECE: {
		{{1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 0, 1, 0}, {0, 1, 1, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
		{{0, 0, 0, 0}, {1, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {1, 1, 0, 0}, {1, 0, 0, 0}, {0, 0, 0, 0}},
	},
	J_PIECE: {
		{{1, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 1, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
		{{0, 0, 0, 0}, {1, 1, 1, 0}, {0, 0, 1, 0}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {0, 1, 0, 0}, {1, 1, 0, 0}, {0, 0, 0, 0}},
	},
	L_PIECE: {
		{{0, 0, 1, 0}, {1, 1, 1, 0}, {0, 0, 0, 0}, {0, 0, 0, 0}},
		{{0, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 1, 0}, {0, 0, 0, 0}},
		{{0, 0, 0, 0}, {1, 1, 1, 0}, {1, 0, 0, 0}, {0, 0, 0, 0}},
		{{1, 1, 0, 0}, {0, 1, 0, 0}, {0, 1, 0, 0}, {0, 0, 0, 0}},
	},
}

type Tetrimino struct {
	Type     int `json:"type"`
	X        int `json:"x"`
	Y        int `json:"y"`
	Rotation int `json:"rotation"`
}

type GameState struct {
	Board         [40][10]int `json:"board"`
	CurrentPiece  *Tetrimino  `json:"current_piece"`
	NextPieces    []int       `json:"next_pieces"`
	HoldPiece     *int        `json:"hold_piece"`
	Score         int         `json:"score"`
	Level         int         `json:"level"`
	LinesCleared  int         `json:"lines_cleared"`
	GameRunning   bool        `json:"game_running"`
	GameOver      bool        `json:"game_over"`
	CanHold       bool        `json:"can_hold"`
	LinesForLevel int         `json:"lines_for_level"`
}

type Game struct {
	state    *GameState
	bag      []int
	bagIndex int
	clients  map[chan string]bool
}

func NewGame() *Game {
	g := &Game{
		state: &GameState{
			NextPieces:    make([]int, 6),
			Level:         1,
			GameRunning:   false,
			GameOver:      false,
			CanHold:       true,
			LinesForLevel: 5,
		},
		clients: make(map[chan string]bool),
	}
	g.fillBag()
	g.fillNextPieces()
	return g
}

func (g *Game) fillBag() {
	g.bag = []int{I_PIECE, O_PIECE, T_PIECE, S_PIECE, Z_PIECE, J_PIECE, L_PIECE}
	// Simple shuffle using current time
	for i := len(g.bag) - 1; i > 0; i-- {
		j := int(time.Now().UnixNano()) % (i + 1)
		g.bag[i], g.bag[j] = g.bag[j], g.bag[i]
	}
	g.bagIndex = 0
}

func (g *Game) getNextPiece() int {
	if g.bagIndex >= len(g.bag) {
		g.fillBag()
	}
	piece := g.bag[g.bagIndex]
	g.bagIndex++
	return piece
}

func (g *Game) fillNextPieces() {
	for i := 0; i < 6; i++ {
		g.state.NextPieces[i] = g.getNextPiece()
	}
}

func (g *Game) spawnPiece() {
	pieceType := g.state.NextPieces[0]
	// Shift next pieces
	copy(g.state.NextPieces[:5], g.state.NextPieces[1:])
	g.state.NextPieces[5] = g.getNextPiece()

	// Spawn position based on guidelines
	x := 3 // left-middle for most pieces
	if pieceType == I_PIECE || pieceType == O_PIECE {
		x = 4 // middle for I and O
	}

	g.state.CurrentPiece = &Tetrimino{
		Type:     pieceType,
		X:        x,
		Y:        21,
		Rotation: 0,
	}

	// Drop one space if possible
	if g.canMove(g.state.CurrentPiece.X, g.state.CurrentPiece.Y-1, g.state.CurrentPiece.Rotation) {
		g.state.CurrentPiece.Y--
	}

	// Check for game over
	if !g.canMove(g.state.CurrentPiece.X, g.state.CurrentPiece.Y, g.state.CurrentPiece.Rotation) {
		g.state.GameOver = true
		g.state.GameRunning = false
	}

	g.state.CanHold = true
}

func (g *Game) canMove(x, y, rotation int) bool {
	shape := tetriminoShapes[g.state.CurrentPiece.Type][rotation]
	for py := 0; py < 4; py++ {
		for px := 0; px < 4; px++ {
			if shape[py][px] == 0 {
				continue
			}
			boardX := x + px
			boardY := y + py
			if boardX < 0 || boardX >= 10 || boardY < 0 {
				return false
			}
			if boardY < 40 && g.state.Board[boardY][boardX] != EMPTY {
				return false
			}
		}
	}
	return true
}

func (g *Game) lockPiece() {
	shape := tetriminoShapes[g.state.CurrentPiece.Type][g.state.CurrentPiece.Rotation]
	for py := 0; py < 4; py++ {
		for px := 0; px < 4; px++ {
			if shape[py][px] == 0 {
				continue
			}
			boardX := g.state.CurrentPiece.X + px
			boardY := g.state.CurrentPiece.Y + py
			if boardY < 40 {
				g.state.Board[boardY][boardX] = g.state.CurrentPiece.Type
			}
		}
	}
	g.clearLines()
	g.spawnPiece()
}

func (g *Game) clearLines() {
	linesCleared := 0
	for y := 0; y < 40; y++ {
		full := true
		for x := 0; x < 10; x++ {
			if g.state.Board[y][x] == EMPTY {
				full = false
				break
			}
		}
		if full {
			// Remove line
			for moveY := y; moveY < 39; moveY++ {
				for x := 0; x < 10; x++ {
					g.state.Board[moveY][x] = g.state.Board[moveY+1][x]
				}
			}
			// Clear top line
			for x := 0; x < 10; x++ {
				g.state.Board[39][x] = EMPTY
			}
			linesCleared++
			y-- // Check same line again
		}
	}

	if linesCleared > 0 {
		g.state.LinesCleared += linesCleared
		g.updateScore(linesCleared)
		g.updateLevel()
	}
}

func (g *Game) updateScore(lines int) {
	baseScore := map[int]int{1: 100, 2: 300, 3: 500, 4: 800}
	if score, ok := baseScore[lines]; ok {
		g.state.Score += score * g.state.Level
	}
}

func (g *Game) updateLevel() {
	requiredLines := g.state.Level * g.state.LinesForLevel
	if g.state.LinesCleared >= requiredLines {
		g.state.Level++
	}
}

func (g *Game) move(direction string) {
	if !g.state.GameRunning || g.state.CurrentPiece == nil {
		return
	}

	newX := g.state.CurrentPiece.X
	newY := g.state.CurrentPiece.Y

	switch direction {
	case "left":
		newX--
	case "right":
		newX++
	case "down":
		newY--
	}

	if g.canMove(newX, newY, g.state.CurrentPiece.Rotation) {
		g.state.CurrentPiece.X = newX
		g.state.CurrentPiece.Y = newY
	}
}

func (g *Game) rotate(direction string) {
	if !g.state.GameRunning || g.state.CurrentPiece == nil {
		return
	}

	newRotation := g.state.CurrentPiece.Rotation
	if direction == "cw" {
		newRotation = (newRotation + 1) % 4
	} else {
		newRotation = (newRotation + 3) % 4
	}

	if g.canMove(g.state.CurrentPiece.X, g.state.CurrentPiece.Y, newRotation) {
		g.state.CurrentPiece.Rotation = newRotation
	}
}

func (g *Game) hardDrop() {
	if !g.state.GameRunning || g.state.CurrentPiece == nil {
		return
	}

	for g.canMove(g.state.CurrentPiece.X, g.state.CurrentPiece.Y-1, g.state.CurrentPiece.Rotation) {
		g.state.CurrentPiece.Y--
	}
	g.lockPiece()
}

func (g *Game) hold() {
	if !g.state.GameRunning || g.state.CurrentPiece == nil || !g.state.CanHold {
		return
	}

	if g.state.HoldPiece == nil {
		g.state.HoldPiece = &g.state.CurrentPiece.Type
		g.spawnPiece()
	} else {
		temp := *g.state.HoldPiece
		g.state.HoldPiece = &g.state.CurrentPiece.Type

		x := 3
		if temp == I_PIECE || temp == O_PIECE {
			x = 4
		}

		g.state.CurrentPiece = &Tetrimino{
			Type:     temp,
			X:        x,
			Y:        21,
			Rotation: 0,
		}

		if g.canMove(g.state.CurrentPiece.X, g.state.CurrentPiece.Y-1, g.state.CurrentPiece.Rotation) {
			g.state.CurrentPiece.Y--
		}
	}
	g.state.CanHold = false
}

func (g *Game) startGame() {
	g.state = &GameState{
		NextPieces:    make([]int, 6),
		Level:         1,
		GameRunning:   true,
		GameOver:      false,
		CanHold:       true,
		LinesForLevel: 5,
	}
	g.fillBag()
	g.fillNextPieces()
	g.spawnPiece()
}

func (g *Game) tick() {
	if !g.state.GameRunning || g.state.CurrentPiece == nil {
		return
	}

	if g.canMove(g.state.CurrentPiece.X, g.state.CurrentPiece.Y-1, g.state.CurrentPiece.Rotation) {
		g.state.CurrentPiece.Y--
	} else {
		g.lockPiece()
	}
}

func (g *Game) broadcast() {
	data, err := json.Marshal(g.state)
	if err != nil {
		log.Printf("Error marshaling game state: %v", err)
		return
	}
	message := fmt.Sprintf("data: %s\n\n", string(data))
	
	// Non-blocking broadcast to all clients
	for client := range g.clients {
		select {
		case client <- message:
			// Message sent successfully
		default:
			// Channel full or client blocked, skip this client
			log.Printf("Dropping message for slow client")
		}
	}
}

var game = NewGame()

//go:embed frontend/*
var frontendFS embed.FS

func main() {
	// Serve static files (index.html)
	// http.Handle("/", http.FileServer(http.Dir("./frontend")))

	// Create a sub-filesystem to serve files from the 'frontend' directory.
	fs, err := fs.Sub(frontendFS, "frontend")
	if err != nil {
		log.Fatal(err)
	}

	// Serve static files from the embedded filesystem
	http.Handle("/", http.FileServer(http.FS(fs)))

	// API endpoints
	http.HandleFunc("/events", handleSSE)
	http.HandleFunc("/action", handleAction)

	// Game loop
	go func() {
		ticker := time.NewTicker(time.Second)
		defer ticker.Stop()

		for {
			select {
			case <-ticker.C:
				if game.state.GameRunning {
					game.tick()
					game.broadcast()
				}
			}
		}
	}()

	fmt.Println("Server starting on :4444")
	fmt.Println("Open http://localhost:4444 in your browser")
	log.Fatal(http.ListenAndServe(":4444", nil))
}

func handleSSE(w http.ResponseWriter, r *http.Request) {
	// Critical SSE headers for nginx compatibility
	w.Header().Set("Content-Type", "text/event-stream")
	w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
	w.Header().Set("Connection", "keep-alive")
	w.Header().Set("X-Accel-Buffering", "no") // Disable nginx buffering
	w.Header().Set("Access-Control-Allow-Origin", "*")

	flusher, ok := w.(http.Flusher)
	if !ok {
		http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
		return
	}

	client := make(chan string, 10) // Buffered channel
	game.clients[client] = true

	// Send initial state
	data, _ := json.Marshal(game.state)
	fmt.Fprintf(w, "data: %s\n\n", string(data))
	flusher.Flush()

	// Heartbeat to keep connection alive
	ticker := time.NewTicker(15 * time.Second)
	defer ticker.Stop()

	for {
		select {
		case message := <-client:
			fmt.Fprint(w, message)
			flusher.Flush()
		case <-ticker.C:
			// Send comment as heartbeat
			fmt.Fprintf(w, ": heartbeat\n\n")
			flusher.Flush()
		case <-r.Context().Done():
			delete(game.clients, client)
			close(client)
			return
		}
	}
}

func handleAction(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Header().Set("Access-Control-Allow-Methods", "POST")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type")

	if r.Method == "OPTIONS" {
		return
	}

	action := r.URL.Query().Get("action")
	direction := r.URL.Query().Get("direction")

	switch action {
	case "move":
		game.move(direction)
	case "rotate":
		game.rotate(direction)
	case "hard_drop":
		game.hardDrop()
	case "hold":
		game.hold()
	case "start":
		game.startGame()
	}

	game.broadcast()
	w.WriteHeader(http.StatusOK)
}
