/*$
mlk in apdtool
Copyright (c) 2020 Azel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
$*/

/*****************************************
 * mGIFDec
 *****************************************/

#include <string.h>

#include "mlk.h"
#include "mlk_gifdec.h"
#include "mlk_io.h"
#include "mlk_util.h"


//--------------------

struct _mGIFDec
{
	mIO *io;

	uint8_t *palbuf[2],	//パレット [R-G-B-X] (0=global, 1=local)
		*imgbuf,		//最終的な画像のバッファ (8bit)
		*currow;

	int width,			//全体の幅
		height,			//全体の高さ
		bits,			//画像ビット数
		palnum[2],		//各パレット個数
		transparent,	//透過色インデックス (-1 でなし)
		remain_height;	//読み込みの残り行数

	uint8_t bkgnd,			//背景色インデックス
		palette_no,			//現在のイメージのパレット (0:global, 1:local)
		img_destroy_type;	//画像の破棄方法

	/*--- lzw ---*/

	uint8_t *lzwbuf, /* LZW 用バッファ まとめて確保 [block(255) + stack(4096) + suffix(4096) + prefix(4096x2)] */
		*lzw_curbuf;

	int offx,offy,		//画像のオフセット位置
		imgw,imgh,		//読み込み画像サイズ
		curx,cury,		//現在の出力座標
		lzw_leftbits,
		lzw_curbits,
		lzw_blockremain;
	uint8_t lzw_readbyte,
		interlace_pass;	//非インターレースなら0、インターレスなら1にしておく
};

//-------------------

//パレット番号
#define _PALETTENO_GLOBAL  0
#define _PALETTENO_LOCAL   1

//画像の破棄方法
enum
{
	_IMG_DESTROY_TYPE_NONE,
	_IMG_DESTROY_TYPE_STAY,
	_IMG_DESTROY_TYPE_FILL_BKGND,
	_IMG_DESTROY_TYPE_RESTORE
};

//--------------------

static int _decode_lzw(mGIFDec *p);

//--------------------



//===========================
// sub
//===========================


/** パレット読み込み */

static int _read_palette(mGIFDec *p,uint8_t flags,int palno)
{
	int num;
	uint8_t *ps,*pd,r,g,b;

	pd = p->palbuf[palno];

	//個数

	num = 1 << ((flags & 7) + 1);

	p->palnum[palno] = num;

	//読み込み

	if(mIO_readOK(p->io, pd, num * 3))
		return MLKERR_DAMAGED;

	//変換 (RGB => RGBX)

	ps = pd + (num - 1) * 3;
	pd += (num - 1) << 2;

	for(; num > 0; num--, ps -= 3, pd -= 4)
	{
		r = ps[0], g = ps[1], b = ps[2];
		pd[0] = r, pd[1] = g, pd[2] = b, pd[3] = 255;
	}

	return MLKERR_OK;
}

//===========================
// 各ブロック処理
//===========================


/** 画像の情報を読み込み */

static int _read_imageinfo(mGIFDec *p)
{
	uint8_t d[9],flags;
	int ret;

	if(mIO_readOK(p->io, d, 9))
		return MLKERR_DAMAGED;

	flags = d[8];

	//ローカルパレット

	if(!(flags & 0x80))
		p->palette_no = _PALETTENO_GLOBAL;
	else
	{
		ret = _read_palette(p, flags, _PALETTENO_LOCAL);
		if(ret) return ret;

		p->palette_no = _PALETTENO_LOCAL;
	}

	//背景塗りつぶし

	if(p->img_destroy_type == _IMG_DESTROY_TYPE_FILL_BKGND)
	{
		memset(p->imgbuf,
			(p->transparent != -1)? p->transparent: p->bkgnd,
			p->width * p->height);
	}

	//画像情報

	p->offx = mGetBufLE16(d);
	p->offy = mGetBufLE16(d + 2);
	p->imgw = mGetBufLE16(d + 4);
	p->imgh = mGetBufLE16(d + 6);
	p->interlace_pass = ((flags & 0x40) != 0);

	return MLKERR_OK;
}

/** グラフィック制御ブロックを読み込み */

static int _read_graphic_ctrl(mGIFDec *p)
{
	uint8_t d[5];

	if(mIO_readOK(p->io, d, 5) || d[0] < 4)
		return MLKERR_DAMAGED;

	//画像の破棄方法

	p->img_destroy_type = (d[1] >> 2) & 7;

	//透過色

	if(d[1] & 1)
		p->transparent = d[4];

	//余りをスキップ

	if(mIO_readSkip(p->io, d[0] - 4))
		return MLKERR_DAMAGED;

	return MLKERR_OK;
}

