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

/*****************************
 * apdtool
 *****************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>

#include "mlk.h"
#include "mlk_str.h"
#include "mlk_buf.h"
#include "mlk_list.h"
#include "mlk_charset.h"
#include "mlk_stdio.h"
#include "mlk_imagebuf.h"

#include "def.h"


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

WorkData g_work;

int main_get_options(int argc,char **argv);

mlkerr load_apd_v1(FILE *fp);
mlkerr load_apd_v2(FILE *fp);
mlkerr load_apd_v3(FILE *fp);
mlkerr load_adw_v1(FILE *fp);
mlkerr load_adw_v2(FILE *fp);

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

enum
{
	INPUTFORMAT_APD_v1,
	INPUTFORMAT_APD_v2,
	INPUTFORMAT_APD_v3,
	INPUTFORMAT_ADW_v1,
	INPUTFORMAT_ADW_v2
};

//合成モード名

static const char *g_blendmode_name[BLENDMODE_NUM] = {
	"normal", "multiply", "add", "sub", "screen", "overlay",
	"hard light", "soft light", "dodge", "burn",
	"linear burn", "vivid light", "linear light", "pin light", "darker", "lighten",
	"diff", "lum add", "lum dodge", "lum sub",
	"hue", "saturation", "color", "lum"
};

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


/** ヘッダ判定
 *
 * return: -1=フォーマットエラー、-2=バージョンエラー */

static int _check_header(FILE *fp)
{
	char m[8];
	uint8_t ver;
	int is_adw;

	//ヘッダ文字列

	if(fread(m, 1, 7, fp) != 7)
		return -1;

	if(strncmp(m, "AZPDATA", 7) == 0)
		is_adw = 0;
	else if(strncmp(m, "AZDWDAT", 7) == 0)
		is_adw = 1;
	else
		return -1;

	//バージョン

	if(fread(&ver, 1, 1, fp) != 1)
		return -1;

	if((!is_adw && ver > 2)
		|| (is_adw && ver > 1))
		return -2;

	if(is_adw)
		printf(" ADW ver %d\n", ver + 1);
	else
		printf(" APD ver %d\n", ver + 1);

	return (is_adw)? INPUTFORMAT_ADW_v1 + ver: INPUTFORMAT_APD_v1 + ver;
}

/** ファイル処理 */

static void _run_proc(void)
{
	FILE *fp;
	int fmt;
	mlkerr ret;
	mlkerr (*func[])(FILE *) = {
		load_apd_v1, load_apd_v2, load_apd_v3, load_adw_v1, load_adw_v2
	};

	fp = mFILEopen(g_work.strInputFile.buf, "rb");
	if(!fp)
	{
		puts(" ! open error");
		return;
	}

	//ヘッダ

	fmt = _check_header(fp);
	if(fmt < 0)
	{
		if(fmt == -1)
			puts(" ! format error");
		else
			puts(" ! version error");

		fclose(fp);
		return;
	}

	//読み込み

	ret = (func[fmt])(fp);

	fclose(fp);

	//エラー

	if(ret)
	{
		switch(ret)
		{
			case MLKERR_ALLOC:
				puts(" ! memory not enough");
				break;
			case MLKERR_DAMAGED:
				puts(" ! file is damaged");
				break;
			default:
				puts(" ! error");
				break;
		}
	}

	//リセット

	mBufReset(&g_work.bufName);
	mListDeleteAll(&g_work.listTexture);
}

/** main */

