/*
 * Copyright 2014 Mihai Niculescu <q.quark@gmail.com>
 *
 * This file is part of EqualX Project (https://launchpad.net/equalx/)
 *
 * EqualX 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.
 *
 * EqualX 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/>.
 */

/*
 * This is the implementation of EqualX::File when using XMP SDK API (intended for Windows)
 */
#include <string>
#define TXMP_STRING_TYPE	std::string
#define XMP_INCLUDE_XMPFILES 1
#define WIN_ENV 1

#include <XMP.hpp>
#include <XMP.incl_cpp>

#include <QDebug>

#include <QDateTime>
#include <QFile>
#include <QTextStream>

#include "defines.h"
#include "File.h"
#include "Util.h"

namespace EqualX{

/* Theft code from GPL code pdfcropxmp.pl by Johannes Grosse - Copyright (C) 2007 */
const char* __templateLatexCropFile = "\\def\\pdffile{%1}\n"
        "\\csname pdfmapfile\\endcsname{}\n"
        "\\newread\\instream\n"
        "\\def\\includexmp#1{%\n"
        "\\openin\\instream=#1\n"
        "\\ifeof\\instream\\else \n"
        "\\closein\\instream\n"
        "% file exists\n"
        "\\begingroup\n"
        "\\pdfcompresslevel=0\n"
        "\\immediate\\pdfobj stream attr {/Type /Metadata /Subtype /XML}\n"
        "file{#1}\n"
        "\\pdfcatalog{/Metadata \\the\\pdflastobj\\space 0 R}\n"
        "\\endgroup %/\n"
        "\\fi\n"
        "}\n"
        "\\def\\page #1 [#2 #3 #4 #5]{%\n"
        "\\count0=#1\\relax\n"
        "\\setbox0=\\hbox{%\n"
        "\\pdfximage page #1{\\pdffile}%\n"
        "\\pdfrefximage\\pdflastximage\n"
        "}%\n"
        "\\pdfhorigin=-#2bp\\relax\n"
        "\\pdfvorigin=#3bp\\relax\n"
        "\\pdfpagewidth=#4bp\\relax\n"
        "\\advance\\pdfpagewidth by -#2bp\\relax\n"
        "\\pdfpageheight=#5bp\\relax\n"
        "\\advance\\pdfpageheight by -#3bp\\relax\n"
        "\\ht0=\\pdfpageheight\n"
        "\\shipout\\box0\\relax\n"
        "}\n"
        "\\def\\pageclip #1 [#2 #3 #4 #5][#6 #7 #8 #9]{%\n"
        "\\count0=#1\\relax\n"
        "\\ndimen0=#4bp\\relax \\advance\\dimen0 by -#2bp\\relax\n"
        "\\edef\\imagewidth{\\the\\dimen0}%\n"
        "\\dimen0=#5bp\\relax \\advance\\ndimen0 by -#3bp\\relax\n"
        "\\edef\\imageheight{\\the\\dimen0}%\n"
        "\\pdfximage page #1{\\pdffile}%\n"
        "\\setbox0=\\hbox{%\n"
        "\\kern -#2bp\\relax\n"
        "\\lower #3bp\\hbox{\\pdfrefximage\\pdflastximage}%\n"
        "}%\n"
        "\\wd0=\\imagewidth\\relax\n"
        "\\ht0=\\imageheight\\relax\n"
        "\\dp0=0pt\\relax\n"
        "\\pdfhorigin=#6pt\\relax\n"
        "\\pdfvorigin=#7bp\\relax\n"
        "\\pdfpagewidth=\\imagewidth\n"
        "\\advance\\pdfpagewidth by #6bp\\relax\n"
        "\\advance\\pdfpagewidth by #8bp\\relax\n"
        "\\pdfpageheight=\\imageheight\\relax\n"
        "\\advance\\pdfpageheight by #7bp\\relax\n"
        "\\advance\\pdfpageheight by #9bp\\relax\n"
        "\\pdfxform0\\relax\n"
        "\\shipout\\hbox{\\pdfrefxform\\pdflastxform}%\n"
        "}%\n"
        "\\includexmp{%2}\n"
        "\\page %3 [%4 %5 %6 %7]\n"
        "\\csname @@end\\endcsname\n\\end\n";

/*
 * Identifiers for __templateLatexCropFile:
 *  - input pdf file
 *  - input xmp metadata file
 *  - page number
 *  - llx: lower left x
 *  - lly: lower left y
 *  - urx: upper right x
 *  - ury: upper right y
 * (gs has the coordinate system with origin in the lower left corner)
 */

/* End Theft */


const char* __templatePSwithXMP = "/currentdistillerparams where\n"
        "{pop currentdistillerparams /CoreDistVersion get 5000 lt} {true} ifelse\n"
        "{ userdict /pdfmark /cleartomark load put\n"
        "userdict /metafile_pdfmark {flushfile cleartomark } bind put}\n"
        "{ userdict /metafile_pdfmark {/PUT pdfmark} bind put} ifelse\n"
        "[/_objdef {%2} /type /stream /OBJ pdfmark\n"
        "[{%2}\n"
        "currentfile 0 (% &&end XMP packet marker&&)\n"
        "/SubFileDecode filter metafile_pdfmark\n"
        "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\n"
        "%1\n"
        "<?xpacket end='r'?>\n"
        "% &&end XMP packet marker&&\n"
        "[{%2}\n"
        "2 dict begin /Type /Metadata def /Subtype /XML currentdict end\n"
        "/PUT pdfmark\n";

/*
 * Identifiers for __templatePSwithXMP:
 *  %1 - the XMP packet
 *  %2 - equalx_metadata_stream dependant on timestamp
 */
}

