/***********************-*-C++-*-********

  klondike.cpp  implements a patience card game

     Copyright (C) 1995  Paul Olav Tvete

 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.

//
// 7 positions, alternating red and black
//

****************************************/

#include "klondike.h"
#include <klocale.h>
#include "deck.h"
#include <kdebug.h>
#include <assert.h>
#include "cardmaps.h"

class KlondikePile : public Pile
{
public:
    KlondikePile( int _index, Dealer* parent)
        : Pile(_index, parent) {}

    void clearSpread() { cardlist.clear(); }

    void addSpread(Card *c) {
        cardlist.append(c);
    }
    virtual TQSize cardOffset( bool _spread, bool, const Card *c) const {
        kdDebug(11111) << "cardOffset " << _spread << " " << (c? c->name() : "(null)") << endl;
        if (cardlist.contains(const_cast<Card * const>(c)))
            return TQSize(+dspread(), 0);
        return TQSize(0, 0);
    }
private:
    CardList cardlist;
};

Klondike::Klondike( bool easy, KMainWindow* parent, const char* _name )
  : Dealer( parent, _name )
{
    // The units of the follwoing constants are pixels
    const int margin = 10; // between card piles and board edge
    const int hspacing = cardMap::CARDX() / 6 + 1; // horizontal spacing between card piles
    const int vspacing = cardMap::CARDY() / 4; // vertical spacing between card piles

    deck = Deck::new_deck(this);
    deck->move(margin, margin);

    EasyRules = easy;

    pile = new KlondikePile( 13, this);

    pile->move(margin + cardMap::CARDX() + cardMap::CARDX() / 4, margin);
    // Move the visual representation of the pile to the intended position
    // on the game board.

    pile->setAddFlags(Pile::disallow);
    pile->setRemoveFlags(Pile::Default);

    for( int i = 0; i < 7; i++ ) {
        play[ i ] = new Pile( i + 5, this);
        play[i]->move(margin + (cardMap::CARDX() + hspacing) * i, margin + cardMap::CARDY() + vspacing);
        play[i]->setAddType(Pile::KlondikeStore);
        play[i]->setRemoveFlags(Pile::several | Pile::autoTurnTop | Pile::wholeColumn);
    }

    for( int i = 0; i < 4; i++ ) {
        target[ i ] = new Pile( i + 1, this );
        target[i]->move(margin + (3 + i) * (cardMap::CARDX()+ hspacing), margin);
        target[i]->setAddType(Pile::KlondikeTarget);
        if (EasyRules) // change default
            target[i]->setRemoveFlags(Pile::Default);
        else
            target[i]->setRemoveType(Pile::KlondikeTarget);
    }

    setActions(Dealer::Hint | Dealer::Demo);

    redealt = false;
}

//  This function returns true when it is certain that the card t is no longer
//  needed on any of the play piles. This function is recursive but the
//  recursion will not get deep.
//
//  To determine wether a card is no longer needed on any of the play piles we
//  obviously must know what a card can be used for there. According to the
//  rules a card can be used to store another card with 1 less unit of value
//  and opposite color. This is the only thing that a card can be used for
//  there. Therefore the cards with lowest value (1) are useless there (base
//  case). The other cards each have 2 cards that can be stored on them, let us
//  call those 2 cards *depending cards*.
//
//  The object of the game is to put all cards on the target piles. Therefore
//  cards that are no longer needed on any of the play piles should be put on
//  the target piles if possible. Cards on the target piles can not be moved
//  and they can not store any of its depending cards. Let us call this that
//  the cards on the target piles are *out of play*.
//
//  The simple and obvious rule is:
//    A card is no longer needed when both of its depending cards are out of
//    play.
//
//  But using only the simplest rule to determine if a card is no longer
//  needed on any of the play piles is not ambitios enough. Therefore, if a
//  depending card is not out of play, we test if it could become out of play.
//  The requirement for getting a card out of play is that it can be placed on
//  a target pile and that it is no longer needed on any of the play piles
//  (this is why this function is recursive). This more ambitious rule lets
//  us extend the base case with the second lowest value (2).
bool Klondike::noLongerNeeded(Card::Rank r, Card::Suit s) {

    if (r <= Card::Two) return true; //  Base case.

    //  Find the 2 suits of opposite color. "- 1" is used here because the
    //  siuts are ranged 1 .. 4 but target_tops is indexed 0 .. 3. (Of course
    //  the subtraction of 1 does not affect performance because it is a
    //  constant expression that is calculated at compile time).
    unsigned char a = Card::Clubs - 1, b = Card::Spades - 1;
    if (s == Card::Clubs || s == Card::Spades)
        a = Card::Diamonds - 1, b = Card::Hearts - 1;

    const Card::Rank depending_rank = static_cast<Card::Rank>(r - 1);
    return
      (((target_tops[a] >= depending_rank)
        ||
        ((target_tops[a] >= depending_rank - 1)
         &&
         (noLongerNeeded
              (depending_rank, static_cast<Card::Suit>(a + 1)))))
       &&
       ((target_tops[b] >= depending_rank)
        ||
        ((target_tops[b] >= depending_rank - 1)
         &&
         (noLongerNeeded
              (depending_rank, static_cast<Card::Suit>(b + 1))))));
}

