/*
 *  Copyright (C) 2008-2019  Anders Gavare.  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright  
 *     notice, this list of conditions and the following disclaimer in the 
 *     documentation and/or other materials provided with the distribution.
 *  3. The name of the author may not be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 *  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE   
 *  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 *  OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *  LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 *  OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 *  SUCH DAMAGE.
 */

#include <assert.h>
#include <string.h>

#include "ComponentFactory.h"
#include "GXemul.h"
#include "StringHelper.h"


struct ComponentListEntry {
	const char* componentName;
	refcount_ptr<Component> (*Create)(const ComponentCreateArgs& args);
	string (*GetAttribute)(const string& attributeName);
};

// Static list of components:
// (Note: components*.h is autogenerated by the configure script.)
#include "../../components_h.h"
static struct ComponentListEntry componentList[] = {
#include "../../components.h"
	{ NULL, NULL, NULL }
};

// List of components that are added dynamically at runtime:
static vector<ComponentListEntry>* componentListRunTime = NULL;


bool ComponentFactory::RegisterComponentClass(const char* name,
	refcount_ptr<Component> (*createFunc)(const ComponentCreateArgs& args),
	string (*getAttributeFunc)(const string& attributeName))
{
	// Attempt to create a component using this name first.
	// Don't add the new component class if the name is already in use.
	refcount_ptr<Component> component = CreateComponent(name);
	if (!component.IsNULL()) {
		assert(false);
		return false;
	}

	if (componentListRunTime == NULL)
		componentListRunTime = new vector<ComponentListEntry>();

	ComponentListEntry cle;
	cle.componentName = name;
	cle.Create = createFunc;
	cle.GetAttribute = getAttributeFunc;

	componentListRunTime->push_back(cle);

	return true;
}


void ComponentFactory::UnregisterAllComponentClasses()
{
	delete componentListRunTime;
	componentListRunTime = NULL;
}


refcount_ptr<Component> ComponentFactory::CreateComponent(
	const string& componentNameAndOptionalArgs, GXemul* gxemul)
{
	ComponentCreateArgs args;
	args.gxemul = gxemul;

	string componentName = componentNameAndOptionalArgs;
	size_t p = componentName.find('(');
	if (p != string::npos && p > 0) {
		componentName = componentName.substr(0, p);

		string argstring = componentNameAndOptionalArgs.substr(p+1);

		// Arguments don't end with a )? Then something's wrong.
		if (argstring[argstring.length()-1] != ')') {
			if (gxemul != NULL)
				gxemul->GetUI()->ShowDebugMessage("Unmatched parenthesis?\n");

			return NULL;
		}

		argstring = argstring.substr(0, argstring.length()-1);
		
		// argstring is now e.g. "cpu=R4400,ncpus=4"

		// Split into assignments:
		vector<string> assignments = StringHelper::SplitStringIntoVector(argstring, ',');

		// Split each assignment into key and value:
		for (size_t i=0; i<assignments.size(); ++i) {
			vector<string> keyAndValue = StringHelper::SplitStringIntoVector(assignments[i], '=');
			if (keyAndValue.size() != 2) {
				if (gxemul != NULL)
					gxemul->GetUI()->ShowDebugMessage("Not a key=value pair: " + assignments[i]);

				return NULL;
			}

			args.componentCreationSettings[keyAndValue[0]] = keyAndValue[1];
		}
	}

	// Find the className in the list of available components, and
	// call the corresponding create function, if found:
	size_t i = 0;
	while (componentList[i].componentName != NULL) {
		if (componentName == componentList[i].componentName)
			return componentList[i].Create(args);

		++ i;
	}

	for (i=0; componentListRunTime != NULL && i<componentListRunTime->size(); ++i) {
		if (componentName == (*componentListRunTime)[i].componentName)
			return (*componentListRunTime)[i].Create(args);
	}

	if (gxemul != NULL)
		gxemul->GetUI()->ShowDebugMessage("Unknown component: " + componentNameAndOptionalArgs + "\n");

	return NULL;
}


