#include <stdio.h>
#include <malloc.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <iostream>

#include "image.h"

#define IMAGE_TEXT_OUT 1

Image::Image () {
  paleta = 0;
  bitmap = 0;
  h = w = 0;
  form = OGL_IMAGE_NULL;
}

Image::Image (const char* filename) {
  paleta = 0;
  bitmap = 0;
  h = w = 0;
  form = OGL_IMAGE_NULL;
  Load(filename);
}

Image::~Image () {
  delete[] paleta;
  delete[] bitmap;
}

void Image::Scale(int sirka, int vyska)
{
  int elementsize = 0;
  switch(format()) {
    case OGL_IMAGE_RGBA8888:
    case OGL_IMAGE_ARGB8888:
      elementsize = 4;
      break;
    case OGL_IMAGE_RGB888:
      elementsize = 3;
      break;
    case OGL_IMAGE_INDEX8:
    case OGL_IMAGE_LUMINANCE8:
      elementsize = 1;
      break;
    default:
      return;
    }

//  vyska=h;
//  sirka=w;

  unsigned char* n_bitmap = new unsigned char[vyska*sirka*elementsize];
  unsigned char* n=n_bitmap;
  unsigned char* p, *pp;

  float xx=0.0,yy=0.0;

  float dx=float(w)/sirka;
  float dy=float(h)/vyska;

  for (int y=0; y<vyska;y++)
    {
    p=bitmap+(int(yy))*elementsize*w;
    for (int x=0; x<sirka;x++)
      {
      pp=p+(int(xx))*elementsize;
      for(int i=elementsize;i;i--) *n++=*pp++;
      xx+=dx;
      }
    xx=0.0;
    yy+=dy;
    }
delete[] bitmap;
bitmap=n_bitmap;
w=sirka;
h=vyska;
}

void Image::FlipVertical () {
  int elementsize = 0;
  switch(format()) {
    case OGL_IMAGE_RGBA8888:
    case OGL_IMAGE_ARGB8888:
      elementsize = 4;
      break;
    case OGL_IMAGE_RGB888:
      elementsize = 3;
      break;
    case OGL_IMAGE_INDEX8:
    case OGL_IMAGE_LUMINANCE8:
      elementsize = 1;
      break;
    default:
      return;
    }
  unsigned char *p1, *p2, *pp1, *pp2;
  unsigned char x;
  int linewidth = width()*elementsize;
  p1 = data();
  p2 = p1 + linewidth*(height()-1);
  int half = height()/2;

  for(int i=half; i; i--) {
    pp1 = p1;
    pp2 = p2;
    for(int j=linewidth; j; j--) {
      x = *pp1;
      *pp1 = *pp2;
      *pp2 = x;
      pp1++;
      pp2++;
    }
    p1+=linewidth;
    p2-=linewidth;
  }
}


