/*
  Copyright (C) 2010-2011  Charles Pence <charles@charlespence.net>
  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 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://www.gnu.org/licenses/>.
*/
#include "pch.h"
#include "quakedef.h"

/*

background clear
rendering
sbar
centerprint / slow centerprint
notify lines
intermission / finale overlay
loading plaque
console
menu

required background clears
required update regions


syncronous draw mode or async
One off screen buffer, with updates either copied or xblited
Need to double buffer?


async draw will require the refresh area to be cleared, because it will be
xblited, but sync draw can just ignore it.

sync
draw

CenterPrint ()
SlowPrint ()
Screen_Update ();
Con_Printf ();

net
turn off messages option

the refresh is allways rendered, unless the console is full screen


console is:
    notify lines
    half
    full


*/


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

float        oldfov;

qboolean    scr_initialized;        // ready to draw

qboolean    scr_disabled_for_loading;
qboolean    scr_drawloading;
float        scr_disabled_time;

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 = Cvar_VariableValue("scr_centertime");
    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_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 = Cvar_VariableValue("scr_printspeed") * (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)
{
    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)
{
    vid.recalc_refdef = 0;

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

    // bound field of view
    if (Cvar_VariableValue("fov") < 10)
        Cvar_Set ("fov","10");
    else if (Cvar_VariableValue("fov") > 170)
        Cvar_Set ("fov","170");

    // intermission disables the status bar
    sb_lines = 24+16+8;
    if (cl.intermission)
        sb_lines = 0;

    r_refdef.fov_x = Cvar_VariableValue("fov");
    r_refdef.fov_y = CalcFov (r_refdef.fov_x, vid.width, vid.height);
}

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

/*
==================
SCR_Init
==================
*/
void SCR_Init (void)
{
    Cvar_RegisterVariable ("fov", "90");
    Cvar_RegisterVariable ("scr_conspeed", "300");
    Cvar_RegisterVariable ("showpause", "1");
    Cvar_RegisterVariable ("scr_centertime", "2");
    Cvar_RegisterVariable ("scr_printspeed", "8");

//
// register our commands
//
    Cmd_AddCommand ("screenshot",SCR_ScreenShot_f);

    scr_initialized = true;
}

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

    // turn off for screenshots
    if (!Cvar_VariableValue("showpause"))
        return;

    if (!cl.paused)
        return;

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



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

    if (!scr_drawloading)
        return;

    pic = Draw_PicFromFile ("gfx/loading.lmp");
    Draw_Pic ((vid.width - pic->wad_width)/2, (vid.height - 48 - pic->wad_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.signon != SIGNONS;

    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 -= Cvar_VariableValue("scr_conspeed") * host_frametime;
        if (scr_conlines > scr_con_current)
            scr_con_current = scr_conlines;

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

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


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

                        SCREEN SHOTS

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

typedef struct _TargaHeader {
    unsigned char     id_length, colormap_type, image_type;
    unsigned short    colormap_index, colormap_length;
    unsigned char    colormap_size;
    unsigned short    x_origin, y_origin, width, height;
    unsigned char    pixel_size, attributes;
} TargaHeader;


/*
==================
SCR_ScreenShot_f
==================
*/
void SCR_ScreenShot_f (void)
{
    byte        *buffer;
    char        pcxname[80];
    int            i, c, temp;
//
// find a file name to save it to
//
    strcpy(pcxname,"quake00.tga");

    for (i=0 ; i<=99 ; i++)
    {
        pcxname[5] = i/10 + '0';
        pcxname[6] = i%10 + '0';
        if (File::Exists(pcxname) == -1)
            break;    // file doesn't exist
    }
    if (i==100)
    {
        Con_Printf ("SCR_ScreenShot_f: Couldn't create a PCX file\n");
        return;
     }


    buffer = (byte *)malloc(vid.width * vid.height * 3 + 18);
    memset (buffer, 0, 18);
    buffer[2] = 2;        // uncompressed type
    buffer[12] = vid.width & 255;
    buffer[13] = vid.width >> 8;
    buffer[14] = vid.height & 255;
    buffer[15] = vid.height >> 8;
    buffer[16] = 24;    // pixel size

    glReadPixels (0, 0, vid.width, vid.height, GL_RGB, GL_UNSIGNED_BYTE, buffer+18);

    // swap rgb to bgr
    c = 18 + vid.width * vid.height * 3;
    for (i=18 ; i<c ; i+=3)
    {
        temp = buffer[i];
        buffer[i] = buffer[i+2];
        buffer[i+2] = temp;
    }
    
    File fp;
    if (fp.OpenWrite(pcxname))
    {
        fp.Write(buffer, vid.width * vid.height * 3 + 18);
        fp.Close();
        Con_Printf ("Wrote %s\n", pcxname);
    }
    else
        Con_Printf ("Writing %s failed\n", pcxname);

    free (buffer);
}


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


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

================
*/
void SCR_BeginLoadingPlaque (void)
{
    service::ConsoleCommand("stopsound");

    if (cls.state != ca_connected)
        return;
    if (cls.signon != SIGNONS)
        return;

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

    scr_drawloading = true;
    SCR_UpdateScreen ();
    scr_drawloading = false;

    scr_disabled_for_loading = true;
    scr_disabled_time = realtime;
}

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

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

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

char    *scr_notifystring;
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)
{
    scr_notifystring = text;

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

    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_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
}

/*
==================
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)
{
    if (scr_disabled_for_loading)
    {
        if (realtime - scr_disabled_time > 60)
        {
            scr_disabled_for_loading = false;
            Con_Printf ("load failed.\n");
        }
        else
            return;
    }

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

    //
    // determine size of refresh window
    //
    if (oldfov != Cvar_VariableValue("fov"))
    {
        oldfov = Cvar_VariableValue("fov");
        vid.recalc_refdef = true;
    }

    if (vid.recalc_refdef)
        SCR_CalcRefdef ();

//
// do 3D refresh drawing, and then update the screen
//
    SCR_SetUpToDrawConsole ();

    V_RenderView ();

    GL_Set2D ();

    if (scr_drawdialog)
    {
        Sbar_Draw ();
        Draw_FadeScreen ();
        SCR_DrawNotifyString ();
    }
    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 (Cvar_VariableValue("crosshair"))
            Draw_Character (vid.width/2 - 4, vid.height/2 - 4, '+');

        SCR_DrawPause ();
        SCR_CheckDrawCenterString ();
        Sbar_Draw ();
        SCR_DrawConsole ();
        M_Draw ();
    }

    V_UpdatePalette ();

    GL_EndRendering ();
}

