// SPDX-License-Identifier: GPL-2.0-or-later
// Copyright (C) 2005-2011, Jari Sundell <jaris@ifi.uio.no>

#include "buildinfo.h"

#include <sstream>

#include <torrent/exceptions.h>
#include <torrent/torrent.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/download_list.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]      = nullptr;
  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() {
  if (is_active()) {
    torrent::destruct_error(
      "ui::DownloadList::~DownloadList() called on an active object");
    return;
  }

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

  delete m_windowLog;
}

void
DownloadList::activate(display::Frame* frame, bool) {
  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 = nullptr;

  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() < current_view()->end_visible() &&
      *current_view()->focus() == d)
    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] = nullptr;

      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(
          [this] { activate_display(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 " RT_VERSION "/" + std::string(torrent::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() != nullptr)
    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(view_name_var)) {
          control->core()->push_log_std("View '" + current_view()->name() +
                                        "' can't be filtered.");
          delete input;
          return;
        }
      }

      title = "filter";
    } break;

    default:
      delete input;
      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(
    [this] { activate_display(DISPLAY_STRING_LIST); });
  input->signal_show_next().push_back([esl] { esl->next_screen(); });

  input->signal_show_range().push_back([this](const auto&, const auto&) {
    activate_display(DISPLAY_STRING_LIST);
  });
  input->signal_show_range().push_back(
    [esl](const auto& first, const auto& last) {
      esl->set_range_dirent(first, last);
    });

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

  input->bindings()['\n']      = [this, type] { receive_exit_input(type); };
  input->bindings()[KEY_ENTER] = [this, type] { receive_exit_input(type); };
  input->bindings()['\x07'] = [this] { receive_exit_input(INPUT_NONE); }; // ^G
  input->bindings()['\x1B'] = [this] {
    receive_exit_input(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 == nullptr)
    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(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'] = [this] { receive_view_input(INPUT_LOAD_DEFAULT); };
  m_bindings[KEY_BACKSPACE] = [this] {
    receive_view_input(INPUT_LOAD_DEFAULT);
  };
  m_bindings['\n']      = [this] { receive_view_input(INPUT_LOAD_MODIFIED); };
  m_bindings[KEY_ENTER] = [this] { receive_view_input(INPUT_LOAD_MODIFIED); };
  m_bindings['\x0F'] = [this] { receive_view_input(INPUT_CHANGE_DIRECTORY); };
  m_bindings['X' - '@'] = [this] { receive_view_input(INPUT_COMMAND); };
  m_bindings['F']       = [this] { receive_view_input(INPUT_FILTER); };

  m_uiArray[DISPLAY_LOG]->bindings()[KEY_LEFT] =
    m_uiArray[DISPLAY_LOG]->bindings()['B' - '@'] =
      m_uiArray[DISPLAY_LOG]->bindings()[' '] =
        [this] { activate_display(DISPLAY_DOWNLOAD_LIST); };

  m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()[KEY_RIGHT] =
    m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['F' - '@'] =
      [this] { activate_display(DISPLAY_DOWNLOAD); };
  m_uiArray[DISPLAY_DOWNLOAD_LIST]->bindings()['l'] = [this] {
    activate_display(DISPLAY_LOG);
  };
}

}
