/*----------------------------------------------------------------------------*
 | This file is part of DEU (Doom Editing Utilities), created by the DEU team:|
 | Raphael Quinet, Brendon Wyber, Ted Vessenes and others.  See README.1ST or |
 | the "about" dialog box for full credits.                                   |
 |                                                                            |
 | DEU is an open project: if you think that you can contribute, please join  |
 | the DEU team.  You will be credited for any code (or ideas) included in    |
 | the next version of the program.                                           |
 |                                                                            |
 | If you want to make any modifications and re-distribute them on your own,  |
 | you must follow the conditions of the DEU license.  Read the file LICENSE  |
 | in this directory or README.1ST in the top directory.  If do not have a    |
 | copy of these files, you can request them from any member of the DEU team, |
 | or by mail: Raphael Quinet, Rue des Martyrs 9, B-4550 Nandrin (Belgium).   |
 |                                                                            |
 | This program comes with absolutely no warranty.  Use it at your own risks! |
 *----------------------------------------------------------------------------*

 M_CHECKS.C - Consitency checker

*/
/*!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  Change all dialogs in this file.  Make them consistent.

  When there is no automatic fix and no object to examine:
    - Continue (Enter) = continue the test
    - Abort (Esc) = abort the test

  When there is no automatic fix but an object to examine:
    - Continue (Enter) = continue the test
    - Examine (Esc) = examine the object (abort the test)

  When an automatic fix is possible:
    - Fix (Enter) = fix the problem and continue
    - Ignore (no key) = do not fix, but continue the test
    - Examine (Esc) = examine the object (abort the test)
  !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
*/

/* the includes */
#include "deu.h"
#include "d_misc.h"
#include "d_wads.h"
#include "d_config.h"
#include "d_main.h"
#include "g_gfx.h"
#include "g_mouse.h"
#include "w_things.h"
#include "w_lindef.h"
#include "w_levels.h"
#include "w_object.h"
#include "w_select.h"
#include "w_templa.h"
#include "i_dialog.h"
#include "i_menus.h"
#include "m_mapdrw.h" /* GoToObject */
#include "m_edit.h"
/* #include "m_object.h" */
#include "m_stats.h"
#include "m_checks.h"


/*
   Display number of objects, etc.
*/
/*! could be improved - example: details about Things (type, when, etc.) */
void Statistics(LevelPtr level, Int16 x0, Int16 y0)
{
  char  string[10][80];
  Int16 skill[5];

  ComputeDifficulty(level, skill);

  sprintf(string[0], "Number of Things:   %4d (%lu K)", level->num_things,
          ((unsigned long) level->num_things   * sizeof(struct Thing)  + 512L) / 1024L);
  sprintf(string[1], "Number of Vertices: %4d (%lu K)", level->num_vertexes,
          ((unsigned long) level->num_vertexes * sizeof(struct Vertex) + 512L) / 1024L);
  sprintf(string[2], "Number of LineDefs: %4d (%lu K)", level->num_linedefs,
          ((unsigned long) level->num_linedefs * sizeof(struct LineDef) + 512L) / 1024L);
  sprintf(string[3], "Number of SideDefs: %4d (%lu K)", level->num_sidedefs,
          ((unsigned long) level->num_sidedefs * sizeof(struct SideDef) + 512L) / 1024L);
  sprintf(string[4], "Number of Sectors:  %4d (%lu K)", level->num_sectors,
          ((unsigned long) level->num_sectors  * sizeof(struct Sector)  + 512L) / 1024L);
  sprintf(string[5], "Skill 1:  %4d", skill[0]);
  sprintf(string[6], "Skill 2:  %4d", skill[1]);
  sprintf(string[7], "Skill 3:  %4d", skill[2]);
  sprintf(string[8], "Skill 4:  %4d", skill[3]);
  sprintf(string[9], "Skill 5:  %4d", skill[4]);


  DrawDialogBox(x0, y0, 270, 190, NULL, 13,
                DBSC_TEXT, -0x0102,   5, '\0', "Statistics",  0, YELLOW,
                DBSC_TEXT,      10,  25, '\0', string[0],     0, BLACK,
                DBSC_TEXT,      10,  35, '\0', string[1],     0, BLACK,
                DBSC_TEXT,      10,  45, '\0', string[2],     0, BLACK,
                DBSC_TEXT,      10,  55, '\0', string[3],     0, BLACK,
                DBSC_TEXT,      10,  65, '\0', string[4],     0, BLACK,
                DBSC_TEXT, -0x0102,  90, '\0', "Wad Ratings", 0, YELLOW,
                DBSC_TEXT,      10, 105, '\0', string[5],     0, BLACK,
                DBSC_TEXT,      10, 115, '\0', string[6],     0, BLACK,
                DBSC_TEXT,      10, 125, '\0', string[7],     0, BLACK,
                DBSC_TEXT,      10, 135, '\0', string[8],     0, BLACK,
                DBSC_TEXT,      10, 145, '\0', string[9],     0, BLACK,
                DBSC_BUTTON, -0x0102, 165, 'D', BUTTON_OK, "Done", YELLOW,
                ACTIVE);
}