int main(int argc,char **argv)
{
	int i;

	mInitLocale();

	//データ初期化

	mMemset0(&g_work, sizeof(WorkData));

	TextureList_init(&g_work.listTexture);
	TexturePathList_init(&g_work.listTexPath);

	if(!mBufAlloc(&g_work.bufName, 4 * 1024, 2*1024))
		exit_app(1);

	mStrPathSetHome_join(&g_work.strTexPathUser, ".azpainter/texture");

	//オプション処理

	i = main_get_options(argc, argv);

	//レイヤテクスチャ変換のテキスト読み込み

	if(ISNOT_PROC_INFO && (g_work.flags & FLAGS_LAYERTEX_TO_COL))
		TexturePathList_readFile(&g_work.listTexPath, g_work.strLayerTex.buf);

	//各ファイル処理

	for(; i < argc; i++)
	{
		printf("< %s >\n", argv[i]);

		mStrSetText_locale(&g_work.strInputFile, argv[i], -1);
	
		_run_proc();

		if(i != argc - 1)
			putchar('\n');
	}

	//レイヤテクスチャパスのテキストを出力

	if(IS_PROC_INFO && (g_work.flags & FLAGS_LAYERTEX_LIST))
		TexturePathList_output(&g_work.listTexPath, g_work.strLayerTex.buf);

	//終了

	exit_app(0);

	return 0;
}


//=======================
// 共通関数
//=======================


/** アプリ終了
 *
 * ret: 戻り値 */

void exit_app(int ret)
{
	WorkData *p = &g_work;

	mBufFree(&p->bufName);

	mListDeleteAll(&p->listTexture);
	mListDeleteAll(&p->listTexPath);

	mStrFree(&p->strInputFile);
	mStrFree(&p->strOutput);
	mStrFree(&p->strLayerPrefix);
	mStrFree(&p->strLayerTex);
	mStrFree(&p->strTexPathSys);
	mStrFree(&p->strTexPathUser);

	mFree(p->layerno_flags);

	exit(ret);
}

/** エラーを表示して、エラー終了させる
 *
 * 終端には改行が出力される。 */

void puterr_exit(const char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);
	vprintf(fmt, ap);
	putchar('\n');
	va_end(ap);

	exit_app(1);
}

/** 進捗用の文字を出力 */

void put_progress_char(void)
{
	putchar('.');
	fflush(stdout);
}

/** レイヤ情報出力 */

void put_layerinfo(LayerData *buf,int layernum)
{
	LayerData *pl,*pl2;
	int i;

	printf(" == layer ==\n");

	pl = buf;

	for(i = 0; i < layernum; i++, pl++)
	{
		putchar(' ');

		//ツリー

		for(pl2 = pl; pl2->parent != 0xffff; )
		{
			if(pl2->parent >= layernum) break;
		
			pl2 = buf + pl2->parent;

			if(pl2->parent == 0xffff)
				printf(" |-");
			else
				printf(" | ");
		}

		//情報

		if(pl->flags & LAYER_FLAGS_FOLDER)
		{
			//フォルダ
			
			printf("[%d] {", i);
			mPutUTF8_stdout(pl->name);
			printf("}");
		}
		else
		{
			printf("[%d] <", i);
			mPutUTF8_stdout(pl->name);
			printf("> (%s)", g_blendmode_name[pl->blendmode]);
		}

		if(pl->flags & LAYER_FLAGS_HIDE)
			printf(" / hide");

		if(pl->texpath)
			printf(" / tex \"%s\"", pl->texpath);

		putchar('\n');
	}

	fflush(stdout);
}

/** レイヤ情報出力 (レイヤテクスチャパス出力に対応する場合) */

void put_layerinfo_texpath(LayerData *buf,int layernum)
{
	LayerData *pl;

	if(!(g_work.flags & FLAGS_LAYERTEX_LIST))
		//通常
		put_layerinfo(buf, layernum);
	else
	{
		//テクスチャパスをリストに追加
	
		for(pl = buf; layernum; layernum--, pl++)
		{
			if(pl->texpath)
				TexturePathList_add(&g_work.listTexPath, pl->texpath, 0);
		}
	}
}

/** レイヤ数から、連番桁数取得 */

int get_layernum_dig(int num)
{
	if(num <= 10)
		return 1;
	else if(num <= 100)
		return 2;
	else
		return 3;
}

/* UTF-8 チェック
 *
 * return: スコア値。0 でエラー */

