/* Copyright (C) 2015 Fredrik Wikstrom. All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
**
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
**
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS `AS IS'
** AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
** LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
** CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
** SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
** INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
** CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
** ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
** POSSIBILITY OF SUCH DAMAGE.
*/

#include <classes/window.h>
#include <gadgets/layout.h>
#include <images/label.h>
#include <gadgets/getfile.h>
#include <gadgets/chooser.h>
#include <datatypes/pictureclass.h>
#include <datatypes/soundclass.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
#include <proto/datatypes.h>
#include "DTConvert_rev.h"

static const char USED verstag[] = VERSTAG;

enum {
	OID_WINDOW,
	OID_INFILE,
	OID_OUTFILE,
	OID_FILETYPE,
	OID_DATATYPE,
	OID_CONVERT,
	OID_MAX
};

struct DTConvert {
	Object *Obj[OID_MAX];
};

static const CONST_STRPTR FileType_Array[] = {
	"picture",
	"sound",
	NULL
};

static const uint32 GroupID_Array[] = {
	GID_PICTURE,
	GID_SOUND
};

static const CONST_STRPTR PictureDT_Array[] = {
	"bmp",
	"gif",
	"ilbm",
	"jpeg",
	"png",
	"tiff",
	NULL
};

static const CONST_STRPTR SoundDT_Array[] = {
	"8svx",
	"aiff",
	"wav",
	NULL
};

static const CONST_STRPTR *const DT_Array[] = {
	PictureDT_Array,
	SoundDT_Array
};

static int32 DTConvert (struct DTConvert *dtc);
static int32 ConvertPicture (Object *src_dt, Object *dst_dt, CONST_STRPTR outfile);
static int32 ConvertSound (Object *src_dt, Object *dst_dt, CONST_STRPTR outfile);

