////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#include <cstring>
#include <cwchar>
#include <new>
#include <vector>
#include <set>
#include <map>
#include <algorithm>
#include "NstLog.hpp"
#include "NstImageDatabase.hpp"
#include "NstXml.hpp"

namespace Nes
{
	namespace Core
	{
		class ImageDatabase::Item
		{
		public:

			~Item();

			enum
			{
				PERIPHERAL_UNSPECIFIED,
				PERIPHERAL_STANDARD,
				PERIPHERAL_ZAPPER,
				PERIPHERAL_POWERPAD,
				PERIPHERAL_FAMILYTRAINER,
				PERIPHERAL_ARKANOID,
				PERIPHERAL_FOURPLAYER,
				PERIPHERAL_3DGLASSES,
				PERIPHERAL_MIRACLEPIANO,
				PERIPHERAL_SUBORKEYBOARD,
				PERIPHERAL_SUBORMOUSE,
				PERIPHERAL_FAMILYKEYBOARD,
				PERIPHERAL_PARTYTAP,
				PERIPHERAL_CRAZYCLIMBER,
				PERIPHERAL_EXCITINGBOXING,
				PERIPHERAL_KONAMIHYPERSHOT,
				PERIPHERAL_BANDAIHYPERSHOT,
				PERIPHERAL_POKKUNMOGURAA,
				PERIPHERAL_OEKAKIDSTABLET,
				PERIPHERAL_MAHJONG,
				PERIPHERAL_ROB,
				PERIPHERAL_POWERGLOVE,
				PERIPHERAL_HORITRACK,
				PERIPHERAL_PACHINKO,
				PERIPHERAL_TOPRIDERBIKE,
				PERIPHERAL_DOREMIKKO,
				PERIPHERAL_TURBOFILE,
				PERIPHERAL_BARCODEWORLD,
				MAX_PERIPHERALS = 4
			};

		private:

			class String
			{
				union
				{
					mutable wcstring string;
					dword id;
				};

			public:

				explicit String(dword i=0)
				: id(i) {}

				bool operator == (const String& s) const
				{
					return id == s.id;
				}

				bool operator < (const String& s) const
				{
					return id < s.id;
				}

				void operator << (wcstring s) const
				{
					string = s + id;
				}

				operator wcstring () const
				{
					return string;
				}
			};

		public:

			struct Ic
			{
				struct Pin
				{
					uint number;
					String function;

					Pin() {}

					Pin(uint n,dword s)
					: number(n), function(s) {}
				};

				typedef std::vector<Pin> Pins;

				String package;
				Pins pins;

				Ic() {}

				Ic(dword p,const Pins& i)
				: package(p), pins(i) {}
			};

			struct Rom : Ic
			{
				dword id;
				String name;
				dword size;
				Hash hash;

				Rom() {}

				Rom(dword i,dword n,dword s,dword e,const Pins& p,const Hash& c)
				: Ic(e,p), id(i), name(n), size(s), hash(c) {}
			};

			typedef std::vector<Rom> Roms;

			struct Ram : Ic
			{
				dword id;
				dword size;
				bool battery;

				Ram() {}

				Ram(dword i,dword s,bool b,dword e,const Pins& p)
				: Ic(e,p), id(i), size(s), battery(b) {}
			};

			typedef std::vector<Ram> Rams;

			struct Chip : Ic
			{
				String type;
				bool battery;

				Chip() {}

				Chip(dword id,bool b,dword e,const Pins& p)
				: Ic(e,p), type(id), battery(b) {}

				bool operator == (const Chip& chip) const
				{
					return type == chip.type;
				}

				bool operator < (const Chip& chip) const
				{
					return type < chip.type;
				}
			};

			typedef std::vector<Chip> Chips;

			struct Dump
			{
				const String by;
				const String date;
				const Profile::Dump::State state;

				Dump(dword b,dword d,Profile::Dump::State o)
				: by(b), date(d), state(o) {}
			};

			struct Property
			{
				String name;
				String value;

				Property() {}

				Property(dword n,dword v)
				: name(n), value(v) {}
			};

			typedef std::vector<Property> Properties;

		private:

			const Hash hash;
			Item* sibling;
			const Dump dump;
			const String title;
			const String altTitle;
			const String clss;
			const String subClss;
			const String catalog;
			const String publisher;
			const String developer;
			const String portDeveloper;
			const String region;
			const String revision;
			const String pcb;
			const String board;
			const String cic;
			const Roms prg;
			const Roms chr;
			const Rams wram;
			const Rams vram;
			Chips chips;
			const Properties properties;
			byte peripherals[MAX_PERIPHERALS];
			const word mapper;
			const byte solderPads;
			const byte system;
			const byte cpu;
			const byte ppu;
			const byte players;
			bool multiRegion;

		public:

