/*__            ___                 ***************************************
/   \          /   \          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/.
      \____/        ********************************************************/

#include <fc_config.h>

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

/* common */
#include "city.h"
#include "game.h"
#include "map.h"
#include "movement.h"
#include "tile.h"
#include "unit.h"
#include "workertask.h"

/* server */
#include "citytools.h"

/* server/advisors */
#include "advbuilding.h"
#include "advdata.h"
#include "autosettlers.h"
#include "infracache.h"

/* ai/default */
#include "aidata.h"

/* ai/tex */
#include "texaimsg.h"
#include "texaiplayer.h"

#include "texaicity.h"

/* Task Want Multiplier */
#define TWMP 100

struct texai_worker_task_req {
  int city_id;
  struct worker_task task;
};

enum texai_worker_task_limitation {
  TWTL_CURRENT_UNITS,
  TWTL_BUILDABLE_UNITS
};

static bool
texai_city_worker_task_select(struct ai_type *ait, struct player *pplayer,
                              struct city *pcity, struct worker_task *task,
                              enum texai_worker_task_limitation limit);

/**********************************************************************/ /**
   Create worker request for the city. Only tasks that existing units can
   do are created.
 **************************************************************************/
void texai_city_worker_requests_create(struct ai_type *ait,
                                       struct player *pplayer,
                                       struct city *pcity)
{
  struct worker_task task;

  if (texai_city_worker_task_select(ait, pplayer, pcity, &task,
                                    TWTL_CURRENT_UNITS)) {
    struct texai_worker_task_req *data = fc_malloc(sizeof(*data));

    data->city_id = pcity->id;
    data->task.ptile = task.ptile;
    data->task.act = task.act;
    data->task.tgt = task.tgt;
    data->task.want = task.want;

    texai_send_req(TEXAI_REQ_WORKER_TASK, pplayer, data);
  }
}

/**********************************************************************/ /**
   Set wants for worker-type units.
 **************************************************************************/
void texai_city_worker_wants(struct ai_type *ait, struct player *pplayer,
                             struct city *pcity)
{
  struct worker_task task;
  struct texai_city *cdata = texai_city_data(ait, pcity);
  int *wants = cdata->unit_wants;
  int maxwant = 0;
  struct unit_type *type = NULL;
  Continent_id place = tile_continent(city_tile(pcity));
  struct ai_plr *dai = dai_plr_data_get(ait, pplayer, NULL);
  struct adv_data *adv = adv_data_get(pplayer, NULL);

  unit_type_iterate(ptype) { wants[utype_index(ptype)] = 0; }
  unit_type_iterate_end;

  texai_city_worker_task_select(ait, pplayer, pcity, &task,
                                TWTL_BUILDABLE_UNITS);

  unit_type_iterate(ptype)
  {
    int twant = wants[utype_index(ptype)];

    twant -= 20 * ptype->pop_cost;
    twant -= ptype->build_cost;
    twant -= ptype->upkeep[O_FOOD] * FOOD_WEIGHTING / 2;

    /* Massage our desire based on available statistics to prevent
     * overflooding with worker type units if they come cheap in
     * the ruleset */
    if (place >= 0) {
      twant /=
          MAX(1, dai->stats.workers[place] / (adv->stats.cities[place] + 1));
      twant -= dai->stats.workers[place];
    } else {
      /* TODO: Handle Oceans with cities sensibly */
    }

    twant = MAX(twant, 0);

    if (twant > maxwant) {
      maxwant = twant;
      type = ptype;
    }
  }
  unit_type_iterate_end;

  cdata->defai.worker_want = maxwant / 70;
  cdata->defai.worker_type = type;
}

struct texai_tile_state {
  int uw_max_base; /* Value for the city working the tile */
  int uw_max;      /* With road connectivity bonus */
  int worst_worked;
  int orig_worst_worked;
  int old_worst_worked;
  int *wants;
};

/**********************************************************************/ /**
   Select worker task suitable for the tile.
 **************************************************************************/
static void texai_tile_worker_task_select(
    struct player *pplayer, struct city *pcity, struct tile *ptile,
    int cindex, struct unit_list *units, struct worker_task *worked,
    struct worker_task *unworked, struct texai_tile_state *state,
    enum texai_worker_task_limitation limit)
{
  int orig_value;
  bool potential_worst_worked = false;

  if (!city_can_work_tile(pcity, ptile)) {
    return;
  }

  orig_value = city_tile_value(pcity, ptile, 0, 0);