/** サブブロックをスキップ */

static int _skip_subblock(mGIFDec *p)
{
	uint8_t size;

	while(1)
	{
		if(mIO_readByte(p->io, &size))
			return MLKERR_DAMAGED;

		if(size == 0) break;

		if(mIO_readSkip(p->io, size))
			return MLKERR_DAMAGED;
	}

	return MLKERR_OK;
}

/** 次の画像ブロックへ移動し、画像情報を読み込み
 *
 * return: エラーコード */

static int _move_nextimage(mGIFDec *p)
{
	uint8_t sig,subsig;
	int ret;

	while(mIO_readByte(p->io, &sig) == 0)
	{
		switch(sig)
		{
			//画像ブロック
			case 0x2c:
				return _read_imageinfo(p);

			//終端
			case 0x3b:
				return -1;

			//サブブロック付きのブロック
			case 0x21:
				//ブロック識別子
				
				if(mIO_readByte(p->io, &subsig))
					return MLKERR_DAMAGED;

				//グラフィック制御ブロック

				if(subsig == 0xf9)
				{
					ret = _read_graphic_ctrl(p);
					if(ret) return ret;
				}

				//次のブロックまでスキップ
				
				ret = _skip_subblock(p);
				if(ret) return ret;
				break;

			//ほか
			default:
				return MLKERR_UNSUPPORTED;
		}
	}

	return -1;
}


//===========================
// main
//===========================


/**@ 閉じる */

void mGIFDec_close(mGIFDec *p)
{
	if(p)
	{
		mIO_close(p->io);

		mFree(p->lzwbuf);
		mFree(p->imgbuf);
		mFree(p->palbuf[0]);
		mFree(p->palbuf[1]);
		
		mFree(p);
	}
}

/**@ GIF デコーダ作成
 *
 * @r:NULL で失敗  */

mGIFDec *mGIFDec_new(void)
{
	mGIFDec *p;

	//確保

	p = (mGIFDec *)mMalloc0(sizeof(mGIFDec));
	if(!p) return NULL;

	//パレットバッファ確保

	p->palbuf[0] = (uint8_t *)mMalloc0(256 * 4);
	p->palbuf[1] = (uint8_t *)mMalloc0(256 * 4);

	if(!p->palbuf[0] || !p->palbuf[1])
	{
		mGIFDec_close(p);
		return NULL;
	}

	//LZW バッファ確保

	p->lzwbuf = (uint8_t *)mMalloc(255 + 4096 * 2 + 4096 * 2);

	if(!p->lzwbuf)
	{
		mGIFDec_close(p);
		return NULL;
	}

	return p;
}

/**@ ファイルを開く */

mlkbool mGIFDec_openFile(mGIFDec *p,const char *filename)
{
	p->io = mIO_openReadFile(filename);
	return (p->io != NULL);
}

/**@ バッファを開く */

mlkbool mGIFDec_openBuf(mGIFDec *p,const void *buf,mlksize bufsize)
{
	p->io = mIO_openReadBuf(buf, bufsize);
	return (p->io != NULL);
}

/**@ FILE * から開く */

mlkbool mGIFDec_openFILEptr(mGIFDec *p,void *fp)
{
	p->io = mIO_openReadFILEptr(fp);
	return (p->io != NULL);
}

/**@ ヘッダを読み込み */

mlkerr mGIFDec_readHeader(mGIFDec *p)
{
	uint8_t d[13],flags;
	int ret;

	if(mIO_readOK(p->io, d, 13))
		return MLKERR_DAMAGED;

	//ヘッダ ("GIF87a" or "GIF89a")

	if(d[0] != 'G' || d[1] != 'I' || d[2] != 'F'
		|| d[3] != '8'
		|| (d[4] != '7' && d[4] != '9')
		|| d[5] != 'a')
		return MLKERR_FORMAT_HEADER;

	//幅・高さ

	p->width  = mGetBufLE16(d + 6);
	p->height = mGetBufLE16(d + 8);

	//フラグ

	flags = d[10];

	//ビット数

	p->bits = ((flags >> 4) & 7) + 1;

	//背景色

	p->bkgnd = d[11];

	//グローバルパレット

	if(flags & 0x80)
	{
		ret = _read_palette(p, flags, _PALETTENO_GLOBAL);
		if(ret) return ret;
	}

	//画像バッファ確保

	p->imgbuf = (uint8_t *)mMalloc(p->width * p->height);
	if(!p->imgbuf) return MLKERR_ALLOC;

	return MLKERR_OK;
}

/**@ 画像の全体サイズを取得
 *
 * @d:ヘッダ読み込み後に行う。 */

