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

#include "egldisplay.h"
#include "egldriver.h"
#include "eglmode.h"
#include "eglglobals.h"
#include "eglscreen.h"


#define MIN2(A, B)  (((A) < (B)) ? (A) : (B))


static char *
my_strdup(const char *s)
{
   if (s) {
      int l = strlen(s);
      char *s2 = malloc(l + 1);
      if (s2)
         strcpy(s2, s);
      return s2;
   }
   else {
      return NULL;
   }
}


/**
 * Given an EGLModeMESA handle, return the corresponding _EGLMode object
 * or null if non-existant.
 */
_EGLMode *
_eglLookupMode(EGLModeMESA mode, _EGLDisplay *disp)
{
   EGLint scrnum;

   /* loop over all screens on the display */
   for (scrnum = 0; scrnum < disp->NumScreens; scrnum++) {
      const _EGLScreen *scrn = disp->Screens[scrnum];
      EGLint i;
      /* search list of modes for handle */
      for (i = 0; i < scrn->NumModes; i++) {
         if (scrn->Modes[i].Handle == mode) {
            return scrn->Modes + i;
         }
      }
   }

   return NULL;
}


/**
 * Add a new mode with the given attributes (width, height, depth, refreshRate)
 * to the given screen.
 * Assign a new mode ID/handle to the mode as well.
 * \return pointer to the new _EGLMode
 */
_EGLMode *
_eglAddNewMode(_EGLScreen *screen, EGLint width, EGLint height,
               EGLint refreshRate, const char *name)
{
   EGLint n;
   _EGLMode *newModes;

   assert(screen);
   assert(width > 0);
   assert(height > 0);
   assert(refreshRate > 0);

   n = screen->NumModes;
   newModes = (_EGLMode *) realloc(screen->Modes, (n+1) * sizeof(_EGLMode));
   if (newModes) {
      screen->Modes = newModes;
      screen->Modes[n].Handle = n + 1;
      screen->Modes[n].Width = width;
      screen->Modes[n].Height = height;
      screen->Modes[n].RefreshRate = refreshRate;
      screen->Modes[n].Optimal = EGL_FALSE;
      screen->Modes[n].Interlaced = EGL_FALSE;
      screen->Modes[n].Name = my_strdup(name);
      screen->NumModes++;
      return screen->Modes + n;
   }
   else {
      return NULL;
   }
}



/**
 * Parse the attrib_list to fill in the fields of the given _eglMode
 * Return EGL_FALSE if any errors, EGL_TRUE otherwise.
 */
static EGLBoolean
_eglParseModeAttribs(_EGLMode *mode, const EGLint *attrib_list)
{
   EGLint i;

   /* init all attribs to EGL_DONT_CARE */
   mode->Handle = EGL_DONT_CARE;
   mode->Width = EGL_DONT_CARE;
   mode->Height = EGL_DONT_CARE;
   mode->RefreshRate = EGL_DONT_CARE;
   mode->Optimal = EGL_DONT_CARE;
   mode->Interlaced = EGL_DONT_CARE;
   mode->Name = NULL;

   for (i = 0; attrib_list && attrib_list[i] != EGL_NONE; i++) {
      switch (attrib_list[i]) {
      case EGL_MODE_ID_MESA:
         mode->Handle = attrib_list[++i];
         if (mode->Handle <= 0) {
            _eglError(EGL_BAD_PARAMETER, "eglChooseModeMESA(handle)");
            return EGL_FALSE;
         }
         break;
      case EGL_WIDTH:
         mode->Width = attrib_list[++i];
         if (mode->Width <= 0) {
            _eglError(EGL_BAD_PARAMETER, "eglChooseModeMESA(width)");
            return EGL_FALSE;
         }
         break;
      case EGL_HEIGHT:
         mode->Height = attrib_list[++i];
         if (mode->Height <= 0) {
            _eglError(EGL_BAD_PARAMETER, "eglChooseModeMESA(height)");
            return EGL_FALSE;
         }
         break;
      case EGL_REFRESH_RATE_MESA:
         mode->RefreshRate = attrib_list[++i];
         if (mode->RefreshRate <= 0) {
            _eglError(EGL_BAD_PARAMETER, "eglChooseModeMESA(refresh rate)");
            return EGL_FALSE;
         }
         break;
      case EGL_INTERLACED_MESA:
         mode->Interlaced = attrib_list[++i];
         if (mode->Interlaced != EGL_TRUE && mode->Interlaced != EGL_FALSE) {
            _eglError(EGL_BAD_PARAMETER, "eglChooseModeMESA(interlaced)");
            return EGL_FALSE;
         }
         break;
      case EGL_OPTIMAL_MESA:
         mode->Optimal = attrib_list[++i];
         if (mode->Optimal != EGL_TRUE && mode->Optimal != EGL_FALSE) {
            _eglError(EGL_BAD_PARAMETER, "eglChooseModeMESA(optimal)");
            return EGL_FALSE;
         }
         break;
      default:
         _eglError(EGL_BAD_ATTRIBUTE, "eglChooseModeMESA");
         return EGL_FALSE;
      }
   }
   return EGL_TRUE;
}


