/*
Copyright (C) 1996-1997 Id Software, Inc.

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.

*/

// screen.c -- master for refresh, status bar, console, chat, notify, etc

#include "screen.h"
#include "quakedef.h"
#include "r_local.h"
#include "host.h"
#include "wad.h"
#include "draw.h"
#include "keys.h"
#include "sys.h"
#include "sbar.h"
#include "cmd.h"
#include "console.h"
#include "sound.h"
#include "menu.h"

// only the refresh window will be updated unless these variables are flagged
int scr_copytop;
int scr_copyeverything;

float scr_con_current;
float scr_conlines;		// lines of console to display

static float oldscreensize, oldfov;

cvar_t scr_viewsize = { "viewsize", "130", true};
cvar_t scr_fov = { "fov", "90" };	// 10 - 170

cvar_t scr_conspeed = { "scr_conspeed", "300" };
cvar_t scr_centertime = { "scr_centertime", "2" };
cvar_t scr_showram = { "showram", "1" };
cvar_t scr_showturtle = { "showturtle", "0" };
cvar_t scr_showpause = { "showpause", "1" };
cvar_t scr_printspeed = { "scr_printspeed", "8" };

qboolean scr_initialized;	// ready to draw

qpic_t *scr_ram;
qpic_t *scr_net;
qpic_t *scr_turtle;

int scr_fullupdate;
int clearconsole;
int clearnotify;

vrect_t *pconupdate;
vrect_t scr_vrect;

qboolean scr_disabled_for_loading;
qboolean scr_drawloading;
qboolean scr_skipupdate;
float scr_disabled_time;

qboolean block_drawing;

void SCR_ScreenShot_f(void);

/*
===============================================================================

CENTER PRINTING

===============================================================================
*/

char scr_centerstring[1024];
float scr_centertime_start;	// for slow victory printing
float scr_centertime_off;
int scr_center_lines;
int scr_erase_lines;
int scr_erase_center;

/*
==============
SCR_CenterPrint

Called for important messages that should stay in the center of the screen
for a few moments
==============
*/
void
SCR_CenterPrint(char *str)
{
    strncpy(scr_centerstring, str, sizeof(scr_centerstring) - 1);
    scr_centertime_off = scr_centertime.value;
    scr_centertime_start = cl.time;

// count the number of lines for centering
    scr_center_lines = 1;
    while (*str) {
	if (*str == '\n')
	    scr_center_lines++;
	str++;
    }
}


void
SCR_EraseCenterString(void)
{
    int y, height;

    if (scr_erase_center++ > vid.numpages) {
	scr_erase_lines = 0;
	return;
    }

    if (scr_center_lines <= 4)
	y = vid.height * 0.35;
    else
	y = 48;

    /* Make sure we don't draw off the bottom of the screen*/
    height = qmin(8 * scr_erase_lines, ((int)vid.height) - y - 1);

    scr_copytop = 1;
    Draw_TileClear(0, y, vid.width, height);
}


void
SCR_DrawCenterString(void)
{
    char *start;
    int l;
    int j;
    int x, y;
    int remaining;

// the finale prints the characters one at a time
    if (cl.intermission)
	remaining = scr_printspeed.value * (cl.time - scr_centertime_start);
    else
	remaining = 9999;

    scr_erase_center = 0;
    start = scr_centerstring;

    if (scr_center_lines <= 4)
	y = vid.height * 0.35;
    else
	y = 48;

    do {
	// scan the width of the line
	for (l = 0; l < 40; l++)
	    if (start[l] == '\n' || !start[l])
		break;
	x = (vid.width - l * 8) / 2;
	for (j = 0; j < l; j++, x += 8) {
	    Draw_Character(x, y, start[j]);
	    if (!remaining--)
		return;
	}

	y += 8;

	while (*start && *start != '\n')
	    start++;

	if (!*start)
	    break;
	start++;		// skip the \n
    } while (1);
}


