#include <cdk_int.h>

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

#define limitFocusIndex(screen, value) \
 	(((value) >= (screen)->objectCount || (value) < 0) \
	 ? 0 \
	 : (value))

static int getFocusIndex (CDKSCREEN *screen)
{
   int result = limitFocusIndex (screen, screen->objectFocus);

   return result;
}

static void setFocusIndex (CDKSCREEN *screen, int value)
{
   screen->objectFocus = limitFocusIndex (screen, value);
}

static void unsetFocus (CDKOBJS *obj)
{
   curs_set (0);
   if (obj != NULL)
   {
      HasFocusObj (obj) = FALSE;
      UnfocusObj (obj);
   }
}

static void setFocus (CDKOBJS *obj)
{
   if (obj != NULL)
   {
      HasFocusObj (obj) = TRUE;
      FocusObj (obj);
   }
   curs_set (1);
}

static CDKOBJS *switchFocus (CDKOBJS *newobj, CDKOBJS *oldobj)
{
   if (oldobj != newobj)
   {
      unsetFocus (oldobj);
      setFocus (newobj);
   }
   return newobj;
}

static boolean checkMenuKey (int keyCode, int functionKey)
{
   int result;

   result = (keyCode == KEY_ESC && !functionKey);

   return result;
}

static CDKOBJS *handleMenu (CDKSCREEN *screen, CDKOBJS *menu, CDKOBJS *oldobj)
{
   bool done = FALSE;
   CDKOBJS *newobj;

   switchFocus (menu, oldobj);
   while (!done)
   {
      boolean functionKey;
      int key = getchCDKObject (menu, &functionKey);

      switch (key)
      {
      case KEY_TAB:
	 done = TRUE;
	 break;

      case KEY_ESC:
	 /* cleanup the menu */
	 (void)injectCDKMenu ((CDKMENU *)menu, (chtype)key);
	 done = TRUE;
	 break;

      default:
	 done = (injectCDKMenu ((CDKMENU *)menu, (chtype)key) >= 0);
	 break;
      }
   }

   if ((newobj = getCDKFocusCurrent (screen)) == NULL)
      newobj = setCDKFocusNext (screen);

   return switchFocus (newobj, menu);
}

/*
 * Save data in widgets on a screen
 */
static void saveDataCDKScreen (CDKSCREEN *screen)
{
   int i;

   for (i = 0; i < screen->objectCount; ++i)
      SaveDataObj (screen->object[i]);
}

/*
 * Refresh data in widgets on a screen
 */
static void refreshDataCDKScreen (CDKSCREEN *screen)
{
   int i;

   for (i = 0; i < screen->objectCount; ++i)
      RefreshDataObj (screen->object[i]);
}


/*
 * ======================================================================
 * Public Interface
 */

void resetCDKScreen (CDKSCREEN *screen)
{
   refreshDataCDKScreen (screen);
}

void exitOKCDKScreen (CDKSCREEN *screen)
{
   screen->exitStatus = CDKSCREEN_EXITOK;
}

void exitCancelCDKScreen (CDKSCREEN *screen)
{
   screen->exitStatus = CDKSCREEN_EXITCANCEL;
}

void exitOKCDKScreenOf (CDKOBJS *obj)
{
   exitOKCDKScreen (obj->screen);
}

void exitCancelCDKScreenOf (CDKOBJS *obj)
{
   exitCancelCDKScreen (obj->screen);
}

void resetCDKScreenOf (CDKOBJS *obj)
{
   resetCDKScreen (obj->screen);
}

/*
 * Returns the object on which the focus lies.
 */
CDKOBJS *getCDKFocusCurrent (CDKSCREEN *screen)
{
   CDKOBJS *result = NULL;
   int n = screen->objectFocus;

   if (n >= 0 && n < screen->objectCount)
      result = screen->object[n];
   return result;
}

/*
 * Set focus to the next object, returning it.
 */
CDKOBJS *setCDKFocusNext (CDKSCREEN *screen)
{
   CDKOBJS *result = NULL;
   CDKOBJS *curobj;
   int n = getFocusIndex (screen);
   int first = n;

   for (;;)
   {
      if (++n >= screen->objectCount)
	 n = 0;
      curobj = screen->object[n];
      if (curobj != NULL && AcceptsFocusObj (curobj))
      {
	 result = curobj;
	 break;
      }
      else
      {
	 if (n == first)
	 {
	    break;
	 }
      }
   }

   setFocusIndex (screen, (result != NULL) ? n : -1);
   return result;
}

