#include "Isis.h"

#include <sstream>

#include <QDebug>

#include "ProcessExport.h"
#include "ProcessByLine.h"
#include "Pvl.h"
#include "PvlToPvlTranslationManager.h"
#include "PvlFormatPds.h"
#include "OriginalLabel.h"
#include "iTime.h"
#include "md5.h"
#include "md5wrapper.h"

#define SCALING_FACTOR 32767

using namespace std;
using namespace Isis;

void ResetGlobals ();
void ProcessImage ( Buffer &in, Buffer &out );
QString MD5Checksum ( QString filename );
void OutputLabel ( std::ofstream &fout, Cube* cube );
void CopyData ( std::ifstream &fin, std::ofstream &fout );

QString g_md5Checksum;

bool g_isIof;

QString g_productVersionId = "N/A";

void IsisMain () {
    ResetGlobals();
    UserInterface &ui = Application::GetUserInterface();

    g_productVersionId = ui.GetString("VERSIONIDSTRING");

    // Set the processing object

    ProcessByLine p;
    Cube *inCube = p.SetInputCube("FROM");

    g_isIof = inCube->label()->findGroup("Radiometry", Pvl::Traverse).findKeyword("RadiometricType")[0].toUpper() == "IOF";

    FileName scaledCube("$TEMPORARY/" + FileName(ui.GetFileName("FROM")).name());
    scaledCube.addExtension("cub");

    scaledCube = FileName::createTempFile(scaledCube);
    p.SetOutputCube(
        scaledCube.expanded(), CubeAttributeOutput(),
        inCube->sampleCount(), inCube->lineCount(),
        inCube->bandCount());

    // Scale image and calculate max and min values
    p.StartProcess(ProcessImage);
    p.EndProcess();

    ProcessExport pe;

    // Setup the input cube
    inCube = pe.SetInputCube(scaledCube.expanded(), CubeAttributeInput());

    if (g_isIof) {
        pe.SetOutputType(Isis::SignedWord);
        pe.SetOutputEndian(Isis::Lsb);

        pe.SetOutputRange(Isis::VALID_MIN2, Isis::VALID_MAX2);

        pe.SetOutputNull(Isis::NULL2);
        pe.SetOutputLrs(Isis::LOW_REPR_SAT2);
        pe.SetOutputLis(Isis::LOW_INSTR_SAT2);
        pe.SetOutputHis(Isis::HIGH_INSTR_SAT2);
        pe.SetOutputHrs(Isis::HIGH_REPR_SAT2);
    }
    else {
        pe.SetOutputType(Isis::Real);
        pe.SetOutputEndian(Isis::Lsb);

        pe.SetOutputRange(Isis::VALID_MIN4, Isis::VALID_MAX4);

        pe.SetOutputNull(Isis::NULL4);
        pe.SetOutputLrs(Isis::LOW_REPR_SAT4);
        pe.SetOutputLis(Isis::LOW_INSTR_SAT4);
        pe.SetOutputHis(Isis::HIGH_INSTR_SAT4);
        pe.SetOutputHrs(Isis::HIGH_REPR_SAT4);
    }

    FileName tempFile;
    tempFile = FileName::createTempFile("$TEMPORARY/" + FileName(ui.GetFileName("TO")).baseName() + ".temp");
    QString tempFileName(tempFile.expanded());
    ofstream temporaryFile(tempFileName.toLatin1().data());

    pe.StartProcess(temporaryFile);
    temporaryFile.close();

    // Calculate MD5 Checksum
    g_md5Checksum = MD5Checksum(tempFileName);

    FileName outFile(ui.GetFileName("TO"));
    QString outFileName(outFile.expanded());
    ifstream inFile(tempFileName.toLatin1().data());
    ofstream pdsFile(outFileName.toLatin1().data());

    // Output the label
    OutputLabel(pdsFile, inCube);

    // Then copy the image data
    CopyData(inFile, pdsFile);

    pdsFile.close();

    pe.EndProcess();

    remove((scaledCube.expanded()).toLatin1().data());
    remove(tempFileName.toLatin1().data());
    return;
}

void ResetGlobals () {
    g_md5Checksum = "";
    g_isIof = false;
}

void ProcessImage ( Buffer &in, Buffer &out ) {
    for (int i = 0; i < in.size(); i++) {
        if (IsSpecial(in[i]))
            out[i] = in[i];
        else if (g_isIof)
            out[i] = SCALING_FACTOR * in[i];
        else
            out[i] = in[i];
    }
}

QString MD5Checksum ( QString filename ) {
    md5wrapper md5;
    QString checkSum = md5.getHashFromFile(filename);
    return checkSum;
}