/*
   Display a message, then ask if the check should continue (prompt2 may be NULL).
*/
/*! change this function and use a dialog box with multiple choices instead */
static Bool CheckFailed(Int16 x0, Int16 y0, char *prompt1, char *prompt2, Bool fatal)
{
  int key;
  int maxlen;

  if (UseMouse)
    HideMousePointer();
  if (fatal == TRUE)
    maxlen = 44;
  else
    maxlen = 27;
  if (strlen(prompt1) > maxlen)
    maxlen = strlen(prompt1);
  if (prompt2 != NULL && strlen(prompt2) > maxlen)
    maxlen = strlen(prompt2);
  if (x0 < 0)
    x0 = (ScrMaxX - 22 - 8 * maxlen) / 2;
  if (y0 < 0)
    y0 = (ScrMaxY - (prompt2 ? 73 : 63)) / 2;
  DrawScreenBox3D(x0, y0, x0 + 22 + 8 * maxlen, y0 + (prompt2 ? 73 : 63));
  SetColor(RED);
  DrawScreenText(x0 + 10, y0 + 8, "Verification failed:");
  Beep();
  SetColor(WHITE);
  DrawScreenText(x0 + 10, y0 + 18, prompt1);
  LogMessage("\t%s\n", prompt1);
  if (prompt2 != NULL)
    {
      DrawScreenText(x0 + 10, y0 + 28, prompt2);
      LogMessage("\t%s\n", prompt2);
    }
  if (fatal == TRUE)
    {
      DrawScreenText(x0 + 10, y0 + (prompt2 ? 38 : 28), "DOOM will crash if you play with this level.");
      SetColor(YELLOW);
      DrawScreenText(x0 + 10, y0 + (prompt2 ? 58 : 48), "Press any key to see the object");
      LogMessage("\n");
    }
  else
    {
      SetColor(YELLOW);
      DrawScreenText(x0 + 10, y0 + (prompt2 ? 48 : 38), "Press Esc to see the object,");
      DrawScreenText(x0 + 10, y0 + (prompt2 ? 58 : 48), "or any other key to continue");
    }
  key = WaitForKey();
  if ((key & 0x00FF) != 0x001B)
    {
      DrawScreenBox3D(x0, y0, x0 + 22 + 8 * maxlen, y0 + (prompt2 ? 73 : 63));
      DrawScreenText(x0 + 10 + 4 * (maxlen - 26), y0 + 28, "Verifying other objects...");
    }
  if (UseMouse)
    ShowMousePointer();

  return ((key & 0x00FF) == 0x001B);
}



/*
   Check if all sectors are closed.
*/

Bool CheckSectors(LevelPtr level)
{
  Int16      s, n, sd;
  Int8 huge *ends;
  char       msg1[80], msg2[80];

  LogMessage("\nVerifying Sectors...\n");
  DisplayProgressMeter(-1, 50, 320, "Checking if all paths are closed...");
  ends = (Int8 huge *)GetFarMemory(level->num_vertexes * sizeof(char));
  for (s = 0; s < level->num_sectors; s++)
    {
      UpdateProgressMeter((float) s / (float) level->num_sectors);
      /* clear the "ends" array */
      for (n = 0; n < level->num_vertexes; n++)
        ends[n] = 0;
      /* for each SideDef bound to the Sector, store a "1" in the "ends" */
      /* array for its starting Vertex, and a "2" for its ending Vertex  */
      for (n = 0; n < level->num_linedefs; n++)
        {
          sd = level->linedefs[n].sidedef1;
          if (sd >= 0 && level->sidedefs[sd].sector == s)
            {
              ends[level->linedefs[n].start] |= 1;
              ends[level->linedefs[n].end] |= 2;
            }
          sd = level->linedefs[n].sidedef2;
          if (sd >= 0 && level->sidedefs[sd].sector == s)
            {
              ends[level->linedefs[n].end] |= 1;
              ends[level->linedefs[n].start] |= 2;
            }
        }
      /* every entry in the "ends" array should be "0" or "3" */
      for (n = 0; n < level->num_vertexes; n++)
        {
          if (ends[n] == 1)
            {
              sprintf(msg1, "Sector #%d is not closed!", s);
              sprintf(msg2, "There is no SideDef ending at Vertex #%d", n);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_VERTEXES, n);
                  return FALSE;
                }
            }
          if (ends[n] == 2)
            {
              sprintf(msg1, "Sector #%d is not closed!", s);
              sprintf(msg2, "There is no SideDef starting at Vertex #%d", n);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_VERTEXES, n);
                  return FALSE;
                }
            }
        }
    }
  FreeFarMemory(ends);

  /*
     Note from RQ:
       This is a very simple idea, but it works!  The first test (above)
       checks that all Sectors are closed.  But if a closed set of LineDefs
       is moved out of a Sector and has all its "external" SideDefs pointing
       to that Sector instead of the new one, then we need a second test.
       That's why I check if the SideDefs facing each other are bound to
       the same Sector.

     Other note from RQ:
       Nowadays, what makes the power of a good editor is its automatic tests.
       So, if you are writing another Doom editor, you will probably want
       to do the same kind of tests in your program.  Fine, but if you use
       these ideas, don't forget to credit DEU...  Just a reminder... :-)
   */

  DisplayProgressMeter(-1, 50, 320, "Checking opposite SideDefs...");
  /* now check if all SideDefs are facing a SideDef with the same Sector number */
  for (n = 0; n < level->num_linedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_linedefs);
      sd = level->linedefs[n].sidedef1;
      if (sd >= 0)
        {
          s = GetOppositeSector(level, n, TRUE);
          if (s < 0 || level->sidedefs[sd].sector != s)
            {
              if (s < 0)
                {
                  sprintf(msg1, "Sector #%d is not closed!", level->sidedefs[sd].sector);
                  sprintf(msg2, "Check LineDef #%d (first SideDef: #%d)", n, sd);
                }
              else
                {
                  sprintf(msg1, "Sectors #%d and #%d are not closed!", level->sidedefs[sd].sector, s);
                  sprintf(msg2, "Check LineDef #%d (first SideDef: #%d) and the one facing it", n, sd);
                }
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
            }
        }
      sd = level->linedefs[n].sidedef2;
      if (sd >= 0)
        {
          s = GetOppositeSector(level, n, FALSE);
          if (s < 0 || level->sidedefs[sd].sector != s)
            {
              if (s < 0)
                {
                  sprintf(msg1, "Sector #%d is not closed!", level->sidedefs[sd].sector);
                  sprintf(msg2, "Check LineDef #%d (second SideDef: #%d)", n, sd);
                }
              else
                {
                  sprintf(msg1, "Sectors #%d and #%d are not closed!", level->sidedefs[sd].sector, s);
                  sprintf(msg2, "Check LineDef #%d (second SideDef: #%d) and the one facing it", n, sd);
                }
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
            }
        }
    }
  return TRUE;
}



