/*__            ___                 ***************************************
/   \          /   \          Copyright (c) 1996-2020 Freeciv21 and Freeciv
\_   \        /  __/          contributors. This file is part of Freeciv21.
 _\   \      /  /__     Freeciv21 is free software: you can redistribute it
 \___  \____/   __/    and/or modify it under the terms of the GNU  General
     \_       _/          Public License  as published by the Free Software
       | @ @  \_               Foundation, either version 3 of the  License,
       |                              or (at your option) any later version.
     _/     /\                  You should have received  a copy of the GNU
    /o)  (o/\ \_                General Public License along with Freeciv21.
    \_____/ /                     If not, see https://www.gnu.org/licenses/.
      \____/        ********************************************************/

// Qt
#include <QGridLayout>
#include <QLineEdit>
#include <QListWidget>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QToolButton>

// utility
#include "fcintl.h"
#include "log.h"

// common
#include "game.h"
#include "government.h"

// ruledit
#include "ruledit.h"
#include "ruledit_qt.h"
#include "tab_enablers.h"

class fix_enabler_item : public req_vec_fix_item {
public:
  explicit fix_enabler_item(struct action_enabler *enabler);
  virtual ~fix_enabler_item();
  void close() override;

  const void *item() override;
  void *item_working_copy() override;
  const char *name() override;
  struct req_vec_problem *find_next_problem() override;
  void apply_accepted_changes() override;
  void undo_accepted_changes() override;
  int num_vectors() override;
  requirement_vector_namer vector_namer() override;
  requirement_vector_by_number vector_getter() override;
  bool vector_in_item(const struct requirement_vector *vec) override;

private:
  struct action_enabler *current_enabler;
  struct action_enabler *local_copy;
  QString my_name;
};

/**
   Returns how big a problem an action enabler has.
   @param enabler the enabler to check the problem size for
   @return how serious a problem the enabler has
 */
static enum req_vec_problem_seriousness
enabler_problem_level(struct action_enabler *enabler)
{
  struct req_vec_problem *problem = action_enabler_suggest_repair(enabler);

  if (problem != nullptr) {
    req_vec_problem_free(problem);
    return RVPS_REPAIR;
  }

  problem = action_enabler_suggest_improvement(enabler);
  if (problem != nullptr) {
    req_vec_problem_free(problem);
    return RVPS_IMPROVE;
  }

  return RVPS_NO_PROBLEM;
}

/**
   Setup tab_enabler object
 */
