/* Copyright (C) 1997 Mathias Mueller   <in5y158@public.uni-hamburg.de>
 * Copyright (C) 2006 Mauricio Piacentini   <mauricio@tabuleiro.com>
 *
 * Kmahjongg 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA. */

#include "boardwidget.h"
#include "prefs.h"

#include <QTimer>
#include <qpainter.h>
#include <qapplication.h>

#include <klocale.h>
#include <kstandarddirs.h>
#include <kmessagebox.h>
#include <krandom.h>
#include <kconfig.h>
#include <kglobal.h>
#include <KDebug>


BoardWidget::BoardWidget(QWidget *parent)
    : KGameCanvasWidget(parent),
    theTiles()
{
    QPalette palette;
    palette.setColor(backgroundRole(), Qt::black);
    setPalette(palette);

    timer = new QTimer(this);
    connect(timer, SIGNAL(timeout()), this, SLOT(helpMoveTimeout()));

    TimerState = Stop;
    gamePaused = false;
    iTimerStep = 0;
    matchCount = 0;
    showMatch = false;
    showHelp = false;

    //memset( &Game->Mask, 0, sizeof( Game->Mask ) );
    gameGenerationNum = 0;
    m_angle = (TileViewAngle) Prefs::angle();

    // Load layout first
    loadBoardLayout(Prefs::layout());

    //Initialize our Game structure
    Game = new GameData(theBoardLayout.board());

    MouseClickPos1.e = Game->m_depth;     // mark tile position as invalid
    MouseClickPos2.e = Game->m_depth;

    //Animation timers
    animateForwardTimer = new QTimer(this);
    animateForwardTimer->setSingleShot(true);
    animateForwardTimer->setInterval(100);
    connect(animateForwardTimer, SIGNAL(timeout()), SLOT(animatingMoveListForward()));
    animateForwardTimer->stop();

    animateBackwardsTimer = new QTimer(this);
    animateBackwardsTimer->setSingleShot(true);
    animateBackwardsTimer->setInterval(100);
    connect(animateBackwardsTimer, SIGNAL(timeout()), SLOT(animatingMoveListBackwards()));
    animateBackwardsTimer->stop();

    loadSettings();
}

BoardWidget::~BoardWidget()
{
    delete Game;
}

void BoardWidget::loadSettings()
{
    // Load tileset. First try to load the last use tileset

    if (!loadTileset(Prefs::tileSet())) {
        kDebug() << "An error occurred when loading the tileset" << Prefs::tileSet() << "KMahjongg "
            "will continue with the default tileset.";
    }

    // Load background
    if (!loadBackground(Prefs::background(), false)) {
        kDebug() << "An error occurred when loading the background" << Prefs::background() << "KMah"
            "jongg will continue with the default background.";
    }

    setShowMatch(Prefs::showMatchingTiles());

    // If the random layout is activated, we won't load a boardlayout, as it will be created
    // randomly in calculateNewGame().
    if (!Prefs::randomLayout()) {
        if (QString::compare(Prefs::layout(), theBoardLayout.path(), Qt::CaseSensitive) !=0 ) {
            //TODO: WARN USER HERE ABOUT DESTRUCTIVE OPERATION!!!
            loadBoardLayout(Prefs::layout());
            calculateNewGame();
        }
    } else {
        calculateNewGame();
    }

    setDisplayedWidth();
    drawBoard(true);

    //Store our updated settings, some values might have been changed to defaults
    saveSettings();
}

void BoardWidget::resizeEvent(QResizeEvent *event)
{
    if (event->spontaneous()) {
        return;
    }

    resizeTileset(event->size());
    theBackground.sizeChanged(requiredWidth(), requiredHeight());
    drawBoard(true);
}

void BoardWidget::resizeTileset(const QSize &wsize)
{
    QSize newtiles = theTiles.preferredTileSize(wsize, requiredHorizontalCells(),
        requiredVerticalCells());

    theTiles.reloadTileset(newtiles);
    stopMatchAnimation();
}

void BoardWidget::saveSettings()
{
    Prefs::setTileSet(theTiles.path());
    Prefs::setLayout(theBoardLayout.path());
    Prefs::setBackground(theBackground.path());
    Prefs::setAngle(m_angle);
    Prefs::self()->writeConfig();
}

void BoardWidget::setDisplayedWidth()
{
    //TODO remove method and let the layout handle it
    //for now we just force our resizeEvent() to be called
    resize(width(), height());
}

