/*
    SPDX-FileCopyrightText: 2007 Paolo Capriotti <p.capriotti@gmail.com>

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

#include "battlefieldview.h"

#include <QMouseEvent>
#include <QSizePolicy>

#include "kbsrenderer.h"
#include "sprite.h"
#include "animator.h"
#include "animation.h"
#include "welcomescreen.h"
#include "button.h"
#include "delegate.h"

BattleFieldView::BattleFieldView(QWidget* parent, KBSRenderer* renderer, const QString& bgID, int gridSize)
: QGraphicsView(parent)
, m_renderer(renderer)
, m_factory(renderer)
, m_bgID(bgID)
, m_gridSize(gridSize)
, m_impact(nullptr)
, m_last_hit(nullptr)
, m_drawGrid(true)
, m_delegate(nullptr)
{
    m_background_lower = new KGameRenderedItem(m_renderer, bgID + QLatin1String("-layer1"));
    m_background_lower->setOpacity(0.98);
    
    m_background = new KGameRenderedItem(m_renderer, bgID + QLatin1String("-layer2"));
    m_background->setOpacity(0.98);
    
    m_screen = new WelcomeScreen(font());

    QGraphicsScene *scene = new QGraphicsScene(this);
    scene->addItem(m_background_lower);
    scene->addItem(m_background);
    scene->addItem(m_screen);
    
    for (Sprites::iterator i = m_sprites.begin();
            i != m_sprites.end();
            ++i) {
        i.value() = nullptr;
    }

    for (int i = 0; i < 11; i++) {
        hlines[i] = new QGraphicsLineItem;
        vlines[i] = new QGraphicsLineItem;

        scene->addItem(hlines[i]);
        scene->addItem(vlines[i]);

        hlines[i]->stackBefore(m_background);
        vlines[i]->stackBefore(m_background);
    }

    setScene(scene);
    setMouseTracking(true);
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
    setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}

void BattleFieldView::toggleGrid(bool show)
{
    if (m_drawGrid != show)
    {
        m_drawGrid = show;
        drawGrid();
    }
}

void BattleFieldView::drawGrid()
{
    if (m_drawGrid)
    {
        int spacing = m_renderer->size().width();
        int width   = spacing * m_gridSize;
        int height  = spacing * m_gridSize;

        for (int i = 0; i < 11; i++) {
            hlines[i]->show();
            hlines[i]->setLine(0, i * spacing, width, i * spacing);

            vlines[i]->show();
            vlines[i]->setLine(i * spacing, 0, i * spacing, height);
        }
    }
    else
    {
        for (int i = 0; i < 11; i++) {
            hlines[i]->hide();
            vlines[i]->hide();
        }
    }
}

void BattleFieldView::refresh()
{
    // Updates this widget.
    int x = pos().x();
    int y = pos().x();
    int w = m_renderer->size().width() * m_gridSize;
    int h = m_renderer->size().height() * m_gridSize;

    setSceneRect(0, 0, w, h);
    // Due the rounded nature of the view, it's necessary to set its
    // geometry to make sure it will show the entire grid.
    int fs = frameWidth();
    setGeometry(x, y, w + 2 * fs, h + 2 * fs); 

    // update welcome screen
    m_screen->setPos(0, 0);
    m_screen->resize(m_renderer->size() * m_gridSize);

    // Updates the backgrounds.
    m_background_lower->hide();
    m_background_lower->setRenderSize(m_renderer->size() * m_gridSize);
    m_background_lower->show();

    m_background->hide();
    m_background->setRenderSize(m_renderer->size() * m_gridSize);
    m_background->show();

    // Updates the grid.
    drawGrid();
 
    // update preview
    if (m_preview.sprite) {
        m_preview.sprite->refresh(m_renderer);
        m_preview.sprite->setPos(m_renderer->toReal(m_preview.pos));
    }
    
    // update sprites
    for (Sprites::const_iterator i = m_sprites.constBegin(); 
            i != m_sprites.constEnd();
            ++i) {
        i.value()->refresh(m_renderer);
        i.value()->setPos(m_renderer->toReal(i.key()));
    }
}

void BattleFieldView::setPreview(const QPoint & pos)
{
    if (!m_delegate) {
        return;
    }
    Ship * ship = m_delegate->nextShip();

    if (!ship) {
        return;
    }

    loadPreviewSprite(ship);
    Coord coordinate = m_renderer->toLogical(pos);

    if (m_delegate->canAddShip(m_player, coordinate)) {
        m_preview.sprite->turnGreen();
    } else {
        m_preview.sprite->turnRed();
    }

    QPointF scenePos = mapToScene(pos);
    m_preview.pos = m_renderer->toLogical(scenePos);
    m_preview.sprite->setPos(m_renderer->toReal(m_preview.pos));
}

void BattleFieldView::loadPreviewSprite(Ship * ship)
{
    if (m_preview.ship) {
        return;
    }

    m_preview.ship      = ship;
    m_preview.sprite    = m_factory.createShip(ship);

    m_preview.sprite->setOpacity(PREVIEW_OPACITY);
    scene()->addItem(m_preview.sprite);
}
void BattleFieldView::cancelPreview()
{
    delete m_preview.sprite;
    m_preview.sprite = nullptr;
    m_preview.ship = nullptr;
}

void BattleFieldView::addSprite(const Coord& c, Sprite* sprite)
{
    m_sprites.insert(c, sprite);
    sprite->setPos(m_renderer->toReal(c));
    scene()->addItem(sprite);
}

void BattleFieldView::add(Ship* ship)
{
    Sprite* sprite = m_factory.createShip(ship);
    sprite->setZValue(BACKGROUND);
    addSprite(ship->position(), sprite);
    
    // fading preview in
    if (ship->alive()) {
        Animation* a = new FadeAnimation(sprite, PREVIEW_OPACITY, 1, 1000);
        Animator::instance()->add(a);
    }

    if (ship == m_preview.ship) {
        cancelPreview();
    }
    else if (!ship->alive()) {
        sprite->setZValue(BACKGROUND);
        Animation* a = new FadeAnimation(sprite, 0, 0.5, 1000);
        Animator::instance()->add(a);
    }
}

void BattleFieldView::sink(Ship* ship)
{
    m_last_hit = nullptr;
    
    Coord p = ship->position();
    for (unsigned int i = 0; 
         i < ship->size(); 
         i++, p += ship->increment()) {
        const auto spritesOfPos = m_sprites.values(p);
        for (Sprite* s : spritesOfPos) {
            if (s->spriteKey().startsWith(QLatin1String("ship"))) {
                s->setZValue(BACKGROUND);
                s->setOpacity(0.5);
            }
            else if (s->spriteKey().startsWith(QLatin1String("hit"))) {
                s->setSpriteKey(QStringLiteral("hit-end"));
            }
        }
    }
}

void BattleFieldView::hit(const Coord& c)
{
    removeImpact();
    m_last_hit = m_factory.createHit();
    m_last_hit->setZValue(FOREGROUND);
    m_last_hit->setOpacity(1.0);
    addSprite(c, m_last_hit);
}

void BattleFieldView::miss(const Coord& c)
{
    removeImpact();
    m_impact = m_factory.createImpact();
    m_impact->setZValue(FOREGROUND);
    m_impact->setOpacity(1.0);
    addSprite(c, m_impact);
}

void BattleFieldView::removeImpact() {
    if (m_impact) {
        m_impact->setSpriteKey(QStringLiteral("water"));
        m_impact->refresh(m_renderer);
        m_impact = nullptr;
    }
    if (m_last_hit) {
        m_last_hit->setSpriteKey(QStringLiteral("hit-after"));
        m_last_hit->refresh(m_renderer);
        m_last_hit = nullptr;
    }
}

void BattleFieldView::clear()
{
    // fixes a crash when the ships can not be placed.
    Animator::instance()->stop();
    delete m_preview.sprite;
    m_preview.sprite = nullptr;
    m_preview.ship = nullptr;
    
    m_impact = nullptr;
    m_last_hit = nullptr;

    qDeleteAll(m_sprites);
    m_sprites.clear();
}

void BattleFieldView::mousePressEvent(QMouseEvent *ev)
{
    Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));

    if (m_screen->isVisible() && button)
    {
        m_screen->onMousePress(button);
    }
    else if (ev->button() == Qt::LeftButton && m_delegate)
    {
        Coord c = m_renderer->toLogical(ev->pos());
        m_delegate->action(m_player, c);
    }
    else if (ev->button() == Qt::RightButton && m_delegate)
    {
        m_delegate->changeDirection(m_player);
        setPreview(ev->pos());
    }
}

void BattleFieldView::mouseReleaseEvent(QMouseEvent *ev)
{
    Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));

    if (m_screen->isVisible() && button && ev->button() == Qt::LeftButton) {
        m_screen->onMouseRelease(button);
    }
}

void BattleFieldView::mouseMoveEvent(QMouseEvent *ev)
{
    Button *button = dynamic_cast<Button *>(itemAt(ev->pos()));

    if (m_screen->isVisible() && button)
    {
        m_screen->onMouseMove(button);
    }
    else if (m_screen->isVisible() && !button)
    {
        m_screen->onMouseLeave();
    }
    else
    {
        cancelPreview();
        setPreview(ev->pos());
    }
}

void BattleFieldView::leaveEvent(QEvent *)
{
    if (m_screen->isVisible()) {
        m_screen->onMouseLeave();
    } else {
        cancelPreview();
    }
}

WelcomeScreen* BattleFieldView::screen() const
{
    return m_screen;
}

void BattleFieldView::setDelegate(Delegate *c)
{
    m_delegate = c;
}

void BattleFieldView::setPlayer(Sea::Player player)
{
    m_player = player;
}

const qreal BattleFieldView::PREVIEW_OPACITY = 0.7;
