/* Copyright 2012 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.
*/

#ifdef __amigaos4__
#define __USE_INLINE__ 1
#endif

#include <datatypes/pictureclass.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/utility.h>
#include <proto/iffparse.h>
#include <stdint.h>
#include <string.h>
#include "endian.h"
#include "shp2ilbm_rev.h"

#define PROGNAME "shp2ilbm"
#define TEMPLATE "SHP/A,CMAP,TO"
#define MAXARGS 3
CONST TEXT USED verstag[] = VERSTAG;

struct Cmap {
	unsigned int size;
	uint8_t data[256*3];
};

#pragma pack(2)

struct BlitzShape {
	uint16_t width, height;
	uint16_t depth;
	uint16_t ebwidth;
	uint16_t bltsize;
	int16_t xhandle, yhandle;
	uint32_t data;
	uint32_t cookie;
	uint16_t onebpmem;
	uint16_t onebpmemx;
	uint16_t allbpmem;
	uint16_t allbpmemx;
	int8_t pad[2];
};

#pragma pack()

BPTR errout;

#define unless(x) if (!(x))

#define BLTSIZE(w,h) ((((h)<<6)&0xffc0)|((((w)+15)>>4)&0x003f))

BOOL Shp2Ilbm (CONST_STRPTR shpfilename, CONST_STRPTR cmapname, CONST_STRPTR outdirname);

int main (void) {
#ifdef __AROS__
	IPTR args[MAXARGS];
#else
	LONG args[MAXARGS];
#endif
	struct RDArgs *rdargs;
	int rc = RETURN_FAIL;

#ifdef __amigaos4__
	errout = ErrorOutput();
	if (errout == ZERO) {
		errout = Output();
	}
#else
	errout = Output();
#endif

	memset(args, 0, sizeof(args));
	rdargs = ReadArgs(TEMPLATE, args, NULL);
	if (rdargs == NULL) {
		PrintFault(IoErr(), PROGNAME);
		return rc;
	}

	if (Shp2Ilbm((CONST_STRPTR)args[0], (CONST_STRPTR)args[1], (CONST_STRPTR)args[2])) {
		rc = RETURN_OK;
	}

	FreeArgs(rdargs);

	return rc;
}

BOOL ReadCmap (CONST_STRPTR cmapname, struct Cmap *cmap);
void GenerateCookie (struct BlitzShape *shape, uint8_t *data, uint8_t *cookie);
BOOL OutputIlbm (CONST_STRPTR filename, struct BlitzShape *shape, struct Cmap *cmap, uint8_t *data, uint8_t *cookie);

