#include "config.h"

#ifdef USE_GRAPHIC
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef USE_NLLIBC
#include <nllibc.h>
#endif

#include "const.h"
#include "nlllib.h"
#include "memory.h"
#include "image.h"

static image_t images[GRAPHIC_IMAGE_MAXNUM];
static int image_num = 0;

int image_init(void)
{
  image_num = 0;
  memset(images, 0, sizeof(images));
  return 0;
}

int image_done(void)
{
  int index;
  image_t image;

  for (index = 0; index < GRAPHIC_IMAGE_MAXNUM; index++) {
    image = images[index];
    if (image) {
      if (image->flags & NLL_G_CONST)
	image->flags &= ~NLL_G_CONST;
      image_destroy(image);
    }
  }

  return 0;
}

int image_check(void)
{
  int index;

  if (image_num)
    return NLL_ERRCODE_MEMORY_NOT_EMPTY;

  for (index = 0; index < GRAPHIC_IMAGE_MAXNUM; index++) {
    if (images[index])
      return NLL_ERRCODE_MEMORY_NOT_EMPTY;
  }

  return 0;
}

static image_t _alloc(void)
{
  image_t image;
  if ((image = memory_alloc(sizeof(*image))) == NULL)
    return NULL;
  return image;
}

static int _free(image_t image)
{
  memory_free(image);
  return 0;
}

int image_is_draw_line(image_t image, image_draw_count_t count, int y)
{
  if (!image->draw.line)
    return 0;
  return (image->draw.line[y] >= count) ? 1 : 0;
}

int image_is_draw_column(image_t image, image_draw_count_t count, int x, int y)
{
  int i;
  if (!image->draw.column)
    return 0;
  i = image->draw.column_num * y + (x / image->draw.column_per);
  return (image->draw.column[i] >= count) ? 1 : 0;
}

int image_is_draw_pixel(image_t image, image_draw_count_t count, int x, int y)
{
  if (!image->draw.pixel)
    return (image->flags & NLL_G_NDRAWPIXEL) ? 1 : 0;
  return (image->draw.pixel[image->width * y + x] >= count) ? 1 : 0;
}

int image_set_draw_line(image_t image, image_draw_count_t count, int y)
{
  if (!image->draw.line)
    return 0;
  image->draw.line[y] = count;
  return 0;
}

int image_set_draw_column(image_t image, image_draw_count_t count, int x, int y)
{
  int i;
  if (!image->draw.column)
    return 0;
  i = image->draw.column_num * y + (x / image->draw.column_per);
  image->draw.column[i] = count;
  return 0;
}

int image_set_draw_pixel(image_t image, image_draw_count_t count, int x, int y)
{
  if (!image->draw.pixel)
    return 0;
  image->draw.pixel[image->width * y + x] = count;
  return 0;
}

static int word_index(image_t image, int width, int x, int y, int *indexp, int *offsetp)
{
  if (indexp)
    *indexp = width * y + x;
  if (offsetp)
    *offsetp = 0;

  return 0;
}

static int fullcolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return image->bitmap.word[index];
}

static int fullcolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  image->bitmap.word[index] = color;
  return 0;
}

static int fullcolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return image->copy.bitmap.word[index];
}

static int bit_index(image_t image, int width, int x, int y, int *indexp, int *offsetp)
{
  int bits, w;

  bits = image->procs->bytesize * 8;
  w = (width + (bits - 1)) / bits;

  if (indexp)
    *indexp = w * y + (x / bits);
  if (offsetp)
    *offsetp = x % bits;

  return 0;
}

static int monocolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return (image->bitmap.word[index] & (1 << offset)) ? image->color.fg : image->color.bg;
}

static int monocolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset, mask;

  image->procs->index(image, image->width, x, y, &index, &offset);
  mask = (1 << offset);

  image->bitmap.word[index] &= ~mask;
  if (color > 0)
    image->bitmap.word[index] |= mask;

  return 0;
}

static int monocolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return (image->copy.bitmap.word[index] & (1 << offset)) ? image->color.fg : image->color.bg;
}

static int monobcolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return (image->bitmap.byte[index] & (1 << offset)) ? image->color.fg : image->color.bg;
}

static int monobcolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset, mask;

  image->procs->index(image, image->width, x, y, &index, &offset);
  mask = (1 << offset);

  image->bitmap.byte[index] &= ~mask;
  if (color > 0)
    image->bitmap.byte[index] |= mask;

  return 0;
}

