/*
 *  wmfs2 by Martin Duquesnoy <xorg62@gmail.com> { for(i = 2011; i < 2111; ++i) ©(i); }
 *  For license, see COPYING.
 */

#include "status.h"
#include "barwin.h"
#include "config.h"
#include "infobar.h"
#include "util.h"
#include "draw.h"

#include <string.h>

struct status_seq*
status_new_seq(char type, int narg, int minarg, char *args[], int *shift)
{
     struct status_seq *sq = xcalloc(1, sizeof(struct status_seq));

     SLIST_INIT(&sq->mousebinds);
     sq->type = type;
     *shift = 0;

     if(narg == minarg ||  !strcmp(args[0], "right") || !strcmp(args[0], "left"))
          sq->align = str_to_position(args[0]);
     else
     {
          sq->align = NoAlign;
          sq->geo.x = ATOI(args[0]);
          sq->geo.y = ATOI(args[1]);
          *shift = 1;
     }

     return sq;
}

struct status_ctx
status_new_ctx(struct barwin *b, struct theme *t)
{
     struct status_ctx ctx = { .barwin = b, .theme = t };

     SLIST_INIT(&ctx.statushead);
     SLIST_INIT(&ctx.gcache);

     return ctx;
}

static void
status_gcache_free(struct status_ctx *ctx)
{
     struct status_gcache *gc;

     while(!SLIST_EMPTY(&ctx->gcache))
     {
          gc = SLIST_FIRST(&ctx->gcache);
          SLIST_REMOVE_HEAD(&ctx->gcache, next);
          free(gc->datas);
          free(gc->name);
          free(gc);
     }
}

void
status_free_ctx(struct status_ctx *ctx)
{
     free(ctx->status);
     status_flush_list(ctx);
     status_gcache_free(ctx);
}

static void
status_graph_draw(struct status_ctx *ctx, struct status_seq *sq, struct status_gcache *gc)
{
     int max = 0;
     int i, j, y;
     float c;
     int ys = sq->geo.y + sq->geo.h - 1;

     /* If invalid maximum, we have to compute it ourselves */
     if(sq->data[2] <= 0)
     {
         for(i = sq->geo.x + sq->geo.w - 1, j = gc->ndata - 1;
             j >= 0 && i >= sq->geo.x;
             --j, --i)
         {
              if(gc->datas[j] > max)
                  max = gc->datas[j];
         }
     }
     else
        max = sq->data[2];

     XSetForeground(W->dpy, W->gc, sq->color2);

     for(i = sq->geo.x + sq->geo.w - 1, j = gc->ndata - 1;
         j >= 0 && i >= sq->geo.x;
         --j, --i)
     {
          /* You divided by zero didn't you? */
          if(gc->datas[j])
          {
               c = (float)max / (float)gc->datas[j];
               y = ys - (sq->geo.h / (c > 1 ? c : 1)) + 1;
               draw_line(ctx->barwin->dr, i, y, i, ys);
          }
     }
}

static void
status_graph_process(struct status_ctx *ctx, struct status_seq *sq, char *name)
{
     int j;
     struct status_gcache *gc;

     /* Graph already exist and have a cache */
     SLIST_FOREACH(gc, &ctx->gcache, next)
          if(!strcmp(name, gc->name))
          {
               /* shift buffer to remove unused old value */
               if(gc->ndata > (sq->geo.w << 1))
                    for(gc->ndata /= 2, j = 0;
                        j < gc->ndata;
                        gc->datas[j] = gc->datas[j + gc->ndata], ++j);

               gc->datas[gc->ndata++] = sq->data[1];
               status_graph_draw(ctx, sq, gc);
               return;
          }

     if(sq->data[1] > sq->data[2])
          sq->data[1] = sq->data[2];

     /* No? Make a cache for it */
     gc = xcalloc(1, sizeof(struct status_gcache));
     gc->name = xstrdup(name);
     gc->ndata = 1;
     gc->datas = xcalloc(sq->geo.w << 2, sizeof(int));
     gc->datas[0] = sq->data[1];

     SLIST_INSERT_HEAD(&ctx->gcache, gc, next);

     status_graph_draw(ctx, sq, gc);
}