void
SCR_CheckDrawCenterString(void)
{
    scr_copytop = 1;
    if (scr_center_lines > scr_erase_lines)
	scr_erase_lines = scr_center_lines;

    scr_centertime_off -= host_frametime;

    if (scr_centertime_off <= 0 && !cl.intermission)
	return;
    if (key_dest != key_game)
	return;

    SCR_DrawCenterString();
}

//=============================================================================

/*
====================
CalcFov
====================
*/
float
CalcFov(float fov_x, float width, float height)
{
    float a;
    float x;

    if (fov_x < 1 || fov_x > 179)
	Sys_Error("Bad fov: %f", fov_x);

    x = width / tan(fov_x / 360 * M_PI);
    a = atan(height / x);
    a = a * 360 / M_PI;

    return a;
}


/*
=================
SCR_CalcRefdef

Must be called whenever vid changes
Internal use only
=================
*/
static void
SCR_CalcRefdef(void)
{
    vrect_t vrect;
    float size;

    scr_fullupdate = 0;		// force a background redraw
    vid.recalc_refdef = 0;

// force the status bar to redraw
    Sbar_Changed();

//========================================

// bound viewsize
    if (scr_viewsize.value < 30)
	Cvar_Set("viewsize", "30");
    if (scr_viewsize.value > 120)
	Cvar_Set("viewsize", "120");

// bound field of view
    // FISHEYE (Deprecate this fov)
    /*
    if (scr_fov.value < 10)
	Cvar_Set("fov", "10");
    if (scr_fov.value > 170)
	Cvar_Set("fov", "170");

    r_refdef.fov_x = scr_fov.value;
    // r_refdef.fov_y =
	 // CalcFov(r_refdef.fov_x, r_refdef.vrect.width, r_refdef.vrect.height);
    r_refdef.fov_y = scr_fov.value;
    */
    r_refdef.fov_x = 90;

// intermission is always full screen
    //if (cl.intermission)
	//size = 120;
    //else
	size = scr_viewsize.value;

    if (size >= 120)
	sb_lines = 0;		// no status bar at all
    else if (size >= 110)
	sb_lines = 24;		// no inventory
    else
	sb_lines = 24 + 16 + 8;

// these calculations mirror those in R_Init() for r_refdef, but take no
// account of water warping
    vrect.x = 0;
    vrect.y = 0;
    vrect.width = vid.width;
    vrect.height = vid.height;

    R_SetVrect(&vrect, &scr_vrect, sb_lines);


// guard against going from one mode to another that's less than half the
// vertical resolution
    if (scr_con_current > vid.height)
	scr_con_current = vid.height;

// notify the refresh of the change
    R_ViewChanged(&vrect, sb_lines, vid.aspect);
}


/*
=================
SCR_SizeUp_f

Keybinding command
=================
*/
void
SCR_SizeUp_f(void)
{
    Cvar_SetValue("viewsize", scr_viewsize.value + 10);
    vid.recalc_refdef = 1;
}


/*
=================
SCR_SizeDown_f

Keybinding command
=================
*/
void
SCR_SizeDown_f(void)
{
    Cvar_SetValue("viewsize", scr_viewsize.value - 10);
    vid.recalc_refdef = 1;
}

//============================================================================

// set flag to take screenshot right after the screen is drawn
// this is to bypass an anomaly with the video buffer getting mangled after VID_Update
// we also added a desire screenshot name
static int screenshot_due = 0;
static int screenshot_bare;
static char screenshot_name[80];
void
SCR_ScreenShot_f_due(void)
{
   screenshot_bare = 0;
   if (Cmd_Argc() < 2) { // no name given
      screenshot_name[0] = 0;
   }
   else {
      strcpy(screenshot_name, Cmd_Argv(1));
      if (Cmd_Argc() > 2) { // bare flag given
         screenshot_bare = Q_atoi(Cmd_Argv(2));
      }
   }
   screenshot_due = 1;
}