bool Klondike::tryToDrop(Card *t)
{
    if (!t || !t->realFace() || t->takenDown())
        return false;

//    kdDebug(11111) << "tryToDrop " << t->name() << endl;

    Pile *tgt = findTarget(t);
    if (tgt) {
        newHint
            (new MoveHint(t, tgt, noLongerNeeded(t->rank(), t->suit())));
        return true;
    }
    return false;
}

void Klondike::getHints() {

    target_tops[0] = target_tops[1] = target_tops[2] = target_tops[3]
        = Card::None;

    for( int i = 0; i < 4; i++ )
    {
        Card *c = target[i]->top();
        if (!c) continue;
        target_tops[c->suit() - 1] = c->rank();
    }


    Card* t[7];
    for(int i=0; i<7;i++)
        t[i] = play[i]->top();

    for(int i=0; i<7; i++)
    {
        CardList list = play[i]->cards();

        for (CardList::ConstIterator it = list.begin(); it != list.end(); ++it)
        {
            if (!(*it)->isFaceUp())
                continue;

            CardList empty;
            empty.append(*it);

            for (int j = 0; j < 7; j++)
            {
                if (i == j)
                    continue;

                if (play[j]->legalAdd(empty)) {
                    if (((*it)->rank() != Card::King) || it != list.begin()) {
                        newHint(new MoveHint(*it, play[j]));
                        break;
                    }
                }
            }
            break; // the first face up
        }

        tryToDrop(play[i]->top());
    }
    if (!pile->isEmpty())
    {
        Card *t = pile->top();
        if (!tryToDrop(t))
        {
            for (int j = 0; j < 7; j++)
            {
                CardList empty;
                empty.append(t);
                if (play[j]->legalAdd(empty)) {
                    newHint(new MoveHint(t, play[j]));
                    break;
                }
            }
        }
    }
}

Card *Klondike::demoNewCards() {
    deal3();
    if (!deck->isEmpty() && pile->isEmpty())
        deal3(); // again
    return pile->top();
}

void Klondike::restart() {
    kdDebug(11111) << "restart\n";
    deck->collectAndShuffle();
    redealt = false;
    deal();
}

void Klondike::deal3()
{
    int draw;

    if ( EasyRules ) {
        draw = 1;
    } else {
        draw = 3;
    }

    pile->clearSpread();

    if (deck->isEmpty())
    {
        redeal();
        return;
    }

    // move the cards back on the deck, so we can have three new
    for (int i = 0; i < pile->cardsLeft(); ++i) {
        pile->at(i)->move(pile->x(), pile->y());
    }

    for (int flipped = 0; flipped < draw ; ++flipped) {

        Card *item = deck->nextCard();
        if (!item) {
            kdDebug(11111) << "deck empty!!!\n";
            return;
        }
        pile->add(item, true, true); // facedown, nospread
        if (flipped < draw - 1)
            pile->addSpread(item);
        // move back to flip
        item->move(deck->x(), deck->y());

        item->flipTo( int(pile->x()) + pile->dspread() * (flipped), int(pile->y()), 8 * (flipped + 1) );
    }

}

//  Add cards from  pile to deck, in reverse direction
void Klondike::redeal() {

    CardList pilecards = pile->cards();
    if (EasyRules)
        // the remaining cards in deck should be on top
        // of the new deck
        pilecards += deck->cards();

    for (int count = pilecards.count() - 1; count >= 0; --count)
    {
        Card *card = pilecards[count];
        card->setAnimated(false);
        deck->add(card, true, false); // facedown, nospread
    }

    redealt = true;
}

void Klondike::deal() {
    for(int round=0; round < 7; round++)
        for (int i = round; i < 7; i++ )
            play[i]->add(deck->nextCard(), i != round, true);
}

bool Klondike::cardClicked(Card *c) {
    kdDebug(11111) << "card clicked " << c->name() << endl;

    if (Dealer::cardClicked(c))
        return true;

    if (c->source() == deck) {
        pileClicked(deck);
        return true;
    }

    return false;
}

void Klondike::pileClicked(Pile *c) {
    kdDebug(11111) << "pile clicked " << endl;
    Dealer::pileClicked(c);

    if (c == deck) {
        deal3();
    }
}

bool Klondike::startAutoDrop()
{
    bool pileempty = pile->isEmpty();
    if (!Dealer::startAutoDrop())
        return false;
    if (pile->isEmpty() && !pileempty)
        deal3();
    return true;
}


