#include "calc.hpp"
#include "json_output.hpp"
#include "proto_loader.hpp"
#include "spatial_grid.hpp"

#include <iostream>
#include <limits>
#include <string>
#include <utility>
#include <vector>


static void print_usage()
{
  std::cout << "ESRI C++ code assignment, E. Nindl 2021\n\n";
  std::cout << "Usage:\n";
  std::cout << "   executable <input-file[s]> <json-ouput-file>\n";
}


std::tuple<double, double, double, double>
total_spatial_extent(std::vector<Magnetics> const& magnetics_recs) {
  double x_min = std::numeric_limits<double>::max();
  double y_min = std::numeric_limits<double>::max();
  double x_max = std::numeric_limits<double>::min();
  double y_max = std::numeric_limits<double>::min();
  for (auto const& magnetics : magnetics_recs) {
    auto const [x_min_i, y_min_i, x_max_i, y_max_i] = ::calc::spatial_extent(magnetics);
    x_min = std::min(x_min, x_min_i);
    y_min = std::min(y_min, y_min_i);
    x_max = std::max(x_max, x_max_i);
    y_max = std::max(y_max, y_max_i);
  }
  return std::make_tuple(x_min, y_min, x_max, y_max);
}


static bool write_output(std::string const& output_file, SpatialGrid<double>& grid)
{
  JsonOutput json_output(output_file.c_str());
  json_output.add("start_x", grid.start_x());
  json_output.add("start_y", grid.start_y());
  json_output.add("spacing", grid.spacing);
  json_output.add("data", grid.data());
  return json_output.write();
}


int main(int argc, char* argv[])
{
  if (argc < 3) {
    print_usage();
    return 1;
  }
  std::string const output_file{argv[argc - 1]};
  std::vector<std::string> input_files;
  for (uint32_t i = 1; i < (argc - 1); i++) {
    input_files.push_back(argv[i]);
  }

  std::vector<Magnetics> magnetics_recs;
  std::vector<Positions> groundtruth_recs;

  try {
    for (auto const& input_file : input_files) {
      ProtoLoader loader(input_file.c_str());
      magnetics_recs.push_back(loader.magnetics());
      groundtruth_recs.push_back(loader.groundtruth());
    }
  } catch (std::exception& e) {
    std::cerr << "Error loading input: " << e.what() << "\n";
    return 1;
  }

  for (size_t rec_id = 0; rec_id < magnetics_recs.size(); rec_id++) {
    Magnetics& magnetics = magnetics_recs[rec_id];
    Positions& groundtruth = groundtruth_recs[rec_id];
    ::calc::crop_temporal_inersection(magnetics, groundtruth);
    if (magnetics.size() == 0) {
      std::cerr << "Error in groundtruth data\n";
      return 1;
    }
    ::calc::interpolate_positions(magnetics, groundtruth);
  }

  auto const [x_min, y_min, x_max, y_max] = total_spatial_extent(magnetics_recs);
  SpatialGrid<std::vector<double>> grid(x_min, y_min, x_max, y_max);

  for (size_t rec_id = 0; rec_id < magnetics_recs.size(); rec_id++) {
    Magnetics& magnetics = magnetics_recs[rec_id];
    Positions& groundtruth = groundtruth_recs[rec_id];
    for (auto& mag : magnetics) {
      auto& cell = grid.at(mag.position);
      double const magnitude = ::calc::norm(mag.field.cbegin(), mag.field.cend());
      cell.push_back(magnitude);
    }
  }

  SpatialGrid<double> average_grid(x_min, y_min, x_max, y_max);
  calc::average_grid(grid, average_grid);
  if (!write_output(output_file, average_grid)) {
    std::cout << "Error writing output file: " << output_file << "\n";
    return 1;
  }

  return 0;
}
