/* ============================================================
 *
 * This file is a part of kipi-plugins project
 * http://www.kipi-plugins.org
 *
 * Date        : 2006-12-09
 * Description : a tread-safe dcraw program interface
 *
 * Copyright (C) 2006-2009 by Gilles Caulier <caulier dot gilles at gmail dot com> 
 * Copyright (C) 2006-2009 by Marcel Wiesweg <marcel dot wiesweg at gmx dot de>
 * Copyright (C) 2007-2008 by Guillaume Castagnino <casta at xwing dot info>
 *
 * NOTE: Do not use kdDebug() in this implementation because 
 *       it will be multithreaded. Use tqDebug() instead. 
 *       See B.K.O #133026 for details.
 *
 * 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, 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.
 *
 * ============================================================ */

// TQt includes.

#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqstringlist.h>

// LibRaw includes.

#include "libraw/libraw_version.h"

// Local includes.

#include "version.h"
#include "rawfiles.h"
#include "kdcrawprivate.h"
#include "kdcraw.h"
#include "kdcraw.moc"

namespace KDcrawIface
{

KDcraw::KDcraw()
{
    d = new KDcrawPriv(this);
    m_cancel = false;
}

KDcraw::~KDcraw()
{
    cancel();
    delete d;
}

TQString KDcraw::version()
{
    return TQString(kdcraw_version);
}

void KDcraw::cancel()
{
    m_cancel = true;
}

bool KDcraw::loadDcrawPreview(TQImage& image, const TQString& path)
{
    // In first, try to extrcat the embedded JPEG preview. Very fast.
    bool ret = loadEmbeddedPreview(image, path);
    if (ret) return true;

    // In second, decode and half size of RAW picture. More slow.
    return (loadHalfPreview(image, path));
}

bool KDcraw::loadEmbeddedPreview(TQImage& image, const TQString& path)
{
    TQByteArray imgData;

    if ( loadEmbeddedPreview(imgData, path) )
    {
        tqDebug("Preview data size: %i", imgData.size());

        if (image.loadFromData( imgData ))
        {
            tqDebug("Using embedded RAW preview extraction");
            return true;
        }
    }

    tqDebug("Failed to load embedded RAW preview");
    return false;
}

bool KDcraw::loadEmbeddedPreview(TQByteArray& imgData, const TQString& path)
{
    TQFileInfo fileInfo(path);
    TQString   rawFilesExt(rawFiles());
    TQString ext = fileInfo.extension(false).upper();

    if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
        return false;

    LibRaw raw;

    int ret = raw.open_file((const char*)(TQFile::encodeName(path)));
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run open_file: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    ret = raw.unpack_thumb();
    if (ret != LIBRAW_SUCCESS)
    {
        raw.recycle();
        tqDebug("LibRaw: failed to run unpack_thumb: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    libraw_processed_image_t *thumb = raw.dcraw_make_mem_thumb(&ret);
    if(!thumb)
    {
        tqDebug("LibRaw: failed to run dcraw_make_mem_thumb: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if(thumb->type == LIBRAW_IMAGE_BITMAP)
    {
        KDcrawPriv::createPPMHeader(imgData, thumb);
    }
    else
    {
        imgData.resize((int)thumb->data_size);
        memcpy(imgData.data(), (const char*)thumb->data, (int)thumb->data_size);
    }

    free(thumb);
    raw.recycle();

    if ( imgData.isEmpty() )
    {
        tqDebug("Failed to load JPEG thumb from LibRaw!");
        return false;
    }

    return true;
}

bool KDcraw::loadHalfPreview(TQImage& image, const TQString& path)
{
    TQFileInfo fileInfo(path);
    TQString   rawFilesExt(rawFiles());
    TQString   ext = fileInfo.extension(false).upper();

    if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
        return false;

    tqDebug("Try to use reduced RAW picture extraction");

    LibRaw raw;
    raw.imgdata.params.use_auto_wb    = 1; // Use automatic white balance.
    raw.imgdata.params.use_camera_wb  = 1; // Use camera white balance, if possible.
    raw.imgdata.params.half_size      = 1; // Half-size color image (3x faster than -q).

    // NOTE: new magic option introduced by LibRaw 0.7.0 to to make better noise filtration.
    raw.imgdata.params.filtering_mode = LIBRAW_FILTERING_AUTOMATIC;

    int ret = raw.open_file((const char*)(TQFile::encodeName(path)));
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run open_file: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    ret = raw.unpack();
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run unpack: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    ret = raw.dcraw_process();
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run dcraw_process: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    libraw_processed_image_t *halfImg = raw.dcraw_make_mem_image(&ret);
    if(!halfImg)
    {
        tqDebug("LibRaw: failed to run dcraw_make_mem_image: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    TQByteArray imgData;
    KDcrawPriv::createPPMHeader(imgData, halfImg);
    free(halfImg);
    raw.recycle();

    if (!image.loadFromData(imgData))
    {
        tqDebug("Failed to load PPM data from LibRaw!");
        return false;
    }

    tqDebug("Using reduced RAW picture extraction");
    return true;
}

bool KDcraw::rawFileIdentify(DcrawInfoContainer& identify, const TQString& path)
{
    TQFileInfo fileInfo(path);
    TQString   rawFilesExt(rawFiles());
    TQString ext          = fileInfo.extension(false).upper();
    identify.isDecodable = false;

    if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
        return false;

    LibRaw raw;

    int ret = raw.open_file((const char*)(TQFile::encodeName(path)));
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run open_file: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    ret = raw.adjust_sizes_info_only();
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run adjust_sizes_info_only: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    KDcrawPriv::fillIndentifyInfo(&raw, identify);
    raw.recycle();
    return true;
}

// ----------------------------------------------------------------------------------

bool KDcraw::extractRAWData(const TQString& filePath, TQByteArray &rawData, DcrawInfoContainer& identify)
{
    TQFileInfo fileInfo(filePath);
    TQString   rawFilesExt(rawFiles());
    TQString ext          = fileInfo.extension(false).upper();
    identify.isDecodable = false;

    if (!fileInfo.exists() || ext.isEmpty() || !rawFilesExt.upper().contains(ext))
        return false;

    if (m_cancel)
        return false;

    d->setProgress(0.1);

    LibRaw raw;
    // Set progress call back function.
    raw.set_progress_handler(callbackForLibRaw, d);

    int ret = raw.open_file((const char*)(TQFile::encodeName(filePath)));
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run open_file: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if (m_cancel)
    {
        raw.recycle();
        return false;
    }
    d->setProgress(0.3);

    raw.imgdata.params.output_bps    = 16;
    raw.imgdata.params.document_mode = 2;

    ret = raw.unpack();
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run unpack: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if (m_cancel)
    {
        raw.recycle();
        return false;
    }
    d->setProgress(0.5);

    KDcrawPriv::fillIndentifyInfo(&raw, identify);

    if (m_cancel)
    {
        raw.recycle();
        return false;
    }
    d->setProgress(0.7);

    rawData = TQByteArray();
    rawData.resize((int)(raw.imgdata.sizes.iwidth * raw.imgdata.sizes.iheight * sizeof(unsigned short)));

    unsigned short *output = (unsigned short *)rawData.data();

    for (uint row=0 ; row < raw.imgdata.sizes.iheight ; row++)
    {
        for (uint col=0 ; col < raw.imgdata.sizes.iwidth ; col++)
        {
            *output = raw.imgdata.image[raw.imgdata.sizes.iwidth*row + col] [raw.FC(row, col)];
            *output++;
        }
    }

    raw.recycle();
    d->setProgress(1.0);

    return true;
}

bool KDcraw::decodeHalfRAWImage(const TQString& filePath, const RawDecodingSettings& rawDecodingSettings, 
                                TQByteArray &imageData, int &width, int &height, int &rgbmax)
{
    m_rawDecodingSettings                    = rawDecodingSettings;
    m_rawDecodingSettings.halfSizeColorImage = true;
    return (loadFromDcraw(filePath, imageData, width, height, rgbmax));
}

bool KDcraw::decodeRAWImage(const TQString& filePath, const RawDecodingSettings& rawDecodingSettings, 
                            TQByteArray &imageData, int &width, int &height, int &rgbmax)
{
    m_rawDecodingSettings = rawDecodingSettings;
    return (loadFromDcraw(filePath, imageData, width, height, rgbmax));
}

bool KDcraw::checkToCancelWaitingData()
{
    return m_cancel;
}

bool KDcraw::checkToCancelReceivingData()
{
    return m_cancel;
}

void KDcraw::setWaitingDataProgress(double)
{
}

void KDcraw::setReceivingDataProgress(double)
{
}

bool KDcraw::loadFromDcraw(const TQString& filePath, TQByteArray &imageData, 
                           int &width, int &height, int &rgbmax)
{
    m_cancel = false;

    TQStringList args;     // List of dcraw options to show as debug message on the console.

    LibRaw raw;
    // Set progress call back function.
    raw.set_progress_handler(callbackForLibRaw, d);

    TQByteArray deadpixelPath = TQFile::encodeName(m_rawDecodingSettings.deadPixelMap);
    TQByteArray cameraProfile = TQFile::encodeName(m_rawDecodingSettings.inputProfile);
    TQByteArray outputProfile = TQFile::encodeName(m_rawDecodingSettings.outputProfile);

    // NOTE: new magic option introduced by LibRaw 0.7.0 to to make better noise/etc filtration.
    raw.imgdata.params.filtering_mode = LIBRAW_FILTERING_AUTOMATIC;

    if (m_rawDecodingSettings.gamma16bit)
    {
        // 16 bits color depth auto-gamma is not implemented in dcraw.
        raw.imgdata.params.gamma_16bit = 1;
    }

    if (m_rawDecodingSettings.sixteenBitsImage)
    {
        // (-4) 16bit ppm output
        args.append("-4");
        raw.imgdata.params.output_bps = 16;
    }

    if (m_rawDecodingSettings.halfSizeColorImage)
    {
        // (-h) Half-size color image (3x faster than -q).
        args.append("-h");
        raw.imgdata.params.half_size = 1;
    }

    if (m_rawDecodingSettings.RGBInterpolate4Colors)
    {
        // (-f) Interpolate RGB as four colors.
        args.append("-f");
        raw.imgdata.params.four_color_rgb = 1;
    }

    if (m_rawDecodingSettings.DontStretchPixels)
    {
        // (-j) Do not stretch the image to its correct aspect ratio.
        args.append("-j");
        raw.imgdata.params.use_fuji_rotate = 1;
    }

    // (-H) Unclip highlight color.
    args.append(TQString("-H %1").arg(m_rawDecodingSettings.unclipColors));
    raw.imgdata.params.highlight = m_rawDecodingSettings.unclipColors;


    if (m_rawDecodingSettings.brightness != 1.0)
    {
        // (-b) Set Brightness value.
        args.append(TQString("-b %1").arg(m_rawDecodingSettings.brightness));
        raw.imgdata.params.bright = m_rawDecodingSettings.brightness;
    }

    if (m_rawDecodingSettings.enableBlackPoint)
    {
        // (-k) Set Black Point value.
        args.append(TQString("-k %1").arg(m_rawDecodingSettings.blackPoint));
        raw.imgdata.params.user_black = m_rawDecodingSettings.blackPoint;
    }

    if (m_rawDecodingSettings.enableWhitePoint)
    {
        // (-S) Set White Point value (saturation).
        args.append(TQString("-S %1").arg(m_rawDecodingSettings.whitePoint));
        raw.imgdata.params.user_sat = m_rawDecodingSettings.whitePoint;
    }

    if (m_rawDecodingSettings.medianFilterPasses > 0)
    {
        // (-m) After interpolation, clean up color artifacts by repeatedly applying a 3x3 median filter to the R-G and B-G channels.
        args.append(TQString("-m %1").arg(m_rawDecodingSettings.medianFilterPasses));
        raw.imgdata.params.med_passes = m_rawDecodingSettings.medianFilterPasses;
    }

    if (!m_rawDecodingSettings.deadPixelMap.isEmpty())
    {
        // (-P) Read the dead pixel list from this file.
        raw.imgdata.params.bad_pixels = deadpixelPath.data();
        args.append(TQString("-P %1").arg(raw.imgdata.params.bad_pixels));
    }

    switch (m_rawDecodingSettings.whiteBalance)
    {
        case RawDecodingSettings::NONE:
        {
            break;
        }
        case RawDecodingSettings::CAMERA:
        {
            // (-w) Use camera white balance, if possible.
            args.append("-w");
            raw.imgdata.params.use_camera_wb = 1;
            break;
        }
        case RawDecodingSettings::AUTO:
        {
            // (-a) Use automatic white balance.
            args.append("-a");
            raw.imgdata.params.use_auto_wb = 1;
            break;
        }
        case RawDecodingSettings::CUSTOM:
        {
            /* Convert between Temperature and RGB.
             */
            double T;
            double RGB[3];
            double xD, yD, X, Y, Z;
            DcrawInfoContainer identify;
            T = m_rawDecodingSettings.customWhiteBalance;

            /* Here starts the code picked and adapted from ufraw (0.12.1)
               to convert Temperature + green multiplier to RGB multipliers
            */
            /* Convert between Temperature and RGB.
             * Base on information from http://www.brucelindbloom.com/
             * The fit for D-illuminant between 4000K and 12000K are from CIE
             * The generalization to 2000K < T < 4000K and the blackbody fits
             * are my own and should be taken with a grain of salt.
             */
            const double XYZ_to_RGB[3][3] = {
                { 3.24071,  -0.969258,  0.0556352 },
                {-1.53726,  1.87599,    -0.203996 },
                {-0.498571, 0.0415557,  1.05707 } };
            // Fit for CIE Daylight illuminant
            if (T <= 4000)
            {
                xD = 0.27475e9/(T*T*T) - 0.98598e6/(T*T) + 1.17444e3/T + 0.145986;
            }
            else if (T <= 7000)
            {
                xD = -4.6070e9/(T*T*T) + 2.9678e6/(T*T) + 0.09911e3/T + 0.244063;
            }
            else
            {
                xD = -2.0064e9/(T*T*T) + 1.9018e6/(T*T) + 0.24748e3/T + 0.237040;
            }

            yD     = -3*xD*xD + 2.87*xD - 0.275;
            X      = xD/yD;
            Y      = 1;
            Z      = (1-xD-yD)/yD;
            RGB[0] = X*XYZ_to_RGB[0][0] + Y*XYZ_to_RGB[1][0] + Z*XYZ_to_RGB[2][0];
            RGB[1] = X*XYZ_to_RGB[0][1] + Y*XYZ_to_RGB[1][1] + Z*XYZ_to_RGB[2][1];
            RGB[2] = X*XYZ_to_RGB[0][2] + Y*XYZ_to_RGB[1][2] + Z*XYZ_to_RGB[2][2];
            /* End of the code picked to ufraw
            */

            RGB[1] = RGB[1] / m_rawDecodingSettings.customWhiteBalanceGreen;

            /* By default, decraw override his default D65 WB
               We need to keep it as a basis : if not, colors with some
               DSLR will have a high dominant of color that will lead to
               a completly wrong WB
            */
            if (rawFileIdentify(identify, filePath))
            {
                RGB[0] = identify.daylightMult[0] / RGB[0];
                RGB[1] = identify.daylightMult[1] / RGB[1];
                RGB[2] = identify.daylightMult[2] / RGB[2];
            }
            else
            {
                RGB[0] = 1.0 / RGB[0];
                RGB[1] = 1.0 / RGB[1];
                RGB[2] = 1.0 / RGB[2];
                tqDebug("Warning: cannot get daylight multipliers");
            }

            // (-r) set Raw Color Balance Multipliers.
            raw.imgdata.params.user_mul[0] = RGB[0];
            raw.imgdata.params.user_mul[1] = RGB[1];
            raw.imgdata.params.user_mul[2] = RGB[2];
            raw.imgdata.params.user_mul[3] = RGB[1];
            args.append(TQString("-r %1 %2 %3 %4").arg(raw.imgdata.params.user_mul[0])
                                                 .arg(raw.imgdata.params.user_mul[1])
                                                 .arg(raw.imgdata.params.user_mul[2])
                                                 .arg(raw.imgdata.params.user_mul[3]));
            break;
        }
        case RawDecodingSettings::AERA:
        {
            // (-A) Calculate the white balance by averaging a rectangular area from image.
            raw.imgdata.params.greybox[0] = m_rawDecodingSettings.whiteBalanceArea.left();
            raw.imgdata.params.greybox[1] = m_rawDecodingSettings.whiteBalanceArea.top();
            raw.imgdata.params.greybox[2] = m_rawDecodingSettings.whiteBalanceArea.width();
            raw.imgdata.params.greybox[3] = m_rawDecodingSettings.whiteBalanceArea.height();
            args.append(TQString("-A %1 %2 %3 %4").arg(raw.imgdata.params.greybox[0])
                                                 .arg(raw.imgdata.params.greybox[1])
                                                 .arg(raw.imgdata.params.greybox[2])
                                                 .arg(raw.imgdata.params.greybox[3]));
            break;
        }
    }

    // (-q) Use an interpolation method.
    raw.imgdata.params.user_qual = m_rawDecodingSettings.RAWQuality;
    args.append(TQString("-q %1").arg(m_rawDecodingSettings.RAWQuality));

    if (m_rawDecodingSettings.enableNoiseReduction)
    {
        // (-n) Use wavelets to erase noise while preserving real detail.
        raw.imgdata.params.threshold = m_rawDecodingSettings.NRThreshold;
        args.append(TQString("-n %1").arg(raw.imgdata.params.threshold));
    }

    if (m_rawDecodingSettings.enableCACorrection)
    {
        // (-C) Set Correct chromatic aberration correction.
        raw.imgdata.params.aber[0] = m_rawDecodingSettings.caMultiplier[0];
        raw.imgdata.params.aber[2] = m_rawDecodingSettings.caMultiplier[1];
        args.append(TQString("-C %1 %2").arg(raw.imgdata.params.aber[0])
                                       .arg(raw.imgdata.params.aber[2]));
    }

    switch (m_rawDecodingSettings.inputColorSpace)
    {
        case RawDecodingSettings::EMBEDDED:
        {
            // (-p embed) Use input profile from RAW file to define the camera's raw colorspace.
            raw.imgdata.params.camera_profile = (char*)"embed";
            args.append(TQString("-p embed"));
            break;
        }
        case RawDecodingSettings::CUSTOMINPUTCS:
        {
            if (!m_rawDecodingSettings.inputProfile.isEmpty())
            {
                // (-p) Use input profile file to define the camera's raw colorspace.
                raw.imgdata.params.camera_profile = cameraProfile.data();
                args.append(TQString("-p %1").arg(raw.imgdata.params.camera_profile));
            }
            break;
        }
        default:   // No input profile
            break;
    }

    switch (m_rawDecodingSettings.outputColorSpace)
    {
        case RawDecodingSettings::CUSTOMOUTPUTCS:
        {
            if (!m_rawDecodingSettings.outputProfile.isEmpty())
            {
                // (-o) Use ICC profile file to define the output colorspace.
                raw.imgdata.params.output_profile = outputProfile.data();
                args.append(TQString("-o %1").arg(raw.imgdata.params.output_profile));
            }
            break;
        }
        default:
        {
            // (-o) Define the output colorspace.
            raw.imgdata.params.output_color = m_rawDecodingSettings.outputColorSpace;
            args.append(TQString("-o %1").arg(raw.imgdata.params.output_color));
            break;
        }
    }

    d->setProgress(0.1);

    args.append(filePath);
    tqDebug("LibRaw: dcraw emulation: %s", args.join(", ").ascii());

    int ret = raw.open_file((const char*)(TQFile::encodeName(filePath)));
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run open_file: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if (m_cancel)
    {
        raw.recycle();
        return false;
    }
    d->setProgress(0.2);

    ret = raw.unpack();
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run unpack: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if (m_cancel)
    {
        raw.recycle();
        return false;
    }
    d->setProgress(0.25);

    ret = raw.dcraw_process();
    if (ret != LIBRAW_SUCCESS)
    {
        tqDebug("LibRaw: failed to run dcraw_process: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if (m_cancel)
    {
        raw.recycle();
        return false;
    }
    d->setProgress(0.3);

    libraw_processed_image_t *img = raw.dcraw_make_mem_image(&ret);
    if(!img)
    {
        tqDebug("LibRaw: failed to run dcraw_make_mem_image: %s", libraw_strerror(ret));
        raw.recycle();
        return false;
    }

    if (m_cancel)
    {
        free(img);
        raw.recycle();
        return false;
    }
    d->setProgress(0.35);

    width     = img->width;
    height    = img->height;
    rgbmax    = (1 << img->bits)-1;
    imageData.resize((int)img->data_size);
    memcpy(imageData.data(), (const char*)img->data, (int)img->data_size);

    free(img);
    raw.recycle();

    if (m_cancel)
        return false;
    d->setProgress(0.4);

    tqDebug("LibRaw: data info: width %i height %i rgbmax %i", width, height, rgbmax);

    return true;
}

const char *KDcraw::rawFiles()
{
    return raw_file_extentions;
}

TQStringList KDcraw::rawFilesList()
{
    TQString string = TQString::fromLatin1(rawFiles()).remove("*.");
    return TQStringList::split(' ', string);
}

TQStringList KDcraw::supportedCamera()
{
    TQStringList camera;
    const char** list = LibRaw::cameraList();
    for (int i = 0; i < LibRaw::cameraCount(); i++)
        camera.append(list[i]);

    return camera;
}

TQString KDcraw::librawVersion()
{
    return TQString(LIBRAW_VERSION_STR).remove("-Release");
}

}  // namespace KDcrawIface
