/**
 * Corona Image I/O Library
 * Version 1.0.2
 * (c) 2003 Chad Austin
 *
 * This API uses principles explained at
 * http://aegisknight.org/cppinterface.html
 *
 * This code licensed under the terms of the zlib license.  See
 * license.txt.
 *
 *
 * Note: When compiling this header in gcc, you may want to use the
 * -Wno-non-virtual-dtor flag to get rid of those annoying "class has
 * virtual functions but no virtual destructor" warnings.
 */


#ifndef CORONA_H
#define CORONA_H


#ifndef __cplusplus
#error Corona requires C++
#endif


#include <stddef.h>


// DLLs in Windows should use the standard calling convention
#ifndef COR_CALL
#  if defined(WIN32) || defined(_WIN32)
#    define COR_CALL __stdcall
#  else
#    define COR_CALL
#  endif
#endif

// Export functions from the DLL
#ifndef COR_DECL
#  if defined(WIN32) || defined(_WIN32)
#    ifdef CORONA_EXPORTS
#      define COR_DECL __declspec(dllexport)
#    else
#      define COR_DECL
#    endif
#  else
#    define COR_DECL
#  endif
#endif


// evil "identifier is too long in debug information" warning
#ifdef _MSC_VER
#pragma warning(disable : 4786)
#endif


#define COR_FUNCTION(ret) extern "C" COR_DECL ret COR_CALL


namespace corona {

  /**
   * File formats supported for reading or writing.
   */
  enum FileFormat {
    FF_AUTODETECT = 0x0100,
    FF_PNG        = 0x0101,
    FF_JPEG       = 0x0102,
    FF_PCX        = 0x0103,
    FF_BMP        = 0x0104,
    FF_TGA        = 0x0105,
    FF_GIF        = 0x0106,
  };

  /**
   * Pixel format specifications.  Pixel data can be packed in one of
   * the following ways.
   */
  enum PixelFormat {
    PF_DONTCARE = 0x0200,  /**< special format used when specifying a
                                desired pixel format */
    PF_R8G8B8A8 = 0x0201,  /**< RGBA, channels have eight bits of precision */
    PF_R8G8B8   = 0x0202,  /**< RGB, channels have eight bits of precision  */
    PF_I8       = 0x0203,  /**< Palettized, 8-bit indices into palette      */
    PF_B8G8R8A8 = 0x0204,  /**< BGRA, channels have eight bits of precision */
    PF_B8G8R8   = 0x0205,  /**< BGR, channels have eight bits of precision  */
  };

  /**
   * Axis specifications.  The image can be flipped along the following
   * axes.
   */
  enum CoordinateAxis {
    CA_X     = 0x0001,
    CA_Y     = 0x0002,
  };

  /**
   * A helper class for DLL-compatible interfaces.  Derive your cross-DLL
   * interfaces from this class.
   *
   * When deriving from this class, do not declare a virtual destructor
   * on your interface.
   */
  class DLLInterface {
  private:
    /**
     * Destroy the object, freeing all associated memory.  This is
     * the same as a destructor.
     */
    virtual void COR_CALL destroy() = 0;

  public:
    /**
     * "delete image" should actually call image->destroy(), thus putting the
     * burden of calling the destructor and freeing the memory on the image
     * object, and thus on Corona's side of the DLL boundary.
     */
    void operator delete(void* p) {
      if (p) {
        DLLInterface* i = static_cast<DLLInterface*>(p);
        i->destroy();
      }
    }
  };


  /**
   * A helper class for DLL-compatible interface implementations.  Derive
   * your implementations from DLLImplementation<YourInterface>.
   */
  template<class Interface>
  class DLLImplementation : public Interface {
  public:
    /**
     * So the implementation can put its destruction logic in the destructor,
     * as natural C++ code does.
     */
    virtual ~DLLImplementation() { }

    /**
     * Call the destructor in a Win32 ABI-compatible way.
     */
    virtual void COR_CALL destroy() {
      delete this;
    }

