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

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

// common
#include "calendar.h"
#include "events.h"
#include "game.h"
#include "nation.h"
#include "packets.h"
#include "spaceship.h"

// server
#include "notify.h"
#include "plrhand.h"
#include "spacerace.h"

/**
   Calculate and fill in the derived quantities about the spaceship.
   Data reverse engineered from Civ1. --dwp
   This could be in common, but its better for the client to take
   the values the server calculates, in case things change.
 */
void spaceship_calc_derived(struct player_spaceship *ship)
{
  int i;
  // these are how many are connected:
  int fuel = 0;
  int propulsion = 0;
  int habitation = 0;
  int life_support = 0;
  int solar_panels = 0;

  fc_assert_ret(ship->structurals <= NUM_SS_STRUCTURALS);
  fc_assert_ret(ship->components <= NUM_SS_COMPONENTS);
  fc_assert_ret(ship->modules <= NUM_SS_MODULES);

  ship->mass = 0;
  ship->support_rate = ship->energy_rate = ship->success_rate =
      ship->travel_time = 0.0;

  for (i = 0; i < NUM_SS_STRUCTURALS; i++) {
    if (BV_ISSET(ship->structure, i)) {
      ship->mass += (i < 6) ? 200 : 100;
      /* s0 to s3 are heavier; actually in Civ1 its a bit stranger
         than this, but not worth figuring out --dwp */
    }
  }
  for (i = 0; i < ship->fuel; i++) {
    if (BV_ISSET(ship->structure, components_info[i * 2].required)) {
      fuel++;
    }
  }
  for (i = 0; i < ship->propulsion; i++) {
    if (BV_ISSET(ship->structure, components_info[i * 2 + 1].required)) {
      propulsion++;
    }
  }
  for (i = 0; i < ship->habitation; i++) {
    if (BV_ISSET(ship->structure, modules_info[i * 3].required)) {
      habitation++;
    }
  }
  for (i = 0; i < ship->life_support; i++) {
    if (BV_ISSET(ship->structure, modules_info[i * 3 + 1].required)) {
      life_support++;
    }
  }
  for (i = 0; i < ship->solar_panels; i++) {
    if (BV_ISSET(ship->structure, modules_info[i * 3 + 2].required)) {
      solar_panels++;
    }
  }

  ship->mass += 1600 * (habitation + life_support)
                + 400 * (solar_panels + propulsion + fuel);

  ship->population = habitation * 10000;

  if (habitation > 0) {
    ship->support_rate = life_support / static_cast<double>(habitation);
  }
  if (life_support + habitation > 0) {
    ship->energy_rate =
        2.0 * solar_panels / static_cast<double>(life_support + habitation);
  }
  if (fuel > 0 && propulsion > 0) {
    ship->success_rate =
        MIN(ship->support_rate, 1.0) * MIN(ship->energy_rate, 1.0);
  }

  /* The Success% can be less by up to a few % in some cases
     (I think if P != F or if P and/or F too small (eg <= 2?) ?)
     but probably not worth worrying about.
     Actually, the Civ1 manual suggests travel time is relevant. --dwp
  */

  ship->travel_time = ship->mass * game.server.spaceship_travel_time / 100
                      / (200.0 * MIN(propulsion, fuel) + 20.0);
}

/**
   Send details of src's spaceship (or spaceships of all players
   if src is nullptr) to specified destinations.  If dest is nullptr then
   game.est_connections is used.
 */
void send_spaceship_info(struct player *src, struct conn_list *dest)
{
  if (!dest) {
    dest = game.est_connections;
  }

  players_iterate(pplayer)
  {
    if (!src || pplayer == src) {
      struct packet_spaceship_info info;
      struct player_spaceship *ship = &pplayer->spaceship;

      info.player_num = player_number(pplayer);
      info.sship_state = ship->state;
      info.structurals = ship->structurals;
      info.components = ship->components;
      info.modules = ship->modules;
      info.fuel = ship->fuel;
      info.propulsion = ship->propulsion;
      info.habitation = ship->habitation;
      info.life_support = ship->life_support;
      info.solar_panels = ship->solar_panels;
      info.launch_year = ship->launch_year;
      info.population = ship->population;
      info.mass = ship->mass;
      info.support_rate = ship->support_rate;
      info.energy_rate = ship->energy_rate;
      info.success_rate = ship->success_rate;
      info.travel_time = ship->travel_time;
      info.structure = ship->structure;

      lsend_packet_spaceship_info(dest, &info);
    }
  }
  players_iterate_end;
}