  if (tile_worked(ptile) == pcity && orig_value < state->worst_worked) {
    state->worst_worked = orig_value;
    state->orig_worst_worked = orig_value;
    potential_worst_worked = true;
  }

  as_transform_action_iterate(act)
  {
    bool consider = true;
    bool possible = false;
    enum extra_cause cause;
    enum extra_rmcause rmcause;
    struct extra_type *tgt = NULL;

    /* Do not request activities that already are under way. */
    unit_list_iterate(ptile->units, punit)
    {
      if (unit_owner(punit) == pplayer
          && unit_has_type_flag(punit, UTYF_SETTLERS)
          && punit->activity == action_id_get_activity(act)) {
        consider = false;
        break;
      }
    }
    unit_list_iterate_end;

    if (!consider) {
      continue;
    }

    cause = activity_to_extra_cause(action_id_get_activity(act));
    rmcause = activity_to_extra_rmcause(action_id_get_activity(act));

    unit_list_iterate(units, punit)
    {
      if (cause != EC_NONE) {
        tgt = next_extra_for_tile(ptile, cause, pplayer, punit);
      } else if (rmcause != ERM_NONE) {
        tgt = prev_extra_in_tile(ptile, rmcause, pplayer, punit);
      }

      if (action_prob_possible(action_speculate_unit_on_tile(
              act, punit, unit_home(punit), ptile, true, ptile, tgt))) {
        possible = true;
        break;
      }
    }
    unit_list_iterate_end;

    if (possible) {
      int value = adv_city_worker_act_get(pcity, cindex,
                                          action_id_get_activity(act));

      if (tile_worked(ptile) == pcity) {
        if ((value - orig_value) * TWMP > worked->want) {
          worked->want = TWMP * (value - orig_value);
          worked->ptile = ptile;
          worked->act = action_id_get_activity(act);
          worked->tgt = NULL;
          if (limit == TWTL_BUILDABLE_UNITS) {
            unit_list_iterate(units, punit)
            {
              if (action_prob_possible(action_speculate_unit_on_tile(
                      act, punit, unit_home(punit), ptile, true, ptile,
                      tgt))) {
                state->wants[utype_index(unit_type_get(punit))] +=
                    worked->want;
              }
            }
            unit_list_iterate_end;
          }
        }
        if (value > state->old_worst_worked) {
          /* After improvement it would not be the worst */
          potential_worst_worked = false;
        } else {
          state->worst_worked = value;
        }
      } else {
        if (value > orig_value && value > state->uw_max) {
          state->uw_max = value;
          state->uw_max_base = value;
          unworked->want = TWMP * (value - orig_value);
          unworked->ptile = ptile;
          unworked->act = action_id_get_activity(act);
          unworked->tgt = NULL;
          if (limit == TWTL_BUILDABLE_UNITS) {
            unit_list_iterate(units, punit)
            {
              if (action_prob_possible(action_speculate_unit_on_tile(
                      act, punit, unit_home(punit), ptile, true, ptile,
                      tgt))) {
                state->wants[utype_index(unit_type_get(punit))] +=
                    unworked->want;
              }
            }
            unit_list_iterate_end;
          }
        }
      }
    }
  }
  as_transform_action_iterate_end;

