/* rifffile.cpp

	Copyright (c) 1996, 1988 by Timothy J. Weber.

	See rifffile.txt for documentation.
*/

#include "rifffile.h"

using namespace std;

/***************************************************************************
	macros and constants
***************************************************************************/

// define REVERSE_ENDIANISM if the endianism of the host platform is not Intel
// (Intel is little-endian)
#ifdef REVERSE_ENDIANISM
 #define SWAP_32(int32) (  \
	((((DWORD) int32) & 0x000000FFL) << 24) +  \
	((((DWORD) int32) & 0x0000FF00L) << 8) +  \
	((((DWORD) int32) & 0x00FF0000L) >> 8) +  \
	((((DWORD) int32) & 0xFF000000L) >> 24))
#endif

struct TypeRecord {
	char* typeName;  // four-letter name
	char* realName;  // English name
};

const int numExtraTypes = 24;
const TypeRecord extraTypes[numExtraTypes] = {
	{ "DISP", "Display name" },
	{ "IARL", "Archival location" },
	{ "IART", "Artist" },
	{ "ICMS", "Commissioned" },
	{ "ICMT", "Comments" },
	{ "ICOP", "Copyright" },
	{ "ICRD", "Creation date" },
	{ "ICRP", "Cropped" },
	{ "IDIM", "Dimensions" },
	{ "IDPI", "Dots Per Inch" },
	{ "IENG", "Engineer" },
	{ "IGNR", "Genre" },
	{ "IKEY", "Keywords" },
	{ "ILGT", "Lightness" },
	{ "IMED", "Medium" },
	{ "INAM", "Name" },
	{ "IPLT", "Palette Setting" },
	{ "IPRD", "Product" },
	{ "ISBJ", "Subject" },
	{ "ISFT", "Software" },
	{ "ISHP", "Sharpness" },
	{ "ISRC", "Source" },
	{ "ISRF", "Source Form" },
	{ "ITCH", "Technician" },
};

/***************************************************************************
	typedefs and class definitions
***************************************************************************/

/***************************************************************************
	prototypes for static functions
***************************************************************************/

/***************************************************************************
	static variables
***************************************************************************/

/***************************************************************************
	member functions for RiffFile
***************************************************************************/

RiffFile::RiffFile(const char *name):
	fp(fopen(name, "rb"))
{
	if (fp && !rewind()) {
		fclose(fp);
		fp = 0;
	}
}

RiffFile::~RiffFile()
{
	if (fp)
		fclose(fp);
}

bool RiffFile::rewind()
{
	// clear the chunk stack
	while (!chunks.empty())
		chunks.pop();

	// rewind to the start of the file
	if (fseek(fp, 0, SEEK_SET))
		return false;

	// look for a valid RIFF header
	RiffChunk topChunk(*this);

	if (feof(fp) || strcmp(topChunk.name, "RIFF"))
		return false;

	// found; push it on the stack, and leave the put pointer in the same place
	// as the get pointer.
	formSize = topChunk.size;
	chunks.push(topChunk);
	return true;
}

bool RiffFile::push(const char* chunkType)
{
	// can't descend if we haven't started out yet.
	if (chunks.empty())
		return false;

	// first, go to the start of the current chunk, if we're looking for a named
	// chunk.
	if (chunkType)
		if (fseek(fp, chunks.top().start, SEEK_SET))
			return false;

	// read chunks until one matches or we exhaust this chunk
	while (!feof(fp) && ftell(fp) < chunks.top().after) {
		RiffChunk chunk(*this);

		if (!feof(fp)) {
			// see if the subchunk type matches
			if (!chunkType || strcmp(chunk.name, chunkType) == 0) {
				// found; synchronize the put pointer, push the chunk, and succeed
				chunks.push(chunk);
				return true;
			} else {
				// not found; go to the next one.
				if (fseek(fp, chunk.after, SEEK_SET))
					return false;
			}
		}
	}

	// couldn't find it; synchronize the put pointer and return error.
	fseek(fp, chunks.top().start, SEEK_SET);
	return false;
}

bool RiffFile::pop()
{
	// if we've only got the top level chunk (or not even that), then we can't
	// go up.
	if (chunks.size() < 2)
		return false;

	// Position the get and put pointers at the end of the current subchunk.
	fseek(fp, chunks.top().after, SEEK_SET);

	// Pop up the stack.
	chunks.pop();
	return true;
}

long RiffFile::chunkSize() const
{
	if (!chunks.empty())
		return chunks.top().size;
	else
		return 0;
}

const char* RiffFile::chunkName() const
{
	if (!chunks.empty())
		return chunks.top().name;
	else
		return 0;
}

const char* RiffFile::subType() const
{
	if (!chunks.empty() && chunks.top().subType[0])
		return chunks.top().subType;
	else
		return 0;
}

