package main

import "fmt"

const (
	Empty  = 0
	Pawn   = 1
	Knight = 2
	Bishop = 3
	Rook   = 4
	Queen  = 5
	King   = 6
)

const (
	White = 1
	Black = 2
)

type Square struct {
	Piece int
	Color int
}

type Position struct {
	Row int
	Col int
}

type Board struct {
	Grid            [8][8]Square
	Castling        map[string]bool
	EnPassantTarget *Position
	MoveHistory     []string
}

func NewBoard() *Board {
	b := &Board{
		Castling: map[string]bool{"K": true, "Q": true, "k": true, "q": true},
	}
	backRank := []int{Rook, Knight, Bishop, Queen, King, Bishop, Knight, Rook}
	for col, p := range backRank {
		b.Grid[0][col] = Square{Piece: p, Color: Black}
		b.Grid[7][col] = Square{Piece: p, Color: White}
	}
	for col := 0; col < 8; col++ {
		b.Grid[1][col] = Square{Piece: Pawn, Color: Black}
		b.Grid[6][col] = Square{Piece: Pawn, Color: White}
	}
	return b
}

func (b *Board) Clone() *Board {
	clone := *b
	clone.Castling = make(map[string]bool)
	for k, v := range b.Castling {
		clone.Castling[k] = v
	}
	if b.EnPassantTarget != nil {
		pt := *b.EnPassantTarget
		clone.EnPassantTarget = &pt
	}
	return &clone
}

func (b *Board) inBounds(r, c int) bool {
	return r >= 0 && r < 8 && c >= 0 && c < 8
}