void BoardWidget::populateSpriteMap()
{
    //Delete previous sprites (full update), synchronize state with GameData
    while (!items()->isEmpty()) {
        delete items()->first();
    }

    //Clear our spritemap as well
    spriteMap.clear();

    //Recreate our background
    QPalette palette;
    palette.setBrush(backgroundRole(), theBackground.getBackground());
    setPalette(palette);
    setAutoFillBackground(true);

    //create the sprites
    for (int z = 0; z < Game->m_depth; z++) {
        // we draw down the board so the tile below over rights our border
        for (int y = 0; y < Game->m_height; y++) {
            // drawing right to left to prevent border overwrite
            for (int x = Game->m_width - 1; x >= 0; x--) {

                // skip if no tile to display
                if (!Game->tilePresent(z, y, x)) {
                    continue;
                }

                QPixmap s;
                QPixmap us;
                QPixmap f;
                bool selected = false;
                s = theTiles.selectedTile(m_angle);
                us = theTiles.unselectedTile(m_angle);
                f = theTiles.tileface(Game->BoardData(z, y, x) - TILE_OFFSET);

                if (Game->HighlightData(z, y, x)) {
                    selected = true;
                }

                TileSprite *thissprite = new TileSprite(this, us, s, f, m_angle, selected);

                spriteMap.insert(TileCoord(x, y, z), thissprite);
            }
        }
    }

    updateSpriteMap();
}

