#include <cdk_int.h>

#ifdef HAVE_SETLOCALE
#include <locale.h>
#endif

/*
 * $Author: tom $
 * $Date: 2025/01/09 00:20:21 $
 * $Revision: 1.92 $
 */

typedef struct _all_screens
{
   struct _all_screens *link;
   CDKSCREEN *screen;
}
ALL_SCREENS;

static ALL_SCREENS *all_screens;

typedef struct _all_objects
{
   struct _all_objects *link;
   CDKOBJS *object;
}
ALL_OBJECTS;

static ALL_OBJECTS *all_objects;

static boolean validObjType (CDKOBJS *obj, EObjectType type)
{
   bool valid = FALSE;

   if (obj != NULL && ObjTypeOf (obj) == type)
   {
      switch (type)
      {
      case vALPHALIST:
      case vBUTTON:
      case vBUTTONBOX:
      case vCALENDAR:
      case vDIALOG:
      case vDSCALE:
      case vENTRY:
      case vFSCALE:
      case vFSELECT:
      case vFSLIDER:
      case vGRAPH:
      case vHISTOGRAM:
      case vITEMLIST:
      case vLABEL:
      case vMARQUEE:
      case vMATRIX:
      case vMENTRY:
      case vMENU:
      case vRADIO:
      case vSCALE:
      case vSCROLL:
      case vSELECTION:
      case vSLIDER:
      case vSWINDOW:
      case vTEMPLATE:
      case vUSCALE:
      case vUSLIDER:
      case vVIEWER:
	 valid = TRUE;
	 break;
      case vTRAVERSE:		/* not really an object */
      case vNULL:
	 break;
      }
   }
   return valid;
}

/*
 * Set indices so the screen and object point to each other.
 */
static void setScreenIndex (CDKSCREEN *screen, int number, CDKOBJS *obj)
{
   (obj)->screenIndex = number;
   (obj)->screen = screen;
   screen->object[number] = obj;
}

/*
 * Returns true if we have done a "new" on this object but no "destroy"
 */
bool validCDKObject (CDKOBJS *obj)
{
   bool result = FALSE;
   if (obj != NULL)
   {
      ALL_OBJECTS *ptr;

      for (ptr = all_objects; ptr != NULL; ptr = ptr->link)
      {
	 if (ptr->object == obj)
	 {
	    result = validObjType (obj, ObjTypeOf (obj));
	    break;
	 }
      }
   }
   return result;
}

/*
 * Create a new object beginning with a CDKOBJS struct.  The whole object is
 * initialized to zeroes except for special cases which have known values.
 */
void *_newCDKObject (unsigned size, const CDKFUNCS * funcs)
{
   ALL_OBJECTS *item;
   CDKOBJS *result = NULL;
   if ((item = typeCalloc (ALL_OBJECTS)) != NULL)
   {
      if ((result = (CDKOBJS *)calloc (1, size)) != NULL)
      {
	 result->fn = funcs;
	 result->hasFocus = TRUE;
	 result->isVisible = TRUE;

	 item->link = all_objects;
	 item->object = result;
	 all_objects = item;

	 /* set default line-drawing characters */
	 result->ULChar = ACS_ULCORNER;
	 result->URChar = ACS_URCORNER;
	 result->LLChar = ACS_LLCORNER;
	 result->LRChar = ACS_LRCORNER;
	 result->HZChar = ACS_HLINE;
	 result->VTChar = ACS_VLINE;
	 result->BXAttr = A_NORMAL;

	 /* set default exit-types */
	 result->exitType = vNEVER_ACTIVATED;
	 result->earlyExit = vNEVER_ACTIVATED;
      }
      else
      {
	 free (item);
      }
   }
   return (void *)result;
}

void _destroyCDKObject (CDKOBJS *obj)
{
   if (validCDKObject (obj))
   {
      ALL_OBJECTS *p, *q;

      for (p = all_objects, q = NULL; p != NULL; q = p, p = p->link)
      {
	 if (p->object == obj)
	 {
	    /* delink it first, to avoid problems with recursion */
	    if (q != NULL)
	       q->link = p->link;
	    else
	       all_objects = p->link;

	    MethodPtr (obj, destroyObj) (obj);
	    free (obj);
	    free (p);
	    break;
	 }
      }
   }
}

