#include "Isis.h"

#include <fstream>
#include <iostream>

#include <QString>

#include "Cube.h"
#include "IException.h"
#include "IString.h"
#include "iTime.h"
#include "LineManager.h"
#include "NaifStatus.h"
#include "OriginalLabel.h"
#include "ProcessBySample.h"
#include "ProcessImportFits.h"
#include "Pvl.h"
#include "PvlGroup.h"
#include "PvlKeyword.h"
#include "UserInterface.h"

using namespace std;
using namespace Isis;

void translateLabels(Pvl &fitsLabel, Cube *ocube);
//void flip(Buffer &in);

void IsisMain() {

  UserInterface &ui = Application::GetUserInterface();

  ProcessImportFits importFits;
  importFits.setFitsFile(FileName(ui.GetFileName("FROM")));

  // Get the primary FITS label
  Pvl primaryLabel;
  primaryLabel.addGroup(importFits.fitsImageLabel(0));

  // Make sure this is a New Horizons MVIC image FITS formatted file
  bool mvic = true;
  if (primaryLabel.hasKeyword("MISSION", Pvl::Traverse)) {
    QString mission = primaryLabel.findKeyword("MISSION", Pvl::Traverse);
    if (!mission.contains("New Horizons")) mvic = false;
    if (primaryLabel.hasKeyword("INSTRU", Pvl::Traverse)) {
      QString instrument = primaryLabel.findKeyword("INSTRU", Pvl::Traverse);
      if (!instrument.contains("mvi")) mvic = false;
    }
    else {
      mvic = false;
    }
  }
  else {
    mvic = false;
  }

  if (!mvic) {
    FileName in = ui.GetFileName("FROM");
    QString msg = "Input file [" + in.expanded() + "] does not appear to be " +
                  "in New Horizons/MVIC FITS format.";
    throw IException(IException::User, msg, _FILEINFO_);
  }

  // Check to see if the undistorted image was requested from the FITS file and that it has the 
  // corresponding extension and keywords
  if (ui.WasEntered("UNDISTORTED")) {
    PvlGroup undistortedLabel = importFits.fitsImageLabel(1);
    if (!undistortedLabel.hasKeyword("COMMENT") ||
        !undistortedLabel["COMMENT"][0].startsWith("This is the bias-subtracted, "
                                                   "flattened, distortion-removed image cube.")) {

      QString msg = QObject::tr("Input file [%1] does not appear to contain an MVIC undistorted "
                                "image in XTENSION [2]").arg(ui.GetFileName("FROM"));
      throw IException(IException::User, msg, _FILEINFO_);
    }
  }

  // Check to see if the error image was requested from the FITS file and it has the 
  // corresponding extension and keywords
  if (ui.WasEntered("ERROR")) {
    PvlGroup errorLabel = importFits.fitsImageLabel(2);
    if (!errorLabel.hasKeyword("COMMENT") ||
        errorLabel["COMMENT"][0] != "1-sigma error per pixel for the image in extension 1.") {

      QString msg = QObject::tr("Input file [%1] does not appear to contain an MVIC Error image "
                                "in the XTENSION [3]").arg(ui.GetFileName("FROM"));
      throw IException(IException::User, msg, _FILEINFO_);
    }
  }

  // Check to see if the quality image was requested from the FITS file and it has the 
  // corresponding extension and keywords
  if (ui.WasEntered("QUALITY")) {
    PvlGroup qualityLabel = importFits.fitsImageLabel(3);
    if (!qualityLabel.hasKeyword("COMMENT") ||
        qualityLabel["COMMENT"][0] != "Data quality flag for the image in extension 1.") {

      QString msg = QObject::tr("Input file [%1] does not appear to contain an MVIC Quality image "
                                "in extension [3]").arg(ui.GetFileName("FROM"));
      throw IException(IException::User, msg, _FILEINFO_);
    }
  }

  // Convert the primary image 
  QString bitpix = primaryLabel.findKeyword("BITPIX", Pvl::Traverse);
  int bytesPerPix = abs(toInt(bitpix)) / 8;
  importFits.SetDataPrefixBytes(bytesPerPix * 12);
  importFits.SetDataSuffixBytes(bytesPerPix * 12);
  importFits.setProcessFileStructure(0);

  // Set up the output cube
  Cube *output = importFits.SetOutputCube("TO");

  translateLabels(primaryLabel, output); // Results are put directly into the cube label (output)

  // Save the input FITS label in the output cube original labels
  OriginalLabel originals(primaryLabel);
  output->write(originals);

  // Import the file
  importFits.Progress()->SetText("Importing main MVIC image");
  importFits.StartProcess();
  importFits.EndProcess();


  // Convert the bias-subtracted, flattened, distortion removed image. It is currently assumed 
  // to be the 2rd image in the FITS file (i.e., 1st extension)
  if (ui.WasEntered("UNDISTORTED")) {
    PvlGroup undistortedLabel = importFits.fitsImageLabel(1);

    QString bitpix = undistortedLabel.findKeyword("BITPIX");
    int bytesPerPix = abs(toInt(bitpix)) / 8;
    importFits.SetDataPrefixBytes(bytesPerPix * 12);
    importFits.SetDataSuffixBytes(bytesPerPix * 12);
    importFits.setProcessFileStructure(1);

    Cube *outputError = importFits.SetOutputCube("UNDISTORTED");

    // Save the input FITS label in the Cube original labels
    Pvl pvlError;
    pvlError += undistortedLabel;
    OriginalLabel originals(pvlError);
    outputError->write(originals);

    // Convert the main image data
    importFits.Progress()->SetText("Importing MVIC Undistorted image");
    importFits.StartProcess();
    importFits.ClearCubes();
  }


  // Convert the Error image. It is currently assumed to be the 3rd image in the FITS file
  if (ui.WasEntered("ERROR")) {
    // Get the label of the Error image and make sure this is a New Horizons MVIC Error image
    PvlGroup errorLabel = importFits.fitsImageLabel(2);
    QString bitpix = errorLabel.findKeyword("BITPIX");
    int bytesPerPix = abs(toInt(bitpix)) / 8;
    importFits.SetDataPrefixBytes(bytesPerPix * 12);
    importFits.SetDataSuffixBytes(bytesPerPix * 12);
    importFits.setProcessFileStructure(2);

    Cube *outputError = importFits.SetOutputCube("ERROR");

    // Save the input FITS label in the Cube original labels
    Pvl pvlError;
    pvlError += errorLabel;
    OriginalLabel originals(pvlError);
    outputError->write(originals);

    // Convert the main image data
    importFits.Progress()->SetText("Importing MVIC Error image");
    importFits.StartProcess();
    importFits.ClearCubes();
  }


  // Convert the Quality image. It is currently assumed to be the 4th image in the FITS file
  if (ui.WasEntered("QUALITY")) {
    // Get the label of the Error image and make sure this is a New Horizons MVIC Quality image
    PvlGroup qualityLabel = importFits.fitsImageLabel(3);
    QString bitpix = qualityLabel.findKeyword("BITPIX");
    int bytesPerPix = abs(toInt(bitpix)) / 8;
    importFits.SetDataPrefixBytes(bytesPerPix * 12);
    importFits.SetDataSuffixBytes(bytesPerPix * 12);
    importFits.setProcessFileStructure(3);

    Cube *outputError = importFits.SetOutputCube("QUALITY");

    // Save the input FITS label in the Cube original labels
    Pvl pvlError;
    pvlError += qualityLabel;
    OriginalLabel originals(pvlError);
    outputError->write(originals);

    // Convert the main image data
    importFits.Progress()->SetText("Importing MVIC Quality image");
    importFits.StartProcess();
    importFits.ClearCubes();
  }
}