			Item
			(
				const Hash& hashIn,
				dword dumpBy,
				dword dumpDate,
				Profile::Dump::State dumpState,
				dword titleIn,
				dword altTitleIn,
				dword clssIn,
				dword subClssIn,
				dword catalogIn,
				dword publisherIn,
				dword developerIn,
				dword portDeveloperIn,
				dword regionIn,
				const Properties& propertiesIn,
				dword playersIn,
				const byte (&e)[MAX_PERIPHERALS],
				Profile::System::Type systemIn,
				Profile::System::Cpu cpuIn,
				Profile::System::Ppu ppuIn,
				dword revisionIn,
				dword boardIn,
				dword pcbIn,
				uint mapperIn,
				const Roms& prgIn,
				const Roms& chrIn,
				const Rams& wramIn,
				const Rams& vramIn,
				const Chips& chipsIn,
				dword cicIn,
				uint solderPadsIn
			)
			:
			hash          ( hashIn                      ),
			sibling       ( NULL                        ),
			dump          ( dumpBy, dumpDate, dumpState ),
			title         ( titleIn                     ),
			altTitle      ( altTitleIn                  ),
			clss          ( clssIn                      ),
			subClss       ( subClssIn                   ),
			catalog       ( catalogIn                   ),
			publisher     ( publisherIn                 ),
			developer     ( developerIn                 ),
			portDeveloper ( portDeveloperIn             ),
			region        ( regionIn                    ),
			revision      ( revisionIn                  ),
			pcb           ( pcbIn                       ),
			board         ( boardIn                     ),
			cic           ( cicIn                       ),
			prg           ( prgIn                       ),
			chr           ( chrIn                       ),
			wram          ( wramIn                      ),
			vram          ( vramIn                      ),
			chips         ( chipsIn                     ),
			properties    ( propertiesIn                ),
			mapper        ( mapperIn                    ),
			solderPads    ( solderPadsIn                ),
			system        ( systemIn                    ),
			cpu           ( cpuIn                       ),
			ppu           ( ppuIn                       ),
			players       ( playersIn                   ),
			multiRegion   ( false                       )
			{
				for (uint i=0; i < MAX_PERIPHERALS; ++i)
					peripherals[i] = e[i];

				std::sort( chips.begin(), chips.end() );
			}

		private:

			template<typename T>
			static dword GetMemSize(const T& t)
			{
				dword size = 0;

				for (typename T::const_iterator it(t.begin()), end(t.end()); it != end; ++it)
					size += it->size;

				return size;
			}

			template<typename T>
			static bool HasBattery(const T& t)
			{
				for (typename T::const_iterator it(t.begin()), end(t.end()); it != end; ++it)
				{
					if (it->battery)
						return true;
				}

				return false;
			}

		public:

			wcstring GetTitle() const
			{
				return title;
			}

			wcstring GetPublisher() const
			{
				return publisher;
			}

			wcstring GetDeveloper() const
			{
				return developer;
			}

			wcstring GetRegion() const
			{
				return region;
			}

			wcstring GetRevision() const
			{
				return revision;
			}

			wcstring GetPcb() const
			{
				return pcb;
			}

			wcstring GetBoard() const
			{
				return board;
			}

			wcstring GetCic() const
			{
				return cic;
			}

			uint NumPlayers() const
			{
				return players;
			}

			uint GetMapper() const
			{
				return mapper;
			}

			uint GetSolderPads() const
			{
				return solderPads;
			}

			Profile::System::Type GetSystem() const
			{
				return static_cast<Profile::System::Type>(system);
			}

			Profile::Dump::State GetDumpState() const
			{
				return dump.state;
			}

			const Hash& GetHash() const
			{
				return hash;
			}

			dword GetPrgSize() const
			{
				return GetMemSize( prg );
			}

			dword GetChrSize() const
			{
				return GetMemSize( chr );
			}

			dword GetWramSize() const
			{
				return GetMemSize( wram );
			}

			dword GetVramSize() const
			{
				return GetMemSize( vram );
			}

			bool HasVRamBattery() const
			{
				return HasBattery( vram );
			}

			bool HasWRamBattery() const
			{
				return HasBattery( wram );
			}

			bool HasChipBattery() const
			{
				return HasBattery( chips );
			}

			bool HasBattery() const
			{
				return HasWRamBattery() || HasVRamBattery() || HasChipBattery();
			}

			const Item* GetNextSibling() const
			{
				return sibling;
			}

			bool IsMultiRegion() const
			{
				return multiRegion;
			}

