/* Emacs style mode select   -*- C++ -*-
 *-----------------------------------------------------------------------------
 *
 *
 *  PrBoom a Doom port merged with LxDoom and LSDLDoom
 *  based on BOOM, a modified and improved DOOM engine
 *  Copyright (C) 1999 by
 *  id Software, Chi Hoang, Lee Killough, Jim Flynn, Rand Phares, Ty Halderman
 *  Copyright (C) 1999-2000 by
 *  Jess Haas, Nicolas Kalkhof, Colin Phipps, Florian Schulze
 *
 *  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 2
 *  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, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
 *  02111-1307, USA.
 *
 * DESCRIPTION:
 *  DOOM main program (D_DoomMain) and game loop (D_DoomLoop),
 *  plus functions to determine game mode (shareware, registered),
 *  parse command line parameters, configure game parameters (turbo),
 *  and call the startup functions.
 *
 *-----------------------------------------------------------------------------
 */


#include "rockmacros.h"

#include "doomdef.h"
#include "doomtype.h"
#include "doomstat.h"
#include "dstrings.h"
#include "sounds.h"
#include "z_zone.h"
#include "w_wad.h"
#include "s_sound.h"
#include "v_video.h"
#include "f_finale.h"
#include "f_wipe.h"
#include "m_argv.h"
#include "m_misc.h"
#include "m_menu.h"
#include "i_system.h"
#include "i_sound.h"
#include "i_video.h"
#include "g_game.h"
#include "hu_stuff.h"
#include "wi_stuff.h"
#include "st_stuff.h"
#include "am_map.h"
#include "p_setup.h"
#include "r_draw.h"
#include "r_main.h"
#include "d_main.h"
#include "d_deh.h"  // Ty 04/08/98 - Externalizations
#include "m_swap.h"

// DEHacked support - Ty 03/09/97 // CPhipps - const char*'s
void ProcessDehFile(const char *filename, const char *outfilename, int lumpnum);

// CPhipps - removed wadfiles[] stuff

boolean devparm;        // started game with -devparm

// jff 1/24/98 add new versions of these variables to remember command line
boolean clnomonsters;   // checkparm of -nomonsters
boolean clrespawnparm;  // checkparm of -respawn
boolean clfastparm;     // checkparm of -fast
// jff 1/24/98 end definition of command line version of play mode switches

boolean nomonsters;     // working -nomonsters
boolean respawnparm;    // working -respawn
boolean fastparm;       // working -fast
boolean dehout=true;

boolean singletics = false; // debug flag to cancel adaptiveness

bool doomexit;

//jff 1/22/98 parms for disabling music and sound
boolean nomusicparm=0;

//jff 4/18/98
extern boolean inhelpscreens;

skill_t startskill;
int     startepisode;
int     startmap;
boolean autostart;
int    debugfile;
int ffmap;

boolean advancedemo;

extern boolean timingdemo, singledemo, demoplayback, fastdemo; // killough

int      basetic;

void D_DoAdvanceDemo (void);

/*
 * D_PostEvent - Event handling
 *
 * Called by I/O functions when an event is received.
 * Try event handlers for each code area in turn.
 */

void D_PostEvent(event_t *ev)
{
   /* cph - suppress all input events at game start
    * FIXME: This is a lousy kludge */
   if (gametic < 3)
       return;

   if(!M_Responder(ev)) {
       if(gamestate == GS_LEVEL && (
              HU_Responder(ev) ||
              ST_Responder(ev) ||
              AM_Responder(ev)
              ))
           return;
       else
           G_Responder(ev);
   }
}

//
// D_Wipe
//
// CPhipps - moved the screen wipe code from D_Display to here
// The screens to wipe between are already stored, this just does the timing
// and screen updating

