/*
 * 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 "testutil.hh"
#include <cxxtest/TestSuite.h>

#include "stock.hh"
#include "userinterface.hh"
#include "calibrationrequest.hh"
#include "realposition.hh"

#include <fstream>
#include <sstream>
#include <sys/stat.h>

using namespace std ;

vector<CapturePoint> TestUtil::aps ;
vector<Mobile> TestUtil::mobiles ;
vector<Request*> TestUtil::requests ;
vector<Result> TestUtil::results ;
vector<ReferencePoint> TestUtil::reference_points ;


void TestUtil::tear_down()
{
  Stock::clear() ;

  aps.clear() ;
  mobiles.clear() ;

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

  results.clear() ;
  reference_points.clear() ;
}


void TestUtil::set_up()
{
  tear_down() ;
  create_mobile_list() ;
  create_cp_list() ;
  create_request_list() ;
  create_result_list() ;
  setup_configuration() ;
}

void TestUtil::create_mobile_list()
{
  Mobile mobile1 ;
  mobile1.set_mac_addr("AA:BB:CC:DD:EE:FF") ;
  mobiles.push_back(mobile1) ;
  mobile1.set_mac_addr("AA:BB:CC:DD:EE:77") ;
  mobiles.push_back(mobile1) ;
}

void TestUtil::create_cp_list()
{
  CapturePoint ap1 ;
  ap1.set_mac_addr("11:22:33:44:55:01") ;
  aps.push_back(ap1) ;
  ap1.set_mac_addr("11:22:33:44:55:02") ;
  aps.push_back(ap1) ;
  ap1.set_mac_addr("11:22:33:44:55:03") ;
  aps.push_back(ap1) ;
}

void TestUtil::create_request_list()
{
  // Create measurement list
  vector < unordered_map<string,Measurement> > measurements(3) ;
  Measurement measurement1 ;

  measurement1.add_ss(1, -58) ;
  measurement1.add_ss(2, -55) ;
  measurement1.set_cp(&aps[0]) ;
  measurements[0][aps[0].get_mac_addr()] = measurement1 ;
  measurement1.clear() ;
  measurement1.add_ss(1, -50) ;
  measurement1.set_cp(&aps[2]) ;
  measurements[0][aps[2].get_mac_addr()] = measurement1 ;
  measurement1.clear() ;
  measurement1.add_ss(1, -42) ;
  measurement1.add_ss(2, -37) ;
  measurement1.set_cp(&aps[1]) ;
  measurements[0][aps[1].get_mac_addr()] = measurement1 ;

  measurement1.clear() ;
  measurement1.add_ss(1, -51) ;
  measurement1.add_ss(2, -19) ;
  measurement1.set_cp(&aps[0]) ;
  measurements[1][aps[0].get_mac_addr()] = measurement1 ;
  measurement1.clear() ;
  measurement1.add_ss(1, -70) ;
  measurement1.add_ss(2, -21) ;
  measurement1.set_cp(&aps[1]) ;
  measurements[1][aps[1].get_mac_addr()] = measurement1 ;
  measurement1.clear() ;
  measurement1.add_ss(1, -59) ;
  measurement1.set_cp(&aps[2]) ;
  measurements[1][aps[2].get_mac_addr()] = measurement1 ;

  measurement1.clear() ;
  measurement1.add_ss(1, -54) ;
  measurement1.add_ss(2, -32) ;
  measurement1.set_cp(&aps[0]) ;
  measurements[2][aps[0].get_mac_addr()] = measurement1 ;
  measurement1.clear() ;
  measurement1.add_ss(1, -45) ;
  measurement1.add_ss(2, -23) ;
  measurement1.set_cp(&aps[1]) ;
  measurements[2][aps[1].get_mac_addr()] = measurement1 ;
  measurement1.clear() ;
  measurement1.add_ss(1, -56) ;
  measurement1.set_cp(&aps[2]) ;
  measurements[2][aps[2].get_mac_addr()] = measurement1 ;

  // Create timestamp list
  vector<Timestamp> timestamps ;
  timestamps.push_back(Timestamp(1265120910725)) ;
  timestamps.push_back(Timestamp(1265120911234)) ;
  timestamps.push_back(Timestamp(1265120912345)) ;

  // Create request list
  requests.push_back(new Request(&mobiles[0], timestamps[0],
                                 measurements[0])) ;

  Request request2(&mobiles[1], timestamps[1], measurements[1]) ;
  reference_points.push_back(ReferencePoint(3.2, 4.1, 3.4)) ;
  requests.push_back(new CalibrationRequest(
                       request2, &reference_points.at(0),
                       Direction(2), OWL_REQUEST_CALIBRATION)) ;

  CalibrationRequest *calibration_request =
    dynamic_cast<CalibrationRequest*>(requests.at(1)) ;
  if (calibration_request)
    calibration_request->reference_point_backward_link() ;

  requests.push_back(new Request(&mobiles[0], timestamps[2],
                                 measurements[2])) ;
}

void TestUtil::create_result_list()
{
  RealPosition algo ;

  for (auto i = requests.begin() ; i != requests.end() ; ++i)
    {
      CalibrationRequest *calibration_request =
        dynamic_cast<CalibrationRequest*>(*i) ;
      Result result ;
      if (calibration_request)
        result = algo.compute(*calibration_request) ;
      else
        result = algo.compute(**i) ;
      results.push_back(result) ;
    }
}

void TestUtil::setup_configuration()
{
  const std::string config_file_name("/dev/null") ;
  const char *argv[] =
  {
    "owlps-positionerd", // program name
    "--config-file", config_file_name.c_str(),
    "--positioning.area-start", "0;0;0",
    "--positioning.area-stop", "10;15;3",
    "--positioning.accept-new-mobiles", "true",
    "--positioning.accept-new-cps", "true"
  } ;
  int argc = 11 ;

  UserInterface ui(argc, argv) ;
}


// Create the file output_file_name and fill it with the contents of
// output_lines
void TestUtil::fill_file(const string &output_file_name,
                         const vector<string> output_lines)
{
  // Open the file
  std::ofstream output_file ;
  output_file.open(output_file_name.c_str()) ;
  if (! output_file)
    TS_FAIL("Cannot open test file `"+ output_file_name
            +"` for creation!") ;

  // Write contents to the file
  for (auto i = output_lines.begin() ; i != output_lines.end() ; ++i)
    output_file << *i ;

  output_file.close() ;
}


void TestUtil::remove_file(const string &file_name)
{
  if (remove(file_name.c_str()) == -1)
    TS_WARN("Cannot remove test file `"+ file_name +"`!") ;
}


void TestUtil::
create_test_csv_file(const string &file_name, bool with_spaces)
{
  // Fill name and contents of the test CSV file
  vector<string> csv_lines ;
  ostringstream line ;
  constexpr unsigned int csv_format = OWL_LATEST_AGGREGATION_CSV_FORMAT ;

  /* The CSV format to use is documented in the header of InputCSV */

  if (with_spaces)
    line << "\n   \n     " ;
  line << csv_format ;
  line << ';' << mobiles[0].get_mac_addr() ;
  line << ';' << static_cast<uint_fast16_t>(requests.at(0)->get_type()) ;
  line << ';' << requests.at(0)->get_nb_packets() ;
  line << ';' << requests.at(0)->get_time_sent() ;
  line << ";0;0;0;0;" ;
  line << aps[0].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << requests.at(0)->get_measurements()
       .at(aps[0].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[2].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << requests.at(0)->get_measurements()
       .at(aps[2].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[1].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << requests.at(0)->get_measurements()
       .at(aps[1].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[0].get_mac_addr() ;
  line << ';' << 2 ;
  line << ';' << requests.at(0)->get_measurements()
       .at(aps[0].get_mac_addr()).get_ss(2) ;
  line << ';' << aps[1].get_mac_addr() ;
  line << ';' << 2 ;
  line << ';' << requests.at(0)->get_measurements()
       .at(aps[1].get_mac_addr()).get_ss(2) ;
  line << '\n' ;
  csv_lines.push_back(line.str()) ;

  line.str("") ;
  line << csv_format ;
  line << ';' << mobiles[1].get_mac_addr() ;
  CalibrationRequest *calibration_request =
    dynamic_cast<CalibrationRequest*>(requests.at(1)) ;
  assert(calibration_request) ;
  line << ';' << static_cast<uint_fast16_t>(calibration_request->get_type()) ;
  line << ';' << calibration_request->get_nb_packets() ;
  line << ';' << calibration_request->get_time_sent() ;
  line << ';' << reference_points.at(0).get_x() ;
  line << ';' << reference_points.at(0).get_y() ;
  line << ';' << reference_points.at(0).get_z() ;
  line << ';' << static_cast<int>(calibration_request->get_direction()) ;
  line << ';' << aps[2].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << calibration_request->get_measurements()
       .at(aps[2].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[0].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << calibration_request->get_measurements()
       .at(aps[0].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[1].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << calibration_request->get_measurements()
       .at(aps[1].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[1].get_mac_addr() ;
  line << ';' << 2 ;
  line << ';' << calibration_request->get_measurements()
       .at(aps[1].get_mac_addr()).get_ss(2) ;
  line << ';' << aps[0].get_mac_addr() ;
  line << ';' << 2 ;
  line << ';' << calibration_request->get_measurements()
       .at(aps[0].get_mac_addr()).get_ss(2) ;
  line << '\n' ;
  if (with_spaces)
    line << "     \n\
\n\t\n\
\n   \t   \n\
\n" ;
  csv_lines.push_back(line.str()) ;

  line.str("") ;
  if (with_spaces)
    line << '\t' ;
  line << csv_format ;
  line << ';' << mobiles[0].get_mac_addr() ;
  line << ';' << static_cast<uint_fast16_t>(requests.at(2)->get_type()) ;
  line << ';' << requests.at(2)->get_nb_packets() ;
  line << ';' << requests.at(2)->get_time_sent() ;
  line << ";0;0;0;0;" ;
  line << aps[2].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << requests.at(2)->get_measurements()
       .at(aps[2].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[1].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << requests.at(2)->get_measurements()
       .at(aps[1].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[0].get_mac_addr() ;
  line << ';' << 1 ;
  line << ';' << requests.at(2)->get_measurements()
       .at(aps[0].get_mac_addr()).get_ss(1) ;
  line << ';' << aps[1].get_mac_addr() ;
  line << ';' << 2 ;
  line << ';' << requests.at(2)->get_measurements()
       .at(aps[1].get_mac_addr()).get_ss(2) ;
  line << ';' << aps[0].get_mac_addr() ;
  line << ';' << 2 ;
  line << ';' << requests.at(2)->get_measurements()
       .at(aps[0].get_mac_addr()).get_ss(2) ;
  line << '\n' ;
  if (with_spaces)
    line << "\n\t\n" ;
  csv_lines.push_back(line.str()) ;

  // Create and fill the test CSV file
  fill_file(file_name, csv_lines) ;
}


// Test equality of two Request comparing pointed values instead of
// pointers and taking care of the unordered_map
bool TestUtil::request_equals(const Request &first,
                              const Request &second)
{
  // Try a classical comparison
  if (first == second)
    return true ;

  // Compare timestamp
  if (first.get_time_sent() != second.get_time_sent())
    return false ;

  // Compare mobile values
  if (! (first.get_mobile() && second.get_mobile()))
    {
      if (first.get_mobile() || second.get_mobile())
        return false ;
    }
  else if (*first.get_mobile() != *second.get_mobile())
    return false ;

  // Compare measurements
  if (first.get_measurements() != second.get_measurements())
    if (! measurements_unordered_map_equals(first.get_measurements(),
                                            second.get_measurements()))
      return false ;

  return true ;
}


// Test equality of two unordered_map<string,Measurement>
bool TestUtil::
measurements_unordered_map_equals(
  unordered_map<string,Measurement> first,
  unordered_map<string,Measurement> second)
{
  if (first.size() != second.size())
    return false ;

  // For each element in 'first', we look for the same element
  // in 'second'
  for (auto i = first.begin() ; i != first.end() ; ++i)
    {
      auto second_measurement = second.find(i->first) ;
      if (second_measurement == second.end())
        return false ;
      if (! measurement_equals(i->second, second_measurement->second))
        return false ;
    }

  return true ;
}


// Test equality of two Measurement comparing pointed values instead of
// pointers
bool TestUtil::measurement_equals(const Measurement &first,
                                  const Measurement &second)
{
  // Try a classical comparison
  if (first == second)
    return true ;

  // Compare average_dbm
  if (first.get_average_dbm() != second.get_average_dbm())
    return false ;

  // Compare ap values
  if (! (first.get_cp() && second.get_cp()))
    {
      if (first.get_cp() || second.get_cp())
        return false ;
    }
  else if (*first.get_cp() != *second.get_cp())
    return false ;

  // Compare ss_list
  /*
  if (first.get_ss_list() != second.get_ss_list())
    return false ;
  */

  return true ;
}


bool TestUtil::file_size_equals(const string &file1_name,
                                const string &file2_name)
{
  try
    {
      return (file_size(file1_name) == file_size(file2_name)) ;
    }
  catch (...)
    {
      return false ;
    }
}


off_t TestUtil::file_size(const string &file_name)
{
  struct stat file_info ;
  if (stat(file_name.c_str(), &file_info) != 0)
    throw "Cannot get information for file `" + file_name + "`!" ;
  return file_info.st_size ;
}