int Image::LoadBMP (const char* filename)
{
  int f;
  struct stat *st;
  st = (struct stat *) malloc(sizeof(*st));

  f = open(filename, O_BINARY|O_RDONLY);
  if (f==(-1))
      return 0;

  fstat(f,st);
  int filesize = st->st_size;
  unsigned char *buffer = new unsigned char[filesize];
  read(f, buffer, filesize);
  close(f);
  free(st);

  header.bfType = *(short*)buffer;
  if(header.bfType!=0x4d42)
    return 0;

  header.bfSize = *(long*)(buffer+2);
  header.bfReserved1 = *(short*)(buffer+6);
  header.bfReserved2 = *(short*)(buffer+8);
  header.bfOffBits = *(long*)(buffer+10);

  header.biSize = *(long*)(buffer+14);
  header.biWidth = *(long*)(buffer+18);
  header.biHeight = *(long*)(buffer+22);
  header.biPlanes = *(short*)(buffer+26);
  header.biBitCount = *(short*)(buffer+28);
  header.biCompression = *(long*)(buffer+30);
  header.biSizeImage = *(long*)(buffer+34);
  header.biXPelsPerMeter = *(long*)(buffer+38);
  header.biYPelsPerMeter = *(long*)(buffer+42);
  header.biClrUsed = *(long*)(buffer+46);
  header.biClrImportant = *(long*)(buffer+50);
/*
  if (IMAGE_TEXT_OUT) printf("bmp size: %ld\n", header.bfSize);
  if (IMAGE_TEXT_OUT) printf("width: %ld\n",header.biWidth);
  if (IMAGE_TEXT_OUT) printf("height: %ld\n",header.biHeight);
  if (IMAGE_TEXT_OUT) printf("planes: %hd\n",header.biPlanes);
  if (IMAGE_TEXT_OUT) printf("bpp: %hd\n",header.biBitCount);
  if (IMAGE_TEXT_OUT) printf("compression: %ld\n",header.biCompression);
  header.biClrUsed ? if (IMAGE_TEXT_OUT) printf("    colors used: %ld, ",header.biClrUsed)
                   : if (IMAGE_TEXT_OUT) printf("    colors used: ALL, ");
  header.biClrImportant ? if (IMAGE_TEXT_OUT) printf("colors important: %ld\n",header.biClrImportant)
                        : if (IMAGE_TEXT_OUT) printf("colors important: ALL\n");
*/
  w = header.biWidth;
  h = header.biHeight;
  bpp = header.biBitCount;

  unsigned char *data;
  unsigned char *map, *map2;
  unsigned char *pal;
  int actualcolors;

  switch(bpp) {

    //  Monochromatic
    case 1:
      break;

    // Indexed 16 color
    case 4:
      break;

    // Indexed 256 color
    case 8:
      form = OGL_IMAGE_INDEX8;
      paleta = new unsigned long[256];
      pal = buffer + 54;
      actualcolors = header.biClrUsed ? header.biClrUsed : 256;
      for(int i=0; i<actualcolors; i++, pal+=4)
          paleta[i] = (*pal) | (*(pal+1)<<8) | (*(pal+2)<<16);

      data = buffer + header.bfOffBits;
      bitmap = new unsigned char[h*w];

      switch(header.biCompression) {

        case BMP_RGB:
          map = bitmap + w*(h-1);

          for(int i=h; i; i--)
          {
              map2=map;

              for(int j=w; j; j--)
                  *map2++ = *data++;

              map -= w;
          }
          break;

        case BMP_RLE8:
          break;

        case BMP_RLE4:
          break;
        }

      break;

    //  24bpp images
    case 24:
      form = OGL_IMAGE_RGBA8888;
      data = buffer + header.bfOffBits;
      bitmap = new unsigned char[(h*w)<<2];

      map = bitmap;

      for(int i=h*w; i; i--)
      {
        *map++ = *(data+2);
        *map++ = *(data+1);
        *map++ = *data;
        *map++ = 255;
        data+=3;

//        *map++ = *data++;
//        *map++ = *data++;
//        *map++ = *data++;
//        *map++ = 255;
      }

      FlipVertical();

      break;

  }

  free(buffer);
  return 1;
}