/**
   Handle spaceship launch request.
 */
void handle_spaceship_launch(struct player *pplayer)
{
  struct player_spaceship *ship = &pplayer->spaceship;
  int arrival;

  if (!player_primary_capital(pplayer)) {
    notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                  _("You need to have a capital in order to launch "
                    "your spaceship."));
    return;
  }
  if (ship->state >= SSHIP_LAUNCHED) {
    notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                  _("Your spaceship is already launched!"));
    return;
  }
  if (ship->state != SSHIP_STARTED || ship->success_rate == 0.0) {
    notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                  _("Your spaceship can't be launched yet!"));
    return;
  }

  ship->state = SSHIP_LAUNCHED;
  ship->launch_year = game.info.year;
  arrival = ship->launch_year + static_cast<int>(ship->travel_time);

  notify_player(nullptr, nullptr, E_SPACESHIP, ftc_server,
                _("The %s have launched a spaceship!  "
                  "It is estimated to arrive at Alpha Centauri in %s."),
                nation_plural_for_player(pplayer), textyear(arrival));

  send_spaceship_info(pplayer, nullptr);
}

/**
   Handle spaceship part placement request
 */
void handle_spaceship_place(struct player *pplayer,
                            enum spaceship_place_type type, int num)
{
  (void) do_spaceship_place(pplayer, ACT_REQ_PLAYER, type, num);
}

/**
   Place a spaceship part
 */
bool do_spaceship_place(struct player *pplayer, enum action_requester from,
                        enum spaceship_place_type type, int num)
{
  struct player_spaceship *ship = &pplayer->spaceship;

  if (ship->state == SSHIP_NONE) {
    if (from == ACT_REQ_PLAYER) {
      notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                    _("Spaceship action received,"
                      " but you don't have a spaceship!"));
    }

    return false;
  }

  if (ship->state >= SSHIP_LAUNCHED) {
    if (from == ACT_REQ_PLAYER) {
      notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                    _("You can't modify your spaceship after launch!"));
    }

    return false;
  }

  if (type == SSHIP_PLACE_STRUCTURAL) {
    if (num < 0 || num >= NUM_SS_STRUCTURALS
        || BV_ISSET(ship->structure, num)) {
      return false;
    }
    if (num_spaceship_structurals_placed(ship) >= ship->structurals) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("You don't have any unplaced Space Structurals!"));
      }

      return false;
    }
    if (num != 0
        && !BV_ISSET(ship->structure, structurals_info[num].required)) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("That Space Structural would not be connected!"));
      }

      return false;
    }

    BV_SET(ship->structure, num);
    spaceship_calc_derived(ship);
    send_spaceship_info(pplayer, nullptr);
    return true;
  }

  if (type == SSHIP_PLACE_FUEL) {
    if (ship->fuel != num - 1) {
      return false;
    }
    if (ship->fuel + ship->propulsion >= ship->components) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("You don't have any unplaced Space Components!"));
      }

      return false;
    }
    if (num > NUM_SS_COMPONENTS / 2) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("Your spaceship already has"
                        " the maximum number of Fuel Components!"));
      }

      return false;
    }

    ship->fuel++;
    spaceship_calc_derived(ship);
    send_spaceship_info(pplayer, nullptr);
    return true;
  }

  if (type == SSHIP_PLACE_PROPULSION) {
    if (ship->propulsion != num - 1) {
      return false;
    }
    if (ship->fuel + ship->propulsion >= ship->components) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("You don't have any unplaced"
                        " Space Components!"));
      }

      return false;
    }
    if (num > NUM_SS_COMPONENTS / 2) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("Your spaceship already has the"
                        " maximum number of Propulsion Components!"));
      }

      return false;
    }

    ship->propulsion++;
    spaceship_calc_derived(ship);
    send_spaceship_info(pplayer, nullptr);
    return true;
  }

  if (type == SSHIP_PLACE_HABITATION) {
    if (ship->habitation != num - 1) {
      return false;
    }
    if (ship->habitation + ship->life_support + ship->solar_panels
        >= ship->modules) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("You don't have any unplaced Space Modules!"));
      }

      return false;
    }
    if (num > NUM_SS_MODULES / 3) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("Your spaceship already has the"
                        " maximum number of Habitation Modules!"));
      }

      return false;
    }

    ship->habitation++;
    spaceship_calc_derived(ship);
    send_spaceship_info(pplayer, nullptr);
    return true;
  }

  if (type == SSHIP_PLACE_LIFE_SUPPORT) {
    if (ship->life_support != num - 1) {
      return false;
    }
    if (ship->habitation + ship->life_support + ship->solar_panels
        >= ship->modules) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("You don't have any unplaced Space Modules!"));
      }

      return false;
    }
    if (num > NUM_SS_MODULES / 3) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("Your spaceship already has the"
                        " maximum number of Life Support Modules!"));
      }

      return false;
    }

    ship->life_support++;
    spaceship_calc_derived(ship);
    send_spaceship_info(pplayer, nullptr);
    return true;
  }

  if (type == SSHIP_PLACE_SOLAR_PANELS) {
    if (ship->solar_panels != num - 1) {
      return false;
    }
    if (ship->habitation + ship->life_support + ship->solar_panels
        >= ship->modules) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("You don't have any unplaced Space Modules!"));
      }

      return false;
    }
    if (num > NUM_SS_MODULES / 3) {
      if (from == ACT_REQ_PLAYER) {
        notify_player(pplayer, nullptr, E_SPACESHIP, ftc_server,
                      _("Your spaceship already has the"
                        " maximum number of Solar Panel Modules!"));
      }

      return false;
    }

    ship->solar_panels++;
    spaceship_calc_derived(ship);
    send_spaceship_info(pplayer, nullptr);
    return true;
  }

  qCritical("Received unknown spaceship place type %d from %s", type,
            player_name(pplayer));
  return false;
}

