/*
 * This file is part of the planetblupi source code
 * Copyright (C) 1997, Daniel Roux & EPSITEC SA
 * Copyright (C) 2017, Mathieu Schroeter
 * http://epsitec.ch; http://www.blupi.org; http://github.com/blupi-games
 *
 * This program 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.
 *
 * This program 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 program. If not, see http://gnu.org/licenses
 */

#include "decor.h"
#include "def.h"
#include "misc.h"

typedef struct {
  Sint16 majRev;
  Sint16 minRev;
  Sint32 nbDecor;
  Sint32 lgDecor;
  Sint32 nbBlupi;
  Sint32 lgBlupi;
  Sint32 nbMove;
  Sint32 lgMove;
  Sint16 reserve1[100];
  Point  celCoin;
  Sint16 world;
  Sint32 time;
  char   buttonExist[MAXBUTTON];
  Term   term;
  Sint16 music;
  Sint16 region;
  Sint32 totalTime;
  Sint16 skill;
  Point  memoPos[4];
  Sint16 reserve2[29];
} DescFile;

typedef struct {
  Sint32 bExist; // true -> utilisé
  Sint32 bHili;  // true -> sélectionné

  Sint16 perso; // personnage, voir (*)

  Sint16 goalAction; // action (Sint32 terme)
  Sint16 goalPhase;  // phase (Sint32 terme)
  Point  goalCel;    // cellule visée (Sint32 terme)
  Point  passCel;    // cellule tranversante

  Sint16 energy; // énergie restante

  Point  cel;     // cellule actuelle
  Point  destCel; // cellule destination
  Sint16 action;  // action en cours
  Sint16 aDirect; // direction actuelle
  Sint16 sDirect; // direction souhaitée

  Point  pos;  // position relative à partir de la cellule
  Sint16 posZ; // déplacement z
  Sint16 channel;
  Sint16 lastIcon;
  Sint16 icon;
  Sint16 phase;     // phase dans l'action
  Sint16 step;      // pas global
  Sint16 interrupt; // 0=prioritaire, 1=normal, 2=misc
  Sint16 clipLeft;

  Sint32 nbUsed; // nb de points déjà visités
  char   nextRankUsed;
  Point  posUsed[MAXUSED];
  char   rankUsed[MAXUSED];

  Sint16 takeChannel; // objet transporté
  Sint16 takeIcon;

  Point fix; // point fixe (cultive, pont)

  Sint16 jaugePhase;
  Sint16 jaugeMax;
  Sint16 stop;     // 1 -> devra stopper
  Sint16 bArrow;   // true -> flèche en dessus de blupi
  Sint16 bRepeat;  // true -> répète l'action
  Sint16 nLoop;    // nb de boucles pour GOAL_OTHERLOOP
  Sint16 cLoop;    // boucle en cours
  Sint16 vIcon;    // icône variable
  Point  goalHili; // but visé
  Sint16 bMalade;  // true -> blupi malade
  Sint16 bCache;   // true -> caché (pas dessiné)
  Sint16 vehicule; // véhicule utilisé par blupi, voir (**)
  char   busyCount;
  char   busyDelay;
  char   clicCount;
  char   clicDelay;
  char   reserve2[2];
} OldBlupi;

// Sauve le décor sur disque.

