#include "w3mimg/w3mimg.h"
#include <ctype.h>

void
w3mimg_exit(int status, FILE *out, const char *frmt, ...)
{
  va_list ap;

  va_start(ap, frmt);
  vfprintf(out, frmt, ap);
  fflush(out);
  va_end(ap);
  exit(status);
}

static W3MImage *imageHead;
static W3MImage *imageTail;
static W3MImage *imageFree;
static W3MImage **imageTab;
static int maxImageSize = (1U << (11 * 2 + 2));
static int imageSize;
static int maxImage;
static int nImage;

static W3MImage *
BsearchImage(int key, int *pos)
{
  int b, e, i, j;
  W3MImage *image = NULL;

  for (b = 0, e = j = nImage ; b < e ;) {
    i = (b + e) / 2;

    if (key < imageTab[i]->index)
      e = j = i;
    else if (key > imageTab[i]->index)
      b = i + 1;
    else {
      j = i;
      image = imageTab[i];
      break;
    }
  }

  if (pos)
    *pos = j;

  return image;
}

static void
UnlinkImage(W3MImage *image)
{
  if (image->prev)
    image->prev->next = image->next;
  else
    imageHead = image->next;

  if (image->next)
    image->next->prev = image->prev;
  else
    imageTail = image->prev;

  image->prev = image->next = NULL;
}

typedef struct _ImageQueue {
  W3MImage *image;
  char dirty_p, clear_p;
  int serial, sx, sy, sw, sh, x, y;
} ImageQueue;

static ImageQueue *q;
static int nq;
static int nq_max;

static int
BsearchQueuedImage(int ser)
{
  int b, e, i, j;

  for (b = 0, e = j = nq ; b < e ;) {
    i = (b + e) / 2;

    if (ser < q[i].serial)
      e = j = i;
    else if (ser > q[i].serial)
      b = i + 1;
    else
      return i;
  }

  return -(j + 1);
}

