/*
    This file is part of the KDE games lskat program
    SPDX-FileCopyrightText: 2006 Martin Heni <kde@heni-online.de>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "aiinput.h"

// Qt includes
#include <QTimer>

// KF includes

// Local includes
#include "lskatglobal.h"
#include "player.h"

// Constructor for the engine
AiInput::AiInput(EngineTwo *engine, QObject *parent)
    : AbstractInput(parent)
{
    // Store engine
    mEngine = engine;
}

// Allow or disallow input with this device
void AiInput::setInputAllowed(bool allowed)
{
    AbstractInput::setInputAllowed(allowed);
    if (allowed) QTimer::singleShot(1000, this, &AiInput::aiTurn);
}

// Calculate and send out AI turn
void AiInput::aiTurn()
{
    // Turn was stopped meanwhile
    if (!mInputAllowed) return;

    if (global_debug > 5) qCDebug(LSKAT_LOG) << "===================================================";
    if (global_debug > 5) qCDebug(LSKAT_LOG) << "AI TURN START " << mInputAllowed;

    // Check we are the right player
    if (mId != mEngine->currentPlayer())
    {
        qCCritical(LSKAT_LOG) << "AI plays for wrong player";
        return;
    }

    // Retrieve game board
    AiInput::Board board = getBoardFromEngine();
    AiInput::Move move;

    if (global_debug > 0) qCDebug(LSKAT_LOG) << QLatin1String("");

    // Initiate move
    if (mEngine->currentMovePhase() == EngineTwo::FirstPlayerTurn)
    {
        if (global_debug > 5) qCDebug(LSKAT_LOG) << "Performing initial move " << mId;
        move = initiateMove(mId, board);
    }
    // Respond to move
    else
    {
        if (global_debug > 5) qCDebug(LSKAT_LOG) << "Performing answer move " << mId;
        move = answerMove(mId, board);
    }

    // Send out move
    if (global_debug > 5) qCDebug(LSKAT_LOG) << "AI player " << mId << " moves to " << move.move;
    if (move.move >= 0) Q_EMIT signalPlayerInput(mId, mId, move.move);
    else qCCritical(LSKAT_LOG) << "Illegal AI Move???";
}

// Extract the current game board from the engine
AiInput::Board AiInput::getBoardFromEngine()
{
    Board b;
    int cnt = 0;
    // Reset array
    for (int i = 0; i < 32; i++)
    {
        b.playedCards[i] = -1;
    }

    for (int i = 0; i < 2; i++)
    {
        Player *p = mEngine->player(i);
        for (int c = 0; c < 16; c++)
        {
            int card      = p->getCard(c);
            b.cards[i][c] = card;
            b.points[i]   = p->points();
        }
        for (int c = 0; c < p->noOfMovesWon() * 2; c++)
        {
            int card = p->getWonCard(c);
            b.playedCards[cnt] = card;
            cnt++;
        }
    }

    b.whoseTurn  = mEngine->currentPlayer();
    b.firstPlay  = mEngine->currentMovePhase() == EngineTwo::FirstPlayerTurn;
    b.playedCard = mEngine->playedCard(0);
    b.trump      = mEngine->trump();
    b.analyze();
    return b;
}

// Game evaluation ratings
// Point ratings
const double RATING_SCHWARZ             = 100000.0;
const double RATING_SCHNEIDER           =  70000.0;
const double RATING_WON                 =  50000.0;
const double RATING_REMIS               =  20000.0;
const double RATING_ONE_POINT           =    500.0;
// Tactical ratings
const double RATING_AMOUNT_TRUMPCARD    = 3.0 * RATING_ONE_POINT;
const double RATING_AMOUNT_OPENCARD     = 1.5 * RATING_ONE_POINT;
const double RATING_AMOUNT_ACES         = 3.0 * RATING_ONE_POINT;
const double RATING_AMOUNT_TENS         = 1.0 * RATING_ONE_POINT;
const double RATING_AMOUNT_JACKS        = 8.5 * RATING_ONE_POINT;

const double RATING_JACK_OF_CLUBS       = 0.8 * RATING_ONE_POINT;
const double RATING_JACK_OF_SPADE       = 0.6 * RATING_ONE_POINT;
const double RATING_JACK_OF_HEART       = 0.4 * RATING_ONE_POINT;
const double RATING_JACK_OF_DIAMOND     = 0.2 * RATING_ONE_POINT;

// Evaluate the current game board and return a rating
double AiInput::evaluteGame(int p, const AiInput::Board &current)
{
    double rating = 0.0;

    // ===== Points evaluation =====
    // Check for won games of our side
    if (current.points[p] == 120) rating += RATING_SCHWARZ;
    else if (current.points[p] >= 90) rating += RATING_SCHNEIDER;
    else if (current.points[p] > 60) rating += RATING_WON;
    else if (current.points[p] == 60) rating += RATING_REMIS;

    // Check for won games of other side
    if (current.points[1 - p] == 120) rating -= RATING_SCHWARZ;
    else if (current.points[1 - p] >= 90) rating -= RATING_SCHNEIDER;
    else if (current.points[1 - p] > 60) rating -= RATING_WON;
    else if (current.points[1 - p] == 60) rating -= RATING_REMIS;

    // Evaluate points
    rating += (current.points[p] - current.points[1 - p]) * RATING_ONE_POINT;

    // ===== Tactical evaluation =====
    // Number of trumps (Stored under index Grand)
    int trump1 = current.amountOfSuite[p][int(Grand)];
    int trump2 = current.amountOfSuite[1 - p][int(Grand)];
    rating += (trump1 - trump2) * RATING_AMOUNT_TRUMPCARD;
    // Increase value of trumps for Grand
    if (current.trump == Grand) rating += (trump1 - trump2) * RATING_AMOUNT_TRUMPCARD;

    // Missing suites
    //if (current.amountOfSuite[p][int(Club)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
    //if (current.amountOfSuite[p][int(Spade)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
    //if (current.amountOfSuite[p][int(Heart)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;
    //if (current.amountOfSuite[p][int(Diamond)] == 0 && (trump1 > trump2)) rating += RATING_GOOD_MISSING_SUITE;

    //if (current.amountOfSuite[1 - p][int(Club)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
    //if (current.amountOfSuite[1 - p][int(Spade)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
    //if (current.amountOfSuite[1 - p][int(Heart)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;
    //if (current.amountOfSuite[1 - p][int(Diamond)] == 0 && (trump1 < trump2)) rating -= RATING_GOOD_MISSING_SUITE;

    // Number of open cards
    int amount1 = amountOfOpenCards(p, current);
    int amount2 = amountOfOpenCards(1 - p, current);
    rating += (amount1 - amount2) * RATING_AMOUNT_OPENCARD;

    // Card types
    // Aces
    int amountAce1 = current.amountOfCardType[p][int(Ace)];
    int amountAce2 = current.amountOfCardType[1 - p][int(Ace)];
    rating += (amountAce1 - amountAce2) * RATING_AMOUNT_ACES;

    // Tens
    int amountTen1 = current.amountOfCardType[p][int(Ten)];
    int amountTen2 = current.amountOfCardType[1 - p][int(Ten)];
    rating += (amountTen1 - amountTen2) * RATING_AMOUNT_TENS;

    // Jacks
    int amountJack1 = current.amountOfCardType[p][int(Jack)];
    int amountJack2 = current.amountOfCardType[1 - p][int(Jack)];
    rating += (amountJack1 - amountJack2) * RATING_AMOUNT_JACKS;
    //qCDebug(LSKAT_LOG) << "    Add rating(p=" << p << ") for jacks j1=" << amountJack1 <<" j2=" << amountJack2 << " has JC=" << findCard(current, Club, Jack);

    if (findCard(current, Club, Jack) == p)    rating += RATING_JACK_OF_CLUBS;
    if (findCard(current, Spade, Jack) == p)   rating += RATING_JACK_OF_SPADE;
    if (findCard(current, Heart, Jack) == p)   rating += RATING_JACK_OF_HEART;
    if (findCard(current, Diamond, Jack) == p) rating += RATING_JACK_OF_DIAMOND;

    if (findCard(current, Club, Jack) == 1 - p)    rating -= RATING_JACK_OF_CLUBS;
    if (findCard(current, Spade, Jack) == 1 - p)   rating -= RATING_JACK_OF_SPADE;
    if (findCard(current, Heart, Jack) == 1 - p)   rating -= RATING_JACK_OF_HEART;
    if (findCard(current, Diamond, Jack) == 1 - p) rating -= RATING_JACK_OF_DIAMOND;
    //qCDebug(LSKAT_LOG) << "    Rating after jacks " << rating;

    return rating;
}

// Initiate a new move as first player
AiInput::Move AiInput::initiateMove(int p, const AiInput::Board &board)
{
    AiInput::Move maxMove;
    maxMove.move  = -1;
    maxMove.value = -100.0 * RATING_SCHWARZ; // Absolute minimum score

    // Loop all moves
    for (int m = 0; m < 8; m++)
    {
        AiInput::Board current(board);
        int card = current.takeCard(p, m);
        if (card < 0) continue; // Illegal move
        // Store move
        current.playedCard = card;
        current.whoseTurn = 1 - p;
        if (global_debug > 5) qCDebug(LSKAT_LOG) << "***** First mover try move on " << m << " (" << Deck::name(card) << ")";
        AiInput::Move answer = answerMove(1 - p, current);
        // Negate answering moves value to get our rating
        double rating = -answer.value;
        if (global_debug > 5) qCDebug(LSKAT_LOG) << "First mover yields rating of " << rating;

        rating += rulebaseFirstMover(p, card, board);
        if (global_debug > 5) qCDebug(LSKAT_LOG) << "  rulesbase correction to " << rating;

        // New best move?
        if (rating > maxMove.value)
        {
            maxMove.value = rating;
            maxMove.move  = m;
        }
    }

    return maxMove;
}

// Answer a move as second player
AiInput::Move AiInput::answerMove(int p, const AiInput::Board &board)
{
    AiInput::Move maxMove;
    maxMove.move  = -1;
    maxMove.value = -100.0 * RATING_SCHWARZ; // Absolute minimum score

    // Loop all moves
    for (int m = 0; m < 8; m++)
    {
        AiInput::Board current(board);
    // qCDebug(LSKAT_LOG) << "CARD " << m << " is "
        //          << Deck::name(current.cards[p][m]) << " on top of "
        //          << Deck::name(current.cards[p][m + 8]);

        int card = current.takeCard(p, m);
        if (card < 0) continue; // Illegal card

        // Check validity of move
        if (!isLegalMove(current.playedCard, card, p, current)) continue;

        // Check move winner
        int winner = EngineTwo::whoWonMove(current.playedCard, card, current.trump);
        if (global_debug > 5)
        qCDebug(LSKAT_LOG) << "   Card" << m << " (" << Deck::name(card) << ") is valid "
                    << "countering" << Deck::name(current.playedCard) << " with "
                    << "winner (0:other, 1:we) " << winner;
        int deltaPoints = 0;
        deltaPoints += Deck::getCardValue(current.playedCard);
        deltaPoints += Deck::getCardValue(card);
        // The first mover won
        if (winner == 0)
        {
            current.points[1 - p] += deltaPoints;
        }
        // The second mover won (us)
        else
        {
            current.points[p] += deltaPoints;
        }

        double rating = evaluteGame(p, current);
        rating += rulebaseAnswerMover(p, card, board);

        if (global_debug > 5)
        qCDebug(LSKAT_LOG) << "   Points after 2nd move " << m << " would be we: "
                    << current.points[p] << " other: " << current.points[1 - p]
                    << " rating is thus " << rating;
        // New best move?
        if (rating > maxMove.value)
        {
            maxMove.value = rating;
            maxMove.move  = m;
        }
    }
    return maxMove;
}

// Board copy constructor
AiInput::Board::Board(const AiInput::Board &board)
{
    for (int i = 0; i < 2; i++)
    {
        points[i] = board.points[i];
        for (int j = 0; j < 16; j++)
        {
            cards[i][j] = board.cards[i][j];
        }
        for (int j = 0; j < 5; j++)
        {
            amountOfSuite[i][j] = board.amountOfSuite[i][j];
        }
        for (int j = 0; j < 8; j++)
        {
            amountOfCardType[i][j] = board.amountOfCardType[i][j];
        }
    }
    for (int i = 0; i < 32; i++)
    {
        playedCards[i] = board.playedCards[i];
    }
    playedCard = board.playedCard;
    whoseTurn  = board.whoseTurn;
    firstPlay  = board.firstPlay;
    trump      = board.trump;
}

// Retrieve card at given position for given player
int AiInput::Board::card(int p, int pos) const
{
    int card = cards[p][pos];  // 1st card
    if (card < 0)
    {
        card = cards[p][pos + 8];  // 2nd card
    }
    return card;
}

// Check amount of cards at position,
// i.e. card on top (2), bottom(1), or none at all (0)
int AiInput::Board::cardsAtPos(int p, int pos) const
{
    int card = cards[p][pos];  // 1st card
    if (card >= 0) return 2;
    card = cards[p][pos + 8];  // 2nd card
    if (card >= 0) return 1;
    return 0;
}

// Retrieve card at given position for given player and
// reset it (take it away)
int AiInput::Board::takeCard(int p, int pos)
{
    int card = cards[p][pos];  // 1st card
    cards[p][pos] = -1;        // Can do in any case
    if (card < 0)
    {
        card = cards[p][pos + 8];  // 2nd card
        cards[p][pos + 8] = -1;    // Can do in any case
    }
    else
    {
        cards[p][pos + 8] = -cards[p][pos + 8]; // Do not look underneath card
    }
    analyze();
    return card;
}

// Perform pre board analysis
// + Count how many of each color
void AiInput::Board::analyze()
{
    // How many cards of given suite
    for (int pl = 0; pl < 2; pl++)
    {
        for (int i = 0; i < 5; i++) amountOfSuite[pl][i] = 0;
        for (int i = 0; i < 8; i++) amountOfCardType[pl][i] = 0;
        for (int i = 0; i < 8; i++)
        {
            int c         = card(pl, i);
            if (c < 0) continue;
            Suite suite   = Deck::getSuite(c);
            CardType type = Deck::getCardType(c);
            if (suite == trump || type == Jack) amountOfSuite[pl][Grand]++;
            else amountOfSuite[pl][int(suite)]++;
            amountOfCardType[pl][int(type)]++;
        }
    }
}

// Internal return values for find card
const int CARD_CURRENTLY_PLAYED  = 2;
const int CARD_PLAYED            = 3;

// Check where a card is:
// -1: unknown (still covered), 0: player 0, 1: player 1: 2: currently played, 2: already played
int AiInput::findCard(const AiInput::Board &current, Suite sSuite, CardType sCardType) const
{
    // Loop player's cards
    for (int p = 0; p < 2; p++)
    {
        for (int i = 0; i < 8; i++)
        {
            int card      = current.card(p, i);
            if (card < 0) continue;
            Suite suite   = Deck::getSuite(card);
            CardType type = Deck::getCardType(card);
            if (suite == sSuite && type == sCardType)
            {
                //qCDebug(LSKAT_LOG) << "Found" << Deck::name(sSuite, sCardType) << " at " << p << "," << i;
                return p;
            }
        }
    }

    // Currently played card if any
    int card = current.playedCard;
    if (card >= 0)
    {
        Suite suite   = Deck::getSuite(card);
        CardType type = Deck::getCardType(card);
        if (suite == sSuite && type == sCardType)
        {
            //qCDebug(LSKAT_LOG) << "Found" << Deck::name(sSuite, sCardType) << " as currently played card";
            return CARD_CURRENTLY_PLAYED;
        }
    }

    // Already played cards
    for (int i = 0; i < 32; i++)
    {
        int card      = current.playedCards[i];
        if (card < 0) continue;
        Suite suite   = Deck::getSuite(card);
        CardType type = Deck::getCardType(card);
        if (suite == sSuite && type == sCardType)
        {
            //qCDebug(LSKAT_LOG) << "Found " << Deck::name(sSuite, sCardType) << " as played card " << i;
            return CARD_PLAYED;
        }
    }

    return -1;
}

// How many open cards has the given player
int AiInput::amountOfOpenCards(int p, const AiInput::Board &current) const
{
    int cnt = 0;
    for (int i = 0; i < 8; i++)
    {
        if (current.cardsAtPos(p, i) > 0) cnt++;
    }
    return cnt;
}

int AiInput::amountOfWinningCards(int p, Suite sSuite, const AiInput::Board &current) const
{
    int cnt = 0;
    for (int i = 0; i < 8; i++)
    {
        int card      = current.card(p, i);
        if (card < 0) continue;
        Suite suite   = Deck::getSuite(card);
        CardType type = Deck::getCardType(card);
        // Treat jacks as trump
        if (type == Jack) suite = Grand;
        // Check only right suite or trump cards
        if (sSuite != suite) continue;

        // Would we win this card?
        if (wouldWinMove(p, card, current)) cnt++;
    }
    return cnt;
}

// Check whether the given card would win a initial move
bool AiInput::wouldWinMove(int p, int card, const AiInput::Board &current) const
{
    Suite suite   = Deck::getSuite(card);
    CardType type = Deck::getCardType(card);
    if (type == Jack) suite = current.trump;

    // Check only right suite or trump cards
    if (suite != current.trump &&
        current.amountOfSuite[1 - p][suite] == 0 &&
        current.amountOfSuite[1 - p][Grand] > 0)
    {
        //qCDebug(LSKAT_LOG) << "Player" << (1 - p) << "can use trump against" << Deck::name(suite);
        return false;
    }

    // Other player has suite..check cards
    for (int j = 0; j < 8; j++)
    {
        int ocard      = current.card(1 - p, j);
        if (ocard < 0) continue;
        Suite osuite   = Deck::getSuite(ocard);
        CardType otype = Deck::getCardType(ocard);
        if (otype == Jack) osuite = current.trump;

        // Check only right suite or trump cards
        if (suite == osuite)
        {
            // 0 if card wins ocards, 1 otherwise
            int owinner = EngineTwo::whoWonMove(card, ocard, current.trump);
            if (owinner == 1)
            {
                //qCDebug(LSKAT_LOG) << "Player " << p << " looses " << Deck::name(card);
                return false;
            }
        }
    }
    //qCDebug(LSKAT_LOG) << "Player " << p << " wins " << Deck::name(card);
    return true;
}

// Game evaluation ratings

// Try to save a free ten
const double RULE_FREE_TEN        =  20.0 * RATING_ONE_POINT;
// Catch a free ten
const double RULE_CATCH_TEN       =  15.0 * RATING_ONE_POINT;
// Use Ace to hunt free tens
const double RULE_HUNTER_ACE      = -20.0 * RATING_ONE_POINT;
// Do not free Ace to possibly hunt free tens
const double RULE_SUPPORT_ACE     = -14.0 * RATING_ONE_POINT;
// Protect possible free ten with minor card
const double RULE_PROTECT_TEN     = -14.0 * RATING_ONE_POINT;
// Protect possible free ten with removing hunter Ace
const double RULE_KILL_HUNTER_ACE =  25.0 * RATING_ONE_POINT;
// Pull trumps
const double RULE_PULL_TRUMP      =  10.0 * RATING_ONE_POINT;
// Save trumps
const double RULE_SAVE_TRUMP      = -10.0 * RATING_ONE_POINT;
// Stay first mover
const double RULE_FIRST_MOVER     =   4.0 * RATING_ONE_POINT;

// Rule based override over board evaluation to tackle special scenarios
double AiInput::rulebaseFirstMover(int p, int card, const AiInput::Board &current) const
{
    double result = 0.0;
    Suite suite   = Deck::getSuite(card);
    CardType type = Deck::getCardType(card);
    Suite altSuite = (suite == current.trump || type == Jack)?Grand:suite;

    // Check whether we win the move
    if (wouldWinMove(p, card, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Staying first mover " << Deck::name(card);
        result += RULE_FIRST_MOVER;
    }

    // Protect free ten
    if (type == Ten &&
        findCard(current, suite, Ace) != CARD_PLAYED &&
        findCard(current, suite, Ace) != p &&
        wouldWinMove(p, card, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Saving Ten " << Deck::name(card);
        result += RULE_FREE_TEN;
    }

    // Catch free ten
    if (type == Ace &&
        (1 - p) == findCard(current, suite, Ten) &&
        hasAmount(1 - p, altSuite, 1, 1, current) &&
        wouldWinMove(p, card, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Catching Ten with " << Deck::name(card);
        result += RULE_CATCH_TEN;
    }

    // Saving Ace to try to catch free ten
    if (type == Ace &&
        suite != current.trump &&
        (1 - p) == findCard(current, suite, Ten) &&
        hasAmount(1 - p, suite, 2, 3, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Hunting Ten with " << Deck::name(card);
        result += RULE_HUNTER_ACE;
    }

    // Saving additional cards for hunter Ace
    if (suite != current.trump &&
        type != Jack &&
        type != Ace &&
        (1 - p) == findCard(current, suite, Ten) &&
        (p) == findCard(current, suite, Ace) &&
        hasAmount(1 - p, suite, 2, 3, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Supporting Hunter ACE with " << Deck::name(card);
        if (hasAmount(1 - p, suite, 2, 2, current)) result += RULE_SUPPORT_ACE;
        else result += 0.75 * RULE_SUPPORT_ACE;
    }

    // Saving additional cards for hunted Ten
    if (suite != current.trump &&
        type != Jack &&
        type != Ten &&
        (1 - p) == findCard(current, suite, Ace) &&
        (p) == findCard(current, suite, Ten) &&
        hasAmount(p, suite, 2, 3, current) &&
        hasAmount(1 - p, suite, 2, 11, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Protecting hunted TEN with " << Deck::name(card);
        if (hasAmount(p, suite, 2, 2, current)) result += RULE_PROTECT_TEN;
        else result += 0.5 * RULE_PROTECT_TEN;
    }

    // Killing hunter Ace
    if (suite != current.trump &&
        type != Jack &&
        type != Ten &&
        (1 - p) == findCard(current, suite, Ace) &&
        (p) == findCard(current, suite, Ten) &&
        hasAmount(p, suite, 2, 11, current) &&
        hasAmount(1 - p, suite, 1, 1, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Killing hunter ACE with " << Deck::name(card);
        result += RULE_KILL_HUNTER_ACE;
    }

    // Pull trumps
    if (altSuite == Grand)
    {
        int trumpWin1 = amountOfWinningCards(p, altSuite, current);
        //int trumpWin2 = amountOfWinningCards(1 - p, altSuite, current);
        int trump1    = current.amountOfSuite[p][int(altSuite)];
        int trump2    = current.amountOfSuite[1 - p][int(altSuite)];
        // Pull trump
        if (trumpWin1 >= trump2 &&
            trump1 > trump2 &&
            trump2 > 0 &&
            wouldWinMove(p, card, current))
        {
            if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Pull trump " << Deck::name(card);
            result += RULE_PULL_TRUMP;
        }

        // Do not play trump if other party has none
        if (trump2 == 0)
        {
            if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER RULE: Save trump " << Deck::name(card);
            if (trump1 == 1) result += RULE_SAVE_TRUMP;
            else if (trump1 == 2) result += 0.75 * RULE_SAVE_TRUMP;
            else result += 0.5 * RULE_SAVE_TRUMP;
        }
    }

    return result;
}

// Rule based override over board evaluation to tackle special scenarios
double AiInput::rulebaseAnswerMover(int p, int card, const AiInput::Board &current) const
{
    double result = 0.0;
    Suite suite   = Deck::getSuite(card);
    CardType type = Deck::getCardType(card);
    Suite altSuite = (suite == current.trump || type == Jack)?Grand:suite;

    // Check whether we win the move
    if (wouldWinMove(p, card, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER ANSWER RULE: Becoming first mover" << Deck::name(card);
        result += RULE_FIRST_MOVER;
    }

    // Saving Ace to try to catch free ten
    if (type == Ace &&
        suite != current.trump &&
        (1 - p) == findCard(current, suite, Ten) &&
        hasAmount(1 - p, suite, 1, 2, current) &&
        hasAmount(p, suite, 2, 3, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER ANSWER RULE: Hunting Ten with" << Deck::name(card);
        result += RULE_HUNTER_ACE;
    }

    // Saving additional cards for hunter Ace
    if (suite != current.trump &&
        type != Jack &&
        type != Ace &&
        (1 - p) == findCard(current, suite, Ten) &&
        (p) == findCard(current, suite, Ace) &&
        hasAmount(1 - p, suite, 1, 2, current))
    {
        if (global_debug > 1) qCDebug(LSKAT_LOG) << "TRIGGER ANSWER RULE: Supporting Hunter ACE with" << Deck::name(card);
        if (hasAmount(1 - p, suite, 1, 1, current)) result += RULE_SUPPORT_ACE;
        else result += 0.75 * RULE_SUPPORT_ACE;
    }

    return result;
}

// Check whether the given player has between [min,max] cards of the given suite
bool AiInput::hasAmount(int player, Suite suite, int min, int max, const AiInput::Board &current) const
{
    if (current.amountOfSuite[player][int(suite)] >= min &&
        current.amountOfSuite[player][int(suite)] <= max)
    {
        return true;
    }
    return false;
}

// Check whether the two cards played are legal, supposed the given player is
// the second one (as the first player always plays a legal card)
bool AiInput::isLegalMove(int card1, int card2, int pl, const AiInput::Board &current) const
{
    Suite suite1   = Deck::getSuite(card1);
    Suite suite2   = Deck::getSuite(card2);
    CardType type1 = Deck::getCardType(card1);
    CardType type2 = Deck::getCardType(card2);

    // Force trump colour as Jacks count as Trump
    if (type1 == Jack) suite1 = current.trump;
    if (type2 == Jack) suite2 = current.trump;

    // Same suite is always OK
    if (suite1 == suite2) return true;

    // Search if current player has a card of the same colour but did not
    // play it (if it was played we checked already above)
    for (int i = 0; i < 8; i++)
    {
        int card = current.card(pl, i);
        // This card is not available anymore
        if (card < 0) continue;
    //   if (type1 == Jack) qCDebug(LSKAT_LOG) << "Check card 2 " <<Deck::name(card);

        // Ignore played card
        if (card == card2) continue;

        // Analyze card
        Suite suite   = Deck::getSuite(card);
        CardType type = Deck::getCardType(card);

        // Force trump colour as Jacks count as Trump
        if (type == Jack) suite = current.trump;

        // Check whether current card matches the first player card
        if (suite == suite1)
        {
            return false;
        }
    }
    // if (type1 == Jack) qCDebug(LSKAT_LOG) << "ALLOWED  ";
    return true;
}
