/*
Copyright (C) 1997-2001 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.

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

#include <alias/ui.h>

/*

  full screen console
  put up loading plaque
  blanked background with loading plaque
  blanked background with menu
  cinematics
  full screen image for quit and victory

  end of unit intermissions

  */

#include "client.h"

float scr_con_current; // aproaches scr_conlines at scr_conspeed
float scr_conlines;    // 0.0 to 1.0 lines of console to display

bool scr_initialized; // ready to draw

int scr_draw_loading;

cvar_t *scr_conspeed;
cvar_t *scr_centertime;
cvar_t *scr_showturtle;
cvar_t *scr_showpause;
cvar_t *scr_printspeed;

cvar_t *scr_netgraph;
cvar_t *scr_timegraph;
cvar_t *scr_debuggraph;
cvar_t *scr_graphheight;
cvar_t *scr_graphscale;
cvar_t *scr_graphshift;

char crosshair_pic[MAX_QPATH];
int crosshair_width, crosshair_height;

void SCR_TimeRefresh_f(void);
void SCR_Loading_f(void);

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

BAR GRAPHS

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

/*
==============
CL_AddNetgraph

A new packet was just parsed
==============
*/
void CL_AddNetgraph(void) {
  int i;
  int in;
  int ping;

  // if using the debuggraph for something else, don't
  // add the net lines
  if(scr_debuggraph->value || scr_timegraph->value)
    return;

  for(i = 0; i < cls.netchan.dropped; i++)
    SCR_DebugGraph(30, 0x40);

  for(i = 0; i < cl.surpressCount; i++)
    SCR_DebugGraph(30, 0xdf);

  // see what the latency was on this packet
  in = cls.netchan.incoming_acknowledged & (CMD_BACKUP - 1);
  ping = cls.realtime - cl.cmd_time[in];
  ping /= 30;
  if(ping > 30)
    ping = 30;
  SCR_DebugGraph(ping, 0xd0);
}

typedef struct {
  float value;
  int color;
} graphsamp_t;

static int current;
static graphsamp_t values[1024];

/*
==============
SCR_DebugGraph
==============
*/
void SCR_DebugGraph(float value, int color) {
  values[current & 1023].value = value;
  values[current & 1023].color = color;
  current++;
}