void BoardWidget::updateSpriteMap()
{
    // initial offset on the screen of tile 0,0
    // think of it as what is left if you add all tilefaces (qwidth/heigh*2) plus one
    // (wholetile - shadow(3dindent) - tileface), and divide by 2
    int xOffset = (width() - (Game->m_width * (theTiles.qWidth())) - (theTiles.width()
        - (theTiles.qWidth() * 2))) / 2;
    int yOffset = (height() - (Game->m_height * (theTiles.qHeight())) - (theTiles.height()
        - (theTiles.qHeight() * 2))) / 2;

    // we iterate over the depth stacking order. Each successive level is
    // drawn one indent up and to the right. The indent is the width
    // of the 3d relief on the tile left (tile shadow width)
    switch (m_angle) {
    case NW:
        //remove shadow from margin calculation
        xOffset += theTiles.levelOffsetX() / 2;
            yOffset += theTiles.levelOffsetY() / 2;

        //Position
        for (int z = 0; z < Game->m_depth; z++) {
            // we draw down the board so the tile below over rights our border
            for (int y = 0; y < Game->m_height; y++) {
            // drawing right to left to prevent border overwrite
                for (int x=Game->m_width - 1; x >= 0; x--) {
                    int sx = x * (theTiles.qWidth()) + xOffset;
                    int sy = y * (theTiles.qHeight()) + yOffset;

                    // skip if no tile to display
                    if (!Game->tilePresent(z, y, x)) {
                        continue;
                    }

                    TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));

                    if (thissprite) {
                        thissprite->moveTo(sx, sy);
                    }

                    if (thissprite) {
                        thissprite->show();
                    }
                }
            }

            xOffset += theTiles.levelOffsetX();
            yOffset -= theTiles.levelOffsetY();
        }

        //Layer
        for (int z = 0; z < Game->m_depth; z++) {
            // start drawing in diagonal for correct layering. For this we double the board,
            //actually starting outside of it, in the bottom right corner, so our first diagonal ends
            // at the actual top right corner of the board
            for (int x = Game->m_width * 2; x >= 0; x--) {
                // reset the offset
                int offset = 0;

                for (int y = Game->m_height - 1; y >= 0; y--) {
                    if (Game->tilePresent(z, y, x - offset)) {
                        TileSprite *thissprite = spriteMap.value(TileCoord(x - offset, y, z));

                        if (thissprite) {
                            thissprite->raise();
                        }
                    }

                    //at each pass, move one place to the left
                    offset++;
                }
            }
        }

        break;

    case NE:
        xOffset -= theTiles.levelOffsetX() / 2;
        yOffset += theTiles.levelOffsetY() / 2;

        //Position
        for (int z = 0; z < Game->m_depth; z++) {
            // we draw down the board so the tile below over rights our border
            for (int y = 0; y < Game->m_height; y++) {
                // drawing right to left to prevent border overwrite
                for (int x = 0; x <= Game->m_width - 1; x++) {
                    int sx = x * (theTiles.qWidth()) + xOffset;
                    int sy = y * (theTiles.qHeight()) + yOffset;

                    // skip if no tile to display
                    if (!Game->tilePresent(z, y, x)) {
                        continue;
                    }

                    TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));

                    if (thissprite) {
                        thissprite->moveTo(sx, sy);
                    }

                    if (thissprite) {
                        thissprite->show();
                    }
                }
            }

            xOffset -= theTiles.levelOffsetX();
            yOffset -= theTiles.levelOffsetY();
        }

        //Layer
        for (int z = 0; z < Game->m_depth; z++) {
            // start drawing in diagonal for correct layering. For this we double the board,
            //actually starting outside of it, in the bottom right corner, so our first diagonal ends
            // at the actual top right corner of the board
            for (int x =- (Game->m_width); x <= Game->m_width - 1; x++) {
                // reset the offset
                int offset = 0;

                for (int y = Game->m_height - 1; y >= 0; y--) {
                    if (Game->tilePresent(z, y, x + offset)) {
                        TileSprite *thissprite = spriteMap.value(TileCoord(x + offset, y, z));

                        if (thissprite) {
                            thissprite->raise();
                        }
                    }

                    //at each pass, move one place to the right
                    offset++;
                }
            }
        }

        break;

    case SE:
        xOffset -= theTiles.levelOffsetX() / 2;
        yOffset -= theTiles.levelOffsetY() / 2;

        //Position
        for (int z = 0; z < Game->m_depth; z++) {
            for (int y = Game->m_height - 1; y >= 0; y--) {
                for (int x = 0; x <= Game->m_width - 1; x++) {
                    int sx = x * (theTiles.qWidth()) + xOffset;
                    int sy = y * (theTiles.qHeight()) + yOffset;

                    if (!Game->tilePresent(z, y, x)) {
                        continue;
                    }

                    TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));

                    if (thissprite) {
                        thissprite->moveTo(sx, sy);
                    }

                    if (thissprite) {
                        thissprite->show();
                    }
                }
            }

            xOffset -= theTiles.levelOffsetX();
            yOffset += theTiles.levelOffsetY();
        }

        //Layer
        for (int z = 0; z < Game->m_depth; z++) {
            for (int x =- (Game->m_width); x <= Game->m_width - 1; x++) {
                int offset = 0;

                for (int y = 0; y < Game->m_height; y++) {
                    if (Game->tilePresent(z, y, x + offset)) {
                        TileSprite *thissprite = spriteMap.value(TileCoord(x + offset, y, z));
                        if (thissprite) {
                            thissprite->raise();
                        }
                    }

                    offset++;
                }
            }
        }

        break;

    case SW:
        xOffset += theTiles.levelOffsetX() / 2;
        yOffset -= theTiles.levelOffsetY() / 2;

        //Position
        for (int z = 0; z < Game->m_depth; z++) {
            for (int y = Game->m_height - 1; y >= 0; y--) {
                for (int x = Game->m_width - 1; x >= 0; x--) {
                    int sx = x * (theTiles.qWidth()) + xOffset;
                    int sy = y * (theTiles.qHeight()) + yOffset;

                    if (!Game->tilePresent(z, y, x)) {
                        continue;
                    }

                    TileSprite *thissprite = spriteMap.value(TileCoord(x, y, z));

                    if (thissprite) {
                        thissprite->moveTo(sx, sy);
                    }

                    if (thissprite) {
                        thissprite->show();
                    }
                }
            }

            xOffset += theTiles.levelOffsetX();
            yOffset += theTiles.levelOffsetY();
        }

        //Layer
        for (int z = 0; z < Game->m_depth; z++) {
            for (int x = Game->m_width * 2; x >= 0; x--) {
                int offset = 0;

                for (int y = 0; y < Game->m_height; y++) {
                    if (Game->tilePresent(z, y, x - offset)) {
                        TileSprite *thissprite = spriteMap.value(TileCoord(x - offset, y, z));

                        if (thissprite) {
                            thissprite->raise();
                        }
                    }

                    offset++;
                }
            }
        }

        break;
    }
}

void BoardWidget::pause()
{
    gamePaused = !gamePaused;
    drawBoard(!gamePaused);
}