/* Parse mousebind sequence next normal sequence: \<seq>[](button;func;cmd) */
static char*
status_parse_mouse(struct status_seq *sq, char *str)
{
     struct mousebind *m;
     char *end, *arg[3] = { NULL };
     int i;

     if(*str != '(' || !(end = strchr(str, ')')))
          return str;

     i = parse_args(++str, ';', ')', 3, arg);

     m = xcalloc(1, sizeof(struct mousebind));

     m->use_area = true;
     m->button   = ATOI(arg[0]);
     m->func     = uicb_name_func(arg[1]);
     m->cmd      = (i > 1 ? xstrdup(arg[2]) : NULL);

     SLIST_INSERT_HEAD(&sq->mousebinds, m, snext);

     return end + 1;
}

#define STATUS_CHECK_ARGS(i, n1, n2, str, end)  \
     if(i != n1 && i != n2)                     \
     {                                          \
          str = end + 2;                        \
          continue;                             \
     }
void
status_parse(struct status_ctx *ctx)
{
     struct status_seq *sq, *prev = NULL;
     int i, tmp, shift = 0;
     char *dstr = xstrdup(ctx->status), *sauv = dstr;
     char type, *p, *pp, *end, *arg[10] = { NULL };

     for(; *dstr; ++dstr)
     {
          /* Check if this is a sequence */
          if(*dstr != '^' && *dstr != '\\')
               continue;

          p = ++dstr;

          /* Search for correct end of sequence (] without \ behind) */
          if((end = strchr(p, ']')))
               while(*(end - 1) == '\\')
                    end = strchr(end + 1, ']');

          if(!(strchr("sRpPig", *p)) || !end)
               continue;

          /* Then parse & list it */
          switch((type = *p))
          {
          /*
           * Text sequence: \s[left/right;#color;text] OR \s[x;y;#color;text]
           */
          case 's':
               i = parse_args(p + 2,  ';', ']', 4, arg);
               STATUS_CHECK_ARGS(i, 2, 3, dstr, end);
               sq = status_new_seq(type, i, 2, arg, &shift);

               sq->color = color_atoh(arg[1 + shift]);
               sq->str = xstrdup(arg[2 + shift]);

               /* Remove \ from string */
               for(pp = sq->str; (pp = strchr(sq->str, '\\'));)
                    memmove(pp, pp + 1, strlen(pp));

               break;

         /*
          * Rectangle sequence: \R[left/right;w;h;#color] OR \R[x;y;w;h;#color]
          */
          case 'R':
               i = parse_args(p + 2, ';', ']', 5, arg);
               STATUS_CHECK_ARGS(i, 3, 4, dstr, end);
               sq = status_new_seq(type, i, 3, arg, &shift);

               sq->geo.w = ATOI(arg[1 + shift]);
               sq->geo.h = ATOI(arg[2 + shift]);
               sq->color = color_atoh(arg[3 + shift]);

               break;

          /*
           * Progress bar sequence: \p[left/right;w;h;bord;val;valmax;bg;fg] OR x;y
           * Position bar sequence: \P[left/right;w;h;tickbord;val;valmax;bg;fg] OR x;y
           */
          case 'p':
          case 'P':
               i = parse_args(p + 2, ';', ']', 9, arg);
               STATUS_CHECK_ARGS(i, 7, 8, dstr, end);
               sq = status_new_seq(type, i, 7, arg, &shift);

               sq->geo.w = ATOI(arg[1 + shift]);
               sq->geo.h = ATOI(arg[2 + shift]);

               sq->data[0] = ATOI(arg[3 + shift]);                     /* Border */
               sq->data[1] = ((tmp = ATOI(arg[4 + shift])) ? tmp : 1); /* Value */
               sq->data[2] = ATOI(arg[5 + shift]);                     /* Value Max */

               sq->color   = color_atoh(arg[6 + shift]);
               sq->color2  = color_atoh(arg[7 + shift]);

               break;

          /*
           * Graph sequence: \g[left/right;w;h;val;valmax;bg;fg;name] OR x;y
           */
          case 'g':
               i = parse_args(p + 2, ';', ']', 9, arg);
               STATUS_CHECK_ARGS(i, 7, 8, dstr, end);
               sq = status_new_seq(type, i, 7, arg, &shift);

               sq->geo.w = ATOI(arg[1 + shift]);
               sq->geo.h = ATOI(arg[2 + shift]);

               sq->data[1] = ATOI(arg[3 + shift]); /* Value */
               sq->data[2] = ATOI(arg[4 + shift]); /* Value Max */

               sq->color   = color_atoh(arg[5 + shift]);
               sq->color2  = color_atoh(arg[6 + shift]);

               sq->str = xstrdup(arg[7 + shift]);

               break;

          /*
           * Image sequence: \i[left/right;w;h;/path/img] OR \i[x;y;w;h;/path/img]
           */
#ifdef HAVE_IMLIB2
          case 'i':
               i = parse_args(p + 2, ';', ']', 5, arg);
               STATUS_CHECK_ARGS(i, 3, 4, dstr, end);
               sq = status_new_seq(type, i, 3, arg, &shift);

               sq->geo.w = ATOI(arg[1 + shift]);
               sq->geo.h = ATOI(arg[2 + shift]);
               sq->str   = xstrdup(arg[3 + shift]);

               break;
#endif /* HAVE_IMLIB2 */
          }

          if(sq->align == Right)
              SLIST_INSERT_HEAD(&ctx->statushead, sq, next);
          else
              SLIST_INSERT_TAIL(&ctx->statushead, sq, next, prev);

          /*
           * Optional mousebind sequence(s) \<seq>[](button;func;cmd)
           * Parse it while there is a mousebind sequence.
           */
          dstr = end + 1;

          do
               dstr = status_parse_mouse(sq, dstr);
          while(*dstr == '(');

          --dstr;

          prev = sq;
     }

     free(sauv);
}

