//
// Created by rhetenor on 3/6/19.
//


#include <Poco/Net/HTTPServerResponse.h>
#include <Poco/Net/HTTPServerRequest.h>
#include <Poco/Exception.h>

#include <string>

#include "OutputRequestHandler.h"

namespace flippR_driver
{
namespace networking
{

using namespace Poco;
using namespace Poco::Net;

OutputRequestHandler::OutputRequestHandler(std::shared_ptr<output::OutputDriver> output_driver) :
  output_driver(output_driver)
  {}

  /**
   * Handles a REST request with a URI form of:
   *
   * address/{item_type}/[item_name]/[action]/[score]
   *
   * Where
   * {item_type} is either solenoids, lamps, sounds, displays, or one of the two special events: activate and deactivate
   * [item_name] is the string name of an item (optional if you want to get the list of all available items)
   * [action] is the particular action for the item:
   *    Solenoids: trigger
   *    lamps: activate, deactivate, status
   *    sounds: play
   *    displays: write_score (for this is the additional optional attribute [score]
   *
   * @param request
   * @param response
   */
void OutputRequestHandler::handleRequest(HTTPServerRequest &request,
                                         HTTPServerResponse &response)
{
  auto path_segments = getPathSegments(URI(request.getURI()));

  // fill up vector
  for(int i = path_segments.size(); i < 4; i++)
  {
    path_segments.emplace_back("");
  }

  std::string item_type = path_segments.at(0);
  std::string item_name = path_segments.at(1);
  std::string action = path_segments.at(2);
  std::string score = path_segments.at(3);

  if(item_type == "deactivate")
  {
    this->output_driver->deactivate_displays();
    this->output_driver->deactivate_all_lamps();
    this->output_driver->deactivate_all_flipper_relays();
    return;
  }

  if(item_type == "activate")
  {
    this->output_driver->activate_displays();
    this->output_driver->activate_all_flipper_relays();
    return;
  }

  response.setContentType("text/json");

  try
  {
    boost::optional<Poco::JSON::Object> json_response = parseRequest(item_type, item_name, action, score);

    if(json_response)
    {
      response.setStatus(HTTPServerResponse::HTTP_OK);
      std::ostream& ostr = response.send();
      json_response->stringify(ostr);
    }
  }
  catch(const NotFoundException &e)
  {
    response.setStatusAndReason(HTTPServerResponse::HTTP_NOT_FOUND, e.displayText());
  }
  catch(const Poco::InvalidArgumentException &e)
  {
    response.setStatusAndReason(HTTPServerResponse::HTTP_BAD_REQUEST, e.displayText());
  }
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseRequest(const std::string& item_type, const std::string& item_name, const std::string& action, const std::string& score)
{

  std::string log_msg = "Got request from client to " + action + " " + item_type + " with name " + item_name + " (" + score + ")";

  Poco::Logger::root().information(log_msg);

  if(item_type == "solenoids")
  {
    return parseSolenoid(item_name, action);
  }
  else if(item_type == "lamps")
  {
    return parseLamp(item_name, action);
  }
  else if(item_type == "sounds")
  {
    return parseSound(item_name, action);
  }
  else if(item_type == "displays")
  {
    return parseDisplay(item_name, action, score);
  }
  else if(item_type == "flippers")
  {
    return parseFlipper(item_name, action);
  }
  else
  {
    throw new Poco::NotFoundException("No item type called " + item_type);
  }
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseSolenoid(const std::string& item_name, const std::string& action)
{
  Poco::JSON::Object response;
  if(item_name == "")
  {
    response.set("solenoids", getItemArray(this->output_driver->get_solenoids()) );
    return response;
  }

  auto opt_solenoid = this->output_driver->get_solenoid(item_name);

  if(!opt_solenoid)
  {
    throw new Poco::NotFoundException("No solenoid with name \"" + item_name + "\"!");
  }

  auto solenoid = opt_solenoid->get();

  if(action == "trigger")
  {
    solenoid->trigger();
  }
  else
  {
    throw new Poco::NotFoundException("No action with name \"" + action + "\" on solenoids!");
  }

  return response;
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseLamp(const std::string& item_name, const std::string& action)
{
  Poco::JSON::Object response;
  if(item_name == "")
  {
    response.set("lamps", getItemArray(this->output_driver->get_lamps()) );
    return response;
  }

  auto opt_lamp = this->output_driver->get_lamp(item_name);

  if(!opt_lamp)
  {
    throw new Poco::NotFoundException("No lamp with name \"" + item_name + "\"!");
  }

  auto lamp = opt_lamp->get();

  if(action == "activate")
  {
    lamp->activate();
  }
  else if(action == "deactivate")
  {
    lamp->deactivate();
  }
  else
  {
    throw new Poco::NotFoundException("No action with name \"" + action + "\" on lamps!");
  }

  return response;
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseSound(const std::string& item_name, const std::string& action)
{
  Poco::JSON::Object response;
  if(item_name == "")
  {
    response.set("sounds", getItemArray(this->output_driver->get_sounds()) );
    return response;
  }

  auto opt_sound = this->output_driver->get_sound(item_name);

  if(!opt_sound)
  {
    throw new Poco::NotFoundException("No sound with name \"" + item_name + "\"!");
  }

  auto sound = opt_sound->get();

  if(action == "play")
  {
    sound->play();
  }
  else
  {
    throw new Poco::NotFoundException("No action with name \"" + action + "\" on sounds!");
  }

  return response;
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseDisplay(const std::string& display_name, const std::string& action, const std::string& score)
{
  Poco::JSON::Object response;
  if(display_name == "")
  {
    response.set("displays", getItemArray(this->output_driver->get_displays()));
    return response;
  }

  if (display_name == "test")
  {
    this->output_driver->rotate_displays();
    return response;
  }

  auto opt_display = this->output_driver->get_display(display_name);

  if(!opt_display)
  {
    throw new Poco::NotFoundException("No display with name \"" + display_name + "\"!");
  }

  auto display = opt_display->get();


  if(action == "write_score")
  {
    try
    {
        unsigned int numerical_score = std::stoi(score);
        display->write_score(numerical_score);
    }
    catch(std::invalid_argument &e)
    {
      throw new Poco::InvalidArgumentException("Could not convert " + score + " to a number!\n" + e.what());
    }
  }
  else
  {
    throw new Poco::NotFoundException("No Action with name \"" + action + "\" on sounds!");
  }

  return response;
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseFlipper(const std::string& item_name, const std::string& action)
{
  Poco::JSON::Object response;
  if(item_name == "")
  {
    response.set("flippers", getItemArray(this->output_driver->get_flippers()) );
    return response;
  }

  auto opt_flipper = this->output_driver->get_flipper(item_name);

  if(!opt_flipper)
  {
    throw new Poco::NotFoundException("No flipper with name \"" + item_name + "\"!");
  }

  auto flipper = opt_flipper->get();

  if(action == "activate")
  {
    flipper->activate();
  }
  else if(action == "deactivate")
  {
    flipper->deactivate();
  }
  else
  {
    throw new Poco::NotFoundException("No action with name \"" + action + "\" on flippers!");
  }

  return response;
}

std::vector<std::string> OutputRequestHandler::getPathSegments(Poco::URI uri)
{
  std::vector<std::string> path_segments;
  uri.getPathSegments(path_segments);
  return path_segments;
}

template<typename T>
Poco::JSON::Array getItemArray(const std::vector<std::shared_ptr<T>> & items)
{
  Poco::JSON::Array array;
  for ( auto & item : items )
  {
    Poco::JSON::Object var;
    var.set("name", item->get_name());
    array.add(var);
  }
  return array;
}

}
}

