/*
    QSoloCards is a collection of Solitaire card games written using Qt
    Copyright (C) 2009  Steve Moore

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "SpiderBoard.h"
#include <QtGui/QMessageBox>
#include <QtGui/QAction>
#include <QtGui/QActionGroup>
#include <QtGui/QResizeEvent>
#include <QtCore/QDateTime>

#include "CardPixmaps.h"
#include "CardDeck.h"


#include <iostream>

const QString  SpiderBoard::GameTypeKeyStr("GameType");


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
SpiderBoard::SpiderBoard(QWidget * pWidget)
    :GameBoard(pWidget,QString(tr("Spider Solitaire")).trimmed(),QString("Spider")),
     m_pDeck(NULL),
     m_homeVector(),
     m_stackVector(),
     m_cheat(false),
     m_gameType(SpiderBoard::FourSuits)
{
    this->setHelpFile(":/help/SpiderHelp.html");
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
SpiderBoard::~SpiderBoard()
{
}

////////////////////////////////////////////////////////////////////////////////
// ok this is an attempt to offer hint moves.  It will prefer same suit moves, then
// consecutive index matches, and then card to empty stack moves.  It will try to
// move all cards that are movable in a stack.  So, there are possibilities for
// tweaks.
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::getHint(CardStack * & pSrcWidget,
			  unsigned int & srcStackIndex,
			  CardStack * & pDstWidget)
{
    bool moveFound=false;
    bool sameSuitFound=false;
    bool nonSameSuitFound=false;

    CardStack * pCanMoveToEmpty=NULL;
    CardStack * pCanMoveTo=NULL;
    CardStack * pCanMoveToSameSuit=NULL;
    bool newMoveFound=false;
    int j;

    qsrand(QDateTime::currentDateTime().toTime_t());
    bool fromZero=((0==qrand()%2 ^ 1==qrand()%3)?true:false); // this introduces some randomness by selecting whether we are going
                                                              // to go through the stacks forward or backwards.


    for ((fromZero?(j=0):(j=this->m_stackVector.size()-1));
	 ((fromZero)?(j<(int)this->m_stackVector.size()):(j>=0))&& !sameSuitFound ;((fromZero)?(j++):(j--)))
    {
        PlayingCardVector cardVector;
        unsigned int currSrcStackIndex;
	bool fromZeroInner=((0==qrand()%2 ^ 1==qrand()%3)?true:false); // this introduces some randomness by selecting whether we are going
	                                                               // to go through the stacks forward or backwards.
	int i;

	newMoveFound=false;

        if (this->m_stackVector[j]->getMovableCards(cardVector,currSrcStackIndex))
        {

            // first see if the cards can be sent home
            // the priority will be the same as a move to
            // cards of the same suit
            if (SpiderHomeStack::canSendHome(cardVector))
            {
                // find an open home widget
                for(unsigned int k=0;k<this->m_homeVector.size();k++)
                {
                    if (this->m_homeVector[k]->isEmpty())
                    {
                        pDstWidget=this->m_homeVector[k];
                        break;
                    }
                }

                if (NULL!=pDstWidget)
                {
                    pSrcWidget=this->m_stackVector[j];
                    srcStackIndex=currSrcStackIndex;
                    moveFound=true;
                    break;
                }
            }

	    for ((fromZeroInner?(i=0):(i=this->m_stackVector.size()-1));
		 ((fromZeroInner)?(i<(int)this->m_stackVector.size()):(i>=0));
		 ((fromZeroInner)?(i++):(i--)))
            {

                // ok we are only moving the cards if the hit is not on the same
                // stack they are already located in
                if (this->m_stackVector[i]!=this->m_stackVector[j])
                {
                    // see if we have a perferred move to a stack that has the same suit
                    // we will break out if we find a perferred match
                    if (this->m_stackVector[i]->canAddCardsSameSuit(cardVector))
                    {
                        pCanMoveToSameSuit=this->m_stackVector[i];
                        newMoveFound=true;
                        break;
                    }
                    // otherwise see if we can move the cards to the stack at all
                    else if (this->m_stackVector[i]->canAddCards(cardVector))
                    {
                        if (this->m_stackVector[i]->isEmpty())
                        {
                            pCanMoveToEmpty=this->m_stackVector[i];
                        }
                        else
                        {
                            pCanMoveTo=this->m_stackVector[i];
                        }
                        newMoveFound=true;
                    }
                }
            }

            if (newMoveFound)
            {
                // ok the best move is to a matching suit and consecutive
                // next best is to a consecutive
                // and lastly to an empty stack.
                if (NULL!=pCanMoveToSameSuit)
                {
                    pDstWidget=pCanMoveToSameSuit;
                    pSrcWidget=this->m_stackVector[j];
                    srcStackIndex=currSrcStackIndex;
                    sameSuitFound=true;
                }
                else if (NULL!=pCanMoveTo && !nonSameSuitFound)
                {
                    pDstWidget=pCanMoveTo;
                    pSrcWidget=this->m_stackVector[j];
                    srcStackIndex=currSrcStackIndex;
                    nonSameSuitFound=true;
                }
                else if (NULL!=pCanMoveToEmpty && !nonSameSuitFound)
                {
                    pDstWidget=pCanMoveToEmpty;
                    pSrcWidget=this->m_stackVector[j];
                    srcStackIndex=currSrcStackIndex;
                }
                moveFound=true;
            }
        }
    }


    return moveFound;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::newGame()
{
    // call the base class to do cleanup for us.
    GameBoard::newGame();

    CardDeck * pDeck=NULL;

    unsigned int i;
    unsigned int j;

    switch(this->m_gameType)
    {
        case SpiderBoard::FourSuits:
        pDeck=new CardDeck(2);
        break;

        case SpiderBoard::TwoSuits:
        {
            PlayingCardVector cardVector;
            // use hearts and spades as the two suits
            for (i=PlayingCard::Ace;i<PlayingCard::MaxCardIndex;i++)
            {
                cardVector.push_back(PlayingCard(PlayingCard::Hearts,(PlayingCard::CardIndex)i));
            }
            for (i=PlayingCard::Ace;i<PlayingCard::MaxCardIndex;i++)
            {
                cardVector.push_back(PlayingCard(PlayingCard::Spades,(PlayingCard::CardIndex)i));
            }
            pDeck=new CardDeck(cardVector,4);
        }
        break;

        case SpiderBoard::OneSuit:
        {
            PlayingCardVector cardVector;
            // use spades as the suit
            for (i=PlayingCard::Ace;i<PlayingCard::MaxCardIndex;i++)
            {
                cardVector.push_back(PlayingCard(PlayingCard::Spades,(PlayingCard::CardIndex)i));
            }
            pDeck=new CardDeck(cardVector,8);
        }
        break;
    };

    // if this happens something is very wrong just return
    if (NULL==pDeck)
    {
        return;
    }


    // Put all cards in the m_pDeck stack.  We will deal
    // from this stack.
    while(!pDeck->isEmpty())
    {
        this->m_pDeck->addCard(pDeck->next());
    }

    delete pDeck;


    DealItemVector dealItemVector;
    

    // Create the dealItemVector to direct the DealAnimation object on
    // how to deal the cards.
    for (i=0;i<this->m_stackVector.size();i++)
    {
	unsigned int cardsInStack=((i<4)?6:5);
	
	dealItemVector.push_back(DealItem(this->m_stackVector[i],m_pDeck));
	
        for (j=0;j<cardsInStack;j++)
        {
	    // add the items to tell how to deal the cards to the stack
	    // we want to flip the last card.
	    if (cardsInStack-1==j)
	    {
		dealItemVector[i].addCard(true);		
	    }
	    else
	    {
		dealItemVector[i].addCard(false);
	    }
        }
    }
    
    // ok now start the deal.  We don't need a move record for this item.
    m_dealAni.dealCards(dealItemVector,false);

}

////////////////////////////////////////////////////////////////////////////////
// The actions and the groups are create on the stack and then added to the QMenu
// Since, the QMenu is the owner it will clean up the memory.
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::addGameMenuItems(QMenu & menu)
{
    QActionGroup * pNumSuitsGroup=new QActionGroup(&menu);

    QAction * pFourSuitsAction=new QAction(tr("Four Suits").trimmed(),pNumSuitsGroup);
    pFourSuitsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_4));
    pFourSuitsAction->setCheckable(true);
    connect(pFourSuitsAction,SIGNAL(triggered()),
            this,SLOT(slotSetFourSuits()));

    QAction * pTwoSuitsAction=new QAction(tr("Two Suits").trimmed(),pNumSuitsGroup);
    pTwoSuitsAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_2));
    pTwoSuitsAction->setCheckable(true);
    connect(pTwoSuitsAction,SIGNAL(triggered()),
            this,SLOT(slotSetTwoSuits()));

    QAction * pOneSuitAction=new QAction(tr("One Suit").trimmed(),pNumSuitsGroup);
    pOneSuitAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_1));
    pOneSuitAction->setCheckable(true);
    connect(pOneSuitAction,SIGNAL(triggered()),
            this,SLOT(slotSetOneSuit()));

    // select the correct item in the list
    switch (this->m_gameType)
    {
        case SpiderBoard::FourSuits:
        pFourSuitsAction->setChecked(true);
        break;

        case SpiderBoard::TwoSuits:
        pTwoSuitsAction->setChecked(true);
        break;

        case SpiderBoard::OneSuit:
        pOneSuitAction->setChecked(true);
        break;
    };

    menu.addAction(pFourSuitsAction);
    menu.addAction(pTwoSuitsAction);
    menu.addAction(pOneSuitAction);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::loadSettings(const QSettings & settings)
{
    int gameType=settings.value(this->GameTypeKeyStr,SpiderBoard::FourSuits).toInt();

    switch (gameType)
    {
        case SpiderBoard::OneSuit:
        this->m_gameType=SpiderBoard::OneSuit;
        break;

        case SpiderBoard::TwoSuits:
        this->m_gameType=SpiderBoard::TwoSuits;
        break;

        case SpiderBoard::FourSuits:
        default:
        this->m_gameType=SpiderBoard::FourSuits;
        break;
    };
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::saveSettings(QSettings & settings)
{
    settings.setValue(this->GameTypeKeyStr,this->m_gameType);
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::setCheat(bool cheat)
{
    this->m_cheat=cheat;

    for(unsigned int i=0;i<this->m_stackVector.size();i++)
    {
        this->m_stackVector[i]->setCheat(cheat);
    }
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotDealNextCards(CardStack * pCardStackWidget,
                                    unsigned int index)
{
    bool aStackIsEmpty=false;
    unsigned int i;

    Q_UNUSED(pCardStackWidget);
    Q_UNUSED(index);

    // if the deck has cards to deal make sure all the stacks have cards
    // and deal them out
    if (!this->m_pDeck->isEmpty())
    {
        // first make sure none of the 10 stacks is empty.  All must have at least one card.
        // to be able to deal the cards.
        for(i=0;i<this->m_stackVector.size();i++)
        {
            if (this->m_stackVector[i]->isEmpty())
            {
                aStackIsEmpty=true;
                break;
            }
        }


        if (aStackIsEmpty)
        {
            QMessageBox::critical(this,this->gameName(),
                                  tr("All stacks must contain at least one card before the next set of cards can be dealt!").trimmed());
	    this->stopDemo();
            return;
        }


	DealItemVector dealItemVector;
	
	
	// Create the dealItemVector to direct the DealAnimation object on
	// how to deal the cards.
	for (i=0;i<this->m_stackVector.size();i++)
	{
	    dealItemVector.push_back(DealItem(this->m_stackVector[i],m_pDeck));
	    
	    dealItemVector[i].addCard(true);		
        }
	// ok now start the deal.
	m_dealAni.dealCards(dealItemVector);
    }
}

////////////////////////////////////////////////////////////////////////////////
// ok we need to perform the move from the stackWidget to the sentHome widget
// and create the CardMoveRecord.  Then just call slotCardsMoved and let the
// move be processed as if it was a drag and drop
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSendSuitHome(SpiderStack * pStack,
                                   const PlayingCardVector & cardVector,
                                   const CardMoveRecord & startMoveRecord)
{
    if (NULL!=pStack)
    {
        // ok first find an empty sentHome widget to add the cards too.
        SpiderHomeStack * pHome=NULL;
        unsigned int i;
        for (i=0;i<this->m_homeVector.size();i++)
        {
            if (this->m_homeVector[i]->isEmpty())
            {
                pHome=this->m_homeVector[i];
                break;
            }
        }

        // we should always find an empty home widget.  But check just in case
        if (pHome)
        {
            CardMoveRecord moveRecord(startMoveRecord);
            // add the cards to the update record.  But don't move the
	    // cards
            pHome->addCards(cardVector,moveRecord,true);

	    // perform the move of the cards and animate it if animations
	    // are enabled
	    m_sToSAniMove.moveCards(moveRecord);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotMoveCardsToDiffStack(SpiderStack * pStack,
                                           const PlayingCardVector & cardVector,
                                           const CardMoveRecord & startMoveRecord)
{
    if (NULL!=pStack)
    {
        SpiderStack * pCanMoveToEmpty=NULL;
        SpiderStack * pCanMoveTo=NULL;
        SpiderStack * pCanMoveToSameSuit=NULL;
        bool cardsWillMove=false;

        for (unsigned int i=0;i<this->m_stackVector.size();i++)
        {
            // ok we are only moving the cards if the hit is not on the same
            // stack they are already located in
            if (this->m_stackVector[i]!=pStack)
            {
                // see if we have a perferred move to a stack that has the same suit
                // we will break out if we find a perferred match
                if (this->m_stackVector[i]->canAddCardsSameSuit(cardVector))
                {
                    pCanMoveToSameSuit=this->m_stackVector[i];
                    cardsWillMove=true;
                    break;
                }
                // otherwise see if we can move the cards to the stack at all
                else if (this->m_stackVector[i]->canAddCards(cardVector))
                {
                    if (this->m_stackVector[i]->isEmpty())
                    {
                        pCanMoveToEmpty=this->m_stackVector[i];
                    }
                    else
                    {
                        pCanMoveTo=this->m_stackVector[i];
                    }
                    cardsWillMove=true;
                }
            }
        }

        // if we are going to move the cards
        if (cardsWillMove)
        {
            SpiderStack * pNewStack=NULL;

            // ok the best move is to a matching suit and consecutive
            // next best is to a consecutive
            // and lastly to an empty stack.
            if (NULL!=pCanMoveToSameSuit)
            {
                pNewStack=pCanMoveToSameSuit;
            }
            else if (NULL!=pCanMoveTo)
            {
                pNewStack=pCanMoveTo;
            }
            else
            {
                pNewStack=pCanMoveToEmpty;
            }

            CardMoveRecord moveRecord(startMoveRecord);
            pNewStack->addCards(cardVector,moveRecord,true);

	    // perform the move of the cards and animate it if animations
	    // are enabled
	    m_sToSAniMove.moveCards(moveRecord);
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSetFourSuits()
{
    if (SpiderBoard::FourSuits!=this->m_gameType)
    {
        this->m_gameType=SpiderBoard::FourSuits;
        this->newGame();
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSetTwoSuits()
{
    if (SpiderBoard::TwoSuits!=this->m_gameType)
    {
        this->m_gameType=SpiderBoard::TwoSuits;
        this->newGame();
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::slotSetOneSuit()
{
    if (SpiderBoard::OneSuit!=this->m_gameType)
    {
        this->m_gameType=SpiderBoard::OneSuit;
        this->newGame();
    }
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::calcScore()
{
    int score=0;
    unsigned int i;

    for(i=0;i<this->m_homeVector.size();i++)
    {
        score+=this->m_homeVector[i]->score();
    }

    for(i=0;i<this->m_stackVector.size();i++)
    {
        score+=this->m_stackVector[i]->score();
    }

    int deals=0;

    // The number of deals left is the number of cards in the deck widget divided by the number of stack widgets
    if (!this->m_pDeck->isEmpty() && this->m_stackVector.size()>0)
    {
        const PlayingCardVector & cardVector=this->m_pDeck->getCardVector();

        deals=cardVector.size()/this->m_stackVector.size();

	if (cardVector.size()%this->m_stackVector.size())
	{
	    deals++;
	}
    }

    QString dealsLeft(tr("Deals remaining:  %1").arg(QString::number(deals)).trimmed());

    emit scoreChanged(score,dealsLeft);
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::resizeEvent (QResizeEvent * event)
{
    unsigned int i;

    GameBoard::resizeEvent(event);


    QSize cardSize(CardPixmaps::getInst().getCardSize());

    QPointF currPos(GameBoard::LayoutSpacing,GameBoard::LayoutSpacing);
    
    m_pDeck->setPos(currPos);

    currPos.setX(GameBoard::LayoutSpacing*3 + cardSize.width()*2);

    for (i=0;i<m_homeVector.size();i++)
    {
	m_homeVector[i]->setPos(currPos);
	currPos.setX(currPos.x()+cardSize.width()+GameBoard::LayoutSpacing);
    }

    currPos.setY(GameBoard::LayoutSpacing*2+cardSize.height());
    currPos.setX(GameBoard::LayoutSpacing);
    for (i=0;i<m_stackVector.size();i++)
    {
	m_stackVector[i]->setPos(currPos);
	currPos.setX(currPos.x()+cardSize.width()+GameBoard::LayoutSpacing);
    }

    std::cout<<__FUNCTION__<<std::endl;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::runDemo(bool stopWhenNoMore)
{
    bool rc=true;

    if (!GameBoard::runDemo(false))
    {
	if (!m_pDeck->isEmpty())
	{
	    slotDealNextCards();
	}
	else if (stopWhenNoMore)
	{
	    stopDemo();
	    rc=false;
	}
	else
	{
	    rc=false;
	}
    }

    return rc;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
void SpiderBoard::createStacks()
{
    setCardResizeAlg(10,ResizeByWidth);

    unsigned int i;
    // create all the widgets for the board
    for(i=0;i<8;i++)
    {
        this->m_homeVector.push_back(new SpiderHomeStack);
	this->m_scene.addItem(m_homeVector[i]);

        // get signals when cards are added to the stack.  So, we can add undo info and
        // see when the game is over.
        this->connect(this->m_homeVector[i],SIGNAL(cardsMovedByDragDrop(CardMoveRecord)),
                      this,SLOT(slotCardsMoved(CardMoveRecord)));
    }

    for(i=0;i<10;i++)
    {
        this->m_stackVector.push_back(new SpiderStack);
	this->m_scene.addItem(m_stackVector[i]);

        // get signals when cards are added to the stack.  So, we can add undo info and
        // see when the game is over.

        this->connect(this->m_stackVector[i],SIGNAL(cardsMovedByDragDrop(CardMoveRecord)),
                      this,SLOT(slotCardsMoved(CardMoveRecord)));
        this->connect(this->m_stackVector[i],SIGNAL(sendSuitHome(SpiderStack *,PlayingCardVector,CardMoveRecord)),
                      this,SLOT(slotSendSuitHome(SpiderStack*,PlayingCardVector,CardMoveRecord)));
        this->connect(this->m_stackVector[i],SIGNAL(moveCardsToDiffStack(SpiderStack *,PlayingCardVector,CardMoveRecord)),
                      this,SLOT(slotMoveCardsToDiffStack(SpiderStack*,PlayingCardVector,CardMoveRecord)));


    }

    m_pDeck=new CardStack;
    this->m_scene.addItem(m_pDeck);

    // get signals when the deck is clicked on so we can deal the next set of cards
    this->connect(this->m_pDeck,SIGNAL(cardClicked(CardStack*,uint)),
                  this,SLOT(slotDealNextCards(CardStack*,uint)));

    std::cout<<__FUNCTION__<<__FILE__<<std::endl;


}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::isGameWon()const
{
    bool allSuitsSentHome=true;

    for(unsigned int i=0;i<this->m_homeVector.size();i++)
    {
        if (this->m_homeVector[i]->isEmpty())
        {
            allSuitsSentHome=false;
            break;
        }
    }

    return allSuitsSentHome;
}


////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////
bool SpiderBoard::isGameWonNotComplete()const
{
    bool rc=m_pDeck->isEmpty();

    if (rc)
    {
	for (unsigned int i=0;i<this->m_stackVector.size();i++)
	{
	    if (!(this->m_stackVector[i]->cardsAscendingTopToBottom() &&
		  this->m_stackVector[i]->allCardsFaceUp()))
	    {
		rc=false;
		break;
	    }
	}
    }

    return rc;
}