/*         ______   ___    ___ 
 *        /\  _  \ /\_ \  /\_ \ 
 *        \ \ \L\ \\//\ \ \//\ \      __     __   _ __   ___ 
 *         \ \  __ \ \ \ \  \ \ \   /'__`\ /'_ `\/\`'__\/ __`\
 *          \ \ \/\ \ \_\ \_ \_\ \_/\  __//\ \L\ \ \ \//\ \L\ \
 *           \ \_\ \_\/\____\/\____\ \____\ \____ \ \_\\ \____/
 *            \/_/\/_/\/____/\/____/\/____/\/___L\ \/_/ \/___/
 *                                           /\____/
 *                                           \_/__/
 *
 *      GUI routines.
 *
 *      The graphics mode selection dialog.
 *
 *      By Shawn Hargreaves.
 *
 *      Rewritten by Henrik Stokseth.
 *
 *      Magnus Henoch modified it to keep the current selection
 *      across the changes as much as possible.
 *
 *      See readme.txt for copyright information.
 */


#include <string.h>
#include <stdio.h>

#include "allegro.h"
#include "allegro/internal/aintern.h"


static AL_CONST char *gfx_mode_getter(int index, int *list_size);
static AL_CONST char *gfx_card_getter(int index, int *list_size);
static AL_CONST char *gfx_depth_getter(int index, int *list_size);

static int change_proc(int msg, DIALOG *d, int c);


#define ALL_BPP(w, h) { w, h, { TRUE, TRUE, TRUE, TRUE, TRUE }}

#define N_COLOR_DEPTH  5

static int bpp_value_list[N_COLOR_DEPTH] = {8, 15, 16, 24, 32};

typedef struct MODE_LIST {
   int  w, h;
   char has_bpp[N_COLOR_DEPTH];
} MODE_LIST;

#define DRVNAME_SIZE  128

typedef struct DRIVER_LIST {
   int       id;
   char      name[DRVNAME_SIZE];
   void      *fetch_mode_list_ptr;
   MODE_LIST *mode_list;
   int       mode_count;
} DRIVER_LIST;

static MODE_LIST default_mode_list[] =
{
   ALL_BPP(320,  200 ),
   ALL_BPP(320,  240 ),
   ALL_BPP(640,  400 ),
   ALL_BPP(640,  480 ),
   ALL_BPP(800,  600 ),
   ALL_BPP(1024, 768 ),
   ALL_BPP(1280, 1024),
   ALL_BPP(1600, 1200),
   ALL_BPP(80,   80  ),
   ALL_BPP(160,  120 ),
   ALL_BPP(256,  200 ),
   ALL_BPP(256,  224 ),
   ALL_BPP(256,  240 ),
   ALL_BPP(256,  256 ),
   ALL_BPP(320,  100 ),
   ALL_BPP(320,  350 ),
   ALL_BPP(320,  400 ),
   ALL_BPP(320,  480 ),
   ALL_BPP(320,  600 ),
   ALL_BPP(360,  200 ),
   ALL_BPP(360,  240 ),
   ALL_BPP(360,  270 ),
   ALL_BPP(360,  360 ),
   ALL_BPP(360,  400 ),
   ALL_BPP(360,  480 ),
   ALL_BPP(360,  600 ),
   ALL_BPP(376,  282 ),
   ALL_BPP(376,  308 ),
   ALL_BPP(376,  564 ),
   ALL_BPP(400,  150 ),
   ALL_BPP(400,  300 ),
   ALL_BPP(400,  600 ),
   ALL_BPP(0,    0   )
};



static DRIVER_LIST *driver_list;
static int driver_count;

static char mode_string[64];
static DIALOG *what_dialog;



