#include "winter.h"

#include <QDebug>
#include <QGraphicsScene>
#include <QGraphicsView>
#include <QPen>
#include <QRandomGenerator>

#include "misc.h"

SnowFlake::SnowFlake(QGraphicsScene *scene, SnowType t)
{
    type = t;

    size = QRandomGenerator::global()->bounded(10) + 3;
    QPen p(Qt::white);
    dv = QPointF(
            QRandomGenerator::global()->bounded(10) / 10.0 - 0.5,
            QRandomGenerator::global()->bounded(10) / 10.0 + 1);

    switch (type) {
        case Smilla: {
            int s4 = size / 4;
            int s3 = size / 3;
            int s6 = size / 6;

            for (int a = 0; a < 6; a++) {
                lines.append(scene->addLine(0, -s6, 0, -size));
                lines.last()->setRotation(a * 60);

                lines.append(scene->addLine(-s4, -size + s6, 0, -size + s3));
                lines.last()->setRotation(a * 60);

                lines.append(scene->addLine(s4, -size + s6, 0, -size + s3));
                lines.last()->setRotation(a * 60);
            }

            foreach (QGraphicsLineItem *l, lines) {
                l->setZValue(1000);
                l->setPen(p);
                l->setParentItem(this);
            }
            da = QRandomGenerator::global()->bounded(20) / 10.0 - 1;
        }
            setRotation(QRandomGenerator::global()->bounded(60));
            break;
        case Disc:
            disc = scene->addEllipse(0, 0, size, size, p);
            disc->setParentItem(this);
            disc->setBrush(Qt::white);
            break;
        case Egg:
            disc = scene->addEllipse(0, 0, size, size * 1.5, p);
            disc->setParentItem(this);
            disc->setBrush(QColor(
                QRandomGenerator::global()->bounded(100) + 150,
                QRandomGenerator::global()->bounded(100) + 150,
                QRandomGenerator::global()->bounded(100) + 150, 255));
            break;
        default:
            break;
    }
}

SnowFlake::~SnowFlake()
{
    // qDebug()<<"Destr. SnowFlake";
    switch (type) {
        case (Smilla):
            while (lines.isEmpty())
                delete lines.takeFirst();
            break;
        case Egg:
            delete disc;
            break;
        case Disc:
            delete disc;
            break;
        default:
            break;
    }
}

QRectF SnowFlake::boundingRect() const
{
    return QRectF(-size, -size, size * 2, size * 2);
}

void SnowFlake::paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *)
{
}

void SnowFlake::animate()
{
    moveBy(dv.x() + dblow.x(), dv.y() + dblow.y());

    if (type == SnowFlake::Smilla)
        setRotation(rotation() + da);

    dblow = dblow * 0.9;
}

void SnowFlake::blow(const QPointF &v) { dblow = v; }

Winter::Winter(QGraphicsView *v)
{
    view = v;

    updateView();
    /*
    test = view->scene()->addRect(
            QRectF(viewRect.topLeft(), viewRect.bottomRight()),
            QPen(Qt::blue) );
    */

    type = SnowFlake::Egg;

    switch (type) {
        case SnowFlake::Smilla:
            maxFlakes = 1500;
            maxFalling = 140;
            maxUnfreeze = 50;
            break;
        case SnowFlake::Egg:
            maxFlakes = 500;
            maxFalling = 150;
            maxUnfreeze = 50;
            break;
        default:
            maxFlakes = 6500;
            maxFalling = 850;
            maxUnfreeze = 50;
            break;
    }

    animTimer = new QTimer;
    connect(animTimer, SIGNAL(timeout()), this, SLOT(animate()));
    animTimer->start(50);

    snowTimer = new QTimer;
    connect(snowTimer, SIGNAL(timeout()), this, SLOT(makeSnow()));
    //snowTimer->setSingleShot(true);

    makeSnow();
}

Winter::~Winter()
{
    delete animTimer;
    delete snowTimer;
    while (!fallingSnow.isEmpty())
        delete fallingSnow.takeFirst();
    while (!frozenSnow.isEmpty())
        delete frozenSnow.takeFirst();
}