/*
   Check cross-references and delete unused objects.
*/

Bool CheckCrossReferences(LevelPtr level)
{
  char   msg[80];
  Int16  n, m;
  SelPtr cur;

  LogMessage("\nVerifying cross-references...\n");
  DisplayProgressMeter(-1, 50, 320, "Checking cross-references - 1/5 ...");
  for (n = 0; n < level->num_linedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_linedefs);
      /* check for missing first SideDefs */
      if (level->linedefs[n].sidedef1 < 0)
        {
          sprintf(msg, "ERROR: LineDef #%d has no first SideDef!", n);
          CheckFailed(-1, -1, msg, NULL, TRUE);
          GoToObject(level, OBJ_LINEDEFS, n);
          return FALSE;
        }
      /* check for SideDefs used twice in the same LineDef */
      if (level->linedefs[n].sidedef1 == level->linedefs[n].sidedef2)
        {
          sprintf(msg, "ERROR: LineDef #%d uses the same SideDef twice (#%d)", n, level->linedefs[n].sidedef1);
          CheckFailed(-1, -1, msg, NULL, TRUE);
          GoToObject(level, OBJ_LINEDEFS, n);
          return FALSE;
        }
      /* check for Vertices used twice in the same LineDef */
      if (level->linedefs[n].start == level->linedefs[n].end)
        {
          sprintf(msg, "ERROR: LineDef #%d uses the same Vertex twice (#%d)", n, level->linedefs[n].start);
          CheckFailed(-1, -1, msg, NULL, TRUE);
          GoToObject(level, OBJ_LINEDEFS, n);
          return FALSE;
        }
    }

  /* check if there aren't two LineDefs between the same Vertices */
  DisplayProgressMeter(-1, 50, 320, "Checking cross-references - 2/5 ...");
  cur = NULL;
  for (n = level->num_linedefs - 1; n >= 1; n--)
    {
      UpdateProgressMeter(1.0 - (float) n / (float) level->num_linedefs);
      for (m = n - 1; m >= 0; m--)
        if ((level->linedefs[n].start == level->linedefs[m].start && level->linedefs[n].end == level->linedefs[m].end)
            || (level->linedefs[n].start == level->linedefs[m].end && level->linedefs[n].end == level->linedefs[m].start))
          {
            SelectObject(&cur, n);
            break;
          }
    }
  if (cur && (Config.expert || Confirm(-1, -1, "There are multiple LineDefs between the same Vertices", "Do you want to delete the redundant LineDefs?")))
    DeleteObjects(level, OBJ_LINEDEFS, &cur);
  else
    ForgetSelection(&cur);

  /* check for invalid flags in the LineDefs */
  DisplayProgressMeter(-1, 50, 320, "Checking cross-references - 3/5 ...");
  for (n = 0; n < level->num_linedefs; n++)
    if ((level->linedefs[n].flags & 0x01) == 0 && level->linedefs[n].sidedef2 < 0)
      SelectObject(&cur, n);
  if (cur && (Config.expert || Confirm(-1, -1, "Some LineDefs have only one side but their Im bit is not set", "Do you want to set the 'Impassible' flag?")))
    {
      while (cur)
        {
          level->linedefs[cur->objnum].flags |= 0x01;
          UnSelectObject(&cur, cur->objnum);
        }
    }
  else
    ForgetSelection(&cur);

  for (n = 0; n < level->num_linedefs; n++)
    if ((level->linedefs[n].flags & 0x04) != 0 && level->linedefs[n].sidedef2 < 0)
      SelectObject(&cur, n);
  if (cur && (Config.expert || Confirm(-1, -1, "Some LineDefs have only one side but their 2S bit is set", "Do you want to clear the 'two-sided' flag?")))
    {
      while (cur)
        {
          level->linedefs[cur->objnum].flags &= ~0x04;
          UnSelectObject(&cur, cur->objnum);
        }
    }
  else
    ForgetSelection(&cur);

  for (n = 0; n < level->num_linedefs; n++)
    if ((level->linedefs[n].flags & 0x04) == 0 && level->linedefs[n].sidedef2 >= 0)
      SelectObject(&cur, n);
  if (cur && (Config.expert || Confirm(-1, -1, "Some LineDefs have two sides but their 2S bit is not set", "Do you want to set the 'two-sided' flag?")))
    {
      while (cur)
        {
          level->linedefs[cur->objnum].flags |= 0x04;
          UnSelectObject(&cur, cur->objnum);
        }
    }
  else
    ForgetSelection(&cur);

  /* select all Vertices */
  for (n = 0; n < level->num_vertexes; n++)
    SelectObject(&cur, n);
  /* unselect Vertices used in a LineDef */
  for (n = 0; n < level->num_linedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_linedefs);
      m = level->linedefs[n].start;
      if (cur && m >= 0)
        UnSelectObject(&cur, m);
      m = level->linedefs[n].end;
      if (cur && m >= 0)
        UnSelectObject(&cur, m);
      continue;
    }
  /* check if there are any Vertices left */
  if (cur && (Config.expert || Confirm(-1, -1, "Some Vertices are not bound to any LineDef", "Do you want to delete these unused Vertices?")))
    DeleteObjects(level, OBJ_VERTEXES, &cur);
  else
    ForgetSelection(&cur);

  DisplayProgressMeter(-1, 50, 320, "Checking cross-references - 4/5 ...");
  /* select all SideDefs */
  for (n = 0; n < level->num_sidedefs; n++)
    SelectObject(&cur, n);
  /* unselect SideDefs bound to a LineDef */
  for (n = 0; n < level->num_linedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_linedefs);
      m = level->linedefs[n].sidedef1;
      if (cur && m >= 0)
        UnSelectObject(&cur, m);
      m = level->linedefs[n].sidedef2;
      if (cur && m >= 0)
        UnSelectObject(&cur, m);
      continue;
    }
  /* check if there are any SideDefs left */
  if (cur && (Config.expert || Confirm(-1, -1, "Some SideDefs are not bound to any LineDef", "Do you want to delete these unused SideDefs?")))
    DeleteObjects(level, OBJ_SIDEDEFS, &cur);
  else
    ForgetSelection(&cur);

  DisplayProgressMeter(-1, 50, 320, "Checking cross-references - 5/5 ...");
  /* select all Sectors */
  for (n = 0; n < level->num_sectors; n++)
    SelectObject(&cur, n);
  /* unselect Sectors bound to a SideDef */
  for (n = 0; n < level->num_linedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_linedefs);
      m = level->linedefs[n].sidedef1;
      if (cur && m >= 0 && level->sidedefs[m].sector >= 0)
        UnSelectObject(&cur, level->sidedefs[m].sector);
      m = level->linedefs[n].sidedef2;
      if (cur && m >= 0 && level->sidedefs[m].sector >= 0)
        UnSelectObject(&cur, level->sidedefs[m].sector);
      continue;
    }
  /* check if there are any Sectors left */
  if (cur && (Config.expert || Confirm(-1, -1, "Some Sectors are not bound to any SideDef", "Do you want to delete these unused Sectors?")))
    DeleteObjects(level, OBJ_SECTORS, &cur);
  else
    ForgetSelection(&cur);
  return TRUE;
}