    /**
     * So destroy()'s "delete this" doesn't go into an infinite loop,
     * calling the interface's operator delete, which calls destroy()...
     */
    void operator delete(void* p) {
      ::operator delete(p);
    }
  };

  
  /**
   * An image object represents a rectangular collections of pixels.
   * They have a width, a height, and a pixel format.  Images cannot
   * be resized.
   */
  class Image : public DLLInterface {
  public:
    /**
     * Get image width.
     * @return  image width
     */
    virtual int COR_CALL getWidth() = 0;

    /**
     * Get image height.
     * @return  image height
     */
    virtual int COR_CALL getHeight() = 0;

    /**
     * Get pixel format.
     * @return  pixel format
     */
    virtual PixelFormat COR_CALL getFormat() = 0;

    /**
     * Get pixel buffer.  The pixels are packed in the format defined
     * by the image's pixel format.
     *
     * @return  pointer to first element in pixel buffer
     */
    virtual void* COR_CALL getPixels() = 0;

    /**
     * Get the palette.  Pixels are packed in the format defined by
     * getPaletteFormat().
     *
     * @return  pointer to first palette entry
     */
    virtual void* COR_CALL getPalette() = 0;

    /**
     * Get the number of entries in the palette.
     *
     * @return  number of palette entries
     */
    virtual int COR_CALL getPaletteSize() = 0;

    /**
     * Get the format of the colors in the palette.
     *
     * @return  pixel format of palette entries
     */
    virtual PixelFormat COR_CALL getPaletteFormat() = 0;
  };


  /**
   * Represents a random-access file, usually stored on a disk.  Files
   * are always binary: that is, they do no end-of-line
   * transformations.  File objects are roughly analogous to ANSI C
   * FILE* objects.
   */
  class File : public DLLInterface {
  public:

    /**
     * The different ways you can seek within a file.
     */
    enum SeekMode {
      BEGIN,    /**< relative to the beginning of the file */
      CURRENT,  /**< relative to the current position in the file */
      END       /**< relative to the end of the file: position should
                     be negative*/
    };

    /**
     * Read size bytes from the file, storing them in buffer.
     *
     * @param buffer  buffer to read into
     * @param size    number of bytes to read
     *
     * @return  number of bytes successfully read
     */
    virtual int COR_CALL read(void* buffer, int size) = 0;

    /**
     * Write size bytes from buffer to the file.
     *
     * @param buffer  buffer that contains the data to write
     * @param size    number of bytes to write
     *
     * @return  number of bytes successfully written
     */
    virtual int COR_CALL write(const void* buffer, int size) = 0;

    /**
     * Jump to a new position in the file, using the specified seek
     * mode.  Remember: if mode is END, the position must be negative,
     * to seek backwards from the end of the file into its contents.
     * If the seek fails, the current position is undefined.
     *
     * @param position  position relative to the mode
     * @param mode      where to seek from in the file
     *
     * @return  true on success, false otherwise
     */
    virtual bool COR_CALL seek(int position, SeekMode mode) = 0;

    /**
     * Get current position within the file.
     *
     * @return  current position
     */
    virtual int COR_CALL tell() = 0;
  };


  /// Describes a file format that Corona supports.
  class FileFormatDesc {
  protected:
    ~FileFormatDesc() { }

  public:
    /// Actual FileFormat being described.
    virtual FileFormat getFormat() = 0;

    /// Short description of format, such as "PNG Files" or "JPEG Files"
    virtual const char* getDescription() = 0;

    /// @{
    /// List of supported extensions, such as {"bmp", "rle", "dib"}
    virtual size_t getExtensionCount() = 0;
    virtual const char* getExtension(size_t i) = 0;
    /// @}
  };


  /// PRIVATE API - for internal use only
  namespace hidden {

    // these are extern "C" so we don't mangle the names


    // API information

    COR_FUNCTION(const char*) CorGetVersion();

    COR_FUNCTION(FileFormatDesc**) CorGetSupportedReadFormats();
    COR_FUNCTION(FileFormatDesc**) CorGetSupportedWriteFormats();

    // creation

    COR_FUNCTION(Image*) CorCreateImage(
      int width,
      int height,
      PixelFormat format);