BOOL Shp2Ilbm (CONST_STRPTR shpfilename, CONST_STRPTR cmapname, CONST_STRPTR outdirname) {
	BOOL res = FALSE;
	BPTR shpfile;
	BPTR outdir, olddir;
	int bytes;
	struct Cmap cmap;
	struct BlitzShape shape;
	APTR data = NULL;
	APTR cookie = NULL;
	TEXT ilbmfilename[256];
	TEXT *extension;
	int ilbmfilenamelen;
	int index = 0;

	shpfile = Open(shpfilename, MODE_OLDFILE);
	if (shpfile == ZERO) {
		PrintFault(IoErr(), shpfilename);
		return res;
	}

	memset(&cmap, 0, sizeof(cmap));
	if (cmapname) {
		if (!ReadCmap(cmapname, &cmap)) {
			Close(shpfile);
			return res;
		}
	}

	if (outdirname) {
		outdir = Lock(outdirname, ACCESS_READ);
		if (!outdir) {
			PrintFault(IoErr(), outdirname);
			Close(shpfile);
			return res;
		}
		olddir = CurrentDir(outdir);
	}

	while (TRUE) {
		bytes = Read(shpfile, &shape, sizeof(shape));
		if (bytes != sizeof(shape)) {
			if (bytes == 0) {
				res = TRUE;
			} else {
				PrintFault(IoErr(), shpfilename);
			}
			break;
		}

		shape.width = h2be16(shape.width);
		shape.height = h2be16(shape.height);
		shape.depth = h2be16(shape.depth);
		shape.ebwidth = h2be16(shape.ebwidth);
		shape.bltsize = h2be16(shape.bltsize);
		shape.xhandle = h2be16(shape.xhandle);
		shape.yhandle = h2be16(shape.yhandle);
		shape.onebpmem = h2be16(shape.onebpmem);
		shape.onebpmemx = h2be16(shape.onebpmemx);
		shape.allbpmem = h2be16(shape.allbpmem);
		shape.allbpmemx = h2be16(shape.allbpmemx);

		Printf("Dimensions: %ldx%ldx%ld\n", shape.width, shape.height, shape.depth);

		unless (0 < shape.width && shape.width <= (1024 - 16)) {
			FPrintf(errout, "Invalid width: %ld\n", shape.width);
			break;
		}
		unless (0 < shape.height && shape.height <= 1024) {
			FPrintf(errout, "Invalid height: %ld\n", shape.height);
			break;
		}
		unless (1 <= shape.depth && shape.depth <= 8) {
			FPrintf(errout, "Unsupported depth: %ld\n", shape.depth);
			break;
		}
		unless (shape.ebwidth == RASSIZE(shape.width, 1)) {
			FPrintf(errout, "Invalid ebwidth: %ld\n", shape.ebwidth);
			break;
		}
		unless (shape.bltsize == BLTSIZE(shape.width + 16, shape.height)) {
			FPrintf(errout, "Invalid bltsize: %04lx\n", shape.bltsize);
			break;
		}
		unless (shape.onebpmem == RASSIZE(shape.width, shape.height)) {
			FPrintf(errout, "Invalid onebpmem: %ld\n", shape.onebpmem);
			break;
		}
		unless (shape.onebpmemx == RASSIZE(shape.width + 1, shape.height)) {
			FPrintf(errout, "Invalid onebpmemx: %ld\n", shape.onebpmemx);
			break;
		}
		unless (shape.allbpmem == (shape.depth * RASSIZE(shape.width, shape.height))) {
			FPrintf(errout, "Invalid allbpmem: %ld\n", shape.allbpmem);
			break;
		}
		unless (shape.allbpmemx == (shape.depth * RASSIZE(shape.width + 1, shape.height))) {
			FPrintf(errout, "Invalid allbpmemx: %ld\n", shape.allbpmemx);
			break;
		}

#ifdef __amigaos4__
		data = AllocVecTags(shape.allbpmem, TAG_END);
		cookie = AllocVecTags(shape.onebpmem, TAG_END);
#else
		data = AllocVec(shape.allbpmem, MEMF_ANY);
		cookie = AllocVec(shape.onebpmem, MEMF_ANY);
#endif
		unless (data && cookie) {
			PrintFault(ERROR_NO_FREE_STORE, PROGNAME);
			break;
		}

		if (Read(shpfile, data, shape.allbpmem) != shape.allbpmem) {
			PrintFault(IoErr(), shpfilename);
			break;
		}
		GenerateCookie(&shape, data, cookie);

		Strlcpy(ilbmfilename, FilePart(shpfilename), sizeof(ilbmfilename));
		if ((extension = strrchr(ilbmfilename, '.')) && Stricmp(extension, ".shp") == 0) {
			*extension = 0;
		}
		ilbmfilenamelen = strlen(ilbmfilename);
		SNPrintf(ilbmfilename + ilbmfilenamelen, sizeof(ilbmfilename) - ilbmfilenamelen, "_%ld.ilbm", index);
		if (!OutputIlbm(ilbmfilename, &shape, &cmap, data, cookie)) {
			break;
		}

		FreeVec(data);
		FreeVec(cookie);
		data = cookie = NULL;

		index++;
	}

	if (outdirname) {
		CurrentDir(olddir);
		UnLock(outdir);
	}

	FreeVec(data);
	FreeVec(cookie);
	Close(shpfile);
	return res;
}

BOOL ReadCmap (CONST_STRPTR filename, struct Cmap *cmap) {
	BOOL res = FALSE;
	struct IFFHandle *iff;
	int32_t error;
	struct ContextNode *cn;

	do {
		iff = AllocIFF();
		if (!iff) {
			PrintFault(ERROR_NO_FREE_STORE, PROGNAME);
			break;
		}

		iff->iff_Stream = Open(filename, MODE_OLDFILE);
		if (!iff->iff_Stream) {
			PrintFault(IoErr(), filename);
			break;
		}
		InitIFFasDOS(iff);

		if ((error = OpenIFF(iff, IFFF_READ)) != 0) {
			FPrintf(errout, "%s: OpenIFF() returned error %ld\n", filename, error);
			break;
		}

		if ((error = StopChunk(iff, ID_ILBM, ID_CMAP)) != 0) {
			FPrintf(errout, "%s: StopChunk() returned error %ld\n", filename, error);
			break;
		}

		if ((error = ParseIFF(iff, IFFPARSE_SCAN)) != 0) {
			FPrintf(errout, "%s: ParseIFF() returned error %ld\n", filename, error);
			break;
		}

		cn = CurrentChunk(iff);
		unless (cn && cn->cn_ID == ID_CMAP && cn->cn_Type == ID_ILBM) {
			FPrintf(errout, "%s: CMAP chunk not found\n", filename);
			break;
		}

		unless (0 < cn->cn_Size && cn->cn_Size <= (256*3) && (cn->cn_Size % 3) == 0) {
			FPrintf(errout, "%s: invalid CMAP size %ld\n", filename, cn->cn_Size);
		}

		cmap->size = cn->cn_Size;
		if ((error = ReadChunkBytes(iff, cmap->data, cmap->size)) != cmap->size) {
			FPrintf(errout, "%s: ReadChunkBytes() returned error %ld\n", filename, error);
			break;
		}

		res = TRUE;
	} while (FALSE);

	if (iff) {
		CloseIFF(iff);
		if (iff->iff_Stream) {
			Close(iff->iff_Stream);
		}
		FreeIFF(iff);
	}
	return res;
}

