#include "config.h"

#ifdef USE_GRAPHIC
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <sys/types.h>

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

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

struct bmp_magic_header {
  uint8_t type[2];
};
#define BMP_MAGIC_HEADER_TYPE0   0
#define BMP_MAGIC_HEADER_TYPE1   1
#define BMP_MAGIC_HEADER_ALLSIZE 2

struct bmp_file_header {
  uint32_t size;
  uint16_t reserve[2];
  uint32_t offset;
};
#define BMP_FILE_HEADER_SIZE     0
#define BMP_FILE_HEADER_OFFSET   8
#define BMP_FILE_HEADER_ALLSIZE 12

struct bmp_info_header {
  uint32_t ihdrsize;
  uint32_t width;
  uint32_t height;
  uint16_t planes;
  uint16_t bits;
  uint32_t compress;
  uint32_t imagesize;
  uint32_t xpix;
  uint32_t ypix;
  uint32_t colors;
  uint32_t impcolors;
};
#define BMP_INFO_HEADER_IHDRSIZE   0
#define BMP_INFO_HEADER_WIDTH      4
#define BMP_INFO_HEADER_HEIGHT     8
#define BMP_INFO_HEADER_PLANES    12
#define BMP_INFO_HEADER_BITS      14
#define BMP_INFO_HEADER_COMPRESS  16
#define BMP_INFO_HEADER_IMAGESIZE 20
#define BMP_INFO_HEADER_XPIX      24
#define BMP_INFO_HEADER_YPIX      28
#define BMP_INFO_HEADER_COLORS    32
#define BMP_INFO_HEADER_IMPCOLORS 36
#define BMP_INFO_HEADER_ALLSIZE   40

#define BMP_PIXEL_BYTE 3

union evalue {
  uint8_t  b[4];
  uint16_t h[2];
  uint32_t w[1];
};

static uint16_t read_le16(uint16_t val)
{
  union evalue e;
  e.h[0] = val;
  return (e.b[1] << 8) | e.b[0];
}

static uint32_t read_le32(uint32_t val)
{
  union evalue e;
  e.w[0] = val;
  return (e.b[3] << 24) | (e.b[2] << 16) | (e.b[1] << 8) | e.b[0];
}

static uint16_t write_le16(uint16_t val)
{
  union evalue e;
  e.b[0] =  val        & 0xff;
  e.b[1] = (val >>  8) & 0xff;
  return e.h[0];
}

static uint32_t write_le32(uint32_t val)
{
  union evalue e;
  e.b[0] =  val        & 0xff;
  e.b[1] = (val >>  8) & 0xff;
  e.b[2] = (val >> 16) & 0xff;
  e.b[3] = (val >> 24) & 0xff;
  return e.w[0];
}

struct bitfield {
  unsigned int mask;
  int shiftbits;
  int maskbits;
};

static int bitfield_calc(struct bitfield *bfd, unsigned int mask)
{
  if (!mask)
    return -1;

  bfd->mask = mask;
  bfd->shiftbits = 0;
  bfd->maskbits = 0;

  while (!(mask & 1)) {
    bfd->shiftbits++;
    mask >>= 1;
  }

  while (mask) {
    bfd->maskbits++;
    mask >>= 1;
  }

  return 0;
}

static int bitfield_load(int c, struct bitfield *bfd)
{
  unsigned int bgr[3];
  int i, s;

  for (i = 0; i < 3; i++) {
    bgr[i] = (c & bfd[i].mask) >> bfd[i].shiftbits;
    if (bfd[i].maskbits < 8) {
      s = 8 - bfd[i].maskbits;
      bgr[i] = bgr[i] ? ((bgr[i] << s) | ((1 << s) - 1)) : 0;
    } else {
      bgr[i] >>= (bfd[i].maskbits - 8);
    }
  }

  c = 0;
  for (i = 0; i < 3; i++)
    c |= ((bgr[i] & 0xff) << (i * 8));

  return c;
}