void BoardWidget::gameLoaded()
{
    int i;
    Game->initialiseRemovedTiles();
    i = Game->TileNum;

    // use the history of moves to put in the removed tiles area the correct tiles
    while (i < Game->MaxTileNum) {
        Game->setRemovedTilePair(Game->MoveListData(i), Game->MoveListData(i + 1));
        i += 2;
    }

    populateSpriteMap();
    drawBoard(true);
}

int BoardWidget::undoMove()
{
    cancelUserSelectedTiles();

    if (Game->TileNum < Game->MaxTileNum)
    {
        Game->clearRemovedTilePair(Game->MoveListData(Game->TileNum), Game->MoveListData(
            Game->TileNum + 1));
        putTileInBoard(Game->MoveListData(Game->TileNum), false);
        Game->TileNum++;
        putTileInBoard(Game->MoveListData(Game->TileNum));
        Game->TileNum++;
        drawTileNumber();
        setStatusText(i18n("Undo operation done successfully."));

        return 1;
    } else {
        setStatusText(i18n("What do you want to undo? You have done nothing!"));

        return 0;
    }
}

void BoardWidget::helpMove()
{
    cancelUserSelectedTiles();
    if (showHelp) helpMoveStop();

    if (Game->findMove(TimerPos1, TimerPos2)) {
        cheatsUsed++;
        iTimerStep = 1;
        showHelp = true;
        helpMoveTimeout();
    } else {
        setStatusText(i18n("Sorry, you have lost the game."));
    }
}

void BoardWidget::helpMoveTimeout()
{
    if (iTimerStep & 1) {
        hilightTile(TimerPos1, true, false);
        hilightTile(TimerPos2, true);
    } else {
        hilightTile(TimerPos1, false, false);
        hilightTile(TimerPos2, false);
    }

    // restart timer
    if (iTimerStep++ < 8) {
        timer->setSingleShot(true);
        timer->start(ANIMSPEED);
    } else {
        showHelp = false;
    }
}

void BoardWidget::helpMoveStop()
{
    timer->stop();
    iTimerStep = 8;
    hilightTile(TimerPos1, false, false);
    hilightTile(TimerPos2, false);
    showHelp = false;
}

void BoardWidget::startDemoMode()
{
    calculateNewGame();

    if (TimerState == Stop) {
        TimerState = Demo;
        iTimerStep = 0;
        emit demoModeChanged(true);
        setStatusText(i18n("Demo mode. Click mousebutton to stop."));
        demoMoveTimeout();
    }
}

void BoardWidget::stopDemoMode()
{
    TimerState = Stop;    // stop demo
    calculateNewGame();
    setStatusText(i18n("Now it is you again."));

    emit demoModeChanged(false);
    emit gameCalculated();
}

void BoardWidget::demoMoveTimeout()
{
    if (TimerState == Demo) {
        switch (iTimerStep++ % 6) {
        case 0:
            if (!Game->findMove(TimerPos1, TimerPos2)) {
                // if computer has won
                if (Game->TileNum == 0) {
                    animateMoveList();
                } else {
                    setStatusText(i18n("Your computer has lost the game."));

                    while (Game->TileNum < Game->MaxTileNum) {
                        putTileInBoard(Game->MoveListData(Game->TileNum), false);
                        Game->TileNum++;
                        putTileInBoard(Game->MoveListData(Game->TileNum));
                        Game->TileNum++;
                        drawTileNumber();
                    }
                }

                TimerState = Stop;
                //startDemoMode();
                //do not loop demo
                stopDemoMode();

                return;
            }

            break;

        case 1:
        case 3:
            hilightTile(TimerPos1, true, false);
            hilightTile(TimerPos2, true);

            break;

        case 2:
        case 4:
            hilightTile(TimerPos1, false, false);
            hilightTile(TimerPos2, false);

            break;

        case 5:
            Game->setRemovedTilePair(TimerPos1, TimerPos2);
            removeTile(TimerPos1, false);
            removeTile(TimerPos2);
            drawTileNumber();

            break;
        }

        // restart timer
        QTimer::singleShot(ANIMSPEED, this, SLOT(demoMoveTimeout()));
    }
}

void BoardWidget::setShowMatch(bool show)
{
    if (showMatch) {
        stopMatchAnimation();
    }

    showMatch = show;
}