/*
 * This creates a new CDK screen.
 */
CDKSCREEN *initCDKScreen (WINDOW *window)
{
   ALL_SCREENS *item;
   CDKSCREEN *screen = NULL;

   /* initialization, for the first time */
   if (all_screens == NULL || stdscr == NULL || window == NULL)
   {
      /* Set up basic curses settings. */
#ifdef HAVE_SETLOCALE
      setlocale (LC_ALL, "");
#endif
      /* Initialize curses after setting the locale, since curses depends
       * on having a correct locale to reflect the terminal's encoding.
       */
      if (stdscr == NULL || window == NULL)
      {
	 window = initscr ();
      }
      noecho ();
      cbreak ();
   }

   if ((item = typeMalloc (ALL_SCREENS)) != NULL)
   {
      if ((screen = typeCalloc (CDKSCREEN)) != NULL)
      {
	 item->link = all_screens;
	 item->screen = screen;
	 all_screens = item;

	 /* Initialize the CDKSCREEN pointer. */
	 screen->objectCount = 0;
	 screen->objectLimit = 2;
	 screen->object = typeMallocN (CDKOBJS *, screen->objectLimit);
	 screen->window = window;

	 /* OK, we are done. */
      }
      else
      {
	 free (item);
      }
   }
   return (screen);
}

/*
 * This registers a CDK object with a screen.
 */
void registerCDKObject (CDKSCREEN *screen, EObjectType cdktype, void *object)
{
   CDKOBJS *obj = (CDKOBJS *)object;

   if (screen->objectCount + 1 >= screen->objectLimit)
   {
      screen->objectLimit += 2;
      screen->objectLimit *= 2;
      screen->object = typeReallocN (CDKOBJS *, screen->object, screen->objectLimit);
   }
   if (validObjType (obj, cdktype))
   {
      setScreenIndex (screen, screen->objectCount++, obj);
   }
}

/*
 * This registers a CDK object with a screen.
 */
void reRegisterCDKObject (EObjectType cdktype, void *object)
{
   CDKOBJS *obj = (CDKOBJS *)object;

   registerCDKObject (obj->screen, cdktype, object);
}

/*
 * This removes an object from the CDK screen.
 */
void unregisterCDKObject (EObjectType cdktype, void *object)
{
   CDKOBJS *obj = (CDKOBJS *)object;

   if (validObjType (obj, cdktype) && obj->screenIndex >= 0)
   {
      CDKSCREEN *screen = (obj)->screen;

      if (screen != NULL)
      {
	 int Index = (obj)->screenIndex;
	 int x;

	 obj->screenIndex = -1;

	 /*
	  * Resequence the objects.
	  */
	 for (x = Index; x < screen->objectCount - 1; x++)
	 {
	    setScreenIndex (screen, x, screen->object[x + 1]);
	 }

	 if (screen->objectCount <= 1)
	 {
	    /* if no more objects, remove the array */
	    freeAndNull (screen->object);
	    screen->objectCount = 0;
	    screen->objectLimit = 0;
	 }
	 else
	 {
	    /* Reduce the list by one object. */
	    screen->object[screen->objectCount--] = NULL;

	    /*
	     * Update the object-focus
	     */
	    if (screen->objectFocus == Index)
	    {
	       screen->objectFocus--;
	       (void)setCDKFocusNext (screen);
	    }
	    else if (screen->objectFocus > Index)
	    {
	       screen->objectFocus--;
	    }
	 }
      }
   }
}

#define validIndex(screen, n) ((n) >= 0 && (n) < (screen)->objectCount)

static void swapCDKIndices (CDKSCREEN *screen, int n1, int n2)
{
   if (n1 != n2 && validIndex (screen, n1) && validIndex (screen, n2))
   {
      CDKOBJS *o1 = screen->object[n1];
      CDKOBJS *o2 = screen->object[n2];
      setScreenIndex (screen, n1, o2);
      setScreenIndex (screen, n2, o1);

      if (screen->objectFocus == n1)
	 screen->objectFocus = n2;
      else if (screen->objectFocus == n2)
	 screen->objectFocus = n1;
   }
}

/*
 * This 'brings' a CDK object to the top of the stack.
 */
