/*
 * This file is part of the Owl Positioning System (OwlPS) project.
 * It is subject to the copyright notice and license terms in the
 * COPYRIGHT.t2t file found in the top-level directory of this
 * distribution and at
 *   https://code.lm7.fr/mcy/owlps/src/master/COPYRIGHT.t2t
 * No part of the OwlPS Project, including this file, may be copied,
 * modified, propagated, or distributed except according to the terms
 * contained in the COPYRIGHT.t2t file; the COPYRIGHT.t2t file must be
 * distributed along with this file, either separately or by replacing
 * this notice by the COPYRIGHT.t2t file's contents.
 */


#include "input.hh"
#include "inputcsv.hh"
#include "inputudpsocket.hh"
#include "inputlogcsv.hh"
#include "calibrationrequest.hh"
#include "configuration.hh"
#include "posexcept.hh"
#include "stock.hh"

#include <iostream>
#include <string>

using namespace std ;



/* *** Constructors *** */


Input::Input():
  medium(nullptr)
{
  initialise_input_medium() ;
  initialise_log_media() ;
}


Input::~Input()
{
  delete medium ;

  for (auto i = log_media.begin() ; i != log_media.end() ; ++i)
    delete *i ;
  log_media.clear() ;
}



/* *** Operations *** */


void Input::initialise_input_medium()
{
  if (! Configuration::is_configured("input.medium"))
    throw missing_configuration(
      "No input medium specified in configuration!") ;

  const string &medium_name =
    Configuration::string_value("input.medium") ;

  if (medium_name == "CSV")
    {
      if (! Configuration::is_configured("input.csv-file"))
        throw missing_configuration(
          "No input CSV file specified in the configuration!") ;
      medium = new
      InputCSV(Configuration::string_value("input.csv-file")) ;
    }

  else if (medium_name == "UDP")
    {
      if (! Configuration::is_configured("input.udp-port"))
        throw missing_configuration(
          "No input UDP port specified in the configuration!") ;
      medium = new
      InputUDPSocket(Configuration::int_value("input.udp-port")) ;
    }

  else
    throw bad_configuration(
      "The specified input medium \""+ medium_name +"\" is unknown!") ;
}


void Input::initialise_log_media()
{
  if (! Configuration::is_configured("log.medium"))
    return ;

  if (Configuration::value_exists_in_string_vector("log.medium", "none"))
    return ;

  const vector<string> &media_names =
    Configuration::string_vector_value("log.medium") ;

  for (auto i = media_names.begin() ; i != media_names.end() ; ++i)
    {
      if (*i == "CSV")
        initialise_log_csv() ;

      else
        throw bad_configuration(
          "The input medium \""+ *i +
          "\" specified for request logging is unknown!") ;
    }
}


void Input::initialise_log_csv()
{
  if (! Configuration::is_configured("log.csv-file"))
    throw missing_configuration(
      "No log CSV file specified in the configuration!") ;

  log_media.push_back(
    new InputLogCSV(
      Configuration::string_value("log.csv-file"))) ;
}


const Request& Input::get_next_request() const
{
  if (eof())
    return medium->get_current_request() ;

  const Request& request = medium->get_next_request() ;
  log_current_request() ;

  if (! request)
    return medium->get_current_request() ;

  /* Update the current time */
  if (Configuration::bool_value("replay"))
    Timestamp::update_current_time(request.get_time_sent()) ;

  /* Clean the old requests */
  unsigned int cr_timeout =
    Configuration::uint_value(
      "positioning.calibration-requests-timeout") ;
  if (cr_timeout > 0)
    Stock::delete_calibration_requests_older_than(cr_timeout) ;

  /* If the request is a calibration request, add it to the Stock */
  CalibrationRequest *calibration_request =
    dynamic_cast<CalibrationRequest*>(const_cast<Request*>(&request)) ;
  if (calibration_request)
    {
      if (Configuration::bool_value(
            "positioning.accept-new-calibration-requests"))
        {
          if (Configuration::is_configured("verbose"))
            cerr
                << "Got a new calibration request (total "
                << Stock::nb_calibration_requests() << " in "
                << Stock::nb_reference_points()
                << " reference points).\n" ;

          if (Configuration::bool_value(
                "positioning.unique-calibration-requests"))
            calibration_request->reference_point_delete_requests() ;
          Stock::store_calibration_request(*calibration_request) ;

          if (Configuration::autocalibration_enabled())
            Stock::regenerate_reference_points() ;
        }
      else if (Configuration::is_configured("verbose"))
        cerr
            << "Ignored new calibration request (positioning."
            << "accept-new-calibration-requests = false).\n" ;

      if (! Configuration::bool_value(
            "positioning.position-calibration-requests"))
        medium->clear_current_request() ;
    }

  return medium->get_current_request() ;
}


bool Input::eof() const
{
  if (!medium)
    throw null_input_medium() ;
  return medium->eof() ;
}


void Input::log_current_request() const
{
  for (auto i = log_media.begin() ; i != log_media.end() ; ++i)
    (*i)->log_request(medium->get_current_request()) ;
}