static int monobcolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return (image->copy.bitmap.byte[index] & (1 << offset)) ? image->color.fg : image->color.bg;
}

static int nibble_index(image_t image, int width, int x, int y, int *indexp, int *offsetp)
{
  int bits, w;

  bits = image->procs->bytesize * 2;
  w = (width + (bits - 1)) / bits;

  if (indexp)
    *indexp = w * y + (x / bits);
  if (offsetp)
    *offsetp = (x % bits) * 4;

  return 0;
}

static int basiccolor_to_fullcolor(int basic_color)
{
  int r, g, b;

  if (basic_color & 0x8)
    return -1;

  r = (basic_color >> 2) & 0x1;
  g = (basic_color >> 1) & 0x1;
  b = (basic_color >> 0) & 0x1;

  r = r ? 0xff : 0;
  g = g ? 0xff : 0;
  b = b ? 0xff : 0;

  return (r << 16) | (g << 8) | b;
}

static int fullcolor_to_basiccolor(int full_color)
{
  int r, g, b;

  if (full_color < 0)
    return 0x8;

  r = (full_color >> 16) & 0xff;
  g = (full_color >>  8) & 0xff;
  b = (full_color >>  0) & 0xff;

  r = (r >> 7) & 0x1;
  g = (g >> 7) & 0x1;
  b = (b >> 7) & 0x1;

  return (r << 2) | (g << 1) | b;
}

static int basiccolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return basiccolor_to_fullcolor((image->bitmap.byte[index] >> offset) & 0xf);
}

static int basiccolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset;

  image->procs->index(image, image->width, x, y, &index, &offset);

  image->bitmap.byte[index] &= ~(0xf << offset);
  if (color > 0)
    image->bitmap.byte[index] |= (fullcolor_to_basiccolor(color) << offset);

  return 0;
}

static int basiccolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return basiccolor_to_fullcolor((image->copy.bitmap.byte[index] >> offset) & 0xf);
}

static int byte_index(image_t image, int width, int x, int y, int *indexp, int *offsetp)
{
  if (indexp)
    *indexp = width * y + x;
  if (offsetp)
    *offsetp = 0;

  return 0;
}

static int roughcolor_to_fullcolor(int rough_color)
{
  int r, g, b;

  if (rough_color & 0x80)
    return -1;

  r = (rough_color >> 4) & 0x3;
  g = (rough_color >> 2) & 0x3;
  b = (rough_color >> 0) & 0x3;

  r = r ? ((r << 6) | 0x3f) : 0;
  g = g ? ((g << 6) | 0x3f) : 0;
  b = b ? ((b << 6) | 0x3f) : 0;

  return (r << 16) | (g << 8) | b;
}

static int fullcolor_to_roughcolor(int full_color)
{
  int r, g, b;

  if (full_color < 0)
    return 0x80;

  r = (full_color >> 16) & 0xff;
  g = (full_color >>  8) & 0xff;
  b = (full_color >>  0) & 0xff;

  r = (r >> 6) & 0x3;
  g = (g >> 6) & 0x3;
  b = (b >> 6) & 0x3;

  return (r << 4) | (g << 2) | b;
}

static int roughcolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return roughcolor_to_fullcolor(image->bitmap.byte[index]);
}

static int roughcolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  image->bitmap.byte[index] = fullcolor_to_roughcolor(color);
  return 0;
}

static int roughcolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return roughcolor_to_fullcolor(image->copy.bitmap.byte[index]);
}

static int graycolor_to_fullcolor(int gray_color)
{
  int r, g, b;

  if (gray_color & 0x80)
    return -1;

  r = g = b = gray_color ? ((gray_color << 1) | 0x1) : 0;

  return (r << 16) | (g << 8) | b;
}

static int fullcolor_to_graycolor(int full_color)
{
  int r, g, b;

  if (full_color < 0)
    return 0x80;

  r = (full_color >> 16) & 0xff;
  g = (full_color >>  8) & 0xff;
  b = (full_color >>  0) & 0xff;

  return (((r + g + b) / 3) >> 1) & 0x7f;
}

static int graycolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return graycolor_to_fullcolor(image->bitmap.byte[index]);
}

static int graycolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  image->bitmap.byte[index] = fullcolor_to_graycolor(color);
  return 0;
}

