#include <string>
#include <iostream>
#include <iomanip>
#include <vector>
#include <functional>

#include "JDB/JDB.hh"
#include "JDB/JSelector.hh"
#include "JDB/JSelectorSupportkit.hh"
#include "JDB/JAHRSCalibration.hh"
#include "JDB/JAHRSCalibrationToolkit.hh"
#include "JDB/JDBToolkit.hh"

#include "JDB/JDetectorIntegration_t.hh"
#include "JDB/JPBSSequence.hh"
#include "JDB/JProductRouter.hh"

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

/**
 * \file
 *
 * Example program for compass calibration.
 * \author mdejong
 */
int main(int argc, char **argv)
{
  using namespace std;
  using namespace JPP;

  JServer     server;
  string      usr;
  string      pwd;
  string      cookie;
  string      detid;
  int         debug;

  try {

    JParser<> zap("Example program for compass calibration.");
    
    zap['s'] = make_field(server)     = getServernames();
    zap['u'] = make_field(usr)        = "";
    zap['!'] = make_field(pwd)        = "";
    zap['C'] = make_field(cookie)     = "";
    zap['D'] = make_field(detid,        "Detector identifier")  = "";
    zap['d'] = make_field(debug)      = 2;

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


  vector<JAHRSCalibration> calibration;
  
  try {

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

    ResultSet& rs  = getResultSet(getTable<JAHRSCalibration>());

    rs >> calibration;

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

  const JAHRSCalibrationValidity is_valid;  

  JDBToolkit::initialise(getUPI, PBS::AHRS);


  JDetectorIntegration_t detector;

  if (detid != "") {

    DEBUG("Reading database table " << getTable<JDetectorIntegration_t>() << endl);

    try {

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

      detid = getDetector<string>(detid);

      ResultSet& rs  = getResultSet(getTable<JDetectorIntegration_t>());

      if (! (rs >> detector)) {
	THROW(JDatabaseException, "Error reading " << getTable<JDetectorIntegration_t>());
      }
    }
    catch(const exception& error) {
      FATAL(error.what() << endl);
    }

    detector.configure(detid);
  }


  const JProductRouter router(detector, getPBSSequences(PBS::AHRS));

  sort(calibration.begin(), calibration.end(), [](const JAHRSCalibration& first, const JAHRSCalibration& second) { return !JAHRSCalibrationComparator()(first, second); });


  map<JLocation_t, JAHRSCalibration> zmap;

  for (vector<JAHRSCalibration>::const_iterator p = calibration.begin(); p != calibration.end(); ) {

    vector<JAHRSCalibration>::const_iterator q = p; 

    for (++q; q != calibration.end() && q->SERIALNUMBER == p->SERIALNUMBER; ++q) {}

    const JUPI_t      upi      = getUPI(PBS::AHRS, p->SERIALNUMBER);
    const JLocation_t location = router.getLocation(upi);

    if (location.is_valid()) {

      for (vector<JAHRSCalibration>::const_iterator i = p; i != q; ++i) {

	if (is_valid(*i)) {

	  if (i != p) {
	    WARNING("AHRS " << location << ' ' << upi << ' '
		    << "invalid calibration"          << ' '
		    << p->TESTEND << " v" << getVersion(p->TESTNAME) << " -> "
		    << i->TESTEND << " v" << getVersion(i->TESTNAME) << endl);
	  }

	  zmap[location] = *p;

	  break;
	}
      }
    }

    for (vector<JAHRSCalibration>::const_iterator i = p; i != q; ++i) {

      const bool valid = is_valid(*i) || (i != p);

      if (debug >= debug_t) {

	cout << setw(8)  << i->SERIALNUMBER         << ' '
	     << setw(22) << left << upi << right    << ' '
	     << setw(8)  << i->TESTEND              << ' '
	     << setw(8)  << i->FIRMWARE_VERSION     << ' '
	     << setw(2)  << getVersion(i->TESTNAME) << ' '
	     << setw(10) << i->TESTOPID             << ' ' 
	     << (valid ? "" : "*") << endl;

	if (!valid) {
	  //cout << " -> " << *i << endl;
	}
      }
    }

    p = q;
  }

  for (map<JLocation_t, JAHRSCalibration>::const_iterator i = zmap.begin(); i != zmap.end(); ++i) {

    const JLocation_t&      location    = i->first;
    const JAHRSCalibration& calibration = i->second;
    const JUPI_t            upi         = getUPI(PBS::AHRS, calibration.SERIALNUMBER);

    cout << location << "  ";

    JDetectorIntegration_t::range_type r1, r2;
    
    r1 = detector.find(upi);

    if (distance(r1.first, r1.second) == 1) {

      r2 = detector.find(detector[r1.first->second].container.getUPI());

      if (distance(r2.first, r2.second) == 1) {
	cout << setw(22) << left << detector[r2.first->second].container.getUPI() << right << ' ';
	cout << setw(22) << left << detector[r1.first->second].container.getUPI() << right << ' ';
	cout << setw(22) << left << upi                                           << right << ' ';
      }
    }

    cout << setw(3)  << (calibration.FIRMWARE_VERSION != "" ?
			 calibration.FIRMWARE_VERSION :
			 "?")           		 << ' ';
    cout << setw(2)  << getVersion(calibration.TESTNAME) << ' ';
    cout << setw(10) << calibration.TESTOPID             << ' ';
    cout << endl;
  }

  return 0;
}