			void Fill(Profile& profile,const bool full) const
			{
				if (full)
				{
					if (*dump.by)
						profile.dump.by = dump.by;

					if (*dump.date)
						profile.dump.date = dump.date;

					if (dump.state != Profile::Dump::UNKNOWN)
						profile.dump.state = dump.state;

					if (*title)
						profile.game.title = title;

					if (*altTitle)
						profile.game.altTitle = altTitle;

					if (*clss)
						profile.game.clss = clss;

					if (*subClss)
						profile.game.subClss = subClss;

					if (*catalog)
						profile.game.catalog = catalog;

					if (*publisher)
						profile.game.publisher = publisher;

					if (*developer)
						profile.game.developer = developer;

					if (*portDeveloper)
						profile.game.portDeveloper = portDeveloper;

					if (*region)
						profile.game.region = region;

					if (*revision)
						profile.game.revision = revision;

					if (players)
						profile.game.players = players;

					if (*cic)
						profile.board.cic = cic;

					if (*pcb)
						profile.board.pcb = pcb;

					if (const dword size = properties.size())
					{
						profile.properties.resize( size );

						Profile::Properties::iterator b(profile.properties.begin());
						for (Properties::const_iterator a(properties.begin()), end(properties.end()); a != end; ++a, ++b)
						{
							b->name = a->name;
							b->value = a->value;
						}
					}
				}

				for (uint i=0; i < MAX_PERIPHERALS; ++i)
				{
					if (peripherals[i] != PERIPHERAL_UNSPECIFIED)
					{
						switch (peripherals[i])
						{
							case PERIPHERAL_STANDARD:

								profile.game.controllers[0] = Api::Input::PAD1;
								profile.game.controllers[1] = Api::Input::PAD2;
								break;

							case PERIPHERAL_FOURPLAYER:

								if (system == Profile::System::FAMICOM)
									profile.game.adapter = Api::Input::ADAPTER_FAMICOM;
								else
									profile.game.adapter = Api::Input::ADAPTER_NES;

								profile.game.controllers[2] = Api::Input::PAD3;
								profile.game.controllers[3] = Api::Input::PAD4;
								break;

							case PERIPHERAL_ZAPPER:

								if (system == Profile::System::VS_UNISYSTEM || system == Profile::System::VS_DUALSYSTEM)
								{
									profile.game.controllers[0] = Api::Input::ZAPPER;
									profile.game.controllers[1] = Api::Input::UNCONNECTED;
								}
								else
								{
									profile.game.controllers[1] = Api::Input::ZAPPER;
								}
								break;

							case PERIPHERAL_POWERPAD:
							case PERIPHERAL_FAMILYTRAINER:

								if (system == Profile::System::FAMICOM || peripherals[i] == PERIPHERAL_FAMILYTRAINER)
								{
									profile.game.controllers[1] = Api::Input::UNCONNECTED;
									profile.game.controllers[4] = Api::Input::FAMILYTRAINER;
								}
								else
								{
									profile.game.controllers[1] = Api::Input::POWERPAD;
								}
								break;

							case PERIPHERAL_ARKANOID:

								if (system == Profile::System::FAMICOM)
									profile.game.controllers[4] = Api::Input::PADDLE;
								else
									profile.game.controllers[1] = Api::Input::PADDLE;
								break;

							case PERIPHERAL_SUBORKEYBOARD:

								profile.game.controllers[4] = Api::Input::SUBORKEYBOARD;
								break;

							case PERIPHERAL_SUBORMOUSE:

								profile.game.controllers[1] = Api::Input::MOUSE;
								break;

							case PERIPHERAL_FAMILYKEYBOARD:

								profile.game.controllers[4] = Api::Input::FAMILYKEYBOARD;
								break;

							case PERIPHERAL_PARTYTAP:

								profile.game.controllers[1] = Api::Input::UNCONNECTED;
								profile.game.controllers[4] = Api::Input::PARTYTAP;
								break;

							case PERIPHERAL_CRAZYCLIMBER:

								profile.game.controllers[4] = Api::Input::CRAZYCLIMBER;
								break;

							case PERIPHERAL_EXCITINGBOXING:

								profile.game.controllers[4] = Api::Input::EXCITINGBOXING;
								break;

							case PERIPHERAL_BANDAIHYPERSHOT:

								profile.game.controllers[4] = Api::Input::BANDAIHYPERSHOT;
								break;

							case PERIPHERAL_KONAMIHYPERSHOT:

								profile.game.controllers[0] = Api::Input::UNCONNECTED;
								profile.game.controllers[1] = Api::Input::UNCONNECTED;
								profile.game.controllers[4] = Api::Input::KONAMIHYPERSHOT;
								break;

							case PERIPHERAL_POKKUNMOGURAA:

								profile.game.controllers[1] = Api::Input::UNCONNECTED;
								profile.game.controllers[4] = Api::Input::POKKUNMOGURAA;
								break;

							case PERIPHERAL_OEKAKIDSTABLET:

								profile.game.controllers[0] = Api::Input::UNCONNECTED;
								profile.game.controllers[1] = Api::Input::UNCONNECTED;
								profile.game.controllers[4] = Api::Input::OEKAKIDSTABLET;
								break;

							case PERIPHERAL_MAHJONG:

								profile.game.controllers[0] = Api::Input::UNCONNECTED;
								profile.game.controllers[1] = Api::Input::UNCONNECTED;
								profile.game.controllers[4] = Api::Input::MAHJONG;
								break;

							case PERIPHERAL_TOPRIDERBIKE:

								profile.game.controllers[0] = Api::Input::UNCONNECTED;
								profile.game.controllers[1] = Api::Input::UNCONNECTED;
								profile.game.controllers[4] = Api::Input::TOPRIDER;
								break;

							case PERIPHERAL_HORITRACK:

								profile.game.controllers[4] = Api::Input::HORITRACK;
								break;

							case PERIPHERAL_PACHINKO:

								profile.game.controllers[4] = Api::Input::PACHINKO;
								break;

							case PERIPHERAL_ROB:

								profile.game.controllers[1] = Api::Input::ROB;
								break;

							case PERIPHERAL_DOREMIKKO:

								profile.game.controllers[4] = Api::Input::DOREMIKKOKEYBOARD;
								break;

							case PERIPHERAL_POWERGLOVE:

								profile.game.controllers[0] = Api::Input::POWERGLOVE;
								break;

							case PERIPHERAL_TURBOFILE:

								profile.game.controllers[4] = Api::Input::TURBOFILE;
								break;

							case PERIPHERAL_BARCODEWORLD:

								profile.game.controllers[4] = Api::Input::BARCODEWORLD;
								break;
						}
					}
				}

				profile.multiRegion = multiRegion;

				profile.system.type = static_cast<Profile::System::Type>(system);
				profile.system.cpu = static_cast<Profile::System::Cpu>(cpu);
				profile.system.ppu = static_cast<Profile::System::Ppu>(ppu);

				if (*board)
					profile.board.type = board;

				if (mapper != Profile::Board::NO_MAPPER)
					profile.board.mapper = mapper;

				profile.board.solderPads = solderPads;

				for (uint j=0; j < 2; ++j)
				{
					if (full || (j ? profile.board.GetChr() == GetChrSize() : profile.board.GetPrg() == GetPrgSize()))
					{
						const Roms& src = (j ? chr : prg);
						Profile::Board::Roms& dst = (j ? profile.board.chr : profile.board.prg);

						dst.resize( src.size() );

						Profile::Board::Roms::iterator b(dst.begin());
						for (Roms::const_iterator a(src.begin()), end(src.end()); a != end; ++a, ++b)
						{
							b->size = a->size;

							if (full)
							{
								b->name = a->name;
								b->package = a->package;
								b->hash = a->hash;
							}

							b->pins.resize( a->pins.size() );

							Profile::Board::Pins::iterator d(b->pins.begin());
							for (Rom::Pins::const_iterator c(a->pins.begin()), end(a->pins.end()); c != end; ++c, ++d)
							{
								d->number = c->number;
								d->function = c->function;
							}
						}
					}
				}

				for (uint j=0; j < 2; ++j)
				{
					if (full || (j ? profile.board.GetVram() == GetVramSize() : profile.board.GetWram() == GetWramSize()))
					{
						const Rams& src = (j ? vram : wram);
						Profile::Board::Rams& dst = (j ? profile.board.vram : profile.board.wram);

						dst.resize( src.size() );

						Profile::Board::Rams::iterator b(dst.begin());
						for (Rams::const_iterator a(src.begin()), end(src.end()); a != end; ++a, ++b)
						{
							b->id = a->id;
							b->size = a->size;
							b->battery = a->battery;

							if (full)
								b->package = a->package;

							b->pins.resize( a->pins.size() );

							Profile::Board::Pins::iterator d(b->pins.begin());
							for (Ram::Pins::const_iterator c(a->pins.begin()), end(a->pins.end()); c != end; ++c, ++d)
							{
								d->number = c->number;
								d->function = c->function;
							}
						}
					}
				}

				profile.board.chips.resize( chips.size() );

				Profile::Board::Chips::iterator b(profile.board.chips.begin());
				for (Chips::const_iterator a(chips.begin()), end(chips.end()); a != end; ++a, ++b)
				{
					b->type = a->type;
					b->package = a->package;
					b->battery = a->battery;

					b->pins.resize( a->pins.size() );

					Profile::Board::Pins::iterator d(b->pins.begin());
					for (Chip::Pins::const_iterator c(a->pins.begin()), end(a->pins.end()); c != end; ++c, ++d)
					{
						d->number = c->number;
						d->function = c->function;
					}
				}
			}

