
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <stddef.h>
#include <endian.h>

#include "bogl.h"
#include "boglP.h"
#include "bogl-font.h"
#include "bogl-bgf.h"

/* Set font struct, let it point to font data in the given memory region */
static inline void set_font(struct bogl_font *font, void *mem)
{
  memcpy(font, mem + 4, sizeof(*font));
  font->name = ((void *)font->name - (void *)0) + mem;
  font->offset = ((void *)font->offset - (void *)0) + mem;
  font->index = ((void *)font->index - (void *)0) + mem;
  font->content = ((void *)font->content - (void *)0) + mem;
}

struct bogl_bgf {
  void *f;    /* mmap area */
  off_t size; /* size of mmap area */
  struct bogl_font font; /* font descriptor */
};
#define bgf_of(font) \
  ((struct bogl_bgf *)((char *)(font) - offsetof(struct bogl_bgf, font)))

/* Scale the font (both width and height)
   Declare as inline function, will run faster if scale factor is constant */
static inline void __attribute__((always_inline))
bgf_scale_inline(struct bogl_bgf *bgf, int scale)
{
  u_int8_t *lut = NULL;
  const u_int32_t bo = be32toh(0x00010203);
  const u_int8_t b0 = bo >> 24, b1 = bo >> 16, b2 = bo >> 8, b3 = bo;
  void *new_f = MAP_FAILED;
  off_t new_size;
  struct bogl_font new_font;

  /* old_size*pow(scale,2) should enough and have some waste here */
  new_size = bgf->size * scale * scale;

  /* Allocate new memory */
  new_f = mmap(0, new_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (new_f == MAP_FAILED) {
    /* If memory allocation failed, skip scaling silently */
    goto fail;
  }

  /* Copy old font data to new memory */
  memcpy(new_f, bgf->f, bgf->size);

  /* Set font metadata */
  struct bogl_font *font = &new_font;
  set_font(font, new_f);
  font->height = bgf->font.height * scale;

  /* Generate lookup table for scaling */
  lut = calloc(0x100, scale);
  if (!lut)
    goto fail;
  for (int i = 0; i < 0x100; i++)
    for (int j = 0; j < 8 * scale; j++)
      lut[i * scale + j / 8] |= ((i >> (7 - j / scale % 8)) & 1) << (7 - j % 8);

  /* Scale each glyph */
  int mask = font->index_mask;
  for (int j = 0; j <= mask; j++) {
    for (int i = font->offset[j]; font->index[i]; i += 2) {
      int old_height = bgf->font.height;
      int old_width = (font->index[i] & mask);
      if (old_width * scale > mask) {
        /* Scaled glyph width can't fit in mask, fail */
        goto fail;
      }
      font->index[i] = (font->index[i] & ~mask) | (old_width * scale);
      font->index[i + 1] *= scale * scale;
      u_int32_t *in_bitmap = &bgf->font.content[bgf->font.index[i + 1]];
      int in_pitch = (old_width + 31) / 32;
      u_int32_t *out_bitmap = &font->content[font->index[i + 1]];
      int out_pitch = (old_width * scale + 31) / 32;
      for (int y = 0; y < old_height; y++, in_bitmap += in_pitch) {
        u_int8_t *in_u8 = (u_int8_t *) in_bitmap;
        u_int8_t *out_u8 = (u_int8_t *) out_bitmap;
        /* If (in_pitch * scale) > out_pitch, this loop will overwrite next
           scanline a little bit.  Fortunately, we reserved (in_pitch * scale)
           for each scanline, and next scanline is not processed yet, so this
           will not cause memory errors or glyph corruption.  */
        for (int x = 0; x < in_pitch * 4; x += 4) {
          memcpy(&out_u8[(x + 0) * scale], &lut[in_u8[x + b0] * scale], scale);
          memcpy(&out_u8[(x + 1) * scale], &lut[in_u8[x + b1] * scale], scale);
          memcpy(&out_u8[(x + 2) * scale], &lut[in_u8[x + b2] * scale], scale);
          memcpy(&out_u8[(x + 3) * scale], &lut[in_u8[x + b3] * scale], scale);
        }
        for (int xx = 0; xx < out_pitch; xx++, out_bitmap++)
          *out_bitmap = be32toh(*out_bitmap);
        for (int yy = 1; yy < scale; yy++, out_bitmap += out_pitch)
          memcpy(out_bitmap, out_u8, sizeof(u_int32_t) * out_pitch);
      }
    }
  }

  /* Replace old font with new font */
  munmap(bgf->f, bgf->size);
  bgf->f = new_f;
  bgf->size = new_size;
  bgf->font = new_font;
  free(lut);
  return;
fail:
  if (new_f != MAP_FAILED)
    munmap(new_f, new_size);
}
static void __attribute__((noinline))
bgf_scale_noinline(struct bogl_bgf *bgf, int scale)
{
  bgf_scale_inline(bgf, scale);
}
static void __attribute__((noinline)) bgf_scale_2x(struct bogl_bgf *bgf)
{
  bgf_scale_inline(bgf, 2);
}
static void __attribute__((noinline)) bgf_scale_4x(struct bogl_bgf *bgf)
{
  bgf_scale_inline(bgf, 4);
}
static void __attribute__((noinline)) bgf_scale_8x(struct bogl_bgf *bgf)
{
  bgf_scale_inline(bgf, 8);
}
static void bgf_scale(struct bogl_bgf *bgf, int scale)
{
  switch (scale) {
    /* Use fast implementation if possible */
    case 2: bgf_scale_2x(bgf); return;
    case 4: bgf_scale_4x(bgf); return;
    case 8: bgf_scale_8x(bgf); return;
    /* Universal implementation as fallback */
    default: bgf_scale_noinline(bgf, scale); return;
  }
}

struct bogl_font *bogl_mmap_font(char *file, int scale)
{
  struct bogl_bgf *bgf = NULL;
  struct bogl_font *font = NULL;
  int fd = -1;
  struct stat buf;
  void *f = MAP_FAILED;

  bgf = (struct bogl_bgf *)malloc(sizeof(struct bogl_bgf));
  if (!bgf)
    goto fail;
  font = &bgf->font;

  fd = open(file, O_RDONLY);
  if (fd == -1)
    goto fail;

  if (bogl_cloexec(fd) < 0)
    goto fail;

  if (fstat(fd, &buf))
    goto fail;
  bgf->size = buf.st_size;

  if (buf.st_size < 4)
    goto fail;

  f = mmap(0, buf.st_size, PROT_READ, MAP_SHARED, fd, 0);
  if (f == MAP_FAILED)
    goto fail;
  bgf->f = f;

  if (memcmp("BGF1", f, 4))
    goto fail;

  set_font(font, f);

  if (scale >= 2)
    bgf_scale(bgf, scale);

done:
  if (fd != -1)
    close(fd);
  return font;

fail:
  if (bgf) {
    free(bgf);
    bgf = NULL;
    font = NULL;
  }
  if (f != MAP_FAILED)
    munmap(f, buf.st_size);
  goto done;
}

void bogl_munmap_font(struct bogl_font *font)
{
  struct bogl_bgf *bgf = bgf_of(font);
  munmap(bgf->f, bgf->size);
  free(bgf);
}
