/******************************************************************************
 *  Copyright (C) 2010-2016 by                                                *
 *    Ralf Lange (ralf.lange@longsoft.de)                                     *
 *                                                                            *
 *  This program 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.,                                           *
 *  59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.                 *
 ******************************************************************************/

#include "frameviewimage.h"

#include <QDebug>
#include <QPainter>
#include <QRect>
#include <QSize>

#include "domain/domainfacade.h"


// const int FrameViewImage::alphaLut[5] = { 128, 64, 43, 32, 26 };
const int FrameViewImage::alphaLut[5] = { 255, 128, 64, 32, 16 };


FrameViewImage::FrameViewImage(Frontend *f, QWidget *parent, int pS)
    : FrameViewInterface(f, parent, pS)
{
    qDebug() << "FrameViewImage::Constructor --> Start";

    this->setObjectName("FrameViewImage");

    // setAttribute(Qt::WA_PaintOnScreen);
    setBackgroundRole(QPalette::Base);
    setAutoFillBackground(true);

    this->showLogo();

    qDebug() << "FrameViewImage::Constructor --> End";
}


FrameViewImage::~FrameViewImage()
{
    qDebug() << "FrameViewImage::Destructor --> Start";

    // Turn off camera if it's on
    if (displayMode == liveImageMode) {
        cameraOff();
    }

    clearImageBuffer();

    qDebug() << "FrameViewImage::Destructor --> End";
}


void FrameViewImage::initCompleted()
{
    qDebug() << "FrameViewImage::initCompleted --> Start";

    // Fill the image buffer
    clearImageBuffer();
    loadImageBuffer();

    // Clear the display
    displayMode = emptyMode;
    repaint();

    qDebug() << "FrameViewImage::initCompleted --> End";
}


QSize FrameViewImage::sizeHint() const
{
    return QSize(400, 200);
}


QSize FrameViewImage::minimumSizeHint() const
{
    return QSize(100, 100);
}


/**************************************************************************
 * Animation notification functions
 **************************************************************************/

void FrameViewImage::updateRemoveProject()
{
    qDebug() << "FrameViewImage::updateRemoveProject --> Start";

    showLogo();

    qDebug() << "FrameViewImage::updateRemoveProject --> End";
}


void FrameViewImage::updatePlaySound(int /*sceneIndex*/ )
{
    qDebug() << "FrameViewImage::updatePlaySound --> Start";

        /*
    if (sceneIndex > -1) {
        setActiveFrame(frameNumber);
    } else {
        this->update();
    }
        */

    qDebug() << "FrameViewImage::updatePlaySound --> End";
}


/**************************************************************************
 * Scene notification functions
 **************************************************************************/

void FrameViewImage::updateRemoveScene(int)
{
    if (frontend->getProject()->getActiveSceneIndex() < 0) {
        showLogo();
        return;
    }
    if (frontend->getProject()->getActiveTakeIndex() < 0) {
        showLogo();
    }
}


/**************************************************************************
 * Take notification functions
 **************************************************************************/

void FrameViewImage::updateRemoveTake(int,
                                      int)
{
    if (frontend->getProject()->getActiveSceneIndex() < 0) {
        showLogo();
        return;
    }
    if (frontend->getProject()->getActiveTakeIndex() < 0) {
        showLogo();
    }
}


/**************************************************************************
 * Exposure notification functions
 **************************************************************************/

void FrameViewImage::updateAddExposure(int sceneIndex,
                                       int takeIndex,
                                       int exposureIndex)
{
    // qDebug() << "FrameViewImage::updateAddExposure --> Start";

    if (displayMode == liveImageMode) {
        Exposure *exposure = frontend->getProject()->getExposure(sceneIndex, takeIndex, exposureIndex);
        addToImageBuffer(QImage(exposure->getImagePath()));
    }

    // qDebug() << "FrameViewImage::updateAddExposure --> End";
}


void FrameViewImage::updateInsertExposure(int sceneIndex,
                                          int takeIndex,
                                          int exposureIndex)
{
    qDebug() << "FrameViewImage::updateInsertExposure --> Start";

    if (displayMode == liveImageMode) {
        Exposure *exposure = frontend->getProject()->getExposure(sceneIndex, takeIndex, exposureIndex);
        addToImageBuffer(QImage(exposure->getImagePath()));
    }

    qDebug() << "FrameViewImage::updateInsertExposure --> End";
}


