//..............................................................................
//
// USB ID database (vendor IDs & product IDs)
//

import "io_base.jncx"
import "io_MappedFile.jnc"
import "std_HashTable.jnc"

namespace io {

//..............................................................................

struct UsbProduct
{
	uint_t m_id;
	char const* m_name;
	UsbVendor* m_vendor;
}

//..............................................................................

class UsbVendor
{
protected:
	std.HashTable m_productMap;

public:
	uint_t m_id;
	char const* m_name;

public:
	UsbProduct* addProduct (
		uint_t id,
		char const* name
		);

	UsbProduct const* findProduct (uint_t productId) const
	{
		return m_productMap.findValue (productId);
	}
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

UsbProduct* UsbVendor.addProduct (
	uint_t id,
	char const* name
	)
{
	UsbProduct* product = new UsbProduct;
	product.m_id = id;
	product.m_name = name;
	product.m_vendor = this;
	m_productMap [id] = product;

	return product;
}

//..............................................................................

class UsbDb
{
protected:
	std.HashTable m_vendorMap;

public:
	bool errorcode load (char const* fileName);

	UsbVendor* addVendor (
		uint_t id,
		char const* name
		);

	UsbVendor const* findVendor (uint_t vendorId) const
	{
		return m_vendorMap.findValue (vendorId);
	}

	UsbProduct const* findProduct (
		uint_t vendorId,
		uint_t productId
		)
	{
		UsbVendor* vendor =  m_vendorMap.findValue (vendorId);
		return vendor ? vendor.findProduct (productId) : null;
	}
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

UsbDb* getStdUsbDb ()
{
	static UsbDb usbDb;

	once
	{
		// on debug builds, we use 0 period size trigger (to stress-test gc by forcing it
		// to collect on each allocation). however, on allocation-heavy apps (usb.ids has 20,000+ lines)
		// this makes everything too slow, so we better patch 0 period size trigger

		sys.GcTriggers triggers = sys.g_gcTriggers;
		if (!triggers.m_periodSizeTrigger)
		{
			triggers.m_periodSizeTrigger = 2 * 1024 * 1024; // 2 MB is a reasonable period
			sys.g_gcTriggers = triggers;
		}

		try usbDb.load (__DIR__ "/usb.ids"); // ignore result
	}

	return &usbDb;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

UsbVendor* UsbDb.addVendor (
	uint_t id,
	char const* name
	)
{
	UsbVendor* vendor = new UsbVendor;
	vendor.m_id = id;
	vendor.m_name = name;
	m_vendorMap [id] = vendor;

	return vendor;
}

// . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

bool errorcode UsbDb.load (char const* fileName)
{
	m_vendorMap.clear ();

	disposable io.MappedFile file;
	file.open (fileName, io.FileOpenFlags.ReadOnly);

	size_t size = file.m_size;
	char const* p = file.view (0, size, false);
	char const* end = p + size;

	jnc.RegexState state;
	UsbVendor* vendor;

	while (p < end)
	{
		reswitch (state, p, end - p)
		{
		case r"(\h{4})\s+([^\n]+[^\s])\s*\n":
			vendor = addVendor (
				strtol (state.m_subMatchArray [0].m_text,, 16),
				state.m_subMatchArray [1].m_text
				);
			break;

		case r"\t(\h{4})\s+([^\n]+[^\s])\s*\n":
			if (!vendor)
			{
				std.setError ("invalid USB database file format");
				return false;
			}

			vendor.addProduct (
				strtol (state.m_subMatchArray [0].m_text,, 16),
				state.m_subMatchArray [1].m_text
				);
			break;

		case r"\s*#[^\n]*\n":
			break; // ignore comment lines

		case r"\s*\n":
			break; // ignore empty lines

		default:
			break2; // any other line stops parsing
		}

		p += state.m_consumedLength;
	}

	return true;
}

//..............................................................................

} // namespace io