void translateLabels(Pvl &label, Cube *ocube) {

  // Get the directory where the New Horizons translation tables are.
  PvlGroup dataDir(Preference::Preferences().findGroup("DataDirectory"));
  QString transDir = (QString) dataDir["NewHorizons"] + "/translations/";

  Pvl *isisLabel = ocube->label();
  // Create an Instrument group
  FileName insTransFile(transDir + "mvicInstrument_fit.trn");
  PvlToPvlTranslationManager insXlater(label, insTransFile.expanded());
  insXlater.Auto(*(isisLabel));

  // Modify/add Instument group keywords not handled by the translater
  PvlGroup &inst = isisLabel->findGroup("Instrument", Pvl::Traverse);

  QString instId = (QString)inst["InstrumentId"];
  QString scanType = (QString)inst["ScanType"];
  instId = instId + "_" + scanType;
  inst.addKeyword(PvlKeyword("InstrumentId", instId), PvlGroup::Replace);

  // Not tested because we didn't have any files that do this at the time the test were done
  QString target = (QString)inst["TargetName"];
  if (target.startsWith("RADEC=")) {
    inst.addKeyword(PvlKeyword("TargetName", "Sky"), PvlGroup::Replace);
  }

  if (inst.hasKeyword("TdiRate")) {
    inst.findKeyword("TdiRate").setUnits("hz");
  }

  //  Create StartTime (UTC) from the SpacecraftClockStartCount.  Need to load the leapsecond
  //  and spacecraft clock kernels to calculate time.
  NaifStatus::CheckErrors();
  // Leapsecond kernel
  QString lsk = "$ISIS3DATA/base/kernels/lsk/naif????.tls";
  FileName lskName(lsk);
  lskName = lskName.highestVersion();
  furnsh_c(lskName.expanded().toLatin1().data());

  // Spacecraft clock kernel
  QString sclk = "$ISIS3DATA/newhorizons/kernels/sclk/new_horizons_???.tsc";
  FileName sclkName(sclk);
  sclkName = sclkName.highestVersion();
  furnsh_c(sclkName.expanded().toLatin1().data());

  SpiceInt sclkCode;
  if (label.hasKeyword("SPCSCID", Pvl::Traverse)) {
    sclkCode = label.findKeyword("SPCSCID", Pvl::Traverse);
  }
  else {
    QString msg = "Input file is missing the spacecraft Naif Id.";
    throw IException(IException::User, msg, _FILEINFO_);
  }

  QString scTime = inst["SpacecraftClockStartCount"];
  double et;
  scs2e_c(sclkCode, scTime.toLatin1().data(), &et);
  SpiceChar utc[30];
  et2utc_c(et, "ISOC", 3, 30, utc);
  inst.addKeyword(PvlKeyword("StartTime", QString(utc)));

  // Create a Band Bin group
  FileName bandTransFile(transDir + "mvicBandBin_fit.trn");
  PvlToPvlTranslationManager bandBinXlater(label, bandTransFile.expanded());
  bandBinXlater.Auto(*(isisLabel));
  // Add units and OriginalBand keyword
  PvlGroup &bandBin = isisLabel->findGroup("BandBin", Pvl::Traverse);
  bandBin.addKeyword(PvlKeyword("OriginalBand", "1"));
  PvlKeyword &center = bandBin.findKeyword("Center");
  center.setUnits("nanometers");
  PvlKeyword &width = bandBin.findKeyword("Width");
  width.setUnits("nanometers");

  // If image is in the framing mode, the BandBin keywords will need to be duplicated for each
  // band.  Also, add BandStartTime
  if (scanType.contains("FRAMING")) {
    PvlGroup &bandBin = isisLabel->findGroup("BandBin", Pvl::Traverse);
    QString name = bandBin.findKeyword("Name")[0];
    QString center = QString(bandBin.findKeyword("Center")[0]);
    QString width = QString(bandBin.findKeyword("Width")[0]);
    for (int i = 1; i < ocube->bandCount(); i++) {
      bandBin.findKeyword("Name").addValue(name);
      bandBin.findKeyword("Center").addValue(center, "nanometers");
      bandBin.findKeyword("Width").addValue(width, "nanometers");
      bandBin.findKeyword("OriginalBand").addValue(QString::number(i+1));
      QString fitsKey = QString("UTCMID%1").arg(i, 2, 10, QChar('0'));
      QString fitsVal = label.findKeyword(fitsKey, Pvl::Traverse);
      bandBin.findKeyword("UtcTime").addValue(fitsVal);
    }
  }

  // Create an Archive group
  FileName archiveTransFile(transDir + "mvicArchive_fit.trn");
  PvlToPvlTranslationManager archiveXlater(label, archiveTransFile.expanded());
  archiveXlater.Auto(*(isisLabel));

  // Create a Kernels group
  FileName kernelsTransFile(transDir + "mvicKernels_fit.trn");
  PvlToPvlTranslationManager kernelsXlater(label, kernelsTransFile.expanded());
  kernelsXlater.Auto(*(isisLabel));
  
  // If Level 2 product, Create a RadiometricCalibration group
  if (label.hasKeyword("SOCL2VER", Pvl::Traverse)) {
    FileName calibrationTransFile(transDir + "mvicCalibration_fit.trn"); 
    PvlToPvlTranslationManager calibrationXlater(label, calibrationTransFile.expanded());
    calibrationXlater.Auto(*(isisLabel));

    //  Add comments to calibration keywords.  This is done by hand because the translation tables
    //  did not handle comments at the time this was written.
    PvlGroup &calibration = isisLabel->findGroup("RadiometricCalibration", Pvl::Traverse);
    if (calibration.hasKeyword("PixelSize")) {
      calibration.findKeyword("PixelSize").setUnits("microns");
    }
    if (calibration.hasKeyword("PixelFov")) {
      calibration.findKeyword("PixelFov").setUnits("microrad/pix");
    }
    if (calibration.hasKeyword("Gain")) {
      calibration.findKeyword("Gain").setUnits("electrons/DN");
    }
    if (calibration.hasKeyword("ReadNoise")) {
      calibration.findKeyword("ReadNoise").setUnits("electrons");
    }
    if (calibration.hasKeyword("TdiRate")) {
      calibration.findKeyword("TdiRate").setUnits("hz");
    }
    // The following do not need hasKeyword tests because the translater creats them everytime
    // due to them having default values if none is in the FITS file.
    calibration.findKeyword("SolarSpectrumResolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("SolarSpectrumUnresolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("PholusSpectrumResolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("PholusSpectrumUnresolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("CharonSpectrumResolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("CharonSpectrumUnresolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("JupiterSpectrumResolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("JupiterSpectrumUnresolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("PlutoSpectrumResolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("PlutoSpectrumUnresolved").setUnits("(erg/cm^2/s/sr)/(DN/s/pix)");
    calibration.findKeyword("SolarPivotWavelength").setUnits("cm");
    calibration.findKeyword("JupiterPivotWavelength").setUnits("cm");
    calibration.findKeyword("PholusPivotWavelength").setUnits("cm");
    calibration.findKeyword("PlutoPivotWavelength").setUnits("cm");
    calibration.findKeyword("CharonPivotWavelength").setUnits("cm");
  }
}


//void flip(Buffer &in) {
//  for(int i = 0; i < in.size() / 2; i++) {
//    swap(in[i], in[in.size() - i - 1]);
//  }
//}

