/*
 * Copyright (C) 2008 Instituto Nokia de Tecnologia. All rights reserved.
 *
 * This file is part of QZion.
 *
 * QZion 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.
 *
 * QZion 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 QZion.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * This file incorporates work covered by the following copyright and
 * permission notice:
 *
 *  Copyright 2006 Maurizio Monge <maurizio.monge@gmail.com>
 *
 *  BSD License
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions
 *  are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 *  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 *  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 *  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Attention: note that even being named "BSD License" by the
 * copyright holder, the license above is a variation of the license
 * known as BSD License.  This variation is compatible with GNU GPL.
 */

#include <QDebug>
#include <QRegion>
#include <QPainter>
#include <QPaintEvent>
#include <QWheelEvent>
#include <QApplication>

#include "qzioncanvas.h"
#include "qzionabstractcanvas.h"
#include "qzionabstractcanvas_p.h"

/*!
    \class QZionCanvasPrivate
    \brief Private class for QZionCanvas.
*/
class QZionCanvasPrivate : public QWidget
{
    Q_DECLARE_PUBLIC(QZionCanvas)

public:
    QZionCanvasPrivate(QWidget *parent = 0);

    bool pendingUpdate;
    QRegion pendingUpdateRegion;

#ifdef QZION_DEBUG_DIRTY
    bool debugPaints;
#endif

    QZionCanvas *q_ptr;

protected:
    virtual void resizeEvent(QResizeEvent *event);
    virtual void mouseMoveEvent(QMouseEvent *e);
    virtual void mousePressEvent(QMouseEvent *e);
    virtual void mouseReleaseEvent(QMouseEvent *e);
    virtual void wheelEvent(QWheelEvent *event);
    virtual void paintEvent(QPaintEvent *event);
    virtual void keyPressEvent(QKeyEvent *event);
    virtual void keyReleaseEvent(QKeyEvent *event);
};


QZionCanvasPrivate::QZionCanvasPrivate(QWidget *parent)
    : QWidget(parent)
{
    setMouseTracking(true);
    setAttribute(Qt::WA_NoSystemBackground, true);
}

void QZionCanvasPrivate::resizeEvent(QResizeEvent *event)
{
    QWidget::resizeEvent(event);

    QZionAbstractCanvasPrivate *da = q_ptr->_QZionAbstractCanvas_data;
    foreach (QZionObject *obj, da->items) {
        obj->canvasResizeEvent(event);
    }
}

void QZionCanvasPrivate::keyPressEvent(QKeyEvent *event)
{
    q_ptr->keyPressEvent(event);
}

void QZionCanvasPrivate::keyReleaseEvent(QKeyEvent *event)
{
    q_ptr->keyReleaseEvent(event);
}

/*!
  Repass QWidget event to QZion event structure.
*/
void QZionCanvasPrivate::mouseMoveEvent(QMouseEvent *e)
{
    q_ptr->mouseMoveEvent(e);
}

/*!
  Repass QWidget event to QZion event structure.
*/
void QZionCanvasPrivate::mousePressEvent(QMouseEvent *e)
{
    q_ptr->mousePressEvent(e);
}

/*!
  Repass QWidget event to QZion event structure.
*/
void QZionCanvasPrivate::mouseReleaseEvent(QMouseEvent *e)
{
    q_ptr->mouseReleaseEvent(e);
}

void QZionCanvasPrivate::wheelEvent(QWheelEvent *event)
{
    q_ptr->wheelEvent(event);
}

void QZionCanvasPrivate::paintEvent(QPaintEvent *event)
{
#ifdef QZION_DEBUG_DIRTY
    if (debugPaints) {
        QPainter p(this);
        p.fillRect(event->rect(), Qt::magenta);
        return;
    }
#endif

    QPainter p(this);

#ifndef QZION_LOW_QUALITY
    p.setRenderHints(QPainter::Antialiasing |
                     QPainter::SmoothPixmapTransform);
#endif

    QRect eventRect = event->rect();
    QRegion eventRegion = event->region();

    QZionAbstractCanvasPrivate *da = q_ptr->_QZionAbstractCanvas_data;
    foreach (QZionObject *obj, da->items) {
        QRect objRect = obj->rect();

        if (obj->visible() && eventRect.intersects(objRect)
            && eventRegion.contains(objRect)) {
            obj->paintInternal(&p, eventRect, eventRegion, QPoint(), 1.0);
        }
    }

    q_ptr->paintEvent(event);
}


QZionCanvas::QZionCanvas(QWidget *parent)
    : QObject(), _QZionCanvas_data(new QZionCanvasPrivate(parent))
{
    QZD(QZionCanvas);
    d->q_ptr = this;
    d->pendingUpdate = false;
}

QZionCanvas::~QZionCanvas()
{
    QZD(QZionCanvas);
    QZDP(QZionAbstractCanvas, da);
    foreach (QZionObject *obj, da->items) {
        delete obj;
    }
    delete d;
}

void QZionCanvas::ensurePendingUpdate()
{
    QZD(QZionCanvas);
    if (!d->pendingUpdate) {
        d->pendingUpdate = true;
        QTimer::singleShot(0, this, SLOT(updateChanges()));
    }
}

void QZionCanvas::updateChanges()
{
    QZD(QZionCanvas);
    QZDP(QZionAbstractCanvas, da);
    foreach (QZionObject *obj, da->items) {
        if (obj->_changed)
            obj->updateChanges();
    }
    d->pendingUpdate = false;

    if (d->pendingUpdateRegion.isEmpty())
        return;

#ifdef QZION_DEBUG_DIRTY
    d->repaint();
    d->debugPaints = true;
    d->repaint(d->pendingUpdateRegion);
    QApplication::syncX();
    d->debugPaints = false;
    usleep(100000);
    d->repaint(d->pendingUpdateRegion);
    QApplication::syncX();
    usleep(100000);
#else
    d->update(d->pendingUpdateRegion);
#endif

    emit updated(d->pendingUpdateRegion);

    d->pendingUpdateRegion = QRegion();
}

void QZionCanvas::invalidate(const QRect &r, bool /*translate*/)
{
    QZD(QZionCanvas);
    d->pendingUpdateRegion |= r;
    ensurePendingUpdate();
}

void QZionCanvas::invalidate(const QRegion &r, bool /*translate*/)
{
    QZD(QZionCanvas);
    d->pendingUpdateRegion |= r;
    ensurePendingUpdate();
}

QSize QZionCanvas::size() const
{
    QZD(QZionCanvas);
    return d->size();
}

void QZionCanvas::setSize(const QSize &size)
{
    QZD(QZionCanvas);
    d->resize(size);
}

QWidget *QZionCanvas::widget()
{
    return _QZionCanvas_data;
}

void QZionCanvas::keyPressEvent(QKeyEvent *)
{

}

void QZionCanvas::keyReleaseEvent(QKeyEvent *)
{

}

void QZionCanvas::paintEvent(QPaintEvent *)
{

}