/*
==================
SCR_Init
==================
*/
void
SCR_Init(void)
{
    // FISHEYE (Deprecate this fov)
    //Cvar_RegisterVariable(&scr_fov);
    Cvar_RegisterVariable(&scr_viewsize);
    Cvar_RegisterVariable(&scr_conspeed);
    Cvar_RegisterVariable(&scr_showram);
    Cvar_RegisterVariable(&scr_showturtle);
    Cvar_RegisterVariable(&scr_showpause);
    Cvar_RegisterVariable(&scr_centertime);
    Cvar_RegisterVariable(&scr_printspeed);

    Cmd_AddCommand("screenshot", SCR_ScreenShot_f_due);
    Cmd_AddCommand("sizeup", SCR_SizeUp_f);
    Cmd_AddCommand("sizedown", SCR_SizeDown_f);

    scr_ram = Draw_PicFromWad("ram");
    scr_net = Draw_PicFromWad("net");
    scr_turtle = Draw_PicFromWad("turtle");

    scr_initialized = true;
}


/*
==============
SCR_DrawRam
==============
*/
void
SCR_DrawRam(void)
{
    if (!scr_showram.value)
	return;

    if (!r_cache_thrash)
	return;

    Draw_Pic(scr_vrect.x + 32, scr_vrect.y, scr_ram);
}


/*
==============
SCR_DrawTurtle
==============
*/
void
SCR_DrawTurtle(void)
{
    static int count;

    if (!scr_showturtle.value)
	return;

    if (host_frametime < 0.1) {
	count = 0;
	return;
    }

    count++;
    if (count < 3)
	return;

    Draw_Pic(scr_vrect.x, scr_vrect.y, scr_turtle);
}


/*
==============
SCR_DrawNet
==============
*/
void
SCR_DrawNet(void)
{
    if (realtime - cl.last_received_message < 0.3)
	return;
    if (cls.demoplayback)
	return;

    Draw_Pic(scr_vrect.x + 64, scr_vrect.y, scr_net);
}


/*
==============
DrawPause
==============
*/
void
SCR_DrawPause(void)
{
    qpic_t *pic;

    if (!scr_showpause.value)	// turn off for screenshots
	return;

    if (!cl.paused)
	return;

    pic = Draw_CachePic("gfx/pause.lmp");
    Draw_Pic((vid.width - pic->width) / 2,
	     (vid.height - 48 - pic->height) / 2, pic);
}


/*
==============
SCR_DrawLoading
==============
*/
void
SCR_DrawLoading(void)
{
    qpic_t *pic;

    if (!scr_drawloading)
	return;

    pic = Draw_CachePic("gfx/loading.lmp");
    Draw_Pic((vid.width - pic->width) / 2,
	     (vid.height - 48 - pic->height) / 2, pic);
}

//=============================================================================

/*
==================
SCR_SetUpToDrawConsole
==================
*/
void
SCR_SetUpToDrawConsole(void)
{
    Con_CheckResize();

    if (scr_drawloading)
	return;			// never a console with loading plaque

// decide on the height of the console
    con_forcedup = !cl.worldmodel || cls.state != ca_active;

    if (con_forcedup) {
	scr_conlines = vid.height;	// full screen
	scr_con_current = scr_conlines;
    } else if (key_dest == key_console)
	scr_conlines = vid.height / 2;	// half screen
    else
	scr_conlines = 0;	// none visible

    if (scr_conlines < scr_con_current) {
	scr_con_current -= scr_conspeed.value * host_frametime;
	if (scr_conlines > scr_con_current)
	    scr_con_current = scr_conlines;

    } else if (scr_conlines > scr_con_current) {
	scr_con_current += scr_conspeed.value * host_frametime;
	if (scr_conlines < scr_con_current)
	    scr_con_current = scr_conlines;
    }

    if (clearconsole++ < vid.numpages) {
	scr_copytop = 1;
	Draw_TileClear(0, (int)scr_con_current, vid.width,
		       vid.height - (int)scr_con_current);
	Sbar_Changed();
    } else if (clearnotify++ < vid.numpages) {
	scr_copytop = 1;
	Draw_TileClear(0, 0, vid.width, con_notifylines);
    } else
	con_notifylines = 0;
}