tab_enabler::tab_enabler(ruledit_gui *ui_in) : QWidget()
{
  QVBoxLayout *main_layout = new QVBoxLayout(this);
  QGridLayout *enabler_layout = new QGridLayout();
  QLabel *label;
  QPushButton *add_button;

  ui = ui_in;
  connect(ui, &ruledit_gui::rec_vec_may_have_changed, this,
          &tab_enabler::incoming_rec_vec_change);

  selected = 0;

  enabler_list = new QListWidget(this);

  connect(enabler_list, &QListWidget::itemSelectionChanged, this,
          &tab_enabler::select_enabler);
  main_layout->addWidget(enabler_list);

  enabler_layout->setSizeConstraint(QLayout::SetMaximumSize);

  label = new QLabel(QString::fromUtf8(R__("Type")));
  label->setParent(this);
  enabler_layout->addWidget(label, 0, 0);

  type_button = new QToolButton();
  type_menu = new QMenu();

  action_iterate(act) { type_menu->addAction(action_id_rule_name(act)); }
  action_iterate_end;

  connect(type_menu, &QMenu::triggered, this, &tab_enabler::edit_type);

  type_button->setToolButtonStyle(Qt::ToolButtonTextOnly);
  type_button->setPopupMode(QToolButton::MenuButtonPopup);

  type_button->setMenu(type_menu);

  type_button->setEnabled(false);
  enabler_layout->addWidget(type_button, 0, 2);

  act_reqs_button =
      new QPushButton(QString::fromUtf8(R__("Actor Requirements")), this);
  connect(act_reqs_button, &QAbstractButton::pressed, this,
          &tab_enabler::edit_actor_reqs);
  act_reqs_button->setEnabled(false);
  enabler_layout->addWidget(act_reqs_button, 1, 2);

  tgt_reqs_button =
      new QPushButton(QString::fromUtf8(R__("Target Requirements")), this);
  connect(tgt_reqs_button, &QAbstractButton::pressed, this,
          &tab_enabler::edit_target_reqs);
  tgt_reqs_button->setEnabled(false);
  enabler_layout->addWidget(tgt_reqs_button, 2, 2);

  add_button = new QPushButton(QString::fromUtf8(R__("Add Enabler")), this);
  connect(add_button, &QAbstractButton::pressed, this,
          &tab_enabler::add_now);
  enabler_layout->addWidget(add_button, 3, 0);
  show_experimental(add_button);

  delete_button =
      new QPushButton(QString::fromUtf8(R__("Remove this Enabler")), this);
  connect(delete_button, &QAbstractButton::pressed, this,
          &tab_enabler::delete_now);
  delete_button->setEnabled(false);
  enabler_layout->addWidget(delete_button, 3, 2);
  show_experimental(delete_button);

  repair_button = new QPushButton(this);
  connect(repair_button, &QAbstractButton::pressed, this,
          &tab_enabler::repair_now);
  repair_button->setEnabled(false);
  enabler_layout->addWidget(repair_button, 3, 1);

  refresh();

  main_layout->addLayout(enabler_layout);

  setLayout(main_layout);
}

/**
   Refresh the information.
 */
void tab_enabler::refresh()
{
  int n = 0;

  enabler_list->clear();

  action_enablers_iterate(enabler)
  {
    if (!enabler->disabled) {
      char buffer[512];
      QListWidgetItem *item;

      fc_snprintf(buffer, sizeof(buffer), "#%d: %s", n,
                  action_rule_name(enabler_get_action(enabler)));

      item = new QListWidgetItem(QString::fromUtf8(buffer));

      enabler_list->insertItem(n++, item);

      mark_item(item, enabler_problem_level(enabler));

      item->setSelected(enabler == selected);
    }
  }
  action_enablers_iterate_end;
}

/**
   Update info of the enabler
 */
void tab_enabler::update_enabler_info(struct action_enabler *enabler)
{
  int i = 0;

  selected = enabler;

  if (selected != nullptr) {
    QString dispn =
        QString::fromUtf8(action_rule_name(enabler_get_action(enabler)));

    type_button->setText(dispn);

    type_button->setEnabled(true);
    act_reqs_button->setEnabled(true);
    tgt_reqs_button->setEnabled(true);

    delete_button->setEnabled(true);

    switch (enabler_problem_level(selected)) {
    case RVPS_REPAIR:
      // Offer to repair the enabler if it has a problem.
      // TRANS: Fix an error in an action enabler.
      repair_button->setText(QString::fromUtf8(R__("Repair Enabler")));
      repair_button->setEnabled(true);
      break;
    case RVPS_IMPROVE:
      // TRANS: Fix a non error issue in an action enabler.
      repair_button->setText(QString::fromUtf8(R__("Improve Enabler")));
      repair_button->setEnabled(true);
      break;
    case RVPS_NO_PROBLEM:
      repair_button->setText(QString::fromUtf8(R__("Enabler Issues")));
      repair_button->setEnabled(false);
      break;
    }
  } else {
    type_button->setText(QStringLiteral("None"));
    type_button->setEnabled(false);

    act_reqs_button->setEnabled(false);
    tgt_reqs_button->setEnabled(false);

    repair_button->setEnabled(false);
    repair_button->setText(QString::fromUtf8(R__("Enabler Issues")));

    delete_button->setEnabled(false);
  }

  // The enabler may have gotten (rid of) a problem.
  action_enablers_iterate(enabler)
  {
    QListWidgetItem *item = enabler_list->item(i++);

    if (item == nullptr) {
      continue;
    }

    mark_item(item, enabler_problem_level(enabler));
  }
  action_enablers_iterate_end;
}