/**
   Handle spaceship arrival.
 */
void spaceship_arrived(struct player *pplayer)
{
  notify_player(nullptr, nullptr, E_SPACESHIP, ftc_server,
                _("The %s spaceship has arrived at Alpha Centauri."),
                nation_adjective_for_player(pplayer));
  pplayer->spaceship.state = SSHIP_ARRIVED;
}

/**
   Handle spaceship loss.
 */
void spaceship_lost(struct player *pplayer)
{
  notify_player(nullptr, nullptr, E_SPACESHIP, ftc_server,
                _("Without guidance from the capital, the %s "
                  "spaceship is lost!"),
                nation_adjective_for_player(pplayer));
  spaceship_init(&pplayer->spaceship);
  send_spaceship_info(pplayer, nullptr);
}

/**
   Return arrival year of player's spaceship (fractional, as one spaceship
   may arrive before another in a given year).
   Only meaningful if spaceship has been launched.
 */
double spaceship_arrival(const struct player *pplayer)
{
  const struct player_spaceship *ship = &pplayer->spaceship;

  return ship->launch_year + ship->travel_time;
}

/**
   Rank launched player spaceships in order of arrival.
   'result' is an array big enough to hold all the players.
   Returns number of launched spaceships, having filled the start of
   'result' with that many players in order of predicted arrival.
   Uses shuffled player order in case of a tie.
 */
int rank_spaceship_arrival(struct player **result)
{
  int n = 0, i;

  shuffled_players_iterate(pplayer)
  {
    struct player_spaceship *ship = &pplayer->spaceship;

    if (ship->state == SSHIP_LAUNCHED) {
      result[n++] = pplayer;
    }
  }
  shuffled_players_iterate_end;

  /* An insertion sort will do; n is probably small, and we need a
   * stable sort to preserve the shuffled order for tie-breaking, so can't
   * use qsort() */
  for (i = 1; i < n; i++) {
    int j;
    for (j = i;
         j > 0
         && spaceship_arrival(result[j - 1]) > spaceship_arrival(result[j]);
         j--) {
      struct player *tmp = result[j];
      result[j] = result[j - 1];
      result[j - 1] = tmp;
    }
  }

  return n;
}
