#include "Isis.h"
#include "Blob.h"
#include "Cube.h"
#include "CubeAttribute.h"
#include "LineManager.h"
#include "Progress.h"
#include "SpecialPixel.h"

using namespace Isis;
using namespace std;

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

  CubeAttributeInput inAtt = ui.GetInputAttribute("FROM");
  Cube icube;

  if (inAtt.bands().size() != 0) {
    icube.setVirtualBands(inAtt.bands());
  }

  icube.open(FileName(ui.GetFileName("FROM")).expanded());
  
  // Make sure it is a Themis EDR/RDR
  FileName inFileName = ui.GetFileName("FROM");
  try {
    if(icube.group("Instrument")["InstrumentID"][0] != "THEMIS_VIS") {
      QString msg = "This program is intended for use on THEMIS VIS images only. [";
      msg += inFileName.expanded() + "] does not appear to be a THEMIS VIS image.";
      throw IException(IException::User, msg, _FILEINFO_);
    }
  }
  catch(IException &e) {
    QString msg = "This program is intended for use on THEMIS VIS images only. [";
    msg += inFileName.expanded() + "] does not appear to be a THEMIS VIS image.";
    throw IException(e, IException::User, msg, _FILEINFO_);
  }
  
  vector<Cube *> flatcubes;
  vector<LineManager *> fcubeMgrs;
  int summing = toInt(icube.group("Instrument")["SpatialSumming"][0]);

  for(int filt = 0; filt < 5; filt++) {
    QString filePattern = "$odyssey/calibration/flat_filter_";
    filePattern += toString(filt + 1) + "_summing_";
    filePattern += toString(summing) + "_v????.cub";
    FileName flatFile = FileName(filePattern).highestVersion();
    Cube *fcube = new Cube();
    fcube->open(flatFile.expanded());
    flatcubes.push_back(fcube);

    LineManager *fcubeMgr = new LineManager(*fcube);
    fcubeMgr->SetLine(1, 1);
    fcubeMgrs.push_back(fcubeMgr);
  }

  Cube ocube;

  CubeAttributeOutput outAtt = ui.GetOutputAttribute("TO");
  ocube.setDimensions(icube.sampleCount(), icube.lineCount(), icube.bandCount());
  ocube.setByteOrder(outAtt.byteOrder());
  ocube.setFormat(outAtt.fileFormat());
  ocube.setLabelsAttached(outAtt.labelAttachment() == AttachedLabel);
  ocube.setPixelType(outAtt.pixelType());

  ocube.create(FileName(ui.GetFileName("TO")).expanded());

  LineManager icubeMgr(icube);
  vector<int> filter;

  PvlKeyword &filtNums =
      icube.label()->findGroup("BandBin", Pvl::Traverse)["FilterNumber"];
  for(int i = 0; i < filtNums.size(); i++) {
    filter.push_back(toInt(filtNums[i]));
  }

  LineManager ocubeMgr(ocube);
  ocubeMgr.SetLine(1, 1);

  Progress prog;
  prog.SetText("Applying Flat-Field Correction");
  prog.SetMaximumSteps(ocube.lineCount() * ocube.bandCount());
  prog.CheckStatus();

  do {
    icube.read(icubeMgr);
    ocube.read(ocubeMgr);

    int fcubeIndex = filter[ocubeMgr.Band()-1] - 1;
    flatcubes[fcubeIndex]->read((*fcubeMgrs[fcubeIndex]));

    for(int i = 0; i < ocubeMgr.size(); i++) {
      if(IsSpecial((*fcubeMgrs[fcubeIndex])[i]) || (*fcubeMgrs[fcubeIndex])[i] == 0.0) {
        ocubeMgr[i] = Isis::Null;
      }
      else if(IsSpecial(icubeMgr[i])) {
        ocubeMgr[i] = icubeMgr[i];
      }
      else {
        ocubeMgr[i] = icubeMgr[i] / (*fcubeMgrs[fcubeIndex])[i];
      }
    }

    ocube.write(ocubeMgr);

    icubeMgr++;
    ocubeMgr++;

    for(int i = 0; i < (int)fcubeMgrs.size(); i++) {
      (*fcubeMgrs[i]) ++;

      if(fcubeMgrs[i]->end()) {
        fcubeMgrs[i]->SetLine(1, 1);
      }
    }

    prog.CheckStatus();
  }
  while (!ocubeMgr.end());

  // Propagate labels and objects (in case of spice data)
  PvlObject &inCubeObj = icube.label()->findObject("IsisCube");
  PvlObject &outCubeObj = ocube.label()->findObject("IsisCube");

  for(int g = 0; g < inCubeObj.groups(); g++) {
    outCubeObj.addGroup(inCubeObj.group(g));
  }

  for(int o = 0; o < icube.label()->objects(); o++) {
    if(icube.label()->object(o).isNamed("Table")) {
      Blob t(icube.label()->object(o)["Name"],
             icube.label()->object(o).name());
      icube.read(t);
      ocube.write(t);
    }
  }

  icube.close();
  ocube.close();

  for(int i = 0; i < (int)flatcubes.size(); i++) {
    delete fcubeMgrs[i];
    delete flatcubes[i];
  }

  fcubeMgrs.clear();
  flatcubes.clear();
}