void OutputLabel ( std::ofstream &fout, Cube* cube ) {
    OriginalLabel origLab(cube->fileName());
    Pvl labelPvl = origLab.ReturnLabels();

    //Pvl to store the labels
    Pvl outLabel;
    PvlFormatPds *p_formatter = new PvlFormatPds("$lro/translations/pdsExportRootGen.typ");
    labelPvl.setFormat(p_formatter);
    labelPvl.setTerminator("END");
    //Set up the directory where the translations are
    PvlGroup dataDir(Preference::Preferences().findGroup("DataDirectory"));
    QString transDir = (QString) dataDir["Lro"] + "/translations/";

    stringstream stream;
    QString pdsLabel = "";

    //Translate the Original Pds Label
    FileName transFile(transDir + "lronacPdsLabelExport.trn");
    PvlToPvlTranslationManager labelXlator(labelPvl, transFile.expanded());
    labelXlator.Auto(outLabel);

    // Copy any Translation changes over
    for (int i = 0; i < outLabel.keywords(); i++) {
        bool hasUnit = false;
        QString unit = "";
        if (labelPvl[outLabel[i].name()].unit() != "") {
            hasUnit = true;
            unit = labelPvl[outLabel[i].name()].unit();
        }
        bool hasComment = false;
        QString comment = "";
        if (labelPvl[outLabel[i].name()].comments() > 0) {
            hasComment = true;
            comment = labelPvl[outLabel[i].name()].comment(0);
        }
        labelPvl[outLabel[i].name()] = outLabel[i];

        if (hasUnit)
            labelPvl[outLabel[i].name()].setUnits(unit);
        if (hasComment)
            labelPvl[outLabel[i].name()].addComment(comment);
    }

    //Update the product ID
    //we switch the last char in the id from edr->cdr
    QString prod_id = labelPvl["PRODUCT_ID"][0];
    labelPvl["PRODUCT_ID"][0].replace((prod_id.length()-1), 1, "C");

    // Update the product creation time
    labelPvl["PRODUCT_CREATION_TIME"].setValue(iTime::CurrentGMT());

    labelPvl["PRODUCT_VERSION_ID"].setValue(g_productVersionId);

    // Update the "IMAGE" Object
    PvlObject &imageObject = labelPvl.findObject("IMAGE");
    imageObject.clear();
    imageObject += PvlKeyword("LINES", toString(cube->lineCount()));
    imageObject += PvlKeyword("LINE_SAMPLES", toString(cube->sampleCount()));
    if (g_isIof) {
        imageObject += PvlKeyword("SAMPLE_BITS", toString(16));
        imageObject += PvlKeyword("SAMPLE_TYPE", "LSB_INTEGER");
        imageObject += PvlKeyword("SCALING_FACTOR", toString(1.0 / SCALING_FACTOR));
        imageObject += PvlKeyword("VALID_MINIMUM", toString(Isis::VALID_MIN2));
        imageObject += PvlKeyword("NULL", toString(Isis::NULL2));
        imageObject += PvlKeyword("LOW_REPR_SATURATION", toString(Isis::LOW_REPR_SAT2));
        imageObject += PvlKeyword("LOW_INSTR_SATURATION", toString(Isis::LOW_INSTR_SAT2));
        imageObject += PvlKeyword("HIGH_INSTR_SATURATION", toString(Isis::HIGH_INSTR_SAT2));
        imageObject += PvlKeyword("HIGH_REPR_SATURATION", toString(Isis::HIGH_REPR_SAT2));
        imageObject += PvlKeyword("UNIT", "Scaled I/F");
    }
    else {
        imageObject += PvlKeyword("SAMPLE_BITS", "32");
        imageObject += PvlKeyword("SAMPLE_TYPE", "PC_REAL");
        imageObject += PvlKeyword("VALID_MINIMUM", "16#FF7FFFFA#");
        imageObject += PvlKeyword("NULL", "16#FF7FFFFB#");
        imageObject += PvlKeyword("LOW_REPR_SATURATION", "16#FF7FFFFC#");
        imageObject += PvlKeyword("LOW_INSTR_SATURATION", "16#FF7FFFFD#");
        imageObject += PvlKeyword("HIGH_INSTR_SATURATION", "16#FF7FFFFE#");
        imageObject += PvlKeyword("HIGH_REPR_SATURATION", "16#FF7FFFFF#");
        imageObject += PvlKeyword("UNIT", "W / (m**2 micrometer sr)");
    }
    imageObject += PvlKeyword("MD5_CHECKSUM", g_md5Checksum);

    stream << labelPvl;

    int recordBytes = cube->sampleCount();
    int labelRecords = (int) ((stream.str().length()) / recordBytes) + 1;

    labelPvl["RECORD_BYTES"] = toString(recordBytes);
    if (g_isIof)
        labelPvl["FILE_RECORDS"] = toString((int) (cube->lineCount() * 2 + labelRecords));
    else
        labelPvl["FILE_RECORDS"] = toString((int) (cube->lineCount() * 4 + labelRecords));
    labelPvl["LABEL_RECORDS"] = toString(labelRecords);
    labelPvl["^IMAGE"] = toString((int) (labelRecords + 1));

    stream.str(std::string());

    stream << labelPvl;
    pdsLabel += stream.str().c_str();
    /* Ensure that we have enough room for the actual label content, plus at
     * least two bytes for a carriage return and a linefeed, so the end of the
     * label looks pretty */
    while ((int)pdsLabel.length() + 2 > (int)(labelRecords * recordBytes)) {
        labelRecords++;
        // Refresh the label content
        labelPvl["FILE_RECORDS"] = toString((int) (cube->lineCount() * 4 + labelRecords));
        labelPvl["LABEL_RECORDS"] = toString(labelRecords);
        labelPvl["^IMAGE"] = toString((int) (labelRecords + 1));
        stream.str(std::string());
        stream << labelPvl;
        pdsLabel = stream.str().c_str();
    }

    /* Now, add a carriage return and linefeed, and then pad the label with
     * spaces if necessary */
    pdsLabel += "\r\n";


    while ((int)pdsLabel.length() < (int) (labelRecords * recordBytes)) {
        pdsLabel += " ";
    }

    fout << pdsLabel;
    return;
}

void CopyData ( std::ifstream &fin, std::ofstream &fout ) {
    char line[2532];
    while (!fin.eof()) {
        fin.read(line, 2532);
        fout.write(line, fin.gcount());
    }
}