static void D_Wipe(void)
{
   boolean done;
   int wipestart = I_GetTime () - 1;

   do
   {
      int nowtime, tics;
      do
      {
         //I_uSleep(5000); // CPhipps - don't thrash cpu in this loop
         nowtime = I_GetTime();
         tics = nowtime - wipestart;
      }
      while (!tics);
      wipestart = nowtime;

      done = wipe_ScreenWipe(0,0,SCREENWIDTH,SCREENHEIGHT,tics);
      M_Drawer();                   // menu is drawn even on top of wipes
      I_FinishUpdate();             // page flip or blit buffer
   }
   while (!done);
}

//
// D_Display
//  draw current display, possibly wiping it from the previous
//

// wipegamestate can be set to -1 to force a wipe on the next draw
gamestate_t    wipegamestate = GS_DEMOSCREEN;
extern boolean setsizeneeded;
extern int     showMessages;

void D_Display (void)
{
   static boolean isborderstate        IDATA_ATTR= false;
   static boolean borderwillneedredraw IDATA_ATTR= false;
   static  gamestate_t  oldgamestate IDATA_ATTR= -1;
   boolean wipe;
   boolean viewactive = false, isborder = false;

   if (nodrawers)                   // for comparative timing / profiling
      return; 

   // save the current screen if about to wipe
   if ((wipe = gamestate != wipegamestate))
      wipe_StartScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);

   if (gamestate != GS_LEVEL) { // Not a level
      switch (oldgamestate) {
      case (gamestate_t)-1:
      case GS_LEVEL:
         V_SetPalette(0); // cph - use default (basic) palette
      default:
         break;
      }

      switch (gamestate) {
      case GS_INTERMISSION:
         WI_Drawer();
         break;
      case GS_FINALE:
         F_Drawer();
         break;
      case GS_DEMOSCREEN:
         D_PageDrawer();
         break;
      default:
         break;
      }
   } else if (gametic != basetic) { // In a level
      boolean redrawborderstuff;

      HU_Erase();

      if (setsizeneeded) {               // change the view size if needed
         R_ExecuteSetViewSize();
         oldgamestate = -1;            // force background redraw
      }

      // Work out if the player view is visible, and if there is a border
      viewactive = (!(automapmode & am_active) || (automapmode & am_overlay)) && !inhelpscreens;
      isborder = viewactive ? (viewheight != SCREENHEIGHT) : (!inhelpscreens && (automapmode & am_active));

      if (oldgamestate != GS_LEVEL) {
         R_FillBackScreen ();    // draw the pattern into the back screen
         redrawborderstuff = isborder;
      } else {
         // CPhipps -
         // If there is a border, and either there was no border last time,
         // or the border might need refreshing, then redraw it.
         redrawborderstuff = isborder && (!isborderstate || borderwillneedredraw);
         // The border may need redrawing next time if the border surrounds the screen,
         // and there is a menu being displayed
         borderwillneedredraw = menuactive && isborder && viewactive && (viewwidth != SCREENWIDTH);
      }

      if (redrawborderstuff)
         R_DrawViewBorder();

      // Now do the drawing
      if (viewactive)
         R_RenderPlayerView (&players[displayplayer]);
      if (automapmode & am_active)
         AM_Drawer();
      ST_Drawer((viewheight != SCREENHEIGHT) || ((automapmode & am_active) && !(automapmode & am_overlay)), redrawborderstuff);
      R_DrawViewBorder();

      HU_Drawer();
   }

   isborderstate      = isborder;
   oldgamestate = wipegamestate = gamestate;

   // draw pause pic
   if (paused) {
      static int x;

      if (!x) { // Cache results of x pos calc
         int lump = W_GetNumForName("M_PAUSE");
         const patch_t* p = W_CacheLumpNum(lump);
         x = (320 - SHORT(p->width))/2;
         W_UnlockLumpNum(lump);
      }

      // CPhipps - updated for new patch drawing
      V_DrawNamePatch(x, (!(automapmode & am_active) || (automapmode & am_overlay))
                      ? 4+(viewwindowy*200/SCREENHEIGHT) : 4, // cph - Must un-stretch viewwindowy
                      0, "M_PAUSE", CR_DEFAULT, VPT_STRETCH);
   }

   // menus go directly to the screen
   M_Drawer();          // menu is drawn even on top of everything
   D_BuildNewTiccmds();

   // normal update
   if (!wipe)
      I_FinishUpdate ();              // page flip or blit buffer
   else {
      // wipe update
      wipe_EndScreen(0, 0, SCREENWIDTH, SCREENHEIGHT);
      D_Wipe();
   }
}