int main (void) {
	int rc = RETURN_FAIL;

	struct ClassLibrary *WindowBase    = NULL;
	struct ClassLibrary *LayoutBase    = NULL;
	struct ClassLibrary *LabelBase     = NULL;
	struct ClassLibrary *GetFileBase   = NULL;
	struct ClassLibrary *ChooserBase   = NULL;
	struct ClassLibrary *ButtonBase    = NULL;
	struct ClassLibrary *RequesterBase = NULL;

	Class *WindowClass;
	Class *LayoutClass;
	Class *LabelClass;
	Class *GetFileClass;
	Class *ChooserClass;
	Class *ButtonClass;
	Class *RequesterClass;

	if ((WindowBase = IIntuition->OpenClass("window.class", 53, &WindowClass)) &&
		(LayoutBase = IIntuition->OpenClass("gadgets/layout.gadget", 53, &LayoutClass)) &&
		(LabelBase = IIntuition->OpenClass("images/label.image", 53, &LabelClass)) &&
		(GetFileBase = IIntuition->OpenClass("gadgets/getfile.gadget", 53, &GetFileClass)) &&
		(ChooserBase = IIntuition->OpenClass("gadgets/chooser.gadget", 53, &ChooserClass)) &&
		(ButtonBase = IIntuition->OpenClass("gadgets/button.gadget", 53, &ButtonClass)) &&
		(RequesterBase = IIntuition->OpenClass("requester.class", 53, &RequesterClass)))
	{
		struct DTConvert DTCStruct;
		struct DTConvert *dtc = &DTCStruct;

		dtc->Obj[OID_WINDOW] = IIntuition->NewObject(WindowClass, NULL,
			WA_Title, VERS,
			WA_Flags, WFLG_CLOSEGADGET|WFLG_DRAGBAR|WFLG_DEPTHGADGET|WFLG_NOCAREREFRESH,
			WA_IDCMP, IDCMP_CLOSEWINDOW|IDCMP_GADGETUP|IDCMP_MENUPICK,
			WINDOW_Position, WPOS_CENTERSCREEN,
			WINDOW_Layout, IIntuition->NewObject(LayoutClass, NULL,
				LAYOUT_Orientation, LAYOUT_ORIENT_VERT,
				LAYOUT_AddChild, dtc->Obj[OID_INFILE] = IIntuition->NewObject(GetFileClass, NULL,
					GA_ID, OID_INFILE,
					GA_RelVerify, TRUE,
					TAG_END),
				CHILD_Label, IIntuition->NewObject(LabelClass, NULL,
					LABEL_Text, "Source",
					TAG_END),
				LAYOUT_AddChild, dtc->Obj[OID_OUTFILE] = IIntuition->NewObject(GetFileClass, NULL,
					GA_ID, OID_OUTFILE,
					GA_RelVerify, TRUE,
					TAG_END),
				CHILD_Label, IIntuition->NewObject(LabelClass, NULL,
					LABEL_Text, "Destination",
					TAG_END),
				LAYOUT_AddChild, dtc->Obj[OID_FILETYPE] = IIntuition->NewObject(ChooserClass, NULL,
					GA_ID, OID_FILETYPE,
					GA_RelVerify, TRUE,
					CHOOSER_LabelArray, FileType_Array,
					CHOOSER_Selected, 0,
					TAG_END),
				CHILD_Label, IIntuition->NewObject(LabelClass, NULL,
					LABEL_Text, "Type",
					TAG_END),
				LAYOUT_AddChild, dtc->Obj[OID_DATATYPE] = IIntuition->NewObject(ChooserClass, NULL,
					GA_ID, OID_DATATYPE,
					GA_RelVerify, TRUE,
					CHOOSER_LabelArray, DT_Array[0],
					CHOOSER_Selected, 0,
					TAG_END),
				CHILD_Label, IIntuition->NewObject(LabelClass, NULL,
					LABEL_Text, "Format",
					TAG_END),
				LAYOUT_AddChild, dtc->Obj[OID_CONVERT] = IIntuition->NewObject(ButtonClass, NULL,
					GA_ID, OID_CONVERT,
					GA_RelVerify, TRUE,
					GA_Text, "Convert",
					TAG_END),
				TAG_END),
			TAG_END);

		if (dtc->Obj[OID_WINDOW] != NULL &&
			IIntuition->IDoMethod(dtc->Obj[OID_WINDOW], WM_OPEN, NULL))
		{
			uint32 sigmask;
			uint32 res;
			uint16 code;
			BOOL done = FALSE;

			while (!done) {
				IIntuition->GetAttr(WINDOW_SigMask, dtc->Obj[OID_WINDOW], &sigmask);
				IExec->Wait(sigmask|SIGBREAKF_CTRL_C);
				if (sigmask & SIGBREAKF_CTRL_C) {
					done = TRUE;
				}
				while ((res = IIntuition->IDoMethod(dtc->Obj[OID_WINDOW], WM_HANDLEINPUT, &code)) != WMHI_LASTMSG) {
					switch (res & WMHI_CLASSMASK) {
					case WMHI_CLOSEWINDOW:
						done = TRUE;
						break;
					case WMHI_ICONIFY:
						IIntuition->IDoMethod(dtc->Obj[OID_WINDOW], WM_ICONIFY, NULL);
						break;
					case WMHI_UNICONIFY:
						IIntuition->IDoMethod(dtc->Obj[OID_WINDOW], WM_OPEN, NULL);
						break;
					case WMHI_GADGETUP:
						switch (res & WMHI_GADGETMASK) {
						case OID_INFILE:
						{
							struct Window *window = NULL;
							IIntuition->GetAttr(WINDOW_Window, dtc->Obj[OID_WINDOW], (APTR)&window);
							IIntuition->IDoMethod(dtc->Obj[OID_INFILE], GFILE_REQUEST, window);
							break;
						}
						case OID_OUTFILE:
						{
							struct Window *window = NULL;
							IIntuition->GetAttr(WINDOW_Window, dtc->Obj[OID_WINDOW], (APTR)&window);
							IIntuition->IDoMethod(dtc->Obj[OID_OUTFILE], GFILE_REQUEST, window);
							break;
						}
						case OID_FILETYPE:
						{
							struct Window *window = NULL;
							IIntuition->GetAttr(WINDOW_Window, dtc->Obj[OID_WINDOW], (APTR)&window);
							IIntuition->SetGadgetAttrs((struct Gadget *)dtc->Obj[OID_DATATYPE], window, NULL,
								CHOOSER_LabelArray, DT_Array[code],
								TAG_END);
							break;
						}
						case OID_CONVERT:
						{
							int32 error = DTConvert(dtc);
							IDOS->PrintFault(error, "DTConvert");
							break;
						}
						}
						break;
					}
				}
			}

			IIntuition->DisposeObject(dtc->Obj[OID_WINDOW]);
		}
	}

	IIntuition->CloseClass(WindowBase);
	IIntuition->CloseClass(LayoutBase);
	IIntuition->CloseClass(LabelBase);
	IIntuition->CloseClass(GetFileBase);
	IIntuition->CloseClass(ChooserBase);
	IIntuition->CloseClass(ButtonBase);
	IIntuition->CloseClass(RequesterBase);

	return rc;
}

