/*******************************************************************
*
* Copyright 2007  Aron Boström <c02ab@efd.lth.se>
*
* Bovo 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, or (at your option)
* any later version.
*
* Bovo 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 Bovo; see the file COPYING.  If not, write to
* the Free Software Foundation, 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
********************************************************************/

#include "scene.h"

#include <QTime>
#include <QTimer>
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
#include <QGraphicsView>
#include <QSvgRenderer>

#include <KConfig>
#include <KConfigGroup>
#include <KDesktopFile>

#include "common.h"
#include "coord.h"
#include "game.h"
#include "hintitem.h"
#include "mark.h"
#include "move.h"
#include "theme.h"

using namespace bovo;

namespace gui {

Scene::Scene(const Theme& theme, bool animation)
  : m_activate(false), m_game(nullptr), m_curCellSize(10.0), m_player(No), m_animation(animation),
  m_paintMarker(false), m_showLast(false) {
    /** @todo read theme from some configuration, I guess */
    /** @todo read file names from from some configuration, I guess */
    m_renderer = nullptr;
    loadTheme(theme);
    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));
    m_hintTimer = new QTimer(this);
    m_hintTimer->setSingleShot(true);
    m_hintItem = nullptr;
    setSceneRect( 0, 0, m_curCellSize*(NUMCOLS+2), m_curCellSize*(NUMCOLS+2) );
}

Scene::~Scene() {
    if (m_renderer) {
        m_renderer->deleteLater();
    }
    if (m_hintItem) {
        m_hintItem->deleteLater();
    }
    if (m_hintTimer) {
        m_hintTimer->disconnect();
        m_hintTimer->stop();
        m_hintTimer->deleteLater();
    }
}

qreal Scene::squareSize() const {
    return m_curCellSize;
}

void Scene::loadTheme(const Theme& theme) {
    m_fill = theme.fill();
    QColor color(theme.backgroundColor());
    QPalette bgPal;
    const auto vs = views();
    for (QGraphicsView* view : vs) {
        bgPal.setColor(view->backgroundRole(), color.isValid() ? color : Qt::white);
        view->setPalette(bgPal);
    }
    if (m_renderer == nullptr)
        m_renderer = new QSvgRenderer(theme.svg());
    else
        m_renderer->load(theme.svg());
    const QList<QGraphicsItem*> allMarks = items();
    for (QGraphicsItem* item : allMarks) {
        if (Mark* mark = qgraphicsitem_cast<Mark *>(item)) {
            mark->setFill(m_fill);
            mark->update(mark->boundingRect());
        }
        else if (auto hintItem = qgraphicsitem_cast<HintItem *>(item)) {
            hintItem->setFill(m_fill);
            hintItem->update(hintItem->boundingRect());
        }
    }
}

void Scene::setTheme(const Theme& theme) {
    loadTheme(theme);
    invalidate(0, 0, width(), height());
}

void Scene::activate(bool activate) {
    m_activate = activate;
}

void Scene::setWin() {
    if (!m_game->isGameOver()) {
        return;
    }
    invalidate(0, 0, width(), height());
}

void Scene::setGame(Game* game) {
    destroyHint();
    m_winningMoves = QList<Move>();
    m_game = game;
    m_player = m_game->player();
    connect(m_game, &Game::boardChanged, this, &Scene::updateBoard);
    if (!m_game->demoMode()) {
        connect(m_game, &Game::playerTurn, this, &Scene::slotPlayerTurn);
        connect(m_game, &Game::oposerTurn, this, &Scene::slotOposerTurn);
    }
    connect(m_game, &Game::gameOver, this, &Scene::slotGameOver);
    connect(this, &Scene::move, m_game, &Game::move);

    qDeleteAll(items()); //remove all marks
    m_paintMarker = false;
    m_showLast = false;
    invalidate(0, 0, width(), height());
}

void Scene::updateBoard(const Move& move) {
    destroyHint();
    if (move.valid()) {
        setShowLast(move.x(), move.y());
        Mark* mark = new Mark(this, move, m_animation, m_fill);
        mark->setSharedRenderer(m_renderer);
        addItem(mark);
    } else if (move.player() == No) {
        if (!m_game->isGameOver() && !m_winningMoves.empty()) {
            m_winningMoves = QList<Move>();
            invalidate(0, 0, width(), height());
        }
        removeShowLast();
        const QList<QGraphicsItem*> allMarks = items();
        for (QGraphicsItem* item : allMarks) {
            if (Mark* mark = qgraphicsitem_cast<Mark *>(item)) {
                if (mark->row() == move.y() && mark->col() == move.x()) {
                    if (m_animation) {
                        connect(mark, &Mark::killed, this, &Scene::killMark);
                        mark->kill();
                    } else {
                        removeItem(mark);
                        delete mark;
                    }
                }
            }
        }
    }
}