static int bitfield_save(int c, struct bitfield *bfd)
{
  unsigned int bgr[3];
  int i, s;

  for (i = 0; i < 3; i++) {
    bgr[i] = (c >> (i * 8)) & 0xff;
    if (bfd[i].maskbits > 8) {
      s = bfd[i].maskbits - 8;
      bgr[i] = bgr[i] ? ((bgr[i] << s) | ((1 << s) - 1)) : 0;
    } else {
      bgr[i] >>= (8 - bfd[i].maskbits);
    }
  }

  c = 0;
  for (i = 0; i < 3; i++)
    c |= ((bgr[i] << bfd[i].shiftbits) & bfd[i].mask);

  return c;
}

static int *palette_load(FILE *fp, int palette_num, int *palette)
{
  int i, s;
  unsigned char bgr[4];
  int c;

  for (i = 0; i < palette_num; i++) {
    s = fread(bgr, 1, sizeof(bgr), fp);
    if (s != sizeof(bgr))
      goto err;
    c  =  bgr[0];
    c |= (bgr[1] <<  8);
    c |= (bgr[2] << 16);
    palette[i] = c;
  }

  return palette;

err:
  return NULL;
}

static int palette_save(FILE *fp, int palette_num, int *palette)
{
  int i, s;
  unsigned char bgr[4];
  int c;

  bgr[3] = 0;

  for (i = 0; i < palette_num; i++) {
    c = palette[i];
    bgr[0] =  c        & 0xff;
    bgr[1] = (c >>  8) & 0xff;
    bgr[2] = (c >> 16) & 0xff;
    s = fwrite(bgr, 1, sizeof(bgr), fp);
    if (s != sizeof(bgr))
      goto err;
  }

  return i;

err:
  return -1;
}

static int palette_search(int palette_num, int *palette, int c)
{
  int i;

  for (i = 0; i < palette_num; i++) {
    if (palette[i] == c)
      return i;
  }

  return -1;
}

static int palette_make(image_t image, int x, int y, int width, int height,
			int palette_num, int *palette)
{
  int x0, y0, bx, by, c, num = 0;

  x0 = x;
  y0 = y;

  for (y = 0; y < height; y++) {
    for (x = 0; x < width; x++) {
      bx = x0 + x;
      by = y0 + (height - y - 1);
      if ((bx < 0) || (bx > image->width  - 1) ||
	  (by < 0) || (by > image->height - 1))
	c = 0;
      else {
	c = image_get_color(image, bx, by);
	c = (c < 0) ? 0 : c;
      }
      if (palette_search(num, palette, c) < 0) {
	if (num == palette_num)
	  return -1;
	palette[num++] = c;
      }
    }
  }

  return num;
}

static image_t bmp_load(FILE *fp, unsigned int flags, int *palette, struct bitfield *bfd,
			int width, int height, int bits, int imagesize)
{
  int size, bytes, w, h, x, y, i, n, s, c, pad_size;
  image_t image = NULL;
  unsigned char bgr[4], mask;

  w = (width  > GRAPHIC_MAXWIDTH ) ? GRAPHIC_MAXWIDTH  : width;
  h = (height > GRAPHIC_MAXHEIGHT) ? GRAPHIC_MAXHEIGHT : height;

  if ((w < 1) || (h < 1))
    goto err;

  image = image_get(w, h, NULL, IMAGE_TYPE_NONE, flags);
  if (!image)
    goto err;

  if (bits <= 8) {
    n = 8 / bits;
    bytes = 1;
    mask = (1 << bits) - 1;
    pad_size = 4 - (((width * bits + 7) / 8) % 4);
    pad_size = (pad_size % 4);
  } else {
    n = 1;
    bytes = bits / 8;
    mask = 0xff;
    pad_size = 4 - ((width * bytes) % 4);
    pad_size = (pad_size % 4);
  }

  size = 0;

  image->flags &= ~NLL_G_RDONLY;

  for (y = 0; y < h; y++) {
    for (x = 0; x < width;) {
      for (i = 0; i < n; i++) {
	if (i == 0) {
	  if (imagesize && (size + bytes > imagesize))
	    goto err;
	  s = fread(bgr, 1, bytes, fp);
	  if (s < bytes)
	    memset(bgr + s, 0, bytes - s);
	  size += bytes;
	}
	if (palette) {
	  c = palette[(bgr[0] >> ((n - 1 - i) * bits)) & mask];
	} else {
	  c = 0;
	  for (i = 0; i < bytes; i++)
	    c |= (bgr[i] << (i * 8));
	  if (bfd)
	    c = bitfield_load(c, bfd);
	}
	if (x < w)
	  image_set_color(image, x, h - y - 1, c, NLL_G_SET);
	x++;
      }
    }
    for (x = 0; x < pad_size; x++) {
      fread(bgr, 1, 1, fp);
      size++;
    }
  }

  image->flags |= (NLL_G_RDONLY & flags);

  return image;

err:
  if (image)
    image_destroy(image);
  return NULL;
}

