#pragma once

#include <windows.h>
#include <stdio.h>

/**
 * データブロック
 */
struct DataBlock {
	int size;
	char* data;

	DataBlock() : size(0), data(NULL) {
	}

	~DataBlock() {
		free(data);
	}
};

// 前方参照
struct DecoderInfo;

/**
 * GIFFileクラス
 * gnu.javax.imageio.gif.GIFFileの内容に基づき作成している
 */
class GIFFile {
private:
	// "NETSCAPE2.0" - identifier
	static const unsigned char nsBlock[];

	/**
	 * Block identifiers
	 */
	static const int EXTENSION = 0x21;
	static const int LOCAL = 0x2C;
	static const int TERMINATOR = 0x3B;

	/**
	 * Extension block types
	 */
	static const int EXTENSION_COMMENT = 254;
	static const int EXTENSION_GCONTROL = 249; 
	static const int EXTENSION_APPLICATION = 255;

	/**
	 * Parent image
	 */
	GIFFile* parent;

	/**
	 * Image position and dimensions (images may be partial)
	 */
	int x, y, width, height;

	/**
	 * Global dimensions
	 */
	int globalWidth, globalHeight;

	/**
	 * Background color index.
	 */
	unsigned char bgIndex;

	/**
	 * Number of colors
	 */
	int nColors;

	/**
	 * Global palette, if any
	 */
	unsigned char* globalPalette;

	/**
	 * Any
	 */
	bool hasGlobalColorMap;

	/**
	 * Local palette, if any (used if available)
	 */
	unsigned char* localPalette;

	/**
	 * Interlaced GIF or not?
	 */
	bool interlaced;

	/**
	 * Has transparency?
	 */
	bool hasTransparency;
  
	/**
	 * disposal method (animations)
	 */
    int disposalMethod;

	/**
     * Transparent index;
     */
	int transparentIndex;

	/**
	 * Frame delay in 100ths of a second ( centiseconds, metrically )
	 */
	int duration;

	/**
	 * Netscape animation extension
	 */
	bool isLooped;

	/** Number of loopCount, 0 = infinite */
	int loopCount;

	/**
	 * Decoded image
	 */
	BYTE* raster;

	/**
	 * Additional frames if it's an animated GIF.
	 */
	GIFFile** animationFrames;
	int animationFrameCount;

	bool valid;

	bool readSignature(FILE* file);
	bool readExtension(FILE* file);
	
	bool readLocal(FILE* file);
	bool loadImage(FILE* file);

	DataBlock* readData(FILE* file);

	bool decodeRaster(int initialCodeSize, DataBlock* compressedData, BYTE* pBits);
	bool deinterlace(BYTE* pBits);

	int getBits(int c, DecoderInfo& info);

	inline DWORD indexToARGB(unsigned char* palette, int index) {
		unsigned char* color = &palette[index * 3];
		DWORD pixel = (DWORD) ((color[0] << 16) & 0xff0000) | ((color[1] << 8) & 0xff00)| (color[2] & 0xff);
		if (! hasTransparentColor() || index != this->transparentIndex) {
			pixel |= 0xff000000;
		}
		return pixel;
	}

	inline WORD indexToRGB565(unsigned char* palette, int index) {
		unsigned char* color = &palette[index * 3];
		WORD pixel = (WORD) ((color[0] << 8) & 0xf800) | ((color[1] << 3) & 0x07e0) | ((color[2] >> 3) & 0x001f);
		return pixel;
	}

	/**
	 * アニメーションGIFの第2フレーム以降を構築する
	 */
	GIFFile(GIFFile* parent, FILE* file, int c);

public:
	GIFFile(_TCHAR* filename);
	~GIFFile();

	/**
	 * この画像が正しく解釈されたかを返す
	 */
	inline bool isValid() { return this->valid; }

	/**
	 * アニメーション用の後続フレーム数を得る
	 */
	inline int getAnimationFrameCount() { return this->animationFrameCount; }

	/**
	 * 後続フレームを取得する
	 */
	inline GIFFile* getAnimationFrame(int frame) { return this->animationFrames[frame]; }

	/**
	 * 1枚目のフレームを取得する
	 */
	inline GIFFile* getParent() { return this->parent; }

	inline int getGlobalWidth() { return this->globalWidth; }
	inline int getGlobalHeight() { return this->globalHeight; }


	inline int getX() { return this->x; }
	inline int getY() { return this->y; }

	/**
	 * イメージの幅を返す
	 */
	inline int getWidth()	{ return this->width; }

	/**
	 * イメージの高さを返す
	 */
	inline int getHeight()	{ return this->height; }

	/**
	 * 表示時間を返す
	 * 単位は10ms
	 */
	inline int getDuration() { return this->duration; }

	/**
	 * ループ回数を返す
	 */
	inline int getLoopCount() { return this->isLooped ? this->loopCount : -1; }

	/**
	 * 透明色をサポートしているかを返す
	 */
	inline bool hasTransparentColor() { return this->hasTransparency; }

	/**
	 * 透明色を返す
	 */
	COLORREF getTransparentColor();
	
	/**
	 * Disposal method for animation.
	 */
	static const int DISPOSAL_MODE_NOT_SPECIFIED               = 0;
	static const int DISPOSAL_MODE_DO_NOT_DISPOSE              = 1;
	static const int DISPOSAL_MODE_RESTORE_TO_BACKGROUND_COLOR = 2;
	static const int DISPOSAL_MODE_RESTORE_TO_PREVIOUS         = 3; 

	inline int getDisposalMethod() { return this->disposalMethod; }

	/**
	 * ラスタデータを返す。
	 * ラスタデータは getWidth() * getHeight() * sizeof(BYTE) 分の BYTE 配列であり
	 * 各要素のカラーインデックスが格納されている
	 */
	inline BYTE* getRaster() { return this->raster; }


	/**
	 * 指定されたバッファに ARGB 形式で画像情報を転送する
	 */
	void draw(DWORD* dest);

	/**
	 * 指定されたバッファにRGB565形式で画像情報を転送する
	 */
	void drawRGB565(WORD* dest);

};