#include "panel_app.h"

MBPanelApp*
panel_app_list_get_prev (MBPanel     *panel, 
			 MBPanelApp  *papp, 
			 MBPanelApp **list_head)
{
  MBPanelApp *tmp = *list_head;
  
  if (tmp == NULL || papp == tmp) return NULL;

  while (tmp->next != NULL)
    {
      if (tmp->next == papp) return tmp;
      tmp = tmp->next;
    }
  
  return NULL;
}

MBPanelApp*
panel_app_list_get_last (MBPanel    *panel, 
			 MBPanelApp *list_head)
{
  MBPanelApp *tmp = list_head;
  
  if (tmp == NULL) return NULL;

  while (tmp->next != NULL) tmp = tmp->next;

  return tmp;
}

MBPanelApp*
panel_app_list_prepend(MBPanel     *panel, 
		       MBPanelApp  *list_to_append_to,
		       MBPanelApp  *papp_new)
{
  papp_new->next = list_to_append_to;
  return papp_new;
}

void
panel_app_list_append (MBPanel     *panel, 
		       MBPanelApp **list_to_append_to,
		       MBPanelApp  *new_client)
{
  MBPanelApp *tmp = NULL;
  if (*list_to_append_to == NULL)
    {
      *list_to_append_to = new_client;
    }
  else
    {
      tmp = *list_to_append_to;
      while ( tmp->next != NULL ) tmp = tmp->next;
      tmp->next = new_client;
    }

  new_client->next = NULL;
}

void 	/* XXX Can probably go */
panel_app_list_insert_after(MBPanel    *panel, 
			    MBPanelApp *papp, 
			    MBPanelApp *new_papp)
{
  MBPanelApp *tmp;
  tmp = papp->next;
  papp->next = new_papp;
  new_papp->next = tmp;
}

void
panel_app_list_remove (MBPanel     *panel, 
		       MBPanelApp  *papp,
		       MBPanelApp **list_head)
{
  MBPanelApp *prev_papp = panel_app_list_get_prev(panel, papp, list_head);

  if (prev_papp == NULL) 
    {
      *list_head = papp->next;
    }
  else
    {
      prev_papp->next = papp->next;
    }
  return;
}

void
panel_app_name_get(MBPanel    *panel, 
		   MBPanelApp *papp)
{
  Atom type;
  int format;
  long bytes_after;
  long n_items;
  int result;

  result =  XGetWindowProperty (panel->dpy, papp->win, 
				panel->atoms[ATOM_NET_WM_NAME],
				0, 1024L,
				False, panel->atoms[ATOM_UTF8_STRING],
				&type, &format, &n_items,
				&bytes_after, (unsigned char **)&papp->name);

  if (result != Success 
      ||  papp->name == NULL 
      ||  type != panel->atoms[ATOM_UTF8_STRING] 
      || format != 8 
      || n_items == 0)
    {
      if (papp->name) XFree (papp->name);

      XFetchName(panel->dpy, papp->win, (char **)&papp->name);
      if (papp->name == NULL) {
	XStoreName(panel->dpy, papp->win, "<unnamed>");
	XFetchName(panel->dpy, papp->win, (char **)&papp->name);
	if (papp->name == NULL) 
	  papp->name = strdup("<unnamed>");
      }
    }
}

Window
panel_app_get_client_leader_win(MBPanel *panel, MBPanelApp *papp)
{
  Atom realType;
  unsigned long n;
  unsigned long extra;
  int format;
  int status;
  Window* value = NULL, win_result = None;
  
  status = XGetWindowProperty(panel->dpy, papp->win,
			      panel->atoms[ATOM_WM_CLIENT_LEADER],
			      0L, 16L,
			      0, XA_WINDOW, &realType, &format,
			      &n, &extra, (unsigned char **) &value);
  if (status == Success && realType == XA_WINDOW 
      && format == 32 && n == 1 && value != NULL)
    {
      win_result = (Window) value[0];
    }
  
  if (value) XFree(value);

  return win_result;
}

int*
panel_app_icon_prop_data_get(MBPanel *d, MBPanelApp *papp)
{
  Atom type;
  int format;
  long bytes_after;
  unsigned char *data = NULL;
  long n_items;
  int result;

  result =  XGetWindowProperty (d->dpy, papp->win, 
			        d->atoms[ATOM_NET_WM_ICON],
				0, 100000L,
				False, XA_CARDINAL,
				&type, &format, &n_items,
				&bytes_after, (unsigned char **)&data);

  if (result != Success || data == NULL)
    {
      if (data) XFree (data);
      DBG("%s() failed for %s (XID: %li)\n", __func__, papp->name, papp->win);
      return NULL;
    }

  return (int *)data;
}

