// license:BSD-3-Clause
// copyright-holders:Miodrag Milanovic
/***************************************************************************

    pluginopts.cpp

    Plugin options manager.

***************************************************************************/

#include "emu.h"
#include "pluginopts.h"
#include "options.h"

#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/istreamwrapper.h>

#include <fstream>


//**************************************************************************
//  PLUGIN OPTIONS
//**************************************************************************

//-------------------------------------------------
//  plugin_options - constructor
//-------------------------------------------------

plugin_options::plugin_options()
{
}


//-------------------------------------------------
//  scan_directory
//-------------------------------------------------

void plugin_options::scan_directory(const std::string &path, bool recursive)
{
	// first try to open as a directory
	osd::directory::ptr directory = osd::directory::open(path);
	if (directory)
	{
		// iterate over all files in the directory
		for (const osd::directory::entry *entry = directory->read(); entry != nullptr; entry = directory->read())
		{
			if (entry->type == osd::directory::entry::entry_type::FILE && !strcmp(entry->name, "plugin.json"))
			{
				std::string curfile = std::string(path).append(PATH_SEPARATOR).append(entry->name);
				load_plugin(curfile);
			}
			else if (entry->type == osd::directory::entry::entry_type::DIR)
			{
				if (recursive && strcmp(entry->name, ".") && strcmp(entry->name, ".."))
					scan_directory(path + PATH_SEPARATOR + entry->name, recursive);
			}
		}
	}
}


//-------------------------------------------------
//  load_plugin
//-------------------------------------------------

bool plugin_options::load_plugin(const std::string &path)
{
	std::ifstream ifs(path);
	rapidjson::IStreamWrapper isw(ifs);
	rapidjson::Document document;
	document.ParseStream<0>(isw);

	if (document.HasParseError())
	{
		const std::string error(GetParseError_En(document.GetParseError()));
		osd_printf_error("Unable to parse plugin definition file %s. Errors returned:\n%s", path, error);
		return false;
	}

	if (!document["plugin"].IsObject())
	{
		osd_printf_error("Bad plugin definition file %s:\n", path);
		return false;
	}

	size_t last_path_sep = path.find_last_of(PATH_SEPARATOR[0]);
	std::string dir = last_path_sep != std::string::npos
		? path.substr(0, last_path_sep)
		: ".";

	plugin p;
	p.m_name        = document["plugin"]["name"].GetString();
	p.m_description = document["plugin"]["description"].GetString();
	p.m_type        = document["plugin"]["type"].GetString();
	p.m_directory   = std::move(dir);
	p.m_start       = false;
	if (document["plugin"].HasMember("start") && (std::string(document["plugin"]["start"].GetString()) == "true"))
		p.m_start = true;

	m_plugins.push_back(std::move(p));
	return true;
}


//-------------------------------------------------
//  find
//-------------------------------------------------

plugin_options::plugin *plugin_options::find(const std::string &name)
{
	auto iter = std::find_if(
		m_plugins.begin(),
		m_plugins.end(),
		[&name](const plugin &p) { return name == p.m_name; });

	return iter != m_plugins.end()
		? &*iter
		: nullptr;
}


//-------------------------------------------------
//  create_core_options
//-------------------------------------------------

static core_options create_core_options(const plugin_options &plugin_opts)
{
	// we're sort of abusing core_options to just get INI file parsing, so we'll build a
	// core_options structure for the sole purpose of parsing an INI file, and then reflect
	// the data back
	static const options_entry s_option_entries[] =
	{
		{ nullptr, nullptr, OPTION_HEADER, "PLUGINS OPTIONS" },
		{ nullptr }
	};

	core_options opts;
	opts.add_entries(s_option_entries);

	// create an entry for each option
	for (const plugin_options::plugin &p : plugin_opts.plugins())
	{
		opts.add_entry(
			{ p.m_name },
			nullptr,
			core_options::option_type::BOOLEAN,
			p.m_start ? "1" : "0");
	}

	return opts;
}


//-------------------------------------------------
//  parse_ini_file
//-------------------------------------------------

void plugin_options::parse_ini_file(util::core_file &inifile)
{
	core_options opts = create_core_options(*this);

	// parse the INI file
	opts.parse_ini_file(inifile, OPTION_PRIORITY_NORMAL, true, true);

	// and reflect these options back
	for (plugin &p : m_plugins)
		p.m_start = opts.bool_value(p.m_name);
}


//-------------------------------------------------
//  output_ini
//-------------------------------------------------

std::string plugin_options::output_ini() const
{
	core_options opts = create_core_options(*this);
	return opts.output_ini();
}
