#include <cstdlib>
#include <cstring>
#include <IL/il.h>
#include "Image.h"
#include "Array2D.h"
#include "exceptions.h"

namespace fft
{
   //template class Matrix<f32>;

   bool Image::initialized = false;


   Image::Image(const char* fileName)
      : width(0), height(0), format(Unknown), data(0)
   {
      // if not initialized OpenIL
      if (!initialized)
      {
	 // init it
	 ilInit();
	 if (ilGetError() != IL_NO_ERROR)
	 {
	    throw BaseException("OpenIL library initialization failed");
	 }
	 initialized = true;
	 ilEnable(IL_ORIGIN_SET);
	 ilOriginFunc(IL_ORIGIN_UPPER_LEFT);
      }

      // creates new OpenIL image
      ILuint imageId;
      ilGenImages(1, &imageId);
      ilBindImage(imageId);

      // load image from file
      ilLoadImage(const_cast<char*>(fileName));

      // check for errors 
      if (ilGetError() != IL_NO_ERROR)
      {
	 throw FileNotFoundException(fileName);
      }

      // get image dimension
      width = static_cast<u32>(ilGetInteger(IL_IMAGE_WIDTH));
      height = static_cast<u32>(ilGetInteger(IL_IMAGE_HEIGHT));

      // get image format
      ILint format = ilGetInteger(IL_IMAGE_BYTES_PER_PIXEL);
      this->format = static_cast<Format>(format);

      // convert image so it has right order of chanelles
      switch (this->format)
      {
	 case GreyScale:
	    ilConvertImage(IL_LUMINANCE, IL_UNSIGNED_BYTE);
	    break;

	 case RGB:
	    ilConvertImage(IL_RGB, IL_UNSIGNED_BYTE);
	    break;

	 case RGBA:
	    ilConvertImage(IL_RGBA, IL_UNSIGNED_BYTE);
	    break;

	 default:
	    throw InvalidOperationException("Unsupported image format");

      }

      // get size of buffer in bytes
      u32 size = format * width * height;
      // create buffer
      data = new u8[size];

      // copy image data
      memcpy(data, ilGetData(), size);


      // delete OpenIL image
      ilDeleteImages(1, &imageId);
		
   }

   Image::Image(const Image& src)
      : width(src.width),height(src.height),format(src.format),data(0)
   {
      u32 size = width * height * static_cast<u8>(format);
      data = new u8[size];
      memcpy(data, src.data, size);
   }

   Image& Image::operator=(const Image& rhs)
   {
      width = rhs.width;
      height = rhs.height;
      format = rhs.format;

      u32 size = width * height * static_cast<u8>(format);

      if (data)
      {
	 delete [] data;
      }

      data = new u8[size];
      memcpy(data, rhs.data, size);

      return *this;
   }


   Image::~Image()
   {
      if (data)
      {
	 delete [] data;
      }
   }


/*  
   Array2D<real> Image::toGreyScaleArray()
   {

      // weight of R,G,B channels to compute luminance
      const f64 rs = .2126;
      const f64 gs = .7152;
      const f64 bs = .0722;

      // create array to store grey scale image
      Array2D<real> ret(width, height);

      // get stride of pixel in bytes
      u8 stride = static_cast<u8>(format);
      // pointer into image data for iteration
      u8* it = data;

      // go thru all pixels
      for (u32 i = 0; i < height; i++)
      {
	 for (u32 j = 0; j < width; j++)
	 {
	    // compute index in output array
	    u32 idx = i * width + j;

	    // perform conversion
	    if (format == GreyScale)
	    {
	       ret[idx] = it[0] / 255.0f;
	    }
	    else
	    {
	       f64 r = it[0] / 255.0;
	       f64 g = it[1] / 255.0;
	       f64 b = it[2] / 255.0;
	       ret[idx] = r * rs + g * gs + b * bs;
	    }

	    // increment image pointer
	    it += stride;
	 }
      }

      return ret;
   }
*/
   /*
   void Image::storeArray(const char* fileName, const Array2D& arr, u32 w, bool normalize)
   {
      // if OpenIL is not yet initialized
      if (!initialized)
      {
	 // initialize it
	 ilInit();
	 initialized = true;
      }

      // create new OpenIL image
      ILuint imageId;
      ilGenImages(1, &imageId);
      ilBindImage(imageId);


      // create buffer for image data
      u32 size = arr.size();
      u8* data = new u8[size];
      u32 h = size/w;

      real mx,mn = mx = abs(arr[0]);
      for (u32 i = 0; i < arr.size(); i++)
      {
	 real val = abs(arr[i]);
	 if (val < mn) mn = val;
	 if (val > mx) mx = val;
      }

   real d = mx - mn;


      // convert data from interval <0; 1> to <0; 255>
      for(u32 i = 0; i < size; i++)
      {
	 Complex c = arr[i];
	 real val = abs(c);
	 if (normalize)
	 {
	    c = (val - mn)/d;
	 }
	 u32 t = static_cast<u32>(val * 255);
	 if (t > 255) t = 255;
	 data[i] = static_cast<u8>(t);
      }

      // store image buffer into OpenIL image
      ilTexImage(w, h, 1, 1, IL_LUMINANCE, IL_UNSIGNED_BYTE, data);

      // save file
      ilEnable(IL_FILE_OVERWRITE);
      ilSaveImage(const_cast<char*>(fileName));

      // delete OpenIL image
      ilDeleteImages(1, &imageId);

   } 
   */
}