		private:

			bool operator == (const Item& item) const
			{
				return
				(
					system == item.system &&
					mapper == item.mapper &&
					board == item.board &&
					solderPads == item.solderPads &&
					chips.size() == item.chips.size() &&
					cpu == item.cpu &&
					ppu == item.ppu &&
					GetVramSize() == item.GetVramSize() &&
					GetWramSize() == item.GetWramSize() &&
					HasVRamBattery() == item.HasVRamBattery() &&
					HasWRamBattery() == item.HasWRamBattery() &&
					HasChipBattery() == item.HasChipBattery() &&
					std::equal( chips.begin(), chips.end(), item.chips.begin() )
				);
			}

			bool Add(Item* const item)
			{
				item->multiRegion = this->multiRegion ||
				(
					(
						this->system == Profile::System::NES_PAL   ||
						this->system == Profile::System::NES_PAL_A ||
						this->system == Profile::System::NES_PAL_B ||
						this->system == Profile::System::DENDY
					)
						!=
					(
						item->system == Profile::System::NES_PAL   ||
						item->system == Profile::System::NES_PAL_A ||
						item->system == Profile::System::NES_PAL_B ||
						item->system == Profile::System::DENDY
					)
				);

				Item* it = this;

				for (;;)
				{
					if (*it == *item)
						return false;

					it->multiRegion = item->multiRegion;

					if (!it->sibling)
						break;

					it = it->sibling;
				}

				it->sibling = item;

				return true;
			}

			void Finalize(wcstring const lut) const
			{
				dump.by   << lut;
				dump.date << lut;

				title         << lut;
				altTitle      << lut;
				clss          << lut;
				subClss       << lut;
				catalog       << lut;
				developer     << lut;
				portDeveloper << lut;
				publisher     << lut;
				region        << lut;
				revision      << lut;
				pcb           << lut;
				board         << lut;
				cic           << lut;

				for (uint i=0; i < 2; ++i)
				{
					for (Roms::const_iterator j((i ? chr : prg).begin()), end((i ? chr : prg).end()); j != end; ++j)
					{
						j->name << lut;
						j->package << lut;

						for (Ic::Pins::const_iterator k(j->pins.begin()), end(j->pins.end()); k != end; ++k)
							k->function << lut;
					}
				}

				for (uint i=0; i < 2; ++i)
				{
					for (Rams::const_iterator j((i ? wram : vram).begin()), end((i ? wram : vram).end()); j != end; ++j)
						j->package << lut;
				}

				for (Chips::const_iterator i(chips.begin()), end(chips.end()); i != end; ++i)
				{
					i->type << lut;
					i->package << lut;

					for (Ic::Pins::const_iterator j(i->pins.begin()), end(i->pins.end()); j != end; ++j)
						j->function << lut;
				}

				for (Properties::const_iterator i(properties.begin()), end(properties.end()); i != end; ++i)
				{
					i->name << lut;
					i->value << lut;
				}

				if (sibling)
					sibling->Finalize( lut );
			}

		public:

			struct Less
			{
				bool operator () (const Item* a,const Item* b) const
				{
					return a->hash < b->hash;
				}

				bool operator () (const Item* a,const Hash& b) const
				{
					return a->hash < b;
				}

				bool operator () (const Hash& a,const Item* b) const
				{
					return a < b->hash;
				}
			};

			class Builder
			{
			public:

				~Builder();

				dword operator << (wcstring);
				void operator << (Item*);

			private:

				struct Less
				{
					bool operator () (wcstring a,wcstring b) const
					{
						return std::wcscmp( a, b ) < 0;
					}

					bool operator () (const Item* a,const Item* b) const
					{
						return a->hash < b->hash;
					}
				};

				typedef std::map<wcstring,dword,Less> StringMap;
				typedef std::set<Item*,Less> ItemMap;

				dword stringLength;
				StringMap stringMap;
				ItemMap itemMap;

			public:

				Builder()
				: stringLength(0)
				{
					(*this) << L"";
				}

				void Construct(Strings& strings,const Item**& itemsBegin,const Item**& itemsEnd)
				{
					NST_ASSERT( !strings.Size() && itemsBegin == NULL && itemsEnd == NULL );

					if (const dword size = itemMap.size())
					{
						strings.Resize( stringLength );
						wchar_t* const NST_RESTRICT sdst = strings.Begin();

						for (StringMap::const_iterator src(stringMap.begin()), end(stringMap.end()); src != end; ++src)
							std::wcscpy( sdst + src->second, src->first );

						const Item** dst = new const Item* [size];

						itemsBegin = dst;
						itemsEnd = dst + size;

						for (ItemMap::const_iterator src(itemMap.begin()), end(itemMap.end()); src != end; ++src, ++dst)
						{
							(*src)->Finalize( sdst );
							*dst = *src;
						}

						itemMap.clear();
					}
				}
			};
		};