/*
==============
SCR_DrawDebugGraph
==============
*/
void SCR_DrawDebugGraph(void) {
  int a, x, y, w, i, h;
  float v;
  int color;

  //
  // draw the graph
  //
  w = viddef.width;

  x = 0;
  y = viddef.height;
  re.DrawFill(x, y - scr_graphheight->value, w, scr_graphheight->value, 8);

  for(a = 0; a < w; a++) {
    i = (current - 1 - a + 1024) & 1023;
    v = values[i].value;
    color = values[i].color;
    v = v * scr_graphscale->value + scr_graphshift->value;

    if(v < 0)
      v += scr_graphheight->value * (1 + (int)(-v / scr_graphheight->value));
    h = (int)v % (int)scr_graphheight->value;
    re.DrawFill(x + w - 1 - a, y - h, 1, h, color);
  }
}

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

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_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) {
  char *s;
  char line[64];
  int i, j, l;

  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;
  s = str;
  while(*s) {
    if(*s == '\n')
      scr_center_lines++;
    s++;
  }

  // echo it to the console
  Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
             "\36\36\36\37\n\n");

  s = str;
  do {
    // scan the width of the line
    for(l = 0; l < 40; l++)
      if(s[l] == '\n' || !s[l])
        break;
    for(i = 0; i < (40 - l) / 2; i++)
      line[i] = ' ';

    for(j = 0; j < l; j++) {
      line[i++] = s[j];
    }

    line[i] = '\n';
    line[i + 1] = 0;

    Com_Printf("%s", line);

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

    if(!*s)
      break;
    s++; // skip the \n
  } while(1);
  Com_Printf("\n\n\35\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36\36"
             "\36\36\36\37\n\n");
  Con_ClearNotify();
}

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

  // the finale prints the characters one at a time
  remaining = 9999;

  scr_erase_center = 0;
  start = scr_centerstring;

  if(scr_center_lines <= 4)
    y = viddef.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 = (viddef.width - l * 8) / 2;
    for(j = 0; j < l; j++, x += 8) {
      re.DrawChar(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_centertime_off -= cls.frametime;

  if(scr_centertime_off <= 0)
    return;

  SCR_DrawCenterString();
}

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

/*
=================
SCR_Sky_f

Set a specific sky and rotation speed
=================
*/
void SCR_Sky_f(void) {
  float rotate;
  vec3_t axis;

  if(Cmd_Argc() < 2) {
    Com_Printf("Usage: sky <basename> <rotate> <axis x y z>\n");
    return;
  }
  if(Cmd_Argc() > 2)
    rotate = atof(Cmd_Argv(2));
  else
    rotate = 0;
  if(Cmd_Argc() == 6) {
    axis[0] = atof(Cmd_Argv(3));
    axis[1] = atof(Cmd_Argv(4));
    axis[2] = atof(Cmd_Argv(5));
  } else {
    axis[0] = 0;
    axis[1] = 0;
    axis[2] = 1;
  }

  re.SetSky(0, Cmd_Argv(1), rotate, axis);
}

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

/*
==================
SCR_Init
==================
*/
void SCR_Init(void) {
  scr_conspeed = Cvar_Get("scr_conspeed", "3", 0);
  scr_showturtle = Cvar_Get("scr_showturtle", "0", 0);
  scr_showpause = Cvar_Get("scr_showpause", "1", 0);
  scr_centertime = Cvar_Get("scr_centertime", "2.5", 0);
  scr_printspeed = Cvar_Get("scr_printspeed", "8", 0);
  scr_netgraph = Cvar_Get("netgraph", "0", 0);
  scr_timegraph = Cvar_Get("timegraph", "0", 0);
  scr_debuggraph = Cvar_Get("debuggraph", "0", 0);
  scr_graphheight = Cvar_Get("graphheight", "32", 0);
  scr_graphscale = Cvar_Get("graphscale", "1", 0);
  scr_graphshift = Cvar_Get("graphshift", "0", 0);

  //
  // register our commands
  //
  Cmd_AddCommand("timerefresh", SCR_TimeRefresh_f);
  Cmd_AddCommand("loading", SCR_Loading_f);
  Cmd_AddCommand("sky", SCR_Sky_f);

  scr_initialized = true;
}

/*
==============
SCR_DrawNet
==============
*/
void SCR_DrawNet(void) {
  if(cls.netchan.outgoing_sequence - cls.netchan.incoming_acknowledged < CMD_BACKUP - 1)
    return;

  re.DrawPic(64, 0, "net");
}

/*
==============
SCR_DrawPause
==============
*/
void SCR_DrawPause(void) {
  if(!scr_showpause->value) // turn off for screenshots
    return;

  if(!cl_paused->value)
    return;

  UI_Align(0.5f, 0.5f);
  UI_Picture("pause");
  // re.DrawGetPicSize(&w, &h, "pause");
  // re.DrawPic((viddef.width - w) / 2, viddef.height / 2 + 8, "pause");
}

/*
==============
SCR_DrawLoading
==============
*/
void SCR_DrawLoading(void) {
  if(!scr_draw_loading)
    return;

  scr_draw_loading = false;

  UI_Align(0.5f, 0.5f);
  UI_Picture("loading");
  // re.DrawGetPicSize(&w, &h, "loading");
  // re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "loading");
}

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

/*
==================
SCR_RunConsole

Scroll it up or down
==================
*/
void SCR_RunConsole(void) {
  // decide on the height of the console
  if(cls.key_dest == key_console)
    scr_conlines = 0.5; // half screen
  else
    scr_conlines = 0; // none visible

  if(scr_conlines < scr_con_current) {
    scr_con_current -= scr_conspeed->value * cls.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 * cls.frametime;
    if(scr_conlines < scr_con_current)
      scr_con_current = scr_conlines;
  }
}

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

  if(cls.state == ca_disconnected || cls.state == ca_connecting) { // forced full screen console
    Con_DrawConsole(1.0);
    return;
  }

  if(cls.state != ca_active || !cl.refresh_prepped) { // connected, but can't render
    Con_DrawConsole(0.5);
    re.DrawFill(0, viddef.height / 2, viddef.width, viddef.height / 2, 0);
    return;
  }

  if(scr_con_current) {
    Con_DrawConsole(scr_con_current);
  } else {
    if(cls.key_dest == key_game || cls.key_dest == key_message)
      Con_DrawNotify(); // only draw notify in game
  }
}

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

