/* Functionality for a bar across the bottom of the screen listing the
 * windows currently managed.
 *
 * Copyright (C) 2000, 2001, 2002, 2003, 2004 Shawn Betts <sabetts@vcn.bc.ca>
 *
 * This file is part of ratpoison.
 *
 * ratpoison is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * ratpoison is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this software; see the file COPYING.  If not, write to
 * the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307 USA
 */

#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#ifdef USE_XFT_FONT
#include <X11/Xft/Xft.h>
#endif

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

#include "ratpoison.h"

#include "assert.h"

/* Possible values for bar_is_raised status. */
#define BAR_IS_HIDDEN  0
#define BAR_IS_WINDOW_LIST 1
#define BAR_IS_MESSAGE     2

/* A copy of the last message displayed in the message bar. */
static char *last_msg = NULL;
static int last_mark_start = 0;
static int last_mark_end = 0;

static void marked_message_internal (char *msg, int mark_start, int mark_end);

/* Reset the alarm to auto-hide the bar in BAR_TIMEOUT seconds. */
static void
reset_alarm (void)
{
  alarm (defaults.bar_timeout);
  alarm_signalled = 0;
}

/* Hide the bar from sight. */
int
hide_bar (rp_screen *s)
{
  if (s->bar_is_raised)
    {
      s->bar_is_raised = 0;
      XUnmapWindow (dpy, s->bar_window);

      /* Possibly restore colormap. */
      if (current_window())
	{
	  XUninstallColormap (dpy, s->def_cmap);
	  XInstallColormap (dpy, current_window()->colormap);
	}

      return 1;
    }

  return 0;
}

/* Show window listing in bar. */
int
show_bar (rp_screen *s, char *fmt)
{
  if (!s->bar_is_raised)
    {
      s->bar_is_raised = BAR_IS_WINDOW_LIST;
      XMapRaised (dpy, s->bar_window);
      update_window_names (s, fmt);

      /* Switch to the default colormap */
      if (current_window())
	XUninstallColormap (dpy, current_window()->colormap);
      XInstallColormap (dpy, s->def_cmap);

      reset_alarm();
      return 1;
    }

  /* If the bar is raised we still need to display the window
     names. */
  update_window_names (s, fmt);
  return 0;
}

int
bar_x (rp_screen *s, int width)
{
  int x = 0;

  switch (defaults.bar_location)
    {
    case NorthWestGravity:
    case WestGravity:
    case SouthWestGravity:
      x = s->left + (defaults.bar_in_padding ? 0 : defaults.padding_left);
      break;
    case NorthGravity:
    case CenterGravity:
    case SouthGravity:
      x = s->left + (s->width - width - defaults.bar_border_width * 2) / 2
          - (defaults.bar_in_padding ? 0 : defaults.padding_left);
      break;
    case NorthEastGravity:
    case EastGravity:
    case SouthEastGravity:
      x = s->left + s->width - width - defaults.bar_border_width * 2
          - (defaults.bar_in_padding ? 0 : defaults.padding_right);
      break;
    }

  return x;
}

int
bar_y (rp_screen *s, int height)
{
  int y = 0;

  switch (defaults.bar_location)
    {
    case NorthEastGravity:
    case NorthGravity:
    case NorthWestGravity:
      y = s->top + (defaults.bar_in_padding ? 0 : defaults.padding_top);
      break;
    case EastGravity:
    case CenterGravity:
    case WestGravity:
      y = s->top + (s->height - height
           - defaults.bar_border_width * 2) / 2
	   - (defaults.bar_in_padding ? 0 : defaults.padding_top);
      break;
    case SouthEastGravity:
    case SouthGravity:
    case SouthWestGravity:
      y = s->top + (s->height - height
           - defaults.bar_border_width * 2)
 	   - (defaults.bar_in_padding ? 0 : defaults.padding_top);
      break;
    }

  return y;
}

void
update_bar (rp_screen *s)
{
  if (s->bar_is_raised == BAR_IS_WINDOW_LIST) {
    update_window_names (s, defaults.window_fmt);
    return;
  }

  if (s->bar_is_raised == BAR_IS_HIDDEN)
    return;

  redraw_last_message();
}

/* Note that we use marked_message_internal to avoid resetting the
   alarm. */
void
update_window_names (rp_screen *s, char *fmt)
{
  struct sbuf *bar_buffer;
  int mark_start = 0;
  int mark_end = 0;

  if (s->bar_is_raised != BAR_IS_WINDOW_LIST) return;

  bar_buffer = sbuf_new (0);

  if(defaults.window_list_style == STYLE_ROW)
    {
      get_window_list (fmt, NULL, bar_buffer, &mark_start, &mark_end);
      marked_message_internal (sbuf_get (bar_buffer), mark_start, mark_end);
    }
  else
    {
      get_window_list (fmt, "\n", bar_buffer, &mark_start, &mark_end);
      marked_message_internal (sbuf_get (bar_buffer), mark_start, mark_end);
    }


/*   marked_message (sbuf_get (bar_buffer), mark_start, mark_end); */
  sbuf_free (bar_buffer);
}