		ImageDatabase::ImageDatabase()
		: enabled(true)
		{
			items.begin = NULL;
			items.end = NULL;
			items.hashing = HASHING_DETECT;
		}

		ImageDatabase::~ImageDatabase()
		{
			Unload();
		}

		ImageDatabase::Entry ImageDatabase::Search(const Hash& hash,const FavoredSystem favoredSystem) const
		{
			if (items.begin)
			{
				const Hash searchHash
				(
					( items.hashing & HASHING_SHA1 ) ? hash.GetSha1() : NULL,
					( items.hashing & HASHING_CRC  ) ? hash.GetCrc32() : 0UL
				);

				const Item** item = std::lower_bound( items.begin, items.end, searchHash, Item::Less() );

				if (item != items.end && (*item)->GetHash() == searchHash)
				{
					for (const Item* it = *item; it; it = it->GetNextSibling())
					{
						switch (it->GetSystem())
						{
							case Profile::System::NES_NTSC:

								if (favoredSystem == FAVORED_NES_NTSC)
									return it;

								break;

							case Profile::System::NES_PAL:
							case Profile::System::NES_PAL_A:
							case Profile::System::NES_PAL_B:

								if (favoredSystem == FAVORED_NES_PAL)
									return it;

								break;

							case Profile::System::FAMICOM:

								if (favoredSystem == FAVORED_FAMICOM)
									return it;

								break;

							case Profile::System::DENDY:

								if (favoredSystem == FAVORED_DENDY)
									return it;

								break;
							case Profile::System::VS_UNISYSTEM:
							case Profile::System::VS_DUALSYSTEM:
							case Profile::System::PLAYCHOICE_10:

								default:
								break;
						}
					}

					return *item;
				}
			}

			return NULL;
		}

		wcstring ImageDatabase::Entry::GetTitle() const
		{
			return item ? item->GetTitle() : L"";
		}

		wcstring ImageDatabase::Entry::GetPublisher() const
		{
			return item ? item->GetPublisher() : L"";
		}

		wcstring ImageDatabase::Entry::GetDeveloper() const
		{
			return item ? item->GetDeveloper() : L"";
		}

		wcstring ImageDatabase::Entry::GetRegion() const
		{
			return item ? item->GetRegion() : L"";
		}

		wcstring ImageDatabase::Entry::GetRevision() const
		{
			return item ? item->GetRevision() : L"";
		}

		wcstring ImageDatabase::Entry::GetPcb() const
		{
			return item ? item->GetPcb() : L"";
		}

		wcstring ImageDatabase::Entry::GetBoard() const
		{
			return item ? item->GetBoard() : L"";
		}

		wcstring ImageDatabase::Entry::GetCic() const
		{
			return item ? item->GetCic() : L"";
		}

		uint ImageDatabase::Entry::NumPlayers() const
		{
			return item ? item->NumPlayers() : 0;
		}

		uint ImageDatabase::Entry::GetMapper() const
		{
			return item ? item->GetMapper() : Profile::Board::NO_MAPPER;
		}

		uint ImageDatabase::Entry::GetSolderPads() const
		{
			return item ? item->GetSolderPads() : 0;
		}

		ImageDatabase::Profile::System::Type ImageDatabase::Entry::GetSystem() const
		{
			return item ? item->GetSystem() : Profile::System::NES_NTSC;
		}

		bool ImageDatabase::Entry::IsMultiRegion() const
		{
			return item && item->IsMultiRegion();
		}

		ImageDatabase::Profile::Dump::State ImageDatabase::Entry::GetDumpState() const
		{
			return item ? item->GetDumpState() : Profile::Dump::UNKNOWN;
		}

		const ImageDatabase::Hash* ImageDatabase::Entry::GetHash() const
		{
			return item ? &item->GetHash() : NULL;
		}

		dword ImageDatabase::Entry::GetPrg() const
		{
			return item ? item->GetPrgSize() : 0;
		}

		dword ImageDatabase::Entry::GetChr() const
		{
			return item ? item->GetChrSize() : 0;
		}

		dword ImageDatabase::Entry::GetWram() const
		{
			return item ? item->GetWramSize() : 0;
		}

		dword ImageDatabase::Entry::GetVram() const
		{
			return item ? item->GetVramSize() : 0;
		}

		bool ImageDatabase::Entry::HasBattery() const
		{
			return item && item->HasBattery();
		}

		void ImageDatabase::Entry::Fill(Profile& profile,bool full) const
		{
			if (item)
				item->Fill( profile, full );
		}