void mGIFDec_getImageSize(mGIFDec *p,mSize *size)
{
	size->w = p->width;
	size->h = p->height;
}

/**@ パレットデータを取得
 *
 * @d:イメージ位置移動後に実行する。
 *
 * @p:pnum  パレットの個数が格納される
 * @r:パレットバッファのポインタ (R-G-B-X 順)。\
 * 内部で確保されているポインタが返るため、解放しないこと。\
 * 256 個分のスペースが確保されている。 */

uint8_t *mGIFDec_getPalette(mGIFDec *p,int *pnum)
{
	*pnum = p->palnum[p->palette_no];

	return p->palbuf[p->palette_no];
}

/**@ 現在の画像の透過色をパレットインデックスで取得
 *
 * @d:イメージ位置移動後に実行する。
 * @r:パレットインデックス値。-1 で透過色なし。 */

int mGIFDec_getTransIndex(mGIFDec *p)
{
	if(p->transparent != -1 && p->transparent < p->palnum[p->palette_no])
		return p->transparent;
	else
		return -1;
}

/**@ 現在の画像の透過色を RGB 値で取得
 *
 * @d:イメージ位置移動後に実行する。
 * @r:RGB値。-1 で透過色なし。 */

int32_t mGIFDec_getTransRGB(mGIFDec *p)
{
	uint8_t *ppal;

	if(p->transparent != -1 && p->transparent < p->palnum[p->palette_no])
	{
		ppal = p->palbuf[p->palette_no] + (p->transparent << 2);

		return (ppal[0] << 16) | (ppal[1] << 8) | ppal[2];
	}

	return -1;
}

/**@ 次のイメージの位置へ移動
 *
 * @d:画像の情報も読み込むが、画像自体は読み込まない。
 * @r:エラーコード。-1 でイメージがない。 */

mlkerr mGIFDec_moveNextImage(mGIFDec *p)
{
	p->transparent = -1;
	p->img_destroy_type = _IMG_DESTROY_TYPE_FILL_BKGND;

	//次のイメージへ移動

	return _move_nextimage(p);
}

/**@ イメージを読み込み
 *
 * @d:mGIFDec_moveNextImage() の後に実行する。 */

mlkerr mGIFDec_getImage(mGIFDec *p)
{
	int ret;

	//LZW 展開

	ret = _decode_lzw(p);
	if(ret) return ret;

	//ライン取得準備

	p->currow = p->imgbuf;
	p->remain_height = p->height;

	return MLKERR_OK;
}

/**@ Y1行のイメージを指定フォーマットで取得
 *
 * @d:すでに読み込んでいるイメージから、Y1行のイメージデータを指定形式で取得。\
 * Y は 0 〜 height - 1 まで順番に読み込むことになる。
 * 
 * @p:format 取得するフォーマット
 * @p:trans_to_a0  フォーマットが RGBA で TRUE の場合、透過色を A=0 にする
 * @r:FALSE で、すでにすべて読み込んだ状態 */

mlkbool mGIFDec_getNextLine(mGIFDec *p,void *buf,int format,mlkbool trans_to_a0)
{
	uint8_t *ps,*pd,*pal,*palbuf;
	int i,c,tpcol;

	if(p->remain_height <= 0) return FALSE;

	//

	if(format == MGIFDEC_FORMAT_RAW)
		//生データ
		memcpy(buf, p->currow, p->width);
	else
	{
		//変換

		ps = p->currow;
		pd = (uint8_t *)buf;

		palbuf = p->palbuf[p->palette_no];
		tpcol = p->transparent;

		for(i = p->width; i > 0; i--)
		{
			c = *(ps++);
		
			pal = palbuf + (c << 2);

			pd[0] = pal[0];
			pd[1] = pal[1];
			pd[2] = pal[2];
			pd += 3;

			//A

			if(format == MGIFDEC_FORMAT_RGBA)
				*(pd++) = (trans_to_a0 && c == tpcol)? 0: 255;
		}
	}

	p->currow += p->width;
	p->remain_height--;

	return TRUE;
}


//========================
// LZW 展開
//========================


/** 次のバイトを取得 */

static mlkbool _lzw_nextbyte(mGIFDec *p)
{
	uint8_t size;

	//次のブロックを読み込み

	if(p->lzw_blockremain <= 0)
	{
		if(mIO_readByte(p->io, &size) || size == 0)
			return FALSE;

		if(mIO_readOK(p->io, p->lzwbuf, size))
			return FALSE;

		p->lzw_blockremain = size;
		p->lzw_curbuf = p->lzwbuf;
	}

	//次のバイト

	p->lzw_readbyte = *(p->lzw_curbuf++);

	p->lzw_blockremain--;

	return TRUE;
}