    COR_FUNCTION(Image*) CorCreateImageWithPixels(
      int width,
      int height,
      PixelFormat format,
      void* pixels);

    COR_FUNCTION(Image*) CorCreatePalettizedImage(
      int width,
      int height,
      PixelFormat format, // must be a palettized format
      int palette_size,
      PixelFormat palette_format);

    COR_FUNCTION(Image*) CorCloneImage(
      Image* source,
      PixelFormat format);

    // loading

    COR_FUNCTION(Image*) CorOpenImage(
      const char* filename,
      FileFormat file_format);

    COR_FUNCTION(Image*) CorOpenImageFromFile(
      File* file,
      FileFormat file_format);

    // saving

    COR_FUNCTION(bool) CorSaveImage(
      const char* filename,
      FileFormat file_format,
      Image* image);

    COR_FUNCTION(bool) CorSaveImageToFile(
      File* file,
      FileFormat file_format,
      Image* image);

    // conversion

    COR_FUNCTION(Image*) CorConvertImage(
      Image* image,
      PixelFormat format);

    COR_FUNCTION(Image*) CorConvertPalette(
      Image* image,
      PixelFormat palette_format);

    COR_FUNCTION(Image*) CorFlipImage(
      Image* image,
      int coordinate_axis);

    // files

    COR_FUNCTION(File*) CorOpenFile(const char* name, bool writeable);
    COR_FUNCTION(File*) CorCreateMemoryFile(const void* buffer, int size);

    // utility

    COR_FUNCTION(int) CorGetPixelSize(PixelFormat format);
  }


  /* PUBLIC API */


  /**
   * Return the Corona version string.
   *
   * @return  Corona version information
   */
  inline const char* GetVersion() {
    return hidden::CorGetVersion();
  }


  /**
   * Returns a null-terminated array of FileFormatDesc* pointers that
   * describe the file formats Corona can read.  The array is owned by
   * Corona, so do not delete it when you are done using it.
   */
  inline FileFormatDesc** GetSupportedReadFormats() {
    return hidden::CorGetSupportedReadFormats();
  }

  /**
   * Returns a null-terminated array of FileFormatDesc* pointers that
   * describe the file formats Corona can write.  The array is owned
   * by Corona, so do not delete it when you are done using it.
   */
  inline FileFormatDesc** GetSupportedWriteFormats() {
    return hidden::CorGetSupportedWriteFormats();
  }


  /**
   * Create a new, blank image with a specified width, height, and
   * format.  If pixels is specified, Corona uses them to initialize
   * the contents of the image.  Corona does *not* take ownership of
   * the pixel memory, so the caller is responsible for cleaning up
   * after itself.  If pixels is not specified, the new image is
   * filled with zeroes.
   *
   * @param width   width of the new image
   * @param height  height of the new image
   * @param format  format the pixels are stored in, cannot be PF_DONTCARE
   * @param pixels  pixel buffer used to initialize the new image
   *
   * @return  newly created blank image
   */
  inline Image* CreateImage(
    int width,
    int height,
    PixelFormat format,
    void* pixels = 0)
  {
    return hidden::CorCreateImageWithPixels(width, height, format, pixels);
  }

  /**
   * Create a new, blank image with a specified width, height, format,
   * and palette.
   *
   * @param width           width of image
   * @param height          height of image
   * @param format          format of palette indices, should be PF_I8
   * @param palette_size    number of colors in palette
   * @param palette_format  pixel format of palette entries
   */
  inline Image* CreateImage(
    int width,
    int height,
    PixelFormat format,
    int palette_size,
    PixelFormat palette_format)
  {
    return hidden::CorCreatePalettizedImage(
      width, height, format,
      palette_size, palette_format);
  }

  /**
   * Create a new image from an old one.  If format is specified, the
   * new image is converted to that pixel format.  If format is not
   * specified, the new image simply uses the same format as the
   * source.  If the image could not be cloned or the pixel format is
   * invalid, CloneImage returns 0.
   *
   * @param source  image to clone
   * @param format  format the new image is stored in, defaults to PF_DONTCARE
   *
   * @return  new image cloned from the source, 0 if failure
   */
  inline Image* CloneImage(
    Image* source,
    PixelFormat format = PF_DONTCARE)
  {
    return hidden::CorCloneImage(source, format);
  }