bool
CDecor::Write (Sint32 rank, bool bUser, Sint32 world, Sint32 time, Sint32 total)
{
  std::string filename;
  FILE *      file    = nullptr;
  DescFile *  pBuffer = nullptr;
  Sint32      i;
  size_t      nb;

  if (bUser)
  {
    filename = string_format ("data/user%.3d.blp", rank);
    AddUserPath (filename);
  }
  else
  {
    filename = string_format ("data/world%.3d.blp", rank);
    AddUserPath (filename);
  }

  file = fopen (filename.c_str (), "wb");
  if (file == nullptr)
    goto error;

  pBuffer = (DescFile *) malloc (sizeof (DescFile));
  if (pBuffer == nullptr)
    goto error;
  memset (pBuffer, 0, sizeof (DescFile));

  pBuffer->majRev    = 1;
  pBuffer->minRev    = 5;
  pBuffer->celCoin   = m_celCorner;
  pBuffer->world     = world;
  pBuffer->time      = time;
  pBuffer->totalTime = total;
  pBuffer->term      = m_term;
  pBuffer->music     = m_music;
  pBuffer->region    = m_region;
  pBuffer->skill     = m_skill;
  pBuffer->nbDecor   = MAXCELX * MAXCELY;
  pBuffer->lgDecor   = sizeof (Cellule);
  pBuffer->nbBlupi   = MAXBLUPI;
  pBuffer->lgBlupi   = sizeof (Blupi);
  pBuffer->nbMove    = MAXMOVE;
  pBuffer->lgMove    = sizeof (Move);

  for (i = 0; i < MAXBUTTON; i++)
    pBuffer->buttonExist[i] = m_buttonExist[i];

  for (i = 0; i < 4; i++)
    pBuffer->memoPos[i] = m_memoPos[i];

  nb = fwrite (pBuffer, sizeof (DescFile), 1, file);
  if (nb < 1)
    goto error;

  nb = fwrite (m_decor, sizeof (Cellule), MAXCELX * MAXCELY / 4, file);
  if (nb < MAXCELX * MAXCELY / 4)
    goto error;

  nb = fwrite (m_blupi, sizeof (Blupi), MAXBLUPI, file);
  if (nb < MAXBLUPI)
    goto error;

  nb = fwrite (m_move, sizeof (Move), MAXMOVE, file);
  if (nb < MAXMOVE)
    goto error;

  nb = fwrite (m_lastDrapeau, sizeof (Point), MAXLASTDRAPEAU, file);
  if (nb < MAXLASTDRAPEAU)
    goto error;

  free (pBuffer);
  fclose (file);
  return true;

error:
  if (pBuffer != nullptr)
    free (pBuffer);
  if (file != nullptr)
    fclose (file);
  return false;
}

// Lit le décor sur disque.