/**
   User selected enabler from the list.
 */
void tab_enabler::select_enabler()
{
  int i = 0;

  action_enablers_iterate(enabler)
  {
    QListWidgetItem *item = enabler_list->item(i++);

    if (item != nullptr && item->isSelected()) {
      update_enabler_info(enabler);
    }
  }
  action_enablers_iterate_end;
}

/**
   User requested enabler deletion
 */
void tab_enabler::delete_now()
{
  if (selected != nullptr) {
    selected->disabled = true;

    refresh();
    update_enabler_info(nullptr);
  }
}

/**
   Initialize new enabler for use.
 */
bool tab_enabler::initialize_new_enabler(struct action_enabler *enabler)
{
  return true;
}

/**
   User requested new enabler
 */
void tab_enabler::add_now()
{
  struct action_enabler *new_enabler;

  // Try to reuse freed enabler slot
  action_enablers_iterate(enabler)
  {
    if (enabler->disabled) {
      if (initialize_new_enabler(enabler)) {
        enabler->disabled = false;
        update_enabler_info(enabler);
        refresh();
      }
      return;
    }
  }
  action_enablers_iterate_end;

  // Try to add completely new enabler
  new_enabler = action_enabler_new();

  fc_assert_action(NUM_ACTIONS > 0, action_enabler_free(new_enabler);
                   return );
  fc_assert_action(action_id_exists(NUM_ACTIONS - 1),
                   action_enabler_free(new_enabler);
                   return );
  new_enabler->action = (NUM_ACTIONS - 1);

  action_enabler_add(new_enabler);

  update_enabler_info(new_enabler);
  refresh();
}

/**
   User requested enabler repair
 */
void tab_enabler::repair_now()
{
  if (selected == nullptr) {
    // Nothing to repair
    return;
  }

  ui->open_req_vec_fix(new fix_enabler_item(selected));
}

/**
   A requirement vector may have been changed.
   @param vec the requirement vector that may have been changed.
 */
void tab_enabler::incoming_rec_vec_change(const requirement_vector *vec)
{
  action_enablers_iterate(enabler)
  {
    if (&enabler->actor_reqs == vec || &enabler->target_reqs == vec) {
      update_enabler_info(enabler);
    }
  }
  action_enablers_iterate_end;
}

/**
   User selected action to enable
 */
void tab_enabler::edit_type(QAction *action)
{
  struct action *paction;
  QByteArray an_bytes;

  an_bytes = action->text().toUtf8();
  paction = action_by_rule_name(an_bytes.data());

  if (selected != nullptr && paction != nullptr) {
    // Must remove and add back because enablers are stored by action.
    action_enabler_remove(selected);
    selected->action = paction->id;
    action_enabler_add(selected);

    // Show the changes.
    update_enabler_info(selected);
    refresh();
  }
}

/**
   User wants to edit target reqs
 */
void tab_enabler::edit_target_reqs()
{
  if (selected != nullptr) {
    ui->open_req_edit(QString::fromUtf8(R__("Enabler (target)")),
                      &selected->target_reqs);
  }
}

/**
   User wants to edit actor reqs
 */
void tab_enabler::edit_actor_reqs()
{
  if (selected != nullptr) {
    ui->open_req_edit(QString::fromUtf8(R__("Enabler (actor)")),
                      &selected->actor_reqs);
  }
}

/**
   Construct fix_enabler_item to help req_vec_fix with the action enabler
   unique stuff.
 */
