#include <string>
#include <iostream>
#include <iomanip>
#include <fstream>

#include "JDB/JDB.hh"
#include "JDB/JSelector.hh"
#include "JDB/JSelectorSupportkit.hh"
#include "JDB/JDBToolkit.hh"
#include "JDB/JDetectors.hh"
#include "JDB/JPersons.hh"
#include "JDB/JRuns.hh"

#include "JDetector/JDetector.hh"
#include "JDetector/JDetectorCalibration.hh"
#include "JDetector/JDetectorToolkit.hh"
#include "JDetector/JDetectorSupportkit.hh"

#include "JTools/JRange.hh"

#include "JSon/JSon.hh"
#include "JSon/JSupport.hh"

#include "JLang/JVectorize.hh"

#include "Jeep/JeepToolkit.hh"
#include "Jeep/JPrint.hh"
#include "Jeep/JParser.hh"
#include "Jeep/JMessage.hh"

namespace {

  using JSON::json;

  /**
   * Invalid UTC.
   */
  static const time_t UTC_INVALID_S = -1;
  
  /**
   * Invalid run.
   */
  static const int    RUN_INVALID   = -1;
  
  /**
   * Get JSon object for given key and UTC time.
   *
   * \param  key              key
   * \param  utc_s            UTC time [s]
   * \return                  JSon object
   */
  inline json getJSon(const std::string& key, const time_t utc_s)
  {
    if (utc_s != UTC_INVALID_S)
      return json({key, JPP::JDateAndTime(utc_s, true).toString() });
    else
      return json({key, json() });
  }
}


/**
 * \file
 *
 * Auxiliary program to decompose detector to separate calibrations.
 *
 * Note that the validity range is defined in the following order.
 *   -# option <tt>-V "<UTC min> <UTC max>"</tt>;
 *   -# option <tt>-r "<first run> <last run>"</tt>;
 *
 * \author mdejong
 */
