// For licensing and usage information, read docs/release/winui_license.txt
// MASTER
//****************************************************************************

#ifndef WINUI_GAMEOPTS_H
#define WINUI_GAMEOPTS_H

class winui_game_options
{
	uint32_t m_total;
	uint32_t m_cache;
	uint32_t m_version;
	bool m_rebuild;

	struct driver_options
	{
		uint32_t game_number;
		int rom;
		int sample;
		uint32_t cache_lower;
		uint32_t cache_upper;
		uint32_t play_count;
		uint32_t play_time;
	};

	std::vector<driver_options> m_list;

	// convert audit cache - normally only 1 digit, although we can do 2. If the input is -1, it is treated as invalid and -1 is returned.
	int convert_to_int(const char* inp)
	{
		if (!inp)
			return -1;
		int c = inp[0];
		if (c < 0x30 || c > 0x39)
			return -1;
		int oup = c - 0x30;
		c = inp[1];
		if (c < 0x30 || c > 0x39)
			return oup;
		else
			return oup * 10 + (c - 0x30);
	}

	// convert all other numbers, up to end-of-string/invalid-character. If number is too large, return 0.
	uint32_t convert_to_uint(const char* inp)
	{
		if (!inp)
			return 0;
		uint32_t oup = 0;
		for (int i = 0; i < 11; i++)
		{
			int c = inp[i];
			if (c >= 0x30 && c <= 0x39)
				oup = oup * 10 + (c - 0x30);
			else
				return oup;
		}
		return 0; // numeric overflow
	}

	// true = recache needed
	bool create_index(std::ifstream &fp)
	{
		// does file exist?
		if (!fp.good())
			return true;

		// check first line is ok
		string file_line;
		std::getline(fp, file_line);
		if (file_line.empty())
			return true;

		char s1[file_line.length()+1];
		strcpy(s1, file_line.c_str());

		// check that totals match
		const char* s2 = strtok(s1, ",");
		if (!s2)
			return true;  // file is corrupt

		uint32_t total = convert_to_uint(s2);
		if (total == m_total)
			m_rebuild = false; // everything is fine so far

		// check that versions match
		s2 = strtok(NULL, ",");
		if (!s2)
			return true;  // file is corrupt or old version

		uint32_t version = convert_to_uint(s2);
		if (version != m_version)
			return true; // file is old version, complete rebuild necessary

		// all good, read file to memory. If rebuild needed, it will overwrite cache, but we need the rest.
		int index; // must be int for validity check below
		bool is_ready = false;
		std::getline(fp, file_line);
		while (fp.good())
		{
			if (is_ready)
			{
				char s[file_line.length()+1];
				strcpy(s, file_line.c_str());

				const char* name = strtok(s, "\t");  // get driver name
				index = driver_list::find(name);
				if (index > -1)
				{
					char* data = strtok(NULL, ",");    // get next part (old game number, ignore)
					m_list[index].game_number = index;  // get new game number
					data = strtok(NULL, ",");    // get next part
					if (data)
						m_list[index].rom = convert_to_int(data);
					data = strtok(NULL, ",");    // get next part
					if (data)
						m_list[index].sample = convert_to_int(data);
					data = strtok(NULL, ",");    // get next part
					if (data)
						m_list[index].cache_lower = convert_to_uint(data);
					data = strtok(NULL, ",");    // get next part
					if (data)
						m_list[index].cache_upper = convert_to_uint(data);
					data = strtok(NULL, ",");    // get next part
					if (data)
						m_list[index].play_count = convert_to_uint(data);
					data = strtok(NULL, ",");    // get next part
					if (data)
						m_list[index].play_time = convert_to_uint(data);
				}
			}
			else
			{
				if (file_line == "$start")
					is_ready = true;
			}

			std::getline(fp, file_line);
		}

		fp.close();
		return m_rebuild;
	}

public:
	// construction - runs before main
	winui_game_options()
	{
		m_version = 1;
		m_rebuild = true;
		m_total = driver_list::total();
		m_list.resize(m_total);
		std::fill(m_list.begin(), m_list.end(), driver_options{0, -1, -1, 0, 0, 0, 0});
	}

	int rom(uint32_t index)
	{
		if (index < m_total)
			return m_list[index].rom;
		else
			return -1;
	}

	void rom(uint32_t index, int val)
	{
		if (index < m_total)
			m_list[index].rom = val;
	}

	int sample(uint32_t index)
	{
		if (index < m_total)
			return m_list[index].sample;
		else
			return -1;
	}

	void sample(uint32_t index, int val)
	{
		if (index < m_total)
			m_list[index].sample = val;
	}

	uint32_t cache_lower(uint32_t index)
	{
		if (index < m_total)
			return m_list[index].cache_lower;
		else
			return 0;
	}

	uint32_t cache_upper(uint32_t index)
	{
		if (index < m_total)
			return m_list[index].cache_upper;
		else
			return 0;
	}

	void cache_upper(uint32_t index, uint32_t val)
	{
		if (index < m_total)
			m_list[index].cache_upper = val;
	}

	uint32_t play_count(uint32_t index)
	{
		if (index < m_total)
			return m_list[index].play_count;
		else
			return 0;
	}

	void play_count(uint32_t index, int val)
	{
		if (index < m_total)
			m_list[index].play_count = val;
	}

	uint32_t play_time(uint32_t index)
	{
		if (index < m_total)
			return m_list[index].play_time;
		else
			return 0;
	}

	void play_time(uint32_t index, int val)
	{
		if (index < m_total)
			m_list[index].play_time = val;
	}

	bool rebuild()
	{
		return m_rebuild;
	}

	void force_rebuild()
	{
		m_rebuild = true;
	}