/*
================
SCR_BeginLoadingPlaque
================
*/
void SCR_BeginLoadingPlaque(void) {
  S_StopAllSounds();
  cl.sound_prepped = false; // don't play ambients
  // CDAudio_Stop ();
  if(cls.disable_screen)
    return;
  if(developer->value)
    return;
  if(cls.state == ca_disconnected)
    return; // if at console, don't bring up the plaque
  if(cls.key_dest == key_console)
    return;
  if(cl.cinematictime > 0)
    scr_draw_loading = 2; // clear to balack first
  else
    scr_draw_loading = 1;
  SCR_UpdateScreen();
  cls.disable_screen = Sys_Milliseconds();
  cls.disable_servercount = cl.servercount;
}

/*
================
SCR_EndLoadingPlaque
================
*/
void SCR_EndLoadingPlaque(void) {
  cls.disable_screen = 0;
  Con_ClearNotify();
}

/*
================
SCR_Loading_f
================
*/
void SCR_Loading_f(void) { SCR_BeginLoadingPlaque(); }

/*
================
SCR_TimeRefresh_f
================
*/
int entitycmpfnc(const entity_t *a, const entity_t *b) {
  /*
  ** all other models are sorted by model then skin
  */
  if(a->model == b->model) {
    return ((intptr_t)a->skin - (intptr_t)b->skin);
  } else {
    return ((intptr_t)a->model - (intptr_t)b->model);
  }
}

void SCR_TimeRefresh_f(void) {
  int i;
  int start, stop;
  float time;

  if(cls.state != ca_active)
    return;

  start = Sys_Milliseconds();

  if(Cmd_Argc() == 2) { // run without page flipping
    re.BeginFrame(0);
    for(i = 0; i < 128; i++) {
      cl.refdef.viewangles[1] = i / 128.0 * 360.0;
      re.RenderFrame(&cl.refdef);
    }
    re.EndFrame();
  } else {
    for(i = 0; i < 128; i++) {
      cl.refdef.viewangles[1] = i / 128.0 * 360.0;

      re.BeginFrame(0);
      re.RenderFrame(&cl.refdef);
      re.EndFrame();
    }
  }

  stop = Sys_Milliseconds();
  time = (stop - start) / 1000.0;
  Com_Printf("%f seconds (%f fps)\n", time, 128 / time);
}

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

#define STAT_MINUS 10 // num frame for '-' stats digit
char *sb_nums[2][11] = {
    {"num_0", "num_1", "num_2", "num_3", "num_4", "num_5", "num_6", "num_7", "num_8", "num_9", "num_minus"},
    {"anum_0", "anum_1", "anum_2", "anum_3", "anum_4", "anum_5", "anum_6", "anum_7", "anum_8", "anum_9", "anum_minus"}};

#define ICON_WIDTH 24
#define ICON_HEIGHT 24
#define CHAR_WIDTH 16
#define ICON_SPACE 8

