from copy import deepcopy
from re import finditer

from operation import *
from pieces import *
from position import Pos
from pprint import pprint

white, black = 0, 1

class Board:
	def __init__(self, board: list, turn: int=0, castling: list=[True for i in range(4)]):
		"""A Board object containing chess pieces and other board information.

		Args:
			board (list): A 2-D list storing classes 'Piece' or None, simulating a chess board
			turn (int, optional): Stores the turn count of the game (white is even, black is odd). Defaults to 0.
			castling (list, optional): Stores if the king is able to castle in the order:\n
				0. white, kingside
				1. white, queenside
				2. black, kingside
				3. black, queenside\n
			Defaults to [True for i in range(4)].
		"""
		self.board = board
		self.turn = turn
		self.white_castle_kingside = castling[0]
		self.white_castle_queenside = castling[1]
		self.black_castle_kingside = castling[2]
		self.black_castle_queenside = castling[3]
		self.en_passant_square = None

	def __str__(self):
		res = ""
		for i in range(7, -1, -1):
			temp = ""
			for j in range(8):
				item = self.board[i][j]
				if isinstance(item, Piece):
					temp += item.get_symbol() + " "
				else:
					temp += "- "
			res += temp + "\n"
		return res

	def get_piece(self, pos: Pos) -> Piece | None:
		return self.board[pos.x][pos.y]

	def place_piece(self, pos: Pos, piece: Piece | None) -> None:
		self.board[pos.x][pos.y] = piece

	@classmethod
	# https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
	def FEN_to_board(self, fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq"):
		"""A classmethod converting a FEN string to a Board object.
		https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation

		Args:
			fen (str, optional): The FEN string to be parsed. Defaults to "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq".

		Returns:
			Board: A Board converted from the FEN string
		"""
		temp_board = []

		# check for illegal characters
		if not len(list(finditer(r'[^wbrnbqkpRNBQKP12345678 \/]+', fen))) == 0: 
			raise Exception("Error while parsing FEN: illegal character found")
		else:
			args = fen.split(" ")

		if len(args) < 3:
			raise Exception("Error while parsing FEN: not enough parameters")
		# arg 0
		rows = args[0].split("/")
		for row in rows:
			temp_row = []
			for char in row:
				if char in P.keys():
					temp_row.append(deepcopy(P[char]))
				elif char in [str(i) for i in range(1,9)]: # if char is 1, 2 ... 8
					temp_row += [None] * int(char)
				else:
					raise Exception("Error while parsing char in FEN (arg 0): '{0}', {1}".format(char, fen))

			if len(temp_row) == 8:
				temp_board.insert(0, temp_row)
			else:
				raise Exception("Error while parsing row in FEN (arg 0): row length is not 8")
				
		# arg 1
		if args[1] == "w":
			turn = 0
		elif args[1] == "b":
			turn = 1
		else:
			raise Exception("Error while parsing char in FEN (arg 1): '{0}', {1}".format(args[1], fen))

		# arg 2
		castling = ["K" in args[2], "k" in args[2], "Q" in args[2], "q" in args[2]]

		return Board(board=temp_board, turn=turn, castling=castling)

	def execute(self, operation: Operation):
		"""Executes an `Operation` object on the board.

		Args:
			operation (Operation): The operation to be executed.
		"""
		piece = self.get_piece(operation.old_pos)

		piece.move_count += 1
		if isinstance(operation, (op_move, op_capture, op_castle, op_pawn_move, op_en_passant)):
			self.place_piece(operation.new_pos, piece)
			self.place_piece(operation.old_pos, None)

		elif isinstance(operation, op_promotion):
			self.place_piece(operation.new_pos, Queen(piece.color, move_count=piece.move_count))
			self.place_piece(operation.old_pos, None)

		if isinstance(operation, op_en_passant):
			self.place_piece(operation.ep_pos, None)

		if isinstance(operation, op_pawn_move):
			if operation.two_squares:
				self.en_passant_square = {
					"color": deepcopy(piece.color),
					"pos": deepcopy(operation.new_pos)
				} # check if removing deepcopy works later!
			else:
				self.en_passant_square = None
		else:
			self.en_passant_square = None

		if isinstance(operation, op_castle):
			if operation.color == white:
				if operation.side == 0:
					self.place_piece(Pos(0,0), None)
					self.place_piece(Pos(0,3), Rook(white, move_count=1))
				else: # operation.side == 1
					self.place_piece(Pos(0,7), None)
					self.place_piece(Pos(0,5), Rook(white, move_count=1))
			else: # operation.color == black
				if operation.side == 0:
					self.place_piece(Pos(7,0), None)
					self.place_piece(Pos(7,3), Rook(black, move_count=1))
				else: # operation.side == 1
					self.place_piece(Pos(7,7), None)
					self.place_piece(Pos(7,5), Rook(black, move_count=1))

		self.turn += 1

from copy import deepcopy
from re import finditer

from operation import *
from pieces import *
from position import Pos
from pprint import pprint

white, black = 0, 1

class Board:
	def __init__(self, board: list, turn: int=0, castling: list=[True for i in range(4)]):
		"""A Board object containing chess pieces and other board information.

		Args:
			board (list): A 2-D list storing classes 'Piece' or None, simulating a chess board
			turn (int, optional): Stores the turn count of the game (white is even, black is odd). Defaults to 0.
			castling (list, optional): Stores if the king is able to castle in the order:\n
				0. white, kingside
				1. white, queenside
				2. black, kingside
				3. black, queenside\n
			Defaults to [True for i in range(4)].
		"""
		self.board = board
		self.turn = turn
		self.white_castle_kingside = castling[0]
		self.white_castle_queenside = castling[1]
		self.black_castle_kingside = castling[2]
		self.black_castle_queenside = castling[3]
		self.en_passant_square = None

	def __str__(self):
		res = ""
		for i in range(7, -1, -1):
			temp = ""
			for j in range(8):
				item = self.board[i][j]
				if isinstance(item, Piece):
					temp += item.get_symbol() + " "
				else:
					temp += "- "
			res += temp + "\n"
		return res

	def get_piece(self, pos: Pos) -> Piece | None:
		return self.board[pos.x][pos.y]

	def place_piece(self, pos: Pos, piece: Piece | None) -> None:
		self.board[pos.x][pos.y] = piece

	@classmethod
	# https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
	def FEN_to_board(self, fen="rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq"):
		"""A classmethod converting a FEN string to a Board object.
		https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation

		Args:
			fen (str, optional): The FEN string to be parsed. Defaults to "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq".

		Returns:
			Board: A Board converted from the FEN string
		"""
		temp_board = []

		# check for illegal characters
		if not len(list(finditer(r'[^wbrnbqkpRNBQKP12345678 \/]+', fen))) == 0: 
			raise Exception("Error while parsing FEN: illegal character found")
		else:
			args = fen.split(" ")

		if len(args) < 3:
			raise Exception("Error while parsing FEN: not enough parameters")
		# arg 0
		rows = args[0].split("/")
		for row in rows:
			temp_row = []
			for char in row:
				if char in P.keys():
					temp_row.append(deepcopy(P[char]))
				elif char in [str(i) for i in range(1,9)]: # if char is 1, 2 ... 8
					temp_row += [None] * int(char)
				else:
					raise Exception("Error while parsing char in FEN (arg 0): '{0}', {1}".format(char, fen))

			if len(temp_row) == 8:
				temp_board.insert(0, temp_row)
			else:
				raise Exception("Error while parsing row in FEN (arg 0): row length is not 8")
				
		# arg 1
		if args[1] == "w":
			turn = 0
		elif args[1] == "b":
			turn = 1
		else:
			raise Exception("Error while parsing char in FEN (arg 1): '{0}', {1}".format(args[1], fen))

		# arg 2
		castling = ["K" in args[2], "k" in args[2], "Q" in args[2], "q" in args[2]]

		return Board(board=temp_board, turn=turn, castling=castling)

	def execute(self, operation: Operation):
		"""Executes an `Operation` object on the board.

		Args:
			operation (Operation): The operation to be executed.
		"""
		piece = self.get_piece(operation.old_pos)

		piece.move_count += 1
		if isinstance(operation, (op_move, op_capture, op_castle, op_pawn_move, op_en_passant)):
			self.place_piece(operation.new_pos, piece)
			self.place_piece(operation.old_pos, None)

		elif isinstance(operation, op_promotion):
			self.place_piece(operation.new_pos, Queen(piece.color, move_count=piece.move_count))
			self.place_piece(operation.old_pos, None)

		if isinstance(operation, op_en_passant):
			self.place_piece(operation.ep_pos, None)

		if isinstance(operation, op_pawn_move):
			if operation.two_squares:
				self.en_passant_square = {
					"color": deepcopy(piece.color),
					"pos": deepcopy(operation.new_pos)
				} # check if removing deepcopy works later!
			else:
				self.en_passant_square = None
		else:
			self.en_passant_square = None

		if isinstance(operation, op_castle):
			if operation.color == white:
				if operation.side == 0:
					self.place_piece(Pos(0,0), None)
					self.place_piece(Pos(0,3), Rook(white, move_count=1))
				else: # operation.side == 1
					self.place_piece(Pos(0,7), None)
					self.place_piece(Pos(0,5), Rook(white, move_count=1))
			else: # operation.color == black
				if operation.side == 0:
					self.place_piece(Pos(7,0), None)
					self.place_piece(Pos(7,3), Rook(black, move_count=1))
				else: # operation.side == 1
					self.place_piece(Pos(7,7), None)
					self.place_piece(Pos(7,5), Rook(black, move_count=1))

		self.turn += 1

A Board object containing chess pieces and other board information.  		Args: 			board (list): A 2-D list storing classes 'Piece' or None, simulating a chess board 			turn (int, optional): Stores the turn count of the game (white is even, black is odd). Defaults to 0. 			castling (list, optional): Stores if the king is able to castle in the order:\n 				0. white, kingside 				1. white, queenside 				2. black, kingside 				3. black, queenside\n 			Defaults to [True for i in range(4)]. # https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation A classmethod converting a FEN string to a Board object. 		https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation  		Args: 			fen (str, optional): The FEN string to be parsed. Defaults to "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq".  		Returns: 			Board: A Board converted from the FEN string # check for illegal characters # arg 0 # if char is 1, 2 ... 8 # arg 1 # arg 2 Executes an `Operation` object on the board.  		Args: 			operation (Operation): The operation to be executed. # check if removing deepcopy works later! # operation.side == 1 # operation.color == black # operation.side == 1