void
message (char *s)
{
  marked_message (s, 0, 0);
}

void
marked_message_printf (int mark_start, int mark_end, char *fmt, ...)
{
  char *buffer;
  va_list ap;

  va_start (ap, fmt);
  buffer = xvsprintf (fmt, ap);
  va_end (ap);

  marked_message (buffer, mark_start, mark_end);
  free (buffer);
}

static int
count_lines (char* msg, int len)
{
  int ret = 1;
  int i;

  if (len < 1)
    return 1;

  for(i=0; i<len; i++)
    {
      if (msg[i] == '\n') ret++;
    }

  return ret;
}


static int
max_line_length (char* msg)
{
  rp_screen *s = current_screen ();
  size_t i;
  size_t start;
  int ret = 0;

  /* Count each line and keep the length of the longest one. */
  for(start=0, i=0; i <= strlen(msg); i++)
    {
      if(msg[i] == '\n' || msg[i] == '\0')
        {
          int current_width;

          /* Check if this line is the longest so far. */
          current_width = rp_text_width (s, defaults.font, msg + start, i - start);
          if(current_width > ret)
            {
              ret = current_width;
            }

          /* Update the start of the new line. */
          start = i + 1;
        }
    }

  return ret;
}

static int
pos_in_line (char* msg, int pos)
{
  int ret;
  int i;

  if(pos <= 0)
    return 0;

  /* Go backwards until we hit the beginning of the string or a new
     line. */
  ret = 0;
  for(i=pos-1; i>=0; ret++, i--)
    {
      if(msg[i]=='\n')
        break;
    }

  return ret;
}

static int
line_beginning (char* msg, int pos)
{
  int ret = 0;
  int i;

  if(pos <= 0)
    return 0;

  /* Go backwards until we hit a new line or the beginning of the
     string. */
  for(i=pos-1; i>=0; --i)
    {
      if (msg[i]=='\n')
        {
          ret = i + 1;
          break;
        }
    }

  return ret;
}

static void
draw_partial_string (rp_screen *s, char *msg, int line_no, int start, int end, int style)
{
  int line_height = FONT_HEIGHT (s);

  rp_draw_string (s, s->bar_window, style,
                  defaults.bar_x_padding,
                  defaults.bar_y_padding + FONT_ASCENT(s)
                  + line_no * line_height,
                  msg + start, end - start + 1);
}

static void
draw_string (rp_screen *s, char *msg, int mark_start, int mark_end)
{
  XGCValues lgv;
  GC lgc;
  unsigned long mask;
  size_t i;
  int line_no;
  int start;
  int style = STYLE_NORMAL, update = 0;

  lgv.foreground = s->fg_color;
  mask = GCForeground;
  lgc = XCreateGC(dpy, s->root, mask, &lgv);

  /* Walk through the string, print each line. */
  start = 0;
  line_no = 0;
/*   if (mark_start == 0 && mark_end == 0) */
/*     mark_start = mark_end = -1; */

  for(i=0; i < strlen(msg); ++i)
    {
      if (i == (size_t)mark_start)
        {
          style = STYLE_INVERSE;
          update = 1;
        }
      if (i == (size_t)mark_end)
        {
          style = STYLE_NORMAL;
          update = 1;
        }
      if (msg[i] == '\n')
        update = 2;

      if (update)
        {
          draw_partial_string (s, msg, line_no, start, update == 2 ? i-1:i, style);
          start = i;
          if (update == 2)
            {
              line_no++;
              start++;
            }
          update = 0;
        }
    }

  /* Print the last line. */
  draw_partial_string (s, msg, line_no, start, strlen (msg)-1, style);
  XSync (dpy, False);
}

/* Move the marks if they are outside the string or if the start is
   after the end. */
static void
correct_mark (int msg_len, int *mark_start, int *mark_end)
{
  /* Make sure the marks are inside the string. */
  if (*mark_start < 0)
    *mark_start = 0;

  if (*mark_end < 0)
    *mark_end = 0;

  if (*mark_start > msg_len)
    *mark_start = msg_len;

  if (*mark_end > msg_len)
    *mark_end = msg_len;

  /* Make sure the marks aren't reversed. */
  if (*mark_start > *mark_end)
    {
      int tmp;
      tmp = *mark_start;
      *mark_start = *mark_end;
      *mark_end = tmp;
    }

}

/* Raise the bar and put it in the right spot */
static void
prepare_bar (rp_screen *s, int width, int height)
{
  width = width < s->width ? width : s->width;
  height = height < s->height ? height : s->height;
  XMoveResizeWindow (dpy, s->bar_window,
                     bar_x (s, width), bar_y (s, height),
                     width, height);

  /* Map the bar if needed */
  if (!s->bar_is_raised)
    {
      s->bar_is_raised = BAR_IS_MESSAGE;
      XMapRaised (dpy, s->bar_window);

      /* Switch to the default colormap */
      if (current_window())
	XUninstallColormap (dpy, current_window()->colormap);
      XInstallColormap (dpy, s->def_cmap);
    }

  XRaiseWindow (dpy, s->bar_window);
  XClearWindow (dpy, s->bar_window);
  XSync (dpy, False);
}