/*
================
SizeHUDString

Allow embedded \n in the string
================
*/
void SizeHUDString(char *string, int *w, int *h) {
  int lines, width, current;

  lines = 1;
  width = 0;

  current = 0;
  while(*string) {
    if(*string == '\n') {
      lines++;
      current = 0;
    } else {
      current++;
      if(current > width)
        width = current;
    }
    string++;
  }

  *w = width * 8;
  *h = lines * 8;
}

void DrawHUDString(char *string, int x, int y, int centerwidth, int xor) {
  int margin;
  char line[1024];
  int width;
  int i;

  margin = x;

  while(*string) {
    // scan out one line of text from the string
    width = 0;
    while(*string && *string != '\n')
      line[width++] = *string++;
    line[width] = 0;

    if(centerwidth)
      x = margin + (centerwidth - width * 8) / 2;
    else
      x = margin;
    for(i = 0; i < width; i++) {
      re.DrawChar(x, y, line[i] ^ xor);
      x += 8;
    }
    if(*string) {
      string++; // skip the \n
      x = margin;
      y += 8;
    }
  }
}

/*
==============
SCR_DrawField
==============
*/
void SCR_DrawField(int x, int y, int color, int width, int value) {
  char num[16], *ptr;
  int l;
  int frame;

  if(width < 1)
    return;

  // draw number string
  if(width > 5)
    width = 5;

  Com_sprintf(num, sizeof(num), "%i", value);
  l = strlen(num);
  if(l > width)
    l = width;
  x += 2 + CHAR_WIDTH * (width - l);

  ptr = num;
  while(*ptr && l) {
    if(*ptr == '-')
      frame = STAT_MINUS;
    else
      frame = *ptr - '0';

    re.DrawPic(x, y, sb_nums[color][frame]);
    x += CHAR_WIDTH;
    ptr++;
    l--;
  }
}

/*
===============
SCR_TouchPics

Allows rendering code to cache all needed sbar graphics
===============
*/
void SCR_TouchPics(void) {
  int i, j;

  for(i = 0; i < 2; i++)
    for(j = 0; j < 11; j++)
      re.RegisterPic(sb_nums[i][j]);

  if(crosshair->value) {
    if(crosshair->value > 3 || crosshair->value < 0)
      crosshair->value = 3;

    Com_sprintf(crosshair_pic, sizeof(crosshair_pic), "ch%i", (int)(crosshair->value));
    re.DrawGetPicSize(&crosshair_width, &crosshair_height, crosshair_pic);
    if(!crosshair_width)
      crosshair_pic[0] = 0;
  }
}

