#ifndef INCLUDE_GUARD_MOVE_H
#define INCLUDE_GUARD_MOVE_H

#include <iterator>
#include <algorithm>
#include <cassert>

#include "utils.h"
#include "types.h"

class Move {
public:
    using base_type = uint64_t;

private:
    base_type _bits;

public:
    Move() = default; // required by MoveList
    // Reverse of cast to base_type
    Move(base_type bits) : _bits{bits} { };

    explicit Move(Index from, Index to)
        : Move(from, to, static_cast<enum piece>(0)) { };
    explicit Move(Index from, Index to, enum piece promotion)
        : Move(static_cast<enum piece>(0), static_cast<enum piece>(0), from, to,
               static_cast<enum piece>(0), promotion) { };
    explicit Move(enum piece color, enum piece piece, Index from, Index to,
        enum piece victim)
        : Move(color, piece, from, to, victim, static_cast<enum piece>(0)) { };
    // General Move constructor
    explicit Move(enum piece color, enum piece piece, Index from, Index to,
        enum piece victim, enum piece promotion)
        : _bits{static_cast<base_type>(
              (    color << 21)
            | (   victim << 18)
            | (    piece << 15)
            | (promotion << 12)
            | (       to <<  6)
            |       from
        )}
    {
        assert(((color == white)
             || (color == black)));
        assert(((victim == knight)
             || (victim == bishop)
             || (victim == rook)
             || (victim == queen)
             || (victim == pawn)
             || !victim));
        assert(((piece == knight)
             || (piece == bishop)
             || (piece == rook)
             || (piece == queen)
             || (piece == pawn)
             || (piece == king)
             || !piece));
        assert(((promotion == knight)
             || (promotion == bishop)
             || (promotion == rook)
             || (promotion == queen)
             || !promotion));
        assert((  to < 64));
        assert((from < 64));
    };

    Index from()         const { return _bits & 63U; }
    Index to()           const { return (_bits >> 6) & 63U; }
    enum piece promote() const {
        return static_cast<enum piece>((_bits >> 12) & 7U);
    }
    enum piece piece() const {
        return static_cast<enum piece>((_bits >> 15) & 7U);
    }
    enum piece victim() const {
        return static_cast<enum piece>((_bits >> 18) & 7U);
    }
    enum piece color() const {
        return static_cast<enum piece>((_bits >> 21) & 7U);
    }

    uint32_t score() const {
        return static_cast<uint32_t>(_bits >> 32);
    }

    static constexpr uint32_t killerScore[2] = { 4096U, 4095U };
    void setScore(const uint32_t val) {
        constexpr base_type mask = (1ULL << 22) - 1ULL;
        _bits = (_bits & mask) | (static_cast<base_type>(val) << 32);
    }
    uint32_t calcScore() const {
        const uint32_t mvv_lva = ((victim() << 3) + (7U - piece()));
        const bool winning = victim() > piece();
        // add 128 to ensure the PST values are positive
        const Index t = color() == white ? to()   : 63 - to();
        const Index f = color() == white ? from() : 63 - from();
        const uint32_t pst = PSQT[piece()][t] - PSQT[piece()][f] + 128;

        return ((static_cast<bool>(victim()) * mvv_lva) << (14 + winning * 6)) + pst;
    }
    // Allows to be cast to the base type (as number)
    operator base_type() { return _bits; }

    bool operator!() const { return this->from() == this->to(); }
    // Comparison of <from><to>[<promotion>] part of the move. Everything else
    // is redundent and allows simple parse move routine (use Board::isLegal
    // for move legality test and augmentation)
    bool operator==(const Move& rhs) const {
        constexpr base_type mask = (1ULL << 22) - 1ULL;
        return (_bits & mask) == (rhs._bits & mask);
    }
    bool operator!=(const Move& rhs) const {
        constexpr base_type mask = (1ULL << 22) - 1ULL;
        return (_bits & mask) != (rhs._bits & mask);
    }

    // default sort order is decreasing (for scored moves the best move first)
    friend bool operator<(const Move& lhs, const Move& rhs) {
        return lhs._bits > rhs._bits;
    }
};

class MoveList {
private:
    static constexpr unsigned _max_size = 256;
    unsigned _size;
    Move _moves[_max_size];   // apparently, 218 are the max nr. of moves

public:
    MoveList() : _size{0} { };

    // Copy Constructor
    MoveList(const MoveList& moveList) : _size{moveList._size} {
        assert(_size <= _max_size);

        std::copy(moveList._moves, moveList._moves + _size, _moves);
    }
    // Copy Assignement Operator
    MoveList& operator=(const MoveList&) = default;

    unsigned size() const { return _size; };
    bool empty() const { return !static_cast<bool>(_size); };
    static constexpr unsigned max_size() { return _max_size; };
    void clear() { _size = 0; };
          Move* data()       { return _moves; };
    const Move* data() const { return _moves; };

