/*$
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.
$*/

/*****************************************
 * PackBits エンコード/デコード
 *****************************************/

#include <string.h>

#include "mlk.h"
#include "mlk_packbits.h"


//=========================
// decode
//=========================
/*
  insize : workbuf 内にある未読込のサイズ
  inpos  : workbuf 先頭からの現在の読み込み位置
*/


/**@ PackBits デコード
 *
 * @d:常に encsize のサイズ分を処理する。\
 * デコード後は、encsize にデコードされたサイズが入る。\
 * デコードサイズが bufsize に達した場合、
 * 残っている入力データはスキップされる。
 * @r:エラーコード */

mlkerr mPackBits_decode(mPackBits *p)
{
	uint8_t *inbuf,*outbuf;
	uint32_t dst_remain,src_remain,insize,inpos,size;
	int ret,len;

	insize = inpos = 0;

	inbuf = p->workbuf;
	outbuf = p->buf;
	dst_remain = p->bufsize;
	src_remain = p->encsize;

	while(src_remain || insize)
	{
		//出力が最大サイズに達した場合、
		//残りの入力をスキップ

		if(dst_remain == 0)
		{
			insize = 0;

			if(src_remain)
			{
				size = p->worksize;
				if(size > src_remain) size = src_remain;

				ret = (p->readwrite)(p, inbuf, size);
				if(ret) return ret;

				src_remain -= size;
			}
			
			continue;
		}
	
		//読み込み
		//(末尾を除き、最小 129 byte)

		if(src_remain && insize < 129)
		{
			//未処理データを先頭に移動

			if(insize)
				memcpy(inbuf, inbuf + inpos, insize);

			inpos = 0;

			//読み込み

			size = p->worksize - insize;
			if(size > src_remain) size = src_remain;

			ret = (p->readwrite)(p, inbuf + insize, size);
			if(ret) return ret;

			insize += size;
			src_remain -= size;
		}

		//長さ (1byte)

		len = inbuf[inpos];
		inpos++;
		insize--;

		//

		if(len >= 128)
		{
			//連続データ

			len = 257 - len;

			if(len > dst_remain) return MLKERR_OVER_DATA;
			if(insize == 0) return MLKERR_NEED_MORE;

			memset(outbuf, inbuf[inpos], len);

			inpos++;
			insize--;
		}
		else
		{
			//非連続データ

			len++;

			if(len > dst_remain) return MLKERR_OVER_DATA;
			if(len > insize) return MLKERR_NEED_MORE;

			memcpy(outbuf, inbuf + inpos, len);

			inpos += len;
			insize -= len;
		}

		outbuf += len;
		dst_remain -= len;
	}

	p->encsize = p->bufsize - dst_remain;

	return MLKERR_OK;
}


//=========================
// encode
//=========================


/* 出力 */

static int _put_encode(mPackBits *p,uint8_t *buf,int size,uint32_t *poutpos)
{
	uint32_t outpos;
	int ret;

	outpos = *poutpos;

	//出力バッファを超える場合、書き込み

	if(outpos + size > p->worksize)
	{
		ret = (p->readwrite)(p, p->workbuf, outpos);
		if(ret) return ret;

		outpos = 0;
	}

	//コピー

	memcpy(p->workbuf + outpos, buf, size);

	//

	*poutpos = outpos + size;

	return MLKERR_OK;
}

/**@ PackBits エンコード
 *
 * @r:エラーコード */

mlkerr mPackBits_encode(mPackBits *p)
{
	uint8_t *ps,*psend,*pp,buf2[2],c;
	uint32_t outpos,encsize;
	int ret,len;

	outpos = encsize = 0;
	
	ps = p->buf;
	psend = p->buf + p->bufsize;

	while(ps < psend)
	{
		if(ps != psend - 1 && *ps == ps[1])
		{
			//---- 2個以上連続

			c = buf2[1] = *ps;

			//連続数 (2〜128)

			for(pp = ps + 2; pp < psend && pp - ps < 128 && *pp == c; pp++);

			len = pp - ps;
			
			buf2[0] = (uint8_t)(-(len - 1));

			ret = _put_encode(p, buf2, 2, &outpos);
			if(ret) return ret;

			encsize += 2;
		}
		else
		{
			//---- 非連続

			//2個連続する位置で終了 (1〜128)
	
			for(pp = ps + 1; pp < psend && pp - ps < 128; pp++)
			{
				if(pp[0] == pp[-1])
				{
					pp--;
					break;
				}
			}

			len = pp - ps;

			//長さ

			buf2[0] = len - 1;

			ret = _put_encode(p, buf2, 1, &outpos);
			if(ret) return ret;

			//データ

			ret = _put_encode(p, ps, len, &outpos);
			if(ret) return ret;

			encsize += 1 + len;
		}

		ps = pp;
	}

	//残りを出力

	if(outpos)
	{
		ret = (p->readwrite)(p, p->workbuf, outpos);
		if(ret) return ret;
	}

	p->encsize = encsize;

	return MLKERR_OK;
}