  extra_type_iterate(tgt)
  {
    struct action *paction = NULL;
    bool removing = tile_has_extra(ptile, tgt);

    unit_list_iterate(units, punit)
    {
      if (removing) {
        as_rmextra_action_iterate(try_act)
        {
          struct action *taction = action_by_number(try_act);
          if (is_extra_removed_by_action(tgt, taction)
              && action_prob_possible(action_speculate_unit_on_tile(
                  try_act, punit, unit_home(punit), ptile, true, ptile,
                  tgt))) {
            paction = taction;
            break;
          }
        }
        as_rmextra_action_iterate_end;
      } else {
        as_extra_action_iterate(try_act)
        {
          struct action *taction = action_by_number(try_act);
          if (is_extra_caused_by_action(tgt, taction)
              && action_prob_possible(action_speculate_unit_on_tile(
                  try_act, punit, unit_home(punit), ptile, true, ptile,
                  tgt))) {
            paction = taction;
            break;
          }
        }
        as_extra_action_iterate_end;
      }
    }
    unit_list_iterate_end;

    if (paction != NULL) {
      adv_want base_value;
      int value;
      adv_want extra;
      bool consider = true;
      struct road_type *proad;

      /* Do not request activities that already are under way. */
      unit_list_iterate(ptile->units, punit)
      {
        if (unit_owner(punit) == pplayer
            && unit_has_type_flag(punit, UTYF_SETTLERS)
            && punit->activity == action_get_activity(paction)) {
          consider = false;
          break;
        }
      }
      unit_list_iterate_end;

      if (!consider) {
        continue;
      }

      proad = extra_road_get(tgt);

      if (removing) {
        base_value = adv_city_worker_rmextra_get(pcity, cindex, tgt);
      } else {
        base_value = adv_city_worker_extra_get(pcity, cindex, tgt);
      }

      if (proad != NULL && road_provides_move_bonus(proad)) {
        int old_move_cost;
        int mc_multiplier = 1;
        int mc_divisor = 1;

        /* Here 'old' means actually 'without the evaluated': In case of
         * removal activity it's the value after the removal. */
        old_move_cost = tile_terrain(ptile)->movement_cost * SINGLE_MOVE;

        extra_type_by_cause_iterate(EC_ROAD, poe)
        {
          struct road_type *pold = extra_road_get(poe);

          if (tile_has_extra(ptile, poe) && poe != tgt) {
            if (road_provides_move_bonus(pold)
                && pold->move_cost < old_move_cost) {
              old_move_cost = pold->move_cost;
            }
          }
        }
        extra_type_by_cause_iterate_end;

        if (proad->move_cost < old_move_cost) {
          if (proad->move_cost >= terrain_control.move_fragments) {
            mc_divisor = proad->move_cost / terrain_control.move_fragments;
          } else {
            if (proad->move_cost == 0) {
              mc_multiplier = 2;
            } else {
              mc_multiplier = 1 - proad->move_cost;
            }
            mc_multiplier += old_move_cost;
          }
        }

        extra = adv_settlers_road_bonus(ptile, proad) * mc_multiplier
                / mc_divisor;

        if (removing) {
          extra = -extra;
        }
      } else {
        extra = 0;
      }

      value = base_value + extra;

      if (tile_worked(ptile) == pcity) {
        if ((value - orig_value) * TWMP > worked->want) {
          worked->want = TWMP * (value - orig_value);
          worked->ptile = ptile;
          worked->act = action_get_activity(paction);
          worked->tgt = tgt;
          if (limit == TWTL_BUILDABLE_UNITS) {
            unit_list_iterate(units, punit)
            {
              fc_assert_action(action_get_target_kind(paction) == ATK_TILE,
                               break);
              if (action_prob_possible(action_speculate_unit_on_tile(
                      paction->id, punit, unit_home(punit), ptile, true,
                      ptile, tgt))) {
                state->wants[utype_index(unit_type_get(punit))] +=
                    worked->want;
              }
            }
            unit_list_iterate_end;
          }
        }
        if (value > state->old_worst_worked) {
          /* After improvement it would not be the worst */
          potential_worst_worked = false;
        } else {
          state->worst_worked = value;
        }
      } else {
        if (value > orig_value && value > state->uw_max) {
          state->uw_max = value;
          state->uw_max_base = base_value;
          unworked->want = TWMP * (value - orig_value);
          unworked->ptile = ptile;
          unworked->act = action_get_activity(paction);
          unworked->tgt = tgt;
          if (limit == TWTL_BUILDABLE_UNITS) {
            unit_list_iterate(units, punit)
            {
              fc_assert_action(action_get_target_kind(paction) == ATK_TILE,
                               break);
              if (action_prob_possible(action_speculate_unit_on_tile(
                      paction->id, punit, unit_home(punit), ptile, true,
                      ptile, tgt))) {
                state->wants[utype_index(unit_type_get(punit))] +=
                    unworked->want;
              }
            }
            unit_list_iterate_end;
          }
        }
      }
    }
  }
  extra_type_iterate_end;

  if (potential_worst_worked) {
    /* Would still be worst worked even if we improved *it*. */
    state->old_worst_worked = state->worst_worked;
  }
}

/**********************************************************************/ /**
   Select worker task suitable for the city.
 **************************************************************************/
static bool
texai_city_worker_task_select(struct ai_type *ait, struct player *pplayer,
                              struct city *pcity, struct worker_task *task,
                              enum texai_worker_task_limitation limit)
{
  struct worker_task *selected;
  struct worker_task worked = {
      .ptile = NULL, .want = 0, .act = ACTIVITY_IDLE, .tgt = NULL};
  struct worker_task unworked = {
      .ptile = NULL, .want = 0, .act = ACTIVITY_IDLE, .tgt = NULL};
  struct texai_tile_state state = {.uw_max = 0,
                                   .uw_max_base = 0,
                                   .worst_worked = FC_INFINITY,
                                   .orig_worst_worked = 0,
                                   .old_worst_worked = FC_INFINITY};
  struct unit_list *units = NULL;