void BoardWidget::matchAnimationTimeout()
{
    if (matchCount == 0) {
        return;
    }

    if (iTimerStep++ & 1) {
        for (short Pos = 0; Pos < matchCount; Pos++) {
          hilightTile(Game->getFromPosTable(Pos), true);
        }
    } else {
        for (short Pos = 0; Pos < matchCount; Pos++) {
          hilightTile(Game->getFromPosTable(Pos), false);
        }
    }

    if (TimerState == Match) {
        QTimer::singleShot(ANIMSPEED, this, SLOT(matchAnimationTimeout()));
    }
}

void BoardWidget::stopMatchAnimation()
{
    for (short Pos = 0; Pos < matchCount; Pos++) {
      hilightTile(Game->getFromPosTable(Pos), false);
    }

    TimerState = Stop;
    matchCount = 0;
}

void BoardWidget::redoMove()
{
    Game->setRemovedTilePair(Game->MoveListData(Game->TileNum - 1), Game->MoveListData(Game->TileNum
        - 2));
    removeTile(Game->MoveListData(Game->TileNum - 1), false);
    removeTile(Game->MoveListData(Game->TileNum - 1));
    drawTileNumber();
}

void BoardWidget::animateMoveList()
{
    setStatusText(i18n("Congratulations. You have won!"));
    animatingMoveListForward();
}

void BoardWidget::animatingMoveListForward()
{
    if (Game->TileNum < Game->MaxTileNum) {
        // put back all tiles
        putTileInBoard(Game->MoveListData(Game->TileNum));
        Game->TileNum++;
        putTileInBoard(Game->MoveListData(Game->TileNum), false);
        Game->TileNum++;
        drawTileNumber();
        animateForwardTimer->start(); //it is a single shot timer
    } else {
        //start removal
      animateBackwardsTimer->start(); //it is a single shot timer
    }
}

void BoardWidget::animatingMoveListBackwards()
{
    if (Game->TileNum > 0) {
        // remove all tiles
        removeTile(Game->MoveListData(Game->TileNum - 1), false);
        removeTile(Game->MoveListData(Game->TileNum - 1));
        drawTileNumber();
        animateBackwardsTimer->start(); //it is a single shot timer
    } else {
        //end of animation
        stopEndAnimation();
    }
}

void BoardWidget::stopEndAnimation()
{
    animateForwardTimer->stop();
    animateBackwardsTimer->stop();
}

QString BoardWidget::getRandomLayoutName() const
{
    QStringList tilesAvailable = KGlobal::dirs()->findAllResources("kmahjongglayout",
        QString("*.desktop"), KStandardDirs::Recursive);

    return tilesAvailable.at(qrand() % tilesAvailable.size());
}

void BoardWidget::calculateNewGame(int gNumber)
{
    cancelUserSelectedTiles();
    stopMatchAnimation();
    stopEndAnimation();
    Game->initialiseRemovedTiles();

    // If random layout is true, we will create a new random layout from the existing layouts.
    if (Prefs::randomLayout()) {
        QString layoutName = getRandomLayoutName();
        loadBoardLayout(layoutName);
    }

    setStatusText(i18n("Calculating new game..."));

    if (!loadBoard()) {
        setStatusText(i18n("Error converting board information!"));

        return;
    }

    if (gNumber == -1) {
        gameGenerationNum = KRandom::random();
    } else {
        gameGenerationNum = gNumber;
    }

    Game->random.setSeed(gameGenerationNum);

    // Translate Game->Map to an array of POSITION data.  We only need to
    // do this once for each new game.
    Game->generateTilePositions();

    // Now use the tile position data to generate tile dependency data.
    // We only need to do this once for each new game.
    Game->generatePositionDepends();

    // Now try to position tiles on the board, 64 tries max.
    for (short nr = 0; nr < 64; nr++) {
        if (Game->generateStartPosition2()) {
            drawBoard(true);
            setStatusText(i18n("Ready. Now it is your turn."));
            cheatsUsed = 0;
            emit gameCalculated();

            return;
        }
    }

    drawBoard(false);
    setStatusText(i18n("Error generating new game!"));
}

void BoardWidget::hilightTile(POSITION& Pos, bool on, bool doRepaint)
{
    TileSprite *atile = 0;
    TileCoord coord = TileCoord(Pos.x, Pos.y, Pos.e);

    if (spriteMap.contains(coord)) {
        atile = spriteMap.value(coord);
    }

    if (on) {
        Game->setHighlightData(Pos.e, Pos.y, Pos.x, 1);
        if (atile) {
            atile->setSelected(true);
        }
    } else {
        Game->setHighlightData(Pos.e, Pos.y, Pos.x, 0);
        if (atile) {
            atile->setSelected(false);
        }
    }
}