bool ComponentFactory::GetCreationArgOverrides(ComponentCreationSettings& settings, const ComponentCreateArgs& createArgs)
{
	// A copy of the default args (for helpful debug output):
	ComponentCreationSettings defaultSettings = settings;

	// Merge in the overrides:
	for (ComponentCreationSettings::const_iterator it = createArgs.componentCreationSettings.begin();
	    it != createArgs.componentCreationSettings.end(); ++it) {
		const string& key = it->first;
		const string& value = it->second;
		
		if (settings.find(key) == settings.end()) {
			if (createArgs.gxemul != NULL) {
				stringstream ss;
				ss << "Unknown setting '" << key << "'. "
				    "Available settings (with default values) are:\n";
				for (ComponentCreationSettings::const_iterator it2 = defaultSettings.begin();
				    it2 != defaultSettings.end(); ++it2)
					ss << "  " << it2->first << " = " << it2->second << "\n";

				createArgs.gxemul->GetUI()->ShowDebugMessage(ss.str());
			}

			return false;
		}

		settings[key] = value;
	}
	
	return true;
}


string ComponentFactory::GetAttribute(const string& name,
	const string& attributeName)
{
	size_t i = 0;
	while (componentList[i].componentName != NULL) {
		if (name == componentList[i].componentName)
			return componentList[i].GetAttribute(attributeName);

		++ i;
	}
	
	for (i=0; componentListRunTime!=NULL && i<componentListRunTime->size(); ++i) {
		if (name == (*componentListRunTime)[i].componentName)
			return (*componentListRunTime)[i].GetAttribute(
			    attributeName);
	}
	
	return "";
}


bool ComponentFactory::HasAttribute(const string& name,
	const string& attributeName)
{
	return !GetAttribute(name, attributeName).empty();
}


vector<string> ComponentFactory::GetAllComponentNames(bool onlyTemplates)
{
	vector<string> result;

	size_t i = 0;
	while (componentList[i].componentName != NULL) {
		if ((!onlyTemplates ||
		    componentList[i].GetAttribute("template") == "yes"))
			result.push_back(componentList[i].componentName);
		++ i;
	}

	for (i=0; componentListRunTime!=NULL && i<componentListRunTime->size(); ++i) {
		if ((!onlyTemplates ||
		    (*componentListRunTime)[i].GetAttribute("template") == "yes"))
			result.push_back((*componentListRunTime)[i].componentName);
	}

	return result;
}


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


#ifdef WITHUNITTESTS

static void Test_ComponentFactory_Nonexistant()
{
	refcount_ptr<Component> component =
	    ComponentFactory::CreateComponent("NoNeXisT");
	UnitTest::Assert("nonexistant component should not be created",
	    component.IsNULL() == true);
}

static void Test_ComponentFactory_SimpleDummy()
{
	refcount_ptr<Component> component =
	    ComponentFactory::CreateComponent("dummy");
	UnitTest::Assert("dummy component should be possible to create",
	    component.IsNULL() == false);

	UnitTest::Assert("the class name should be 'dummy'",
	    component->GetClassName(), "dummy");
	UnitTest::Assert("the dummy component should have children",
	    component->GetChildren().size(), 0);
}

static void Test_ComponentFactory_FromTemplate()
{
	refcount_ptr<Component> component =
	    ComponentFactory::CreateComponent("testm88k");
	UnitTest::Assert("component should be possible to create from template",
	    component.IsNULL() == false);

	UnitTest::Assert("the class name should be 'machine'",
	    component->GetClassName(), "machine");
	UnitTest::Assert("the component should have children",
	    component->GetChildren().size() > 0);

	refcount_ptr<Component> clone = component->Clone();
	UnitTest::Assert("cloning should have been possible",
	    clone.IsNULL() == false);

	UnitTest::Assert("clone: the class name should still be 'machine'",
	    clone->GetClassName(), "machine");
	UnitTest::Assert("clone: the clone should also have children",
	    clone->GetChildren().size() > 0);
}

static void Test_ComponentFactory_HasAttribute()
{
	UnitTest::Assert("nonexistantattr should not exist",
	    !ComponentFactory::HasAttribute("testm88k", "nonexistantattr"));

	UnitTest::Assert("testm88k is a machine",
	    ComponentFactory::HasAttribute("testm88k", "machine"));

	UnitTest::Assert("testm88k has a description",
	    ComponentFactory::HasAttribute("testm88k", "description"));
}

UNITTESTS(ComponentFactory)
{
	UNITTEST(Test_ComponentFactory_Nonexistant);
	UNITTEST(Test_ComponentFactory_SimpleDummy);
	UNITTEST(Test_ComponentFactory_FromTemplate);
	UNITTEST(Test_ComponentFactory_HasAttribute);
}

#endif