int main(int argc, char **argv)
{
  using namespace std;
  using namespace JPP;

  typedef JRange<time_t>  JRange_t;

  JServer         server;
  string          usr;
  string          pwd;
  string          cookie;
  string          detectorFile;
  string          outputFile;
  JRange_t        validity;
  JRange_t        range;
  double          precision;
  int             debug;

  try {

    JParser<> zap("Auxiliary program to decompose detector to separate calibrations.");

    zap['s'] = make_field(server)        = getServernames();
    zap['u'] = make_field(usr)           = "";
    zap['!'] = make_field(pwd)           = "";
    zap['C'] = make_field(cookie)        = "";
    zap['a'] = make_field(detectorFile, "detector file");
    zap['o'] = make_field(outputFile,   "detector calibrations file in JSON format."\
                          "\nFile name should contain wild card \'" << FILENAME_WILDCARD << "\' or"\
                          "\none of the key words: " << JEEPZ() << make_array(getCalibrationType.begin(),
									      getCalibrationType.end(),
									      &JCalibrationType::nick_name));
    zap['V'] = make_field(validity,     "validity range UTC [s], e.g. \"<UTC min> <UTC max>\"")  = JRange_t(UTC_INVALID_S, UTC_INVALID_S);
    zap['r'] = make_field(range,        "run range e.g. \"<first run> <last run>\"")             = JRange_t(RUN_INVALID,   RUN_INVALID);
    zap['p'] = make_field(precision,    "precision for match with reference module")             = 1.0e-5;
    zap['d'] = make_field(debug)         = 1;

    zap(argc, argv);
  }
  catch(const exception &error) {
    FATAL(error.what() << endl);
  }



  JDetector detector;

  try {
    load(detectorFile, detector);
  }
  catch(const JException& error) {
    FATAL(error);
  }

  if (!hasDetectorAddressMap(detector.getID())) {
    FATAL("No detector address map for detector identier " << detector.getID() << endl);
  }

  const JDetectorBuilder& demo = getDetectorBuilder(detector.getID());

  string userid = "";
  string detoid = "";
  string locid  = "";

  try {

    JDB::reset(usr, pwd, cookie);

    {
      ResultSet& rs  = getResultSet(JPersons::getName(), getSelector<JPersons>(JDB::get()->User()));

      for (JPersons object; rs >> object; ) {
	userid  = object.OID;
      }

      rs.Close();
    }
    {
      ResultSet& rs  = getResultSet(JDetectors::getName(), getSelector<JDetectors>(detector.getID()));
    
      for (JDetectors object; rs >> object; ) {
	locid   = object.LOCATIONID;
	detoid  = object.OID;
      }

      rs.Close();
    }

    if (range.getLowerLimit() != RUN_INVALID && validity.getLowerLimit() == UTC_INVALID_S) {

      ResultSet& rs  = getResultSet(JRuns::getName(), getSelector<JRuns>(detector.getID(), range.getLowerLimit()));
    
      for (JRuns object; rs >> object; ) {
	validity.setLowerLimit(object.UNIXJOBSTART / 1000);
      }

      rs.Close();   
    }

    if (range.getUpperLimit() != RUN_INVALID && validity.getUpperLimit() == UTC_INVALID_S) {

      ResultSet& rs  = getResultSet(JRuns::getName(), getSelector<JRuns>(detector.getID(), range.getUpperLimit()));
    
      for (JRuns object; rs >> object; ) {
	validity.setUpperLimit(object.UNIXJOBEND   / 1000);
      }

      rs.Close();   
    }
  }
  catch(const exception& error) {
    FATAL(error.what() << endl);
  }


  // Test compatibility of modules with reference(s).

  for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {

    if (module->getFloor() != 0) {

      JModule buffer = demo.getModule(module->getID(), module->getLocation());

      const JRotation3D R = getRotation(buffer, *module);

      buffer.rotate(R);

      if (!JModule::compare(buffer, *module, precision)) {
	FATAL("Module " << setw(10) <<  module->getID() << ' ' << module->getLocation() << " incompatible with reference." << endl);
      }
    }
  }

  const json header = { { UserId_t,      userid        },
			{ TypeId_t,      json()        },
			{ LocationId_t,  locid         },
			{ DetOID_t,      detoid        },
			{ StartTime_t,   JDateAndTime(true).toString() },
			{ EndTime_t,     JDateAndTime(true).toString() },
			getJSon(ValidFrom_t,    validity.getLowerLimit()),
			getJSon(ValidThrough_t, validity.getUpperLimit()) };

  const json error  = { {Message_t,   ""            },
			{Code_t,      OK_t          },
			{Arguments_t, json::array() } };


  json   js;

  js[Comment_t]                            = json(detector.comment);
  js[Error_t]                              = json(error); 

  if (hasWildCard(outputFile.c_str()) || outputFile.find(TCAL) != string::npos) {

    JPMTCalibration    data;

    for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {
      for (JModule::const_iterator pmt = module->begin(); pmt != module->end(); ++pmt) {
	data.push_back(JPMTCalibration_t(pmt->getID(), getCalibration(JCalibration(), *pmt)));
      }
    }

    js[Data_t][0]                          = json(header);
    js[Data_t][0][PMTT0s_t]                = json(data);    

    store(hasWildCard(outputFile) ? setWildCard(outputFile, TCAL) : outputFile, js);
  }

  if (hasWildCard(outputFile) || outputFile.find(PCAL) != string::npos) {

    JModulePosition    data[2];

    for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {

      const JModule& buffer = demo.getModule(module->getID(), module->getLocation());

      if (buffer.getFloor() == 0)
	data[0].push_back(JModulePosition_t(module->getID(), getPosition(buffer, *module)));
      else
	data[1].push_back(JModulePosition_t(module->getID(), getPosition(buffer, *module)));
    }

    js[Data_t][0]                          = json(header);
    js[Data_t][0][BasePositions_t]         = json(data[0]);
    js[Data_t][0][DOMPositions_t]          = json(data[1]);

    store(hasWildCard(outputFile) ? setWildCard(outputFile, PCAL) : outputFile, js);
  }

  if (hasWildCard(outputFile) || outputFile.find(RCAL) != string::npos) {

    JModuleRotation    data;

    for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {

      const JModule& buffer = demo.getModule(module->getID(), module->getLocation());

      if (module->getFloor() != 0) {
	data.push_back(JModuleRotation_t(module->getID(), getRotation(buffer, *module)));
      }
    }

    js[Data_t][0]                          = json(header);
    js[Data_t][0][DOMRotations_t]          = json(data);    

    store(hasWildCard(outputFile) ? setWildCard(outputFile, RCAL) : outputFile, js);
  }

  if (hasWildCard(outputFile) || outputFile.find(ACAL) != string::npos) {

    JModuleCalibration data[2];

    for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {

      if (module->getFloor() == 0)
	data[0].push_back(JModuleCalibration_t(module->getID(), module->getT0()));
      else
	data[1].push_back(JModuleCalibration_t(module->getID(), module->getT0()));
    }

    js[Data_t][0]                          = json(header);
    js[Data_t][0][BaseAcousticT0_t]        = json(data[0]);    
    js[Data_t][0][DOMAcousticT0_t]         = json(data[1]);    

    store(hasWildCard(outputFile) ? setWildCard(outputFile, ACAL) : outputFile, js);
  }

  if (hasWildCard(outputFile) || outputFile.find(CCAL) != string::npos) {

    JCompassRotation   data[2];
 
    for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {
      if (module->getFloor() == 0)
	data[0].push_back(JCompassRotation_t(module->getID(), module->getQuaternion()));
      else
	data[1].push_back(JCompassRotation_t(module->getID(), module->getQuaternion()));
    }

    js[Data_t][0]                          = json(header);
    js[Data_t][0][BaseCompassRotations_t]  = json(data[0]);    
    js[Data_t][0][DOMCompassRotations_t]   = json(data[1]);    
 
    store(hasWildCard(outputFile) ? setWildCard(outputFile, CCAL) : outputFile, js);
  }

  if (hasWildCard(outputFile) || outputFile.find(SCAL) != string::npos) {

    js[Data_t][0]                          = json(header);

    {
      JPMTStatus         data;

      for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {
	for (JModule::const_iterator pmt = module->begin(); pmt != module->end(); ++pmt) {
	  data.push_back(JPMTStatus_t(pmt->getID(), pmt->getStatus()));
	}
      }

      js[Data_t][0][PMTStatusInfo_t ]      = json(data);
    }
    {
      JModuleStatus      data[2];

      for (JDetector::const_iterator module = detector.begin(); module != detector.end(); ++module) {
	if (module->getFloor() == 0)
	  data[0].push_back(JModuleStatus_t(module->getID(), module->getStatus()));
	else
	  data[1].push_back(JModuleStatus_t(module->getID(), module->getStatus()));
      }

      js[Data_t][0][BaseStatusInfo_t]      = json(data[0]);
      js[Data_t][0][DOMStatusInfo_t]       = json(data[1]);
    }

    store(hasWildCard(outputFile) ? setWildCard(outputFile, SCAL) : outputFile, js);
  }

  return 0;
}