/*
================
SCR_ExecuteLayoutString

================
*/
void SCR_ExecuteLayoutString(const char *s) {
  int x, y;
  int value;
  char *token;
  int width;
  int index;
  clientinfo_t *ci;

  if(cls.state != ca_active || !cl.refresh_prepped)
    return;

  if(!s[0])
    return;

  x = 0;
  y = 0;
  width = 3;

  while(s) {
    token = COM_Parse(&s);
    if(!strcmp(token, "xl")) {
      token = COM_Parse(&s);
      x = atoi(token);
      continue;
    }
    if(!strcmp(token, "xr")) {
      token = COM_Parse(&s);
      x = viddef.width + atoi(token);
      continue;
    }
    if(!strcmp(token, "xv")) {
      token = COM_Parse(&s);
      x = viddef.width / 2 - 160 + atoi(token);
      continue;
    }

    if(!strcmp(token, "yt")) {
      token = COM_Parse(&s);
      y = atoi(token);
      continue;
    }
    if(!strcmp(token, "yb")) {
      token = COM_Parse(&s);
      y = viddef.height + atoi(token);
      continue;
    }
    if(!strcmp(token, "yv")) {
      token = COM_Parse(&s);
      y = viddef.height / 2 - 120 + atoi(token);
      continue;
    }

    if(!strcmp(token, "pic")) { // draw a pic from a stat number
      token = COM_Parse(&s);
      value = cl.frame.playerstate.stats[atoi(token)];
      if(value >= MAX_IMAGES)
        Com_Error(ERR_DROP, "Pic >= MAX_IMAGES");
      if(cl.configstrings[CS_IMAGES + value][0] != 0) {
        re.DrawPic(x, y, cl.configstrings[CS_IMAGES + value]);
      }
      continue;
    }

    if(!strcmp(token, "client")) { // draw a deathmatch client block
      int score, ping, time;

      token = COM_Parse(&s);
      x = viddef.width / 2 - 160 + atoi(token);
      token = COM_Parse(&s);
      y = viddef.height / 2 - 120 + atoi(token);

      token = COM_Parse(&s);
      value = atoi(token);
      if(value >= MAX_CLIENTS || value < 0)
        Com_Error(ERR_DROP, "client >= MAX_CLIENTS");
      ci = &cl.clientinfo[value];

      token = COM_Parse(&s);
      score = atoi(token);

      token = COM_Parse(&s);
      ping = atoi(token);

      token = COM_Parse(&s);
      time = atoi(token);

      DrawAltString(x + 32, y, ci->name);
      DrawString(x + 32, y + 8, "Score: ");
      DrawAltString(x + 32 + 7 * 8, y + 8, va("%i", score));
      DrawString(x + 32, y + 16, va("Ping:  %i", ping));
      DrawString(x + 32, y + 24, va("Time:  %i", time));

      if(!ci->icon)
        ci = &cl.baseclientinfo;
      re.DrawPic(x, y, ci->iconname);
      continue;
    }

    if(!strcmp(token, "ctf")) { // draw a ctf client block
      int score, ping;
      char block[80];

      token = COM_Parse(&s);
      x = viddef.width / 2 - 160 + atoi(token);
      token = COM_Parse(&s);
      y = viddef.height / 2 - 120 + atoi(token);

      token = COM_Parse(&s);
      value = atoi(token);
      if(value >= MAX_CLIENTS || value < 0)
        Com_Error(ERR_DROP, "client >= MAX_CLIENTS");
      ci = &cl.clientinfo[value];

      token = COM_Parse(&s);
      score = atoi(token);

      token = COM_Parse(&s);
      ping = atoi(token);
      if(ping > 999)
        ping = 999;

      sprintf(block, sizeof(block), "%3d %3d %-12.12s", score, ping, ci->name);

      if(value == cl.playernum)
        DrawAltString(x, y, block);
      else
        DrawString(x, y, block);
      continue;
    }

    if(!strcmp(token, "picn")) { // draw a pic from a name
      token = COM_Parse(&s);
      re.DrawPic(x, y, token);
      continue;
    }

    if(!strcmp(token, "num")) { // draw a number
      token = COM_Parse(&s);
      width = atoi(token);
      token = COM_Parse(&s);
      value = cl.frame.playerstate.stats[atoi(token)];
      SCR_DrawField(x, y, 0, width, value);
      continue;
    }

    if(!strcmp(token, "hnum")) { // health number
      int color;

      width = 3;
      value = cl.frame.playerstate.stats[STAT_HEALTH];
      if(value > 25)
        color = 0; // green
      else if(value > 0)
        color = (cl.frame.serverframe >> 2) & 1; // flash
      else
        color = 1;

      if(cl.frame.playerstate.stats[STAT_FLASHES] & 1)
        re.DrawPic(x, y, "field_3");

      SCR_DrawField(x, y, color, width, value);
      continue;
    }

    if(!strcmp(token, "anum")) { // ammo number
      int color;

      width = 3;
      value = cl.frame.playerstate.stats[STAT_AMMO];
      if(value > 5)
        color = 0; // green
      else if(value >= 0)
        color = (cl.frame.serverframe >> 2) & 1; // flash
      else
        continue; // negative number = don't show

      if(cl.frame.playerstate.stats[STAT_FLASHES] & 4)
        re.DrawPic(x, y, "field_3");

      SCR_DrawField(x, y, color, width, value);
      continue;
    }

    if(!strcmp(token, "rnum")) { // armor number
      int color;

      width = 3;
      value = cl.frame.playerstate.stats[STAT_ARMOR];
      if(value < 1)
        continue;

      color = 0; // green

      if(cl.frame.playerstate.stats[STAT_FLASHES] & 2)
        re.DrawPic(x, y, "field_3");

      SCR_DrawField(x, y, color, width, value);
      continue;
    }

    if(!strcmp(token, "stat_string")) {
      token = COM_Parse(&s);
      index = atoi(token);
      if(index < 0 || index >= MAX_CONFIGSTRINGS)
        Com_Error(ERR_DROP, "Bad stat_string index");
      index = cl.frame.playerstate.stats[index];
      if(index < 0 || index >= MAX_CONFIGSTRINGS)
        Com_Error(ERR_DROP, "Bad stat_string index");
      DrawString(x, y, cl.configstrings[index]);
      continue;
    }

    if(!strcmp(token, "cstring")) {
      token = COM_Parse(&s);
      DrawHUDString(token, x, y, 320, 0);
      continue;
    }

    if(!strcmp(token, "string")) {
      token = COM_Parse(&s);
      DrawString(x, y, token);
      continue;
    }

    if(!strcmp(token, "cstring2")) {
      token = COM_Parse(&s);
      DrawHUDString(token, x, y, 320, 0x80);
      continue;
    }

    if(!strcmp(token, "string2")) {
      token = COM_Parse(&s);
      DrawAltString(x, y, token);
      continue;
    }

    if(!strcmp(token, "if")) { // draw a number
      token = COM_Parse(&s);
      value = cl.frame.playerstate.stats[atoi(token)];
      if(!value) { // skip to endif
        while(s && strcmp(token, "endif")) {
          token = COM_Parse(&s);
        }
      }

      continue;
    }
  }
}