static DIALOG gfx_mode_dialog[] =
{
   /* (dialog proc)        (x)   (y)   (w)   (h)   (fg)  (bg)  (key) (flags)  (d1)  (d2)  (dp)                     (dp2) (dp3) */
   { _gui_shadow_box_proc, 0,    0,    313,  159,  0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  },
   { change_proc,          0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    0,                       0,    0     },
   { _gui_ctext_proc,      156,  8,    1,    1,    0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  },
   { _gui_button_proc,     196,  105,  101,  17,   0,    0,    0,    D_EXIT,  0,    0,    NULL,                    NULL, NULL  },
   { _gui_button_proc,     196,  127,  101,  17,   0,    0,    27,   D_EXIT,  0,    0,    NULL,                    NULL, NULL  },
   { _gui_list_proc,       16,   28,   165,  116,  0,    0,    0,    D_EXIT,  0,    0,    (void*)gfx_card_getter,  NULL, NULL  },
   { _gui_list_proc,       196,  28,   101,  68,   0,    0,    0,    D_EXIT,  3,    0,    (void*)gfx_mode_getter,  NULL, NULL  },
   { d_yield_proc,         0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  },
   { NULL,                 0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  }
};



static DIALOG gfx_mode_ex_dialog[] =
{
   /* (dialog proc)        (x)   (y)   (w)   (h)   (fg)  (bg)  (key) (flags)  (d1)  (d2)  (dp)                     (dp2) (dp3) */
   { _gui_shadow_box_proc, 0,    0,    313,  159,  0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  },
   { change_proc,          0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    0,                       0,    0     },
   { _gui_ctext_proc,      156,  8,    1,    1,    0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  },
   { _gui_button_proc,     196,  105,  101,  17,   0,    0,    0,    D_EXIT,  0,    0,    NULL,                    NULL, NULL  },
   { _gui_button_proc,     196,  127,  101,  17,   0,    0,    27,   D_EXIT,  0,    0,    NULL,                    NULL, NULL  },
   { _gui_list_proc,       16,   28,   165,  68,   0,    0,    0,    D_EXIT,  0,    0,    (void*)gfx_card_getter,  NULL, NULL  },
   { _gui_list_proc,       196,  28,   101,  68,   0,    0,    0,    D_EXIT,  3,    0,    (void*)gfx_mode_getter,  NULL, NULL  },
   { _gui_list_proc,       16,   105,  165,  44,   0,    0,    0,    D_EXIT,  0,    0,    (void*)gfx_depth_getter, NULL, NULL  },
   { d_yield_proc,         0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  },
   { NULL,                 0,    0,    0,    0,    0,    0,    0,    0,       0,    0,    NULL,                    NULL, NULL  }
};


#define GFX_CHANGEPROC     1
#define GFX_TITLE          2
#define GFX_OK             3
#define GFX_CANCEL         4
#define GFX_DRIVERLIST     5
#define GFX_MODELIST       6
#define GFX_DEPTHLIST      7



/* bpp_value:
 *  Returns the bpp value of the color depth pointed to by INDEX.
 */
static INLINE int bpp_value(int index)
{
   ASSERT(index < N_COLOR_DEPTH);

   return bpp_value_list[index];
}



/* bpp_value_for_mode:
 *  Returns the bpp value of the color depth pointed to by INDEX
 *  for the specified MODE of the specified DRIVER or -1 if INDEX
 *  is out of bound.
 */
static int bpp_value_for_mode(int index, int driver, int mode)
{
   int i, j = -1;

   ASSERT(index < N_COLOR_DEPTH);

   for (i=0; i < N_COLOR_DEPTH; i++) {
      if (driver_list[driver].mode_list[mode].has_bpp[i]) {
         if (++j == index)
            return bpp_value(i);
      }
   }

   return -1;
}



/* bpp_index:
 *  Returns the bpp index of the color depth given by DEPTH.
 */
static int bpp_index(int depth)
{
   int i;

   for (i=0; i < N_COLOR_DEPTH; i++) {
      if (bpp_value_list[i] == depth)
         return i;
   }

   ASSERT(FALSE);
   return -1;
}



/* bpp_index_for_mode:
 *  Returns the bpp index of the color depth given by DEPTH for
 *  the specified MODE of the specified DRIVER or -1 if DEPTH
 *  is not supported.
 */