/**
 * Determine if the candidate mode's attributes are at least as good
 * as the minimal mode's.
 * \return EGL_TRUE if qualifies, EGL_FALSE otherwise
 */
static EGLBoolean
_eglModeQualifies(const _EGLMode *c, const _EGLMode *min)
{
   if (min->Handle != EGL_DONT_CARE && c->Handle != min->Handle)
      return EGL_FALSE;
   if (min->Width != EGL_DONT_CARE && c->Width < min->Width)
      return EGL_FALSE;
   if (min->Height != EGL_DONT_CARE && c->Height < min->Height)
      return EGL_FALSE;
   if (min->RefreshRate != EGL_DONT_CARE && c->RefreshRate < min->RefreshRate)
      return EGL_FALSE;
   if (min->Optimal != EGL_DONT_CARE && c->Optimal != min->Optimal)
      return EGL_FALSE;
   if (min->Interlaced != EGL_DONT_CARE && c->Interlaced != min->Interlaced)
      return EGL_FALSE;

   return EGL_TRUE;
}


/**
 * Return value of given mode attribute, or -1 if bad attrib.
 */
static EGLint
getModeAttrib(const _EGLMode *m, EGLint attrib)
{
   switch (attrib) {
   case EGL_MODE_ID_MESA:
      return m->Handle;
   case EGL_WIDTH:
      return m->Width;
   case EGL_HEIGHT:
      return m->Height;
   case EGL_REFRESH_RATE_MESA:
      return m->RefreshRate;
   case EGL_OPTIMAL_MESA:
      return m->Optimal;
   case EGL_INTERLACED_MESA:
      return m->Interlaced;
   default:
      return -1;
   }
}


#define SMALLER 1
#define LARGER  2

struct sort_info {
   EGLint Attrib;
   EGLint Order; /* SMALLER or LARGER */
};

/* the order of these entries is the priority */
static struct sort_info SortInfo[] = {
   { EGL_OPTIMAL_MESA, LARGER },
   { EGL_INTERLACED_MESA, SMALLER },
   { EGL_WIDTH, LARGER },
   { EGL_HEIGHT, LARGER },
   { EGL_REFRESH_RATE_MESA, LARGER },
   { EGL_MODE_ID_MESA, SMALLER },
   { 0, 0 }
};


/**
 * Compare modes 'a' and 'b' and return -1 if a belongs before b, or 1 if a
 * belongs after b, or 0 if they're equal.
 * Used by qsort().
 */
static int
_eglCompareModes(const void *a, const void *b)
{
   const _EGLMode *aMode = *((const _EGLMode **) a);
   const _EGLMode *bMode = *((const _EGLMode **) b);
   EGLint i;

   for (i = 0; SortInfo[i].Attrib; i++) {
      const EGLint aVal = getModeAttrib(aMode, SortInfo[i].Attrib);
      const EGLint bVal = getModeAttrib(bMode, SortInfo[i].Attrib);
      if (aVal == bVal) {
         /* a tie */
         continue;
      }
      else if (SortInfo[i].Order == SMALLER) {
         return (aVal < bVal) ? -1 : 1;
      }
      else if (SortInfo[i].Order == LARGER) {
         return (aVal > bVal) ? -1 : 1;
      }
   }

   /* all attributes identical */
   return 0;
}