void
panel_app_add_start(MBPanel *panel, MBPanelApp *papp_new)
{
  MBPanelApp *papp_prev;

  papp_prev = panel_app_list_get_last(panel, panel->apps_start_head);

  panel_app_list_append (panel, &panel->apps_start_head, papp_new);

  if (papp_prev != NULL)
    {
      papp_new->offset = panel_app_get_offset(panel, papp_prev) 
	+ panel_app_get_size(panel, papp_prev);
      DBG("%s() got %i = %i + %i\n", __func__, papp_new->offset, 
	  panel_app_get_offset(panel, papp_prev) ,
	  panel_app_get_size(panel, papp_prev) );
    }
  else
    papp_new->offset = 0;

  papp_new->gravity = PAPP_GRAVITY_START;
}

void
panel_app_add_end(MBPanel *panel, MBPanelApp *papp_new)
{
  MBPanelApp *papp_prev;

  papp_prev = panel_app_list_get_last(panel, panel->apps_end_head);

  panel_app_list_append (panel, &panel->apps_end_head, papp_new);

  if (papp_prev != NULL)
    papp_new->offset = panel_app_get_offset(panel, papp_prev) 
      - panel_app_get_size(panel, papp_new);
  else
    papp_new->offset = ( PANEL_IS_VERTICAL(panel) ? panel->h : panel->w ) 
      - panel_app_get_size(panel, papp_new);


  papp_new->gravity = PAPP_GRAVITY_END;
}

Bool
panel_app_check_for_space (MBPanel *panel, MBPanelApp *papp)
{
  MBPanelApp *papp_end = NULL, *papp_start = NULL;

  papp_end  = panel_app_list_get_last(panel, panel->apps_end_head);
  papp_start = panel_app_list_get_last(panel, panel->apps_start_head);

  if (papp_end && papp_start &&
      panel_app_get_offset(panel, papp_start) 
      + panel_app_get_size(panel, papp_start) 
      > panel_app_get_offset(panel, papp_end))
    {
      panel_handle_full_panel(panel, papp);
      return False;
    }
  return True;
}

MBPanelApp *
panel_app_new(MBPanel *panel, Window win, char *cmd_str, Bool from_session)
{
  MBPanelApp        *papp;
  XWindowAttributes  attr;
  int                padding = 0;
  Bool               add_at_start = False;

  papp = NEW(MBPanelApp);

  papp->name   = NULL;
  papp->next   = NULL;
  papp->win    = win;
  papp->panel  = panel;
  papp->ignore = False;
  papp->ignore_unmap = 0;
  papp->icon   = NULL;

  /* XXX should check we actually get this */
  XGetWindowAttributes(panel->dpy, win, &attr);  

  papp->w = attr.width;
  papp->h = attr.height;

  if (session_preexisting_restarting(panel) && !panel->session_run_first_time && from_session)
    {
      if (panel->session_cur_gravity == PAPP_GRAVITY_START)
	add_at_start = True;
    }
  else if ( ((attr.x < 0) && !PANEL_IS_VERTICAL(panel))
	    || ((attr.y < 0) && PANEL_IS_VERTICAL(panel)))
    {
      add_at_start = True;
    }

  /* Assume a square app unless it has already asked to be bigger.
     Psion's max-size.patch makes the same allowance for height against
     0.8.1; this code moved in 0.9.3, so the patch no longer applies and
     the allowance is made here for both axes.  gpe-buttonbox depends on
     it: it sizes itself to (slots + fixed_slots) * slot_width before
     docking, and is otherwise clipped to one square slot, which is why
     Psion's bottom bar showed nine buttons and an unpatched panel shows
     only "Desk". */

  if (panel->orientation == North || panel->orientation == South)
    {
      papp->h = panel->h - (2*panel->margin_topbottom);
      /* Only a client that is dramatically wider than a slot keeps its
         own width -- gpe-buttonbox asks for (slots+fixed)*slot_width,
         which is many slots across.  Ordinary tray applets stay square
         and ask for any other size through a ConfigureRequest, which
         the handler below honours; letting their initial width through
         here instead makes them sprawl. */
      papp->w = (attr.width >= papp->h * 4) ? attr.width : papp->h;
    }
  else
    {
      papp->w = panel->w - (2*panel->margin_topbottom); 
      papp->h = (attr.height >= papp->w * 4) ? attr.height : papp->w;
    }


  if (add_at_start)
    {
      if (panel->apps_start_head == NULL)
	padding = panel->margin_sides;
      else
	padding = panel->padding;

      panel_app_add_start(panel, papp);
    }
  else
    {
      if (panel->apps_end_head == NULL)
	padding = -1 * panel->margin_sides;
      else
	padding = -1 * panel->padding;

      panel_app_add_end(panel, papp); 
    }



  DBG("%s() papp offset at %i\n", __func__, papp->offset );

  panel_app_name_get(panel, papp);

  papp->cmd_str = cmd_str;

  if (panel->orientation == North || panel->orientation == South)
    {
      papp->y = (panel->h - papp->h) / 2;
      papp->x = papp->offset + padding;
    }
  else
    {
      papp->x = (panel->w - papp->w) / 2;
      papp->y = papp->offset + padding;
    }

  if (!panel_app_check_for_space(panel, papp))
    return NULL;
  
  XResizeWindow(panel->dpy, papp->win, papp->w, papp->h);
  XReparentWindow(panel->dpy, papp->win, panel->win, papp->x, papp->y);
  panel_app_deliver_config_event(panel, papp);

  panel_update_client_list_prop (panel);

  return papp;
}