static int _check_text_utf8(uint8_t *pc,int maxlen)
{
	uint8_t c;
	int i,len,bytes,score = 0;

	bytes = maxlen * 2;

	while(maxlen)
	{
		c = *(pc++);
		maxlen--;

		if(c < 0x80)
			//ASCII
			score++;
		else if(c <= 0xc1 || c >= 0xfe)
			//この範囲は UTF-8 では先頭バイトに来ない
			return 0;
		else
		{
			//2バイト目以降のバイト数

			if(c < 0xe0) len = 1;
			else if(c < 0xf0) len = 2;
			else if(c < 0xf8) len = 3;
			else if(c < 0xfc) len = 4;
			else len = 5;
			
			//データが足りない

			if(maxlen < len) return 0;

			//2バイト目以降が 0x80-0xBF の範囲内か

			for(i = len; i; i--, pc++)
			{
				if(*pc < 0x80 || *pc > 0xbf)
					return 0;
			}

			score += (1 + len) * 2;
			maxlen -= len;
		}
	}

	return (score << 4) / bytes;
}

/* Shift-JIS のチェック */

static int _check_text_sjis(uint8_t *pc,int maxlen)
{
	uint8_t c,c2;
	int bytes,score = 0;

	bytes = maxlen * 2;

	while(maxlen)
	{
		c = *(pc++);
		maxlen--;

		if(c < 0x80 || (c >= 0xa1 && c <= 0xdf))
			//ASCII/半角カナ
			score++;
		else if((c >= 0x81 && c <= 0x9f) || (c >= 0xe0 && c <= 0xfc))
		{
			//2byte文字

			if(maxlen < 1) return 0;

			c2 = *(pc++);
			maxlen--;

			if(!((c2 >= 0x40 && c2 <= 0x7e) || (c2 >= 0x80 && c2 <= 0xfc)))
				return 0;

			score += 4;
		}
		else
			return 0;
	}

	return (score << 4) / bytes;
}

/** UTF-8 レイヤ名/テクスチャパスをバッファに追加 */

const char *add_name_utf8(const char *name,int len)
{
	return mBufAppendUTF8(&g_work.bufName, name, len);
}

/** レイヤ名の UTF-8/Shift_JIS を自動判別して、バッファに追加
 *
 * len: 負の値でヌル文字まで
 * return: 空文字列なら NULL */

const char *add_name_checkcode(void *buf,int len)
{
	int score1,score2;
	char *strbuf,*retbuf;

	if(len < 0)
		len = strlen((char *)buf);

	if(len == 0) return NULL;

	//チェック

	score1 = _check_text_utf8((uint8_t *)buf, len);
	score2 = _check_text_sjis((uint8_t *)buf, len);

	//セット

	if(score2 <= score1)
		//UTF-8
		retbuf = mBufAppendUTF8(&g_work.bufName, (char *)buf, len);
	else
	{
		//SJIS
		
		strbuf = mConvertCharset((char *)buf, len, "CP932", "UTF-8", &len);

		if(!strbuf)
			retbuf = NULL;
		else
		{
			retbuf = mBufAppendUTF8(&g_work.bufName, strbuf, len);
			mFree(strbuf);
		}
	}

	return retbuf;
}

/* レイヤ抽出時、抽出対象レイヤかどうか確認 */

static mlkbool _check_extract_layer(LayerData *pl,int no)
{
	//レイヤ番号、対象外

	if(g_work.layerno_flags
		&& (no >= g_work.layerno_num
			|| !( g_work.layerno_flags[no >> 3] & (1 << (7 - (no & 7))) ) ) )
		return FALSE;

	return TRUE;
}

/** レイヤ画像出力 */

mlkerr output_layers(mImageBuf2 *img,LayerData *layer,int layernum,
	func_read_layerimage readimg,void *ptr)
{
	LayerData *pl = layer;
	int i,dig;
	mlkerr ret;

	dig = get_layernum_dig(layernum);

	for(i = 0; i < layernum; i++, pl++)
	{
		if(pl->flags & LAYER_FLAGS_FOLDER)
			continue;
	
		if(_check_extract_layer(pl, i))
		{
			ret = (readimg)(pl, ptr);
			if(ret) return ret;

			write_image_layer(img, i, dig);
		}
	}

	return MLKERR_OK;
}