#define STATUS_ALIGN(align)                                       \
     if(align == Left)                                            \
     {                                                            \
          sq->geo.x = left;                                       \
          left += sq->geo.w;                                      \
     }                                                            \
     else if(align == Right)                                      \
     {                                                            \
          sq->geo.x = ctx->barwin->geo.w - right - sq->geo.w;     \
          right += sq->geo.w;                                     \
     }

#define STORE_MOUSEBIND()                                       \
     if(!SLIST_EMPTY(&sq->mousebinds))                          \
          SLIST_FOREACH(m, &sq->mousebinds, snext)              \
               m->area = sq->geo;

#define NOALIGN_Y()                                                     \
     if(sq->align != NoAlign)                                           \
          sq->geo.y = (ctx->barwin->geo.h >> 1) - (sq->geo.h >> 1);
static void
status_apply_list(struct status_ctx *ctx)
{
     struct status_seq *sq;
     struct mousebind *m;
     struct geo g;
     int left = 0, right = 0, w, h;

     SLIST_FOREACH(sq, &ctx->statushead, next)
     {
          switch(sq->type)
          {
          /* Text */
          case 's':
               sq->geo.w = draw_textw(ctx->theme, sq->str);
               sq->geo.h = ctx->theme->font.height;

               if(sq->align != NoAlign)
                    sq->geo.y = TEXTY(ctx->theme, ctx->barwin->geo.h);

               STATUS_ALIGN(sq->align);

               draw_text(ctx->barwin->dr, ctx->theme, sq->geo.x, sq->geo.y, sq->color, sq->str);

               if(!SLIST_EMPTY(&sq->mousebinds))
                    SLIST_FOREACH(m, &sq->mousebinds, snext)
                    {
                         m->area = sq->geo;
                         m->area.y -= sq->geo.h;
                    }

               break;

          /* Rectangle */
          case 'R':
               NOALIGN_Y();
               STATUS_ALIGN(sq->align);

               draw_rect(ctx->barwin->dr, &sq->geo, sq->color);

               STORE_MOUSEBIND();

               break;

          /* Progress */
          case 'p':
               NOALIGN_Y();
               STATUS_ALIGN(sq->align);

               draw_rect(ctx->barwin->dr, &sq->geo, sq->color);

               /* Progress bar geo */
               g.x = sq->geo.x + sq->data[0];
               g.y = sq->geo.y + sq->data[0];
               g.w = sq->geo.w - sq->data[0] - sq->data[0];
               g.h = sq->geo.h - sq->data[0] - sq->data[0];

               if(sq->data[1] > sq->data[2])
                    sq->data[1] = sq->data[2];

               if(sq->geo.w > sq->geo.h)
                    g.w /= ((float)sq->data[2] / (float)sq->data[1]);
               else
               {
                    g.y += g.h;
                    g.h /= ((float)sq->data[2] / (float)sq->data[1]);
                    g.y -= g.h;
               }

               draw_rect(ctx->barwin->dr, &g, sq->color2);

               STORE_MOUSEBIND();

               break;

          /* Position */
          case 'P':
               NOALIGN_Y();
               STATUS_ALIGN(sq->align);

               draw_rect(ctx->barwin->dr, &sq->geo, sq->color);

               if(sq->data[1] > sq->data[2])
                    sq->data[1] = sq->data[2];

               g.x = sq->geo.x + ((sq->geo.w - sq->data[0]) / ((float)sq->data[2] / (float)sq->data[1]));
               g.y = sq->geo.y;
               g.w = sq->data[0];
               g.h = sq->geo.h;

               draw_rect(ctx->barwin->dr, &g, sq->color2);

               STORE_MOUSEBIND();

               break;

          /* Graph */
          case 'g':
               NOALIGN_Y();
               STATUS_ALIGN(sq->align);

               draw_rect(ctx->barwin->dr, &sq->geo, sq->color);

               status_graph_process(ctx, sq, sq->str);

               STORE_MOUSEBIND();

               break;

          /* Image */
#ifdef HAVE_IMLIB2
          case 'i':
               draw_image_load(sq->str, &w, &h);

               if(sq->geo.w <= 0)
                    sq->geo.w = w;
               if(sq->geo.h <= 0)
                    sq->geo.h = h;

               if(sq->align != NoAlign)
                    sq->geo.y = (ctx->barwin->geo.h >> 1) - (sq->geo.h >> 1);

               STATUS_ALIGN(sq->align);
               draw_image(ctx->barwin->dr, &sq->geo);
               STORE_MOUSEBIND();

               break;
#endif /* HAVE_IMLIB2 */

          }
     }
}