void
panel_app_handle_configure_request(MBPanel *panel, XConfigureRequestEvent *ev)
{
  XWindowChanges xwc;
  MBPanelApp *papp = NULL;

  papp = panel_app_get_from_window( panel, ev->window );

  if (panel->is_hidden) return;

  if (papp != NULL)
    {
      DBG("%s() config req  x: %i , y: %i w: %i h: %i for %s\n", 
	  __func__, ev->x, ev->y, ev->width, ev->height, papp->name );

      DBG("%s() panel is w: %i  %i h:\n", 
	  __func__, panel->w, panel->h );

      /*
      if (papp->x == ev->x && papp->y == ev->y 
	  && papp->h == ev->height && papp->w == ev->width)
	return;
      */

      if (panel->orientation == North || panel->orientation == South)
	{
	  xwc.width  = ev->width;
	  xwc.height = panel->h - (2*panel->margin_topbottom);

	  if (ev->width == ev->height) /* app wants to be square */
	    {
	      xwc.width  = xwc.height;
	    }

	  papp->y = (panel->h - papp->h) / 2;

	  if (xwc.width != papp->w)	/* Handle width changes */
	    {
	      if (papp == panel_app_list_get_last(panel, panel->apps_end_head))
		{
		  DBG("%s() papp %s is last at end\n", __func__, papp->name);
		  panel_app_move_to(panel, papp, 
				    papp->x - (xwc.width - papp->w));
		}
	      else if (papp == panel_app_list_get_last(panel, 
						       panel->apps_start_head))
		{
		  DBG("%s() papp %s is last at start\n", __func__, papp->name);
		  panel_app_move_to(panel, papp, 
				    papp->x /*+ (xwc.width - papp->w)*/);
		}
	      else if (papp == panel->apps_end_head)
		{
		  DBG("******** %s() papp %s panel->apps_end_head ****\n", __func__, papp->name);
		  panel_app_move_to(panel, papp, 
				    panel->w - panel->margin_sides - xwc.width);
		  panel_apps_nudge (panel, papp->next, papp->w - xwc.width);
		}
	      else
		{
		  /* An END-gravity applet somewhere in the middle of the
		   * row. Its neighbours are packed against both of its
		   * edges, so one of them has to give: the convention for
		   * END gravity is that an applet grows away from the panel
		   * edge it is docked to -- its RIGHT edge stays put and
		   * everything to its left shuffles along.
		   *
		   * That takes both calls below. The move keeps this
		   * applet's right edge where it was; the nudge moves the
		   * ones further left (papp->next onwards -- the END list
		   * runs right to left) by the same amount, so the row stays
		   * packed.
		   *
		   * It used to do neither: no move at all, and a nudge with
		   * the sign inverted. A middle applet that grew therefore
		   * ate its right-hand neighbour, while its left-hand ones
		   * marched the other way and ate IT. Nothing had ever
		   * resized from this position -- the clock is
		   * apps_end_head and the taskbar is START gravity, so both
		   * take branches above -- until
		   * mb-applet-system-monitor started growing a third bar
		   * when the SD card gets a swapfile, and fused itself with
		   * the volume applet.
		   *
		   * Both branches above already pair a move with a nudge for
		   * the positions they handle; this one now matches them. */
		  DBG("%s() papp %s is mid end-list\n", __func__, papp->name);
		  panel_app_move_to(panel, papp,
				    papp->x - (xwc.width - papp->w));
		  panel_apps_nudge (panel, papp->next, papp->w - xwc.width);
		}
	      papp->w = xwc.width;
	    }

	  xwc.x = papp->x; 	      /* NOT allowed to move themselves */
	  xwc.y = papp->y;

	} else { 		/* East / West orientated dock */

	  xwc.width  = panel->w - (2*panel->margin_topbottom);
	  xwc.height = ev->height;

	  if (ev->width == ev->height) /* app wants to be square */
	    xwc.height  = xwc.width;

	  papp->x = (panel->w - papp->w) / 2;
	  xwc.x = papp->x;
	  xwc.y = papp->y;

	  if (xwc.height != papp->h)	/* Handle width changes */
	    {
	      if (papp == panel_app_list_get_last(panel, panel->apps_end_head))
		{
		  panel_app_move_to(panel, papp, 
				    papp->y - (xwc.height - papp->h));
		}
	      else if (papp == panel_app_list_get_last(panel, 
						       panel->apps_start_head))
		{
		  panel_app_move_to(panel, papp, 
				    papp->y /* + (xwc.height - papp->h) */);
		}
	      else
		{
		  panel_apps_nudge (panel, papp->next, xwc.height - papp->h);
		}
	      papp->h = xwc.height;
	    }
	}

      xwc.border_width = 0;
      xwc.sibling      = None;
      xwc.stack_mode   = None;

      DBG("%s() setting x: %i , y: %i w: %i h: %i \n", 
	  __func__, xwc.x, xwc.y, xwc.width, xwc.height  );

      XConfigureWindow(panel->dpy, papp->win, ev->value_mask, &xwc);
    }
}