bool
CDecor::Read (
  Sint32 rank, bool bUser, Sint32 & world, Sint32 & time, Sint32 & total)
{
  std::string filename;
  FILE *      file    = nullptr;
  DescFile *  pBuffer = nullptr;
  Sint32      majRev, minRev;
  Sint32      i, x, y;
  size_t      nb;
  OldBlupi    oldBlupi;

  Init (-1, -1);

  if (bUser)
  {
    filename = string_format ("data/user%.3d.blp", rank);
    AddUserPath (filename);
  }
  else if (rank >= 200)
  {
    filename = string_format ("data/world%.3d.blp", rank);
    AddUserPath (filename);
  }
  else
    filename = string_format (GetBaseDir () + "data/world%.3d.blp", rank);

  file = fopen (filename.c_str (), "rb");
  if (file == nullptr)
    goto error;

  pBuffer = (DescFile *) malloc (sizeof (DescFile));
  if (pBuffer == nullptr)
    goto error;

  nb = fread (pBuffer, sizeof (DescFile), 1, file);
  if (nb < 1)
    goto error;

  majRev = pBuffer->majRev;
  minRev = pBuffer->minRev;

  if (majRev == 1 && minRev == 0)
    goto error;

  if (majRev == 1 && minRev == 3)
  {
    if (
      pBuffer->nbDecor != MAXCELX * MAXCELY ||
      pBuffer->lgDecor != sizeof (Cellule) || pBuffer->nbBlupi != MAXBLUPI ||
      pBuffer->lgBlupi != sizeof (OldBlupi) || pBuffer->nbMove != MAXMOVE ||
      pBuffer->lgMove != sizeof (Move))
      goto error;
  }
  else
  {
    if (
      pBuffer->nbDecor != MAXCELX * MAXCELY ||
      pBuffer->lgDecor != sizeof (Cellule) || pBuffer->nbBlupi != MAXBLUPI ||
      pBuffer->lgBlupi != sizeof (Blupi) || pBuffer->nbMove != MAXMOVE ||
      pBuffer->lgMove != sizeof (Move))
      goto error;
  }

  SetCorner (pBuffer->celCoin);
  if (bUser)
  {
    world = pBuffer->world;
    time  = pBuffer->time;
    total = pBuffer->totalTime;
  }
  m_celHome = pBuffer->celCoin;
  m_term    = pBuffer->term;
  m_music   = pBuffer->music;
  m_region  = pBuffer->region;

  if (bUser)
    m_skill = pBuffer->skill;

  for (i = 0; i < MAXBUTTON; i++)
    m_buttonExist[i] = pBuffer->buttonExist[i];

  for (i = 0; i < 4; i++)
    m_memoPos[i] = pBuffer->memoPos[i];

  nb = fread (m_decor, sizeof (Cellule), MAXCELX * MAXCELY / 4, file);
  if (nb < MAXCELX * MAXCELY / 4)
    goto error;
  if (majRev == 1 && minRev < 5)
  {
    for (x = 0; x < MAXCELX / 2; x++)
    {
      for (y = 0; y < MAXCELY / 2; y++)
      {
        if (m_decor[x][y].objectIcon >= 128 && m_decor[x][y].objectIcon <= 130)
          m_decor[x][y].objectIcon -= 128 - 17;
      }
    }
  }

  /* Restore the flagged state where flagged ground */
  for (size_t i = 0; i < countof (m_decor); ++i)
    for (size_t j = 0; j < countof (m_decor[i]); ++j)
    {
      if (m_decor[i][j].objectIcon == 124)
        m_decorMem[i][j].flagged = true;
    }

  if (majRev == 1 && minRev == 3)
  {
    memset (m_blupi, 0, sizeof (Blupi) * MAXBLUPI);
    for (i = 0; i < MAXBLUPI; i++)
    {
      nb = fread (&oldBlupi, sizeof (OldBlupi), 1, file);
      if (nb != 1)
        goto error;
      memcpy (m_blupi + i, &oldBlupi, sizeof (OldBlupi));
      ListFlush (i);
    }
  }
  else
  {
    nb = fread (m_blupi, sizeof (Blupi), MAXBLUPI, file);
    if (nb < MAXBLUPI)
      goto error;
  }

  nb = fread (m_move, sizeof (Move), MAXMOVE, file);
  if (nb < MAXMOVE)
    goto error;

  nb = fread (m_lastDrapeau, sizeof (Point), MAXLASTDRAPEAU, file);
  if (nb < MAXLASTDRAPEAU)
    InitDrapeau ();

  BlupiDeselect (); // désélectionne tous les blupi

  free (pBuffer);
  fclose (file);
  return true;

error:
  if (pBuffer != nullptr)
    free (pBuffer);
  if (file != nullptr)
    fclose (file);

  Flush (); // initialise un décor neutre
  return false;
}

// Indique si un fichier existe sur disque.