/**
 * Search for EGLModes which match the given attribute list.
 * Called via eglChooseModeMESA API function.
 */
EGLBoolean
_eglChooseModeMESA(_EGLDriver *drv, _EGLDisplay *dpy, _EGLScreen *scrn,
                   const EGLint *attrib_list, EGLModeMESA *modes,
                   EGLint modes_size, EGLint *num_modes)
{
   _EGLMode **modeList, min;
   EGLint i, count;

   if (!_eglParseModeAttribs(&min, attrib_list)) {
      /* error code will have been recorded */
      return EGL_FALSE;
   }

   /* allocate array of mode pointers */
   modeList = (_EGLMode **) malloc(modes_size * sizeof(_EGLMode *));
   if (!modeList) {
      _eglError(EGL_BAD_MODE_MESA, "eglChooseModeMESA(out of memory)");
      return EGL_FALSE;
   }

   /* make array of pointers to qualifying modes */
   for (i = count = 0; i < scrn->NumModes && count < modes_size; i++) {
      if (_eglModeQualifies(scrn->Modes + i, &min)) {
         modeList[count++] = scrn->Modes + i;
      }
   }

   /* sort array of pointers */
   qsort(modeList, count, sizeof(_EGLMode *), _eglCompareModes);

   /* copy mode handles to output array */
   for (i = 0; i < count; i++) {
      modes[i] = modeList[i]->Handle;
   }

   free(modeList);

   *num_modes = count;

   return EGL_TRUE;
}



/**
 * Return all possible modes for the given screen.  No sorting of results.
 * Called via eglGetModesMESA() API function.
 */
EGLBoolean
_eglGetModesMESA(_EGLDriver *drv, _EGLDisplay *dpy, _EGLScreen *scrn,
                 EGLModeMESA *modes, EGLint modes_size, EGLint *num_modes)
{
   if (modes) {
      EGLint i;
      *num_modes = MIN2(scrn->NumModes, modes_size);
      for (i = 0; i < *num_modes; i++) {
         modes[i] = scrn->Modes[i].Handle;
      }
   }
   else {
      /* just return total number of supported modes */
      *num_modes = scrn->NumModes;
   }

   return EGL_TRUE;
}


/**
 * Query an attribute of a mode.
 */
EGLBoolean
_eglGetModeAttribMESA(_EGLDriver *drv, _EGLDisplay *dpy,
                      _EGLMode *m, EGLint attribute, EGLint *value)
{
   EGLint v;

   v = getModeAttrib(m, attribute);
   if (v < 0) {
      _eglError(EGL_BAD_ATTRIBUTE, "eglGetModeAttribMESA");
      return EGL_FALSE;
   }
   *value = v;
   return EGL_TRUE;
}


/**
 * Return human-readable string for given mode.
 * This is the default function called by eglQueryModeStringMESA().
 */
const char *
_eglQueryModeStringMESA(_EGLDriver *drv, _EGLDisplay *dpy, _EGLMode *m)
{
   return m->Name;
}


#if 0
static int
_eglRand(int max)
{
   return rand() % max;
}

void
_eglTestModeModule(void)
{
   EGLint count = 30;
   _EGLMode *modes = (_EGLMode *) malloc(count * sizeof(_EGLMode));
   _EGLMode **modeList = (_EGLMode **) malloc(count * sizeof(_EGLMode*));
   EGLint i;

   for (i = 0; i < count; i++) {
      modes[i].Handle = _eglRand(20);
      modes[i].Width = 512 + 256 * _eglRand(2);
      modes[i].Height = 512 + 256 * _eglRand(2);
      modes[i].RefreshRate = 50 + 5 * _eglRand(3);
      modes[i].Interlaced = _eglRand(2);
      modes[i].Optimal = _eglRand(4) == 0;
      modeList[i] = modes + i;
   }

   /* sort array of pointers */
   qsort(modeList, count, sizeof(_EGLMode *), compareModes);

   for (i = 0; i < count; i++) {
      _EGLMode *m = modeList[i];
      printf("%2d: %3d  %4d x %4d  @ %3d  opt %d  int %d\n", i,
             m->Handle, m->Width, m->Height, m->RefreshRate,
             m->Optimal, m->Interlaced);
   }
}
#endif