static int32 DTConvert (struct DTConvert *dtc) {
	int32 error;
	Object *src_dt = NULL;
	Object *dst_dt = NULL;

	IIntuition->SetAttrs(dtc->Obj[OID_WINDOW],
		WA_BusyPointer, TRUE,
		TAG_END);

	CONST_STRPTR infile = NULL, outfile = NULL;
	int32 filetype_idx = -1, datatype_idx = -1;
	IIntuition->GetAttr(GETFILE_FullFile, dtc->Obj[OID_INFILE], (APTR)&infile);
	IIntuition->GetAttr(GETFILE_FullFile, dtc->Obj[OID_OUTFILE], (APTR)&outfile);
	IIntuition->GetAttr(CHOOSER_Selected, dtc->Obj[OID_FILETYPE], (APTR)&filetype_idx);
	IIntuition->GetAttr(CHOOSER_Selected, dtc->Obj[OID_DATATYPE], (APTR)&datatype_idx);
	if (infile == NULL || outfile == NULL) {
		error = ERROR_REQUIRED_ARG_MISSING;
		goto fail;
	}

	const uint32 group_id = GroupID_Array[filetype_idx];
	const CONST_STRPTR base_name = DT_Array[filetype_idx][datatype_idx];
	src_dt = IDataTypes->NewDTObject(infile,
		DTA_GroupID, group_id,
		PDTA_DestMode, PMODE_V43,
		TAG_END);
	if (src_dt == NULL) {
		error = IDOS->IoErr();
		goto fail;
	}

	dst_dt = IDataTypes->NewDTObject(NULL,
		DTA_SourceType, DTST_RAM,
		DTA_BaseName, base_name,
		PDTA_DestMode, PMODE_V43,
		TAG_END);
	if (dst_dt == NULL) {
		error = IDOS->IoErr();
		goto fail;
	}

	switch (group_id) {
	case GID_PICTURE:
		error = ConvertPicture(src_dt, dst_dt, outfile);
		break;
	case GID_SOUND:
		error = ConvertSound(src_dt, dst_dt, outfile);
		break;
	default:
		error = ERROR_NOT_IMPLEMENTED;
		break;
	}

fail:
	if (dst_dt != NULL)
		IDataTypes->DisposeDTObject(dst_dt);
	if (src_dt != NULL)
		IDataTypes->DisposeDTObject(src_dt);

	IIntuition->SetAttrs(dtc->Obj[OID_WINDOW],
		WA_BusyPointer, FALSE,
		TAG_END);

	return error;
}