/** 次のコードを取得
 *
 * return: -1 でデータが足りない */

static int _lzw_nextcode(mGIFDec *p)
{
	int ret;

	if(p->lzw_leftbits == 0)
	{
		if(!_lzw_nextbyte(p)) return -1;

		p->lzw_leftbits = 8;
	}

	ret = p->lzw_readbyte >> (8 - p->lzw_leftbits);

	//値が次のバイトをまたぐ場合

	while(p->lzw_curbits > p->lzw_leftbits)
	{
		if(!_lzw_nextbyte(p)) return -1;

		ret |= p->lzw_readbyte << p->lzw_leftbits;

		p->lzw_leftbits += 8;
	}

	p->lzw_leftbits -= p->lzw_curbits;

	return ret & ((1 << p->lzw_curbits) - 1);
}

/** 点をセット
 *
 * return: 1 ですべての点を埋めた */

static int _lzw_setpixel(mGIFDec *p,int c)
{
	int x,y;
	uint8_t pat[5][2] = {{1,0},{8,0},{8,4},{4,2},{2,1}};

	//色を置く

	x = p->offx + p->curx;
	y = p->offy + p->cury;

	if(x >= 0 && x < p->width && y >= 0 && y < p->height)
		*(p->imgbuf + y * p->width + x) = c;

	//次の位置

	p->curx++;

	if(p->curx >= p->imgw)
	{
		p->curx = 0;
		p->cury += pat[p->interlace_pass][0];

		while(p->cury >= p->imgh && p->interlace_pass >= 1 && p->interlace_pass <= 3)
		{
			p->interlace_pass++;
			p->cury = pat[p->interlace_pass][1];
		}

		if(p->cury >= p->imgh) return 1;
	}

	return 0;
}

/** 展開メイン処理 */

static int _lzw_main(mGIFDec *p,int minbits)
{
	uint8_t *sp,*stack,*suffix;
	uint16_t *prefix;
	int c,oc,fc,code;
	int clearcode,endcode,newcode,topslot,slot;

	stack  = p->lzwbuf + 255;
	suffix = p->lzwbuf + 255 + 4096;
	prefix = (uint16_t *)(p->lzwbuf + 255 + 4096 * 2);

	//

	topslot = 1 << p->lzw_curbits;
	clearcode = 1 << minbits;
	endcode = clearcode + 1;
	slot = newcode = endcode + 1;

	//

	sp = stack;
	oc = fc = 0;

	while(1)
	{
		c = _lzw_nextcode(p);
		if(c == -1) return MLKERR_DAMAGED;

		if(c == endcode)
			//終了コード
			break;
		else if(c == clearcode)
		{
			//クリアコード

			p->lzw_curbits = minbits + 1;
			slot = newcode;
			topslot = 1 << p->lzw_curbits;

			while((c = _lzw_nextcode(p)) == clearcode);

			if(c == -1) return MLKERR_DAMAGED;
			if(c == endcode) break;

			if(c >= slot) c = 0;

			oc = fc = c;

			if(_lzw_setpixel(p, c)) break;
		}
		else
		{
			code = c;

			if(code >= slot)
			{
				code = oc;
				*(sp++) = fc;
			}

			while(code >= newcode)
			{
				*(sp++) = suffix[code];
				code = prefix[code];
			}

			*(sp++) = code;

			if(slot < topslot)
			{
				suffix[slot] = fc = code;
				prefix[slot++] = oc;
				oc = c;
			}

			if(slot >= topslot)
			{
				if(p->lzw_curbits < 12)
				{
					topslot <<= 1;
					p->lzw_curbits++;
				}
			}

			while(sp > stack)
			{
				if(_lzw_setpixel(p, *(--sp)))
					return MLKERR_OK;
			}
		}
	}

	return MLKERR_OK;
}

/** LZW 展開開始 */

int _decode_lzw(mGIFDec *p)
{
	int ret;
	uint8_t minbits;

	//LZW 最小ビット長

	if(mIO_readByte(p->io, &minbits)
		|| minbits < 2 || minbits > 9)
		return MLKERR_DAMAGED;

	//処理

	p->lzw_leftbits = 0;
	p->lzw_blockremain = 0;
	p->lzw_curbits = minbits + 1;

	ret = _lzw_main(p, minbits);

	//成功時、ブロック終了のバイトをスキップ

	if(ret == MLKERR_OK)
	{
		if(mIO_readSkip(p->io, 1))
			ret = MLKERR_DAMAGED;
	}

	return ret;
}