/*
   Check for missing textures.
*/

Bool CheckTextures(LevelPtr level)
{
  Int16 n;
  Int16 sd1, sd2;
  Int16 s1, s2;
  char  msg1[80], msg2[80];

  LogMessage("\nVerifying textures...\n");
  DisplayProgressMeter(-1, 50, 320, "Checking ceiling and floor textures...");
  for (n = 0; n < level->num_sectors; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_sectors);
      if (level->sectors[n].ceilt[0] == '-' && level->sectors[n].ceilt == '\0')
        {
          sprintf(msg1, "Error: Sector #%d has no ceiling texture", n);
          sprintf(msg2, "You probaly used a brain-damaged editor to do that...");
          CheckFailed(-1, -1, msg1, msg2, TRUE);
          GoToObject(level, OBJ_SECTORS, n);
          return FALSE;
        }
      if (level->sectors[n].floort[0] == '-' && level->sectors[n].floort == '\0')
        {
          sprintf(msg1, "Error: Sector #%d has no floor texture", n);
          sprintf(msg2, "You probaly used a brain-damaged editor to do that...");
          CheckFailed(-1, -1, msg1, msg2, TRUE);
          GoToObject(level, OBJ_SECTORS, n);
          return FALSE;
        }
      if (level->sectors[n].ceilh < level->sectors[n].floorh)
        {
          sprintf(msg1, "Error: Sector #%d has its ceiling lower than its floor", n);
          sprintf(msg2, "The textures will never be displayed if you cannot go there");
          CheckFailed(-1, -1, msg1, msg2, TRUE);
          GoToObject(level, OBJ_SECTORS, n);
          return FALSE;
        }
#ifdef OLDDOOM
      if (level->sectors[n].ceilh - level->sectors[n].floorh > 1023)
        {
          sprintf(msg1, "Error: Sector #%d has its ceiling too high", n);
          sprintf(msg2, "The maximum difference allowed is 1023 (ceiling - floor)");
          CheckFailed(-1, -1, msg1, msg2, TRUE);
          GoToObject(level, OBJ_SECTORS, n);
          return FALSE;
        }
#endif
    }
  DisplayProgressMeter(-1, 50, 320, "Checking wall textures...");
  for (n = 0; n < level->num_linedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_linedefs);
      sd1 = level->linedefs[n].sidedef1;
      sd2 = level->linedefs[n].sidedef2;
      if (sd1 >= 0)
        s1 = level->sidedefs[sd1].sector;
      else
        s1 = -1;
      if (sd2 >= 0)
        s2 = level->sidedefs[sd2].sector;
      else
        s2 = -1;
      if (s1 >= 0 && s2 < 0)
        {
          if (level->sidedefs[sd1].tex3[0] == '-' && level->sidedefs[sd1].tex3[1] == '\0')
            {
              sprintf(msg1, "Error in one-sided Linedef #%d: SideDef #%d has no normal texture", n, sd1);
              sprintf(msg2, "Do you want to set the texture to \"%s\" and continue?", Config.wallTexture);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
              strncpy(level->sidedefs[sd1].tex3, Config.wallTexture, 8);
            }
        }
      if (s1 >= 0 && s2 >= 0 && level->sectors[s1].ceilh > level->sectors[s2].ceilh)
        {
          if (level->sidedefs[sd1].tex1[0] == '-' && level->sidedefs[sd1].tex1[1] == '\0'
              && (strncmp(level->sectors[s1].ceilt, "F_SKY1", 8) || strncmp(level->sectors[s2].ceilt, "F_SKY1", 8)))
            {
              sprintf(msg1, "Error in first SideDef of Linedef #%d: SideDef #%d has no upper texture", n, sd1);
              sprintf(msg2, "Do you want to set the texture to \"%s\" and continue?", Config.upperTexture);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
              strncpy(level->sidedefs[sd1].tex1, Config.upperTexture, 8);
            }
        }
      if (s1 >= 0 && s2 >= 0 && level->sectors[s1].floorh < level->sectors[s2].floorh)
        {
          if (level->sidedefs[sd1].tex2[0] == '-' && level->sidedefs[sd1].tex2[1] == '\0')
            {
              sprintf(msg1, "Error in first SideDef of Linedef #%d: SideDef #%d has no lower texture", n, sd1);
              sprintf(msg2, "Do you want to set the texture to \"%s\" and continue?", Config.lowerTexture);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
              strncpy(level->sidedefs[sd1].tex2, Config.lowerTexture, 8);
            }
        }
      if (s1 >= 0 && s2 >= 0 && level->sectors[s2].ceilh > level->sectors[s1].ceilh)
        {
          if (level->sidedefs[sd2].tex1[0] == '-' && level->sidedefs[sd2].tex1[1] == '\0'
              && (strncmp(level->sectors[s1].ceilt, "F_SKY1", 8) || strncmp(level->sectors[s2].ceilt, "F_SKY1", 8)))
            {
              sprintf(msg1, "Error in second SideDef of Linedef #%d: SideDef #%d has no upper texture", n, sd2);
              sprintf(msg2, "Do you want to set the texture to \"%s\" and continue?", Config.upperTexture);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
              strncpy(level->sidedefs[sd2].tex1, Config.upperTexture, 8);
            }
        }
      if (s1 >= 0 && s2 >= 0 && level->sectors[s2].floorh < level->sectors[s1].floorh)
        {
          if (level->sidedefs[sd2].tex2[0] == '-' && level->sidedefs[sd2].tex2[1] == '\0')
            {
              sprintf(msg1, "Error in second SideDef of Linedef #%d: SideDef #%d has no lower texture", n, sd2);
              sprintf(msg2, "Do you want to set the texture to \"%s\" and continue?", Config.lowerTexture);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_LINEDEFS, n);
                  return FALSE;
                }
              strncpy(level->sidedefs[sd2].tex2, Config.lowerTexture, 8);
            }
        }
    }
  return TRUE;
}