static void
get_mark_box (char *msg, size_t mark_start, size_t mark_end,
              int *x, int *y, int *width, int *height)
{
  rp_screen *s = current_screen ();
  int start, end;
  int mark_end_is_new_line = 0;
  int start_line;
  int end_line;
  int start_pos_in_line;
  int end_pos_in_line;
  int start_line_beginning;
  int end_line_beginning;

  /* If the mark_end is on a new line or the end of the string, then
     back it up one character. */
  if (msg[mark_end-1] == '\n' || mark_end == strlen (msg))
    {
      mark_end--;
      mark_end_is_new_line = 1;
    }

  start_line = count_lines(msg, mark_start);
  end_line = count_lines(msg, mark_end);

  start_pos_in_line = pos_in_line(msg, mark_start);
  end_pos_in_line = pos_in_line(msg, mark_end);

  start_line_beginning = line_beginning(msg, mark_start);
  end_line_beginning = line_beginning(msg, mark_end);

  PRINT_DEBUG (("start_line = %d, end_line = %d\n", start_line, end_line));
  PRINT_DEBUG (("start_line_beginning = %d, end_line_beginning = %d\n",
                start_line_beginning, end_line_beginning));

  if (mark_start == 0 || start_pos_in_line == 0)
    start = 0;
  else
    start = rp_text_width (s, defaults.font,
                           &msg[start_line_beginning],
                           start_pos_in_line) + defaults.bar_x_padding;
   
  end = rp_text_width (s, defaults.font,
                       &msg[end_line_beginning],
                       end_pos_in_line) + defaults.bar_x_padding * 2;

  if (mark_end != strlen (msg))
    end -= defaults.bar_x_padding;

  /* A little hack to highlight to the end of the line, if the
     mark_end is at the end of a line. */
  if (mark_end_is_new_line)
    {
      *width = max_line_length(msg) + defaults.bar_x_padding * 2;
    }
  else
    {
      *width = end - start;
    }

  *x = start;
  *y = (start_line - 1) * FONT_HEIGHT (s) + defaults.bar_y_padding;
  *height = (end_line - start_line + 1) * FONT_HEIGHT (s);
}

static void
draw_box (rp_screen *s, int x, int y, int width, int height)
{
  XGCValues lgv;
  GC lgc;
  unsigned long mask;

  lgv.foreground = s->fg_color;
  mask = GCForeground;
  lgc = XCreateGC(dpy, s->root, mask, &lgv);

  XFillRectangle (dpy, s->bar_window, lgc,
                  x, y, width, height);
  XFreeGC (dpy, lgc);
}

static void
draw_mark (rp_screen *s, char *msg, int mark_start, int mark_end)
{
  int x, y, width, height;

  /* when this happens, there is no mark. */
  if (mark_end == 0 || mark_start == mark_end)
    return;

  get_mark_box (msg, mark_start, mark_end,
                &x, &y, &width, &height);
  draw_box (s, x, y, width, height);
}

static void
update_last_message (char *msg, int mark_start, int mark_end)
{
  if (last_msg)
    free (last_msg);
  last_msg = xstrdup (msg);
  last_mark_start = mark_start;
  last_mark_end = mark_end;
}

void
marked_message (char *msg, int mark_start, int mark_end)
{
  /* Schedule the bar to be hidden after some amount of time. */
  reset_alarm ();
  marked_message_internal (msg, mark_start, mark_end);
}

static void
marked_message_internal (char *msg, int mark_start, int mark_end)
{
  rp_screen *s = current_screen ();
  int num_lines;
  int width;
  int height;

  PRINT_DEBUG (("msg = %s\n", msg?msg:"NULL"));
  PRINT_DEBUG (("mark_start = %d, mark_end = %d\n", mark_start, mark_end));

  /* Calculate the width and height of the window. */
  num_lines = count_lines (msg, strlen(msg));
  width = defaults.bar_x_padding * 2 + max_line_length(msg);
  height = FONT_HEIGHT (s) * num_lines + defaults.bar_y_padding * 2;

  prepare_bar (s, width, height);

  /* Draw the mark over the designated part of the string. */
  correct_mark (strlen (msg), &mark_start, &mark_end);
  draw_mark (s, msg, mark_start, mark_end);

  draw_string (s, msg, mark_start, mark_end);

  /* Keep a record of the message. */
  update_last_message (msg, mark_start, mark_end);
}

/* Use this just to update the bar. show_last_message will draw it and
   leave it up for a period of time. */
void
redraw_last_message (void)
{
  char *msg;

  if (last_msg == NULL) return;

  /* A little kludge to avoid last_msg in marked_message from being
     strdup'd right after freeing the pointer. Note: in this case
     marked_message's msg arg would have been the same as
     last_msg.  */
  msg = xstrdup (last_msg);
  marked_message_internal (msg, last_mark_start, last_mark_end);
  free (msg);
}

void
show_last_message (void)
{
  redraw_last_message();
  reset_alarm();
}

/* Free any memory associated with the bar. */
void
free_bar (void)
{
  if (last_msg)
    free (last_msg);
}