    template <typename... Args>
    void emplace_back(Args&&... args) {
        assert(_size < _max_size);

        _moves[_size++] = Move(std::forward<Args>(args)...);
    }

    void push_back(const Move& move) {
        assert(_size < _max_size);

        _moves[_size++] = move;
    }

    void append(const MoveList& list) {
        assert((_size + list._size) < _max_size);

        for (unsigned i = 0; i < list._size; i++) {
            _moves[_size++] = list._moves[i];
        }
    }

    bool contains(const Move& move) const {
        for (unsigned i = 0; i < _size; i++) {
            if (_moves[i] == move) {
                return true;
            }
        }
        return false;
    }

    /**
     * Searches for an occurence of `move` in the list and places the `move` as
     * the first element in the list. If the provided `move` is not an element
     * no opperation is performed and `false` will be returned.
     *
     * @param move a move to be searched for and placed as first element.
     * @return `true` if `move` is found and placed as first element,
     *  `false` otherwise.
     */
    bool move_front(const Move& move) {
        for (unsigned i = 0; i < _size; i++) {
            if (_moves[i] == move) {
                std::swap(_moves[0], _moves[i]);
                return true;
            }
        }
        return false;
    }

          Move& operator[](unsigned i)       { return _moves[i]; }
    const Move& operator[](unsigned i) const { return _moves[i]; }

    struct MoveIter {
        using iterator_category = std::forward_iterator_tag;
        using difference_type   = std::ptrdiff_t;
        using value_type        = Move;
        using pointer           = value_type*;
        using reference         = value_type&;

        MoveIter(pointer ptr) : _ptr{ptr} { };

        // Dereference operators
        reference operator*() { return *_ptr; };
        pointer   operator->() { return _ptr; };

        // Arithmetic operators
        MoveIter& operator++() { _ptr++; return *this; };
        MoveIter  operator++(int) {
            MoveIter copy = *this; ++(*this); return copy;
        };
        MoveIter& operator--() { _ptr--; return *this; };
        MoveIter  operator--(int) {
            MoveIter copy = *this; --(*this); return copy;
        };
        MoveIter& operator+=(int i) {
            _ptr += i; return *this;
        };
        friend MoveIter operator+(MoveIter it, int i) { return (it += i); };
        MoveIter& operator-=(int i) {
            _ptr -= i; return *this;
        };
        friend MoveIter operator-(MoveIter it, int i) { return (it -= i); };

        friend difference_type operator-(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr - rhs._ptr;
        };

        // Comparison operators
        friend bool operator==(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr == rhs._ptr;
        };
        friend bool operator<=(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr <= rhs._ptr;
        };
        friend bool operator>=(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr >= rhs._ptr;
        };
        friend bool operator!=(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr != rhs._ptr;
        };
        friend bool operator<(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr < rhs._ptr;
        };
        friend bool operator>(const MoveIter& lhs, const MoveIter& rhs) {
            return lhs._ptr > rhs._ptr;
        };


    private:
        pointer _ptr;
    };

    MoveIter begin() { return MoveIter(_moves); };
    MoveIter end()   { return MoveIter(_moves + _size); };

    struct ConstMoveIter {
        using iterator_category = std::forward_iterator_tag;
        using difference_type   = std::ptrdiff_t;
        using value_type        = const Move;
        using pointer           = const value_type*;
        using reference         = const value_type&;

        ConstMoveIter(pointer ptr) : _ptr{ptr} { };

        // Dereference operators
        reference operator*() { return *_ptr; };
        pointer   operator->() { return _ptr; };

        // Arithmetic operators
        ConstMoveIter& operator++() { _ptr++; return *this; };
        ConstMoveIter  operator++(int) {
            ConstMoveIter copy = *this; ++(*this); return copy;
        };
        ConstMoveIter& operator--() { _ptr--; return *this; };
        ConstMoveIter  operator--(int) {
            ConstMoveIter copy = *this; --(*this); return copy;
        };
        ConstMoveIter& operator+=(int i) {
            _ptr += i; return *this;
        };

        // Comparison operators
        friend bool operator==(const ConstMoveIter& lhs, const ConstMoveIter& rhs) {
            return lhs._ptr == rhs._ptr;
        };
        friend bool operator!=(const ConstMoveIter& lhs, const ConstMoveIter& rhs) {
            return lhs._ptr != rhs._ptr;
        };

    private:
        pointer _ptr;
    };

    ConstMoveIter begin() const { return ConstMoveIter(_moves); };
    ConstMoveIter end()   const { return ConstMoveIter(_moves + _size); };
};

#endif /* INCLUDE_GUARD_MOVE_H */
