//  DescriptionHere - canvas.cpp
//  Created by John Ryland (jryland@xiaofrog.com), 29/10/2017
//  Copyright (c) 2017 InvertedLogic
//  All rights reserved.
#include <stdlib.h>
#include <stdio.h>
#include <png.h>
#include <algorithm>
#include "canvas.h"

bool loadSpriteSheet(SpriteSheet& sheet, const char* fileName)
{
  FILE *file = fopen(fileName, "r");
  if ( !file ) {
    printf("Error opening file %s\n", fileName);
    return false;
  }
  char buf[1024];
  if (!fscanf(file, "%s\n", buf)) {
    printf("Error reading file %s\n", fileName);
    fclose(file);
    return false;
  }
  loadPngImage(sheet.image, buf);
  while (fgets(buf, 1024, file)) {
    Sprite s;
    s.image = &sheet.image;
    sscanf(buf, "%u %u %u %u\n", &s.x, &s.y, &s.w, &s.h);
    sheet.sprites.push_back(s);
    printf("next sprite: %u %u %u %u\n", s.x, s.y, s.w, s.h);
  }
  fclose(file);
  return true;
}

bool loadPngImage(Image& img, const char *pngFileName)
{
  /* Open and initialise */
  FILE *file = fopen(pngFileName, "r");
  if ( !file ) {
    printf("Error opening file %s\n", pngFileName);
    return false; 
  }
  png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
  png_infop cinfo = png_create_info_struct(png);
  png_infop cinfo_end = png_create_info_struct(png);

  png_init_io(png, file);
  png_read_info(png, cinfo);

  /* Setup image */
  img.w = png_get_image_width(png, cinfo);
  img.h = png_get_image_height(png, cinfo);
  unsigned char **scanLine = (unsigned char**)malloc(sizeof(char*)*img.h);
  img.pixels.resize(img.w * img.h);
  for (int i = img.h - 1; i >= 0; i--)
    scanLine[i] = (unsigned char*)&img.pixels[i*img.w];

  /* Decode image */
  png_start_read_image(png);
  png_read_image(png, (png_bytepp)scanLine);

  /* Clean up */
  png_read_end(png, cinfo_end);
  png_destroy_read_struct(&png, &cinfo, &cinfo_end);
  free(scanLine);
  return true;
}

void drawTestGradient(Image& img)
{
  for (int j = 0; j < img.h; j++)
    for (int i = 0; i < img.w; i++)
      img.pixels[j*img.w+i] = pixelVal(255, i>>1, i>>1, i>>1);
}

template <bool withAlpha>
void drawLine(uint32_t* dst, const uint32_t* src, size_t len)
{
  for (int i = 0; i < len; i++, src++, dst++) {
    if (!withAlpha) {
      *dst = *src;
    } else {
      uint32_t sa = (*src >> 24) & 0xFF;
      uint32_t da = 256 - sa;
      uint32_t r = ((((*dst >> 0 ) & 0xFF) * da) >> 8) + ((((*src >> 0 ) & 0xFF) * sa) >> 8);
      uint32_t g = ((((*dst >> 8 ) & 0xFF) * da) >> 8) + ((((*src >> 8 ) & 0xFF) * sa) >> 8);
      uint32_t b = ((((*dst >> 16) & 0xFF) * da) >> 8) + ((((*src >> 16) & 0xFF) * sa) >> 8);
      *dst = (255<<24) | (b<<16) | (g<<8) | (r<<0);
    }
  }
}

template <bool withAlpha>
void drawImageGeneric(Image& image, int x, int y, const Image& img, uint32_t srcX = 0, uint32_t srcY = 0, int w = -1, int h = -1)
{
#if USE_OPENGL
  y = image.h - y;
#endif
  if (w < 0) w = img.w;
  if (h < 0) h = img.h;
  if (x < 0) { srcX += -x; w += x; x = 0; }
  if (y < 0) { srcY += -y; h += y; y = 0; }
  if (x < 0 || y < 0 || x >= image.w || y >= image.h || srcX >= img.w || srcY >= img.h || w <= 0 || h <= 0)
    return;
  const uint32_t *srcPix = &img.pixels[srcY*img.w + srcX];
  uint32_t *dstPix = &image.pixels[y*image.w + x];
  int copyH = std::min(h, std::min<int>(img.h-srcY, image.h-y));
  int copyW = std::min(w, std::min<int>(img.w-srcX, image.w-1-x));
  for (int j = 0; j < copyH; j++)
  {
    drawLine<withAlpha>(dstPix, srcPix, copyW);
#if USE_OPENGL
    dstPix -= image.w;
#else
    dstPix += image.w;
#endif
    srcPix += img.w;
  }
}

void drawImage(Image& image, int x, int y, const Image& img)
{
  drawImageGeneric<false>(image, x, y, img);
}

void drawImageWithAlpha(Image& image, int x, int y, const Image& img)
{
  drawImageGeneric<true>(image, x, y, img);
}

void drawSprite(Image& image, int x, int y, const Sprite& s)
{
  drawImageGeneric<true>(image, x, y, *s.image, s.x, s.y, s.w, s.h);
}

void drawCanvas(Image& image, const Canvas& canvas)
{
  drawTestGradient(image);
  //drawImage(image, 0, 0, canvas.backgroundImage);
  for (int i = 0; i < canvas.sprites.size(); i++) {
    const CanvasItem& item = canvas.sprites[i];
    if (item.visible) {
      drawSprite(image, item.x, item.y, *item.sprite);
    }
  }
}

void canvasAddSprite(Canvas& canvas, int x, int y, const Sprite& s)
{
  CanvasItem item;
  item.visible = true;
  item.frame = 0;
  item.x = x;
  item.y = y;
  item.sprite = &s;
  canvas.sprites.push_back(item);
}

template <bool findClosestToCenter, bool testAlpha>
int canvasHitTestGeneric(const Canvas& canvas, int x, int y, int alphaThreshold)
{
  int closestIndex = -1;
  int closestDistSqr = INT_MAX;
  // Assuming the array's order is used for z-ordering with painters algorithm, the last drawn
  // items will draw on top so be the first to be hit by the mouse, therefore we search in reverse order.
  for (int i = canvas.sprites.size() - 1; i >= 0; i--)
  {
    const CanvasItem& item = canvas.sprites[i];
    // TODO: could use a quad-tree to store the sprites in if want to scale to large number of canvas items
    if (x >= item.x && x <= item.x+item.sprite->w &&
        y >= item.y && y <= item.y+item.sprite->h)
    {
      if (testAlpha)
      {
        const Sprite& s = *item.sprite;
        uint32_t pix = s.image->pixels[(s.y+y-item.y)*s.image->w + s.x+x-item.x];
        if (((pix >> 24) & 0xFF) < alphaThreshold) {
          continue;
        }
      }
      if (!findClosestToCenter) {
        return i;
      }
      int itemCenterX = item.x + item.sprite->w / 2;
      int itemCenterY = item.y + item.sprite->h / 2;
      int distSqr = (itemCenterX - x) * (itemCenterX - x) + (itemCenterY - y) * (itemCenterY - y);
      if (distSqr < closestDistSqr) {
        closestDistSqr = distSqr;
        closestIndex = i;
      }
    }
  }
  return closestIndex;
}

int canvasHitTest(const Canvas& canvas, int x, int y)
{
  return canvasHitTestGeneric<false, false>(canvas, x, y, 0);
}

int canvasHitTestWithAlpha(const Canvas& canvas, int x, int y)
{
  return canvasHitTestGeneric<false, true>(canvas, x, y, 32);
}