void
panel_app_deliver_config_event(MBPanel *panel, MBPanelApp *papp)
{
   XConfigureEvent ce;

   if (panel->is_hidden) return;
   
   ce.type = ConfigureNotify;
   ce.event  = papp->win;
   ce.window = papp->win;

   if (PANEL_IS_VERTICAL(panel))
     {
       papp->x = (panel->w - papp->w) / 2;
       ce.x = papp->x;
       ce.y = papp->y;
       ce.width  = papp->w;
       ce.height = papp->h;
     } else {
       papp->y = (panel->h - papp->h) / 2;
       ce.x = papp->x;
       ce.y = papp->y; //  + panel->y;
       ce.width  = papp->w;
       ce.height = papp->h;
     }
   
   ce.border_width = 0;
   ce.above =  panel->win;
   ce.override_redirect = 0;

   DBG("%s() delivering x: %i , y: %i w: %i h: %i name : %s\n", 
       __func__, ce.x, ce.y, ce.width, ce.height, papp->name  );
   
   XSendEvent(panel->dpy, papp->win, False,
	      StructureNotifyMask, (XEvent *)&ce);
}

void 
panel_app_move_to (MBPanel    *panel, 
		   MBPanelApp *papp, 
		   int         origin_offset)
{
  if (panel->orientation == North || panel->orientation == South)
    {
      papp->x = origin_offset;
      papp->y = (panel->h - papp->h) / 2;
    }
  else
    {
      papp->y = origin_offset;
      papp->x = (panel->w - papp->w) / 2;
    }

  papp->offset = origin_offset;

  if (!panel_app_check_for_space(panel, papp))
    return;

  XMoveWindow(panel->dpy, papp->win, papp->x, papp->y);      
}

/*
 * Re-pack both gravity lists from scratch, exactly as docking would have
 * laid them out at the panel's current length and orientation.
 *
 * WHY NOT NUDGE. panel_apps_nudge() shifts one list by a delta, which is
 * right for a local change (an applet grew, an applet left) but wrong for
 * a change to the panel itself: it only ever walks ONE of the two lists --
 * the caller picks start or end -- so a resize moves half the row and
 * leaves the other half where it was. Do that once per rotation and the
 * two halves march apart in opposite directions, a few hundred pixels at
 * a time, until every applet is outside the panel and the bar looks
 * empty. Found exactly that way after the panel learned to come back from
 * portrait (see panel_handle_screen_rotation): the applet windows were
 * still mapped and still children of the panel, sitting at y=1200 and
 * y=-604 on a 640-tall panel.
 *
 * Offsets here are derived, never accumulated, so this is idempotent and
 * self-healing: whatever state the row drifted into, one call puts it
 * back. The arithmetic deliberately mirrors panel_app_add_start() and
 * panel_app_add_end() -- START packs up from 0 in list order, END packs
 * down from the far end -- so a re-layout and a fresh dock cannot
 * disagree.
 */