//
//  D_DoomLoop()
//
// Not a globally visible function,
//  just included for source reference,
//  called by D_DoomMain, never exits.
// Manages timing and IO,
//  calls all ?_Responder, ?_Ticker, and ?_Drawer,
//  calls I_GetTime, I_StartFrame, and I_StartTic
//

extern  boolean demorecording;

static void D_DoomLoop (void)
{
   basetic = gametic;

   I_SubmitSound();

   while (!doomexit)
   {
      // process one or more tics
      if (singletics)
      {
         I_StartTic ();
         G_BuildTiccmd (&netcmds[consoleplayer][maketic%BACKUPTICS]);
         if (advancedemo)
            D_DoAdvanceDemo ();
         M_Ticker ();
         G_Ticker ();
         gametic++;
         maketic++;
      }
      else
         TryRunTics (); // will run at least one tic

      // killough 3/16/98: change consoleplayer to displayplayer
      if (players[displayplayer].mo) // cph 2002/08/10
         S_UpdateSounds(players[displayplayer].mo);// move positional sounds

      // Update display, next frame, with current state.
      D_Display();

      // Give the system some time
      rb->yield();
   }
}

//
//  DEMO LOOP
//

static int  demosequence;         // killough 5/2/98: made static
static int  pagetic;
static const char *pagename; // CPhipps - const

//
// D_PageTicker
// Handles timing for warped projection
//
void D_PageTicker(void)
{
   if (--pagetic < 0)
      D_AdvanceDemo();
}

//
// D_PageDrawer
//
void D_PageDrawer(void)
{
   // CPhipps - updated for new patch drawing
   V_DrawNamePatch(0, 0, 0, pagename, CR_DEFAULT, VPT_STRETCH);
}

//
// D_AdvanceDemo
// Called after each demo or intro demosequence finishes
//
void D_AdvanceDemo (void)
{
   advancedemo = true;
}

/* killough 11/98: functions to perform demo sequences
 * cphipps 10/99: constness fixes
 */

static void D_SetPageName(const char *name)
{
   pagename = name;
}

static void D_DrawTitle1(const char *name)
{
   S_StartMusic(mus_intro);
   pagetic = (TICRATE*170)/35;
   D_SetPageName(name);
}

static void D_DrawTitle2(const char *name)
{
   S_StartMusic(mus_dm2ttl);
   D_SetPageName(name);
}

/* killough 11/98: tabulate demo sequences
 */

static struct
{
   void (*func)(const char *);
   const char *name;
} const demostates[][4] =
   {
      {
         {D_DrawTitle1, "TITLEPIC"},
         {D_DrawTitle1, "TITLEPIC"},
         {D_DrawTitle2, "TITLEPIC"},
         {D_DrawTitle1, "TITLEPIC"},
      },

      {
         {G_DeferedPlayDemo, "demo1"},
         {G_DeferedPlayDemo, "demo1"},
         {G_DeferedPlayDemo, "demo1"},
         {G_DeferedPlayDemo, "demo1"},
      },
      {
         {D_SetPageName, "CREDIT"},
         {D_SetPageName, "CREDIT"},
         {D_SetPageName, "CREDIT"},
         {D_SetPageName, "CREDIT"},
      },

      {
         {G_DeferedPlayDemo, "demo2"},
         {G_DeferedPlayDemo, "demo2"},
         {G_DeferedPlayDemo, "demo2"},
         {G_DeferedPlayDemo, "demo2"},
      },

      {
         {D_SetPageName, "HELP2"},
         {D_SetPageName, "HELP2"},
         {D_SetPageName, "CREDIT"},
         {D_DrawTitle1,  "TITLEPIC"},
      },

      {
         {G_DeferedPlayDemo, "demo3"},
         {G_DeferedPlayDemo, "demo3"},
         {G_DeferedPlayDemo, "demo3"},
         {G_DeferedPlayDemo, "demo3"},
      },

      {
         {NULL,0},
         {NULL,0},
         {NULL,0},
         {D_SetPageName, "CREDIT"},
      },

      {
         {NULL,0},
         {NULL,0},
         {NULL,0},
         {G_DeferedPlayDemo, "demo4"},
      },

      {
         {NULL,0},
         {NULL,0},
         {NULL,0},
         {NULL,0},
      }
   };