bool
CDecor::FileExist (
  Sint32 rank, bool bUser, Sint32 & world, Sint32 & time, Sint32 & total)
{
  std::string filename;
  FILE *      file    = nullptr;
  DescFile *  pBuffer = nullptr;
  Sint32      majRev, minRev;
  size_t      nb;

  if (bUser)
  {
    filename = string_format ("data/user%.3d.blp", rank);
    AddUserPath (filename);
  }
  else if (rank >= 200)
  {
    filename = string_format ("data/world%.3d.blp", rank);
    AddUserPath (filename);
  }
  else
    filename = string_format (GetBaseDir () + "data/world%.3d.blp", rank);

  file = fopen (filename.c_str (), "rb");
  if (file == nullptr)
    goto error;

  pBuffer = (DescFile *) malloc (sizeof (DescFile));
  if (pBuffer == nullptr)
    goto error;

  nb = fread (pBuffer, sizeof (DescFile), 1, file);
  if (nb < 1)
    goto error;

  majRev = pBuffer->majRev;
  minRev = pBuffer->minRev;

  if (majRev == 1 && minRev == 0)
    goto error;

  if (majRev == 1 && minRev == 3)
  {
    if (
      pBuffer->nbDecor != MAXCELX * MAXCELY ||
      pBuffer->lgDecor != sizeof (Cellule) || pBuffer->nbBlupi != MAXBLUPI ||
      pBuffer->lgBlupi != sizeof (OldBlupi) || pBuffer->nbMove != MAXMOVE ||
      pBuffer->lgMove != sizeof (Move))
      goto error;
  }
  else
  {
    if (
      pBuffer->nbDecor != MAXCELX * MAXCELY ||
      pBuffer->lgDecor != sizeof (Cellule) || pBuffer->nbBlupi != MAXBLUPI ||
      pBuffer->lgBlupi != sizeof (Blupi) || pBuffer->nbMove != MAXMOVE ||
      pBuffer->lgMove != sizeof (Move))
      goto error;
  }

  world = pBuffer->world;
  time  = pBuffer->time;
  total = pBuffer->totalTime;

  free (pBuffer);
  fclose (file);
  return true;

error:
  if (pBuffer != nullptr)
    free (pBuffer);
  if (file != nullptr)
    fclose (file);
  return false;
}

#define MARG 18

// Initialise un décor neutre.

void
CDecor::Flush ()
{
  Sint32 x, y, i, icon;

  Init (-1, -1);

  for (x = 0; x < MAXCELX; x += 2)
  {
    for (y = 0; y < MAXCELY; y += 2)
    {
      if (x < MARG || x > MAXCELX - MARG || y < MARG || y > MAXCELY - MARG)
      {
        icon = 14; // eau
        goto put;
      }

      if (x == MARG && y == MARG)
      {
        icon = 12;
        goto put;
      }
      if (x == MAXCELX - MARG && y == MARG)
      {
        icon = 13;
        goto put;
      }
      if (x == MARG && y == MAXCELY - MARG)
      {
        icon = 11;
        goto put;
      }
      if (x == MAXCELX - MARG && y == MAXCELY - MARG)
      {
        icon = 10;
        goto put;
      }

      if (x == MARG)
      {
        icon = 4;
        goto put;
      }
      if (x == MAXCELX - MARG)
      {
        icon = 2;
        goto put;
      }
      if (y == MARG)
      {
        icon = 5;
        goto put;
      }
      if (y == MAXCELY - MARG)
      {
        icon = 3;
        goto put;
      }

      icon = 1; // terre

    put:
      m_decor[x / 2][y / 2].floorChannel = CHFLOOR;
      m_decor[x / 2][y / 2].floorIcon    = icon;
    }
  }

  for (i = 0; i < MAXBLUPI; i++)
    m_blupi[i].bExist = false;

  BlupiCreate (GetCel (102, 100), ACTION_STOP, DIRECT_S, 0, MAXENERGY);
  m_decor[98 / 2][100 / 2].floorChannel  = CHFLOOR;
  m_decor[98 / 2][100 / 2].floorIcon     = 16;
  m_decor[98 / 2][100 / 2].objectChannel = CHOBJECT;
  m_decor[98 / 2][100 / 2].objectIcon    = 113;

  for (i = 0; i < MAXBUTTON; i++)
    m_buttonExist[i] = 1;

  for (i = 0; i < 4; i++)
  {
    m_memoPos[i].x = 0;
    m_memoPos[i].y = 0;
  }

  memset (&m_term, 0, sizeof (Term));
  m_term.bHomeBlupi = true;
  m_term.nbMinBlupi = 1;
  m_term.nbMaxBlupi = 1;

  m_music  = 0;
  m_region = 0;

  m_celHome.x = 90;
  m_celHome.y = 98;
  SetCorner (m_celHome);
  InitAfterBuild ();
  LoadImages ();
}