void BoardWidget::drawBoard(bool showTiles)
{
    if (gamePaused) {
        showTiles = false;
    }

    if (showTiles) {
        populateSpriteMap();
        drawTileNumber();
    } else {
        //Delete previous sprites
        while (!items()->isEmpty()) {
            delete items()->first();
        }

        //Clear our spritemap as well
        spriteMap.clear();

        //Recreate our background
        QPalette palette;
        palette.setBrush(backgroundRole(), theBackground.getBackground());
        setPalette(palette);
        setAutoFillBackground(true);
    }
}

void BoardWidget::putTileInBoard(POSITION& Pos, bool doRepaint)
{
    short E = Pos.e;
    short Y = Pos.y;
    short X = Pos.x;

    // we ensure that any tile we put on has highlighting off
    Game->putTile(E, Y, X, Pos.f);
    Game->setHighlightData(E, Y, X, 0);

    QPixmap s;
    QPixmap us;
    QPixmap f;
    s = theTiles.selectedTile(m_angle);
    us = theTiles.unselectedTile(m_angle);
    f = theTiles.tileface(Game->BoardData(E, Y, X) - TILE_OFFSET);
    TileSprite *thissprite = new TileSprite(this, us, s, f, m_angle, false);
    thissprite->show();
    spriteMap.insert(TileCoord(X, Y, E), thissprite);

    updateSpriteMap();
}

void BoardWidget::removeTile(POSITION& Pos, bool doRepaint)
{
    short E = Pos.e;
    short Y = Pos.y;
    short X = Pos.x;

    Game->TileNum--;                    // Eine Figur weniger
    Game->setMoveListData(Game->TileNum,Pos); // Position ins Protokoll eintragen

    delete spriteMap.take(TileCoord(X,Y,E));

    // remove tile from game board
    Game->putTile(E, Y, X, 0);
}

void BoardWidget::mousePressEvent(QMouseEvent *event)
{
    if (gamePaused) {
        return;
    }

    if (event->button() == Qt::LeftButton) {
        if (TimerState == Demo) {
            stopDemoMode();
        } else if (showMatch) {
            stopMatchAnimation();
        }

        if (showHelp) { // stop hilighting tiles
            helpMoveStop();
        }

        if (MouseClickPos1.e == Game->m_depth) {      // first tile
            transformPointToPosition(event->pos(), MouseClickPos1);

            if (MouseClickPos1.e != Game->m_depth && showMatch) {
                matchCount = Game->findAllMatchingTiles(MouseClickPos1);
                TimerState = Match;
                iTimerStep = 1;
                matchAnimationTimeout();
                cheatsUsed++;
            }
        } else {                             // second tile
            transformPointToPosition(event->pos(), MouseClickPos2);
            if (MouseClickPos2.e == Game->m_depth) {
                cancelUserSelectedTiles();
            } else {
                if (Game->isMatchingTile(MouseClickPos1, MouseClickPos2)) {
                    // update the removed tiles (we do this before the remove below
                    // so that we only require 1 screen paint for both actions)
                    Game->setRemovedTilePair(MouseClickPos1, MouseClickPos2);

                    // now we remove the tiles from the board*t,
                    removeTile(MouseClickPos1, false);
                    removeTile(MouseClickPos2);

                    // removing a tile means redo is impossible without
                    // a further undo.
                    Game->allow_redo = false;
                    demoModeChanged(false);
                    drawTileNumber();

                    // if no tiles are left, the player has `won`, so celebrate
                    if (Game->TileNum == 0) {
                        gameOver(Game->MaxTileNum,cheatsUsed);
                    } else {
                    // else if no more moves are possible, display the sour grapes dialog
                        validMovesAvailable();
                    }
                } else {
                    // redraw tiles in normal state
                    hilightTile(MouseClickPos1, false, false);
                    hilightTile(MouseClickPos2, false);
                }

                MouseClickPos1.e = Game->m_depth;     // mark tile position as invalid
                MouseClickPos2.e = Game->m_depth;
            }
        }
    }
}