/*
================
SCR_DrawStats

The status bar is a small layout program that
is based on the stats array
================
*/
void SCR_DrawStats(void) { SCR_ExecuteLayoutString(cl.configstrings[CS_STATUSBAR]); }

/*
================
SCR_DrawLayout

================
*/
#define STAT_LAYOUTS 13

void SCR_DrawLayout(void) {
  if(!cl.frame.playerstate.stats[STAT_LAYOUTS])
    return;
  SCR_ExecuteLayoutString(cl.layout);
}

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

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

This is called every frame, and can also be called explicitly to flush
text to the screen.
==================
*/
static alias_ui_OutputGroup ui_output_groups[1024];
static uint32_t ui_output_indexes[1 << 10];
static struct DrawVertex ui_output_vertexes[1 << 10];
static struct BaseImage *ui_frame_images[1024];
static alias_ui *frame_ui = NULL;

uint32_t UI_GetTextureIndex(const char *name) {
  uint32_t i;
  for(i = 0; ui_frame_images[i]; i++) {
    if(strcmp(name, ui_frame_images[i]->name) == 0) {
      return i;
    }
  }
  ui_frame_images[i] = re.RegisterPic(name);
  ui_frame_images[i + 1] = NULL;
  return i;
}

void UI_SetTexture(const char *name) { alias_ui_set_texture(frame_ui, UI_GetTextureIndex(name)); }

void UI_Align(float align_x, float align_y) { alias_ui_align(frame_ui, align_x, align_y); }

void UI_Horizontal(void) { alias_ui_begin_horizontal_stack(frame_ui); }
void UI_Vertical(void) { alias_ui_begin_vertical_stack(frame_ui); }
void UI_End(void) { alias_ui_end_stack(frame_ui); }

void UI_ForceSize(float w, float h) { alias_ui_size(frame_ui, w, h); }

