#include <memory>
#include <string>
#include <vector>
#include <string.h>
#include <ctype.h>
#include "corona.h"
#include "MemoryFile.h"
#include "Open.h"
#include "Save.h"
#include "SimpleImage.h"


namespace corona {
  namespace hidden {

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(const char*) CorGetVersion() {
      return "1.0.2";
    }

    ///////////////////////////////////////////////////////////////////////////

    class FFDImpl : public FileFormatDesc {
    public:
      FFDImpl(FileFormat format, const char* description, const char* exts) {
        m_format = format;
        m_description = description;

        const char* ext = exts;
        while (*ext) {
          m_extensions.push_back(ext);
          ext += strlen(ext) + 1;
        }
      }

      FileFormat getFormat()             { return m_format;                }
      const char* getDescription()       { return m_description.c_str();   }
      size_t getExtensionCount()         { return m_extensions.size();     }
      const char* getExtension(size_t i) { return m_extensions[i].c_str(); }

    private:
      FileFormat m_format;
      std::string m_description;
      std::vector<std::string> m_extensions;
    };

    FFDImpl ffPNG (FF_PNG,  "PNG Files",  "png\0");
    FFDImpl ffJPEG(FF_JPEG, "JPEG Files", "jpeg\0jpg\0");
    FFDImpl ffPCX (FF_PCX,  "PCX Files",  "pcx\0");
    FFDImpl ffBMP (FF_BMP,  "BMP Files",  "bmp\0");
    FFDImpl ffTGA (FF_TGA,  "TGA Files",  "tga\0");
    FFDImpl ffGIF (FF_GIF,  "GIF Files",  "gif\0");

    const int MAX_FORMAT_COUNT = 64;
    FileFormatDesc** g_read_formats  = 0;
    FileFormatDesc** g_write_formats = 0;
    FileFormatDesc* g_read_array[MAX_FORMAT_COUNT + 1]  = {0};
    FileFormatDesc* g_write_array[MAX_FORMAT_COUNT + 1] = {0};


    COR_EXPORT(FileFormatDesc**) CorGetSupportedReadFormats() {
      if (!g_read_formats) {
        g_read_formats = g_read_array;
        FileFormatDesc** f = g_read_formats;
        *f++ = &ffPNG;
        *f++ = &ffPCX;
        *f++ = &ffBMP;
        *f++ = &ffTGA;
      }
      return g_read_formats;
    }