/* 親フォルダの不透明度を適用 */

static int _get_layer_opacity(LayerData *pl,LayerData *layer,int layernum)
{
	int opacity;

	opacity = pl->opacity;

	while(pl->parent != 0xffff)
	{
		if(pl->parent >= layernum) break;
		
		pl = layer + pl->parent;

		if(pl->flags & LAYER_FLAGS_HIDE)
			return 0;
		
		opacity = opacity * pl->opacity >> 7;
	}

	return opacity;
}

/** 合成画像出力 (8bit、レイヤ画像 = mImageBuf2) */

mlkerr output_blendimage8(int width,int height,
	mImageBuf2 *imglayer,LayerData *layer,int layernum,
	func_read_layerimage readimage,void *ptr)
{
	mImageBuf2 *img;
	LayerData *pl;
	int i;
	mlkerr ret;

	//合成イメージ作成

	img = mImageBuf2_new(width, height, 24, -4);
	if(!img) return MLKERR_ALLOC;

	image_fill_white(img);

	//レイヤ読み込み&合成

	putchar(' ');

	pl = layer + layernum - 1;

	for(i = 0; i < layernum; i++, pl--)
	{
		if(pl->flags & (LAYER_FLAGS_FOLDER | LAYER_FLAGS_HIDE))
			continue;
	
		ret = (readimage)(pl, ptr);
		if(ret)
		{
			mImageBuf2_free(img);
			return ret;
		}

		blendimage_8bit(img, imglayer, pl->blendmode, pl->opacity);
		
		put_progress_char();
	}

	putchar('\n');

	//出力

	write_image_blend(img);

	mImageBuf2_free(img);

	return MLKERR_OK;
}

/** 合成画像出力 (タイルごと)
 *
 * imgbits: 24 or 48 */

mlkerr output_blendimage_tile(int width,int height,
	LayerData *layer,int layernum,func_blend_layer blendimage,void *ptr,int imgbits)
{
	mImageBuf2 *img;
	LayerData *pl;
	mlkerr ret;
	int i,opacity;

	//合成イメージ作成

	img = mImageBuf2_new(width, height, imgbits, -4);
	if(!img) return MLKERR_ALLOC;

	if(imgbits == 48)
		image_fill_white16(img);
	else
		image_fill_white(img);

	//後ろのレイヤから順に、タイルごとに合成

	putchar(' ');

	pl = layer + layernum - 1;

	for(i = 0; i < layernum; i++, pl--)
	{
		if(pl->flags & (LAYER_FLAGS_FOLDER | LAYER_FLAGS_HIDE))
			continue;

		opacity = _get_layer_opacity(pl, layer, layernum);
		if(opacity == 0) continue;

		//合成
	
		ret = (blendimage)(img, pl, opacity, ptr);
		if(ret)
		{
			mImageBuf2_free(img);
			return ret;
		}

		put_progress_char();
	}

	putchar('\n');

	//16bit->8bit

	if(imgbits == 48)
		image_convert_16to8(img);

	//出力
	
	write_image_blend(img);

	mImageBuf2_free(img);

	return MLKERR_OK;
}

/** レイヤテクスチャ画像読み込み */

void read_layertexture(LayerData *layer,int layernum)
{
	LayerData *pl;
	mStr str = MSTR_INIT;

	//レイヤ出力時、テクスチャを適用しない

	if(IS_PROC_LAYER && (g_work.flags & FLAGS_LAYER_NO_TEXTURE))
		return;

	//

	for(pl = layer; layernum; layernum--, pl++)
	{
		if(pl->texpath)
		{
			if(pl->texpath[0] == '/')
			{
				mStrCopy(&str, &g_work.strTexPathSys);
				mStrPathJoin(&str, pl->texpath + 1);
			}
			else
			{
				mStrCopy(&str, &g_work.strTexPathUser);
				mStrPathJoin(&str, pl->texpath);
			}

			pl->texitem = TextureList_load(&g_work.listTexture, str.buf);
		}
	}

	mStrFree(&str);
}