static int graycolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return graycolor_to_fullcolor(image->copy.bitmap.byte[index]);
}

static int half_index(image_t image, int width, int x, int y, int *indexp, int *offsetp)
{
  if (indexp)
    *indexp = width * y + x;
  if (offsetp)
    *offsetp = 0;

  return 0;
}

static int halfcolor_to_fullcolor(int half_color)
{
  int r, g, b;

  if (half_color & 0x8000)
    return -1;

  r = (half_color >> 10) & 0x1f;
  g = (half_color >>  5) & 0x1f;
  b = (half_color >>  0) & 0x1f;

  r = r ? ((r << 3) | 0x7) : 0;
  g = g ? ((g << 3) | 0x7) : 0;
  b = b ? ((b << 3) | 0x7) : 0;

  return (r << 16) | (g << 8) | b;
}

static int fullcolor_to_halfcolor(int full_color)
{
  int r, g, b;

  if (full_color < 0)
    return 0x8000;

  r = (full_color >> 16) & 0xff;
  g = (full_color >>  8) & 0xff;
  b = (full_color >>  0) & 0xff;

  r = (r >> 3) & 0x1f;
  g = (g >> 3) & 0x1f;
  b = (b >> 3) & 0x1f;

  return (r << 10) | (g << 5) | b;
}

static int halfcolor_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return halfcolor_to_fullcolor(image->bitmap.half[index]);
}

static int halfcolor_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  image->bitmap.half[index] = fullcolor_to_halfcolor(color);
  return 0;
}

static int halfcolor_copy_get_color(image_t image, int x, int y)
{
  int index, offset;
  image->procs->index(image, image->width, x, y, &index, &offset);
  return halfcolor_to_fullcolor(image->copy.bitmap.half[index]);
}

static int word_copy_save(image_t image, int x, int y, int width, int height)
{
  int i, index0, index1;

  for (i = 0; i < height; i++) {
    image->procs->index(image, image->width, x            , y + i, &index0, NULL);
    image->procs->index(image, image->width, x + width - 1, y + i, &index1, NULL);
    memcpy(&image->copy.bitmap.word[index0], &image->bitmap.word[index0],
	   (index1 - index0 + 1) * image->procs->bytesize);
  }

  return 0;
}

static int word_copy_save_color(image_t image, int x, int y)
{
  int index;
  image->procs->index(image, image->width, x, y, &index, NULL);
  image->copy.bitmap.word[index] = image->bitmap.word[index];
  return 0;
}

static int half_copy_save(image_t image, int x, int y, int width, int height)
{
  int i, index0, index1;

  for (i = 0; i < height; i++) {
    image->procs->index(image, image->width, x            , y + i, &index0, NULL);
    image->procs->index(image, image->width, x + width - 1, y + i, &index1, NULL);
    memcpy(&image->copy.bitmap.half[index0], &image->bitmap.half[index0],
	   (index1 - index0 + 1) * image->procs->bytesize);
  }

  return 0;
}

static int half_copy_save_color(image_t image, int x, int y)
{
  int index;
  image->procs->index(image, image->width, x, y, &index, NULL);
  image->copy.bitmap.half[index] = image->bitmap.half[index];
  return 0;
}

static int byte_copy_save(image_t image, int x, int y, int width, int height)
{
  int i, index0, index1;

  for (i = 0; i < height; i++) {
    image->procs->index(image, image->width, x            , y + i, &index0, NULL);
    image->procs->index(image, image->width, x + width - 1, y + i, &index1, NULL);
    memcpy(&image->copy.bitmap.byte[index0], &image->bitmap.byte[index0],
	   (index1 - index0 + 1) * image->procs->bytesize);
  }

  return 0;
}

static int byte_copy_save_color(image_t image, int x, int y)
{
  int index;
  image->procs->index(image, image->width, x, y, &index, NULL);
  image->copy.bitmap.byte[index] = image->bitmap.byte[index];
  return 0;
}