KGameCanvasItem* BoardWidget::itemAt(const QPoint& point) const
{
    // Get the shadows...
    // theTiles.width() == The whole tile width including offset and shadow.
    // theTiles.qWidth() == Half of the width of the tile face (without the offset and the shadow).
    // theTiles.levelOffsetX() == The level of the tile (the perspective height of the tile)
    int shadowWidth = theTiles.width() - (theTiles.qWidth() * 2 + theTiles.levelOffsetX());
    int shadowHeight = theTiles.height() - (theTiles.qHeight() * 2 + theTiles.levelOffsetY());

    for (int i = m_items.size() - 1; i >= 0; i--) {
        KGameCanvasItem *canvasItem = m_items[i];

        // Cause of the shadow, that is not really a part of a tile. Correct the rect positions
        // related to the angle. Actually we just correct the rect of the canvas item cause of the
        // shadow, that is not part of the clickable tile.
        QRect oldRect = canvasItem->rect();
        QRect rectCorrection = oldRect;

        // Correct the positions related to the angle we had set.
        switch (m_angle) {
        case NW:
            rectCorrection.setRect(oldRect.x() + shadowWidth, oldRect.y(),
                oldRect.width() - shadowWidth, oldRect.height() - shadowHeight);
            break;
        case NE:
            rectCorrection.setRect(oldRect.x(), oldRect.y(), oldRect.width() - shadowWidth,
                oldRect.height() - shadowHeight);
            break;
        case SW:
            rectCorrection.setRect(oldRect.x() + shadowWidth, oldRect.y() + shadowHeight,
                oldRect.width() - shadowWidth, oldRect.height() - shadowHeight);
            break;
        case SE:
            rectCorrection.setRect(oldRect.x(), oldRect.y() + shadowHeight,
                oldRect.width() - shadowWidth, oldRect.height() - shadowHeight);
            break;
        }

        // if the canvas is visible and we click inside the corrected rect of the canvas item, we
        // can return the actual canvas item.
        if (canvasItem->visible() && rectCorrection.contains(point)) {
            return canvasItem;
        }
    }

    return NULL;
}

void BoardWidget::transformPointToPosition(const QPoint &point, POSITION &MouseClickPos)
{
    int E;
    int X;
    int Y;

    TileSprite *clickedItem = NULL;
    clickedItem = (TileSprite *) itemAt(point);

    if (!clickedItem) {
        //no item under mouse
        qDebug() << "no tile registered";

        return;
    }

    TileCoord coord = spriteMap.key(clickedItem);
    E = coord.z();
    X = coord.x();
    Y = coord.y();

    //sanity checking
    if (X < 0 || X >= Game->m_width || Y < 0 || Y >= Game->m_height) {
        return;
    }

    // if gameboard is empty, skip
    //sanity checking
    if (!Game->BoardData(E, Y, X)) {
        qDebug() << "Tile not in BoardData. Fading out?";

        return;
    }

    // tile must be 'free' (nothing left, right or above it)
    //Optimization, skip "over" test for the top layer
    if (E < Game->m_depth - 1) {
        if (Game->BoardData(E + 1, Y, X)
            || (Y < Game->m_height - 1 && Game->BoardData(E + 1, Y + 1, X))
            || (X < Game->m_width - 1 && Game->BoardData(E + 1, Y, X + 1))
            || (X < Game->m_width - 1 && Y < Game->m_height - 1
            && Game->BoardData(E + 1, Y + 1, X + 1))) {
            return;
        }
    }

    // No left test on left edge
    if (( X > 0) && (Game->BoardData(E, Y, X - 1) || Game->BoardData(E, Y + 1, X - 1))) {
        if ((X < Game->m_width - 2)
            && (Game->BoardData(E, Y, X + 2) || Game->BoardData(E, Y + 1, X + 2))) {
                return;
        }
    }

    // if we reach here, position is legal
    MouseClickPos.e = E;
    MouseClickPos.y = Y;
    MouseClickPos.x = X;
    MouseClickPos.f = Game->BoardData(E, Y, X);

    // give visible feedback
    hilightTile( MouseClickPos );
}

bool BoardWidget::loadBoard( )
{
    delete Game;
    Game = new GameData(theBoardLayout.board());

    return true;
}

void BoardWidget::setStatusText(const QString &pszText)
{
    emit statusTextChanged(pszText, gameGenerationNum);
}