/* Render current statustext of an element */
void
status_render(struct status_ctx *ctx)
{
     if(!ctx->status)
          return;

     if(!(ctx->flags & STATUS_BLOCK_REFRESH))
          barwin_refresh_color(ctx->barwin);

     /* Use simple text instead sequence if no sequence found */
     if(SLIST_EMPTY(&ctx->statushead))
     {
          int l = draw_textw(ctx->theme, ctx->status);
          draw_text(ctx->barwin->dr, ctx->theme, ctx->barwin->geo.w - l,
                    TEXTY(ctx->theme, ctx->barwin->geo.h), ctx->barwin->fg, ctx->status);
     }
     else
          status_apply_list(ctx);

     barwin_refresh(ctx->barwin);
}

void
status_flush_list(struct status_ctx *ctx)
{
     struct status_seq *sq;
     struct mousebind *m;

     /* Flush previous linked list of status sequences */
     while(!SLIST_EMPTY(&ctx->statushead))
     {
          sq = SLIST_FIRST(&ctx->statushead);
          SLIST_REMOVE_HEAD(&ctx->statushead, next);

          while(!SLIST_EMPTY(&sq->mousebinds))
          {
               m = SLIST_FIRST(&sq->mousebinds);
               SLIST_REMOVE_HEAD(&sq->mousebinds, snext);
               free((void*)m->cmd);
               free(m);
          }

          free(sq->str);
          free(sq);
     }

     SLIST_INIT(&ctx->statushead);
}