int Image::LoadPNG (const char* filename) {

  FILE *fp = fopen(filename, "rb");

  if(!fp)
  {
    if (IMAGE_TEXT_OUT) printf("file %s not found\n", filename);
    return 0;
  }

  png_structp png_ptr = png_create_read_struct
     (PNG_LIBPNG_VER_STRING, (png_voidp)0,
      0, 0);
  if (!png_ptr)
  {
    if (IMAGE_TEXT_OUT) printf("png read structure cannot be created\n");
    fclose(fp);
    return 0;
  }

  png_infop info_ptr = png_create_info_struct(png_ptr);
  if (!info_ptr)
  {
    if (IMAGE_TEXT_OUT) printf("png info structure cannot be created\n");
    png_destroy_read_struct(&png_ptr,
       (png_infopp)0, (png_infopp)0);
    fclose(fp);
    return 0;
  }

  png_infop end_info = png_create_info_struct(png_ptr);
  if (!end_info)
  {
    png_destroy_read_struct(&png_ptr, &info_ptr,
      (png_infopp)0);
    fclose(fp);
    return 0;
  }

  png_init_io(png_ptr, fp);
  png_read_info(png_ptr, info_ptr);

  w = png_get_image_width(png_ptr, info_ptr);
  h = png_get_image_height(png_ptr, info_ptr);

  png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr);
  png_byte color_type = png_get_color_type(png_ptr, info_ptr);

  if(bit_depth == 8 && color_type == PNG_COLOR_TYPE_PALETTE)
  {
    form = OGL_IMAGE_INDEX8;
    png_bytep rowpointers[h];
    bitmap = new unsigned char[h*w];
    paleta = new unsigned long[256];
    png_colorp pngpal;
    int num_palette;
    bpp=bit_depth;

    png_get_PLTE(png_ptr, info_ptr, &pngpal, &num_palette);

    for(png_uint_16 i=0; i<num_palette; i++)
        paleta[i] = (pngpal[i].red<<16)|(pngpal[i].green<<8)|(pngpal[i].blue);

    for(long i=0; i<h; i++)
        rowpointers[i] = (png_bytep)(bitmap+i*w);

    png_read_image(png_ptr, rowpointers);

    png_read_end(png_ptr, end_info);
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

    fclose(fp);
    return 1;
  }

  if(bit_depth == 8 && color_type == PNG_COLOR_TYPE_GRAY)
  {
//        if (IMAGE_TEXT_OUT) printf("Grayscale ");
    form = OGL_IMAGE_LUMINANCE8;
    png_bytep rowpointers[h];
    bitmap = new unsigned char[h*w];
    bpp=bit_depth;
    for(long i=0; i<h; i++)
        rowpointers[i] = (png_bytep)(bitmap+i*w);

    png_read_image(png_ptr, rowpointers);
    png_read_end(png_ptr, end_info);
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

//        if (IMAGE_TEXT_OUT) printf("%s\n",filename);

    fclose(fp);
    return 1;
  }


  if(bit_depth == 8 && color_type == PNG_COLOR_TYPE_RGB)
  {
    form = OGL_IMAGE_RGBA8888;
    png_bytep rowpointers[h];
    bitmap = new unsigned char[h*w*4];
    bpp=bit_depth;

    for(long i=0; i<h; i++)
        rowpointers[i] = (png_bytep)(bitmap+i*w*4);

//    png_set_bgr(png_ptr);
    png_set_filler(png_ptr, 255, PNG_FILLER_AFTER);

    png_read_update_info(png_ptr, info_ptr);

    png_read_image(png_ptr, rowpointers);

    png_read_end(png_ptr, end_info);
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

    fclose(fp);
    return 1;
  }

  if(bit_depth == 8 && color_type == PNG_COLOR_TYPE_RGB_ALPHA)
  {
    form = OGL_IMAGE_RGBA8888;
    png_bytep rowpointers[h];
    bitmap = new unsigned char[h*w*4];
    bpp=bit_depth;

    for(long i=0; i<h; i++)
        rowpointers[i] = (png_bytep)(bitmap+i*w*4);

//    png_set_bgr(png_ptr);
    png_read_update_info(png_ptr, info_ptr);
    png_read_image(png_ptr, rowpointers);
    png_read_end(png_ptr, end_info);
    png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

    fclose(fp);
    return 1;
  }

  png_read_end(png_ptr, end_info);
  png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);

  fclose(fp);
  return 0;
}

int Image::LoadJPG(const char *filename)
{
  struct jpeg_decompress_struct cinfo;
  struct jpeg_error_mgr jerr;

  FILE * infile;		/* source file */
  int row_stride;		/* physical row width in output buffer */

  if ((infile = fopen(filename, "rb")) == 0) {
    if (IMAGE_TEXT_OUT) fprintf(stderr, "can't open %s\n", filename);
    return 0;
  }

  cinfo.err = jpeg_std_error(&jerr);
  jpeg_create_decompress(&cinfo);
  jpeg_stdio_src(&cinfo, infile);
  jpeg_read_header(&cinfo, TRUE);
  jpeg_start_decompress(&cinfo);
  row_stride = cinfo.output_width * cinfo.output_components;
  
  if (IMAGE_TEXT_OUT) cout<<"width: " << cinfo.output_width << ",";
  if (IMAGE_TEXT_OUT) cout<<"height: " << cinfo.output_height << ",";
  if (IMAGE_TEXT_OUT) cout<<"components: " <<cinfo.output_components << endl;

  bitmap = new unsigned char[row_stride*cinfo.output_height];
  w = cinfo.output_width;
  h = cinfo.output_height;
  form = OGL_IMAGE_RGB888;
  bpp = cinfo.output_components << 3;

  JSAMPROW row_pointer[1];
  int i=0;

  while (cinfo.output_scanline < cinfo.output_height) {
    row_pointer[0] = bitmap+i*row_stride;
    i++;
    jpeg_read_scanlines(&cinfo, row_pointer, 1);
  }

  jpeg_finish_decompress(&cinfo);
  jpeg_destroy_decompress(&cinfo);
  fclose(infile);
  return 1;
}

const char *getext(const char *str) {
  const char *ext = 0;
  while(*str)
  {
    if(*str==46) ext=str+1;
    str++;
  }
  if(*ext==0)
    ext = 0;
  return ext;
}

int Image::Load(const char *filename)
{
  const char *extension = getext(filename);

  if(stricmp(extension, "bmp")==0) {
    return LoadBMP(filename);
  }
  if(stricmp(extension, "png")==0) {
    return LoadPNG(filename);
  }
  if(stricmp(extension, "jpg")==0) {
    return LoadJPG(filename);
  }
  return 0;
}