void FrameViewImage::updateActivateExposure()
{
    qDebug() << "FrameViewImage::updateActivateExposure --> Start";

    activateExposure();

    qDebug() << "FrameViewImage::updateActivateExposure --> End";
}


void FrameViewImage::updateModifyExposure(int modSceneIndex,
                                          int modTakeIndex,
                                          int modExposureIndex)
{
    qDebug() << "FrameViewImage::updateModifyExposure --> Start";

    modifyExposure(modSceneIndex, modTakeIndex, modExposureIndex);

    qDebug() << "FrameViewImage::updateModifyExposure --> End";
}


/**************************************************************************
 * Other functions
 **************************************************************************/

void FrameViewImage::nextAnimationFrame(int exposureIndex)
{
    // qDebug() << "FrameViewImage::nextAnimationFrame --> Start");

    int activeSceneIndex = frontend->getProject()->getActiveSceneIndex();
    int activeTakeIndex = frontend->getProject()->getActiveTakeIndex();

    Q_ASSERT(activeSceneIndex >= 0);
    Q_ASSERT(activeTakeIndex >= 0);

    Exposure *exposure;
    exposure = frontend->getProject()->getExposure(activeSceneIndex, activeTakeIndex, exposureIndex);

    if (!exposure->isEmpty()) {
        QImage nextImage;
        nextImage.load(exposure->getImagePath());
        activeImage = scaleImage(nextImage);
        QPainter widgetPainter(this);
        widgetPainter.drawImage(0, 0, scaleImage(activeImage));
    }

    // Call the redraw function
    this->update();

    // qDebug() << "FrameViewImage::nextAnimationFrame --> End");
    return;
}

/**************************************************************************
 * Signals
 **************************************************************************/

/**************************************************************************
 * Slots
 **************************************************************************/

void FrameViewImage::redraw()
{
    // qDebug() << "FrameViewImage::redraw --> Start";

    const QImage liveImage = frontend->getLiveImage();
    if (liveImage.isNull()) {
        qDebug() << "FrameViewImage::redraw --> End (empty live image)";
        return;
    }

    activeImage = clipImage(liveImage);

    update();

    // qDebug() << "FrameViewImage::redraw --> End";
}


void FrameViewImage::nextPlayBack()
{
    qDebug() << "FrameViewImage::nextPlayBack --> Start";

    static int i = 0;

    // Need to check that there is an active scene before checking
    // what its size is.

    int activeSceneIndex = frontend->getProject()->getActiveSceneIndex();
    int activeTakeIndex = frontend->getProject()->getActiveTakeIndex();
    int activeExposureIndex = frontend->getProject()->getActiveExposureIndex();

    Q_ASSERT(activeSceneIndex >= 0);
    Q_ASSERT(activeTakeIndex >= 0);
    Q_ASSERT(activeExposureIndex >= 0);

    // Step 1: Draw the last mixCount images of the project
    if ((int)i < mixCount && i < (int)activeExposureIndex + 1) {

        Exposure *exposure;
        if (activeExposureIndex <= mixCount) {
            exposure = frontend->getProject()->getExposure(activeSceneIndex, activeTakeIndex, i++);
        } else {
            exposure = frontend->getProject()->getExposure(activeSceneIndex, activeTakeIndex, activeExposureIndex - mixCount + i++);
        }

        if (!exposure->isEmpty()) {
            activeImage.load(exposure->getImagePath());
            QPainter widgetPainter(this);
            widgetPainter.drawImage(0, 0, scaleImage(activeImage));
        }

        this->update();
        //Exit from function/skip redraw(). This is better than having a bool which is
        //set because this is a play function run "often".

        qDebug() << "FrameViewImage::nextPlayBack --> End (project image)";
        return;
    }

    // Step 2: Draw the live image
    const QImage liveImage = frontend->getLiveImage();
    if (liveImage.isNull()) {
        qDebug() << "FrameViewImage::nextPlayBack --> End (empty live image)";
    }
    else {
        activeImage = clipImage(liveImage);
        QPainter widgetPainter(this);
        widgetPainter.drawImage(0, 0, scaleImage(activeImage));
        this->update();
        // Restart the playback
        i = 0;
        // redraw();

        qDebug() << "FrameViewImage::nextPlayBack --> End (Live Image)";
    }
}