static int bpp_index_for_mode(int depth, int driver, int mode)
{
   int i, index = -1;

   for (i=0; i < N_COLOR_DEPTH; i++) {
      if (driver_list[driver].mode_list[mode].has_bpp[i]) {
         index++;
         if (bpp_value_list[i] == depth)
            return index;
      }
   }

   return -1;
}



/* change_proc:
 *  Stores the current driver in d1 and graphics mode in d2; if a new
 *  driver is selected in the listbox, it updates the resolution and 
 *  color depth listboxes so that they redraw; if a new resolution is
 *  selected in the listbox, it updates the color depth listbox. The
 *  current selection is kept across the changes as much as possible.
 */
static int change_proc(int msg, DIALOG *d, int c)
{
   int width = driver_list[d->d1].mode_list[d->d2].w;
   int height = driver_list[d->d1].mode_list[d->d2].h;
   int depth = bpp_value_for_mode(what_dialog[GFX_DEPTHLIST].d1, d->d1, d->d2);
   int i;

   if (msg != MSG_IDLE)
      return D_O_K;

   if (what_dialog[GFX_DRIVERLIST].d1 != d->d1) {
      /* record the new setting */
      d->d1 = what_dialog[GFX_DRIVERLIST].d1;

      /* try to preserve the resolution */      
      what_dialog[GFX_MODELIST].d1 = 0;

      for (i = 0; i < driver_list[d->d1].mode_count; i++) {
         if (driver_list[d->d1].mode_list[i].w == width &&
             driver_list[d->d1].mode_list[i].h == height) {
           what_dialog[GFX_MODELIST].d1 = i;
           break;
         }
      }

      what_dialog[GFX_MODELIST].d2 = 0;
      scare_mouse();
      object_message(&what_dialog[GFX_MODELIST], MSG_DRAW, 0);
      unscare_mouse();

      /* record the new setting */
      d->d2 = what_dialog[GFX_MODELIST].d1;

      if (what_dialog == gfx_mode_ex_dialog) {
         /* try to preserve the color depth */
         what_dialog[GFX_DEPTHLIST].d1 = bpp_index_for_mode(depth, d->d1, d->d2);
         if (what_dialog[GFX_DEPTHLIST].d1 < 0)
            what_dialog[GFX_DEPTHLIST].d1 = 0;

         what_dialog[GFX_DEPTHLIST].d2 = 0;
         scare_mouse();
         object_message(&what_dialog[GFX_DEPTHLIST], MSG_DRAW, 0);
         unscare_mouse();
      }
   }

   if (what_dialog[GFX_MODELIST].d1 != d->d2) {
      /* record the new setting */
      d->d2 = what_dialog[GFX_MODELIST].d1;

      if (what_dialog == gfx_mode_ex_dialog) {
         /* try to preserve the color depth */
         what_dialog[GFX_DEPTHLIST].d1 = bpp_index_for_mode(depth, d->d1, d->d2);
         if (what_dialog[GFX_DEPTHLIST].d1 < 0)
            what_dialog[GFX_DEPTHLIST].d1 = 0;

         what_dialog[GFX_DEPTHLIST].d2 = 0;
         scare_mouse();
         object_message(&what_dialog[GFX_DEPTHLIST], MSG_DRAW, 0);
         unscare_mouse();
      }
   }

   return D_O_K;
}



/* create_mode_list:
 *  Creates a mode list table. Returns 0 on success and -1 on failure.
 */