fix_enabler_item::fix_enabler_item(struct action_enabler *enabler)
    : current_enabler(nullptr), local_copy(nullptr)
{
  char buf[MAX_LEN_NAME * 2];
  struct action *paction = action_by_number(enabler->action);

  fc_assert_ret(paction);

  /* Can't use QString::asprintf() as msys libintl.h defines asprintf()
   * as a macro */
  fc_snprintf(buf, sizeof(buf), R__("action enabler for %s"),
              action_rule_name(paction));

  // Don't modify the original until the user accepts
  local_copy = action_enabler_copy(enabler);
  current_enabler = enabler;

  // As precise a title as possible
  my_name = QString(buf);
}

/**
   Destructor for fix_enabler_item
 */
fix_enabler_item::~fix_enabler_item() { action_enabler_free(local_copy); }

/**
   Tell the helper that it has outlived its usefulnes.
 */
void fix_enabler_item::close() { delete this; }

/**
   Returns a pointer to the ruleset item.
   @return a pointer to the ruleset item.
 */
const void *fix_enabler_item::item() { return current_enabler; }

/**
     Returns a pointer to the working copy of the ruleset item.
     @return a pointer to the working copy of the ruleset item.
 */
void *fix_enabler_item::item_working_copy() { return local_copy; }

/**
   Returns a name to describe the item, hopefully good enough to
   distinguish it from other items. Must be short enough for a quick
   mention.
   @return a (not always unique) name for the ruleset item.
 */
const char *fix_enabler_item::name() { return my_name.toUtf8().data(); }

/**
   Returns the next detected requirement vector problem for the ruleset
   item or nullptr if no fix is found to be needed.
   @return the next requirement vector problem for the item.
 */
struct req_vec_problem *fix_enabler_item::find_next_problem()
{
  struct req_vec_problem *out = action_enabler_suggest_repair(local_copy);
  if (out != nullptr) {
    return out;
  }

  return action_enabler_suggest_improvement(local_copy);
}

/**
   Do all the changes the user has accepted to the ruleset item.
   N.B.: This could be called *before* all problems are fixed if the user
   wishes to try to fix problems by hand or to come back and fix the
   remaining problems later.
 */
void fix_enabler_item::apply_accepted_changes()
{
  // The user has approved the solution
  current_enabler->action = local_copy->action;
  requirement_vector_copy(&current_enabler->actor_reqs,
                          &local_copy->actor_reqs);
  requirement_vector_copy(&current_enabler->target_reqs,
                          &local_copy->target_reqs);
}

/**
   Undo all the changes the user has accepted to the ruleset item.
   N.B.: This could be called *after* all problems are fixed if the user
   wishes to see all problems and try to fix them by hand.
 */
void fix_enabler_item::undo_accepted_changes()
{
  // The user has rejected all solutions
  local_copy->action = current_enabler->action;
  requirement_vector_copy(&local_copy->actor_reqs,
                          &current_enabler->actor_reqs);
  requirement_vector_copy(&local_copy->target_reqs,
                          &current_enabler->target_reqs);
}

/**
   Returns the number of requirement vectors in this item.
   @return the number of requirement vectors the item has.
 */
int fix_enabler_item::num_vectors() { return 2; }

/**
   Returns a function pointer to a function that names this item kind's
   requirement vector number number. Useful when there is more than one
   requirement vector.
   @return the requirement vector namer for ruleset items of this kind.
 */
requirement_vector_namer fix_enabler_item::vector_namer()
{
  return action_enabler_vector_by_number_name;
}

/**
   Returns a function pointer to a function that returns a writable
   pointer to the specified requirement vector in the specified parent
   item.
   @return a writable pointer to the requirement vector getter function.
 */
requirement_vector_by_number fix_enabler_item::vector_getter()
{
  return action_enabler_vector_by_number;
}

/**
   Check if the specified vector belongs to this item
   @param vec the requirement vector that may belong to this item.
   @return true iff the vector belongs to this item.
 */
bool fix_enabler_item::vector_in_item(const struct requirement_vector *vec)
{
  return (&current_enabler->actor_reqs == vec
          || &current_enabler->target_reqs == vec);
}