void Winter::updateView()
{
    QPointF topLeft = view->mapToScene(0, 0);
    QPointF topRight = view->mapToScene(view->rect().width(), 0);
    QPointF botLeft = view->mapToScene(0, view->rect().height());
    QPointF botRight =
        view->mapToScene(view->rect().width(), view->rect().height());

    QPointF p0;
    QPointF p1;

    topLeft.y() < topRight.y() ? p0.setY(topLeft.y()) : p0.setY(topRight.y());
    topLeft.x() < topRight.x() ? p0.setX(topLeft.x()) : p0.setX(topRight.x());

    botLeft.y() > botRight.y() ? p1.setY(botLeft.y()) : p1.setY(botRight.y());
    botLeft.x() > botRight.x() ? p1.setX(botLeft.x()) : p1.setX(botRight.x());

    viewRect = QRectF(p0, p1);
}

void Winter::setObstacles(QList<QRectF> obslist)
{
    obstacles = obslist;

    QList<SnowFlake *> unfreeze;

    // Find frozen snowflakes, which are free again
    QPointF p;
    int i = 0;
    bool frozen;
    while (i < frozenSnow.count()) {
        p = frozenSnow.at(i)->pos();
        frozen = false;

        int j = 0;
        while (j < obstacles.count() && !frozen)

        {
            if (obstacles.at(j).contains(p))
                frozen = true;
            j++;
        }
        if (!frozen) {
            unfreeze.append(frozenSnow.at(i));
            frozenSnow.removeAt(i);
        }
        else
            i++;
    }

    // Remove some flakes, if too many
    while (fallingSnow.count() + unfreeze.count() > maxFalling + maxUnfreeze)
        delete unfreeze.takeFirst();

    while (!unfreeze.isEmpty()) {
        // Blow a bit up
        unfreeze.first()->blow(
            QPointF(
                QRandomGenerator::global()->bounded(10) / 10.0 - 0.5,
                QRandomGenerator::global()->bounded(10) / 10.0 - 5));
        fallingSnow.append(unfreeze.takeFirst());
    }
}

void Winter::animate()  // FIXME-3   z-order and releasing (shake branch)  not working
{
    // test->setRect(QRectF(viewRect.topLeft(), viewRect.bottomRight()));

    QPointF p;
    int i = 0;
    bool cont;
    while (i < fallingSnow.count()) {
        p = fallingSnow.at(i)->pos();
        cont = true;

        int j = 0;
        while (j < obstacles.count() && cont) {
            if (obstacles.at(j).contains(p) &&
                QRandomGenerator::global()->bounded(obstacles.count() + 1) > obstacles.count() - 1) {
                // Freeze snowflake on obstacle
                // Probality is equale for obstacles or falling through
                frozenSnow.append(fallingSnow.at(i));
                fallingSnow.removeAt(i);
                cont = false;
            }
            j++;
        }
        if (cont && p.y() > viewRect.bottomRight().y() + 20)

        {
            delete fallingSnow.takeAt(i);
            cont = false;
        }
        // Let snowflake fall further
        if (cont)
            fallingSnow.at(i)->animate();
        i++;
    }
}

void Winter::makeSnow()
{
    // qDebug()<<"falling: "<<fallingSnow.count()<<"  frozen:
    // "<<frozenSnow.count();
    if (fallingSnow.count() + frozenSnow.count() < maxFlakes) {
        if (fallingSnow.count() < maxFalling) {
            // Create more snowflakes
            SnowFlake *snowflake;
            for (int i = 0; i < 10; i++) {
                snowflake = new SnowFlake(view->scene(), type);
                view->scene()->addItem(snowflake);
                snowflake->setPos(rand() % round_int(viewRect.width()) +
                                      viewRect.x(),
                                  viewRect.y() - 20);
                fallingSnow.append(snowflake);
            }
        }
    }
    else {
        // Remove some of the existing frozen flakes
        for (int i = 0; i < 10; i++) {
            if (frozenSnow.count() > 0) {
                int j = QRandomGenerator::global()->bounded(frozenSnow.count());
                delete frozenSnow.takeAt(j);
            }
        }
    }
    snowTimer->start(2000);
}