	void load_file(const char *filename)
	{
		std::ifstream infile (filename);
		if (create_index(infile))
		{
			// rebuild the cache
			device_t::feature_type ft;
			const game_driver *drv = 0;
			windows_options dummy;
			ui_options ui_opts;
			printf("game_opts.h::load_file : Rebuilding cache\n");fflush(stdout);
			for (uint32_t i = 0; i < m_total; i++)
			{
				uint32_t t = 0;
				// BITS 0,1 = arcade, non-arcade, unused, unused
				drv = &driver_list::driver(i);
				machine_config config(*drv, dummy);
				ui::machine_static_info const info(ui_opts, config);
				if ((info.machine_flags() & machine_flags::MASK_TYPE) != machine_flags::TYPE_ARCADE)
					t = 1;
				m_cache = t;
				// BIT 2 = SWAP_XY
				t = (drv->flags & ORIENTATION_SWAP_XY) ? 0x0004 : 0;
				m_cache |= t;
				// BIT 6 = NOT_WORKING
				t = (info.machine_flags() & machine_flags::NOT_WORKING) ? 0x0040 : 0;
				m_cache |= t;
				// BIT 7 = SUPPORTS_SAVE
				t = (info.machine_flags() & machine_flags::SUPPORTS_SAVE) ? 0: 0x0080;
				m_cache |= t;
				// BIT 8 = NO_COCKTAIL
				t = (info.machine_flags() & machine_flags::NO_COCKTAIL) ? 0x0100 : 0;
				m_cache |= t;
				// BIT 9 = IS_BIOS_ROOT
				t = (info.machine_flags() & machine_flags::IS_BIOS_ROOT) ? 0x0200 : 0;
				m_cache |= t;
				// BIT 10 = REQUIRES_ARTWORK
				t = (info.machine_flags() & machine_flags::REQUIRES_ARTWORK) ? 0x0400 : 0;
				m_cache |= t;
				// BIT 11 = CLICKABLE_ARTWORK
				t = (info.machine_flags() & machine_flags::CLICKABLE_ARTWORK) ? 0x0800 : 0;
				m_cache |= t;
				// BIT 12 = UNOFFICIAL
				t = (info.machine_flags() & machine_flags::UNOFFICIAL) ? 0x1000 : 0;
				m_cache |= t;
				// BIT 13 = NO_SOUND_HW
				t = (info.machine_flags() & machine_flags::NO_SOUND_HW) ? 0x2000 : 0;
				m_cache |= t;
				// BIT 14 = MECHANICAL
				t = (info.machine_flags() & machine_flags::MECHANICAL) ? 0x4000 : 0;
				m_cache |= t;
				// BIT 15 = IS_INCOMPLETE
				t = (info.machine_flags() & machine_flags::IS_INCOMPLETE) ? 0x8000 : 0;
				m_cache |= t;

				ft = info.imperfect_features();
				// BIT 16 = IMPERFECT_SOUND
				t = (ft & device_t::feature::SOUND) ? 0x10000 : 0;
				m_cache |= t;
				// BIT 18 = IMPERFECT_GRAPHICS
				t = (ft & device_t::feature::GRAPHICS) ? 0x40000 : 0;
				m_cache |= t;
				// BIT 20 = IMPERFECT_COLOUR
				t = (ft & device_t::feature::PALETTE) ? 0x100000 : 0;
				m_cache |= t;
				// BIT 22 = PROTECTION
				t = (ft & device_t::feature::PROTECTION) ? 0x400000 : 0;
				m_cache |= t;
				// BIT 23 = IMPERFECT_CONTROLS
				t = (ft & device_t::feature::CONTROLS) ? 0x800000 : 0;
				m_cache |= t;

				ft = info.unemulated_features();
				// BIT 17 = NO_SOUND
				t = (ft & device_t::feature::SOUND) ? 0x20000 : 0;
				m_cache |= t;
				// BIT 19 = NO_GRAPHICS
				t = (ft & device_t::feature::GRAPHICS) ? 0x80000 : 0;
				m_cache |= t;
				// BIT 21 = NO_COLOUR
				t = (ft & device_t::feature::PALETTE) ? 0x200000 : 0;
				m_cache |= t;

				m_list[i].cache_lower = m_cache;
				m_list[i].cache_upper = 0;
			}
			printf("game_opts.h::load_file : Finished Rebuilding cache\n");fflush(stdout);
		}
	}

	void save_file(const char *filename)
	{
		string inistring = std::to_string(m_total).append(",").append(std::to_string(m_version)).append("\n");
		inistring.append("YOU CAN SAFELY DELETE THIS FILE TO RESET THE GAME STATS.\n\n$start\n");

		for (uint32_t i = 0; i < m_total; i++)
		{
			// 1:Game number(for debugging only), 2:Rom, 3:Sample, 4:Cache(New), 5:Cache(Legacy) 6:Play Count, 7:Play Time
			inistring.append(driver_list::driver(i).name).append("\t");
			inistring.append(std::to_string(m_list[i].game_number)).append(",");
			inistring.append(std::to_string(m_list[i].rom)).append(",");
			inistring.append(std::to_string(m_list[i].sample)).append(",");
			inistring.append(std::to_string(m_list[i].cache_lower)).append(",");
			inistring.append(std::to_string(m_list[i].cache_upper)).append(",");
			inistring.append(std::to_string(m_list[i].play_count)).append(",");
			inistring.append(std::to_string(m_list[i].play_time)).append("\n");
		}

		std::ofstream outfile (filename, std::ios::out | std::ios::trunc);
		size_t size = inistring.size();
		char t1[size+1];
		strcpy(t1, inistring.c_str());
		outfile.write(t1, size);
		outfile.close();
		return;
	}
};

#endif //  __GAME_OPTS_H__