		Result ImageDatabase::Load(std::istream& baseStream,std::istream* overrideStream)
		{
			Unload();

			try
			{
				Xml baseXml, overrideXml;
				Item::Builder builder;

				for (uint multi=0; multi < (overrideStream ? 2 : 1); ++multi)
				{
					Xml& xml = (multi ? overrideXml : baseXml);

					try
					{
						if (!xml.Read( multi ? *overrideStream : baseStream ))
							return RESULT_ERR_CORRUPT_FILE;
					}
					catch (...)
					{
						throw RESULT_ERR_CORRUPT_FILE;
					}

					if (!xml.GetRoot().IsType( L"database" ))
						return RESULT_ERR_INVALID_FILE;

					if (const Xml::Attribute attribute=xml.GetRoot().GetAttribute( L"version" ))
					{
						wcstring const version = attribute.GetValue();

						if
						(
							(version[0] < L'1' || version[0] > L'9') ||
							(version[1] != L'.') ||
							(version[2] < L'0' || version[2] > L'9') ||
							(version[3] != L'\0')
						)
							throw RESULT_ERR_INVALID_FILE;
					}

					const bool strict = !xml.GetRoot().GetAttribute( L"conformance" ).IsValue( L"loose" );

					for (Xml::Node game(xml.GetRoot().GetFirstChild()); game.IsType( L"game" ); game=game.GetNextSibling())
					{
						byte peripherals[4] =
						{
							Item::PERIPHERAL_UNSPECIFIED,
							Item::PERIPHERAL_UNSPECIFIED,
							Item::PERIPHERAL_UNSPECIFIED,
							Item::PERIPHERAL_UNSPECIFIED
						};

						if (Xml::Node device=game.GetChild( L"peripherals" ))
						{
							uint i = 0;

							for (device=device.GetFirstChild(); i < 4 && device.IsType( L"device" ); device=device.GetNextSibling())
							{
								if (const Xml::Attribute attribute = device.GetAttribute( L"type" ))
								{
                                         if (attribute.IsValue( L"3dglasses"        )) peripherals[i++] = Item::PERIPHERAL_3DGLASSES;
									else if (attribute.IsValue( L"arkanoid"         )) peripherals[i++] = Item::PERIPHERAL_ARKANOID;
									else if (attribute.IsValue( L"bandaihypershot"  )) peripherals[i++] = Item::PERIPHERAL_BANDAIHYPERSHOT;
									else if (attribute.IsValue( L"barcodeworld"     )) peripherals[i++] = Item::PERIPHERAL_BARCODEWORLD;
									else if (attribute.IsValue( L"crazyclimber"     )) peripherals[i++] = Item::PERIPHERAL_CRAZYCLIMBER;
									else if (attribute.IsValue( L"doremikko"        )) peripherals[i++] = Item::PERIPHERAL_DOREMIKKO;
									else if (attribute.IsValue( L"excitingboxing"   )) peripherals[i++] = Item::PERIPHERAL_EXCITINGBOXING;
									else if (attribute.IsValue( L"familykeyboard"   )) peripherals[i++] = Item::PERIPHERAL_FAMILYKEYBOARD;
									else if (attribute.IsValue( L"familyfunfitness" )) peripherals[i++] = Item::PERIPHERAL_POWERPAD;
									else if (attribute.IsValue( L"familytrainer"    )) peripherals[i++] = Item::PERIPHERAL_FAMILYTRAINER;
									else if (attribute.IsValue( L"fourplayer"       )) peripherals[i++] = Item::PERIPHERAL_FOURPLAYER;
									else if (attribute.IsValue( L"horitrack"        )) peripherals[i++] = Item::PERIPHERAL_HORITRACK;
									else if (attribute.IsValue( L"konamihypershot"  )) peripherals[i++] = Item::PERIPHERAL_KONAMIHYPERSHOT;
									else if (attribute.IsValue( L"mahjong"          )) peripherals[i++] = Item::PERIPHERAL_MAHJONG;
									else if (attribute.IsValue( L"miraclepiano"     )) peripherals[i++] = Item::PERIPHERAL_MIRACLEPIANO;
									else if (attribute.IsValue( L"oekakidstablet"   )) peripherals[i++] = Item::PERIPHERAL_OEKAKIDSTABLET;
									else if (attribute.IsValue( L"pachinko"         )) peripherals[i++] = Item::PERIPHERAL_PACHINKO;
									else if (attribute.IsValue( L"partytap"         )) peripherals[i++] = Item::PERIPHERAL_PARTYTAP;
									else if (attribute.IsValue( L"pokkunmoguraa"    )) peripherals[i++] = Item::PERIPHERAL_POKKUNMOGURAA;
									else if (attribute.IsValue( L"powerglove"       )) peripherals[i++] = Item::PERIPHERAL_POWERGLOVE;
									else if (attribute.IsValue( L"powerpad"         )) peripherals[i++] = Item::PERIPHERAL_POWERPAD;
									else if (attribute.IsValue( L"rob"              )) peripherals[i++] = Item::PERIPHERAL_ROB;
									else if (attribute.IsValue( L"suborkeyboard"    )) peripherals[i++] = Item::PERIPHERAL_SUBORKEYBOARD;
									else if (attribute.IsValue( L"subormouse"       )) peripherals[i++] = Item::PERIPHERAL_SUBORMOUSE;
									else if (attribute.IsValue( L"topriderbike"     )) peripherals[i++] = Item::PERIPHERAL_TOPRIDERBIKE;
									else if (attribute.IsValue( L"turbofile"        )) peripherals[i++] = Item::PERIPHERAL_TURBOFILE;
									else if (attribute.IsValue( L"zapper"           )) peripherals[i++] = Item::PERIPHERAL_ZAPPER;
								}
							}
						}

						for (Xml::Node image(game.GetFirstChild()); image; image=image.GetNextSibling())
						{
							Profile::System::Type system = Profile::System::NES_NTSC;
							Profile::System::Cpu cpu = Profile::System::CPU_RP2A03;
							Profile::System::Ppu ppu = Profile::System::PPU_RP2C02;

							if (image.IsType( L"cartridge" ))
							{
								if (const Xml::Attribute attribute=image.GetAttribute( L"system" ))
								{
									if (attribute.IsValue( L"famicom" ))
									{
										system = Profile::System::FAMICOM;
									}
									else if (attribute.IsValue( L"nes-ntsc" ))
									{
										system = Profile::System::NES_NTSC;
									}
									else if (attribute.IsValue( L"nes-pal" ))
									{
										system = Profile::System::NES_PAL;
										cpu = Profile::System::CPU_RP2A07;
										ppu = Profile::System::PPU_RP2C07;
									}
									else if (attribute.IsValue( L"nes-pal-a" ))
									{
										system = Profile::System::NES_PAL_A;
										cpu = Profile::System::CPU_RP2A07;
										ppu = Profile::System::PPU_RP2C07;
									}
									else if (attribute.IsValue( L"nes-pal-b" ))
									{
										system = Profile::System::NES_PAL_B;
										cpu = Profile::System::CPU_RP2A07;
										ppu = Profile::System::PPU_RP2C07;
									}
									else if (attribute.IsValue( L"dendy" ))
									{
										system = Profile::System::DENDY;
										cpu = Profile::System::CPU_DENDY;
										ppu = Profile::System::PPU_DENDY;
									}
									else if (strict)
									{
										continue;
									}
								}
								else if (strict)
								{
									continue;
								}
							}
							else if (image.IsType( L"arcade" ))
							{
								ppu = Profile::System::PPU_RP2C03B;

								if (const Xml::Attribute attribute=image.GetAttribute( L"system" ))
								{
									if (attribute.IsValue( L"vs-unisystem" ))
									{
										system = Profile::System::VS_UNISYSTEM;
									}
									else if (attribute.IsValue( L"vs-dualsystem" ))
									{
										system = Profile::System::VS_DUALSYSTEM;
									}
									else if (attribute.IsValue( L"playchoice-10" ))
									{
										system = Profile::System::PLAYCHOICE_10;
									}
									else
									{
										continue;
									}
								}
								else
								{
									continue;
								}
							}
							else
							{
								continue;
							}

							if (system == Profile::System::VS_UNISYSTEM || system == Profile::System::VS_DUALSYSTEM)
							{
								if (const Xml::Attribute attribute=image.GetAttribute( L"ppu" ))
								{
                                         if (attribute.IsValue( L"rp2c03b"     )) ppu = Profile::System::PPU_RP2C03B;
									else if (attribute.IsValue( L"rp2c03g"     )) ppu = Profile::System::PPU_RP2C03G;
									else if (attribute.IsValue( L"rp2c04-0001" )) ppu = Profile::System::PPU_RP2C04_0001;
									else if (attribute.IsValue( L"rp2c04-0002" )) ppu = Profile::System::PPU_RP2C04_0002;
									else if (attribute.IsValue( L"rp2c04-0003" )) ppu = Profile::System::PPU_RP2C04_0003;
									else if (attribute.IsValue( L"rp2c04-0004" )) ppu = Profile::System::PPU_RP2C04_0004;
									else if (attribute.IsValue( L"rc2c03b"     )) ppu = Profile::System::PPU_RC2C03B;
									else if (attribute.IsValue( L"rc2c03c"     )) ppu = Profile::System::PPU_RC2C03C;
									else if (attribute.IsValue( L"rc2c05-01"   )) ppu = Profile::System::PPU_RC2C05_01;
									else if (attribute.IsValue( L"rc2c05-02"   )) ppu = Profile::System::PPU_RC2C05_02;
									else if (attribute.IsValue( L"rc2c05-03"   )) ppu = Profile::System::PPU_RC2C05_03;
									else if (attribute.IsValue( L"rc2c05-04"   )) ppu = Profile::System::PPU_RC2C05_04;
									else if (attribute.IsValue( L"rc2c05-05"   )) ppu = Profile::System::PPU_RC2C05_05;
								}
							}

							Profile::Dump::State dump = Profile::Dump::OK;

							if (const Xml::Attribute attribute=image.GetAttribute( L"dump" ))
							{
								if (attribute.IsValue( L"bad" ))
								{
									if (strict)
										continue;

									dump = Profile::Dump::BAD;
								}
								else if (attribute.IsValue( L"unknown" ))
								{
									if (strict)
										continue;

									dump = Profile::Dump::UNKNOWN;
								}
							}

							if (items.hashing == HASHING_DETECT)
							{
								if (*image.GetAttribute( L"sha1" ).GetValue())
									items.hashing |= HASHING_SHA1;

								if (*image.GetAttribute( L"crc" ).GetValue())
									items.hashing |= HASHING_CRC;
							}

							const Hash hash
							(
								( items.hashing & HASHING_SHA1 ) ? image.GetAttribute( L"sha1" ).GetValue() : L"",
								( items.hashing & HASHING_CRC  ) ? image.GetAttribute( L"crc"  ).GetValue() : L""
							);

							if (!hash)
								continue;

							if (const Xml::Node board=image.GetChild( L"board" ))
							{
								uint players = 0;

								if (const Xml::Attribute attribute=game.GetAttribute( L"players" ))
								{
									ulong value = attribute.GetUnsignedValue();

									if (value >= MIN_PLAYERS && value <= MAX_PLAYERS)
										players = value;
								}

								uint mapper = Profile::Board::NO_MAPPER;

								if (const Xml::Attribute attribute=board.GetAttribute( L"mapper" ))
								{
									ulong value = attribute.GetUnsignedValue();

									if (value <= MAX_MAPPER)
										mapper = value;
								}

								uint solderPads = 0;

								if (const Xml::Node pad=board.GetChild( L"pad" ))
								{
									solderPads =
									(
										(pad.GetAttribute( L"h" ).IsValue( L"1" ) ? Profile::Board::SOLDERPAD_H : 0U) |
										(pad.GetAttribute( L"v" ).IsValue( L"1" ) ? Profile::Board::SOLDERPAD_V : 0U)
									);
								}

								Item::Properties properties;

								if (Xml::Node node=image.GetChild( L"properties" ))
								{
									for (node=node.GetFirstChild(); node.IsType( L"property" ); node=node.GetNextSibling())
									{
										properties.push_back
										(
											Item::Property
											(
												builder << node.GetAttribute(L"name").GetValue(),
												builder << node.GetAttribute(L"value").GetValue()
											)
										);
									}
								}

								Item::Roms prg, chr;
								Item::Rams wram, vram;
								Item::Chips chips;

								for (Xml::Node node=board.GetFirstChild(); node; node=node.GetNextSibling())
								{
									dword size = 0;

									if (const Xml::Attribute attribute=node.GetAttribute( L"size" ))
									{
										wcstring end;
										const ulong value = attribute.GetUnsignedValue( end, 10 );

										if (end[0] == L'\0')
										{
											size = value;
										}
										else if ((end[0] == L'k' || end[0] == L'K') && end[1] == L'\0' && value <= MAX_CHIP_SIZE/SIZE_1K)
										{
											size = value * SIZE_1K;
										}
									}

									Item::Ic::Pins pins;

									for (Xml::Node child(node.GetFirstChild()); child; child=child.GetNextSibling())
									{
										if (child.IsType(L"pin"))
										{
											const ulong number = child.GetAttribute(L"number").GetUnsignedValue();
											wcstring const function = child.GetAttribute(L"function").GetValue();

											if (number >= MIN_IC_PINS && number <= MAX_IC_PINS && *function)
												pins.push_back( Item::Ic::Pin(number,builder << function) );
										}
									}

									bool first;

									if (true == (first=node.IsType( L"prg" )) || node.IsType( L"chr" ))
									{
										if (size >= MIN_CHIP_SIZE && size <= MAX_CHIP_SIZE)
										{
											(first ? prg : chr).push_back
											(
												Item::Rom
												(
													node.GetAttribute( L"id" ).GetUnsignedValue(),
													builder << node.GetAttribute( L"name" ).GetValue(),
													size,
													builder << node.GetAttribute( L"package" ).GetValue(),
													pins,
													Hash(node.GetAttribute( L"sha1" ).GetValue(),node.GetAttribute( L"crc" ).GetValue())
												)
											);
										}
									}
									else if (true == (first=node.IsType( L"wram" )) || node.IsType( L"vram" ))
									{
										if (size >= MIN_CHIP_SIZE && size <= MAX_CHIP_SIZE)
										{
											(first ? wram : vram).push_back
											(
												Item::Ram
												(
													node.GetAttribute( L"id" ).GetUnsignedValue(),
													size,
													node.GetAttribute( L"battery" ).IsValue( L"1" ),
													builder << node.GetAttribute( L"package" ).GetValue(),
													pins
												)
											);
										}
									}
									else if (node.IsType( L"chip" ))
									{
										chips.push_back
										(
											Item::Chip
											(
												builder << node.GetAttribute( L"type" ).GetValue(),
												node.GetAttribute( L"battery" ).IsValue( L"1" ),
												builder << node.GetAttribute( L"package" ).GetValue(),
												pins
											)
										);
									}
								}

								builder << new Item
								(
									hash,
									builder << image.GetAttribute( L"dumper" ).GetValue(),
									builder << image.GetAttribute( L"datedumped" ).GetValue(),
									dump,
									builder << game.GetAttribute( L"name" ).GetValue(),
									builder << game.GetAttribute( L"altname" ).GetValue(),
									builder << game.GetAttribute( L"class" ).GetValue(),
									builder << game.GetAttribute( L"subclass" ).GetValue(),
									builder << game.GetAttribute( L"catalog" ).GetValue(),
									builder << game.GetAttribute( L"publisher" ).GetValue(),
									builder << game.GetAttribute( L"developer" ).GetValue(),
									builder << game.GetAttribute( L"portdeveloper" ).GetValue(),
									builder << game.GetAttribute( L"region" ).GetValue(),
									properties,
									players,
									peripherals,
									system,
									cpu,
									ppu,
									builder << image.GetAttribute( L"revision" ).GetValue(),
									builder << board.GetAttribute( L"type" ).GetValue(),
									builder << board.GetAttribute( L"pcb" ).GetValue(),
									mapper,
									prg,
									chr,
									wram,
									vram,
									chips,
									builder << board.GetChild( L"cic" ).GetAttribute( L"type" ).GetValue(),
									solderPads
								);
							}
						}
					}
				}

				builder.Construct( strings, items.begin, items.end );
			}
			catch (Result result)
			{
				Unload( true );
				return result;
			}
			catch (const std::bad_alloc&)
			{
				Unload( true );
				return RESULT_ERR_OUT_OF_MEMORY;
			}
			catch (...)
			{
				Unload( true );
				return RESULT_ERR_GENERIC;
			}

			Log() << "Database: "
                  << static_cast<dword>(items.end - items.begin)
                  << " items imported from "
                  << (overrideStream ? "internal & external" : "internal")
                  <<  " DB" NST_LINEBREAK;

			return RESULT_OK;
		}