struct EqualX::File::FileImplementation{
    SXMPMeta mMetadata;
    SXMPFiles mFile;
};

EqualX::File::File() :
    mFileName(),
    mFileInfo(0)
{
    __init();
}

EqualX::File::File(const EqualX::FileInfo &fileinfo)
    : mFileName(),
      mFileInfo(fileinfo)
{
    __init();
}

EqualX::File::~File()
{
    close();

    // Terminate the toolkit
    SXMPFiles::Terminate();
    SXMPMeta::Terminate();

    delete mClassImpl;
}


void EqualX::File::__init()
{
    if(SXMPMeta::Initialize()){
        mClassImpl = new FileImplementation;
    }
}


void EqualX::File::open(const QString &filename, EqualX::File::OpenModes mode)
{
    XMP_OptionBits options=0;
    if (!SXMPFiles::Initialize(options))
    {
        qDebug() << "Could not initialize SXMPFiles.";
        return;
    }

    mFileName = filename;
    qDebug() << "[EqualX::File::open] Openning file:"<<filename<<" in mode:";

    XMP_OptionBits opts = 0;
    if(mode.testFlag(EqualX::File::OPEN_READ)){
        opts = kXMPFiles_OpenForRead;
        qDebug() << "read";
    }

    if(mode.testFlag(EqualX::File::OPEN_UPDATE)){
        opts = (XMP_OptionBits)kXMPFiles_OpenForUpdate;
        qDebug() << "update";
    }

    if(mode.testFlag(EqualX::File::OPEN_SMART)){
        opts = ( XMP_OptionBits)(opts | kXMPFiles_OpenUseSmartHandler);
        qDebug() << "smart";
    }

    if(mode.testFlag(EqualX::File::OPEN_SCAN)){
        opts = ( XMP_OptionBits)(opts | kXMPFiles_OpenUsePacketScanning);
        qDebug() << "scan";

    }

   mClassImpl->mFile.CloseFile();

    mClassImpl->mFile.OpenFile(filename.toStdString(), kXMP_UnknownFile, opts);
}

void EqualX::File::close()
{
    mClassImpl->mFile.CloseFile();
}

