/*
 * 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.
 *
 */
// r_screen.c -- master for refresh, status bar, console, chat, notify, etc

#include "quakedef.h"
#include "r_local.h"
#ifdef _WIN32
#  include "movie.h"
#endif

// 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

float         oldscreensize, oldfov;
float         oldsbar;
cvar_t        scr_viewsize = { "viewsize", "120", CVAR_ARCHIVE };
cvar_t        scr_fov = { "fov", "90", CVAR_ARCHIVE };  // 10 - 170
cvar_t        scr_consize = { "scr_consize", "0.5", CVAR_ARCHIVE };
cvar_t        scr_conspeed = { "scr_conspeed", "1000" };
cvar_t        scr_conswitch = { "scr_conswitch", "0", CVAR_ARCHIVE };
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" };
cvar_t        scr_sshot_format = { "scr_sshot_format", "png" };

extern cvar_t crosshair;

qboolean      scr_initialized;         // ready to draw

mpic_t       *scr_ram;
mpic_t       *scr_net;
mpic_t       *scr_turtle;

int           scr_fullupdate;

int           clearconsole;
int           clearnotify;

viddef_t      vid;                     // global video state

vrect_t       scr_vrect;

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

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;

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

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

    scr_copytop = 1;
    Draw_TileClear (0, y, vid.width, 8 * scr_erase_lines);
}

void SCR_DrawCenterString (void) {
    char         *start;
    int           l, j, x, y, 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, 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_SetValue (&scr_viewsize, 30);
    if (scr_viewsize.value > 120)
        Cvar_SetValue (&scr_viewsize, 120);

    // bound field of view
    if (scr_fov.value < 10)
        Cvar_SetValue (&scr_fov, 10);
    if (scr_fov.value > 170)
        Cvar_SetValue (&scr_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);

    // 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 (&scr_viewsize, scr_viewsize.value + 10);
    vid.recalc_refdef = 1;
}

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

// 

/*
 * SCR_Init
 */
void SCR_Init (void) {
    Cvar_Register (&scr_fov);
    Cvar_Register (&scr_viewsize);
    Cvar_Register (&scr_consize);
    Cvar_Register (&scr_conspeed);
    Cvar_Register (&scr_conswitch);
    Cvar_Register (&scr_showram);
    Cvar_Register (&scr_showturtle);
    Cvar_Register (&scr_showpause);
    Cvar_Register (&scr_centertime);
    Cvar_Register (&scr_printspeed);
    Cvar_Register (&scr_sshot_format);

    // register our commands
    Cmd_AddCommand ("screenshot", SCR_ScreenShot_f);
    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");

#ifdef _WIN32
    Movie_Init ();
#endif

    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);
}

/*
 * SCR_DrawPause
 */
void SCR_DrawPause (void) {
    mpic_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) {
    mpic_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) {
    float         conspeed;

    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) {
        if (scr_conswitch.value && vid.width > 640)
            scr_conlines = vid.height;
        else
            scr_conlines = vid.height * scr_consize.value;
        if (scr_conlines < 30)
            scr_conlines = 30;
        if (scr_conlines > vid.height)
            scr_conlines = vid.height;
    } else {
        scr_conlines = 0;              // none visible
    }

    conspeed = scr_conspeed.value;
    if (scr_conswitch.value && vid.width > 640)
        conspeed *= 2;

    if (scr_conlines < scr_con_current) {
        scr_con_current -= conspeed * host_frametime;
        if (scr_conlines > scr_con_current)
            scr_con_current = scr_conlines;

    } else if (scr_conlines > scr_con_current) {
        scr_con_current += conspeed * 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, true);
        clearconsole = 0;
    } else {
        if (key_dest == key_game || key_dest == key_message)
            Con_DrawNotify ();         // only draw notify in game
    }
}

/*
 *
 * SCREEN SHOTS
 *
 *
 */

int SCR_ScreenShot (char *name) {
    qboolean      ok = false;
    char         *ext;

    D_EnableBackBufferAccess ();       // enable direct drawing of console to
    // back buffer

    ext = COM_FileExtension (name);

    if (!Q_strcasecmp (ext, "png"))
        ok = Image_WritePNGPLTE (name, png_compression_level.value, vid.buffer, vid.width, vid.height, vid.rowbytes,
                                 current_pal);
    else
        ok = Image_WritePCX (name, vid.buffer, vid.width, vid.height, vid.rowbytes, current_pal);

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

    return ok;
}

/*
 * SCR_ScreenShot_f
 */
void SCR_ScreenShot_f (void) {
    int           i, success;
    char          name[MAX_OSPATH], ext[4], *sshot_dir = "shots";

    if (Cmd_Argc () == 2) {
        Q_strncpyz (name, Cmd_Argv (1), sizeof (name));
    } else if (Cmd_Argc () == 1) {
        // find a file name to save it to
        if (!Q_strcasecmp (scr_sshot_format.string, "png"))
            Q_strncpyz (ext, "png", 4);
        else
            Q_strncpyz (ext, "pcx", 4);

        for (i = 0; i < 999; i++) {
            Q_snprintfz (name, sizeof (name), "%s%03i.%s", ENGINE_FSNAME, i, ext);
            if (Sys_FileTime (va ("%s/%s", sshot_dir, name)) == -1)
                break;                 // file doesn't exist
        }

        if (i == 1000) {
            Con_Printf ("Error: Cannot create more than 1000 screenshots\n");
            return;
        }
    } else {
        Con_Printf ("Usage: %s [filename]", Cmd_Argv (0));
        return;
    }

    success = SCR_ScreenShot (va ("%s/%s", sshot_dir, name));
    Con_Printf ("%s %s\n", success ? "Wrote" : "Couldn't write", name);
}

// 

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

    if (cls.state != ca_connected || 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_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 ();
}

// 

char         *scr_notifystring;
qboolean      scr_drawdialog;

void SCR_DrawNotifyString (void) {
    char         *start;
    int           l, j, 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_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;
    static float  oldlcd_x;
    vrect_t       vrect;

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

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

    if (scr_skipupdate || block_drawing)
        return;

    if (scr_disabled_for_loading) {
        if (realtime - scr_disabled_time > 60)
            scr_disabled_for_loading = false;
        else
            return;
    }
#ifdef _WIN32
    {                                  // don't suck up any cpu if minimized
        extern int    Minimized;

        if (Minimized)
            return;
    }
#endif

    scr_copytop = 0;
    scr_copyeverything = 0;

    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 (oldlcd_x != lcd_x.value) {
        oldlcd_x = lcd_x.value;
        vid.recalc_refdef = true;
    }

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

    if (oldsbar != cl_sbar.value) {
        oldsbar = cl_sbar.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 ();
    }

    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 ();
        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 {
        Draw_Crosshair ();
        SCR_DrawRam ();
        SCR_DrawNet ();
        SCR_DrawTurtle ();
        SCR_DrawPause ();
        SCR_CheckDrawCenterString ();
        SCR_DrawClock ();
        SCR_DrawFPS ();
        SCR_DrawSpeed ();
        SCR_DrawStats ();
        SCR_DrawVolume ();
        Sbar_Draw ();
        SCR_DrawConsole ();
        M_Draw ();
    }

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

    V_UpdatePalette ();

    // 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);
    }

#ifdef _WIN32
    Movie_UpdateScreen ();
#endif
}

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