void
w3mimg_draw(w3mimg_desc *desc, char *buf)
{
  char *p = buf;
  int n = 0, ser = 0, x = 0, y = 0, w = 0, h = 0, sx = 0, sy = 0, sw = 0, sh = 0, pos, j;
  W3MImage *image;
  ImageQueue *iq;

  if (!p || !desc)
    return;

  for (; isdigit(*p); p++)
    n = 10 * n + (*p - '0');

  if (--n < 0 || *(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    ser = 10 * ser + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    x = 10 * x + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    y = 10 * y + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    w = 10 * w + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    h = 10 * h + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    sx = 10 * sx + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    sy = 10 * sy + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    sw = 10 * sw + (*p - '0');

  if (*(p++) != ';')
    return;

  for (; isdigit(*p); p++)
    sh = 10 * sh + (*p - '0');

  if (*(p++) != ';')
    return;

  desc->init_func(desc);

  if (!desc->priv)
    return;

  if (!(image = BsearchImage(n, &pos))) {
    int newImageSize;

    newImageSize = imageSize + w * h * desc->depth / CHAR_BIT;

    if (newImageSize > maxImageSize) {
      int tail, size;
      W3MImage *prev;

      for (image = imageTail ; image ; image = prev) {
	prev = image->prev;

	if (image->queued)
	  continue;

	BsearchImage(image->index, &tail);

	if (tail + 1 < nImage)
	  memmove(&imageTab[tail], &imageTab[tail + 1], sizeof(W3MImage *) * (nImage - tail - 1));

	--nImage;
	UnlinkImage(image);
	BsearchImage(n, &pos);

	if (image->pixmap) {
	  size = image->width * image->height * desc->depth / CHAR_BIT;
	  desc->free_func(desc, image);
	  image->pixmap = NULL;
	  imageSize -= size;
	  newImageSize -= size;

	  if (newImageSize <= maxImageSize)
	    goto set_image;
	}

	image->next = imageFree;
	imageFree = image;
      }
    }

    if (nImage >= maxImage) {
      maxImage = (nImage / 2 + 1) * 3;

      if (!(imageTab = imageTab ? realloc(imageTab, sizeof(W3MImage *) * maxImage) : malloc(sizeof(W3MImage *) * maxImage)))
	w3mimg_exit(1, stderr, "imageTab =malloc(sizeof(W3MImage *) * %d): %s\n", maxImage, strerror(errno));
    }

    if ((image = imageFree))
      imageFree = image->next;
    else if (!(image = malloc(sizeof(W3MImage))))
      w3mimg_exit(1, stderr, "malloc(sizeof(W3MImage)): %s\n", strerror(errno));

    image->pixmap = NULL;
    image->next = NULL;
  set_image:
    if ((image->prev = imageTail))
      imageTail->next = image;
    else
      imageHead = image;

    imageTail = image;

    if (pos < nImage)
      memmove(&imageTab[pos + 1], &imageTab[pos], sizeof(W3MImage *) * (nImage - pos));

    ++nImage;
    image->index = n;
    image->queued = 0;
    imageTab[pos] = image;
  }

  if (!image->pixmap) {
    desc->load_func(desc, image, p, w, h);

    if (!image->pixmap) {
      UnlinkImage(image);

      if (pos + 1 < nImage)
	memmove(&imageTab[pos], &imageTab[pos + 1], sizeof(W3MImage *) * (nImage - pos - 1));

      --nImage;
      image->next = imageFree;
      imageFree = image;
      return;
    }

    imageSize += image->width * image->height * desc->depth / CHAR_BIT;
  }

  if ((j = BsearchQueuedImage(ser)) < 0) {
    if (nq >= nq_max) {
      nq_max = (nq / 2 + 1) * 3;

      if (!(q = q ? realloc(q, sizeof(ImageQueue) * nq_max) : malloc(sizeof(ImageQueue) * nq_max)))
	w3mimg_exit(1, stderr, "q = malloc(%d): %s\n", sizeof(ImageQueue) * nq_max, strerror(errno));
    }

    j = -j - 1;

    if (j < nq)
      memmove(q + j + 1, q + j, sizeof(q[0]) * (nq - j));

    ++nq;
    iq = &q[j];
    iq->image = image;
    iq->serial = ser;
    iq->sx = sx;
    iq->sy = sy;
    iq->sw = sw;
    iq->sh = sh;
    iq->x = x;
    iq->y = y;
    image->queued = 1;
  }
  else
    iq = &q[j];

  iq->dirty_p = 1;
  iq->clear_p = 0;
  UnlinkImage(image);

  if ((image->next = imageHead))
    imageHead->prev = image;
  else
    imageTail = image;

  imageHead = image;
}

void
w3mimg_clear(w3mimg_desc *desc)
{
  W3MImage *image;

  if (imageTab) {
    int i;

    for (i = 0; i < nImage; i++) {
      if (imageTab[i]->pixmap)
	desc->free_func(desc, imageTab[i]);

      free(imageTab[i]);
    }

    free(imageTab);
    imageTab = NULL;
  }

  imageHead = imageTail = NULL;

  if (imageFree)
    do {
      image = imageFree;
      imageFree = image->next;
      free(image);
    } while (imageFree);

  nImage = maxImage = imageSize = 0;
  nq = 0;

  if (desc->clear_func)
    desc->clear_func(desc);
}

void
w3mimg_flush(w3mimg_desc *desc)
{
  int i, j, sw, sh;
  ImageQueue *p;

  desc->clipped = 0;

  if (desc->flush_start_hook && !desc->flush_start_hook(desc))
    return;

  for (i = j = 0 ; i < nq ;) {
    p = &q[i++];
    sw = p->sw ? p->sw : p->image->width;
    sh = p->sh ? p->sh : p->image->height;

    if (p->clear_p)
      p->image->queued = 0;
    else {
      if (desc->alldirty || p->dirty_p)
	desc->show_func(desc, p->image, p->sx, p->sy, sw, sh, p->x + desc->offset_x, p->y + desc->offset_y);

      p->dirty_p = desc->clipped && desc->clip_test(desc, p->image, p->x + desc->offset_x, p->y + desc->offset_y, sw, sh);
      q[j++] = *p;
    }
  }

  nq = j;

  if (desc->flush_end_hook)
    desc->flush_end_hook(desc);
}

void
w3mimg_clear_one(char *p)
{
  int ser, i;

  if (!p)
    return;

  for (ser = 0; isdigit(*p); p++)
    ser = 10 * ser + (*p - '0');

  if ((i = BsearchQueuedImage(ser)) >= 0 && !q[i].clear_p)
    q[i].dirty_p = q[i].clear_p = 1;
}

#ifndef PIPE_BUF
#define PIPE_BUF (512)
#endif
static char buf[PIPE_BUF];
static char *bob = buf;
static char *eob = buf;

int
w3mimg_gets(w3mimg_desc *desc, w3mimg_line *l, int doread)
{
  int n, llen;
  char *eol, *bol;

  if (eob > bob)
    n = eob - bob;
  else {
    bob = eob = buf;

    if (!doread)
      return w3mimg_line_continued;
    else if ((n = read(desc->stdin_fd, buf, sizeof(buf))) > 0)
      eob = buf + n;
    else if (!n)
      return w3mimg_line_eof;
    else if (errno == EINTR)
      return w3mimg_line_continued;
    else {
      w3mimg_exit(1, stderr, "read(): %s\n", strerror(errno));
      return w3mimg_line_eof;
    }
  }

  bol = bob;

  if ((eol = memchr(bob, '\n', n))) {
    llen = eol - bob;
    bob = eol + 1;
  }
  else {
    llen = n;
    bob = eob;
  }

  if (l->n + llen >= l->n_max) {
    l->n_max = ((l->n + llen) / 2 + 1) * 3;

    if (!(l->v = l->v ? realloc(l->v, l->n_max) : malloc(l->n_max)))
      w3mimg_exit(1, stderr, "l->v = malloc(%d): %s\n", l->n_max, strerror(errno));
  }

  memcpy(l->v + l->n, bol, llen);
  l->v[l->n += llen] = '\0';

  if (eol && l->n && l->v[l->n - 1] == '\r')
    --(l->n);

  return eol ? w3mimg_line_eol : w3mimg_line_continued;
}

void
w3mimg_get_option(int argc, char **argv, w3mimg_desc *desc, w3mimg_opts *opts)
{
  int i;

  for (i = 1; i < argc; i++)
    if (!strcmp("-bg", argv[i])) {
      if (++i >= argc)
	w3mimg_exit(1, stderr, "No argument to ``-bg''\n");

      opts->background = argv[i];
      opts->defined_bg = 1;
    }
    else if (!strcmp("-x", argv[i])) {
      if (++i >= argc)
	w3mimg_exit(1, stderr, "No argument to ``-x''\n");

      desc->offset_x = atoi(argv[i]);
      opts->defined_x = 1;
    }
    else if (!strcmp("-y", argv[i])) {
      if (++i >= argc)
	w3mimg_exit(1, stderr, "No argument to ``-y''\n");

      desc->offset_y = atoi(argv[i]);
      opts->defined_y = 1;
    }
    else if (!strcmp("-test", argv[i]))
      opts->defined_test = 1;
    else if (!strcmp("-debug", argv[i]))
      opts->defined_debug = 1;
    else if (!strcmp("-ignore_exposure", argv[i]))
      opts->defined_ignore_exposure = 1;
    else if (!strcmp("-sizemax", argv[i])) {
      if (++i >= argc)
	w3mimg_exit(1, stderr, "No argument to ``-sizemax''\n");

      maxImageSize = atoi(argv[i]);
    }
    else if (!strcmp("-xflush_delay", argv[i]) ||
	     !strcmp("-flush_delay", argv[i])) {
      if (++i >= argc)
	w3mimg_exit(1, stderr, "No argument to ``%s''\n", argv[i - 1]);

      desc->flush_delay = atoi(argv[i]);
    }
    else
      w3mimg_exit(1, stderr, "Unknown option ``%s''\n", argv[i]);
}

w3mimg_desc *
w3mimg_open(w3mimg_desc *desc, w3mimg_opts *opts)
{
  static w3mimg_desc *(*w3mimg_open_funcs[])(w3mimg_desc *, w3mimg_opts *) = {
#ifdef USE_W3MIMG_X11
    w3mimg_x11open,
#endif
#ifdef USE_W3MIMG_FB
    w3mimg_fbopen,
#endif
    NULL,
  };
  w3mimg_desc *(**p)(w3mimg_desc *, w3mimg_opts *);
  w3mimg_desc *res;

  for (p = w3mimg_open_funcs ; *p ; ++p)
    if ((res = (*p)(desc, opts)))
      return res;

  return NULL;
}