static int create_mode_list(DRIVER_LIST *driver_list_entry)
{
   MODE_LIST *temp_mode_list;
   GFX_MODE_LIST *gfx_mode_list;
   GFX_MODE *gfx_mode_entry;
   int mode, res_exist;

   /* autodetect drivers? */
   if ((driver_list_entry->id == GFX_AUTODETECT) ||
       (driver_list_entry->id == GFX_AUTODETECT_WINDOWED) ||
       (driver_list_entry->id == GFX_AUTODETECT_FULLSCREEN)) {
      driver_list_entry->mode_count = sizeof(default_mode_list) / sizeof(MODE_LIST) - 1;
      driver_list_entry->mode_list = default_mode_list;
      driver_list_entry->fetch_mode_list_ptr = NULL;
      return 0;
   }

   gfx_mode_list = get_gfx_mode_list(driver_list_entry->id);

   /* driver supports fetch_mode_list? */
   if (gfx_mode_list) {
      temp_mode_list = NULL;
      driver_list_entry->mode_count = 0;

      /* build mode list */
      for (gfx_mode_entry = gfx_mode_list->mode; gfx_mode_entry->width; gfx_mode_entry++) {
         res_exist = FALSE;

         /* resolution already there? */
         for(mode = 0; mode < driver_list_entry->mode_count; mode++) {
            if ((gfx_mode_entry->width == temp_mode_list[mode].w) &&
                (gfx_mode_entry->height == temp_mode_list[mode].h)) {
               res_exist = TRUE;
               temp_mode_list[mode].has_bpp[bpp_index(gfx_mode_entry->bpp)] = TRUE;
               break;
            }
         }

         /* add resolution. */
         if (!res_exist) {
            driver_list_entry->mode_count++;
            temp_mode_list = _al_sane_realloc(temp_mode_list, sizeof(MODE_LIST) * (driver_list_entry->mode_count));
            if (!temp_mode_list) {
               destroy_gfx_mode_list(gfx_mode_list);
               return -1;
            }

            mode = driver_list_entry->mode_count - 1;

            temp_mode_list[mode].w = gfx_mode_entry->width;
            temp_mode_list[mode].h = gfx_mode_entry->height;

            memset(temp_mode_list[mode].has_bpp, 0, N_COLOR_DEPTH);
            temp_mode_list[mode].has_bpp[bpp_index(gfx_mode_entry->bpp)] = TRUE;
         }
      }

      /* terminate mode list */
      temp_mode_list = _al_sane_realloc(temp_mode_list, sizeof(MODE_LIST) * (driver_list_entry->mode_count + 1));
      if (!temp_mode_list) {
         destroy_gfx_mode_list(gfx_mode_list);
         return -1;
      }

      temp_mode_list[driver_list_entry->mode_count].w = 0;
      temp_mode_list[driver_list_entry->mode_count].h = 0;

      driver_list_entry->mode_list = temp_mode_list;

      destroy_gfx_mode_list(gfx_mode_list);
   }
   else {
      /* driver doesn't support fetch_mode_list() or the call failed */
      driver_list_entry->mode_count = sizeof(default_mode_list) / sizeof(MODE_LIST) - 1;
      driver_list_entry->mode_list = default_mode_list;
      driver_list_entry->fetch_mode_list_ptr = NULL;
   }

   return 0;
}



/* create_driver_list:
 *  Fills the list of video cards with info about the available drivers. Returns -1 on failure.
 */