void UI_ForceWidth(float w) { alias_ui_width(frame_ui, w); }
void UI_ForceHeight(float h) { alias_ui_height(frame_ui, h); }

void UI_Picture(const char *name, ...) {
  char path[MAX_QPATH];

  va_list ap;
  va_start(ap, name);
  vsprintf(path, sizeof(path), name, ap);
  va_end(ap);

  int index = UI_GetTextureIndex(path);
  const struct BaseImage *image = ui_frame_images[index];
  if(image == NULL)
    return;
  alias_ui_image(frame_ui, image->width, image->height, image->s0, image->t0, image->s1, image->t1, index);
}

void UI_Fill(float r, float g, float b, float a) {
  alias_ui_color_fill(frame_ui, r, g, b, a);
}

static void text_size(alias_ui *ui, const char *buffer, alias_R size, alias_R max_width, alias_R *out_width,
                      alias_R *out_height) {
  *out_width = strlen(buffer) * 8;
  *out_height = 8;
}

static void text_draw(alias_ui *ui, const char *buffer, alias_R x, alias_R y, alias_R width, alias_R size,
                      alias_Color color) {
  UI_SetTexture("conchars");

  for(int i = 0; buffer[i]; i++, x += 8) {
    int num = buffer[i];

    int row, col;
    float frow, fcol, size;

    num &= 255;

    if((num & 127) == 32)
      continue; // space

    if(y <= -8)
      continue; // totally off screen

    row = num >> 4;
    col = num & 15;

    frow = row * 0.0625;
    fcol = col * 0.0625;
    size = 0.0625;

    alias_ui_draw_rectangle(ui, x, y, 8, 8, fcol, frow, fcol + size, frow + size, color.r, color.g, color.b, color.a);
  }
}

void UI_Text(const char *format, ...) {
  va_list ap;
  va_start(ap, format);
  alias_ui_textv(frame_ui, format, ap);
  va_end(ap);
}