bool EqualX::File::read()
{
    mClassImpl->mFile.GetXMP(&mClassImpl->mMetadata);

    if(!mClassImpl->mMetadata.GetNamespacePrefix(METADATA_NS, NULL) ){
        qDebug() << "[EqualX::File::read] Namespace ["<<METADATA_NS<<"] is not registered. Aborting read...";
        return false;
    }

    if(!mClassImpl->mMetadata.GetNamespaceURI(METADATA_PREFIX, NULL) ){
        qDebug() << "[EqualX::File::read] Prefix ["<<METADATA_PREFIX<<"] is not registered. Aborting read...";
        return false;
    }

    std::string preamble;
    std::string equation;
    std::string fgColor ;
    std::string bgColor ;
    XMP_Int32 environment;
    XMP_Int32 fontSize;

    bool readStatus = true;
    readStatus = mClassImpl->mMetadata.GetProperty(METADATA_NS, METADATA_PREAMBLE, &preamble,0);
    readStatus = mClassImpl->mMetadata.GetProperty(METADATA_NS, METADATA_EQUATION, &equation,0);
    readStatus = mClassImpl->mMetadata.GetProperty_Int(METADATA_NS, METADATA_ENV, &environment, 0);
    readStatus = mClassImpl->mMetadata.GetProperty(METADATA_NS, METADATA_FG, &fgColor, 0);
    readStatus = mClassImpl->mMetadata.GetProperty(METADATA_NS, METADATA_BG, &bgColor, 0);
    readStatus = mClassImpl->mMetadata.GetProperty_Int(METADATA_NS, METADATA_DOC_FONT_SIZE, &fontSize, 0);

    QString preambleStr = QString::fromStdString(preamble);
    preambleStr.replace( "#perc", "%");
    preambleStr.replace( "#", "\\");

    QString equationStr = QString::fromStdString(equation);
    equationStr.replace("#perc", "%");
    equationStr.replace( "#", "\\");

    QColor c1;
    QColor c2;

    EqualX::Util::LatexToColor(fgColor.c_str(), c1);
    EqualX::Util::LatexToColor(bgColor.c_str(), c2);

    mFileInfo.setPreamble(preambleStr);
    mFileInfo.setEquation(equationStr);
    mFileInfo.setForegroundColor( c1 );
    mFileInfo.setBackgroundColor( c2 );
    mFileInfo.setEnvironment(environment);
    mFileInfo.setFontSize(fontSize);

    return readStatus;

}

bool EqualX::File::write()
{
    bool status = false;
    QString filetype = mFileName.section(".",-1);

    qDebug() << "[EqualX::File::write] Trying to insert metadata into "<<mFileName<<" ext:"<<filetype;

    if(filetype.contains("svg") ){
        qDebug() << "[EqualX::File::write] \tSeriliazing metadata";

        std::string buf ;
        mClassImpl->mMetadata.SerializeToBuffer(&buf, kXMP_OmitPacketWrapper);

        QString metadataStr=QString::fromStdString(buf);

        qDebug() << "[EqualX::File::write] \tOpenning file";
        //mClassImpl->mFile.PutXMP(mClassImpl->mMetadata);

        mClassImpl->mFile.CloseFile();

        QFile file( mFileName );
        file.open(QIODevice::ReadWrite | QIODevice::Text);
        if(!file.isOpen()){
            qDebug() << file.errorString();
            return false;
        }
        QTextStream stream(&file);

        qDebug() << "[EqualX::File::write] \tSearching for EOF";
        QString line;
        qint64 pos;
        do{
            pos = stream.pos();
            line = stream.readLine();
            qDebug() << "line: " << line;
        } while (!line.contains("</svg>"));

        qDebug() << "[EqualX::File::write] \tFound EOF. Flushing metadata...";
        stream.seek(pos);
        stream << "<![CDATA[\n" << "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\n"<<metadataStr <<"<?xpacket end='r'?>"<< "]]>\n";
        stream << "</svg>";
        stream.flush();
        file.close();
        qDebug() << "[EqualX::File::write] \tFlushing Done.";

        status = true;

    }
    else if(filetype.contains("ps")){ // for ps and eps documents, level 2
        std::string buf ;
         mClassImpl->mMetadata.SerializeToBuffer(&buf, kXMP_OmitPacketWrapper);
        QString metadataStr=QString::fromStdString(buf);

        QFile in(mFileName); QFile out(mFileName+"~");
        in.open(QIODevice::ReadOnly); out.open(QIODevice::WriteOnly);
        QTextStream stream(&out);

        QString line;
        QString curTimeStamp = QString::number(QDateTime::currentDateTime().toTime_t());
        while(!in.atEnd()){
            line = in.readLine();
            stream << line;
            if(line.contains("%%BeginPageSetup")){
                stream << QString(__templatePSwithXMP).arg(metadataStr, curTimeStamp);
            }
            stream.flush();
        }

        in.close();
        out.close();

        QFile::remove(mFileName);
        QFile::rename(mFileName+"~", mFileName);

        status = true;
    }
    else if(mClassImpl->mFile.CanPutXMP(mClassImpl->mMetadata)){
        mClassImpl->mFile.PutXMP(mClassImpl->mMetadata);
        status=true;
    }
    else{
        status = false;
    }

    if(status) qDebug() << "[EqualX::File::write] inserted metadata to file:" << mFileName;

    return status;
}

