/*
 * InputDriverFactory.cpp
 *
 *  Created on: Jun 14, 2018
 *      Author: Andreas Schneider, Johannes Wendel, Jonas Zeunert
 */


#include "InputDriverFactory.h"

#include "utility/config.h"

#include "utility/LoggerFactory.h"

#include "input/detail/InputDriver.h"
#include "input/detail/EventNotifier.h"
#include "input/detail/Detector.h"

namespace flippR_driver
{
namespace input
{
namespace InputDriverFactory
{

using namespace nlohmann;
using namespace flippR_driver::utility;

std::shared_ptr<InputDriver> get_InputDriver(std::istream &input_pin_stream, std::istream &matrix_config_stream)
{
  LoggerFactory::CreateInputLogger();

  IBlockingQueue<Event> *event_queue = new BlockingQueue<Event>;
  std::shared_ptr<EventNotifier> event_notifier(new detail::EventNotifier(event_queue));

  std::vector<std::shared_ptr<DistributingEvent>> events;
  std::map<std::string, std::shared_ptr<Event>> name_event_map;

  create_events(matrix_config_stream, events, name_event_map, event_notifier);

  std::unique_ptr<InputPinController> input_gpio_interface(new detail::InputPinController(create_pin_map(input_pin_stream)));
  std::unique_ptr<Detector> detector(new detail::Detector(std::move(input_gpio_interface), events));

  return std::make_shared<detail::InputDriver>(event_notifier, std::move(detector), name_event_map);
}

namespace
{
  void create_events(std::istream &matrix_config_stream, std::vector<std::shared_ptr<DistributingEvent>> &events,
                      std::map<std::string, std::shared_ptr<Event>> &name_event_map,
                      std::shared_ptr<EventNotifier> event_notifier)
  {
    json matrix_config;
    matrix_config_stream >> matrix_config;

    try
    {
      int global_bounce_time = matrix_config.at("global_bounce_time").get<uint8_t>();
      auto &json_events = matrix_config.at("input_matrix");

      for(auto &json_event : json_events)
      {
        std::shared_ptr<DistributingEvent> event = create_event(json_event, event_notifier, global_bounce_time);

        events.push_back(event);
        name_event_map.emplace(event->name, event);
      }
    }
    catch(json::exception &e)
    {
      CLOG(ERROR, INPUT_LOGGER) << "Event matrix config file corrupted: " << e.what();
      exit(EXIT_FAILURE);
    }
  }

  std::shared_ptr<DistributingEvent> create_event(json &json_event, std::shared_ptr<EventNotifier> event_notifier, int bounce_time)
  {
    std::string name = json_event.at("name");
    uint8_t address = json_event.at("address").get<uint8_t>();
    int priority = json_event.at("priority").get<uint8_t>();

    set_individual_bounce_time(json_event, bounce_time);

    return std::make_shared<DistributingEvent>(address, priority, name, std::chrono::milliseconds(bounce_time), event_notifier);
  }

  void set_individual_bounce_time(json &json_event, int &bounce_time)
  {
    auto it_bounce_time = json_event.find("bounce_time");

    if(it_bounce_time != json_event.end())
    {
      bounce_time = it_bounce_time->get<uint8_t>();
    }
  }

  std::map<std::string, uint8_t> create_pin_map(std::istream &input_pin_stream)
  {
    std::map<std::string, uint8_t> input_pin_map;

    json pin_config;
    input_pin_stream >> pin_config;

    try
    {
      json row_json = pin_config.at("row");
      input_pin_map["row_address_A"] = row_json.at("A").get<uint8_t>();
      input_pin_map["row_address_B"] = row_json.at("B").get<uint8_t>();
      input_pin_map["row_address_C"] = row_json.at("C").get<uint8_t>();

      json col_json = pin_config.at("col");
      input_pin_map["col_address_A"] = col_json.at("A").get<uint8_t>();
      input_pin_map["col_address_B"] = col_json.at("B").get<uint8_t>();
      input_pin_map["col_address_C"] = col_json.at("C").get<uint8_t>();

      input_pin_map["data_address"] = pin_config.at("data").get<uint8_t>();
    }
    catch(json::exception &e)
    {
      CLOG(ERROR, INPUT_LOGGER) << "Input pin config file corrupted! " << e.what();
      exit(EXIT_FAILURE);
    }

    return input_pin_map;
  }

}
}
}
}