static image_t picture_bmp_load(char *filename, unsigned int flags)
{
  FILE *fp = NULL;
  struct bmp_magic_header mhdr;
  struct bmp_file_header fhdr;
  struct bmp_info_header ihdr;
  unsigned char dummy;
  int offset, i, n, c;
  int palette[256];
  struct bitfield bfd[3], *bfdp = NULL;
  int *p = NULL;
  image_t image = NULL;
  const char *mode;

#ifndef WIN32
  mode = "r";
#else
  mode = "rb";
#endif

  fp = fopen(filename, mode);
  if (!fp)
    goto err;

  if (fread(&mhdr, 1, BMP_MAGIC_HEADER_ALLSIZE, fp) != BMP_MAGIC_HEADER_ALLSIZE)
    goto err;

  offset = BMP_MAGIC_HEADER_ALLSIZE;

  if ((mhdr.type[0] != 'B') ||
      (mhdr.type[1] != 'M'))
    goto err;

  if (fread(&fhdr, 1, BMP_FILE_HEADER_ALLSIZE, fp) != BMP_FILE_HEADER_ALLSIZE)
    goto err;

  if (fread(&ihdr, 1, BMP_INFO_HEADER_ALLSIZE, fp) != BMP_INFO_HEADER_ALLSIZE)
    goto err;

  fhdr.size   = read_le32(fhdr.size);
  fhdr.offset = read_le32(fhdr.offset);

  offset += BMP_FILE_HEADER_ALLSIZE;

  ihdr.ihdrsize  = read_le32(ihdr.ihdrsize);
  ihdr.width     = read_le32(ihdr.width);
  ihdr.height    = read_le32(ihdr.height);
  ihdr.planes    = read_le16(ihdr.planes);
  ihdr.bits      = read_le16(ihdr.bits);
  ihdr.compress  = read_le32(ihdr.compress);
  ihdr.imagesize = read_le32(ihdr.imagesize);
  ihdr.xpix      = read_le32(ihdr.xpix);
  ihdr.ypix      = read_le32(ihdr.ypix);
  ihdr.colors    = read_le32(ihdr.colors);
  ihdr.impcolors = read_le32(ihdr.impcolors);

  if (ihdr.ihdrsize < BMP_INFO_HEADER_ALLSIZE)
    goto err;
  if (ihdr.planes != 1)
    goto err;

  for (n = 0; n < ihdr.ihdrsize - BMP_INFO_HEADER_ALLSIZE; n++) {
    if (fread(&dummy, 1, 1, fp) != 1)
      goto err;
  }

  offset += ihdr.ihdrsize;

  switch (ihdr.bits) {
  case  1:
  case  4:
  case  8:
  case 24:
    if (ihdr.compress != 0)
      goto err;
    break;

  case 16:
  case 32:
    switch (ihdr.compress) {
    case 3:
      if (offset + 3 * sizeof(c) <= fhdr.offset) {
	for (i = 0; i < 3; i++) {
	  if (fread(&c, 1, sizeof(c), fp) != sizeof(c))
	    goto err;
	  c = read_le32(c);
	  bitfield_calc(&bfd[2 - i], c);
	  offset += sizeof(c);
	}
	break;
      }
      /* fall through */
    case 0:
      switch (ihdr.bits) {
      case 16:
	bitfield_calc(&bfd[0], 0x001f);
	bitfield_calc(&bfd[1], 0x03e0);
	bitfield_calc(&bfd[2], 0x7c00);
	break;
      case 32:
	bitfield_calc(&bfd[0], 0x000000ff);
	bitfield_calc(&bfd[1], 0x0000ff00);
	bitfield_calc(&bfd[2], 0x00ff0000);
	break;
      default:
	goto err;
      }
      break;
    default:
      goto err;
    }
    bfdp = bfd;
    break;

  default:
    goto err;
  }

  if (ihdr.bits <= 8) {
    n = ihdr.colors ? ihdr.colors : (1 << ihdr.bits);
    if (n > 256)
      goto err;
    memset(palette, 0, sizeof(palette));
    p = palette_load(fp, n, palette);
    if (!p)
      goto err;
    offset += n * sizeof(*p);
  }

  if (fhdr.offset < offset)
    goto err;

  for (offset = fhdr.offset - offset; offset > 0; offset--) {
    if (fread(&dummy, 1, 1, fp) != 1)
      goto err;
  }

  image = bmp_load(fp, flags, p, bfdp, ihdr.width, ihdr.height, ihdr.bits, ihdr.imagesize);
  if (!image)
    goto err;

  fclose(fp);
  fp = NULL;

  return image;

err:
  if (fp)
    fclose(fp);
  if (image)
    image_destroy(image);
  return NULL;
}