void
panel_apps_relayout (MBPanel *panel)
{
  MBPanelApp *papp;
  int         length = PANEL_IS_VERTICAL(panel) ? panel->h : panel->w;
  int         offset;

  offset = 0;
  for (papp = panel->apps_start_head; papp != NULL; papp = papp->next)
    {
      panel_app_move_to (panel, papp, offset);
      offset += panel_app_get_size (panel, papp);
      panel_app_deliver_config_event (panel, papp);
    }

  offset = length;
  for (papp = panel->apps_end_head; papp != NULL; papp = papp->next)
    {
      offset -= panel_app_get_size (panel, papp);
      panel_app_move_to (panel, papp, offset);
      panel_app_deliver_config_event (panel, papp);
    }
}

void
panel_apps_nudge (MBPanel    *panel,
		  MBPanelApp *papp,
		  int         amount)
{
  MBPanelApp *papp_cur = papp;

  while (papp_cur != NULL)
    {
      panel_app_move_to (panel, papp_cur, 
			 panel_app_get_offset(panel, papp_cur) + amount);
      papp_cur = papp_cur->next;
    }
}

void
panel_apps_rescale (MBPanel    *panel, 
		    MBPanelApp *papp)
{
  MBPanelApp *papp_cur = papp;

  while (papp_cur != NULL)
    {
      papp->h = panel->h - (2*panel->margin_topbottom);
      XResizeWindow(panel->dpy, papp->win, papp->w, papp->h);
      panel_app_deliver_config_event(panel, papp);
      papp_cur = papp_cur->next;
    }
}

void
panel_app_destroy (MBPanel    *panel, 
		   MBPanelApp *papp)
{
   MBMenuItem *tmp = NULL;
   
   if (!papp) return;
      
   /* remove popup menu entry  XXX this functionaility should be in mbmenu */
   if (panel->remove_menu)
     tmp = panel->remove_menu->items;
   
   while (tmp != NULL)
     {
       if ((MBPanelApp *)tmp->cb_data == papp)
	 {
	   mb_menu_item_remove(panel->mbmenu, panel->remove_menu, tmp);
	   break;
	 }
       tmp = tmp->next_item;
     }

   if (papp->gravity ==  PAPP_GRAVITY_START)
     {
       panel_apps_nudge(panel, papp->next, 
			-1 *  panel_app_get_size(panel, papp));
       panel_app_list_remove(panel, papp, &panel->apps_start_head);
     }
   else
     {
       panel_apps_nudge(panel, papp->next, panel_app_get_size(panel, papp));
       panel_app_list_remove(panel, papp, &panel->apps_end_head);
     }

   if (papp->name) XFree(papp->name);

   if (papp->cmd_str) free(papp->cmd_str);

   if (papp->icon) mb_pixbuf_img_free(panel->pb, papp->icon);

   free(papp);

   panel_update_client_list_prop (panel);
}

/* Utilities */

int
panel_app_get_offset (MBPanel    *panel, 
		      MBPanelApp *papp)
{
  if (panel->orientation == East || panel->orientation == West)
    return papp->y;
  else
    return papp->x;
}

int
panel_app_get_size (MBPanel    *panel, 
		    MBPanelApp *papp)
{
  if (panel->orientation == East || panel->orientation == West)
    return papp->h;
  else
    return papp->w;
}

MBPanelApp*
panel_app_get_from_window (MBPanel *panel, 
			   Window   win)
{
   MBPanelApp *papp = panel->apps_start_head;
   DBG("%s() called, looking for win %li\n", __func__, win);

   while( papp != NULL)
   {
     DBG("%s() check %s ( %li )\n", __func__, papp->name, papp->win);
      if (papp->win == win) return papp;
      papp = papp->next;
   }

   papp = panel->apps_end_head;

   while( papp != NULL)
   {
     // DBG("%s() check %s ( %li )\n", __func__, papp->name, papp->win);
      if (papp->win == win) return papp;
      papp = papp->next;
   }

   return NULL;
}