/*
 * Set focus to the previous object, returning it.
 */
CDKOBJS *setCDKFocusPrevious (CDKSCREEN *screen)
{
   CDKOBJS *result = NULL;
   CDKOBJS *curobj;
   int n = getFocusIndex (screen);
   int first = n;

   for (;;)
   {
      if (--n < 0)
	 n = screen->objectCount - 1;
      curobj = screen->object[n];
      if (curobj != NULL && AcceptsFocusObj (curobj))
      {
	 result = curobj;
	 break;
      }
      else if (n == first)
      {
	 break;
      }
   }

   setFocusIndex (screen, (result != NULL) ? n : -1);
   return result;
}

/*
 * Set focus to a specific object, returning it.
 * If the object cannot be found, return null.
 */
CDKOBJS *setCDKFocusCurrent (CDKSCREEN *screen, CDKOBJS *newobj)
{
   CDKOBJS *result = NULL;
   CDKOBJS *curobj;
   int n = getFocusIndex (screen);
   int first = n;

   for (;;)
   {
      if (++n >= screen->objectCount)
	 n = 0;

      curobj = screen->object[n];
      if (curobj == newobj)
      {
	 result = curobj;
	 break;
      }
      else if (n == first)
      {
	 break;
      }
   }

   setFocusIndex (screen, (result != NULL) ? n : -1);
   return result;
}

/*
 * Set focus to the first object in the screen.
 */
CDKOBJS *setCDKFocusFirst (CDKSCREEN *screen)
{
   setFocusIndex (screen, screen->objectCount - 1);
   return switchFocus (setCDKFocusNext (screen), NULL);
}

/*
 * Set focus to the last object in the screen.
 */
CDKOBJS *setCDKFocusLast (CDKSCREEN *screen)
{
   setFocusIndex (screen, 0);
   return switchFocus (setCDKFocusPrevious (screen), NULL);
}

void traverseCDKOnce (CDKSCREEN *screen,
		      CDKOBJS *curobj,
		      int keyCode,
		      boolean functionKey,
		      CHECK_KEYCODE funcMenuKey)
{
   switch (keyCode)
   {
   case KEY_BTAB:
      switchFocus (setCDKFocusPrevious (screen), curobj);
      break;

   case KEY_TAB:
      switchFocus (setCDKFocusNext (screen), curobj);
      break;

   case KEY_F (10):
      /* save data and exit */
      exitOKCDKScreen (screen);
      break;

   case CTRL ('X'):
      exitCancelCDKScreen (screen);
      break;

   case CTRL ('R'):
      /* reset data to defaults */
      resetCDKScreen (screen);
      setFocus (curobj);
      break;

   case CDK_REFRESH:
      /* redraw screen */
      refreshCDKScreen (screen);
      setFocus (curobj);
      break;

   default:
      /* not everyone wants menus, so we make them optional here */
      if (funcMenuKey != NULL && funcMenuKey (keyCode, functionKey))
      {
	 /* find and enable drop down menu */
	 int j;

	 for (j = 0; j < screen->objectCount; ++j)
	    if (ObjTypeOf (screen->object[j]) == vMENU)
	    {
	       handleMenu (screen, screen->object[j], curobj);
	       break;
	    }
      }
      else
      {
	 InjectObj (curobj, (chtype)keyCode);
      }
      break;
   }
}

/*
 * Traverse the widgets on a screen.
 */
int traverseCDKScreen (CDKSCREEN *screen)
{
   int result = 0;
   CDKOBJS *curobj = setCDKFocusFirst (screen);

   if (curobj != NULL)
   {
      refreshDataCDKScreen (screen);

      screen->exitStatus = CDKSCREEN_NOEXIT;

      while (((curobj = getCDKFocusCurrent (screen)) != NULL)
	     && (screen->exitStatus == CDKSCREEN_NOEXIT))
      {
	 int key;
	 boolean function;

	 key = getchCDKObject (curobj, &function);

	 traverseCDKOnce (screen, curobj, key, function, checkMenuKey);
      }

      if (screen->exitStatus == CDKSCREEN_EXITOK)
      {
	 saveDataCDKScreen (screen);
	 result = 1;
      }
   }
   return result;
}