static struct image_procs image_procs[] = {
  { sizeof(int), word_index,
    fullcolor_get_color, fullcolor_set_color, fullcolor_copy_get_color,
    word_copy_save, word_copy_save_color
  },
  { sizeof(int), bit_index,
    monocolor_get_color, monocolor_set_color, monocolor_copy_get_color,
    word_copy_save, word_copy_save_color
  },
  { sizeof(unsigned char), bit_index,
    monobcolor_get_color, monobcolor_set_color, monobcolor_copy_get_color,
    byte_copy_save, byte_copy_save_color
  },
  { sizeof(unsigned char), nibble_index,
    basiccolor_get_color, basiccolor_set_color, basiccolor_copy_get_color,
    byte_copy_save, byte_copy_save_color
  },
  { sizeof(unsigned char), byte_index,
    roughcolor_get_color, roughcolor_set_color, roughcolor_copy_get_color,
    byte_copy_save, byte_copy_save_color
  },
  { sizeof(unsigned char), byte_index,
    graycolor_get_color, graycolor_set_color, graycolor_copy_get_color,
    byte_copy_save, byte_copy_save_color
  },
  { sizeof(unsigned short), half_index,
    halfcolor_get_color, halfcolor_set_color, halfcolor_copy_get_color,
    half_copy_save, half_copy_save_color
  },
  { 0, NULL, NULL, NULL, NULL, NULL }
};

int image_get_color(image_t image, int x, int y)
{
  if ((x < 0) || (x > image->width  - 1) ||
      (y < 0) || (y > image->height - 1))
    return -1;
  return image->procs->get_color(image, x, y);
}

int image_set_color(image_t image, int x, int y, int color, unsigned int flags)
{
  int c, set;

  if ((x < 0) || (x > image->width  - 1) ||
      (y < 0) || (y > image->height - 1))
    return -1;

  if (image->flags & NLL_G_RDONLY)
    return 0;

  set = flags & NLL_G_SET_MASK;

  switch (set) {
  case NLL_G_SET:
    break;
  default:
    c = image->procs->get_color(image, x, y);
    switch (set) {
    case NLL_G_AND: color &= c; break;
    case NLL_G_OR:  color |= c; break;
    case NLL_G_XOR: color ^= c; break;
    default:
      break;
    }
  }

  if (color < 0)
    color = -1;

  return image->procs->set_color(image, x, y, color, flags);
}

int image_copy_get_color(image_t image, int x, int y)
{
  if ((x < 0) || (x > image->width  - 1) ||
      (y < 0) || (y > image->height - 1))
    return -1;
  return image->procs->copy_get_color(image, x, y);
}

int image_copy_save(image_t image, int x, int y, int width, int height)
{
  if (width  < 0) { x += width;  width  = -width;  }
  if (height < 0) { y += height; height = -height; }

  if (x < 0) { width  += x; x = 0; }
  if (y < 0) { height += y; y = 0; }

  if (x + width  > image->width ) width  = image->width  - x;
  if (y + height > image->height) height = image->height - y;

  if ((width <= 0) || (height <= 0))
    return 0;

  if (image->copy.bitmap.p == image->bitmap.p)
    return 0;

  return image->procs->copy_save(image, x, y, width, height);
}

int image_copy_save_color(image_t image, int x, int y)
{
  if ((x < 0) || (x > image->width  - 1) ||
      (y < 0) || (y > image->height - 1))
    return -1;
  if (image->copy.bitmap.p == image->bitmap.p)
    return 0;
  return image->procs->copy_save_color(image, x, y);
}

int image_get_index(image_t image)
{
  if (!image)
    return -1;
  return image->index;
}

image_t image_get_image(int index)
{
  image_t image;

  if (index >= GRAPHIC_IMAGE_MAXNUM)
    return NULL;

  if (index < 0) {
    for (index = 0; index < GRAPHIC_IMAGE_MAXNUM; index++) {
      image = images[index];
      if (image && image->status == IMAGE_STATUS_FREE) {
	image_destroy(image);
	image = images[index];
      }
      if (!image)
	break;
    }
    if (index == GRAPHIC_IMAGE_MAXNUM)
      return NULL;

    if ((image = _alloc()) == NULL)
      return NULL;
    image_num++;

    memset(image, 0, sizeof(*image));
    image->status = IMAGE_STATUS_NONE;
    image->index = index;

    images[index] = image;
  }

  return images[index];
}

int image_reset_draw(image_t image)
{
  if (image->draw.line)
    memset(image->draw.line, 0, image->height * sizeof(image_draw_count_t));
  if (image->draw.column)
    memset(image->draw.column, 0, image->draw.column_num * image->height * sizeof(image_draw_count_t));
  if (image->draw.pixel)
    memset(image->draw.pixel, 0, image->width * image->height * sizeof(image_draw_count_t));
  return 0;
}