QPointF Scene::cellCenter(int x, int y) const {
    return cellTopLeft(x, y) + QPointF(m_curCellSize/2.0, m_curCellSize/2.0);
}

QPointF Scene::cellTopLeft(int x, int y) const {
    return {(x+1) * m_curCellSize, (y+1) * m_curCellSize};
}

void Scene::drawBackground(QPainter *p, const QRectF&) {
    QRectF sRect = sceneRect();
    int minSize = qMin(static_cast<int>(sRect.width()),
                       static_cast<int>(sRect.height()));
    qreal shrinkSize = static_cast<qreal>(NUMCOLS) /
                       static_cast<qreal>(NUMCOLS+2);
    qreal size = static_cast<qreal>(minSize) /
                 static_cast<qreal>(NUMCOLS+2);
/*    sRect.setWidth(minSize*cellSize);
    sRect.setHeight(minSize*cellSize);
    sRect.setLeft(cellSize);*/
    QRectF tmpRect(size, size, minSize*shrinkSize, minSize*shrinkSize);
    m_renderer->render(p, QStringLiteral("background"));
    m_renderer->render(p, QStringLiteral("grid"), tmpRect);
}

void Scene::drawForeground(QPainter *p, const QRectF& bounds) {
    if (m_paintMarker) {
        QRectF rect(cellTopLeft(m_col, m_row), QSizeF(m_curCellSize,
                       m_curCellSize));
        qreal adjusting((1.0-m_fill)*m_curCellSize/2.0);
        rect.adjust(adjusting, adjusting, -adjusting, -adjusting);
        if (bounds.intersects(rect)) {
            p->setOpacity(0.4);
            m_renderer->render(p, QStringLiteral("x1"), rect);
            p->setOpacity(1);
        }
    }
    if (m_showLast) {
        QRectF rect(cellTopLeft(m_lastCol, m_lastRow), QSizeF(m_curCellSize,
                       m_curCellSize));
        if (bounds.intersects(rect)) {
            m_renderer->render(p, QStringLiteral("last"), rect);
        }
    }
    if (!m_winningMoves.empty()) {
        QList<Move>::const_iterator it = m_winningMoves.constBegin();
        QList<Move>::const_iterator end = m_winningMoves.constEnd();
        while (it != end) {
            QRectF tmpRect(cellTopLeft(it->x(), it->y()), QSizeF(m_curCellSize,
                           m_curCellSize));
            if (bounds.intersects(tmpRect)) {
                m_renderer->render(p, QStringLiteral("win"), tmpRect);
            }
            ++it;
        }
    }
}

void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *ev) {
    if (!m_game->isGameOver() && !m_game->computerTurn() && m_activate) {
        QRectF boardRect(cellTopLeft(0, 0), QSizeF(m_curCellSize * NUMCOLS,
                         m_curCellSize * NUMCOLS));
        if (!boardRect.contains(ev->scenePos())) {
            removePaintMarker();
        } else {
            uint row = (uint)((ev->scenePos().y()-boardRect.y()) / m_curCellSize);
            uint col = (uint)((ev->scenePos().x()-boardRect.x()) / m_curCellSize);
            row = qMax(row, static_cast<uint>(0));
            row = qMin(static_cast<uint>(NUMCOLS-1), row);
            col = qMax(col, static_cast<uint>(0));
            col = qMin(static_cast<uint>(NUMCOLS-1), col);
            if (m_row != row || m_col != col || !m_paintMarker) {
                if (m_game->player(Coord(col, row)) == No) {
                    setPaintMarker(col, row);
                } else {
                    removePaintMarker();
                }
            }
        }
    }
}