static int picture_bmp_save(char *filename, image_t image,
			    int x, int y, int width, int height, unsigned int flags)
{
  FILE *fp = NULL;
  struct bmp_magic_header mhdr;
  struct bmp_file_header fhdr;
  struct bmp_info_header ihdr;
  int hdrsize, bfdsize, pltsize, imgsize, bits, colors, line_size, pad_size, write_size;
  int x0, y0, bx, by, c, i, n;
  int palette[256];
  struct bitfield bfd[3], *bfdp = NULL;
  unsigned char bgr[4], mask;
  const char *mode;

  x0 = x;
  y0 = y;

  if (!width ) width  = image->width  - x0;
  if (!height) height = image->height - y0;

#ifndef WIN32
  mode = "w";
#else
  mode = "wb";
#endif

  fp = fopen(filename, mode);
  if (!fp)
    goto err;

  memset(&mhdr, 0, sizeof(mhdr));
  memset(&fhdr, 0, sizeof(fhdr));
  memset(&ihdr, 0, sizeof(ihdr));

  mhdr.type[0] = 'B';
  mhdr.type[1] = 'M';

  bfdsize = 0;
  colors = -1;
  n = 1;
  mask = 0xff;

  switch (flags & NLL_G_COLOR_MASK) {
  case NLL_G_HALFCOLOR:
    bfdsize = sizeof(unsigned int) * 3;
    write_size = 2;
    bits = write_size * 8;
    line_size = width * write_size;
    break;

  case NLL_G_FULLCOLOR:
    bfdsize = sizeof(unsigned int) * 3;
    write_size = 4;
    bits = write_size * 8;
    line_size = width * write_size;
    break;

  default:
    colors = palette_make(image, x, y, width, height, 256, palette);
    if (colors < 0) {
      write_size = 3;
      bits = write_size * 8;
      line_size = width * write_size;
    } else {
      write_size = 1;
      if (colors <= 2)
	bits = 1;
      else if (colors <= 16)
	bits = 4;
      else
	bits = 8;
      line_size = (width * bits + 7) / 8;
      n = 8 / bits;
      mask = (1 << bits) - 1;
    }
    break;
  }

  pad_size = 4 - (line_size % 4);
  pad_size = (pad_size % 4);
  line_size += pad_size;

  hdrsize = BMP_MAGIC_HEADER_ALLSIZE + BMP_FILE_HEADER_ALLSIZE + BMP_INFO_HEADER_ALLSIZE;
  pltsize = ((colors < 0) ? 0 : colors) * sizeof(palette[0]);
  imgsize = line_size * height;

  fhdr.size   = write_le32(hdrsize + bfdsize + pltsize + imgsize);
  fhdr.offset = write_le32(hdrsize + bfdsize + pltsize);

  ihdr.ihdrsize  = write_le32(BMP_INFO_HEADER_ALLSIZE);
  ihdr.width     = write_le32(width);
  ihdr.height    = write_le32(height);
  ihdr.planes    = write_le16(1);
  ihdr.bits      = write_le16(bits);
  ihdr.compress  = write_le32(bfdsize ? 3 : 0);
  ihdr.imagesize = write_le32(imgsize);
  ihdr.xpix      = write_le32(2925);
  ihdr.ypix      = write_le32(2925);
  ihdr.colors    = write_le32((colors < 0) ? 0 : colors);
  ihdr.impcolors = write_le32(0);

  if (fwrite(&mhdr, 1, BMP_MAGIC_HEADER_ALLSIZE, fp) != BMP_MAGIC_HEADER_ALLSIZE)
    goto err;

  if (fwrite(&fhdr, 1, BMP_FILE_HEADER_ALLSIZE, fp) != BMP_FILE_HEADER_ALLSIZE)
    goto err;

  if (fwrite(&ihdr, 1, BMP_INFO_HEADER_ALLSIZE, fp) != BMP_INFO_HEADER_ALLSIZE)
    goto err;

  switch (bits) {
  case 16:
    bitfield_calc(&bfd[0], 0x001f);
    bitfield_calc(&bfd[1], 0x03e0);
    bitfield_calc(&bfd[2], 0x7c00);
    bfdp = bfd;
    break;
  case 32:
    bitfield_calc(&bfd[0], 0x000000ff);
    bitfield_calc(&bfd[1], 0x0000ff00);
    bitfield_calc(&bfd[2], 0x00ff0000);
    bfdp = bfd;
    break;
  default:
    break;
  }

  if (bfdsize) {
    for (i = 0; i < 3; i++) {
      c = write_le32(bfd[2 - i].mask);
      if (fwrite(&c, 1, sizeof(c), fp) != sizeof(c))
	goto err;
    }
  }

  if (colors >= 0) {
    if (palette_save(fp, colors, palette) < 0)
      goto err;
  }

  for (y = 0; y < height; y++) {
    for (x = 0; x < width;) {
      bgr[0] = 0;
      for (i = 0; i < n; i++) {
	bx = x0 + x;
	by = y0 + (height - y - 1);
	if ((bx < 0) || (bx > image->width  - 1) ||
	    (by < 0) || (by > image->height - 1)) {
	  c = 0;
	} else {
	  c = image_get_color(image, bx, by);
	  c = (c < 0) ? 0 : c;
	  if (colors >= 0) {
	    c = palette_search(colors, palette, c);
	    if (c < 0)
	      goto err;
	  }
	}
	if (colors < 0) {
	  if (bfdp)
	    c = bitfield_save(c, bfdp);
	  for (i = 0; i < write_size; i++)
	    bgr[i] = (c >> (i * 8)) & 0xff;
	} else {
	  bgr[0] |= (c & mask) << ((n - 1 - i) * bits);
	}
	x++;
      }
      if (fwrite(bgr, 1, write_size, fp) != write_size)
	goto err;
    }
    bgr[0] = 0;
    for (x = 0; x < pad_size; x++) {
      if (fwrite(bgr, 1, 1, fp) != 1)
	goto err;
    }
  }

  fclose(fp);

  return 0;

err:
  if (fp)
    fclose(fp);
  return -1;
}

image_t picture_load(char *filename, unsigned int flags)
{
  return picture_bmp_load(filename, flags);
}

int picture_save(char *filename, image_t image,
		 int x, int y, int width, int height, unsigned int flags)
{
  return picture_bmp_save(filename, image, x, y, width, height, flags);
}
#endif