static int create_driver_list(void)
{
   _DRIVER_INFO *driver_info;
   GFX_DRIVER *gfx_driver;
   int driver_count2, used_prefetched;

   if (system_driver->gfx_drivers)
      driver_info = system_driver->gfx_drivers();
   else
      driver_info = _gfx_driver_list;

   driver_list = malloc(sizeof(DRIVER_LIST) * 3);
   if (!driver_list) return -1;

   driver_list[0].id = GFX_AUTODETECT;
   ustrzcpy(driver_list[0].name, DRVNAME_SIZE, get_config_text("Autodetect"));
   create_mode_list(&driver_list[0]);

   driver_list[1].id = GFX_AUTODETECT_FULLSCREEN;
   ustrzcpy(driver_list[1].name, DRVNAME_SIZE, get_config_text("Auto fullscreen"));
   create_mode_list(&driver_list[1]);

   driver_list[2].id = GFX_AUTODETECT_WINDOWED;
   ustrzcpy(driver_list[2].name, DRVNAME_SIZE, get_config_text("Auto windowed"));
   create_mode_list(&driver_list[2]);

   driver_count = 0;

   while(driver_info[driver_count].driver) {
      driver_list = _al_sane_realloc(driver_list, sizeof(DRIVER_LIST) * (driver_count + 4));
      if (!driver_list) return -1;
      driver_list[driver_count+3].id = driver_info[driver_count].id;
      gfx_driver = driver_info[driver_count].driver;
      do_uconvert(gfx_driver->ascii_name, U_ASCII, driver_list[driver_count+3].name, U_CURRENT, DRVNAME_SIZE);
      driver_list[driver_count+3].fetch_mode_list_ptr = gfx_driver->fetch_mode_list;

      used_prefetched = FALSE;

      /* use already fetched mode-list if possible */
      for (driver_count2=0; driver_count2 < driver_count+3; driver_count2++) {
         if (driver_list[driver_count+3].fetch_mode_list_ptr == driver_list[driver_count2].fetch_mode_list_ptr) {
            driver_list[driver_count+3].mode_list = driver_list[driver_count2].mode_list;
            driver_list[driver_count+3].mode_count = driver_list[driver_count2].mode_count;
            /* the following line prevents a mode-list from beeing free'd more than once */
            driver_list[driver_count+3].fetch_mode_list_ptr = NULL;
            used_prefetched = TRUE;
            break;
         }
      }
      /* didn't find an already fetched mode-list */
      if (!used_prefetched) {
         create_mode_list(&driver_list[driver_count+3]);
      }

      driver_count++;
   }

   driver_count += 3;

   return 0;
}



/* destroy_driver_list:
 *  Frees allocated memory used by driver lists and mode lists.
 */
static void destroy_driver_list(void)
{
   int driver;

   for (driver=0; driver < driver_count; driver++) {
      if (driver_list[driver].fetch_mode_list_ptr)
         free(driver_list[driver].mode_list);
   }

   free(driver_list);
}



/* gfx_card_getter:
 *  Listbox data getter routine for the graphics card list.
 */
static AL_CONST char *gfx_card_getter(int index, int *list_size)
{
   if (index < 0) {
      if (list_size)
         *list_size = driver_count;
      return NULL;
   }

   return driver_list[index].name;
}



/* gfx_mode_getter:
 *  Listbox data getter routine for the graphics mode list.
 */
static AL_CONST char *gfx_mode_getter(int index, int *list_size)
{
   int entry;
   char tmp[32];

   entry = what_dialog[GFX_DRIVERLIST].d1;

   if (index < 0) {
      if (list_size) {
         *list_size = driver_list[entry].mode_count;
         return NULL;
      }
   }

   uszprintf(mode_string, sizeof(mode_string), uconvert_ascii("%ix%i", tmp),
             driver_list[entry].mode_list[index].w, driver_list[entry].mode_list[index].h);

   return mode_string;
}



/* gfx_depth_getter:
 *  Listbox data getter routine for the color depth list.
 */
static AL_CONST char *gfx_depth_getter(int index, int *list_size)
{
   static char *bpp_string_list[N_COLOR_DEPTH] = {"256", "32K", "64K", "16M", "16M"};
   MODE_LIST *mode;
   int card_entry, mode_entry, bpp_entry, bpp_count, bpp_index;
   char tmp[128];

   card_entry = what_dialog[GFX_DRIVERLIST].d1;
   mode_entry = what_dialog[GFX_MODELIST].d1;
   mode = &driver_list[card_entry].mode_list[mode_entry];

   if (index < 0) {
      if (list_size) {
         /* Count the number of BPP entries for the mode. */
         for (bpp_count = 0, bpp_entry = 0; bpp_entry < N_COLOR_DEPTH; bpp_entry++) {
            if (mode->has_bpp[bpp_entry])
               bpp_count++;
         }

         *list_size = bpp_count;
         return NULL;
      }
   }

   /* Find the BPP entry for the mode corresponding to the zero-based index. */
   bpp_index = -1;
   bpp_entry = -1;
   while (bpp_index < index) {
      if (mode->has_bpp[++bpp_entry])
         bpp_index++;
   }

   uszprintf(mode_string,
             sizeof(mode_string),
             uconvert_ascii("%2d %s (%s %s)", tmp),
             bpp_value(bpp_entry),
             get_config_text("bpp"),
             bpp_string_list[bpp_entry],
             get_config_text("colors"));
 
   return mode_string;
}