/*
   Check if a texture name matches one of the elements of a list.
*/

static Bool IsTextureNameInList(char *name, char **list, Int16 numelems)
{
  Int16 n;

  for (n = 0; n < numelems; n++)
    if (! strnicmp(name, list[n], 8))
      return TRUE;
  return FALSE;
}



/*
   Check for invalid texture names.
*/

Bool CheckTextureNames(LevelPtr level)
{
  Int16  n;
  char   msg1[80], msg2[80];
  SelPtr special;

  LogMessage("\nVerifying texture names...\n");
  DisplayProgressMeter(-1, 50, 320, "Checking ceiling and floor textures...");
  if (FindMasterDir(MasterDir, "F2_START") == NULL)
    level->num_sectors--;
  special = NULL;
  for (n = 0; n < level->num_sectors; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_sectors);
      if (! IsTextureNameInList(level->sectors[n].ceilt, ftexture, num_ftexture))
        {
          if (IsSpecialFTexture(level->sectors[n].ceilt))
            SelectObject(&special, n);
          else
            {
              sprintf(msg1, "Invalid ceiling texture in Sector #%d", n);
              sprintf(msg2, "The name \"%s\" is not a floor/ceiling texture", level->sectors[n].ceilt);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  ForgetSelection(&special);
                  GoToObject(level, OBJ_SECTORS, n);
                  return FALSE;
                }
            }
        }
      if (! IsTextureNameInList(level->sectors[n].floort, ftexture, num_ftexture))
        {
          if (IsSpecialFTexture(level->sectors[n].floort))
            SelectObject(&special, n);
          else
            {
              sprintf(msg1, "Invalid floor texture in Sector #%d", n);
              sprintf(msg2, "The name \"%s\" is not a floor/ceiling texture", level->sectors[n].floort);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  ForgetSelection(&special);
                  GoToObject(level, OBJ_SECTORS, n);
                  return FALSE;
                }
            }
        }
    }
  if (special != NULL)
    {
      strcpy(msg1, "There are some placeholders left for floor/ceiling textures (names");
      strcpy(msg2, "beginning with '_').  Do you want to convert them automatically?");
      if (Confirm(-1, -1, msg1, msg2))
        {
          UpdateSpecialFTextures(level, &special);
          if (special != NULL)
            {
              strcpy(msg1, "Some special textures (__AUTO) could not be converted because the");
              strcpy(msg2, "sectors have no normal neighbours.  You should change them manually.");
              n = special->objnum;
              ForgetSelection(&special);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_SECTORS, n);
                  return FALSE;
                }
            }
        }
      else
        ForgetSelection(&special);
    }

  DisplayProgressMeter(-1, 50, 320, "Checking wall textures...");
  for (n = 0; n < level->num_sidedefs; n++)
    {
      UpdateProgressMeter((float) n / (float) level->num_sidedefs);
      if (! IsTextureNameInList(level->sidedefs[n].tex1, wtexture, num_wtexture))
        {
          if (IsSpecialWTexture(level->sidedefs[n].tex1))
            SelectObject(&special, n);
          else
            {
              sprintf(msg1, "Invalid upper texture in SideDef #%d", n);
              sprintf(msg2, "The name \"%s\" is not a wall texture", level->sidedefs[n].tex1);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  ForgetSelection(&special);
                  GoToObject(level, OBJ_SIDEDEFS, n);
                  return FALSE;
                }
            }
        }
      if (! IsTextureNameInList(level->sidedefs[n].tex2, wtexture, num_wtexture))
        {
          if (IsSpecialWTexture(level->sidedefs[n].tex2))
            SelectObject(&special, n);
          else
            {
              sprintf(msg1, "Invalid lower texture in SideDef #%d", n);
              sprintf(msg2, "The name \"%s\" is not a wall texture", level->sidedefs[n].tex2);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  ForgetSelection(&special);
                  GoToObject(level, OBJ_SIDEDEFS, n);
                  return FALSE;
                }
            }
        }
      if (! IsTextureNameInList(level->sidedefs[n].tex3, wtexture, num_wtexture))
        {
          if (IsSpecialWTexture(level->sidedefs[n].tex3))
            SelectObject(&special, n);
          else
            {
              sprintf(msg1, "Invalid normal texture in SideDef #%d", n);
              sprintf(msg2, "The name \"%s\" is not a wall texture", level->sidedefs[n].tex3);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  ForgetSelection(&special);
                  GoToObject(level, OBJ_SIDEDEFS, n);
                  return FALSE;
                }
            }
        }
    }
  if (special != NULL)
    {
      strcpy(msg1, "There are some placeholders left for wall textures (special names");
      strcpy(msg2, "beginning with '_').  Do you want to convert them automatically?");
      if (Confirm(-1, -1, msg1, msg2))
        UpdateSpecialWTextures(level, &special);
      else
        ForgetSelection(&special);
    }
  return TRUE;
}


