/*
Copyright (C) by Dmitry E. Oboukhov 2006, 2007

  This package 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 2 of the License, or
  (at your option) any later version.
  
  This package is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this package; if not, write to the Free Software
  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include "sim.h"
#include "unit.h"
#include "random.h"

#ifndef __WIN32__
#include <sys/types.h>
#include <sys/wait.h>
#endif

//===================================================================
// симулирует один раунд
//===================================================================
static void simulate_attack(int acount, unit * attacker,
                    int dcount, unit * defender)
{
  int i, dkilled, next;

  // выбираем по очереди атакующую единицу
  // если вдруг все убиты то прекращаем симуляцию
  for (i=dkilled=0; i<acount && dkilled<dcount; i++)
  {
    for (next=1; next && dkilled<dcount; )
    {
      int didx=random()%dcount;
      if (!defender[didx].armory) dkilled--;
      next=attack_unit(&attacker[i], &defender[didx]);
      if (!defender[didx].armory) dkilled++;
    } 
  }
}
//===================================================================
// нормализует массив юнитов, выкидывая из них убитые
// и восстанавливая им щиты
// возвращает число живых юнитов
//===================================================================
static int normalize_result(int count, unit * fleet)
{
  int won;  // число живых (победивших юнитов)
  int i;

  for (won=i=0; i<count; i++)
  {
    // восстанавливаем щиты
    fleet[i].shield=fleet[i].max.shield;

    // если живой 
    if (fleet[i].armory)
    {
      // в начале никуда не двигаем живых
      if (won==i) { won++; continue; }
      fleet[won]=fleet[i];
      won++;
    }
  }
  return won;
}

//===================================================================
// заполняет поле who_win в результатах
//===================================================================
static void set_who_win(sim_result * sim)
{
  sim->who_win=NO_WIN;
  // ничья
  if (sim->attacker.count && sim->defender.count) return;

  // ничья со смертью обоих
  if (sim->attacker.count==0 && sim->defender.count==0) return;

  if (sim->attacker.count) sim->who_win=ATTACKER_WIN;
  else sim->who_win=DEFENDER_WIN;
}

//===================================================================
// вычисляет потери
// атакующего и защитника
//===================================================================
static void set_debris(sim_result *after_attack, 
    const sim_info *before_attack)
{
  int i, delta_units;
  debris_resources *attacker, *defender;
  long long ametal, acrystal, adeut, dmetal, dcrystal, ddeut;
  
  memset(&after_attack->debris, 0, sizeof(debris));
  
  for (i=0; i<UNITS_COUNT; i++)
  {
    if (units[i].flags.ground)
    {
      attacker=&after_attack->debris.attacker.ground;
      defender=&after_attack->debris.defender.ground;
    }
    else
    {
      attacker=&after_attack->debris.attacker.fleet;
      defender=&after_attack->debris.defender.fleet;
    }

    delta_units=
      before_attack->attacker.unit[i]-after_attack->attacker.unit[i];
    ametal=((long long)units[i].metal)*delta_units;
    acrystal=((long long)units[i].crystal)*delta_units;
    adeut=((long long)units[i].deut)*delta_units;
    
    delta_units=
      before_attack->defender.unit[i]-after_attack->defender.unit[i];
    dmetal=((long long)units[i].metal)*delta_units;
    dcrystal=((long long)units[i].crystal)*delta_units;
    ddeut=((long long)units[i].deut)*delta_units;

    // потери атакующего
    attacker->metal+=ametal;
    attacker->crystal+=acrystal;
    attacker->deut+=adeut;
    attacker->structure+=ametal+acrystal;
    attacker->total+=ametal+acrystal+adeut;
    
    after_attack->debris.attacker.all.metal+=ametal;
    after_attack->debris.attacker.all.crystal+=acrystal;
    after_attack->debris.attacker.all.deut+=adeut;
    after_attack->debris.attacker.all.structure+=ametal+acrystal;
    after_attack->debris.attacker.all.total+=ametal+acrystal+adeut;
    
    // потери защитника
    defender->metal+=dmetal;
    defender->crystal+=dcrystal;
    defender->deut+=ddeut;
    defender->structure+=dmetal+dcrystal;
    defender->total+=dmetal+dcrystal+ddeut;

    after_attack->debris.defender.all.metal+=dmetal;
    after_attack->debris.defender.all.crystal+=dcrystal;
    after_attack->debris.defender.all.deut+=ddeut;
    after_attack->debris.defender.all.structure+=dmetal+dcrystal;
    after_attack->debris.defender.all.total+=dmetal+dcrystal+ddeut;
  }
  
  // суммарные потери
  after_attack->debris.both=after_attack->debris.attacker;
  for (i=0; i<sizeof(debris_info)/sizeof(long long); i++)
  {
    ((long long *)&after_attack->debris.both)[i]+=
      ((long long *)&after_attack->debris.defender)[i];
  }
 
  // количество переработчиков
  long long all_debris=
    NORMALIZE_DEBRIS(after_attack->debris.both.all.structure*30/100);
  long long fleet_debris=
    NORMALIZE_DEBRIS(after_attack->debris.both.fleet.structure*30/100);

  if (units[RECYCLER].capacity)
  {
    if (all_debris)
    {
      after_attack->debris.recyclers.all=
        all_debris/units[RECYCLER].capacity+1;
      after_attack->debris.moon.all=all_debris/100000;
      if (after_attack->debris.moon.all>20) 
        after_attack->debris.moon.all=20;
    }

    if (fleet_debris)
    {
      after_attack->debris.recyclers.fleet=
        fleet_debris/units[RECYCLER].capacity+1;
      after_attack->debris.moon.fleet=fleet_debris/100000;
      if (after_attack->debris.moon.fleet>20)
        after_attack->debris.moon.fleet=20;
    }
  }
}

//===================================================================
// возвращает количество взятых ресурсов при текущем capacity
//===================================================================
static resource get_resources_from_planet(long long capacity, 
    resource planet)
{
  resource get={0, 0, 0};
  int get_res;
  
  planet.metal/=2;
  planet.crystal/=2;
  planet.deut/=2;

  if (planet.metal<capacity/3) { get_res=planet.metal; planet.metal=0; }
  else { get_res=capacity/3; planet.metal-=get_res; }
  capacity-=get_res; get.metal+=get_res;
  
  if (planet.crystal<capacity/2) 
  { 
    get_res=planet.crystal; planet.crystal=0; 
  }
  else { get_res=capacity/2; planet.crystal-=get_res; }
  capacity-=get_res; get.crystal+=get_res;

  if (planet.deut<capacity) { get_res=planet.deut; planet.deut=0; }
  else { get_res=capacity; planet.deut-=get_res; }
  capacity-=get_res;  get.deut+=get_res;

  if (planet.metal<capacity/2) 
  { 
    capacity-=planet.metal; get.metal+=planet.metal; 
  }
  else { get.metal+=capacity/2; capacity/=2; }

  if (planet.crystal<capacity) get.crystal+=planet.crystal;
  else get.crystal+=capacity;
 
  return get;
}

//===================================================================
// возвращает необходимое вместимость флота в зависимости от
// того сколько ресурсов на планете
//===================================================================
static int get_max_capacity(resource planet)
{
  // http://student.kuleuven.be/~m9815303/pvt/cargocap.png
  int cap1 = planet.metal+planet.crystal+planet.deut;
  int cap2 = 3*(cap1 + planet.metal)/4;
  int cap3 = 2 * planet.metal + planet.deut;

  int cap23=cap2; 
  if (cap2>cap3) cap23=cap3;

  int capacity=cap1;
  if (cap1<cap23) capacity=cap23;

  return capacity/2;
}
//===================================================================
// вычисляет сколько транспортов надо чтобы увезти все ресурсы
//===================================================================
static void set_chargo(sim_result *after_attack, 
    const sim_info *before_attack)
{
  int i;
  // считаем вместимости флотов
  after_attack->attacker.capacity=0;
  after_attack->defender.capacity=0;
  for (i=0; i<UNITS_COUNT; i++)
  {
    after_attack->attacker.capacity+=
      ((long long)units[i].capacity)*after_attack->attacker.unit[i];
    after_attack->defender.capacity+=
      ((long long)units[i].capacity)*after_attack->defender.unit[i];
  }
  
  // расчитываем сколько атакер взял ресов с планеты
  after_attack->attacker_plunder=get_resources_from_planet(
      after_attack->attacker.capacity, 
      before_attack->defender_resource);


  int req_capacity=get_max_capacity(before_attack->defender_resource);
  if (after_attack->attacker.capacity<req_capacity)
  {
    int delta_capacity=req_capacity-after_attack->attacker.capacity;
    after_attack->chargo.small=
      delta_capacity/units[SMALL_CHARGO].capacity;
    after_attack->chargo.large=
      delta_capacity/units[LARGE_CHARGO].capacity;
    after_attack->chargo.small++; after_attack->chargo.large++;
  }
}
//===================================================================
// симулирует 6 раундов, возвращает структуру sim_result
// с результатами симуляции
//===================================================================
static sim_result * simulate6rounds(const sim_info * sim)
{
  int acount, dcount, i, j, round;
  for (acount=dcount=i=0; i<UNITS_COUNT; i++)
  {
    acount+=sim->attacker.unit[i];
    dcount+=sim->defender.unit[i];
  }
  
  sim_result *result=calloc(sizeof(sim_result), 1);

  // если атакующего или дефендера нет, то
  // возвращаем структуру задания 
  // с одним раундом
  if ((!acount)||(!dcount))
  {
    result->rounds=1;
    memcpy(result->attacker.unit, 
        sim->attacker.unit, sizeof(int)*UNITS_COUNT);
    memcpy(result->defender.unit, 
        sim->defender.unit, sizeof(int)*UNITS_COUNT);
    result->attacker.count=acount;
    result->defender.count=dcount;
    set_who_win(result);
    set_chargo(result, sim);
    return result;
  }

  // массивы атакующего и обороняющегося
  unit * attacker=(unit *)calloc(sizeof(unit), acount);
  unit * defender=(unit *)calloc(sizeof(unit), dcount);
 
  // заполняем массивы юнитами
  for (acount=dcount=i=0; i<UNITS_COUNT; i++)
  {
    // если юниты такого типа есть у атакующего
    // создаем юнит и дописываем его в массив нужное кол-во
    // раз
    if (sim->attacker.unit[i])
    {
      unit u=create_unit(i, sim->attacker.attack, 
          sim->attacker.shield, sim->attacker.armory);
      
      for (j=0; j<sim->attacker.unit[i]; j++)
      {
        attacker[acount++]=u;
      }
    }
    if (sim->defender.unit[i])
    {
      unit u=create_unit(i, sim->defender.attack, 
          sim->defender.shield, sim->defender.armory);
      
      for (j=0; j<sim->defender.unit[i]; j++)
      {
        defender[dcount++]=u;
      }
    }
  }

  // 6 раундов
  for (round=1; round<=MAX_ROUNDS; round++)
  {
    simulate_attack(acount, attacker, dcount, defender);
    simulate_attack(dcount, defender, acount, attacker);

    acount=normalize_result(acount, attacker);
    dcount=normalize_result(dcount, defender);

    if (!(acount && dcount)) break;
  }

  if (round>MAX_ROUNDS) round=MAX_ROUNDS;
  result->rounds=round;

  // составляем результирующую таблицу какого флота сколько
  for (i=0; i<acount; i++) 
  {
    if (attacker[i].armory>0) 
    {
      result->attacker.unit[attacker[i].id]++;
    }
    result->attacker.count++;
  }
  for (i=0; i<dcount; i++) 
  {
    if (defender[i].armory>0) 
    {
      result->defender.unit[defender[i].id]++;
    }
    result->defender.count++;
  }
  
  free(attacker);
  free(defender);
  
  set_who_win(result);
  set_debris(result, sim);
  set_chargo(result, sim);

  return result;
}

//===================================================================
// расчитывает средние потери по массиву потерь
//===================================================================
static debris * get_average_debris(const psim_result * sims, 
    int sim_count)
{
  debris * result=calloc(sizeof(debris), 1);
  int i, j;

  // сумма всех повреждений
  for (i=0; i<sim_count; i++)
  {
    long long *beg_l=sims[i]->debris.ldata_begin;

    for (j=0; beg_l<sims[i]->debris.ldata_end; j++, beg_l++)
      result->ldata_begin[j]+=*beg_l;

    int * beg_i=sims[i]->debris.idata_begin;
    for (j=0; beg_i<sims[i]->debris.idata_end; j++, beg_i++)
      result->idata_begin[j]+=*beg_i;
  }

  // усредняем
  long long *beg_l = result->ldata_begin;
  int * beg_i=result->idata_begin;

  for (j=0; beg_l<result->ldata_end; j++, beg_l++)
    result->ldata_begin[j]/=sim_count;

  for (j=0; beg_i<result->idata_end; j++, beg_i++)
    result->idata_begin[j]/=sim_count;

  return result;
}
//===================================================================
// делает несколько симуляций
// возвращает массив sim_result
//===================================================================
static psim_result * simulations(const sim_info * sim, 
    int count)
{
  time_t begin_time; time(&begin_time);
  if (!count) return 0;
  int i;
  psim_result * result=
    (psim_result *)calloc(sizeof(psim_result), count);
  for (i=0; i<count; i++)
  {
    result[i]=simulate6rounds(sim);
    if (!sim->work_time) continue;

    time_t current_time;
    time(&current_time);
    if(current_time-begin_time>sim->work_time) break;
  }
  return result;
}

//===================================================================
// усредняет все сделанные симуляции
// находит лучший/худший бои аттакера/дефендера итп
//===================================================================
sims_result * get_common_results(const psim_result * results,
    const sim_info * task,
    int sim_count)
{
  sims_result *sr=calloc(sizeof(sims_result), 1);
  
  int  i, j, rounds, max_attacker, min_attacker, 
       max_defender, min_defender, max_debris, min_debris;
  long long metal, crystal, deut, small_chargo, large_chargo;
  
  max_attacker=min_attacker=max_defender=min_defender=
    max_debris=min_debris=0;
  metal=crystal=deut=0;
  small_chargo=large_chargo=0;
  rounds=0;
  
  sr->task=*task;
  sr->rounds.min=sr->rounds.max=results[0]->rounds;
  sr->sim_count=sim_count;
  
  // сумма всех симуляций
  for (i=0; i<sim_count; i++)
  {
    // флоты
    for (j=0; j<UNITS_COUNT; j++)
    {
      sr->attacker.unit[j]+=results[i]->attacker.unit[j];
      sr->defender.unit[j]+=results[i]->defender.unit[j];
    }

    // потери/обломки
    long long *beg_l=results[i]->debris.ldata_begin;
    for (j=0; beg_l<results[i]->debris.ldata_end; j++, beg_l++)
      sr->debris.ldata_begin[j]+=*beg_l;

    int * beg_i=results[i]->debris.idata_begin;
    for (j=0; beg_i<results[i]->debris.idata_end; j++, beg_i++)
      sr->debris.idata_begin[j]+=*beg_i;
    
    // минимальные/максимальные потери
    // атакующего/защитника и вообще
    if (results[max_attacker]->debris.attacker.all.total<
        results[i]->debris.attacker.all.total) max_attacker=i;
    if (results[min_attacker]->debris.attacker.all.total>
        results[i]->debris.attacker.all.total) min_attacker=i;
    
    if (results[max_defender]->debris.defender.all.total<
        results[i]->debris.defender.all.total) max_defender=i;
    if (results[min_defender]->debris.defender.all.total>
        results[i]->debris.defender.all.total) min_defender=i;
      
    if (results[max_debris]->debris.both.all.total<
          results[i]->debris.both.all.total) max_debris=i;
    if (results[min_debris]->debris.both.all.total>
          results[i]->debris.both.all.total) min_debris=i;

    // количество раундов
    rounds+=results[i]->rounds;
    if (sr->rounds.min>results[i]->rounds) 
      sr->rounds.min=results[i]->rounds;
    if (sr->rounds.max<results[i]->rounds) 
      sr->rounds.max=results[i]->rounds;

    // количество побед
    if (results[i]->who_win==ATTACKER_WIN) sr->wins.attacker++;
    if (results[i]->who_win==DEFENDER_WIN) sr->wins.defender++;

    // вместимость
    sr->attacker.capacity+=results[i]->attacker.capacity;
    sr->defender.capacity+=results[i]->defender.capacity;

    metal+=results[i]->attacker_plunder.metal;
    crystal+=results[i]->attacker_plunder.crystal;
    deut+=results[i]->attacker_plunder.deut;

    // транспорты
    small_chargo+=results[i]->chargo.small;
    large_chargo+=results[i]->chargo.large;
  }
  i--; //anilatx:important BUGFIX - after the loop it's one too much!

  // усредняем результаты
  // флоты
  for (j=0; j<UNITS_COUNT; j++)
  {
    sr->attacker.unit[j]/=sim_count;
    sr->defender.unit[j]/=sim_count;
  }

  // обломки
  long long *beg_l=results[i]->debris.ldata_begin;
  for (j=0; beg_l<results[i]->debris.ldata_end; j++, beg_l++)
    sr->debris.ldata_begin[j]/=sim_count;

  int * beg_i=results[i]->debris.idata_begin;
  for (j=0; beg_i<results[i]->debris.idata_end; j++, beg_i++)
    sr->debris.idata_begin[j]/=sim_count;

  // раунды
  sr->rounds.average=((double)rounds)/sim_count;

  sr->best_attacker=*results[min_attacker];
  sr->worst_attacker=*results[max_attacker];
  sr->best_defender=*results[min_defender];
  sr->worst_defender=*results[max_defender];
  sr->max_debris=*results[max_debris];
  sr->min_debris=*results[min_debris];

  // вместимость
  sr->attacker.capacity/=sim_count;
  sr->defender.capacity/=sim_count;

  // забранные ресурсы
  sr->attacker_plunder.metal=metal/sim_count;
  sr->attacker_plunder.crystal=crystal/sim_count;
  sr->attacker_plunder.deut=deut/sim_count;

  // количество транспортов
  sr->chargo.small=small_chargo/sim_count;
  sr->chargo.large=large_chargo/sim_count;
  return sr;
}

//===================================================================
// собственно симулятор
// Windows-версия
//===================================================================
#ifdef __WIN32__
sims_result * simulator(const sim_info * sim)
{
  int i;

  int sim_count=sim->simulations;
  // если кого-то нет, то делаем только одну симуляцию
  // ибо незачем симулировать пустой бой
  if (!sim->attacker.count || !sim->defender.count) sim_count=1; 
  
  // считаем
  psim_result * results=simulations(sim, sim_count);
  
  // считаем сколько вышло симуляций
  for (i=0; i<sim_count && results[i]; i++); sim_count=i;

  // усредняем результаты
  sims_result * common_results=
    get_common_results(results, sim, sim_count);
  
  // освобождаем память
  for (i=0; i<sim_count; i++) free(results[i]); free(results); 
  
  return common_results;
}

//===================================================================
// оптимизированная симуляция в несколько потоков
// LINUX - версия
//===================================================================
#else
sims_result * simulator(const sim_info *sim)
{
  int forks_count=sim->forks_count;
  int sim_count=sim->simulations;
  int i, j, acount, dcount, status;
  
  acount=sim->attacker.count;
  dcount=sim->defender.count;
  
  // если кого-то нет, то делаем только одну симуляцию
  // ибо незачем симулировать пустой бой
  if (!acount || !dcount) { forks_count=0; sim_count=1; }

  if (sim_count<forks_count) forks_count=sim_count-1;

  int sim_per_fork=sim_count/(forks_count+1);
  int sim_per_main=sim_count-sim_per_fork*(forks_count+1);
  sim_per_main+=sim_per_fork;

  // структура для управления симуляциями
  typedef struct
  {
    pid_t child;
    union
    {
      int pipe[2];
      struct { int reader, writer; };
    };
  } sim_process;

  sim_process *sims=0;
  if (forks_count) sims=(sim_process *)
                      calloc(sizeof(sim_process), forks_count);

  // создаем пайпы для всех дочек
  for (i=0; i<forks_count; i++)
  {
    if (pipe(sims[i].pipe)!=0)
    {
      fprintf(stderr, "Error create pipe: %s\n", strerror(errno));
      exit(-1);
    }
  }

  fflush(stdout); fflush(stderr);
  // запускаем процессы симуляции
  for (i=0; i<forks_count; i++)
  {
    srandom(random());    // каждой дочке свой srand
    sims[i].child=fork();

    // мы в дочке
    if (sims[i].child==0)
    {
      close(sims[i].reader);
      psim_result * psr=simulations(sim, sim_per_fork);

      // считаем сколько симуляций сделано
      for (j=0; j<sim_per_fork && psr[j]; j++);
      
      write(sims[i].writer, &j, sizeof(int));
      for (j--; j>=0; j--)
      {
        write(sims[i].writer, psr[j], sizeof(sim_result));
        free(psr[j]);
      }
      free(psr);
      
      exit(0);
    }

    // мы в родителе
    close(sims[i].writer);

    // ошибка fork
    if (sims[i].child==-1)
    {
      fprintf(stderr, "Error fork: %s\n", strerror(errno));
      // прибиваем всех дочек, которых успели создать 
      for (i--; i>=0; i--)
      {
        kill(sims[i].child, 15);
        waitpid(sims[i].child, &status, WUNTRACED);
      }
      exit(-1);
    }
  }

  // выделяем место под указатели на резултаты
  psim_result * results=(psim_result *)
      calloc(sizeof(psim_result), sim_count);
  
  
  psim_result * ms=simulations(sim, sim_per_main);
  // основной родитель тоже симулирует (последним потоком)
  for (i=0; i<sim_per_main; i++)
  {
    if (!ms[i]) break;
    results[i]=ms[i];
  }
  free(ms);

  // i - указывает на первую незаполненную результатом структуру

  // читаем результаты что там дочки насчитали
  for (j=0; j<forks_count; j++)
  {
    int count=0;

    // читаем количество сделанных дочкой симуляций
    if ((read(sims[j].reader, &count, sizeof(int))!=sizeof(int))||
          count>sim_per_fork || count<0)
    {
      count=0;
    }

    // читаем результаты симуляций
    for (;count; count--)
    {
      sim_result *sr=(sim_result *)malloc(sizeof(sim_result));
      if (read(sims[j].reader, sr, sizeof(sim_result))!=
          sizeof(sim_result))
      {
        // ошибка
        free(sr);
        break;
      }
      results[i++]=sr;
    }
  }

  // завершаем корректно все процессы
  for (j=0; j<forks_count; j++)
  {
    close(sims[j].reader);
    kill(sims[j].child, 15);
    waitpid(sims[j].child, &status, WUNTRACED);
  }

  
  sim_count=i;

  sims_result * common_results=
    get_common_results(results, sim, sim_count);
  
  
  for (i=0; i<sim_count; i++) free(results[i]);
  free(results); free(sims); 
  
  return common_results;
}

#endif
//===================================================================