void EqualX::File::writeLatexFile(bool withBackgroundColor)
{
    QString latexColors; // sets font color
    QString latexHLColor; // sets HighLight color

    QString latexFGColor, latexBGColor;

    EqualX::Util::colorToLatex(mFileInfo.fgColor(), latexFGColor);
    EqualX::Util::colorToLatex(mFileInfo.bgColor(), latexBGColor);

    latexColors = QString("\\definecolor{fgC}{rgb}{%1}\\color{fgC}").arg(latexFGColor);
    if(withBackgroundColor){
        QString bgColor = QString("\\definecolor{bgC}{rgb}{%1}\\pagecolor{bgC} ").arg(latexBGColor);
        latexColors.append(bgColor);
    }

    QString equation = mFileInfo.equation(); // we modify it if we have selections

    /* Highlight selections */
    if(!mFileInfo.selections().isEmpty()){
        if(withBackgroundColor){
            latexHLColor = QString("\\definecolor{selC}{rgb}{%1}").arg(latexBGColor);
        }
        else {
            latexHLColor = QString("\\definecolor{selC}{rgb}{1,1,1}");
        }

        QString insert1 = "\\colorbox{fgC}{\\color{selC} $";
        QString insert2 = "$}";

        int insertStr1Len = insert1.length();
        int insertStr2Len = insert2.length();
        int insertStrLen = insertStr1Len + insertStr2Len;
        int newInsert1Pos, newInsert2Pos;

        for(int i=0; i < mFileInfo.selections().size(); i++){
            SelectionIndex *selectionItem = mFileInfo.selections().at(i);

            int ss = selectionItem->start; // start selection
            int se = selectionItem->end; // end selection

            newInsert1Pos = ss + i*insertStrLen;
            newInsert2Pos = se + i*insertStrLen + insertStr1Len;

            equation.insert(newInsert1Pos, insert1);
            equation.insert(newInsert2Pos, insert2);
        }

    }

    /* Set Font Size */
    QString latexFontSize;
    switch(mFileInfo.fontSize()){
    case LATEX_FONT_TINY:
        latexFontSize="\\tiny ";
        break;
    case LATEX_FONT_SCRIPT:
        latexFontSize="\\scriptsize ";
        break;
    case LATEX_FONT_FOOTNOTE:
        latexFontSize="\\footnotesize ";
        break;
    case LATEX_FONT_SMALL:
        latexFontSize="\\small ";
        break;
    default:
    case LATEX_FONT_NORMAL:
        latexFontSize="";
        break;
    case LATEX_FONT_LARGE:
        latexFontSize="\\large ";
        break;
    case LATEX_FONT_VERY_LARGE:
        latexFontSize="\\LARGE ";
        break;
    case LATEX_FONT_HUGE:
        latexFontSize="\\huge ";
        break;
    case LATEX_FONT_VERY_HUGE:
        latexFontSize="\\Huge ";
        break;

    }

    /* Set Environment */
    QString envBegin, envEnd;
    switch(mFileInfo.environment()){
    default:
    case LATEX_ENV_MODE_DISPLAY:
        envBegin = "\\[";
        envEnd = "\\]";
        break;
    case LATEX_ENV_MODE_INLINE:
        envBegin = "$";
        envEnd = "$";
        break;
    case LATEX_ENV_MODE_ALIGN:
        envBegin = "\\begin{align*}";
        envEnd = "\\end{align*}";
        break;
    case LATEX_ENV_MODE_TEXT:
        envBegin = "";
        envEnd = "";
        break;
    }

    // compose whole content of latex file
    QString latexFileContent = mFileInfo.preamble()+"\n\\begin{document}"+latexFontSize+latexColors+latexHLColor+envBegin+equation+envEnd+"\\end{document}";

    // write the content to file
    QFile texFile( TEMP_LATEX_FILE );
    texFile.open(QIODevice::WriteOnly | QIODevice::Text);

    QTextStream out(&texFile);
    out << latexFileContent;
    out.flush();
    texFile.close();
}