  /**
   * Opens an image from the default filesystem.  This function simply
   * forwards the call to OpenImage(file, file_format, pixel_format)
   * with a standard C library file.
   *
   * See OpenImage(fs, filename, file_format, pixel_format) for more
   * information.
   *
   * @param filename      image filename to open
   * @param file_format   file format the image is stored in, or FF_AUTODETECT
   *                      to try all loaders
   * @param pixel_format  desired pixel format, or PF_DONTCARE to use image's
   *                      native format
   *
   * @return  the image loaded from the disk, or 0 if it cannot be opened
   */
  inline Image* OpenImage(
    const char* filename,
    PixelFormat pixel_format = PF_DONTCARE,
    FileFormat file_format = FF_AUTODETECT)
  {
    return hidden::CorConvertImage(
      hidden::CorOpenImage(filename, file_format),
      pixel_format);
  }

  /**
   * Opens an image from the specified file.
   *
   * If file_format is FF_AUTODETECT, the loader tries
   * to load each format until it finds one that succeeds.  Otherwise,
   * it tries the specific loader specified.
   *
   * If pixel_format is PF_DONTCARE, the new image object has the
   * pixel format closest to the image's format on disk.  Otherwise,
   * the pixels are converted to the specified format before the image
   * is returned.
   *
   * @param file          name of the file that contains the image
   * @param file_format   file format the image is stored in, or FF_AUTODETECT
   *                      to try all loaders
   * @param pixel_format  desired pixel format, or PF_DONTCARE to use image's
   *                      native format
   *
   * @return  the image loaded from the file, or 0 if it cannot be opened
   */
  inline Image* OpenImage(
    File* file,
    PixelFormat pixel_format = PF_DONTCARE,
    FileFormat file_format = FF_AUTODETECT)
  {
    return hidden::CorConvertImage(
      hidden::CorOpenImageFromFile(file, file_format),
      pixel_format);
  }

  /// For compatibility.  This function may be deprecated.
  inline Image* OpenImage(
    const char* filename,
    FileFormat file_format,
    PixelFormat pixel_format = PF_DONTCARE)
  {
    return OpenImage(filename, pixel_format, file_format);
  }

  /// For compatibility.  This function may be deprecated.
  inline Image* OpenImage(
    File* file,
    FileFormat file_format,
    PixelFormat pixel_format = PF_DONTCARE)
  {
    return OpenImage(file, pixel_format, file_format);
  }

  /**
   * Saves an image to a file in the default filesystem.  This
   * function simply calls SaveImage(file, file_format, image)
   * with a standard C library file.
   *
   * See SaveImage(fs, filename, file_format, image) for more information.
   *
   * @param filename     name of the file to save the image to
   * @param file_format  file format in which to save image.  if FF_AUTODETECT,
   *                     SaveImage guesses the type from the file extension
   * @param image        image to save
   *
   * @return  true if save succeeds, false otherwise
   */
  inline bool SaveImage(
    const char* filename,
    FileFormat file_format,
    Image* image)
  {
    return hidden::CorSaveImage(filename, file_format, image);
  }

  /**
   * Saves an image to the specified file.  This function saves image
   * to a file of type file_format.  If file_format is not a supported
   * output type, the function fails.  As of now, Corona only supports
   * saving images of type FF_PNG and FF_TGA.
   *
   * @note This function may create the file even if the save does not
   *       succeed, so users of this library should remove the file after
   *       the call to SaveImage().
   *
   * @param file         file in which to save the image
   * @param file_format  file format in which to save image -- must not be
   *                     FF_AUTODETECT
   * @param image        image to save
   *
   * @return  true if the save succeeds, false otherwise
   */
  inline bool SaveImage(
    File* file,
    FileFormat file_format,
    Image* image)
  {
    return hidden::CorSaveImageToFile(file, file_format, image);
  }

