/*
 * Copyright (C) 2016 ~ 2018 Deepin Technology Co., Ltd.
 *
 * 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 3 of the License, or
 * 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, see <http://www.gnu.org/licenses/>.
 */
#include "baseutils.h"
#include <FreeImage.h>
#include <QDateTime>
#include <QFileInfo>
#include <QImage>
#include <QImageReader>
#include <QMap>
#include <QString>
#include <QDebug>

namespace utils {

namespace image {

namespace freeimage {

FREE_IMAGE_FORMAT fFormat(const QString &path)
{
    const QByteArray ba = path.toUtf8();
    const char *pc = ba.data();
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    fif = FreeImage_GetFileType(pc, 0);
    if (fif == FIF_UNKNOWN) {
        fif = FreeImage_GetFIFFromFilename(pc);
    }

    return fif;
}

const QString getFileFormat(const QString &path)
{
    const FREE_IMAGE_FORMAT fif = fFormat(path);
    if (fif == FIF_UNKNOWN) {
        return QString("UNKNOW");
    }
    else {
        return QString(FreeImage_GetFIFMimeType(fif));
    }
}

FIBITMAP * readFileToFIBITMAP(const QString &path, int flags FI_DEFAULT(0))
{
    const FREE_IMAGE_FORMAT fif = fFormat(path);

    if ((fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif)) {
        const QByteArray ba = path.toUtf8();
        const char *pc = ba.data();
        FIBITMAP *dib = FreeImage_Load(fif, pc, flags);
        return dib;
    }

    return NULL;
}


QMap<QString, QString> getMetaData(FREE_IMAGE_MDMODEL model, FIBITMAP *dib)
{
    QMap<QString, QString> mdMap;  // key-data
    FITAG *tag = NULL;
    FIMETADATA *mdhandle = NULL;
    mdhandle = FreeImage_FindFirstMetadata(model, dib, &tag);
    if (mdhandle) {
        do {
            mdMap.insert(FreeImage_GetTagKey(tag),
                         FreeImage_TagToString(model, tag));
        } while (FreeImage_FindNextMetadata(mdhandle, &tag));

        FreeImage_FindCloseMetadata(mdhandle);
    }

    return mdMap;
}

const QDateTime getDateTime(const QString &path, bool createTime = true)
{
    FIBITMAP *dib = readFileToFIBITMAP(path, FIF_LOAD_NOPIXELS);
    auto datas = getMetaData(FIMD_EXIF_EXIF, dib);
    if (datas.isEmpty()) {
        QFileInfo info(path);
        if (createTime) {
            return info.created();
        }
        else {
            return info.lastModified();
        }
    }

    if (createTime) {
        return utils::base::stringToDateTime(datas["DateTimeOriginal"]);
    }
    else {
        return utils::base::stringToDateTime(datas["DateTimeDigitized"]);
    }
}

const QString getOrientation(const QString &path)
{
    FIBITMAP *dib = readFileToFIBITMAP(path, FIF_LOAD_NOPIXELS);
    auto datas = getMetaData(FIMD_EXIF_MAIN, dib);
    if (datas.isEmpty()) {
        return QString();
    }

    return datas["Orientation"];
}

/*!
 * \brief getAllMetaData
 * This function is very fast with FIF_LOAD_NOPIXELS flag
 * \param path
 * \return
 */
QMap<QString, QString> getAllMetaData(const QString &path)
{
    FIBITMAP *dib = readFileToFIBITMAP(path, FIF_LOAD_NOPIXELS);
    QMap<QString, QString> admMap;
    admMap.unite(getMetaData(FIMD_EXIF_MAIN, dib));
    admMap.unite(getMetaData(FIMD_EXIF_EXIF, dib));
    admMap.unite(getMetaData(FIMD_EXIF_GPS, dib));
    admMap.unite(getMetaData(FIMD_EXIF_MAKERNOTE, dib));
    admMap.unite(getMetaData(FIMD_EXIF_INTEROP, dib));
    admMap.unite(getMetaData(FIMD_IPTC, dib));

    // Basic extended data
    QFileInfo info(path);
    QImageReader reader(path);
    if (admMap.isEmpty()) {
        QDateTime emptyTime(QDate(0, 0, 0), QTime(0, 0, 0));
        admMap.insert("DateTimeOriginal",  emptyTime.toString("yyyy.MM.dd HH:mm:ss"));
        admMap.insert("DateTimeDigitized", info.lastModified().toString("yyyy.MM.dd HH:mm:ss"));
    }
    else {
        // ReFormat the date-time
        using namespace utils::base;
        // Exif version 0231
        QDateTime ot = stringToDateTime(admMap.value("DateTimeOriginal"));
        QDateTime dt = stringToDateTime(admMap.value("DateTimeDigitized"));
        if (! ot.isValid()) {
            // Exif version 0221
            ot = stringToDateTime(admMap.value("DateTime"));
            dt = ot;

            // NO valid date information
            if (! ot.isValid()) {
                admMap.insert("DateTimeOriginal", info.created().toString("yyyy.MM.dd HH:mm:ss"));
                admMap.insert("DateTimeDigitized", info.lastModified().toString("yyyy.MM.dd HH:mm:ss"));
            }
        }
        admMap.insert("DateTimeOriginal", ot.toString("yyyy.MM.dd HH:mm:ss"));
        admMap.insert("DateTimeDigitized", dt.toString("yyyy.MM.dd HH:mm:ss"));
    }
    // The value of width and height might incorrect
    int w = reader.size().width();
    w = w > 0 ? w : FreeImage_GetWidth(dib);
    int h = reader.size().height();
    h = h > 0 ? h : FreeImage_GetHeight(dib);
    admMap.insert("Resolution", QString::number(w) + "x" + QString::number(h));
    admMap.insert("FileName", info.fileName());
    admMap.insert("FileFormat", getFileFormat(path));
    admMap.insert("FileSize", utils::base::sizeToHuman(info.size()));

    FreeImage_Unload(dib);

    return admMap;
}

FIBITMAP * makeThumbnail(const QString &path, int size) {
    const QByteArray pb = path.toUtf8();
    const char *pc = pb.data();
    FIBITMAP *dib = NULL;
    int flags = 0;              // default load flag

    FREE_IMAGE_FORMAT fif = fFormat(path);
    if(fif == FIF_UNKNOWN) {
        return NULL;
    }

    // for JPEG images, we can speedup the loading part
    // Using LibJPEG downsampling feature while loading the image...
    if(fif == FIF_JPEG) {
        flags = JPEG_EXIFROTATE;
        flags |= size << 16;
        // Load the dib
        dib = FreeImage_Load(fif, pc, flags);
        if(! dib) return NULL;
    }
    else {
        // Any cases other than the JPEG case: load the dib ...
        if(fif == FIF_RAW || fif == FIF_TIFF) {
            // ... except for RAW images, try to load the embedded JPEG preview
            // or default to RGB 24-bit ...
            flags = RAW_PREVIEW;
            dib = FreeImage_Load(fif, pc, flags);
            if(!dib) return NULL;
        }
        else {
            // 某些损坏的图片� �式会识别错误，freeimage在load的时候会崩溃，暂时没法解决
            // 除了上面� 种可能� 速缩略图读取的方式，都返回空
            return NULL;
        }
    }

    // create the requested thumbnail
    FIBITMAP *thumbnail = FreeImage_MakeThumbnail(dib, size, TRUE);
    FreeImage_Unload(dib);
    return thumbnail;
}

bool isSupportsReading(const QString &path)
{
    const FREE_IMAGE_FORMAT fif = fFormat(path);

    return (fif != FIF_UNKNOWN) && FreeImage_FIFSupportsReading(fif);
}

bool isSupportsWriting(const QString &path)
{
    FREE_IMAGE_FORMAT fif = fFormat(path);

    return (fif != FIF_UNKNOWN) && FreeImage_FIFSupportsWriting(fif);
}

bool canSave(FIBITMAP* dib, const QString &path)
{
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    // Try to guess the file format from the file extension
    fif = FreeImage_GetFIFFromFilename(path.toUtf8().data());
    if(fif != FIF_UNKNOWN ) {
        // Check that the dib can be saved in this format
        BOOL bCanSave;
        FREE_IMAGE_TYPE image_type = FreeImage_GetImageType(dib);
        if(image_type == FIT_BITMAP) {
            // standard bitmap type
            // check that the plugin has sufficient writing
            // and export capabilities ...
            WORD bpp = FreeImage_GetBPP(dib);
            bCanSave = (FreeImage_FIFSupportsWriting(fif) &&
                        FreeImage_FIFSupportsExportBPP(fif, bpp));
        } else {
            // special bitmap type
            // check that the plugin has sufficient export capabilities
            bCanSave = FreeImage_FIFSupportsExportType(fif, image_type);
        }
        return bCanSave;
    }
    return false;
}

bool canSave(const QString &path)
{
    FIBITMAP *dib = readFileToFIBITMAP(path, FIF_LOAD_NOPIXELS);
    bool v= canSave(dib, path);
    FreeImage_Unload(dib);
    return v;
}

bool writeFIBITMAPToFile(FIBITMAP* dib, const QString &path, int flag = 0) {
    FREE_IMAGE_FORMAT fif = FIF_UNKNOWN;
    BOOL bSuccess = FALSE;
    const QByteArray ba = path.toUtf8();
    const char *pc = ba.data();
    // Try to guess the file format from the file extension
    fif = FreeImage_GetFIFFromFilename(pc);
    if(fif != FIF_UNKNOWN && canSave(dib, path)) {
        bSuccess = FreeImage_Save(fif, dib, pc, flag);
    }

    return (bSuccess == TRUE) ? true : false;
}

QImage noneQImage()
{
    static QImage none(0,0,QImage::Format_Invalid);
    return none;
}

bool isNoneQImage(const QImage &qi)
{
    return qi == noneQImage();
}

QImage FIBitmapToQImage(FIBITMAP *dib)
{
    if (!dib || FreeImage_GetImageType(dib) != FIT_BITMAP)
        return noneQImage();
    int width  = FreeImage_GetWidth(dib);
    int height = FreeImage_GetHeight(dib);

    switch (FreeImage_GetBPP(dib))
    {
    case 1:
    {
        QImage result(width, height, QImage::Format_Mono);
        FreeImage_ConvertToRawBits(
                    result.scanLine(0), dib, result.bytesPerLine(), 1, 0, 0, 0, true
                    );
        return result;
    }
    case 4: /* NOTE: QImage do not support 4-bit, convert it to 8-bit  */
    {
        QImage result(width, height, QImage::Format_Indexed8);
        FreeImage_ConvertToRawBits(
                    result.scanLine(0), dib, result.bytesPerLine(), 8, 0, 0, 0, true
                    );
        return result;
    }
    case 8:
    {
        QImage result(width, height, QImage::Format_Indexed8);
        FreeImage_ConvertToRawBits(
                    result.scanLine(0), dib, result.bytesPerLine(), 8, 0, 0, 0, true
                    );
        return result;
    }
    case 16:
        if ( // 5-5-5
             (FreeImage_GetRedMask(dib)   == FI16_555_RED_MASK) &&
             (FreeImage_GetGreenMask(dib) == FI16_555_GREEN_MASK) &&
             (FreeImage_GetBlueMask(dib)  == FI16_555_BLUE_MASK))
        {
            QImage result(width, height, QImage::Format_RGB555);
            FreeImage_ConvertToRawBits(
                        result.scanLine(0), dib, result.bytesPerLine(), 16,
                        FI16_555_RED_MASK, FI16_555_GREEN_MASK, FI16_555_BLUE_MASK,
                        true
                        );
            return result;
        }
        else // 5-6-5
        {
            QImage result(width, height, QImage::Format_RGB16);
            FreeImage_ConvertToRawBits(
                        result.scanLine(0), dib, result.bytesPerLine(), 16,
                        FI16_565_RED_MASK, FI16_565_GREEN_MASK, FI16_565_BLUE_MASK,
                        true
                        );
            return result;
        }
    case 24:
    {
        QImage result(width, height, QImage::Format_RGB32);
        FreeImage_ConvertToRawBits(
                    result.scanLine(0), dib, result.bytesPerLine(), 32,
                    FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK,
                    true
                    );
        return result;
    }
    case 32:
    {
        QImage result(width, height, QImage::Format_ARGB32);
        FreeImage_ConvertToRawBits(
                    result.scanLine(0), dib, result.bytesPerLine(), 32,
                    FI_RGBA_RED_MASK, FI_RGBA_GREEN_MASK, FI_RGBA_BLUE_MASK,
                    true
                    );
        return result;
    }
    default:
        break;
    }
    return noneQImage();
}

}  // namespace freeimage

}  // namespace image

}  // namespace utils