/*
 * This cycles through the demo sequences.
 * killough 11/98: made table-driven
 */

void D_DoAdvanceDemo(void)
{
   players[consoleplayer].playerstate = PST_LIVE;  /* not reborn */
   advancedemo = usergame = paused = false;
   gameaction = ga_nothing;

   pagetic = TICRATE * 11;         /* killough 11/98: default behavior */
   gamestate = GS_DEMOSCREEN;

   if (netgame && !demoplayback) {
      demosequence = 0;
   } else
      if (!demostates[++demosequence][gamemode].func)
         demosequence = 0;
   demostates[demosequence][gamemode].func(demostates[demosequence][gamemode].name);
}

//
// D_StartTitle
//
void D_StartTitle (void)
{
   gameaction = ga_nothing;
   demosequence = -1;
   D_AdvanceDemo();
}

//
// D_AddFile
//
// Rewritten by Lee Killough
//
// Ty 08/29/98 - add source parm to indicate where this came from
// CPhipps - static, const char* parameter
//         - source is an enum
//         - modified to allocate & use new wadfiles array
void D_AddFile (const char *file, wad_source_t source)
{
   wadfiles = realloc(wadfiles, sizeof(*wadfiles)*(numwadfiles+1));
   wadfiles[numwadfiles].name =
      AddDefaultExtension(strcpy(malloc(strlen(file)+5), file), ".wad");
   wadfiles[numwadfiles].src = source; // Ty 08/29/98
   numwadfiles++;
}