  switch (limit) {
  case TWTL_CURRENT_UNITS:
    units = texai_player_units(pplayer);
    state.wants = NULL;
    break;
  case TWTL_BUILDABLE_UNITS:
    units = unit_list_new();
    unit_type_iterate(ptype)
    {
      if (can_city_build_unit_now(pcity, ptype)) {
        unit_list_append(units,
                         unit_virtual_create(pplayer, pcity, ptype, 0));
      }
    }
    unit_type_iterate_end;
    state.wants = texai_city_data(ait, pcity)->unit_wants;
    break;
  }

  city_tile_iterate_index(city_map_radius_sq_get(pcity), city_tile(pcity),
                          ptile, cindex)
  {
    texai_tile_worker_task_select(pplayer, pcity, ptile, cindex, units,
                                  &worked, &unworked, &state, limit);
  }
  city_tile_iterate_end;

  if (worked.ptile == NULL
      || (state.old_worst_worked < state.uw_max
          && (state.uw_max - state.orig_worst_worked) * TWMP > worked.want)
      || (state.uw_max - state.uw_max_base * TWMP > worked.want)) {
    /* It's better to improve best yet unworked tile and take it to use after
       that, than to improve already worked tile. OR it's more important to
       improve road connectivity outside worked tiles than improve worked
       tiles */
    selected = &unworked;
  } else {
    selected = &worked;
  }

  if (limit == TWTL_BUILDABLE_UNITS) {
    unit_list_iterate(units, punit) { unit_virtual_destroy(punit); }
    unit_list_iterate_end;
    unit_list_destroy(units);
  }

  if (selected->ptile != NULL) {
    struct extra_type *target = NULL;

    if (selected->tgt == NULL) {
      enum extra_cause cause = activity_to_extra_cause(selected->act);

      if (cause != EC_NONE) {
        target = next_extra_for_tile(selected->ptile, cause, pplayer, NULL);
      } else {
        enum extra_rmcause rmcause =
            activity_to_extra_rmcause(selected->act);

        if (rmcause != ERM_NONE) {
          target =
              prev_extra_in_tile(selected->ptile, rmcause, pplayer, NULL);
        }
      }
    } else {
      target = selected->tgt;
    }

    task->ptile = selected->ptile;
    task->act = selected->act;
    task->tgt = target;
    task->want = selected->want;

    return true;
  }

  return false;
}

/**********************************************************************/ /**
   Receive message from thread to main thread.
 **************************************************************************/
void texai_req_worker_task_rcv(struct texai_req *req)
{
  struct texai_worker_task_req *data =
      (struct texai_worker_task_req *) req->data;
  struct city *pcity;

  pcity = game_city_by_number(data->city_id);

  if (pcity != NULL && city_owner(pcity) == req->plr) {
    /* City has not been lost meanwhile */
    struct worker_task *ptask = worker_task_list_get(pcity->task_reqs, 0);

    if (ptask == NULL) {
      ptask = new worker_task{};
      worker_task_init(ptask);
      worker_task_list_append(pcity->task_reqs, ptask);
    }

    log_debug("%s storing req for act %d at (%d,%d)", pcity->name,
              data->task.act, TILE_XY(data->task.ptile));
    ptask->ptile = data->task.ptile;
    ptask->act = data->task.act;
    ptask->tgt = data->task.tgt;
    ptask->want = data->task.want;

    /* Send info to observers */
    package_and_send_worker_tasks(pcity);
  }

  delete[] data;
}

/**********************************************************************/ /**
   Initialize city for use with tex AI.
 **************************************************************************/
void texai_city_alloc(struct ai_type *ait, struct city *pcity)
{
  struct texai_city *city_data = new struct texai_city {
  };

  city_data->defai.building_wait = BUILDING_WAIT_MINIMUM;
  adv_init_choice(&(city_data->defai.choice));

  city_set_ai_data(pcity, ait, city_data);
}

/**********************************************************************/ /**
   Free city from use with tex AI.
 **************************************************************************/
void texai_city_free(struct ai_type *ait, struct city *pcity)
{
  struct texai_city *city_data = texai_city_data(ait, pcity);

  if (city_data != NULL) {
    adv_deinit_choice(&(city_data->defai.choice));
    city_set_ai_data(pcity, ait, nullptr);
    delete city_data;
    city_data = nullptr;
  }
}
