/**
 * This file is a part of LuminanceHDR package.
 * ----------------------------------------------------------------------
 * Copyright (C) 2006,2007 Giuseppe Rota
 *
 *  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
 * ----------------------------------------------------------------------
 *
 * Original Work
 * @author Giuseppe Rota <grota@users.sourceforge.net>
 * Improvements, bugfixing
 * @author Franco Comida <fcomida@users.sourceforge.net>
 *
 */

#include "HdrViewer.h"

#include <QFileInfo>
#include <QDebug>

#include <cmath>
#include <cassert>
#include "arch/math.h"

#include "Common/global.h"


#include "Viewers/IGraphicsPixmapItem.h"
#include "Viewers/LuminanceRangeWidget.h"

#include "Libpfs/array2d.h"
#include "Libpfs/channel.h"
#include "Libpfs/frame.h"
#include "Libpfs/utils/sse.h"
#include "Libpfs/utils/msec_timer.h"
#include "Libpfs/colorspace/rgbremapper.h"

namespace // anonymous namespace
{
// Implementation details stay inside an anonymous namespace
// In this way, we let know the compiler it can mess up as much as it wants with the code,
// because it will only used inside this compilation unit

const pfs::Array2Df* getPrimaryChannel(const pfs::Frame& frame)
{
    return frame.getChannel("Y");
}

} // end anonymous namespace

HdrViewer::HdrViewer(pfs::Frame* frame, QWidget *parent, bool ns,
                     unsigned int neg, unsigned int naninf)
    : GenericViewer(frame, parent, ns)
    , m_mappingMethod(MAP_GAMMA2_2)
    , m_minValue(0.f)
    , m_maxValue(1.f)
    , m_nanInfColor(naninf)
    , m_negColor(neg)
{
    initUi();

    // I prefer to do everything by hand, so the flow of the calls is clear
    m_lumRange->blockSignals(true);

    m_lumRange->setHistogramImage(getPrimaryChannel(*getFrame()));
    m_lumRange->fitToDynamicRange();

    m_mappingMethod = static_cast<RGBMappingType>( m_mappingMethodCB->currentIndex() );
    m_minValue = powf( 10.0f, m_lumRange->getRangeWindowMin() );
    m_maxValue = powf( 10.0f, m_lumRange->getRangeWindowMax() );

    mPixmap->setPixmap(QPixmap::fromImage(mapFrameToImage(getFrame())));

    updateView();
    m_lumRange->blockSignals(false);
}

void HdrViewer::initUi()
{
    m_mappingMethodLabel = new QLabel( mToolBar );
    m_mappingMethodLabel->setAlignment( Qt::AlignRight | Qt::AlignVCenter );
    m_mappingMethodCB = new QComboBox( mToolBar );
    mToolBar->addWidget(m_mappingMethodLabel);
    mToolBar->addWidget(m_mappingMethodCB);
    mToolBar->addSeparator();
    m_mappingMethodLabel->setBuddy( m_mappingMethodCB );

    m_histLabel = new QLabel( mToolBar );
    m_lumRange = new LuminanceRangeWidget( mToolBar );
    mToolBar->addWidget(m_histLabel);
    mToolBar->addWidget(m_lumRange);
    mToolBar->addSeparator();
    connect( m_lumRange, SIGNAL( updateRangeWindow() ), this, SLOT( updateRangeWindow() ) );
    mToolBar->setSizePolicy(QSizePolicy::Preferred,QSizePolicy::Fixed);

    retranslateUi();
}

void HdrViewer::retranslateUi()
{
    m_mappingMethodLabel->setText(tr("&Mapping:"));
    m_histLabel->setText(tr("Histogram:"));

    int oldMappingMethodIndex = m_mappingMethodCB->currentIndex();

    disconnect( m_mappingMethodCB, SIGNAL( activated( int ) ), this, SLOT( setLumMappingMethod(int) ) );
	QStringList methods;
	methods << tr("Linear")
			<< tr("Gamma 1.4")
			<< tr("Gamma 1.8")
			<< tr("Gamma 2.2")
			<< tr("Gamma 2.6")
			<< tr("Logarithmic");

    m_mappingMethodCB->clear();
    m_mappingMethodCB->addItems(methods);
    m_mappingMethodCB->setCurrentIndex( oldMappingMethodIndex >= 0 ? oldMappingMethodIndex : 3 );
    connect( m_mappingMethodCB, SIGNAL( activated( int ) ), this, SLOT( setLumMappingMethod(int) ) );

	GenericViewer::retranslateUi();
}