func (b *Board) isSquareAttacked(pos Position, byColor int) bool {
	r, c := pos.Row, pos.Col
	pawnDir := -1
	if byColor == Black {
		pawnDir = 1
	}
	for dc := -1; dc <= 1; dc += 2 {
		nr, nc := r-pawnDir, c+dc
		if b.inBounds(nr, nc) {
			if b.Grid[nr][nc].Piece == Pawn && b.Grid[nr][nc].Color == byColor {
				return true
			}
		}
	}
	knightMoves := [][2]int{{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}
	for _, dm := range knightMoves {
		nr, nc := r+dm[0], c+dm[1]
		if b.inBounds(nr, nc) {
			if b.Grid[nr][nc].Piece == Knight && b.Grid[nr][nc].Color == byColor {
				return true
			}
		}
	}
	for dr := -1; dr <= 1; dr++ {
		for dc := -1; dc <= 1; dc++ {
			if dr == 0 && dc == 0 {
				continue
			}
			nr, nc := r+dr, c+dc
			if b.inBounds(nr, nc) {
				if b.Grid[nr][nc].Piece == King && b.Grid[nr][nc].Color == byColor {
					return true
				}
			}
		}
	}
	ortho := [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
	for _, dir := range ortho {
		nr, nc := r+dir[0], c+dir[1]
		for b.inBounds(nr, nc) {
			s := b.Grid[nr][nc]
			if s.Piece != Empty {
				if s.Color == byColor && (s.Piece == Rook || s.Piece == Queen) {
					return true
				}
				break
			}
			nr += dir[0]
			nc += dir[1]
		}
	}
	diag := [][2]int{{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}
	for _, dir := range diag {
		nr, nc := r+dir[0], c+dir[1]
		for b.inBounds(nr, nc) {
			s := b.Grid[nr][nc]
			if s.Piece != Empty {
				if s.Color == byColor && (s.Piece == Bishop || s.Piece == Queen) {
					return true
				}
				break
			}
			nr += dir[0]
			nc += dir[1]
		}
	}
	return false
}

func (b *Board) findKing(color int) Position {
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			if b.Grid[r][c].Piece == King && b.Grid[r][c].Color == color {
				return Position{r, c}
			}
		}
	}
	return Position{}
}

func (b *Board) isInCheck(color int) bool {
	kingPos := b.findKing(color)
	oppColor := Black
	if color == Black {
		oppColor = White
	}
	return b.isSquareAttacked(kingPos, oppColor)
}

type Move struct {
	From      Position
	To        Position
	Promotion int
}

func (b *Board) generatePseudoLegalMoves(color int) []Move {
	var moves []Move
	for r := 0; r < 8; r++ {
		for c := 0; c < 8; c++ {
			s := b.Grid[r][c]
			if s.Piece == Empty || s.Color != color {
				continue
			}
			from := Position{r, c}
			switch s.Piece {
			case Pawn:
				moves = append(moves, b.pawnMoves(from, color)...)
			case Knight:
				moves = append(moves, b.knightMoves(from, color)...)
			case Bishop:
				moves = append(moves, b.slideMoves(from, color, diagDirs)...)
			case Rook:
				moves = append(moves, b.slideMoves(from, color, orthoDirs)...)
			case Queen:
				all := append(append([][2]int{}, diagDirs...), orthoDirs...)
				moves = append(moves, b.slideMoves(from, color, all)...)
			case King:
				moves = append(moves, b.kingMoves(from, color)...)
			}
		}
	}
	return moves
}

var orthoDirs = [][2]int{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}
var diagDirs = [][2]int{{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}

func (b *Board) pawnMoves(from Position, color int) []Move {
	var moves []Move
	r, c := from.Row, from.Col
	dir := -1
	if color == Black {
		dir = 1
	}
	nr := r + dir
	if b.inBounds(nr, c) && b.Grid[nr][c].Piece == Empty {
		if (color == White && nr == 0) || (color == Black && nr == 7) {
			for _, p := range []int{Queen, Rook, Bishop, Knight} {
				moves = append(moves, Move{From: from, To: Position{nr, c}, Promotion: p})
			}
		} else {
			moves = append(moves, Move{From: from, To: Position{nr, c}})
		}
		startRow := 6
		if color == Black {
			startRow = 1
		}
		if r == startRow {
			nr2 := r + 2*dir
			if b.inBounds(nr2, c) && b.Grid[nr2][c].Piece == Empty {
				moves = append(moves, Move{From: from, To: Position{nr2, c}})
			}
		}
	}
	for dc := -1; dc <= 1; dc += 2 {
		nc := c + dc
		if !b.inBounds(nr, nc) {
			continue
		}
		target := b.Grid[nr][nc]
		if target.Piece != Empty && target.Color != color {
			if (color == White && nr == 0) || (color == Black && nr == 7) {
				for _, p := range []int{Queen, Rook, Bishop, Knight} {
					moves = append(moves, Move{From: from, To: Position{nr, nc}, Promotion: p})
				}
			} else {
				moves = append(moves, Move{From: from, To: Position{nr, nc}})
			}
		}
		if b.EnPassantTarget != nil && b.EnPassantTarget.Row == nr && b.EnPassantTarget.Col == nc {
			moves = append(moves, Move{From: from, To: Position{nr, nc}})
		}
	}
	return moves
}

func (b *Board) knightMoves(from Position, color int) []Move {
	var moves []Move
	kd := [][2]int{{-2, -1}, {-2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}, {2, -1}, {2, 1}}
	for _, d := range kd {
		nr, nc := from.Row+d[0], from.Col+d[1]
		if !b.inBounds(nr, nc) {
			continue
		}
		if b.Grid[nr][nc].Color != color {
			moves = append(moves, Move{From: from, To: Position{nr, nc}})
		}
	}
	return moves
}

func (b *Board) slideMoves(from Position, color int, dirs [][2]int) []Move {
	var moves []Move
	for _, d := range dirs {
		nr, nc := from.Row+d[0], from.Col+d[1]
		for b.inBounds(nr, nc) {
			if b.Grid[nr][nc].Piece == Empty {
				moves = append(moves, Move{From: from, To: Position{nr, nc}})
			} else {
				if b.Grid[nr][nc].Color != color {
					moves = append(moves, Move{From: from, To: Position{nr, nc}})
				}
				break
			}
			nr += d[0]
			nc += d[1]
		}
	}
	return moves
}

func (b *Board) kingMoves(from Position, color int) []Move {
	var moves []Move
	for dr := -1; dr <= 1; dr++ {
		for dc := -1; dc <= 1; dc++ {
			if dr == 0 && dc == 0 {
				continue
			}
			nr, nc := from.Row+dr, from.Col+dc
			if !b.inBounds(nr, nc) {
				continue
			}
			if b.Grid[nr][nc].Color != color {
				moves = append(moves, Move{From: from, To: Position{nr, nc}})
			}
		}
	}
	oppColor := Black
	if color == White {
		oppColor = Black
	} else {
		oppColor = White
	}
	if color == White && from.Row == 7 && from.Col == 4 {
		if b.Castling["K"] && b.Grid[7][5].Piece == Empty && b.Grid[7][6].Piece == Empty {
			if !b.isSquareAttacked(Position{7, 4}, oppColor) &&
				!b.isSquareAttacked(Position{7, 5}, oppColor) &&
				!b.isSquareAttacked(Position{7, 6}, oppColor) {
				moves = append(moves, Move{From: from, To: Position{7, 6}})
			}
		}
		if b.Castling["Q"] && b.Grid[7][3].Piece == Empty && b.Grid[7][2].Piece == Empty && b.Grid[7][1].Piece == Empty {
			if !b.isSquareAttacked(Position{7, 4}, oppColor) &&
				!b.isSquareAttacked(Position{7, 3}, oppColor) &&
				!b.isSquareAttacked(Position{7, 2}, oppColor) {
				moves = append(moves, Move{From: from, To: Position{7, 2}})
			}
		}
	} else if color == Black && from.Row == 0 && from.Col == 4 {
		if b.Castling["k"] && b.Grid[0][5].Piece == Empty && b.Grid[0][6].Piece == Empty {
			if !b.isSquareAttacked(Position{0, 4}, oppColor) &&
				!b.isSquareAttacked(Position{0, 5}, oppColor) &&
				!b.isSquareAttacked(Position{0, 6}, oppColor) {
				moves = append(moves, Move{From: from, To: Position{0, 6}})
			}
		}
		if b.Castling["q"] && b.Grid[0][3].Piece == Empty && b.Grid[0][2].Piece == Empty && b.Grid[0][1].Piece == Empty {
			if !b.isSquareAttacked(Position{0, 4}, oppColor) &&
				!b.isSquareAttacked(Position{0, 3}, oppColor) &&
				!b.isSquareAttacked(Position{0, 2}, oppColor) {
				moves = append(moves, Move{From: from, To: Position{0, 2}})
			}
		}
	}
	return moves
}

func (b *Board) generateLegalMoves(color int) []Move {
	pseudo := b.generatePseudoLegalMoves(color)
	var legal []Move
	for _, m := range pseudo {
		clone := b.Clone()
		clone.applyMove(m)
		if !clone.isInCheck(color) {
			legal = append(legal, m)
		}
	}
	return legal
}

func (b *Board) applyMove(m Move) {
	from, to := m.From, m.To
	movingPiece := b.Grid[from.Row][from.Col]
	if movingPiece.Piece == Pawn && to.Col != from.Col && b.Grid[to.Row][to.Col].Piece == Empty {
		b.Grid[from.Row][to.Col] = Square{}
	}
	b.Grid[to.Row][to.Col] = movingPiece
	b.Grid[from.Row][from.Col] = Square{}
	if movingPiece.Piece == Pawn && m.Promotion != 0 {
		b.Grid[to.Row][to.Col].Piece = m.Promotion
	}
	if movingPiece.Piece == King && MathAbs(to.Col-from.Col) == 2 {
		row := from.Row
		if to.Col == 6 {
			b.Grid[row][5] = b.Grid[row][7]
			b.Grid[row][7] = Square{}
		} else if to.Col == 2 {
			b.Grid[row][3] = b.Grid[row][0]
			b.Grid[row][0] = Square{}
		}
	}
	if movingPiece.Piece == King {
		if movingPiece.Color == White {
			b.Castling["K"] = false
			b.Castling["Q"] = false
		} else {
			b.Castling["k"] = false
			b.Castling["q"] = false
		}
	}
	if movingPiece.Piece == Rook {
		if from.Row == 7 && from.Col == 0 {
			b.Castling["Q"] = false
		}
		if from.Row == 7 && from.Col == 7 {
			b.Castling["K"] = false
		}
		if from.Row == 0 && from.Col == 0 {
			b.Castling["q"] = false
		}
		if from.Row == 0 && from.Col == 7 {
			b.Castling["k"] = false
		}
	}
	if to.Row == 7 && to.Col == 0 {
		b.Castling["Q"] = false
	}
	if to.Row == 7 && to.Col == 7 {
		b.Castling["K"] = false
	}
	if to.Row == 0 && to.Col == 0 {
		b.Castling["q"] = false
	}
	if to.Row == 0 && to.Col == 7 {
		b.Castling["k"] = false
	}
	b.EnPassantTarget = nil
	if movingPiece.Piece == Pawn {
		if MathAbs(to.Row-from.Row) == 2 {
			b.EnPassantTarget = &Position{(from.Row + to.Row) / 2, from.Col}
		}
	}
}

func MathAbs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

func (b *Board) Display(checkColor int) string {
	pieceChars := map[int]string{
		Pawn: "P", Knight: "N", Bishop: "B", Rook: "R", Queen: "Q", King: "K",
	}
	var out string
	out += "  a  b  c  d  e  f  g  h\n"
	for r := 0; r < 8; r++ {
		out += fmt.Sprintf("%d", 8-r)
		for c := 0; c < 8; c++ {
			s := b.Grid[r][c]
			if s.Piece == Empty {
				out += "  ."
			} else {
				ch := pieceChars[s.Piece]
				if s.Color == White {
					out += "  " + ch
				} else {
					out += "  " + string(rune(97+int(ch[0])-65))
				}
			}
		}
		out += fmt.Sprintf("  %d\n", 8-r)
	}
	out += "  a  b  c  d  e  f  g  h\n"
	if b.isInCheck(checkColor) {
		out += fmt.Sprintf("\n  !! %s IS IN CHECK !!\n", colorName(checkColor))
	}
	return out
}

func colorName(c int) string {
	if c == White {
		return "WHITE"
	}
	return "BLACK"
}

func (b *Board) ParseMove(input string) (*Move, error) {
	if len(input) < 4 || len(input) > 5 {
		return nil, fmt.Errorf("invalid move format: %s (use e.g. e2e4)", input)
	}
	fromCol := int(input[0] - 'a')
	fromRow := 8 - int(input[1] - '0')
	toCol := int(input[2] - 'a')
	toRow := 8 - int(input[3] - '0')
	if fromCol < 0 || fromCol > 7 || toCol < 0 || toCol > 7 {
		return nil, fmt.Errorf("invalid columns")
	}
	if fromRow < 0 || fromRow > 7 || toRow < 0 || toRow > 7 {
		return nil, fmt.Errorf("invalid rows")
	}
	promotion := 0
	if len(input) == 5 {
		switch input[4] {
		case 'q', 'Q':
			promotion = Queen
		case 'r', 'R':
			promotion = Rook
		case 'b', 'B':
			promotion = Bishop
		case 'n', 'N':
			promotion = Knight
		default:
			return nil, fmt.Errorf("invalid promotion piece: %c", input[4])
		}
	}
	m := Move{
		From:      Position{fromRow, fromCol},
		To:        Position{toRow, toCol},
		Promotion: promotion,
	}
	return &m, nil
}

func MoveToAlgebraic(m Move) string {
	from := string(rune('a'+m.From.Col)) + string(rune('0'+(8-m.From.Row)))
	to := string(rune('a'+m.To.Col)) + string(rune('0'+(8-m.To.Row)))
	s := from + to
	if m.Promotion != 0 {
		pieceChars := map[int]byte{Queen: 'q', Rook: 'r', Bishop: 'b', Knight: 'n'}
		s += string(pieceChars[m.Promotion])
	}
	return s
}

