/*
 * OutputDriver.cpp
 *
 *  Created on: Aug 2, 2018
 *      Author: Andreas Schneider, Johannes Wendel, Jonas Zeunert
 */

#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm/copy.hpp>
#include <thread>

#include "output/OutputDriver.h"

#include "utility/config.h"

namespace flippR_driver
{
namespace output
{

using namespace items;

OutputDriver::OutputDriver(std::shared_ptr<output::DisplayController> display_controller, std::map<std::string, std::shared_ptr<Solenoid>> solenoids,
                            std::map<std::string, std::shared_ptr<Lamp>> lamps, std::map<std::string, std::shared_ptr<Sound>> sounds,
                            std::map<std::string, std::shared_ptr<items::Flipper>> flippers, std::map<std::string, std::shared_ptr<items::Display>> displays):
  display_controller(std::move(display_controller)),
  solenoids(std::move(solenoids)),
  lamps(std::move(lamps)),
  sounds(std::move(sounds)),
  flippers(std::move(flippers)),
  displays(std::move(displays))
{
  CLOG(INFO, OUTPUT_LOGGER) << "OutputDriver created and running.";
}

void OutputDriver::activate_displays() const
{
  display_controller->activate_displays();
}

void OutputDriver::deactivate_displays() const
{
  display_controller->deactivate_displays();
}


void OutputDriver::deactivate_all_lamps() const
{
  std::for_each(lamps.begin(), lamps.end(), [](std::pair<std::string, std::shared_ptr<items::Lamp>> lamp){lamp.second->deactivate();});
}

void OutputDriver::activate_all_lamps() const
{
  std::for_each(lamps.begin(), lamps.end(), [](std::pair<std::string, std::shared_ptr<items::Lamp>> lamp){lamp.second->activate();});
}

void OutputDriver::rotate_all_lamps() const
{
  for(const auto & lamp : this->lamps)
  {
    lamp.second->activate();
    // ToDo is this thread safe??
    std::this_thread::sleep_for(lamp.second->get_activation_time());
    lamp.second->deactivate();
  }
}

void OutputDriver::activate_all_flipper_relays() const
{
  for(auto flipper_relay : this->flippers)
  {
    flipper_relay.second->activate();
  }
}

void OutputDriver::deactivate_all_flipper_relays() const
{
  for(auto flipper_relay : this->flippers)
  {
    flipper_relay.second->deactivate();
  }
}

std::vector<std::shared_ptr<Sound>> OutputDriver::get_sounds() const
{
  std::vector<std::shared_ptr<Sound>> sounds;

  // TODO: why a copy?
  boost::copy(this->sounds | boost::adaptors::map_values, std::back_inserter(sounds));

  return sounds;
}

std::vector<std::shared_ptr<Display>> OutputDriver::get_displays() const
{
  std::vector<std::shared_ptr<Display>> displays;

  boost::copy(this->displays | boost::adaptors::map_values, std::back_inserter(displays));

  return displays;
}

std::vector<std::shared_ptr<Lamp>> OutputDriver::get_lamps() const
{
  std::vector<std::shared_ptr<Lamp>> lamps;

  boost::copy(this->lamps | boost::adaptors::map_values, std::back_inserter(lamps));

  return lamps;
}

std::vector<std::shared_ptr<items::Flipper>> OutputDriver::get_flippers() const
{
  std::vector<std::shared_ptr<Flipper>> flippers;

  boost::copy(this->flippers | boost::adaptors::map_values, std::back_inserter(flippers));

  return flippers;
}

std::vector<std::shared_ptr<Solenoid>> OutputDriver::get_solenoids() const
{
  std::vector<std::shared_ptr<Solenoid>> solenoids;

  boost::copy(this->solenoids | boost::adaptors::map_values, std::back_inserter(solenoids));

  return solenoids;
}

boost::optional<std::shared_ptr<items::Lamp>> OutputDriver::get_lamp(const std::string &name) const
{
  return this->lamps.find(name)->second;
}

boost::optional<std::shared_ptr<items::Solenoid>> OutputDriver::get_solenoid(const std::string &name) const
{
  return this->solenoids.find(name)->second;
}

boost::optional<std::shared_ptr<items::Sound>> OutputDriver::get_sound(const std::string &name) const
{
  return this->sounds.find(name)->second;
}

boost::optional<std::shared_ptr<items::Flipper>> OutputDriver::get_flipper(const std::string &name) const
{
  return this->flippers.find(name)->second;
}

boost::optional<std::shared_ptr<items::Display>> OutputDriver::get_display(const std::string &name) const
{
  return this->displays.find(name)->second;
}

void OutputDriver::shut_down_driver() const
{
  this->deactivate_all_flipper_relays();
  this->deactivate_all_lamps();
  this->deactivate_displays();

  CLOG(INFO, OUTPUT_LOGGER) << "Deactivated all output items.";
}

void OutputDriver::rotate_displays() const
{
  CLOG(INFO, OUTPUT_LOGGER) << "Running display test!";
  for (auto display : this->get_displays())
  {
    CLOG(INFO, OUTPUT_LOGGER) << "Testing display " << display->get_name();
    for (int i = 8; i < 100000000; i = i*10)
    {
      CLOG(INFO, OUTPUT_LOGGER) << "Score " << i;
      display->write_score(i);
      std::this_thread::sleep_for(std::chrono::milliseconds(250));
    }
    display->write_score(0);
  }
  CLOG(INFO, OUTPUT_LOGGER) << "Display test finished!";
}


}
} /* namespace output */