void raiseCDKObject (EObjectType cdktype, void *object)
{
   CDKOBJS *obj = (CDKOBJS *)object;

   if (validObjType (obj, cdktype))
   {
      CDKSCREEN *screen = obj->screen;
      swapCDKIndices (screen, obj->screenIndex, screen->objectCount - 1);
   }
}

/*
 * This 'lowers' an object.
 */
void lowerCDKObject (EObjectType cdktype, void *object)
{
   CDKOBJS *obj = (CDKOBJS *)object;

   if (validObjType (obj, cdktype))
   {
      CDKSCREEN *screen = obj->screen;
      swapCDKIndices (screen, obj->screenIndex, 0);
   }
}

/*
 * This calls refreshCDKScreen. (made consistent with widgets)
 */
void drawCDKScreen (CDKSCREEN *cdkscreen)
{
   refreshCDKScreen (cdkscreen);
}

/*
 * Refresh one CDK window.
 * FIXME: this should be rewritten to use the panel library, so it would not
 * be necessary to touch the window to ensure that it covers other windows.
 */
void refreshCDKWindow (WINDOW *win)
{
   touchwin (win);
   wrefresh (win);
}

/*
 * This refreshes all the objects in the screen.
 */
void refreshCDKScreen (CDKSCREEN *cdkscreen)
{
   int objectCount = cdkscreen->objectCount;
   int x;
   int focused = -1;
   int visible = -1;

   refreshCDKWindow (cdkscreen->window);

   /* We erase all the invisible objects, then only
    * draw it all back, so that the objects
    * can overlap, and the visible ones will always
    * be drawn after all the invisible ones are erased */
   for (x = 0; x < objectCount; x++)
   {
      CDKOBJS *obj = cdkscreen->object[x];

      if (validObjType (obj, ObjTypeOf (obj)))
      {
	 if (obj->isVisible)
	 {
	    if (visible < 0)
	       visible = x;
	    if (obj->hasFocus && focused < 0)
	       focused = x;
	 }
	 else
	 {
	    obj->fn->eraseObj (obj);
	 }
      }
   }

   for (x = 0; x < objectCount; x++)
   {
      CDKOBJS *obj = cdkscreen->object[x];

      if (validObjType (obj, ObjTypeOf (obj)))
      {
	 obj->hasFocus = (x == focused);

	 if (obj->isVisible)
	 {
	    obj->fn->drawObj (obj, obj->box);
	 }
      }
   }
}

/*
 * This clears all the objects in the screen.
 */
void eraseCDKScreen (CDKSCREEN *cdkscreen)
{
   int objectCount = cdkscreen->objectCount;
   int x;

   /* We just call the drawObject function. */
   for (x = 0; x < objectCount; x++)
   {
      CDKOBJS *obj = cdkscreen->object[x];

      if (validObjType (obj, ObjTypeOf (obj)))
      {
	 obj->fn->eraseObj (obj);
      }
   }

   /* Refresh the screen. */
   wrefresh (cdkscreen->window);
}

/*
 * Destroy all of the objects on a screen
 */
void destroyCDKScreenObjects (CDKSCREEN *cdkscreen)
{
   int x;

   for (x = 0; x < cdkscreen->objectCount; x++)
   {
      CDKOBJS *obj = cdkscreen->object[x];
      int before = cdkscreen->objectCount;

      if (validObjType (obj, ObjTypeOf (obj)))
      {
	 MethodPtr (obj, eraseObj) (obj);
	 _destroyCDKObject (obj);
	 x -= (cdkscreen->objectCount - before);
      }
   }
}

/*
 * This destroys a CDK screen.
 */
void destroyCDKScreen (CDKSCREEN *screen)
{
   ALL_SCREENS *p, *q;

   for (p = all_screens, q = NULL; p != NULL; q = p, p = p->link)
   {
      if (screen == p->screen)
      {
	 if (q != NULL)
	    q->link = p->link;
	 else
	    all_screens = p->link;
	 free (p);
	 free (screen);
	 break;
      }
   }
}

/*
 * This is added to remain consistent.
 */
void endCDK (void)
{
   /* Turn echoing back on. */
   echo ();

   /* Turn off cbreak. */
   nocbreak ();

   /* End the curses windows. */
   endwin ();

#ifdef HAVE_XCURSES
   XCursesExit ();
#endif
}