/* gfx_mode_select_ex:
 *  Extended version of the graphics mode selection dialog, which allows the 
 *  user to select the color depth as well as the resolution and hardware 
 *  driver. This version of the function reads the initial values from the 
 *  parameters when it activates, so you can specify the default values.
 */
int gfx_mode_select_ex(int *card, int *w, int *h, int *color_depth)
{
   int i, ret, what_driver, what_mode, what_bpp, extd;

   clear_keybuf();

   extd = color_depth ? TRUE : FALSE;

   while (gui_mouse_b());

   what_dialog = extd ? gfx_mode_ex_dialog : gfx_mode_dialog;

   what_dialog[GFX_TITLE].dp = (void*)get_config_text("Graphics Mode");
   what_dialog[GFX_OK].dp = (void*)get_config_text("OK");
   what_dialog[GFX_CANCEL].dp = (void*)get_config_text("Cancel");

   create_driver_list();

   /* We try to use the values passed through the argument pointers
    * as initial settings for the dialog boxes, but only if we have
    * been called from the extended function.
    */
   if (extd) {
      /* firstly the driver */
      what_dialog[GFX_DRIVERLIST].d1 = 0;  /* GFX_AUTODETECT */

      for (i=0; i<driver_count; i++) {
         if (driver_list[i].id == *card) {
            what_dialog[GFX_DRIVERLIST].d1 = i;
            break;
         }
      }

      what_driver = what_dialog[GFX_DRIVERLIST].d1;
      what_dialog[GFX_CHANGEPROC].d1 = what_dialog[GFX_DRIVERLIST].d1;

      /* secondly the resolution */
      what_dialog[GFX_MODELIST].d1 = 0;

      for (i=0; driver_list[what_driver].mode_list[i].w; i++) {
         if ((driver_list[what_driver].mode_list[i].w == *w)
              && (driver_list[what_driver].mode_list[i].h == *h)) {
            what_dialog[GFX_MODELIST].d1 = i;
            break; 
         }
      }

      what_mode = what_dialog[GFX_MODELIST].d1;
      what_dialog[GFX_CHANGEPROC].d2 = what_dialog[GFX_MODELIST].d1;  /* not d2 */

      /* thirdly the color depth */
      what_bpp = bpp_index_for_mode(*color_depth, what_driver, what_mode);
      if (what_bpp < 0)
         what_bpp = 0;

      what_dialog[GFX_DEPTHLIST].d1 = what_bpp;
   }

   centre_dialog(what_dialog);
   set_dialog_color(what_dialog, gui_fg_color, gui_bg_color);
   ret = popup_dialog(what_dialog, GFX_DRIVERLIST);

   what_driver = what_dialog[GFX_DRIVERLIST].d1;
   what_mode = what_dialog[GFX_MODELIST].d1;

   if (extd)
      what_bpp = what_dialog[GFX_DEPTHLIST].d1;
   else
      what_bpp = 0;

   *card = driver_list[what_driver].id;

   *w = driver_list[what_driver].mode_list[what_mode].w;
   *h = driver_list[what_driver].mode_list[what_mode].h;

   if (extd)
      *color_depth = bpp_value_for_mode(what_bpp, what_driver, what_mode);

   destroy_driver_list();

   if (ret == GFX_CANCEL)
      return FALSE;
   else 
      return TRUE;
}



/* gfx_mode_select:
 *  Displays the Allegro graphics mode selection dialog, which allows the
 *  user to select a screen mode and graphics card. Stores the selection
 *  in the three variables, and returns zero if it was closed with the 
 *  Cancel button, or non-zero if it was OK'd.
 */
int gfx_mode_select(int *card, int *w, int *h)
{
   return gfx_mode_select_ex(card, w, h, NULL);
}