/**************************************************************************
 * QWidget functions
 **************************************************************************/

void FrameViewImage::resizeEvent(QResizeEvent*)
{
    qDebug() << "FrameViewImage::resizeEvent --> Start";

    frameViewWidth = width();
    frameViewHeight = height();
    if (activeImage.isNull()) {
        showLogo();
    }

    qDebug() << "FrameViewImage::resizeEvent --> End";
}


void FrameViewImage::paintEvent(QPaintEvent *)
{
    // qDebug() << "FrameViewImage::paintEvent --> Start";

    // QImage   outputImage = activeImage;

    QPainter widgetPainter(this);
    QColor   gridColor;
    QRect    widgetRect(this->rect());

    widgetPainter.setRenderHint(QPainter::Antialiasing, true);

    QStyleOption opt;
    opt.init(this);
    QPainter p(this);
    style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);

    if (displayMode == liveImageMode) {
        // Playing live video

        QPainter imagePainter(&activeImage);
        int offset;

        switch (mixMode) {
        case DomainFacade::mixImageMode:
            // Image mixing

            // Set the composite mode for the images in the buffer
            imagePainter.setCompositionMode(QPainter::CompositionMode_Overlay);

            offset = imageBuffer.count() - 1;
            for (int i = 0; i < imageBuffer.count() && i < mixCount; ++i) {
                QImage image(imageBuffer[offset - i]);
                // QImage alphaImage(this->createAlphaImage(image));
                QImage alphaImage(this->createGrayAlphaImage(image));
                imagePainter.drawImage(0, 0, scaleImage(alphaImage));
            }
            break;
        case DomainFacade::diffImageMode:
            // Image differentiating

            // Set the composite mode for the images in the buffer
            imagePainter.setCompositionMode(QPainter::CompositionMode_Difference);

            if (imageBuffer.count() > 0) {
                QImage image(imageBuffer.last());
                // QImage diffImage(this->createDifferentiatedImage(activeImage, image));
                // imagePainter.eraseRect(activeImage.rect());
                // imagePainter.drawImage(0, 0, scaleImage(diffImage));
                QImage alphaImage(this->createGrayAlphaImage(image));
                imagePainter.drawImage(0, 0, scaleImage(alphaImage));
            }
            break;
        }
    }

    switch (displayMode) {
    case emptyMode:
        // Display nothing
        widgetPainter.fillRect(widgetRect, QColor(225, 225, 225, 255));
        break;
    case logoMode:
        // Fill the start screen
        widgetPainter.fillRect(widgetRect, QColor(225, 225, 225, 255));
    default:
        // Display the actual image
        QImage   outputImage = scaleImage(activeImage);
        QSize    outputImageSize(outputImage.size());
        int x = (widgetRect.width() - outputImageSize.width()) / 2;
        int y = (widgetRect.height() - outputImageSize.height()) / 2;
        widgetPainter.drawImage(x, y, outputImage);
    }

    switch (displayMode) {
    case liveImageMode:
    case playbackMode:
        int r, g, b, a;
        frontend->getGridColorRGB(&r, &g, &b, &a);
        gridColor.setRgb(r, g, b, a);
        if (frontend->getVerticalGrid()) {
            // Draw a vertical grid on top of the image

            int verticalLines = frontend->getVerticalSpin();
            int vericalDistance = widgetRect.width() / (verticalLines + 1);
            int x1, y1, x2, y2;

            x1 = y1 = 0;
            y2 = widgetRect.height();

            widgetPainter.setPen(gridColor);

            for (int v = 0 ; v < verticalLines ; v++) {
                x1 += vericalDistance;
                x2 = x1;
                widgetPainter.drawLine(x1, y1, x2, y2);
            }
        }

        if (frontend->getHorizontalGrid()) {
            // Draw a horizontal grid on top of the image

            int horizontalLines = frontend->getHorizontalSpin();
            int horizontalDistance = widgetRect.height() / (horizontalLines + 1);
            int x1, y1, x2, y2;

            x1 = y1 = 0;
            x2 = widgetRect.width();

            widgetPainter.setPen(gridColor);

            for (int h = 0 ; h < horizontalLines ; h++) {
                y1 += horizontalDistance;
                y2 = y1;
                widgetPainter.drawLine(x1, y1, x2, y2);
            }
        }

        break;
    }

    widgetPainter.setRenderHint(QPainter::Antialiasing, false);

    // qDebug() << "FrameViewImage::paintEvent --> End";
}


