﻿using BeeEngine.Common;
using BeeEngine.Exports;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;

namespace BeeEngine.Position
{
    internal struct Move
    {
        public static readonly Move NULL_MOVE = new Move();

        // Bits 0..5 - from cell
        private const uint CELL_MASK = 0x3F;
        private const int SHIFT_FROM_CELL = 0;
        // Bits 6..11 - to cell
        private const int SHIFT_TO_CELL = 6;
        // Bits 12..15 - piece to move
        private const uint PIECE_MASK = 0x0F;
        private const int SHIFT_PIECE_TO_MOVE = 12;
        // Bits 16..19 - piece to promote
        private const int SHIFT_PROMOTE_TO_PIECE = 16;
        private const int BIT_CAPTURE = 20;
        private const int BIT_EN_PASSANT_CAPTURE = 21;
        private const int BIT_SHORT_CASTLE = 22;
        private const int BIT_LONG_CASTLE = 23;
        private const int BIT_PROMOTION = 24;
        private const int BIT_CHECK = 25;
        // Bits 26..31 - cell index in ArrayOfCells (for optimized access)
        private const uint CELL_INDEX_MASK = 0x3F;
        private const int SHIFT_CELL_INDEX = 26;

        // TODO - convert this to uint
        [Flags]
        public enum Flags
        {
            Capture = 1 << BIT_CAPTURE,
            EnPassantCapture = 1 << BIT_EN_PASSANT_CAPTURE,
            ShortCastle = 1 << BIT_SHORT_CASTLE,
            LongCastle = 1 << BIT_LONG_CASTLE,
            Promotion = 1 << BIT_PROMOTION,
            Check = 1 << BIT_CHECK,
        }

        private Bitmask32 m_data;

        public bool IsNullMove
        {
            get { return m_data.Value == NULL_MOVE.m_data.Value; }
        }

        public int FromCell
        {
            get { return (int)m_data.GetBits(SHIFT_FROM_CELL, CELL_MASK); }
        }

        public int ToCell
        {
            get { return (int)m_data.GetBits(SHIFT_TO_CELL, CELL_MASK); }
        }

        public uint CellIndex
        {
            get { return m_data.GetBits(SHIFT_CELL_INDEX, CELL_INDEX_MASK); }
        }

        public Exports.Pieces Piece
        {
            get
            {
                return (Exports.Pieces)m_data.GetBits(SHIFT_PIECE_TO_MOVE, PIECE_MASK);
            }
        }

        public Exports.Pieces PromoteToPiece
        {
            get
            {
                return (Exports.Pieces)m_data.GetBits(SHIFT_PROMOTE_TO_PIECE,
                    PIECE_MASK);
            }
        }

        public bool IsCapture
        {
            get { return m_data.IsSet(BIT_CAPTURE); }
        }

        public bool IsPromotion
        {
            get { return m_data.IsSet(BIT_PROMOTION); }
        }

        public bool IsEnPassantCapture
        {
            get { return m_data.IsSet(BIT_EN_PASSANT_CAPTURE); }
        }

        public bool IsShortCastle
        {
            get { return m_data.IsSet(BIT_SHORT_CASTLE); }
        }

        public bool IsLongCastle
        {
            get { return m_data.IsSet(BIT_LONG_CASTLE); }
        }

        public bool IsCheck
        {
            get { return m_data.IsSet(BIT_CHECK); }
            set { m_data.SetBit(BIT_CHECK, value); }
        }

        public Move(Exports.Pieces piece, int from_cell, int to_cell, uint cell_index,
            Flags flags)
        {
            // If castle flag is set, then this must be a king's move
            Debug.Assert(
                !((flags.HasFlag(Flags.ShortCastle) || flags.HasFlag(Flags.LongCastle))
                && piece != Exports.Pieces.King));
            // Must use another consructor for pawn promotions
            Debug.Assert(!flags.HasFlag(Flags.Promotion));
            // EnPassantCapture implies Capture
            Debug.Assert(!flags.HasFlag(Flags.EnPassantCapture)
                || flags.HasFlag(Flags.Capture));

            m_data = new Bitmask32();
            Init((int)piece, from_cell, to_cell, (int)flags, 0);
        }

        /// <summary>
        /// Constructor for pawn promotions moves
        /// </summary>
        public Move(int from_cell, int to_cell, Flags flags, Exports.Pieces promote_to)
        {
            Debug.Assert(flags.HasFlag(Flags.Promotion));
            Debug.Assert(!(flags.HasFlag(Flags.EnPassantCapture)
                || flags.HasFlag(Flags.ShortCastle) || flags.HasFlag(Flags.LongCastle)));
            Debug.Assert(promote_to == Exports.Pieces.Knight
                || promote_to == Exports.Pieces.Bishop
                || promote_to == Exports.Pieces.Rook
                || promote_to == Exports.Pieces.Queen);

            m_data = new Bitmask32();
            Init((int)Exports.Pieces.Pawn, from_cell, to_cell, (int)flags,
                (int)promote_to);
        }

        public static Move MakeFromCoordinates(string coords)
        {
            if (coords.Length == 4)
            {
                // Parse move in form 'e2e4'
                return new Move(Pieces.NoPiece,
                    new ChessboardCell(coords[1], coords[0]).Value,
                    new ChessboardCell(coords[3], coords[2]).Value,
                    0, 0);
            }
            else if (coords.Length == 5)
            {
                // Parse move in form 'e7e8q'
                var promote_to = NotationHelper.CharToPiece(coords[4]);
                if (promote_to == Exports.Pieces.NoPiece
                    || promote_to == Exports.Pieces.Pawn 
                    || promote_to == Exports.Pieces.King)
                {
                    throw new ArgumentException(
                        "Cannot parse piece to promote for move " + coords);
                }

                return new Move(
                    new ChessboardCell(coords[1], coords[0]).Value,
                    new ChessboardCell(coords[3], coords[2]).Value,
                    Flags.Promotion, promote_to);
            }
            else
            {
                throw new ArgumentException(
                    "Cannot parse move from " + coords);
            }
        }

        public MoveDesc ToMoveDesc(Position child_position)
        {
            // TODO - consider null moves
            return new MoveDesc(Piece, new ChessboardCell(FromCell),
                new ChessboardCell(ToCell), PromoteToPiece, IsCapture, IsCheck,
                child_position.IsCheckmate);
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private void Init(int piece, int from_cell, int to_cell, int flags,
            int promote_to_piece)
        {
            Debug.Assert(from_cell >= 0 && from_cell < 64);
            Debug.Assert(to_cell >= 0 && to_cell < 64);
            Debug.Assert(from_cell != to_cell);

            m_data.SetBits(0, uint.MaxValue,
                (uint)((from_cell << SHIFT_FROM_CELL) + (to_cell << SHIFT_TO_CELL)
                + (piece << SHIFT_PIECE_TO_MOVE)
                + (promote_to_piece << SHIFT_PROMOTE_TO_PIECE)
                + flags));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool IsSameCells(Move other)
        {
            return FromCell == other.FromCell && ToCell == other.ToCell;
        }

        public string ToCoordinatesNotation()
        {
            if (IsNullMove)
            {
                return "null";
            }
            else
            {
                return new ChessboardCell(FromCell).ToString()
                    + new ChessboardCell(ToCell).ToString()
                    + (IsPromotion ? NotationHelper.GetPieceString(PromoteToPiece)
                    : string.Empty);
            }
        }

        public override string ToString()
        {
            return Piece.ToString() + " " + (new ChessboardCell(FromCell))
                + (IsCapture ? ":" : "-")
                + (new ChessboardCell(ToCell));
        }
    }
}