void
status_copy_mousebind(struct status_ctx *ctx)
{
     struct mousebind *m;
     struct status_seq *sq;

     if(!ctx->barwin)
          return;

     /* Flush barwin head of status mousebinds */
     SLIST_INIT(&ctx->barwin->statusmousebinds);

     SLIST_FOREACH(sq, &ctx->statushead, next)
     {
          SLIST_FOREACH(m, &sq->mousebinds, snext)
               SLIST_INSERT_HEAD(&ctx->barwin->statusmousebinds, m, next);
     }
}

/* Parse and render statustext */
void
status_manage(struct status_ctx *ctx)
{
     if(!ctx->status)
          return;

     ctx->update = false;

     status_flush_list(ctx);
     status_parse(ctx);
     status_render(ctx);
     status_copy_mousebind(ctx);
}

void
status_flush_surface(void)
{
     struct barwin *b;

     while(!SLIST_EMPTY(&W->h.vbarwin))
     {
          b = SLIST_FIRST(&W->h.vbarwin);
          SLIST_REMOVE_HEAD(&W->h.vbarwin, vnext);
          barwin_remove(b);
     }
}

static void
status_surface(int x, int y, int w, int h, Color bg, char *status)
{
     struct barwin *b;
     struct screen *s;
     struct status_ctx ctx;
     int d;
     Window rw;

     if(!status)
          return;

     if(x + y < 0)
          XQueryPointer(W->dpy, W->root, &rw, &rw, &x, &y, &d, &d, (unsigned int *)&d);

     s = screen_gb_geo(x, y);

     if(x + w > s->geo.x + s->geo.w)
          x -= w;
     if(y + h > s->geo.y + s->geo.h)
          y -= h;

     b = barwin_new(W->root, x, y, w, h, 0, bg, false);
     barwin_map(b);

     /* Use client theme */
     ctx = status_new_ctx(b, W->ctheme);
     ctx.status = xstrdup(status);

     SLIST_INSERT_HEAD(&W->h.vbarwin, b, vnext);

     status_manage(&ctx);
     status_free_ctx(&ctx);
}

void
uicb_status_surface(Uicb cmd)
{
     char *p, *ccmd = xstrdup(cmd);
     int s, w, h, x = -1, y = -1;
     Color bg;

     if(!ccmd || !(p = strchr(ccmd, ' ')))
          return;

     *p = '\0';
     ++p;

     if(!(((s = sscanf(ccmd, "%d,%d,#%x", &w, &h, &bg)) == 3)
        || (s = sscanf(ccmd, "%d,%d,%d,%d,#%x", &x, &y, &w, &h, &bg)) == 5))
     {
          free(ccmd);
          return;
     }

     status_surface(x, y, w, h, bg, p);

     free(ccmd);
}

/* Syntax: "<infobar name> <status string>" */
void
uicb_status(Uicb cmd)
{
     struct infobar *ib;
     struct screen *s;
     char *p;

     if(!cmd || !(p = strchr(cmd, ' ')))
          return;

     /* Get infobar name & status */
     *p = '\0';
     ++p;

     SLIST_FOREACH(s, &W->h.screen, next)
     {
          SLIST_FOREACH(ib, &s->infobars, next)
               if(!strcmp(cmd, ib->name))
               {
                    free(ib->statusctx.status);
                    ib->statusctx.status = xstrdup(p);
                    ib->statusctx.update = true;
                    infobar_elem_screen_update(s, ElemStatus);
               }
     }
}