//
// CheckIWAD
//
// Verify a file is indeed tagged as an IWAD
// Scan its lumps for levelnames and return gamemode as indicated
// Detect missing wolf levels in DOOM II
//
// The filename to check is passed in iwadname, the gamemode detected is
// returned in gmode, hassec returns the presence of secret levels
//
// jff 4/19/98 Add routine to test IWAD for validity and determine
// the gamemode from it. Also note if DOOM II, whether secret levels exist
// CPhipps - const char* for iwadname, made static
#if 0
static void CheckIWAD(const char *iwadname,GameMode_t *gmode,boolean *hassec)
{
   if ( !fileexists (iwadname) )
   {
      int ud=0,rg=0,sw=0,cm=0,sc=0;
      int handle;

      // Identify IWAD correctly
      if ( (handle = open (iwadname,O_RDONLY)) != -1)
      {
         wadinfo_t header;

         // read IWAD header
         read (handle, &header, sizeof(header));
         if (!strncmp(header.identification,"IWAD",4))
         {
            size_t length;
            filelump_t *fileinfo;

            // read IWAD directory
            header.numlumps = LONG(header.numlumps);
            header.infotableofs = LONG(header.infotableofs);
            length = header.numlumps;
            fileinfo = malloc(length*sizeof(filelump_t));
            lseek (handle, header.infotableofs, SEEK_SET);
            read (handle, fileinfo, length*sizeof(filelump_t));
            close(handle);

            // scan directory for levelname lumps
            while (length--)
               if (fileinfo[length].name[0] == 'E' &&
                     fileinfo[length].name[2] == 'M' &&
                     fileinfo[length].name[4] == 0)
               {
                  if (fileinfo[length].name[1] == '4')
                     ++ud;
                  else if (fileinfo[length].name[1] == '3')
                     ++rg;
                  else if (fileinfo[length].name[1] == '2')
                     ++rg;
                  else if (fileinfo[length].name[1] == '1')
                     ++sw;
               }
               else if (fileinfo[length].name[0] == 'M' &&
                        fileinfo[length].name[1] == 'A' &&
                        fileinfo[length].name[2] == 'P' &&
                        fileinfo[length].name[5] == 0)
               {
                  ++cm;
                  if (fileinfo[length].name[3] == '3')
                     if (fileinfo[length].name[4] == '1' ||
                           fileinfo[length].name[4] == '2')
                        ++sc;
               }

            free(fileinfo);
         }
         else // missing IWAD tag in header
            I_Error("CheckIWAD: IWAD tag %s not present", iwadname);
      }
      else // error from open call
         I_Error("CheckIWAD: Can't open IWAD %s", iwadname);

      // Determine game mode from levels present
      // Must be a full set for whichever mode is present
      // Lack of wolf-3d levels also detected here

      *gmode = indetermined;
      *hassec = false;
      if (cm>=30)
      {
         *gmode = commercial;
         *hassec = sc>=2;
      }
      else if (ud>=9)
         *gmode = retail;
      else if (rg>=18)
         *gmode = registered;
      else if (sw>=9)
         *gmode = shareware;
   }
   else // error from access call
      I_Error("CheckIWAD: IWAD %s not readable", iwadname);
}
#endif
void D_DoomMainSetup(void)
{
   int   p;

   nomonsters = M_CheckParm ("-nomonsters");
   respawnparm = M_CheckParm ("-respawn");
   fastparm = M_CheckParm ("-fast");
   devparm = M_CheckParm ("-devparm");
   if (M_CheckParm ("-altdeath"))
      deathmatch = 2;
   else if (M_CheckParm ("-deathmatch"))
      deathmatch = 1;

   printf("Welcome to Rockdoom\n");

   switch ( gamemode )
   {
   case retail:
      printf ("The Ultimate DOOM Startup v%d.%d\n",DVERSION/100,DVERSION%100);
      break;
   case shareware:
      printf ("DOOM Shareware Startup v%d.%d\n",DVERSION/100,DVERSION%100);
      break;
   case registered:
      printf ("DOOM Registered Startup v%d.%d\n",DVERSION/100,DVERSION%100);
      break;
   case commercial:
      switch (gamemission)
      {
      case pack_plut:
         printf ("DOOM 2: Plutonia Experiment v%d.%d\n",DVERSION/100,DVERSION%100);
         break;
      case pack_tnt:
         printf ("DOOM 2: TNT - Evilution v%d.%d\n",DVERSION/100,DVERSION%100);
         break;
      default:
         printf ("DOOM 2: Hell on Earth v%d.%d\n",DVERSION/100,DVERSION%100);
         break;
      }
      break;
   default:
      printf ("Public DOOM v%d.%d\n",DVERSION/100,DVERSION%100);
      break;
   }

   if (devparm)
      printf(D_DEVSTR);

   // turbo option
   if ((p=M_CheckParm ("-turbo")))
   {
      int scale = 200;
      extern int forwardmove[2];
      extern int sidemove[2];

      if (p<myargc-1)
         scale = atoi (myargv[p+1]);
      if (scale < 10)
         scale = 10;
      if (scale > 400)
         scale = 400;
      printf ("turbo scale: %d%%\n",scale);
      forwardmove[0] = forwardmove[0]*scale/100;
      forwardmove[1] = forwardmove[1]*scale/100;
      sidemove[0] = sidemove[0]*scale/100;
      sidemove[1] = sidemove[1]*scale/100;
   }

   // get skill / episode / map from parms
   startskill = sk_medium;
   startepisode = 1;
   startmap = 1;
   autostart = false;

   p = M_CheckParm ("-skill");
   if (p && p < myargc-1)
   {
      startskill = myargv[p+1][0]-'1';
      autostart = true;
   }

   p = M_CheckParm ("-episode");
   if (p && p < myargc-1)
   {
      startepisode = myargv[p+1][0]-'0';
      startmap = 1;
      autostart = true;
   }

   p = M_CheckParm ("-warp");
   if (p && p < myargc-1)
   {
      if (gamemode == commercial)
         startmap = atoi (myargv[p+1]);
      else
      {
         startepisode = myargv[p+1][0]-'0';
         startmap = myargv[p+2][0]-'0';
      }
      autostart = true;
   }

   // CPhipps - move up netgame init
   printf("D_InitNetGame: Checking for network game.\n");
   D_InitNetGame();

   // init subsystems
   printf ("V_Init: allocate screens.\n");
   V_Init ();

   printf ("W_Init: Init WADfiles.\n");
   W_Init();

	if ((p = W_CheckNumForName("DEHACKED")) != -1) // cph - add dehacked-in-a-wad support
		ProcessDehFile(NULL, dehout ? "/dehlog.txt" : NULL, p);

   V_InitColorTranslation(); //jff 4/24/98 load color translation lumps

   // Check for -file in shareware
   if (modifiedgame)
   {
      // These are the lumps that will be checked in IWAD,
      // if any one is not present, execution will be aborted.
      const char name[23][8]=
         {
            "e2m1","e2m2","e2m3","e2m4","e2m5","e2m6","e2m7","e2m8","e2m9",
            "e3m1","e3m3","e3m3","e3m4","e3m5","e3m6","e3m7","e3m8","e3m9",
            "dphoof","bfgga0","heada1","cybra1","spida1d1"
         };
      int i;

      if ( gamemode == shareware)
         I_Error("\nYou cannot -file with the shareware version. Register!\n");

      // Check for fake IWAD with right name,
      // but w/o all the lumps of the registered version.
      if (gamemode == registered)
         for (i = 0;i < 23; i++)
            if (W_CheckNumForName(name[i])<0)
               I_Error("This is not the registered version.\n");
   }

   // Iff additonal PWAD files are used, print modified banner
   if (modifiedgame)
      printf ("ATTENTION:  This version of DOOM has been modified.\n");

   // Check and print which version is executed.
   switch ( gamemode )
   {
   case shareware:
   case indetermined:
      printf ("Shareware!\n");
      break;
   case registered:
   case retail:
   case commercial:
      printf ("Commercial product - do not distribute!\n");
      break;
   default:
      // Ouch.
      break;
   }

   printf ("M_Init: Init miscellaneous info.\n");
   M_Init ();

   printf ("R_Init: Init DOOM refresh daemon - ");
   R_Init ();

   printf ("P_Init: Init Playloop state.\n");
   P_Init ();

   printf ("I_Init: Setting up machine state.\n");
   I_Init ();

   printf ("S_Init: Setting up sound.\n");
   S_Init (snd_SfxVolume /* *8 */, snd_MusicVolume /* *8*/ );

   printf ("HU_Init: Setting up heads up display.\n");
   HU_Init ();

   I_InitGraphics ();

   printf ("ST_Init: Init status bar.\n");
   ST_Init ();

   // check for a driver that wants intermission stats
   p = M_CheckParm ("-statcopy");
   if (p && p<myargc-1)
   {
      // for statistics driver
      extern  void* statcopy;

      statcopy = (void*)(intptr_t)atoi(myargv[p+1]);
      printf ("External statistics registered.\n");
   }

   // start the apropriate game based on parms
   p = M_CheckParm ("-record");
   if (p && p < myargc-1)
   {
      G_RecordDemo (myargv[p+1]);
      autostart = true;
   }

   p = M_CheckParm ("-loadgame");
   if (p && p < myargc-1)
      G_LoadGame (atoi(myargv[p+1]), false);

   if ( gameaction != ga_loadgame )
   {
      if (!singledemo) {                  /* killough 12/98 */
         if (autostart || netgame)
            G_InitNew (startskill, startepisode, startmap);
         else
            D_StartTitle ();                // start up intro loop
      }
   }
}

//
// D_DoomMain
//
void D_DoomMain (void)
{
   D_DoomMainSetup(); // get this crap off the stack

   D_DoomLoop ();  // never returns
}