void GenerateCookie (struct BlitzShape *shape, uint8_t *data, uint8_t *cookie) {
	unsigned int i, j;
	uint8_t *src, *dst;

	memset(cookie, 0, shape->onebpmem);

	for (i = 0; i < shape->depth; i++) {
		src = data + (shape->onebpmem * i);
		dst = cookie;
		for (j = 0; j < shape->onebpmem; j++) {
			*dst++ |= *src++;
		}
	}
}

BOOL OutputIlbm (CONST_STRPTR filename, struct BlitzShape *shape, struct Cmap *cmap, uint8_t *data, uint8_t *cookie) {
	BOOL res = FALSE;
	struct IFFHandle *iff;
	int32_t error;
	struct BitMapHeader bmh;
	unsigned int i, j;
	uint8_t *src;

	do {
		iff = AllocIFF();
		if (!iff) {
			PrintFault(ERROR_NO_FREE_STORE, PROGNAME);
			break;
		}

		iff->iff_Stream = Open(filename, MODE_NEWFILE);
		if (!iff->iff_Stream) {
			PrintFault(IoErr(), filename);
			break;
		}
		InitIFFasDOS(iff);

		if ((error = OpenIFF(iff, IFFF_WRITE)) != 0) {
			FPrintf(errout, "%s: OpenIFF() returned error %ld\n", filename, error);
			break;
		}

		if ((error = PushChunk(iff, ID_ILBM, ID_FORM, IFFSIZE_UNKNOWN)) != 0) {
			FPrintf(errout, "%s: PushChunk() returned error %ld\n", filename, error);
			break;
		}

		bmh.bmh_Width = h2be16(shape->width);
		bmh.bmh_Height = h2be16(shape->height);
		bmh.bmh_Left = h2be16(0);
		bmh.bmh_Top = h2be16(0);
		bmh.bmh_Depth = shape->depth;
		bmh.bmh_Masking = mskNone;
		bmh.bmh_Compression = cmpNone;
		bmh.bmh_Pad = 0;
		bmh.bmh_Transparent = h2be16(0);
		bmh.bmh_XAspect = 0;
		bmh.bmh_YAspect = 0;
		bmh.bmh_PageWidth = h2be16(0);
		bmh.bmh_PageHeight = h2be16(0);

		if ((error = PushChunk(iff, ID_ILBM, ID_BMHD, sizeof(bmh))) != 0) {
			FPrintf(errout, "%s: PushChunk() returned error %ld\n", filename, error);
			break;
		}
		if ((error = WriteChunkBytes(iff, &bmh, sizeof(bmh))) != sizeof(bmh)) {
			FPrintf(errout, "%s: WriteChunkBytes() returned error %ld\n", filename, error);
			break;
		}
		if ((error = PopChunk(iff)) != 0) {
			FPrintf(errout, "%s: PopChunk() returned error %ld\n", filename, error);
			break;
		}

		if (cmap->size > 0) {
			if ((error = PushChunk(iff, ID_ILBM, ID_CMAP, cmap->size)) != 0) {
				FPrintf(errout, "%s: PushChunk() returned error %ld\n", filename, error);
				break;
			}
			if ((error = WriteChunkBytes(iff, cmap->data, cmap->size)) != cmap->size) {
				FPrintf(errout, "%s: WriteChunkBytes() returned error %ld\n", filename, error);
				break;
			}
			if ((error = PopChunk(iff)) != 0) {
				FPrintf(errout, "%s: PopChunk() returned error %ld\n", filename, error);
				break;
			}
		}

		if ((error = PushChunk(iff, ID_ILBM, ID_BODY, shape->allbpmem)) != 0) {
			FPrintf(errout, "%s: PushChunk() returned error %ld\n", filename, error);
			break;
		}
		for (i = 0; i < shape->height; i++) {
			for (j = 0; j < shape->depth; j++) {
				src = data + (j * shape->onebpmem) + (i * shape->ebwidth);
				if ((error = WriteChunkBytes(iff, src, shape->ebwidth)) != shape->ebwidth) {
					FPrintf(errout, "%s: WriteChunkBytes() returned error %ld\n", filename, error);
					break;
				}
			}
		}
		if ((error = PopChunk(iff)) != 0) {
			FPrintf(errout, "%s: PopChunk() returned error %ld\n", filename, error);
			break;
		}

		if ((error = PopChunk(iff)) != 0) {
			FPrintf(errout, "%s: PopChunk() returned error %ld\n", filename, error);
			break;
		}

		res = TRUE;
	} while (FALSE);

	if (iff) {
		CloseIFF(iff);
		if (iff->iff_Stream) {
			Close(iff->iff_Stream);
		}
		FreeIFF(iff);
	}
	return res;
}
