/*
 * Copyright 2009-2013, Ingo Weinhold, ingo_weinhold@gmx.de.
 * Distributed under the terms of the MIT License.
 */


#include <ctype.h>
#include <errno.h>
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <package/hpkg/HPKGDefs.h>
#include <package/hpkg/PackageAttributeValue.h>
#include <package/hpkg/PackageContentHandler.h>
#include <package/hpkg/PackageEntry.h>
#include <package/hpkg/PackageEntryAttribute.h>
#include <package/hpkg/PackageReader.h>
#include <package/hpkg/StandardErrorOutput.h>

#include "package.h"


using namespace BPackageKit::BHPKG;


struct PackageContentDumpHandler : BLowLevelPackageContentHandler {
	PackageContentDumpHandler()
		:
		fLevel(0),
		fErrorOccurred(false),
		fHasChildren(false)
	{
	}

	virtual status_t HandleSectionStart(BHPKGPackageSectionID sectionID,
		bool& _handleSection)
	{
		const char* sectionName;

		switch (sectionID) {
			case B_HPKG_SECTION_HEADER:
				sectionName = "header";
				break;
			case B_HPKG_SECTION_HEAP:
				sectionName = "heap";
				break;
			case B_HPKG_SECTION_PACKAGE_TOC:
				sectionName = "TOC";
				break;
			case B_HPKG_SECTION_PACKAGE_ATTRIBUTES:
				sectionName = "package attributes";
				break;
			case B_HPKG_SECTION_REPOSITORY_INFO:
				sectionName = "repository info";
				break;
			default:
				sectionName = "<unknown section>";
				break;
		}

		printf("\n====  SECTION: %s ====\n", sectionName);

		_handleSection = true;
		return B_OK;
	}

	virtual status_t HandleSectionEnd(BHPKGPackageSectionID sectionID)
	{
		return B_OK;
	}

	virtual status_t HandleAttribute(BHPKGAttributeID attributeID,
		const BPackageAttributeValue& value, void* parentToken, void*& _token)
	{
		if (fErrorOccurred)
			return B_OK;

		printf("%*s>%s: ", fLevel * 2, "", AttributeNameForID(attributeID));
		_PrintValue(value);
		printf("\n");

		fHasChildren = false;
		fLevel++;
		return B_OK;
	}

	virtual status_t HandleAttributeDone(BHPKGAttributeID attributeID,
		const BPackageAttributeValue& value, void* parentToken, void* token)
	{
		if (fErrorOccurred)
			return B_OK;

		fLevel--;

		if (fHasChildren)
			printf("%*s<%s\n", fLevel * 2, "", AttributeNameForID(attributeID));

		fHasChildren = true;
		return B_OK;
	}

	virtual void HandleErrorOccurred()
	{
		fErrorOccurred = true;
	}

private:
	void _PrintValue(const BPackageAttributeValue& value)
	{
		switch (value.type) {
			case B_HPKG_ATTRIBUTE_TYPE_INT:
				printf("%lld (%#llx)", (long long)value.signedInt,
					(long long)value.signedInt);
				break;
			case B_HPKG_ATTRIBUTE_TYPE_UINT:
				printf("%llu (%#llx)", (unsigned long long)value.unsignedInt,
					(unsigned long long)value.unsignedInt);
				break;
			case B_HPKG_ATTRIBUTE_TYPE_STRING:
				printf("\"%s\"", value.string);
				break;
			case B_HPKG_ATTRIBUTE_TYPE_RAW:
				switch (value.encoding) {
					case B_HPKG_ATTRIBUTE_ENCODING_RAW_INLINE:
						printf("data: size: %llu, inline",
							(unsigned long long)value.data.size);
						// TODO: Print the data bytes!
						break;
					case B_HPKG_ATTRIBUTE_ENCODING_RAW_HEAP:
						printf("data: size: %llu, offset: %llu",
							(unsigned long long)value.data.size,
							(unsigned long long)value.data.offset);
						break;
					default:
						break;
				}
				break;
			default:
				printf("<unknown type %u>\n", value.type);
				break;
		}
	}

private:
	int		fLevel;
	bool	fErrorOccurred;
	bool	fHasChildren;
};


int
command_dump(int argc, const char* const* argv)
{
	while (true) {
		static struct option sLongOptions[] = {
			{ "help", no_argument, 0, 'h' },
			{ 0, 0, 0, 0 }
		};

		opterr = 0; // don't print errors
		int c = getopt_long(argc, (char**)argv, "+h", sLongOptions, NULL);
		if (c == -1)
			break;

		switch (c) {
			case 'h':
				print_usage_and_exit(false);
				break;

			default:
				print_usage_and_exit(true);
				break;
		}
	}

	// One argument should remain -- the package file name.
	if (optind + 1 != argc)
		print_usage_and_exit(true);

	const char* packageFileName = argv[optind++];

	// open package
	BStandardErrorOutput errorOutput;
	BPackageReader packageReader(&errorOutput);
	status_t error = packageReader.Init(packageFileName);
	if (error != B_OK)
		return 1;

	// list
	PackageContentDumpHandler handler;
	error = packageReader.ParseContent(&handler);
	if (error != B_OK)
		return 1;

	return 0;
}