/**************************************************************************
 * Private functions
 **************************************************************************/

void FrameViewImage::activateExposure()
{
    qDebug() << "FrameViewImage::activateExposure --> Start";

    Exposure *exposure = NULL;

    switch (displayMode) {
    case logoMode:
        displayMode = stillImageMode;
    case stillImageMode:
        exposure = frontend->getProject()->getActiveExposure();

        // Q_ASSERT(exposure != NULL);

        if (exposure != NULL) {
            const QString fileName = exposure->getImagePath();
            activeImage = QImage(fileName);
        } else {
            showLogo();
        }
        update();
        break;
    case liveImageMode:
        break;
    case playbackMode:
        break;
    }

    qDebug() << "FrameViewImage::activateExposure --> End";
}


void FrameViewImage::modifyExposure(int modSceneIndex,
                                    int modTakeIndex,
                                    int modExposureIndex)
{
    qDebug() << "FrameViewImage::modifyExposure --> Start";

    int activeSceneIndex = frontend->getProject()->getActiveSceneIndex();
    if (activeSceneIndex != modSceneIndex) {
        // The scene of the modifyed exposure is not displayed in the frame view
        qDebug() << "FrameViewImage::modifyExposure --> End (Nothing)";
        return;
    }

    int activeTakeIndex = frontend->getProject()->getActiveTakeIndex();
    if (activeTakeIndex != modTakeIndex) {
        // The take of the modifyed exposure is not displayed in the frame view
        qDebug() << "FrameViewImage::modifyExposure --> End (Nothing)";
        return;
    }

    int activeExposureIndex = frontend->getProject()->getActiveSceneIndex();
    if (activeExposureIndex != modExposureIndex) {
        // The modifyed exposure is not displayed in the frame view
        qDebug() << "FrameViewImage::modifyExposure --> End (Nothing)";
        return;
    }

    Exposure *exposure = frontend->getProject()->getActiveExposure();
    if (exposure != NULL) {
        const QString fileName = exposure->getImagePath();
        activeImage.load(fileName);
    } else {
        this->clearImageBuffer();
        this->showLogo();
    }
    this->update();

    qDebug() << "FrameViewImage::modifyExposure --> End";
}


void FrameViewImage::addToImageBuffer(QImage const image)
{
    qDebug() << "FrameViewImage::addToImageBuffer --> Start";

    imageBuffer.append(image);

    qDebug() << "FrameViewImage::addToImageBuffer --> End";
}


void FrameViewImage::loadImageBuffer()
{
    qDebug() << "FrameViewImage::loadImageBuffer --> Start";

    int activeExposure = frontend->getProject()->getActiveExposureIndex();
    int exposureIndex = activeExposure - this->mixCount + 1;

    if (exposureIndex < 0) {
        exposureIndex = 0;
    }

    for ( ; exposureIndex <= activeExposure ; exposureIndex++ ) {
        Exposure *exposure = frontend->getProject()->getExposure(frontend->getProject()->getActiveSceneIndex(),
                                                                 frontend->getProject()->getActiveTakeIndex(),
                                                                 exposureIndex);
        addToImageBuffer(QImage(exposure->getImagePath()));
    }

    qDebug() << "FrameViewImage::loadImageBuffer --> End";
}


void FrameViewImage::clearImageBuffer()
{
    qDebug() << "FrameViewImage::clearImageBuffer --> Start";

    imageBuffer.clear();

    qDebug() << "FrameViewImage::clearImageBuffer --> End";
}