int image_destroy(image_t image)
{
  int r;

  if (image->flags & NLL_G_CONST)
    return 0;

  image->status = IMAGE_STATUS_NONE;

  images[image->index] = NULL;

  if (image->copy.bitmap.p && (image->copy.bitmap.p != image->bitmap.p))
    memory_free(image->copy.bitmap.p);
  if (image->draw.pixel)    memory_free(image->draw.pixel);
  if (image->draw.column)   memory_free(image->draw.column);
  if (image->draw.line)     memory_free(image->draw.line);

  if (!(image->flags & NLL_G_STATIC)) {
    if (image->bitmap.p)
      memory_free(image->bitmap.p);
  }

  memset(image, 0, sizeof(*image));

  image_num--;
  if ((r = _free(image)) < 0)
    return r;

  return 0;
}

int image_create(image_t image, int width, int height, void *bitmap,
		 image_type_t type, unsigned int flags)
{
  int size;

  if (type == IMAGE_TYPE_NONE) {
    switch (flags & NLL_G_COLOR_MASK) {
    case NLL_G_MONOCOLOR:  type = IMAGE_TYPE_MONOCOLOR;  break;
    case NLL_G_MONOBCOLOR: type = IMAGE_TYPE_MONOBCOLOR; break;
    case NLL_G_BASICCOLOR: type = IMAGE_TYPE_BASICCOLOR; break;
    case NLL_G_ROUGHCOLOR: type = IMAGE_TYPE_ROUGHCOLOR; break;
    case NLL_G_GRAYCOLOR:  type = IMAGE_TYPE_GRAYCOLOR;  break;
    case NLL_G_HALFCOLOR:  type = IMAGE_TYPE_HALFCOLOR;  break;
    case NLL_G_FULLCOLOR:
    case NLL_G_AUTOCOLOR:  /* default type */
    default:               type = IMAGE_TYPE_FULLCOLOR;  break;
    }
  }

  image->width  = width;
  image->height = height;

  image->type = type;
  image->procs = &image_procs[type];

  image->procs->index(image, image->width, image->width - 1, 0, &size, NULL);
  size = (size + 1) * image->procs->bytesize;
  size *= image->height;

  image->size = size;
  image->flags = flags;

  image->color.fg   = NLL_G_WHITE;
  image->color.bg   = -1;
  image->color.from = -1;
  image->color.to   = -1;

  if (bitmap) {
    image->bitmap.word = bitmap;
  } else {
    image->bitmap.p = memory_alloc(image->size);
    if (!image->bitmap.p)
      goto err;
    memset(image->bitmap.p, 0, image->size);
  }

  image->draw.column_per = GRAPHIC_IMAGE_DRAW_COLUMN_PER;
  image->draw.column_num = (image->width + image->draw.column_per - 1) / image->draw.column_per;

  if (image->flags & NLL_G_RDONLY) {
    image->draw.line   = NULL;
    image->draw.column = NULL;
    image->draw.pixel  = NULL;
    image->copy.bitmap.p = image->bitmap.p;
  } else {
    image->draw.line   = memory_alloc(image->height * sizeof(image_draw_count_t));
    image->draw.column = memory_alloc(image->draw.column_num * image->height * sizeof(image_draw_count_t));
    if (!image->draw.line || !image->draw.column)
      goto err;

    if (image->flags & NLL_G_NDRAWPIXEL) {
      image->draw.pixel = NULL;
    } else {
      image->draw.pixel = memory_alloc(image->width * image->height * sizeof(image_draw_count_t));
      if (!image->draw.pixel)
	goto err;
    }

    image->copy.bitmap.p = memory_alloc(image->size);
    if (!image->copy.bitmap.p)
      goto err;
    memset(image->copy.bitmap.p, 0, image->size);
  }

  image_reset_draw(image);

  image->draw.count = 0;

  image->status = IMAGE_STATUS_ACTIVE;

  return 0;

err:
  image_destroy(image);
  return -1;
}

image_t image_get(int width, int height, void *bitmap,
		  image_type_t type, unsigned int flags)
{
  image_t image;

  image = image_get_image(-1);
  if (!image)
    return NULL;

  if (image_create(image, width, height, bitmap, type, flags) < 0)
    return NULL;

  return image;
}
#endif