void SCR_UpdateScreen(void) {
  void SNDDMA_DrawStats(void);

  // if the screen is disabled (loading plaque is up, or vid mode changing)
  // do nothing at all
  if(cls.disable_screen) {
    if(Sys_Milliseconds() - cls.disable_screen > 120000) {
      cls.disable_screen = 0;
      Com_Printf("Loading plaque timed out.\n");
    }
    return;
  }

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

  if(frame_ui == NULL) {
    alias_ui_initialize(alias_default_MemoryCB(), &frame_ui);
  }

  alias_ui_Input ui_input = {
      .screen_size = {.width = viddef.width, .height = viddef.height}, .text_draw = text_draw, .text_size = text_size};

  ui_frame_images[0] = NULL;
  UI_GetTextureIndex("white");

  alias_ui_begin_frame(frame_ui, alias_default_MemoryCB(), &ui_input);

  alias_ui_begin_stack(frame_ui);

  re.BeginFrame(0.0f);

  if(scr_draw_loading == 2) { //  loading plaque over black screen
    re.CinematicSetPalette(NULL);
    scr_draw_loading = false;

    UI_Align(0.5f, 0.5f);
    UI_Picture("loading");
    // re.DrawGetPicSize(&w, &h, "loading");
    // re.DrawPic((viddef.width - w) / 2, (viddef.height - h) / 2, "loading");
    //			re.EndFrame();
    //			return;
  }
  // if a cinematic is supposed to be running, handle menus
  // and console specially
  else if(cl.cinematictime > 0) {
    if(cls.key_dest == key_menu) {
      if(cl.cinematicpalette_active) {
        re.CinematicSetPalette(NULL);
        cl.cinematicpalette_active = false;
      }
      M_Draw();
    } else if(cls.key_dest == key_console) {
      if(cl.cinematicpalette_active) {
        re.CinematicSetPalette(NULL);
        cl.cinematicpalette_active = false;
      }
      SCR_DrawConsole();
    } else {
      SCR_DrawCinematic();
    }
  } else {

    // make sure the game palette is active
    if(cl.cinematicpalette_active) {
      re.CinematicSetPalette(NULL);
      cl.cinematicpalette_active = false;
    }

    // clear any dirty part of the background
    // SCR_TileClear();

    V_RenderView(0.0f);

    SCR_DrawStats();
    if(cl.frame.playerstate.stats[STAT_LAYOUTS] & 1)
      SCR_DrawLayout();

    if(cl.frame.playerstate.stats[STAT_LAYOUTS] & 2)
      CL_DrawInventory();

    SCR_DrawNet();
    SCR_CheckDrawCenterString();

    if(scr_timegraph->value)
      SCR_DebugGraph(cls.frametime * 300, 0);

    if(scr_debuggraph->value || scr_timegraph->value || scr_netgraph->value)
      SCR_DrawDebugGraph();

    SCR_DrawPause();

    SCR_DrawConsole();

    M_Draw();

    SCR_DrawLoading();

    UI_Vertical();
    SNDDMA_DrawStats();
    UI_End();
  }

  // GL_draw_stats
  {
    extern void alias_gl_temporaryBufferStats(uint32_t type, uint32_t * total_allocated, uint32_t * used);

    UI_Align(0, 1);
    UI_Vertical();

    uint32_t element_alloc, element_used;
    alias_gl_temporaryBufferStats(0x8893, &element_alloc, &element_used);
    UI_Text("Temp Element Buffer: %i used of %i", element_used, element_alloc);

    uint32_t vertex_alloc, vertex_used;
    alias_gl_temporaryBufferStats(0x8892, &vertex_alloc, &vertex_used);
    UI_Text("Temp Vertex Buffer: %i used of %i", vertex_used, vertex_alloc);

    UI_End();
  }

  UI_Align(1, 1);
  UI_Text("Elysian Break pre-alpha");

  alias_ui_end_stack(frame_ui);

  alias_ui_Output ui_output = {
      .groups = ui_output_groups,
      .num_groups = 0,
      .max_groups = sizeof(ui_output_groups) / sizeof(ui_output_groups[0]),
      .num_vertexes = 0,
      .index_sub_buffer = {.pointer = ui_output_indexes,
                           .stride = sizeof(ui_output_indexes[0]),
                           .count = sizeof(ui_output_indexes) / sizeof(ui_output_indexes[0]),
                           .type_format = alias_memory_Format_Uint32,
                           .type_length = 1},
      .num_vertexes = 0,
      .xy_sub_buffer = {.pointer = &ui_output_vertexes[0].xy[0],
                        .stride = sizeof(ui_output_vertexes[0]),
                        .count = sizeof(ui_output_vertexes) / sizeof(ui_output_vertexes[0]),
                        .type_format = alias_memory_Format_Float32,
                        .type_length = 2},
      .st_sub_buffer = {.pointer = &ui_output_vertexes[0].st[0],
                        .stride = sizeof(ui_output_vertexes[0]),
                        .count = sizeof(ui_output_vertexes) / sizeof(ui_output_vertexes[0]),
                        .type_format = alias_memory_Format_Float32,
                        .type_length = 2},
      .rgba_sub_buffer = {.pointer = &ui_output_vertexes[0].rgba,
                          .stride = sizeof(ui_output_vertexes[0]),
                          .count = sizeof(ui_output_vertexes) / sizeof(ui_output_vertexes[0]),
                          .type_format = alias_memory_Format_Unorm8,
                          .type_length = 4},
  };

  alias_ui_end_frame(frame_ui, alias_default_MemoryCB(), &ui_output);

  for(int i = 0; i < ui_output.num_groups; i++) {
    re.DrawTriangles(ui_frame_images[ui_output_groups[i].texture_id], ui_output_vertexes, ui_output.num_vertexes,
                     ui_output_indexes + ui_output_groups[i].index, ui_output_groups[i].length);
  }

  re.EndFrame();
}