bool RiffFile::getNextExtraItem(string& type, string& value)
{
	// if the current chunk is LIST/INFO, then try to read another subchunk.
	if (strcmp(chunkName(), "LIST") == 0
		&& strcmp(subType(), "INFO") == 0)
	{
		if (push()) {
			if (readExtraItem(type, value))
				return true;
			else
				// unrecognized type.  Continue on.
				return getNextExtraItem(type, value);
		} else {
			// got to the end of the LIST/INFO chunk.  Pop back out and continue
			// looking.
			pop();
			return getNextExtraItem(type, value);
		}
	// we're not in a LIST/INFO chunk, so look for the next DISP or LIST/INFO.
	} else {
		push();

		if (strcmp(chunkName(), "DISP") == 0) {
			// DISP chunk: read and pop back out.
			return readExtraItem(type, value);
		} else if (strcmp(chunkName(), "LIST") == 0
			&& strcmp(subType(), "INFO") == 0)
		{
			// LIST/INFO chunk: read first element
			return getNextExtraItem(type, value);
		} else {
			// Some other chunk.  Pop back out and move on.
			if (pop())
				return getNextExtraItem(type, value);
			else
				return false;
		}
	}
}

// Reads extra data from the current chunk, and pops out of it.
bool RiffFile::readExtraItem(string& type, string& value)
{
	// see if it's one we recognize
	bool found = false;
	for (int i = 0; i < numExtraTypes; i++) {
		if (strcmp(chunkName(), extraTypes[i].typeName) == 0) {
			type = extraTypes[i].realName;
			found = true;
		}
	}

	// DISP chunks skip four bytes before the display name starts.
	if (strcmp(chunkName(), "DISP") == 0) {
		fgetc(filep());
		fgetc(filep());
		fgetc(filep());
		fgetc(filep());
	}

	// read the value, if we recognize the type
	if (found) {
		int c;
		value = "";
		while ((c = fgetc(filep())) != '\0' && c != EOF)
			value += char(c);
	}

	// whether we recognize it or not, pop back out.
	pop();
	return found;
}

/***************************************************************************
	member functions for RiffChunk
***************************************************************************/

RiffChunk::RiffChunk(RiffFile& parent)
{
	// read the chunk name
	fread(name, 1, 4, parent.filep());
	name[4] = '\0';

	// read the chunk size
	fread(&size, 4, 1, parent.filep());

#ifdef REVERSE_ENDIANISM
	// reverse the endianism of the chunk size.
	size = SWAP_32(size);
#endif

	// if this is a RIFF or LIST chunk, read its subtype.
	if (strcmp(name, "RIFF") == 0
		|| strcmp(name, "LIST") == 0)
	{
		fread(subType, 1, 4, parent.filep());
		subType[4] = '\0';

		// subtract the subtype from the size of the data.
		size -= 4;
	} else
		*subType = '\0';

	// the chunk starts after the name and size.
	start = ftell(parent.filep());

	// the next chunk starts after this one, but starts on a word boundary.
	after = start + size;
	if (after % 2)
		after++;
}

/***************************************************************************
	main()
***************************************************************************/

#ifdef TEST_RIFFFILE

#include <iostream>

static void reportProblem()
{
	cout << "  *** ERROR: Result incorrect." << endl;
}

static void checkResult(bool got, bool expected)
{
	if (got)
		cout << "success." << endl;
	else 
		cout << "fail." << endl;

	if (got != expected)
		reportProblem();
}

static void pause()
{
	cout << "Press Enter to continue." << endl;
	cin.get();
}

static void showChunk(RiffFile& file, int indent, bool expandLists)
{
	for (int i = 0; i < indent; i++)
		cout << ' ';

	cout << "Chunk type: " << file.chunkName();

	if (file.subType())
		cout << " (" << file.subType() << ")";

	cout << ", " << file.chunkSize() << " bytes" << endl;

	// show all subchunks
	if (strcmp(file.chunkName(), "RIFF") == 0
		|| (expandLists && strcmp(file.chunkName(), "LIST") == 0))
	{
		while (file.push()) {
			showChunk(file, indent + 2, expandLists);
			file.pop();
		}
	}
}

int main(int argc, const char* argv[])
{
	if (argc == 1)
		cout << "Lists the chunks in a named RIFF file." << endl;
	else {
		RiffFile file(argv[1]);

		if (!file.filep()) {
			cout << "No RIFF form in file " << argv[1] << "." << endl;
		} else {
			cout << "Without expanding lists:" << endl;
			showChunk(file, 0, false);

			cout << endl << "Expanding lists:" << endl;
			showChunk(file, 0, true);
		}
	}

	return 0;
}

#endif