bool BoardWidget::loadBackground(const QString &pszFileName, bool bShowError)
{
    if (theBackground.load(pszFileName, requiredWidth(), requiredHeight())) {
        if (theBackground.loadGraphics()) {
            return true;
        }
    }

    //Try default
    if (theBackground.loadDefault()) {
        if (theBackground.loadGraphics()) {
        }
    }

    return false;
}

void BoardWidget::drawTileNumber()
{
    emit tileNumberChanged(Game->MaxTileNum, Game->TileNum, Game->moveCount());
}

void BoardWidget::cancelUserSelectedTiles()
{
    if (MouseClickPos1.e != Game->m_depth) {
        hilightTile(MouseClickPos1, false); // redraw tile
        MouseClickPos1.e = Game->m_depth;    // mark tile invalid
    }
}

bool BoardWidget::loadTileset(const QString &path)
{
    if (theTiles.loadTileset(path)) {
        if (theTiles.loadGraphics()) {
            resizeTileset(size());

            return true;
        }
    }

    //Tileset or graphics could not be loaded, try default
    if (theTiles.loadDefault()) {
        if (theTiles.loadGraphics()) {
            resizeTileset(size());
        }
    }

    return false;
}

bool BoardWidget::loadBoardLayout(const QString &file)
{
    if (theBoardLayout.load(file)) {
        return true;
    } else {
        if (theBoardLayout.loadDefault()) {
            return false;
        } else {
            return false;
        }
    }
}

int BoardWidget::requiredHorizontalCells()
{
    int res = (Game->m_width / 2);
    /*if (Prefs::showRemoved())
        res = res + 3; //space for removed tiles*/

    return res;
}

int BoardWidget::requiredVerticalCells()
{
    int res = (Game->m_height / 2);

    return res;
}

int BoardWidget::requiredWidth()
{
    //int res = ((BoardLayout::width+12)* theTiles.qWidth());
    int res = width();

    return res;
}

int BoardWidget::requiredHeight()
{
    //int res = ((BoardLayout::height+3)* theTiles.qHeight());
    int res = height();

    return res;
}

void BoardWidget::angleSwitchCCW()
{
    switch (m_angle) {
    case NW :
        m_angle = NE;

        break;

    case NE :
        m_angle = SE;

        break;

    case SE :
        m_angle = SW;

        break;

    case SW :
        m_angle = NW;

        break;
    }

    QHashIterator<TileCoord, TileSprite*> i(spriteMap);
    while (i.hasNext()) {
        i.next();
        QPixmap u = theTiles.unselectedTile(m_angle);
        QPixmap s = theTiles.selectedTile(m_angle);
        i.value()->setAngle(m_angle, u, s);
    }

    //re-position and re-layer
    updateSpriteMap();

    //save angle
    saveSettings();
}

void BoardWidget::angleSwitchCW()
{
    switch (m_angle) {
    case NW :
        m_angle = SW;

        break;

    case NE :
        m_angle = NW;

        break;

    case SE :
        m_angle = NE;

        break;

    case SW :
        m_angle = SE;

        break;
    }

    QHashIterator<TileCoord, TileSprite *> i(spriteMap);
    while (i.hasNext()) {
        i.next();
        QPixmap u = theTiles.unselectedTile(m_angle);
        QPixmap s = theTiles.selectedTile(m_angle);
        i.value()->setAngle(m_angle, u, s);
    }

    //re-position and re-layer
    updateSpriteMap();

    //save settings
    saveSettings();
}

void BoardWidget::shuffle()
{
    cancelUserSelectedTiles();
    Game->shuffle();

    // force a full redraw
    populateSpriteMap();

    // I consider this s very bad cheat so, I punish the user
    // 300 points per use
    cheatsUsed += 15;
    drawTileNumber();

    // Test if any moves are available
    validMovesAvailable();
}

bool BoardWidget::validMovesAvailable()
{
    if (!Game->findMove(TimerPos1, TimerPos2)) {
        KMessageBox::information(this, i18n("Game over: You have no moves left."));

        return false;
    }

    return true;
}

QString  BoardWidget::getLayoutName()
{
    QString key("Name");

    return theBoardLayout.authorProperty(key);
}

void BoardWidget::wheelEvent(QWheelEvent *event)
{
    if (event->orientation() == Qt::Vertical) {
        if (event->delta() > 0) {
            angleSwitchCCW();
        } else {
            angleSwitchCW();
        }
    }

    event->accept();
}


#include "boardwidget.moc"