void Scene::mousePressEvent( QGraphicsSceneMouseEvent* ev ) {
    if (m_game->isGameOver() || m_game->computerTurn() || !m_activate) {
        return;
    }
    QRectF boardRect(cellTopLeft(0, 0), QSizeF(m_curCellSize * NUMCOLS,
                     m_curCellSize * NUMCOLS));
    if (!boardRect.contains(ev->scenePos())) {
        return;
    }
    int row = (int)((ev->scenePos().y()-boardRect.y()) / m_curCellSize);
    int col = (int)((ev->scenePos().x()-boardRect.x()) / m_curCellSize);
    row = qMax(row, 0);
    row = qMin(NUMCOLS-1, row);
    col = qMax(col, 0);
    col = qMin(NUMCOLS-1, col);
    Q_EMIT move(Move(m_player, Coord(col, row)));
}

void Scene::removePaintMarker() {
    if (!m_paintMarker) {
        return;
    }
    m_paintMarker = false;
    QRectF rectOld(cellTopLeft(m_col, m_row), QSizeF(m_curCellSize,
                   m_curCellSize));
    qreal adjusting((1.0-m_fill)*m_curCellSize/2.0);
    rectOld.adjust(adjusting, adjusting, -adjusting, -adjusting);
    invalidate(rectOld, ForegroundLayer);
}

void Scene::setPaintMarker(uint col, uint row) {
    if (m_paintMarker == true && m_col == col && m_row == row) {
        return;
    }
    removePaintMarker();
    m_paintMarker = true;
    m_col = col;
    m_row = row;
    QRectF rect(cellTopLeft(m_col, m_row), QSizeF(m_curCellSize,
                   m_curCellSize));
    qreal adjusting((1.0-m_fill)*m_curCellSize/2.0);
    rect.adjust(adjusting, adjusting, -adjusting, -adjusting);
    invalidate(rect, ForegroundLayer);
}

void Scene::removeShowLast() {
    if (!m_showLast) {
        return;
    }
    m_showLast = false;
    QRectF rectOld(cellTopLeft(m_lastCol, m_lastRow), QSizeF(m_curCellSize,
                   m_curCellSize));
    invalidate(rectOld, ForegroundLayer);
}

void Scene::setShowLast(uint col, uint row) {
    if (m_showLast == true && m_lastCol == col && m_lastRow == row) {
        return;
    }
    removeShowLast();
    m_showLast = true;
    m_lastCol = col;
    m_lastRow = row;
    QRectF rect(cellTopLeft(m_lastCol, m_lastRow), QSizeF(m_curCellSize,
                   m_curCellSize));
    invalidate(rect, ForegroundLayer);
}

void Scene::slotPlayerTurn() {
    activate(true);
}

void Scene::slotOposerTurn() {
    removePaintMarker();
    activate(false);
}

void Scene::slotGameOver(const QList<Move>& win) {
    removePaintMarker();
    removeShowLast();
    m_winningMoves = win;
    setWin();
    activate(false);
}

void Scene::hint(const Move& hint) {
    destroyHint();
    m_hintItem = new HintItem(this, hint, m_animation, m_fill);
    m_hintItem->setSharedRenderer(m_renderer);
    addItem(m_hintItem);
    connect(m_hintTimer, &QTimer::timeout, this, &Scene::hintTimeout);
    m_hintTimer->start(2000);
}

void Scene::destroyHint() {
    if (m_hintItem != nullptr) {
        m_hintTimer->disconnect();
        m_hintTimer->stop();
        m_hintItem->killAnimation();
        removeItem(m_hintItem);
        m_hintItem->deleteLater();
        m_hintItem = nullptr;
    }
}

void Scene::hintTimeout() {
    if (!m_animation) {
        destroyHint();
    } else if (m_hintItem != nullptr) {
        connect(m_hintItem, &HintItem::killed, this, &Scene::destroyHint);
        m_hintItem->kill();
    }
}

void Scene::enableAnimation(bool enabled) {
    m_animation = enabled;
    if (!m_animation) {
        killAnimations();
    }
}

void Scene::killAnimations() {
    if (m_hintItem != nullptr) {
        m_hintItem->killAnimation();
    }
}

void Scene::killMark(Mark* mark) {
    removeItem(mark);
    mark->deleteLater();
}

void Scene::replay() {
    const QList<QGraphicsItem*> allMarks = items();
    for (QGraphicsItem* mark : allMarks) {
        removeItem(mark);
        delete mark;
    }
    m_winningMoves = QList<Move>();
    invalidate(0, 0, width(), height());
    connect(m_game, &Game::boardChanged, this, &Scene::updateBoard);
}

bool Scene::event(QEvent *event) {
    bool ret = QGraphicsScene::event(event);
    if (event->type() == QEvent::Leave) {
        removePaintMarker();
    }
    return ret;
}

} /* namespace gui */