const QImage FrameViewImage::createDifferentiatedImage(QImage &image1, QImage &image2)
{
    qDebug() << "FrameViewImage::createDifferentiatedImage --> Start";

    int imageRows = image2.height();
    int imageCols = image2.width();
    QImage diffImage(imageCols, imageRows, QImage::Format_ARGB32);
    QColor diffColor;
    QColor image1Color;
    QColor image2Color;

    // Goes through the surfaces as one-dimensional arrays.
    for (int r = 0; r < imageRows; ++r) {
        for (int c = 0; c < imageCols; ++c) {
            image1Color = image1.pixel(c, r);
            image2Color = image2.pixel(c, r);
            diffColor.setRed(abs(image1Color.red() - image2Color.red()));
            diffColor.setGreen(abs(image1Color.green() - image2Color.green()));
            diffColor.setBlue(abs(image1Color.blue() - image2Color.blue()));
            diffColor.setAlpha(image1Color.alpha());
            diffImage.setPixel(c, r, diffColor.rgba());
        }
    }

    qDebug() << "FrameViewImage::createDifferentiatedImage --> End";

    return diffImage;
}


const QImage FrameViewImage::createAlphaImage(QImage &image)
{
    // qDebug() << "FrameViewImage::createAlphaImage --> Start";

    int    imageRows = image.height();
    int    imageCols = image.width();
    int    overlayIntensity = frontend->getProject()->getOverlayIntensity();
    QImage alphaImage(image.size(), QImage::Format_ARGB32);
    QColor alphaColor;
    int    overlayAlpha = 255 - overlayIntensity;

    // We assume the format to be RGB32!!!
    Q_ASSERT(image.format() == QImage::Format_RGB32);

    // Goes through the surfaces as one-dimensional arrays.
    for (int r = 0; r < imageRows; ++r) {
        for (int c = 0; c < imageCols; ++c) {
            alphaColor = image.pixel(c, r);
            alphaColor.setAlpha(overlayAlpha);
            // Qt < 5.6 version
            alphaImage.setPixel(c, r, alphaColor.rgba());
            // Qt >= 5.6 version
            // alphaImage.setPixelColor(c, r, alphaColor);
        }
    }

    // qDebug() << "FrameViewImage::createAlphaImage --> End";

    return alphaImage;
}


const QImage FrameViewImage::createGrayAlphaImage(QImage &image)
{
    // qDebug() << "FrameViewImage::createGrayAlphaImage --> Start";

    int    imageRows = image.height();
    int    imageCols = image.width();
    int    overlayIntensity = frontend->getProject()->getOverlayIntensity();
    QImage alphaImage(image.size(), QImage::Format_ARGB32);
    QColor alphaColor;
    QRgb   grayRgba;
    // QColor grayColor;
    int    gray;
    int    overlayAlpha = 255 - overlayIntensity;

    // We assume the format to be RGB32!!!
    Q_ASSERT(image.format() == QImage::Format_RGB32);

    // Goes through the surfaces as one-dimensional arrays.
    for (int r = 0; r < imageRows; ++r) {
        for (int c = 0; c < imageCols; ++c) {
            alphaColor = image.pixel(c, r);
            gray = qGray(alphaColor.rgb());
            // Qt < 5.6
            grayRgba = qRgba(gray, gray, gray, overlayAlpha);
            alphaImage.setPixel(c, r, grayRgba);
            // Qt >= 5.6
            // grayColor.setRgba(qRgba(gray, gray, gray, overlayAlpha));
            // alphaImage.setPixelColor(c, r, grayColor);
        }
    }

    // qDebug() << "FrameViewImage::createGrayAlphaImage --> End";

    return alphaImage;
}


void FrameViewImage::showLogo()
{
    qDebug() << "FrameViewImage::showLogo --> Start";

    QString iconFile(frontend->getGraphicsDirName());
    iconFile.append(QLatin1String("qstopmotion_logo_120.png"));

    activeImage = scaleImage(QImage(iconFile));

    displayMode = logoMode;
    update();

    qDebug() << "FrameViewImage::showLogo --> End";
}