bool Klondike::isGameLost() const
{
    kdDebug( 11111 ) << "Is the game lost?" << endl;

    if (!deck->isEmpty()) {
        kdDebug( 11111 ) << "We should only check this when the deck is exhausted." << endl;
        return false;
    }

    // Check whether top of the pile can be added to any of the target piles.
    if ( !pile->isEmpty() ) {
        for ( int i = 0; i < 4; ++i ) {
            if ( target[ i ]->isEmpty() ) {
                continue;
            }
            if ( pile->top()->suit() == target[ i ]->top()->suit() &&
                 pile->top()->rank() - 1 == target[ i ]->top()->rank() ) {
                kdDebug( 11111 ) << "No, the source pile's top card could be added to target pile " << i << endl;
                return false;
            }
        }
    }

    // Create a card list - srcPileCards - that contains all accessible
    // cards in the pile and the deck.
    CardList srcPileCards;
    if ( EasyRules ) {
        srcPileCards = pile->cards();
    } else {
        /* In the draw3 mode, not every card in the source pile is
         * accessible, but only every third one.
         */
        for ( unsigned int i = 2; i < pile->cards().count(); i += 3 ) {
	    kdDebug( 11111 ) << "Found card "<< pile->cards()[i]->name()<< endl;
            srcPileCards += pile->cards()[ i ];
        }
        if ( !pile->cards().isEmpty() && pile->cards().count() % 3 != 0 ) {
	    kdDebug( 11111 ) << "Found last card "<< pile->cards()[pile->cards().count() - 1]->name()<< endl;
            srcPileCards += pile->cards()[ pile->cards().count() - 1 ];
        }
    }

    //  Check all seven stores
    for ( int i = 0; i < 7; ++i ) {

        // If this store is empty...
        if ( play[ i ]->isEmpty() ) {
            // ...check whether the pile contains a king we could move here.
            CardList::ConstIterator it  = srcPileCards.begin();
            CardList::ConstIterator end = srcPileCards.end();
            for ( ; it != end; ++it ) {
                if ( ( *it )->rank() == Card::King ) {
                    kdDebug( 11111 ) << "No, the pile contains a king which we could move onto store " << i << endl;
                    return false;
                }
            }

            // ...check whether any of the other stores contains a (visible)
            // king we could move here.
            for ( int j = 0; j < 7; ++j ) {
                if ( j == i || play[ j ]->isEmpty() ) {
                    continue;
                }
                const CardList cards = play[ j ]->cards();
                CardList::ConstIterator it = ++cards.begin();
                CardList::ConstIterator end = cards.end();
                for ( ; it != end; ++it ) {
                    if ( ( *it )->realFace() && ( *it )->rank() == Card::King ) {
                        kdDebug( 11111 ) << "No, store " << j << " contains a visible king which we could move onto store " << i << endl;
                        return false;
                    }
                }
            }
        } else { // This store is not empty...
            Card *topCard = play[ i ]->top();

            // ...check whether the top card is an Ace (we can start a target)
            if ( topCard->rank() == Card::Ace ) {
                kdDebug( 11111 ) << "No, store " << i << " has an Ace, we could start a target pile." << endl;
                return false;
            }

            // ...check whether the top card can be added to any target pile
            for ( int targetIdx = 0; targetIdx < 4; ++targetIdx ) {
                if ( target[ targetIdx ]->isEmpty() ) {
                    continue;
                }
                if ( target[ targetIdx ]->top()->suit() == topCard->suit() &&
                     target[ targetIdx ]->top()->rank() == topCard->rank() - 1 ) {
                    kdDebug( 11111 ) << "No, store " << i << "'s top card could be added to target pile " << targetIdx << endl;
                    return false;
                }
            }

            // ...check whether the source pile contains a card which can be
            // put onto this store.
            CardList::ConstIterator it = srcPileCards.begin();
            CardList::ConstIterator end = srcPileCards.end();
            for ( ; it != end; ++it ) {
                if ( ( *it )->isRed() != topCard->isRed() &&
                     ( *it )->rank() == topCard->rank() - 1 ) {
                    kdDebug( 11111 ) << "No, the pile contains a card which we could add to store " << i << endl;
                    return false;
                }
            }

            // ...check whether any of the other stores contains a visible card
            // which can be put onto this store, and which is on top of an
            // uncovered card.
            for ( int j = 0; j < 7; ++j ) {
                if ( j == i ) {
                    continue;
                }
                const CardList cards = play[ j ]->cards();
                CardList::ConstIterator it = cards.begin();
                CardList::ConstIterator end = cards.end();
                for ( ; it != end; ++it ) {
                    if ( ( *it )->realFace() &&
                         ( *it )->isRed() != topCard->isRed() &&
                         ( *it )->rank() == topCard->rank() - 1 ) {
                        kdDebug( 11111 ) << "No, store " << j << " contains a card which we could add to store " << i << endl;
                        return false;
                    }
                }
            }
        }
    }
    kdDebug( 11111 ) << "Yep, all hope is lost." << endl;
    return true;
}

static class LocalDealerInfo0 : public DealerInfo
{
public:
    LocalDealerInfo0() : DealerInfo(I18N_NOOP("&Klondike"), 0) {}
    virtual Dealer *createGame(KMainWindow *parent) { return new Klondike(true, parent); }
} ldi0;

static class LocalDealerInfo14 : public DealerInfo
{
public:
    LocalDealerInfo14() : DealerInfo(I18N_NOOP("Klondike (&draw 3)"), 13) {}
    virtual Dealer *createGame(KMainWindow *parent) { return new Klondike(false, parent); }
} ldi14;


#include "klondike.moc"