		void ImageDatabase::Unload(const bool error)
		{
			if (const Item** it=items.begin)
			{
				do
				{
					delete *it;
				}
				while (++it != items.end);

				delete [] items.begin;

				items.begin = NULL;
				items.end = NULL;
			}

			items.hashing = HASHING_DETECT;

			strings.Destroy();

			if (error)
				Log::Flush( "Database: error, aborting.." NST_LINEBREAK );
		}

		ImageDatabase::Item::Builder::~Builder()
		{
			for (ItemMap::const_iterator it(itemMap.begin()), end(itemMap.end()); it != end; ++it)
				delete *it;
		}

		dword ImageDatabase::Item::Builder::operator << (wcstring string)
		{
			const std::pair<StringMap::iterator,bool> entry
			(
				stringMap.insert( std::pair<wcstring,dword>(string,stringLength) )
			);

			if (entry.second)
				stringLength += std::wcslen(string) + 1;

			return entry.first->second;
		}

		void ImageDatabase::Item::Builder::operator << (Item* item)
		{
			std::pair<ItemMap::iterator,bool> entry;

			try
			{
				entry = itemMap.insert( item );
			}
			catch (...)
			{
				delete item;
				throw;
			}

			if (!entry.second && !(*entry.first)->Add(item))
				delete item;
		}

		ImageDatabase::Item::~Item()
		{
			if (const Item* item=sibling)
			{
				sibling = NULL;
				delete item;
			}
		}
	}
}