/*
==================
SCR_DrawConsole
==================
*/
void
SCR_DrawConsole(void)
{
    if (scr_con_current) {
	scr_copyeverything = 1;
	Con_DrawConsole(scr_con_current);
	clearconsole = 0;
    } else {
	if (key_dest == key_game || key_dest == key_message)
	    Con_DrawNotify();	// only draw notify in game
    }
}


/*
==============================================================================

				SCREEN SHOTS

==============================================================================
*/

typedef struct {
    char manufacturer;
    char version;
    char encoding;
    char bits_per_pixel;
    unsigned short xmin, ymin, xmax, ymax;
    unsigned short hres, vres;
    unsigned char palette[48];
    char reserved;
    char color_planes;
    unsigned short bytes_per_line;
    unsigned short palette_type;
    char filler[58];
    unsigned char data;		// unbounded
} pcx_t;

/*
==============
WritePCXfile
==============
*/
void
WritePCXfile(char *filename, byte *data, int width, int height,
	     int rowbytes, byte *palette)
{
    int i, j, length;
    pcx_t *pcx;
    byte *pack;

    pcx = Hunk_TempAlloc(width * height * 2 + 1000);
    if (pcx == NULL) {
	Con_Printf("SCR_ScreenShot_f: not enough memory\n");
	return;
    }

    pcx->manufacturer = 0x0a;	// PCX id
    pcx->version = 5;		// 256 color
    pcx->encoding = 1;		// uncompressed
    pcx->bits_per_pixel = 8;	// 256 color
    pcx->xmin = 0;
    pcx->ymin = 0;
    pcx->xmax = LittleShort((short)(width - 1));
    pcx->ymax = LittleShort((short)(height - 1));
    pcx->hres = LittleShort((short)width);
    pcx->vres = LittleShort((short)height);
    memset(pcx->palette, 0, sizeof(pcx->palette));
    pcx->color_planes = 1;	// chunky image
    pcx->bytes_per_line = LittleShort((short)width);
    pcx->palette_type = LittleShort(2);	// not a grey scale
    memset(pcx->filler, 0, sizeof(pcx->filler));

// pack the image
    pack = &pcx->data;

    for (i = 0; i < height; i++) {
	for (j = 0; j < width; j++) {
	    if ((*data & 0xc0) != 0xc0)
		*pack++ = *data++;
	    else {
		*pack++ = 0xc1;
		*pack++ = *data++;
	    }
	}

	data += rowbytes - width;
    }

// write the palette
    *pack++ = 0x0c;		// palette ID byte
    for (i = 0; i < 768; i++)
	*pack++ = *palette++;

// write output file
    length = pack - (byte *)pcx;
    COM_WriteFile(filename, pcx, length);
}

/*
==================
SCR_ScreenShot_f
==================
*/
void
SCR_ScreenShot_f(void)
{
    int i;
    char pcxname[80];
    char checkname[MAX_OSPATH];

//
// find a file name to save it to
//
    if (strlen(screenshot_name) > 0) {
       sprintf(pcxname, "%s.pcx", screenshot_name);
    }
    else {
       strcpy(pcxname, "quake00.pcx");

       for (i = 0; i <= 99; i++) {
      pcxname[5] = i / 10 + '0';
      pcxname[6] = i % 10 + '0';
      sprintf(checkname, "%s/%s", com_gamedir, pcxname);
      if (Sys_FileTime(checkname) == -1)
          break;		// file doesn't exist
       }
       if (i == 100) {
      Con_Printf("SCR_ScreenShot_f: Couldn't create a PCX file\n");
      return;
       }
    }
//
// save the pcx file
//
    D_EnableBackBufferAccess();	// enable direct drawing of console to back
    //  buffer

    WritePCXfile(pcxname, vid.buffer, vid.width, vid.height, vid.rowbytes,
		 host_basepal);

    D_DisableBackBufferAccess();	// for adapters that can't stay mapped in
    //  for linear writes all the time

    Con_Printf("Wrote %s\n", pcxname);
}