    COR_EXPORT(FileFormatDesc**) CorGetSupportedWriteFormats() {
      if (!g_write_formats) {
        g_write_formats = g_write_array;
        FileFormatDesc** f = g_write_formats;
		*f++ = &ffPNG;
        *f++ = &ffTGA;
      }
      return g_write_formats;
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(Image*) CorCreateImage(
      int width,
      int height,
      PixelFormat format)
    {
      return CreateImage(width, height, format, 0);
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(Image*) CorCreateImageWithPixels(
      int width,
      int height,
      PixelFormat format,
      void* pixels)
    {
      // this function only supports creation of non-palettized images
      if (!IsDirect(format)) {
        return 0;
      }

      int size = width * height * GetPixelSize(format);
      byte* p = new byte[size];
      if (pixels) {
        memcpy(p, pixels, size);
      } else {
        memset(p, 0, size);
      }
      return new SimpleImage(width, height, format, p);
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(Image*) CorCreatePalettizedImage(
      int width,
      int height,
      PixelFormat format,
      int palette_size,
      PixelFormat palette_format)
    {
      // only support creation of palettized images
      if (!IsPalettized(format) || !IsDirect(palette_format)) {
        return 0;
      }

      // make sure the palette is the right size
      if (palette_size != GetPaletteSize(format)) {
        return 0;
      }

      int size = width * height * GetPixelSize(format);
      byte* pixels = new byte[size];
      memset(pixels, 0, size);

      int palette_bytes = palette_size * GetPixelSize(palette_format);
      byte* palette = new byte[palette_bytes];
      memset(palette, 0, palette_bytes);

      return new SimpleImage(width, height, format, pixels,
                             palette, palette_size, palette_format);      
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(Image*) CorCloneImage(
      Image* source,
      PixelFormat format)
    {
      if (!source) {
        // we need an image to clone :)
        return 0;
      }

      const int width = source->getWidth();
      const int height = source->getHeight();
      const PixelFormat source_format = source->getFormat();

      const int source_pixel_size = GetPixelSize(source_format);
      if (source_pixel_size == 0) {
        // unknown pixel size?
        return 0;
      }

      // duplicate the image
      int image_size = width * height * source_pixel_size;
      byte* pixels = new byte[image_size];
      memcpy(pixels, source->getPixels(), image_size);
      
      if (IsPalettized(source_format)) {
        // clone palette
        int palette_size = source->getPaletteSize();
        PixelFormat palette_format = source->getPaletteFormat();
        int palette_bytes = palette_size * GetPixelSize(palette_format);
        byte* palette = new byte[palette_bytes];
        memcpy(palette, source->getPalette(), palette_bytes);
        Image* image = new SimpleImage(width, height, source_format, pixels,
                                       palette, palette_size, palette_format);
        return ConvertImage(image, format);
      }

      Image* image = new SimpleImage(width, height, source_format, pixels);
      return ConvertImage(image, format);
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(Image*) CorOpenImage(
      const char* filename,
      FileFormat file_format)
    {
      if (!filename) {
        return 0;
      }

      std::auto_ptr<File> file(OpenFile(filename, false));
      return CorOpenImageFromFile(file.get(), file_format);
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(Image*) CorOpenImageFromFile(
      File* file,
      FileFormat file_format)
    {
      if (!file) {
        return 0;
      }

#define TRY_TYPE(type)                                 \
  {                                                    \
    Image* image = CorOpenImageFromFile(file, (type)); \
    if (image) { return image; }                       \
  }

      file->seek(0, File::BEGIN);
      switch (file_format) {
        case FF_AUTODETECT: {
          TRY_TYPE(FF_PNG);
          TRY_TYPE(FF_PCX);
          TRY_TYPE(FF_BMP);
          TRY_TYPE(FF_TGA);
          return 0;
        }
        
        case FF_PNG:  return OpenPNG(file);
        case FF_PCX:  return OpenPCX(file);
        case FF_BMP:  return OpenBMP(file);
        case FF_TGA:  return OpenTGA(file);
        default:      return 0;
      }
    }

    ///////////////////////////////////////////////////////////////////////////

    int strcmp_ci(const char* a, const char* b) {
      while (*a && *b) {
        const int diff = tolower(*a) - tolower(*b);
        if (diff != 0) {
          return diff;
        }
        ++a;
        ++b;
      }
      return tolower(*a) - tolower(*b);
    }

    bool ends_with(const char* str, const char* ext) {
      const int str_len = strlen(str);
      const int ext_len = strlen(ext);
      return (str_len >= ext_len &&
              strcmp_ci(str + str_len - ext_len, ext) == 0);
    }

    COR_EXPORT(bool) CorSaveImage(
      const char* filename,
      FileFormat file_format,
      Image* image)
    {
      if (!filename) {
        return false;
      }

      if (file_format == FF_AUTODETECT) {
        if (ends_with(filename, ".png")) {
          file_format = FF_PNG;
        } else if (ends_with(filename, ".tga")) {
          file_format = FF_TGA;
        } else {
          return false;
        }
      }

      std::auto_ptr<File> file(OpenFile(filename, true));
      return CorSaveImageToFile(file.get(), file_format, image);
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(bool) CorSaveImageToFile(
      File* file,
      FileFormat file_format,
      Image* image)
    {
      if (!file || !image) {
        return false;
      }

      switch (file_format) {
        case FF_PNG:  return SavePNG(file, image);
        case FF_JPEG: return false;
        case FF_PCX:  return false;
        case FF_BMP:  return false;
        case FF_TGA:  return SaveTGA(file, image);
        case FF_GIF:  return false;
        default:      return false;
      }
    }

    ///////////////////////////////////////////////////////////////////////////

    COR_EXPORT(int) CorGetPixelSize(PixelFormat format) {
      switch (format) {
        case PF_R8G8B8A8: return 4;
        case PF_R8G8B8:   return 3;
        case PF_B8G8R8A8: return 4;
        case PF_B8G8R8:   return 3;
        case PF_I8:       return 1;
        default:          return 0;
      }
    }

    ///////////////////////////////////////////////////////////////////////////

  }
}
