// This is the main DLL file.

#include "stdafx.h"

#include "AGifExport.h"
#include "frmConfig.h"
#include "wand/MagickWand.h"
#include <vector>
using namespace AGifExport;
//using namespace System::Drawing;
//using namespace System::Drawing::Imaging;
/*
using namespace AForge::Imaging;
using namespace AForge::Imaging::Filters;
using namespace AForge::Imaging::ColorReduction;
*/

//---------------------------------------------------------------------
//		出力プラグイン構造体定義
//---------------------------------------------------------------------
OUTPUT_PLUGIN_TABLE output_plugin_table = {
	NULL,												//	フラグ
	"Animated GIF Export",										//	プラグインの名前
	"GifFile (*.gif)\0*.gif\0AllFile (*.*)\0*.*\0",		//	出力ファイルのフィルタ
	"Animated Gif Export 0.01 by Maverick Tse",			//	プラグインの情報
	NULL,												//	DLL開始時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	NULL,												//	DLL終了時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	func_output,										//	出力時に呼ばれる関数へのポインタ
	func_config,										//	出力設定のダイアログを要求された時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	func_config_get,									//	出力設定データを取得する時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
	func_config_set,									//	出力設定データを設定する時に呼ばれる関数へのポインタ (NULLなら呼ばれません)
};

//---------------------------------------------------------------------
//		出力プラグイン構造体のポインタを渡す関数
//---------------------------------------------------------------------
EXTERN_C OUTPUT_PLUGIN_TABLE __declspec(dllexport) * __stdcall GetOutputPluginTable(void)
{
	return &output_plugin_table;
}

//---------------------------------------------------------------------
//		Setting data
//---------------------------------------------------------------------

static CONFIG config = {2, 0, 128, 3, Dither_Method::Ordered}; //defaults

/*---------------------------------------------------------------------
//		Internal functions for export plugin
//---------------------------------------------------------------------
//
//	oip->flag;				// フラグ
//							// OUTPUT_INFO_FLAG_VIDEO	: 画像データあり
//							// OUTPUT_INFO_FLAG_AUDIO	: 音声データあり
//	oip->w,oip->h;			// 縦横サイズ
//	oip->rate,oip->scale;	// フレームレート
//	oip->n;					// フレーム数
//	oip->size;				// １フレームのバイト数
//	oip->audio_rate;		// 音声サンプリングレート
//	oip->audio_ch;			// 音声チャンネル数
//	oip->audio_n;			// 音声サンプリング数
//	oip->audio_size;		// 音声１サンプルのバイト数
//	oip->savefile;			// セーブファイル名へのポインタ
//
//	void *oip->func_get_video( int frame );
//							// DIB形式(RGB24bit)の画像データへのポインタを取得します。
//							// frame	: フレーム番号
//							// 戻り値	: データへのポインタ
//	void *oip->func_get_audio( int start,int length,int *readed );
//							// 16bitPCM形式の音声データへのポインタを取得します。
//							// start	: 開始サンプル番号
//							// length	: 読み込むサンプル数
//							// readed	: 読み込まれたサンプル数
//							// 戻り値	: データへのポインタ
//	BOOL oip->func_is_abort( void );
//							// 中断するか調べます。
//							// 戻り値	: TRUEなら中断
//	BOOL oip->func_rest_time_disp( int now,int total );
//							// 残り時間を表示させます。
//							// now		: 処理しているフレーム番号
//							// total	: 処理する総フレーム数
//							// 戻り値	: TRUEなら成功
//	int oip->func_get_flag( int frame );
//							//	フラグを取得します。
//							//	frame	: フレーム番号
//							//	戻り値	: フラグ
//							//  OUTPUT_INFO_FRAME_FLAG_KEYFRAME		: キーフレーム推奨
//							//  OUTPUT_INFO_FRAME_FLAG_COPYFRAME	: コピーフレーム推奨
//	BOOL oip->func_update_preview( void );
//							//	プレビュー画面を更新します。
//							//	最後にfunc_get_videoで読み込まれたフレームが表示されます。
//							//	戻り値	: TRUEなら成功
//*/