//=============================================================================

/*
===============
SCR_BeginLoadingPlaque

================
*/
void
SCR_BeginLoadingPlaque(void)
{
    S_StopAllSounds(true);

    if (cls.state != ca_active)
	return;

// redraw with no console and the loading plaque
    Con_ClearNotify();
    scr_centertime_off = 0;
    scr_con_current = 0;

    scr_drawloading = true;
    scr_fullupdate = 0;
    Sbar_Changed();
    SCR_UpdateScreen();
    scr_drawloading = false;

    scr_disabled_for_loading = true;
    scr_disabled_time = realtime;
    scr_fullupdate = 0;
}


/*
===============
SCR_EndLoadingPlaque

================
*/
void
SCR_EndLoadingPlaque(void)
{
    scr_disabled_for_loading = false;
    scr_fullupdate = 0;
    Con_ClearNotify();
}

//=============================================================================

static char *scr_notifystring;
static qboolean scr_drawdialog;

void
SCR_DrawNotifyString(void)
{
    char *start;
    int l;
    int j;
    int x, y;

    start = scr_notifystring;

    y = vid.height * 0.35;

    do {
	// scan the width of the line
	for (l = 0; l < 40; l++)
	    if (start[l] == '\n' || !start[l])
		break;
	x = (vid.width - l * 8) / 2;
	for (j = 0; j < l; j++, x += 8)
	    Draw_Character(x, y, start[j]);

	y += 8;

	while (*start && *start != '\n')
	    start++;

	if (!*start)
	    break;
	start++;		// skip the \n
    } while (1);
}


/*
==================
SCR_ModalMessage

Displays a text string in the center of the screen and waits for a Y or N
keypress.
==================
*/
int
SCR_ModalMessage(char *text)
{
    if (cls.state == ca_dedicated)
	return true;

    scr_notifystring = text;

// draw a fresh screen
    scr_fullupdate = 0;
    scr_drawdialog = true;
    SCR_UpdateScreen();
    scr_drawdialog = false;

    S_ClearBuffer();		// so dma doesn't loop current sound

    do {
	key_count = -1;		// wait for a key down and up
	Sys_SendKeyEvents();
    } while (key_lastpress != 'y' && key_lastpress != 'n'
	     && key_lastpress != K_ESCAPE);

    scr_fullupdate = 0;
    SCR_UpdateScreen();

    return key_lastpress == 'y';
}


//=============================================================================

/*
===============
SCR_BringDownConsole

Brings the console down and fades the palettes back to normal
================
*/
void
SCR_BringDownConsole(void)
{
    int i;

    scr_centertime_off = 0;

    for (i = 0; i < 20 && scr_conlines != scr_con_current; i++)
	SCR_UpdateScreen();

    cl.cshifts[0].percent = 0;	// no area contents palette on next frame
    VID_SetPalette(host_basepal);
}