  /**
   * Converts an image from one format to another, destroying the old
   * image.  If source is 0, the function returns 0.  If format is
   * PF_DONTCARE or the source and target formats match, returns the
   * unmodified source image.  If a valid conversion is not found,
   * ConvertImage destroys the old image and returns 0.  For example,
   * ConvertImage does not support creating a palettized image from a
   * direct color image yet.
   *
   * @param source  image to convert
   * @param format  desired format -- can be PF_DONTCARE
   *
   * @return  valid image object if conversion succeeds, 0 otherwise
   */
  inline Image* ConvertImage(Image* source, PixelFormat format) {
    return hidden::CorConvertImage(source, format);
  }

  /**
   * Converts the palette of a palettized image from one format to
   * another, destroying the old image.  If the source is 0, the
   * palette_format is PF_DONTCARE, or the source and target formats
   * match, the function returns the unmodified source image.  If a
   * valid conversion is not found or invalid inputs are given (such
   * as a direct-color source image), this function destroys the old
   * image and returns 0.
   *
   * @param source          palettized image to convert
   * @param palette_format  desired pixel format of palette
   *
   * @return  valid image object if conversion succeeds, 0 otherwise
   */
  inline Image* ConvertPalette(Image* source, PixelFormat palette_format) {
    return hidden::CorConvertPalette(source, palette_format);
  }

  /**
   * Flips the pixels in the image around the given axis.
   *
   * @param source           image to flip
   * @param coordinate_axis  Axis around which to flip.  Both CA_X and CA_Y
   *                         can be specified by ORing them together.
   *
   * @return  the image passed in
   */
  inline Image* FlipImage(Image* source, int coordinate_axis) {
    return hidden::CorFlipImage(source, coordinate_axis);
  }

  /**
   * Returns a default File implementation.
   *
   * @param  filename   name of the file on local filesystem
   * @param  writeable  whether the file can be written to
   */
  inline File* OpenFile(const char* filename, bool writeable) {
    return hidden::CorOpenFile(filename, writeable);
  }

  /**
   * Creates a File implementation that reads from a buffer in memory.
   * It stores a copy of the buffer that is passed in.
   *
   * The File object does <i>not</i> take ownership of the memory buffer.
   * When the file is destroyed, it will not free the memory.
   *
   * @param buffer  Pointer to the beginning of the data.
   * @param size    Size of the buffer in bytes.
   *
   * @return  0 if size is non-zero and buffer is null. Otherwise,
   *          returns a valid File object.
   */
  inline File* CreateMemoryFile(const void* buffer, int size) {
    return hidden::CorCreateMemoryFile(buffer, size);
  }

  /**
   * Returns the number of bytes needed to store a pixel of a gixen format.
   *
   * @param format  The format to query.
   *
   * @return  Number of bytes each pixel takes, or 0 if the format is invalid.
   */
  inline int GetPixelSize(PixelFormat format) {
    return hidden::CorGetPixelSize(format);
  }

  /**
   * Returns true if the pixel format does not require a palette; that
   * is, if each pixel itself contains color data.
   *
   * @param format  The format to query.
   *
   * @return  True if format is direct color, false otherwise.
   */
  inline bool IsDirect(PixelFormat format) {
    return (format == PF_R8G8B8A8 || format == PF_R8G8B8 ||
            format == PF_B8G8R8A8 || format == PF_B8G8R8);
  }

  /**
   * Returns true if the pixel format requires a palette; that
   * is, if each pixel is an index into a separate palette.
   *
   * @param format  The format to query.
   *
   * @return  True if format is palettized, false otherwise.
   */
  inline bool IsPalettized(PixelFormat format) {
    return format == PF_I8;
  }

  /**
   * Returns the number of color entries in a palette for an image
   * of the given format.
   *
   * @param format  The format to query.
   *
   * @return  Number of color entries, or 0 if the format is not palettized.
   */
  inline int GetPaletteSize(PixelFormat format) {
    return (format == PF_I8 ? 256 : 0);
  }

}


#endif
