#include "config.h"

#include "ui/download_list.h"

#include <cassert>
#include <sstream>
#include <torrent/exceptions.h>
#include <torrent/torrent.h>
#include <torrent/runtime/runtime.h>
#include <torrent/utils/log.h>
#include <torrent/utils/string_manip.h>

#include "control.h"
#include "core/download.h"
#include "core/download_list.h"
#include "core/manager.h"
#include "core/view.h"
#include "core/view_manager.h"
#include "display/window_log.h"
#include "display/window_title.h"
#include "input/bindings.h"
#include "input/manager.h"
#include "input/path_input.h"
#include "rpc/parse_commands.h"
#include "ui/download.h"
#include "ui/element_download_list.h"
#include "ui/element_log_complete.h"
#include "ui/element_string_list.h"
#include "ui/root.h"

namespace ui {

DownloadList::DownloadList() {
  m_uiArray[DISPLAY_DOWNLOAD]      = NULL;
  m_uiArray[DISPLAY_DOWNLOAD_LIST] = new ElementDownloadList();
  m_uiArray[DISPLAY_LOG]           = new ElementLogComplete(control->core()->log_complete());
  m_uiArray[DISPLAY_STRING_LIST]   = new ElementStringList();

  m_windowLog                      = new WLog(control->core()->log_important());

  setup_keys();
}

DownloadList::~DownloadList() {
  assert(!is_active() && "ui::DownloadList::~DownloadList() called on an active object");

  std::for_each(m_uiArray, m_uiArray + DISPLAY_MAX_SIZE, [](ElementBase* eb) { delete eb; });

  delete m_windowLog;
}

void
DownloadList::activate(display::Frame* frame, [[maybe_unused]] bool focus) {
  if (is_active())
    throw torrent::internal_error("ui::DownloadList::activate() called on an already activated object");

  m_frame = frame;

  control->input()->push_back(&m_bindings);

  activate_display(DISPLAY_DOWNLOAD_LIST);
}

void
DownloadList::disable() {
  if (!is_active())
    throw std::logic_error("ui::DownloadList::disable() called on an already disabled object");

  receive_exit_input(INPUT_NONE);
  activate_display(DISPLAY_MAX_SIZE);

  m_frame = NULL;

  control->input()->erase(&m_bindings);
}

core::View*
DownloadList::current_view() {
  return dynamic_cast<ElementDownloadList*>(m_uiArray[DISPLAY_DOWNLOAD_LIST])->view();
}

void
DownloadList::set_current_view(const std::string& name) {
  return dynamic_cast<ElementDownloadList*>(m_uiArray[DISPLAY_DOWNLOAD_LIST])->receive_change_view(name);
}

void
DownloadList::unfocus_download(core::Download* d) {
  if (m_state == DISPLAY_DOWNLOAD && d == static_cast<Download*>(m_uiArray[DISPLAY_DOWNLOAD])->download())
    activate_display(DISPLAY_DOWNLOAD_LIST);

  if (*current_view()->focus() == d && current_view()->focus() < current_view()->end_visible())
      current_view()->next_focus();
}

void
DownloadList::activate_display(Display displayType) {
  if (!is_active())
    throw torrent::internal_error("ui::DownloadList::activate_display(...) !is_active().");

  if (displayType > DISPLAY_MAX_SIZE)
    throw torrent::internal_error("ui::DownloadList::activate_display(...) out of bounds");

  if (displayType == m_state)
    return;

  // Cleanup previous state.
  switch (m_state) {
  case DISPLAY_DOWNLOAD:
    m_uiArray[m_state]->disable();

    delete m_uiArray[m_state];
    m_uiArray[m_state] = NULL;

    break;

  case DISPLAY_DOWNLOAD_LIST:
    m_uiArray[DISPLAY_DOWNLOAD_LIST]->disable();

    m_windowLog->set_active(false);
    m_frame->frame(1)->clear();

    m_frame->clear();
    break;

  case DISPLAY_LOG:
  case DISPLAY_STRING_LIST:
    m_uiArray[m_state]->disable();
    break;

  default:
    break;
  }

  m_state = displayType;

  // Initialize new state.
  switch (displayType) {
  case DISPLAY_DOWNLOAD:
    // If no download has the focus, just return to the download list.
    if (current_view()->focus() == current_view()->end_visible()) {
      m_state = DISPLAY_MAX_SIZE;

      activate_display(DISPLAY_DOWNLOAD_LIST);
      return;

    } else {
      Download* download = new Download(*current_view()->focus());

      download->activate(m_frame);
      download->slot_exit(std::bind(&DownloadList::activate_display, this, DISPLAY_DOWNLOAD_LIST));

      m_uiArray[DISPLAY_DOWNLOAD] = download;
      break;
    }

  case DISPLAY_DOWNLOAD_LIST:
    m_frame->initialize_row(2);

    m_uiArray[DISPLAY_DOWNLOAD_LIST]->activate(m_frame->frame(0));

    m_frame->frame(1)->initialize_window(m_windowLog);
    m_windowLog->set_active(true);
    m_windowLog->receive_update();
    break;

  case DISPLAY_LOG:
  case DISPLAY_STRING_LIST:
    m_uiArray[displayType]->activate(m_frame);
    break;

  default:
    break;
  }

  // Set title.
  switch (displayType) {
  case DISPLAY_DOWNLOAD_LIST:
    control->ui()->window_title()->set_title("rTorrent " PACKAGE_VERSION "/" +
                                             std::string(torrent::runtime::version()) + " - " +
                                             rpc::call_command_string("session.name"));
    break;
  case DISPLAY_LOG:
    control->ui()->window_title()->set_title("Log");
    break;
  default: break;
  }

  control->display()->adjust_layout();
}

void
DownloadList::receive_view_input(Input type) {
  if (control->ui()->current_input() != NULL)
    return;

  input::PathInput* input = new input::PathInput;

  const char* title;

  switch (type) {
  case INPUT_LOAD_DEFAULT:
    title = "load.start";
    break;

  case INPUT_LOAD_MODIFIED:
    title = "load.normal";
    break;

  case INPUT_CHANGE_DIRECTORY:
    title = "change_directory";

    input->str() = rpc::call_command_string("directory.default");
    input->set_pos(input->str().length());

    break;

  case INPUT_COMMAND:
    title = "command";
    break;

  case INPUT_FILTER:
    {
      // Do not allow to subfilter the defined excluded views
      const std::string excluded_views = rpc::call_command_string("view.filter.temp.excluded");
      std::stringstream ss(excluded_views);
      std::string view_name_var;

      while(ss.good()) {
          std::getline(ss, view_name_var, ',');
          if (current_view()->name() == torrent::utils::trim_spaces_str(view_name_var)) {
              control->core()->push_log_std("View '" + current_view()->name() + "' can't be filtered.");
              return;
          }
      }

      title = "filter";
    }
    break;

  default:
    throw torrent::internal_error("DownloadList::receive_view_input(...) Invalid input type.");
  }

  ElementStringList* esl = dynamic_cast<ElementStringList*>(m_uiArray[DISPLAY_STRING_LIST]);

  input->signal_show_next().push_back(std::bind(&DownloadList::activate_display, this, DISPLAY_STRING_LIST));
  input->signal_show_next().push_back(std::bind(&ElementStringList::next_screen, esl));

  input->signal_show_range().push_back(std::bind(&DownloadList::activate_display, this, DISPLAY_STRING_LIST));
  input->signal_show_range().push_back(std::bind(&ElementStringList::set_range_dirent<utils::Directory::iterator>,
                                                      esl,
                                                      std::placeholders::_1,
                                                      std::placeholders::_2));

  // reset ESC delay for input prompt
  set_escdelay(0);

  input->bindings()['\n']      = std::bind(&DownloadList::receive_exit_input, this, type);
  input->bindings()[KEY_ENTER] = std::bind(&DownloadList::receive_exit_input, this, type);
  input->bindings()['\x07']    = std::bind(&DownloadList::receive_exit_input, this, INPUT_NONE); // ^G
  input->bindings()['\x1B']    = std::bind(&DownloadList::receive_exit_input, this, INPUT_NONE); // ESC , ^[

  control->ui()->enable_input(title, input, type);
}

void
DownloadList::receive_exit_input(Input type) {
  // set back ESC delay to default
  set_escdelay(1000);

  input::TextInput* input = control->ui()->current_input();

  // We should check that this object is the one holding the input.
  if (input == NULL)
    return;

  if (type != INPUT_NONE && type != INPUT_EOI)
    control->ui()->add_to_input_history(type, input->str());

  control->ui()->disable_input();

  try {

    switch (type) {
    case INPUT_NONE:
      break;

    case INPUT_LOAD_DEFAULT:
    case INPUT_LOAD_MODIFIED:
      if (input->str().empty())
        break;

      control->core()->try_create_download_expand(input->str(), type == INPUT_LOAD_DEFAULT ? core::Manager::create_start : 0);
      break;

    case INPUT_CHANGE_DIRECTORY:
      if (current_view()->focus() == current_view()->end_visible())
        throw torrent::input_error("No download in focus to change root directory.");

      if ((*current_view()->focus())->is_open())
        throw torrent::input_error("Cannot change root directory on an open download.");

      rpc::call_command("d.directory.set", torrent::utils::trim_spaces_str(input->str()), rpc::make_target(*current_view()->focus()));
      control->core()->push_log_std("New root directory \"" + rpc::call_command_string("d.directory", rpc::make_target(*current_view()->focus())) + "\" for torrent.");
      break;

    case INPUT_COMMAND:
      rpc::parse_command_single(current_view()->focus() != current_view()->end_visible() ?
                                rpc::make_target(*current_view()->focus()) : rpc::make_target(),
                                input->str());
      break;

    case INPUT_FILTER:
      if (input->str().empty()) {
        if (rpc::call_command_value("view.filter.temp.log"))
          control->core()->push_log_std("Clear temporary filter on '" + current_view()->name() + "' view.");
        current_view()->set_filter_temp(torrent::Object());
        current_view()->filter();
        current_view()->sort();
      } else {
        std::string pattern = input->str();
        if (pattern.back() != '$')
          pattern = pattern + ".*";
        if (pattern.front() != '^')
          pattern = ".*" + pattern;
        std::transform(pattern.begin(), pattern.end(), pattern.begin(), ::tolower);
        std::string temp_filter = "match={d.name=," + pattern + "}";
        if (rpc::call_command_value("view.filter.temp.log"))
          control->core()->push_log_std("Temporary filter on '" + current_view()->name() + "' view: " + pattern);
        current_view()->set_filter_temp(temp_filter);
        current_view()->filter();
      }
      break;

    default:
      throw torrent::internal_error("DownloadList::receive_exit_input(...) Invalid input type.");
    }

  } catch (torrent::input_error& e) {
    lt_log_print(torrent::LOG_WARN, "Input failed: %s", e.what());
  }

  activate_display(DISPLAY_DOWNLOAD_LIST);

  delete input;
}

void
DownloadList::setup_keys() {
  m_bindings['\x7f']        = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_DEFAULT);
  m_bindings[KEY_BACKSPACE] = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_DEFAULT);
  m_bindings['\n']          = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_MODIFIED);
  m_bindings[KEY_ENTER]     = std::bind(&DownloadList::receive_view_input, this, INPUT_LOAD_MODIFIED);
  m_bindings['\x0F']        = std::bind(&DownloadList::receive_view_input, this, INPUT_CHANGE_DIRECTORY);
  m_bindings['X' - '@']     = std::bind(&DownloadList::receive_view_input, this, INPUT_COMMAND);
  m_bindings['F']           = std::bind(&DownloadList::receive_view_input, this, INPUT_FILTER);

  m_uiArray[DISPLAY_LOG]->bindings()[KEY_LEFT] =
    m_uiArray[DISPLAY_LOG]->bindings()[control->ui()->navigation_key(RT_KEY_LEFT)] =
    m_uiArray[DISPLAY_LOG]->bindings()[' '] = std::bind(&DownloadList::activate_display, this, DISPLAY_DOWNLOAD_LIST);

  m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[KEY_RIGHT] =
    m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[control->ui()->navigation_key(RT_KEY_RIGHT)] = std::bind(&DownloadList::activate_display, this, DISPLAY_DOWNLOAD);
  m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[control->ui()->navigation_key(RT_KEY_DISPLAY_LOG)] = std::bind(&DownloadList::activate_display, this, DISPLAY_LOG);
}

}
