//
// 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();
    return;
  }

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

  response.setContentType("text/json");
  response.setStatus(HTTPServerResponse::HTTP_OK);

  try
  {
    boost::optional<Poco::JSON::Object> json_response = parseRequest(item_type, item_name, action, score);
    if(json_response)
    {
      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)
{
  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
  {
    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)
{
  if(item_name == "")
  {
    Poco::JSON::Object response;
    response.set("solenoids", 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 {};
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseLamp(const std::string& item_name, const std::string& action)
{
  if(item_name == "")
  {
    Poco::JSON::Object response;
    response.set("lamps", 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 {};
}

boost::optional<Poco::JSON::Object> OutputRequestHandler::parseSound(const std::string& item_name, const std::string& action)
{
  if(item_name == "")
  {
    Poco::JSON::Object response;
    response.set("sounds", 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 {};
}

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

  uint8_t display_number = std::stoi(item_name);
  auto opt_display = this->output_driver->get_display(display_number);

  if(!opt_display)
  {
    throw new Poco::NotFoundException("No display with number \"" + item_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 {};
}

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

}
}