BOOL func_output(OUTPUT_INFO *oip)
{
	//TODO:
	//Determine frame# to process
	std::vector<Int32> frame_to_encode;
	std::vector<Int32> delay;
	int base_delay = round(100 * oip->scale / oip->rate);
	frame_to_encode.push_back(0);
	if (config.fps >1)
	{
		for (int i = 1; i < oip->n-1; ++i)
		{
			if (!(i%config.fps))
			{
				frame_to_encode.push_back(i);
			}
		}
		frame_to_encode.push_back(oip->n); //always add the last frame
		
		for (int i = 0; i < frame_to_encode.size()-1; ++i)
		{
			int fdelay = round((frame_to_encode.at(i + 1) - frame_to_encode.at(i))*base_delay);
			delay.push_back(fdelay);
		}
		delay.push_back(base_delay); //standard delay for last frame
		
	}
	else
	{
		//No drop
		for (int i = 0; i < oip->n; ++i)
		{
			frame_to_encode.push_back(i);
			delay.push_back(base_delay);
		}
	}
	
	
	//Prepare magic wand
	MagickWandGenesis();
	MagickWand *dest = NewMagickWand();
	TCHAR digit[4];
	_itoa_s(config.loop, digit, sizeof(digit), 10);
	//Loop through vector and process each frame
	for (int i = 0; i < frame_to_encode.size(); ++i)
	{
		if (!oip->func_is_abort()){
			oip->func_rest_time_disp(i, frame_to_encode.size()); //progress display
			//
			Bitmap ^buffer = ditherFrame(oip->func_get_video(frame_to_encode.at(i)), oip->w, oip->h, &config);
			Bitmap ^stdimg = AForge::Imaging::Image::Clone(buffer, Imaging::PixelFormat::Format24bppRgb);
			
			//Lock stdimg
			System::Drawing::Rectangle rect = System::Drawing::Rectangle(0, 0, oip->w, oip->h);
			System::Drawing::Imaging::BitmapData ^bmpData = stdimg->LockBits(rect, System::Drawing::Imaging::ImageLockMode::ReadWrite, stdimg->PixelFormat);
			IntPtr rawimg = bmpData->Scan0;

			//
			MagickWand *source = NewMagickWand();
			if (!MagickConstituteImage(source, oip->w, oip->h, "BGR", CharPixel, rawimg.ToPointer()))    //NULLだと警告
				System::Windows::Forms::MessageBox::Show("Failed to pass image from AForge to ImagickMagick.");

			//Set delay
			MagickSetImageDelay(source, delay.at(i));
			
			//Add image
			MagickAddImage(dest, source);
			//Add loop info
			if (i == 0)
			{
				MagickSetImageProperty(dest, "loop", digit);
			}
			//
			DestroyMagickWand(source);
			stdimg->UnlockBits(bmpData);
			
		}
		else
		{
			System::Windows::Forms::MessageBox::Show("Export Aborted!");
			return FALSE;
		}

	}
	//ImagickMagick optimization
	MagickSetFirstIterator(dest);
	MagickQuantizeImages(dest, config.color, ColorspaceType::RGBColorspace, 0, MagickFalse, MagickFalse);

	if (!MagickSetFormat(dest, "GIF"))
		System::Windows::Forms::MessageBox::Show("Failed to setup GIF format");
	MagickSetImageFuzz(dest, config.fuzz); //Fuzziness
	dest = MagickOptimizeImageLayers(MagickCoalesceImages(dest)); //Layer optimization
	MagickOptimizeImageTransparency(dest);
	

	//Save to file
	if (!MagickWriteImages(dest, oip->savefile, MagickTrue))
		System::Windows::Forms::MessageBox::Show("Unable to save to file");

	//Clean-up
	DestroyMagickWand(dest);
	MagickWandTerminus();
	return TRUE;
}

#pragma warning( push )
#pragma warning( disable: 4100 )
BOOL func_config(HWND hwnd, HINSTANCE dll_hinst)
{
	//OK
	ShowfrmConfig(&config);
	return FALSE;
}

int func_config_get(void *data, int size)
{
	//OK
	if (data && size == sizeof(CONFIG))
	{
		memcpy_s(data, sizeof(config), &config, sizeof(config));
	}
	return sizeof(config);
}

int func_config_set(void *data, int size)
{
	//OK
	if (size != sizeof(config)) return NULL;
	memcpy_s(&config, sizeof(config), data, sizeof(config));
	return size;
}

/// -------------------------------------------------
///     設定画面の表示
/// -------------------------------------------------

[STAThreadAttribute]
void ShowfrmConfig(CONFIG *data) {

	frmConfig frmConf(data);
	frmConf.ShowDialog();
}

Bitmap^ ditherFrame(void *dib, int width, int height, CONFIG* settings)
{
	using namespace AForge::Imaging;
	using namespace AForge::Imaging::Filters;
	using namespace AForge::Imaging::ColorReduction;
	{
		//TODO:
		UnmanagedImage ^worksrc = gcnew UnmanagedImage(IntPtr(dib), width, height, width * 3, Imaging::PixelFormat::Format24bppRgb);
		//Flip image
		Mirror ^flipper = gcnew Mirror(true, true);
		flipper->ApplyInPlace(worksrc);

		//Calculate color platte
		ColorImageQuantizer ^ciq = gcnew ColorImageQuantizer(gcnew MedianCutQuantizer());
		array<Color> ^colorTable = ciq->CalculatePalette(worksrc, settings->color);
		//Object ^dithering;
		//Perform dithering
		switch ((int)settings->method){
		case 0:
		{
			//TODO:Burkes
			BurkesColorDithering ^dithering = gcnew BurkesColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
			break;
		case 1:
		{
			//TODO:Floyd
			FloydSteinbergColorDithering ^dithering = gcnew FloydSteinbergColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
			break;
		case 2:
		{
			//TODO:JJN
			JarvisJudiceNinkeColorDithering ^dithering = gcnew JarvisJudiceNinkeColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
			break;
		case 3:
		{
			//TODO: Ordered
			OrderedColorDithering ^dithering = gcnew OrderedColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
			break;
		case 4:
		{
			//TODO: Sierra3
			SierraColorDithering ^dithering = gcnew SierraColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
			break;
		case 5:
		{
			//TODO:Stucki
			StuckiColorDithering ^dithering = gcnew StuckiColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
			break;
		default:
		{
			//Same as 2
			JarvisJudiceNinkeColorDithering ^dithering = gcnew JarvisJudiceNinkeColorDithering();
			dithering->ColorTable = colorTable;
			return dithering->Apply(worksrc);
		}
		}
	}

}