#include "Isis.h"

// system include files go first
#include <string>
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>

// Isis specific include files go next
#include "ProcessByLine.h"
#include "SpecialPixel.h"
#include "IException.h"
#include "Pvl.h"
#include "TextFile.h"
#include "Statistics.h"

using namespace std;
using namespace Isis;

static vector<int> band;
static vector<double> average;
static vector<double> normalizer;

// function prototypes
void getStats(Buffer &in);
void normalize(Buffer &in, Buffer &out);
void Tokenize(const QString &str,
              vector<QString> & tokens,
              const QString &delimiters = " ");

void IsisMain() {
  // We will be processing by line
  ProcessByLine p;

  // Setup the input cube
  UserInterface &ui = Application::GetUserInterface();
  Cube *icube = p.SetInputCube("FROM");

  // Now get the statistics for each band or the entire cube
  QString avg = ui.GetString("AVERAGE");
  p.StartProcess(getStats);
  if(avg == "BAND") {
    int b = 0;
    Statistics stats;
    for(int i = 0; i < (int)average.size(); i++) {
      if(b == band[i]) {
        stats.AddData(&average[i], (unsigned int)1);
      }
      else {
        normalizer.push_back(stats.Average());
        b++;
        stats.Reset();
      }
    }
    normalizer.push_back(stats.Average());
  }
  else if(avg == "PENCIL") {
    TextFile pencil;
    pencil.Open(ui.GetFileName("SPECTRUM"));
    if(pencil.LineCount() - 1 < icube->bandCount()) {
      QString msg = "The spectral pencil file [" + ui.GetAsString("SPECTRUM") +
                    "] does not contain enough data for all bands.";
      throw IException(IException::User, msg, _FILEINFO_);
    }
    QString st;
    int column = -1;
    vector<QString> tokens;
    pencil.GetLine(st);     //Takes care of title line
    Tokenize(st, tokens, ", \"-+");
    if(ui.GetAsString("METHOD") == "number") {
      column = ui.GetInteger("NUMBER");
    }
    else {
      for(unsigned i = 0; i < tokens.size(); i++) {
        if(tokens[i] == ui.GetString("NAME")) {
          column = i;
          break;
        }
      }
    }
    if(column < 0  || (unsigned)column > tokens.size()) {
      QString msg = "The column specified in file [" + ui.GetFileName("SPECTRUM")
                    + "] was not found.";
      throw IException(IException::User, msg, _FILEINFO_);
    }
    // Add the correct column of data to normalizer
    for(int i = 0; i < icube->bandCount(); i++) {
      tokens.clear();
      pencil.GetLine(st);
      Tokenize(st, tokens, ", \"");
      normalizer.push_back(Isis::IString(tokens[column]).ToDouble());
    }
  }
  else {  // avg == "CUBE"
    Statistics stats;
    for(int i = 0; i < (int)average.size(); i++) {
      stats.AddData(&average[i], (unsigned int)1);
    }
    for(int b = 0; b < icube->bandCount(); b++) {
      normalizer.push_back(stats.Average());
    }
  }

  // Setup the output file and apply the correction
  p.SetOutputCube("TO");
  p.StartProcess(normalize);

  // Cleanup
  p.EndProcess();
  normalizer.clear();
  band.clear();
  average.clear();
}

//**********************************************************
// Get statistics on a band or entire cube
//**********************************************************
void getStats(Buffer &in) {
  Statistics stats;
  stats.AddData(in.DoubleBuffer(), in.size());
  average.push_back(stats.Average());
  band.push_back(in.Band() - 1);
}

// Apply coefficients
void normalize(Buffer &in, Buffer &out) {
  int index = in.Band() - 1;
  double coeff = normalizer[index];

  // Now loop and apply the coefficents
  for(int i = 0; i < in.size(); i++) {
    if(IsSpecial(in[i])) {
      out[i] = in[i];
    }
    else {
      out[i] = Null;
      if(coeff != 0.0 && IsValidPixel(coeff)) {
        out[i] = in[i] / coeff;
      }
    }
  }
}

// Tokenizer
void Tokenize(const QString &strQStr,
              vector<QString> & tokens,
              const QString &delimitersQStr) {
  IString str = strQStr;
  IString delimiters = delimitersQStr;

  //Skip delimiters at the beginning
  string::size_type lastPos = str.find_first_not_of(delimiters, 0);
  // Find first "non-delimiter".
  string::size_type pos     = str.find_first_of(delimiters, lastPos);

  while(string::npos != pos  ||  string::npos != lastPos) {
    // Found a token, add it to the vector
    tokens.push_back(str.substr(lastPos, pos - lastPos).c_str());
    // Skip delimiters
    lastPos = str.find_first_not_of(delimiters, pos);
    // Find next "non-delimiter"
    pos = str.find_first_of(delimiters, lastPos);
  }
}