void EqualX::File::writeLatexFileCropped(float llx, float lly, float urx, float ury)
{
    qDebug() << "[File::writeLatexFileCropped] Boundingbox: ("<<llx<<","<<lly<<","<<urx<<","<<ury<<")";

    QString texFileContents = QString(EqualX::__templateLatexCropFile).arg(TEMP_PDF_FILE,TEMP_METADATA_FILE,QString::number(1),
                                                                           QString::number(llx), QString::number(lly),
                                                                           QString::number(urx), QString::number(ury));

    // write generated LaTeX to a temp file
    QFile tmpTexFile( TEMP_LATEX_CROP_FILE );
    tmpTexFile.open(QIODevice::WriteOnly | QIODevice::Text);

    QTextStream out(&tmpTexFile);
    out << texFileContents;
    out.flush();
    tmpTexFile.close();
}

void EqualX::File::writeMetadataFile()
{
    std::string buf;
    mClassImpl->mMetadata.SerializeToBuffer(&buf, kXMP_OmitPacketWrapper);

    //QString metadataStr=QString::fromStdString(buf);

    // write generated metadata to file
    QFile metadatafile( TEMP_METADATA_FILE );
    metadatafile.open(QIODevice::WriteOnly | QIODevice::Text);

    QTextStream out(&metadatafile);
    out << "<?xpacket begin='' id='W5M0MpCehiHzreSzNTczkc9d'?>\n";
    out << buf.c_str();
    out << "<?xpacket end='r'?>";
    out.flush();
    metadatafile.close();

}

bool EqualX::File::fetchInfo(const QString &filename, EqualX::FileInfo *info)
{
    if(!info) info = new EqualX::FileInfo;

    QString fileExt = filename.section(".", -1);

    EqualX::File::OpenModes mode = EqualX::File::OPEN_READ;

    if(fileExt.contains("svg") || fileExt.contains("ps")){
        mode = EqualX::File::OPEN_READ | EqualX::File::OPEN_SCAN;
    }

    EqualX::File f;
    f.open(filename, mode);
    bool readStatus = f.read();
    if(readStatus)
    {
        *info = f.info();
    }

    f.close();


    return readStatus;
}

EqualX::FileInfo EqualX::File::info() const
{
    return mFileInfo;
}

void EqualX::File::setInfo(const EqualX::FileInfo &fileinfo)
{
    mFileInfo = fileinfo;

    mClassImpl->mMetadata.Erase();

    std::string prefix ; // actual prefix

    mClassImpl->mMetadata.RegisterNamespace(METADATA_NS, METADATA_PREFIX, &prefix);

    // escape chars
    QString preamble = mFileInfo.preamble();
    preamble.replace("%", "#perc");
    preamble.replace("\\", "#");

    QString eq = mFileInfo.equation();
    eq.replace("%", "#perc");
    eq.replace("\\", "#");

    QString fgCol, bgCol;

    EqualX::Util::colorToLatex(mFileInfo.fgColor(), fgCol);
    EqualX::Util::colorToLatex(mFileInfo.bgColor(), bgCol);

    mClassImpl->mMetadata.SetProperty(METADATA_NS, METADATA_PREAMBLE, preamble.toStdString().c_str(), 0);
    mClassImpl->mMetadata.SetProperty(METADATA_NS, METADATA_EQUATION, eq.toStdString().c_str(), 0);
    mClassImpl->mMetadata.SetProperty_Int(METADATA_NS, METADATA_ENV, mFileInfo.environment(), 0);
    mClassImpl->mMetadata.SetProperty(METADATA_NS, METADATA_FG, fgCol.toLatin1().constData(), 0);
    mClassImpl->mMetadata.SetProperty(METADATA_NS, METADATA_BG, bgCol.toLatin1().constData(), 0);
    mClassImpl->mMetadata.SetProperty_Int(METADATA_NS, METADATA_DOC_FONT_SIZE, mFileInfo.fontSize(), 0);
}