static int32 ConvertPicture (Object *src_dt, Object *dst_dt, CONST_STRPTR outfile) {
	int32 error = ERROR_REQUIRED_ARG_MISSING;
	struct BitMapHeader *sbmh   = NULL;
	uint32               ncols  = 0;
	uint32              *scregs = NULL;
	uint8               *scm    = NULL;
	struct BitMapHeader *dbmh   = NULL;
	uint32              *dcregs = NULL;
	uint8               *dcm    = NULL;

	if (IDataTypes->GetDTAttrs(src_dt,
			PDTA_BitMapHeader, &sbmh,
			PDTA_NumColors, &ncols,
			PDTA_CRegs, &scregs,
			PDTA_ColorRegisters, &scm,
			TAG_END) == 4)
	{
		if (IDataTypes->GetDTAttrs(dst_dt,
				PDTA_BitMapHeader, &dbmh,
				TAG_END) == 1)
		{
			int32 error_lev = 0;
			int32 error_num = 0;

			*dbmh = *sbmh;

			dbmh->bmh_Compression = cmpNone;

			uint32 width = sbmh->bmh_Width;
			uint32 height = sbmh->bmh_Height;
			IDataTypes->SetDTAttrs(dst_dt, NULL, NULL,
				PDTA_SourceMode, PMODE_V43,
				PDTA_NumColors, ncols,
				DTA_ErrorLevel, &error_lev,
				DTA_ErrorNumber, &error_num,
				DTA_NominalHoriz, width,
				DTA_NominalVert, height,
				TAG_END);

			if (error_lev || error_num)
				return ERROR_NO_FREE_STORE;

			if (ncols == 0 ||
				IDataTypes->GetDTAttrs(dst_dt,
					PDTA_CRegs, &dcregs,
					PDTA_ColorRegisters, &dcm,
					TAG_END) == 2)
			{
				uint32 i;
				for (i = 0; i < ncols; i++) {
					dcregs[(i * 3) + 0] = scregs[(i * 3) + 0];
					dcregs[(i * 3) + 1] = scregs[(i * 3) + 1];
					dcregs[(i * 3) + 2] = scregs[(i * 3) + 2];
					dcm[(i * 3) + 0] = scm[(i * 3) + 0];
					dcm[(i * 3) + 1] = scm[(i * 3) + 1];
					dcm[(i * 3) + 2] = scm[(i * 3) + 2];
				}

				uint32 bpp, fmt;
				if (sbmh->bmh_Depth >= 1 && sbmh->bmh_Depth <= 8) {
					bpp = 1;
					fmt = PBPAFMT_LUT8;
				} else if (sbmh->bmh_Depth == 24) {
					bpp = 3;
					fmt = PBPAFMT_RGB;
				} else if (sbmh->bmh_Depth == 32) {
					bpp = 4;
					fmt = PBPAFMT_ARGB;
				} else
					return ERROR_NOT_IMPLEMENTED;

				uint32 bpr = width * bpp;
				uint8 *buffer = IExec->AllocVecTagList(bpr, NULL);
				if (buffer == NULL)
					return ERROR_NO_FREE_STORE;

				uint32 y;
				for (y = 0; y < height; y++) {
					IIntuition->IDoMethod(src_dt, PDTM_READPIXELARRAY,
						buffer, fmt, bpr, 0, y, width, 1);
					IIntuition->IDoMethod(dst_dt, PDTM_WRITEPIXELARRAY,
						buffer, fmt, bpr, 0, y, width, 1);
				}

				IExec->FreeVec(buffer);

				if (IDataTypes->SaveDTObjectA(dst_dt, NULL, NULL, outfile, DTWM_RAW, TRUE, NULL))
					error = 0;
				else
					error = IDOS->IoErr();
			}
		}
	}

	return error;
}

static int32 ConvertSound (Object *src_dt, Object *dst_dt, CONST_STRPTR outfile) {
	int32 error = ERROR_REQUIRED_ARG_MISSING;
	struct VoiceHeader *svh     = NULL;
	uint8              *sample  = NULL;
	uint8              *lsample = NULL;
	uint8              *rsample = NULL;
	uint32              bytelen = 0;
	uint32              freq    = 0;
	uint32              bps     = 0;
	struct VoiceHeader *dvh     = NULL;

	if (IDataTypes->GetDTAttrs(src_dt,
			SDTA_VoiceHeader, &svh,
			SDTA_Sample, &sample,
			SDTA_LeftSample, &lsample,
			SDTA_RightSample, &rsample,
			SDTA_SampleLength, &bytelen,
			SDTA_SamplesPerSec, &freq,
			SDTA_BitsPerSample, &bps,
			TAG_END) == 7)
	{
		if (IDataTypes->GetDTAttrs(dst_dt,
			SDTA_VoiceHeader, &dvh,
			TAG_END) == 1)
		{
			*dvh = *svh;

			IDataTypes->SetDTAttrs(dst_dt, NULL, NULL,
				SDTA_FreeSampleData, FALSE,
				SDTA_Sample, sample,
				SDTA_LeftSample, lsample,
				SDTA_RightSample, rsample,
				SDTA_SampleLength, bytelen,
				SDTA_SamplesPerSec, freq,
				SDTA_BitsPerSample, bps,
				TAG_END);

			if (IDataTypes->SaveDTObjectA(dst_dt, NULL, NULL, outfile, DTWM_RAW, TRUE, NULL))
				error = 0;
			else
				error = IDOS->IoErr();
		}
	}

	return error;
}