/*
==================
SCR_UpdateScreen

This is called every frame, and can also be called explicitly to flush
text to the screen.

WARNING: be very careful calling this from elsewhere, because the refresh
needs almost the entire 256k of stack space!
==================
*/
void
SCR_UpdateScreen(void)
{
    static float oldscr_viewsize;
    vrect_t vrect;
    int draw_bare = screenshot_due && screenshot_bare;

    if (scr_skipupdate || block_drawing)
	return;

    scr_copytop = 0;
    scr_copyeverything = 0;

    if (scr_disabled_for_loading) {
	/*
	 * FIXME - this really needs to be fixed properly.
	 * Simply starting a new game and typing "changelevel foo" will hang
	 * the engine for 15s (was 60s!) if foo.bsp does not exist.
	 */
	if (realtime - scr_disabled_time > 15) {
	    scr_disabled_for_loading = false;
	    Con_Printf("load failed.\n");
	} else
	    return;
    }

    if (cls.state == ca_dedicated)
	return;			// stdout only

    if (!scr_initialized || !con_initialized)
	return;			// not initialized yet

    if (scr_viewsize.value != oldscr_viewsize) {
	oldscr_viewsize = scr_viewsize.value;
	vid.recalc_refdef = 1;
    }
//
// check for vid changes
//
    /*
    if (oldfov != scr_fov.value) {
	oldfov = scr_fov.value;
	vid.recalc_refdef = true;
    }
    */

    if (oldscreensize != scr_viewsize.value) {
	oldscreensize = scr_viewsize.value;
	vid.recalc_refdef = true;
    }

    if (vid.recalc_refdef) {
	// something changed, so reorder the screen
	SCR_CalcRefdef();
    }
//
// do 3D refresh drawing, and then update the screen
//
    D_EnableBackBufferAccess();	// of all overlay stuff if drawing directly

    if (scr_fullupdate++ < vid.numpages) {	// clear the entire screen
	scr_copyeverything = 1;
	Draw_TileClear(0, 0, vid.width, vid.height);
	Sbar_Changed();
    }

    pconupdate = NULL;

    SCR_SetUpToDrawConsole();
    SCR_EraseCenterString();

    D_DisableBackBufferAccess();	// for adapters that can't stay mapped in
    //  for linear writes all the time

    VID_LockBuffer();
    V_RenderView();
    VID_UnlockBuffer();

    D_EnableBackBufferAccess();	// of all overlay stuff if drawing directly

    if (scr_drawdialog) {
	Sbar_Draw();
	Draw_FadeScreen();
	if (!draw_bare) SCR_DrawNotifyString();
	scr_copyeverything = true;
    } else if (scr_drawloading) {
	SCR_DrawLoading();
	Sbar_Draw();
    } else if (cl.intermission == 1 && key_dest == key_game) {
	Sbar_IntermissionOverlay();
    } else if (cl.intermission == 2 && key_dest == key_game) {
	Sbar_FinaleOverlay();
	SCR_CheckDrawCenterString();
    } else if (cl.intermission == 3 && key_dest == key_game) {
	SCR_CheckDrawCenterString();
    } else {
	SCR_DrawRam();
	SCR_DrawNet();
	SCR_DrawTurtle();
	SCR_DrawPause();
	if (!draw_bare) SCR_CheckDrawCenterString();
	Sbar_Draw();

   if (!draw_bare) SCR_DrawConsole();
	M_Draw();
    }

    D_DisableBackBufferAccess();	// for adapters that can't stay mapped in
    //  for linear writes all the time
    if (pconupdate) {
	D_UpdateRects(pconupdate);
    }

    V_UpdatePalette();

    // take a screenshot if flagged
    if (screenshot_due)
    {
       SCR_ScreenShot_f();
       screenshot_due = 0;
    }

//
// update one of three areas
//
    if (scr_copyeverything) {
	vrect.x = 0;
	vrect.y = 0;
	vrect.width = vid.width;
	vrect.height = vid.height;
	vrect.pnext = 0;

	VID_Update(&vrect);
    } else if (scr_copytop) {
	vrect.x = 0;
	vrect.y = 0;
	vrect.width = vid.width;
	vrect.height = vid.height - sb_lines;
	vrect.pnext = 0;

	VID_Update(&vrect);
    } else {
	vrect.x = scr_vrect.x;
	vrect.y = scr_vrect.y;
	vrect.width = scr_vrect.width;
	vrect.height = scr_vrect.height;
	vrect.pnext = 0;

	VID_Update(&vrect);
    }

}


/*
==================
SCR_UpdateWholeScreen
==================
*/
void
SCR_UpdateWholeScreen(void)
{
    scr_fullupdate = 0;
    SCR_UpdateScreen();
}