/*
   Check for players starting points, keys, objects outside the map,...
*/

Bool CheckThings(LevelPtr level)
{
  char    msg1[80], msg2[80];
  Int16   t, ld, s;
  TPtr    tptr;
  LDPtr   ldptr;
  SPtr    sptr;
  UInt16  props;
  int     player[5][3];
  int     key[3][3];
  int     door[3];
  Bool    endlevel;

  DisplayProgressMeter(-1, 50, 320, "Checking Things...");
  /* Clear the tables of player starting positions and keys. */
  for (t = 0; t < 3; t++) /* 0 = D12, 1 = D3, 2 = D45 */
    {
      player[0][t] = 0; /* DeathMatch (0 only - for D12345) */
      player[1][t] = 0; /* Player 1   */
      player[2][t] = 0; /* Player 2   */
      player[3][t] = 0; /* Player 3   */
      player[4][t] = 0; /* Player 4   */
      key[0][t] = 0;    /* Blue key   */
      key[1][t] = 0;    /* Yellow key */
      key[2][t] = 0;    /* Red key    */
      door[t] = 0;
    }
  endlevel = FALSE;
  /* Check all Things on this level */
  for (t = 0, tptr = level->things; t < level->num_things; t++, tptr++)
    {
      UpdateProgressMeter((float) t / (float) level->num_things);
      props = GetThingProperties(tptr->type);
      /* Heretic "sound objects" can be outside the map, so skip the test */
      if (props & TP_SOUND)
        continue;
      /* check if the Thing is inside a Sector */
      s = GetSectorForThing(level, t);
      if (s < 0)
        {
          sprintf(msg1, "Thing #%d (%s) is not inside any Sector.",
                  t, GetThingName(tptr->type));
          if (CheckFailed(-1, -1, msg1, NULL, FALSE))
            {
              GoToObject(level, OBJ_THINGS, t);
              return FALSE;
            }
        }
      else
        {
          sptr = &(level->sectors[s]);
          /* check if the ceiling is high enough for that Thing */
          if (sptr->ceilh - sptr->floorh < GetThingHeight(tptr->type))
            {
              sprintf(msg1, "Thing #%d (%s) is too big to fit in the room.",
                      t, GetThingName(tptr->type));
              sprintf(msg2, "Thing height is %d and the room is only %d high.",
                      GetThingHeight(tptr->type), sptr->ceilh - sptr->floorh);
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_THINGS, t);
                  return FALSE;
                }
            }
          /* check for Things stuck in walls - only for monsters & players */
          else if (props & (TP_KILL | TP_PLAYER))
            {
              Int16 r;

              /* note: the monsters in Doom can be partially in the walls
                 without being stuck, so I substract 8 from the real radius */
              r = GetThingRadius(tptr->type) - 8;
              for (ld = 0; ld < level->num_linedefs; ld++)
                {
                  if (IsLineDefInside(level, ld,
                                      tptr->xpos - r, tptr->ypos - r,
                                      tptr->xpos + r, tptr->ypos + r))
                    {
                      if (level->linedefs[ld].sidedef2 < 0)
                        {
                          sprintf(msg1, "Thing #%d (%s) is stuck in a wall.",
                                  t, GetThingName(tptr->type));
                          sprintf(msg2, "It will not be able to move.");
                          if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                            {
                              GoToObject(level, OBJ_THINGS, t);
                              return FALSE;
                            }
                          /* do not test other LineDefs */
                          break;
                        }
                      else
                        {
                          Int16 sd, s0;

                          sptr = NULL;
                          sd = level->linedefs[ld].sidedef1;
                          if (sd >= 0)
                            s0 = level->sidedefs[sd].sector;
                          if (s0 >= 0 && s0 != s)
                            sptr = &(level->sectors[s0]);
                          else
                            {
                              sd = level->linedefs[ld].sidedef2;
                              if (sd >= 0)
                                s0 = level->sidedefs[sd].sector;
                              if (s0 >= 0 && s0 != s)
                                sptr = &(level->sectors[s0]);

                            }
                          if (sptr != NULL && sptr->ceilh - sptr->floorh < GetThingHeight(tptr->type))
                            {
                              sprintf(msg1, "Thing #%d (%s) is too big to fit in the room.",
                                      t, GetThingName(tptr->type));
                              sprintf(msg2, "Thing height is %d and the room besides it is only %d high.",
                                      GetThingHeight(tptr->type), sptr->ceilh - sptr->floorh);
                              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                                {
                                  GoToObject(level, OBJ_THINGS, t);
                                  return FALSE;
                                }
                            }
                        }
                    }
                }
              /*! check Things stuck together */
            }

          if (tptr->type >= THING_PLAYER1 && tptr->type <= THING_PLAYER4)
            {
              if (tptr->when & 0x01)
                player[tptr->type][0]++;
              if (tptr->when & 0x02)
                player[tptr->type][1]++;
              if (tptr->when & 0x04)
                player[tptr->type][2]++;
            }
          else if (tptr->type == THING_DEATHMATCH)
            player[0][0]++; /* ignore difficulty setting */
          else if ((tptr->when & 0x10) == 0) /* ignore keys if "Net only" */
            {
              if (tptr->type == THING_TELEPORT)
                {
                  /*! Keep some info for later, to speed up the tests for
                      exits in some sectors.  Also check if some exits are
                      in sectors that are not used by teleporters (no tag). */
                }
              else if (GetKeyType(tptr->type) == KEYTYPE_BLUE)
                {
                  if (tptr->when & 0x01)
                    key[0][0]++;
                  if (tptr->when & 0x02)
                    key[0][1]++;
                  if (tptr->when & 0x04)
                    key[0][2]++;
                }
              else if (GetKeyType(tptr->type) == KEYTYPE_YLLW)
                {
                  if (tptr->when & 0x01)
                    key[1][0]++;
                  if (tptr->when & 0x02)
                    key[1][1]++;
                  if (tptr->when & 0x04)
                    key[1][2]++;
                }
              else if (GetKeyType(tptr->type) == KEYTYPE_RED)
                {
                  if (tptr->when & 0x01)
                    key[2][0]++;
                  if (tptr->when & 0x02)
                    key[2][1]++;
                  if (tptr->when & 0x04)
                    key[2][2]++;
                }
            }
        }
    }

  /* Check player starting positions */
  for (t = 1; t <= 4; t++)
    {
      if (player[t][0] == 0 || player[t][1] == 0 || player[t][2] == 0)
        {
          Beep();
          if (player[t][0] == 0 && player[t][1] == 0 && player[t][2] == 0)
            sprintf(msg1, "Warning: there is no starting point for player %d.  You will not be able", t);
          else if (player[t][0] == 0)
            sprintf(msg1, "Warning: there is no player %d starting point on D12.  You will not be able", t);
          else if (player[t][1] == 0)
            sprintf(msg1, "Warning: there is no player %d starting point on D3.  You will not be able", t);
          else if (player[t][2] == 0)
            sprintf(msg1, "Warning: there is no player %d starting point on D45.  You will not be able", t);
          sprintf(msg2, "to use this level for multi-player games.  Press Y to return to the editor.");
          if (Confirm(-1, -1, msg1, msg2))
            return FALSE;
        }
      if (player[t][0] + player[t][1] + player[t][2] > 3)
        {
          Beep();
          sprintf(msg1, "Warning: there is more than one player %d starting point (on each difficulty", t);
          sprintf(msg2, "setting).  You will have 'ghost' players.  Press Y to return to the editor.");
          if (Confirm(-1, -1, msg1, msg2))
            return FALSE;
        }
    }

  /* Check number of DeathMatch starts */
  if (player[0][0] < 4)
    {
      Beep();
      if (player[0][0] == 0)
        sprintf(msg1, "Warning: there are no DeathMatch starting points.  You need at least four");
      else if (player[0][0] == 1)
        sprintf(msg1, "Warning: there is only one DeathMatch starting point.  You need at least four");
      else
        sprintf(msg1, "Warning: there are only %d DeathMatch starting points.  You need at least four", player[0][0]);
      sprintf(msg2, "starting points to play DeathMatch games.  Press Y to return to the editor.");
      if (Confirm(-1, -1, msg1, msg2))
        return FALSE;
    }
  else if (player[0][0] > 10)
    {
      Beep();
      sprintf(msg1, "Warning: there are %d DeathMatch starting points.  Only the first ten", player[0][0]);
      sprintf(msg2, "starting points will be used.  Press Y to return to the editor.");
      if (Confirm(-1, -1, msg1, msg2))
        return FALSE;
    }

  /* After having counted the teleporter exits and keys, check if
     there are any teleporters and locked doors. */
  DisplayProgressMeter(-1, 50, 320, "Checking LineDef tags...");
  ldptr = level->linedefs;
  for (ld = 0; ld < level->num_linedefs; ld++)
    {
      UpdateProgressMeter((float) ld / (float) level->num_linedefs);
      if (ldptr->type != 0)
        {
          props = GetLineDefProperties(ldptr->type);
          if ((props & LDP_NEEDTAG) != 0 && ldptr->tag == 0)
            {
              sprintf(msg1, "LineDef #%d has no tag number, but has the special", ld);
              sprintf(msg2, "type %d (%s) which requires a tag number.",
                      ldptr->type, GetLineDefTypeName(ldptr->type));
              if (CheckFailed(-1, -1, msg1, msg2, FALSE))
                {
                  GoToObject(level, OBJ_THINGS, t);
                  return FALSE;
                }
            }
          else if ((props & LDP_MV_MASK) == LDP_MV_CEIL)
            {
              if ((props & LDP_KEY_MASK) == LDP_KEY_BLUE)
                door[0]++;
              else if ((props & LDP_KEY_MASK) == LDP_KEY_YLLW)
                door[1]++;
              else if ((props & LDP_KEY_MASK) == LDP_KEY_RED)
                door[2]++;
            }
          else if ((props & LDP_MV_MASK) == LDP_MV_NONE)
            {
              if ((props & LDP_TELEPORT) != 0)
                {
                  sptr = level->sectors;
                  for (s = 0; s < level->num_sectors; s++)
                    {
                      if (sptr->tag == ldptr->tag)
                        {
                          /*! Check if there is a teleport exit in the sector. */
                          if (TRUE)
                            {
                              /*! Be sure to check all difficulty settings.
                                  Continue if necessary. */
                              break;
                            }
                        }
#ifdef BUGGY_TURBOC_3
                      sptr = sptr + 1;
#else
                      sptr++;
#endif
                    }
                  if (s >= level->num_sectors)
                    {
                      /*! Error : no exit (for at least one difficulty setting). */
                    }
                }
              else if ((props & LDP_ENDLEVEL) != 0)
                endlevel = TRUE;
            }
        }
#ifdef BUGGY_TURBOC_3
        ldptr = ldptr + 1;
#else
        ldptr++;
#endif
    }

  /* check if all doors have a matching key */
  for (t = 0; t <= 2; t++)
    {
      char *keycolor;

      if (t == 0)
        keycolor = "BLUE";
      else if (t == 1)
        keycolor = "YELLOW";
      else if (DoomVersion < 16)
        keycolor = "RED";
      else
        keycolor = "GREEN";
      if (door[t] == 0)
        {
          if (key[t][0] != 0 || key[t][1] != 0 || key[t][2] != 0)
            {
              Beep();
              sprintf(msg1, "Warning: there is a %s key, but no %s door.", keycolor, keycolor);
              sprintf(msg2, "This key is useless.  Press Y to return to the editor.");
              if (Confirm(-1, -1, msg1, msg2))
                return FALSE;
            }
          continue;
        }
      if (key[t][0] == 0 || key[t][1] == 0 || key[t][2] == 0)
        {
          Beep();
          if (key[t][0] == 0 && key[t][1] == 0 && key[t][2] == 0)
            sprintf(msg1, "Warning: there is a %s door, but no %s key.  The player will", keycolor, keycolor);
          else if (key[t][0] == 0)
            sprintf(msg1, "Warning: there is a %s door, but no %s key on D12.  The player will", keycolor, keycolor);
          else if (key[t][1] == 0)
            sprintf(msg1, "Warning: there is a %s door, but no %s key on D3.  The player will", keycolor, keycolor);
          else if (key[t][2] == 0)
            sprintf(msg1, "Warning: there is a %s door, but no %s key on D45.  The player will not", keycolor, keycolor);
          sprintf(msg2, "not be able to open all doors.  Press Y to return to the editor.");
          if (Confirm(-1, -1, msg1, msg2))
            return FALSE;
        }
    }

  /* check if the level has at least one exit */
  if (endlevel == FALSE && Confirm(-1, -1,
                                   "Warning: there is no exit in this level",
                                   NULL))
    {
      return FALSE;
    }

  return TRUE;
}



/*
   Run all automatic checks.
*/

Bool CheckEverything(LevelPtr level)
{
 return CheckCrossReferences(level) && CheckSectors(level)
        && CheckTextures(level) && CheckTextureNames(level)
        && CheckThings(level);
}



/*
   Check if the level is valid before saving it.
*/

Bool CheckBeforeSaving(LevelPtr level)
{
  Int16 t;

  if (level->num_sectors < 2)
    {
      Beep();
      Notify(-1, -1,
             "Error: your level must contain at least two Sectors.",
             "Add a second Sector before saving your level.");
      return FALSE;
    }

  for (t = 0; t < level->num_things; t++)
    if (level->things[t].type == THING_PLAYER1)
      return TRUE;
  Beep();
  return Confirm(-1, -1,
                 "Warning: there is no player 1 starting point!  DOOM will crash if",
                 "you play with this level.  Do you really want to save it?");
}


/* end of file */