void HdrViewer::refreshPixmap()
{
#ifdef QT_DEBUG
    qDebug() << "void HdrViewer::refreshPixmap()";
#endif

    setCursor( Qt::WaitCursor );

    mPixmap->setPixmap(QPixmap::fromImage(mapFrameToImage(getFrame())));

    unsetCursor();
}

void HdrViewer::updatePixmap()
{
#ifdef QT_DEBUG
    qDebug() << "void HdrViewer::updatePixmap()";
#endif

    m_lumRange->blockSignals(true);

    refreshPixmap();

    // I need to set the histogram again during the setFrame function
    m_lumRange->setHistogramImage(getPrimaryChannel(*getFrame()));
    m_lumRange->fitToDynamicRange();
    m_lumRange->blockSignals(false);
}

LuminanceRangeWidget* HdrViewer::lumRange()
{
    return m_lumRange;
}

void HdrViewer::updateRangeWindow()
{
    setRangeWindow(powf(10.0f, m_lumRange->getRangeWindowMin()),
                   powf(10.0f, m_lumRange->getRangeWindowMax()));
}

void HdrViewer::setRangeWindow( float min, float max )
{
    m_minValue = min;
    m_maxValue = max;

    refreshPixmap();
}

int HdrViewer::getLumMappingMethod()
{
    return m_mappingMethodCB->currentIndex();
}

void HdrViewer::setLumMappingMethod( int method )
{
    m_mappingMethodCB->setCurrentIndex( method );
    m_mappingMethod = static_cast<RGBMappingType>(method);

    refreshPixmap();
}

void HdrViewer::update_colors(int neg, int naninf)
{
    m_nanInfColor = naninf;
    m_negColor = neg;

    refreshPixmap();
}

//! empty dtor
HdrViewer::~HdrViewer()
{}

QString HdrViewer::getFileNamePostFix()
{
    return QString("_hdr_preview");
}

QString HdrViewer::getExifComment()
{
    return QString("HDR Created with Luminance HDR");
}

//! \brief returns max value of the handled frame
float HdrViewer::getMaxLuminanceValue()
{
    return m_maxValue;
}

//! \brief returns min value of the handled frame
float HdrViewer::getMinLuminanceValue()
{
    return m_minValue;
}

RGBMappingType HdrViewer::getLuminanceMappingMethod()
{
    return m_mappingMethod;
}

QImage HdrViewer::mapFrameToImage(pfs::Frame* in_frame)
{
#ifdef TIMER_PROFILING
    msec_timer stop_watch;
    stop_watch.start();
#endif

    // Channels X Y Z contain R G B data
    pfs::Channel *ChR, *ChG, *ChB;
    in_frame->getXYZChannels( ChR, ChG, ChB );
    assert( ChR != NULL && ChG != NULL && ChB != NULL);

    const float* R = ChR->data();
    const float* G = ChG->data();
    const float* B = ChB->data();

    assert( R != NULL && G != NULL && B != NULL );

    QImage return_qimage(in_frame->getWidth(), in_frame->getHeight(), QImage::Format_RGB32);
    QRgb *pixels = reinterpret_cast<QRgb*>(return_qimage.bits());

    RGBRemapper rgbRemapper(m_minValue, m_maxValue, m_mappingMethod);

    int indexEnd = in_frame->getWidth()*in_frame->getHeight();
#pragma omp parallel for
    for ( int index = 0; index < indexEnd; ++index )
    {
        if ( !finite( R[index] ) || !finite( G[index] ) || !finite( B[index] ) )   // x is NaN or Inf
        {
            pixels[index] = m_nanInfColor;
        }
        else if ( R[index] < 0 || G[index]<0 || B[index]<0 )    // x is negative
        {
            pixels[index] = m_negColor;
        }
        else
        {
            rgbRemapper.toQRgb(R[index], G[index], B[index], pixels[index]);
        }
    }

#ifdef TIMER_PROFILING
    stop_watch.stop_and_update();
    std::cout << "HdrViewer::mapFrameToImage() [NEW]= " << stop_watch.get_time() << " msec" << std::endl;
#endif

    return return_qimage;
}
