/*
 * This source file is part of libRocket, the HTML/CSS Interface Middleware
 *
 * For the latest information, see http://www.librocket.com
 *
 * Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include "precompiled.h"
#include "../../Include/Rocket/Core/PropertySpecification.h"
#include "PropertyShorthandDefinition.h"
#include "../../Include/Rocket/Core/Log.h"
#include "../../Include/Rocket/Core/PropertyDefinition.h"
#include "../../Include/Rocket/Core/PropertyDictionary.h"
#include "../../Include/Rocket/Core/StyleSheetSpecification.h"

namespace Rocket {
namespace Core {

PropertySpecification::PropertySpecification()
{
}

PropertySpecification::~PropertySpecification()
{
	for (PropertyMap::iterator iterator = properties.begin(); iterator != properties.end(); ++iterator)
		delete (*iterator).second;

	for (ShorthandMap::iterator iterator = shorthands.begin(); iterator != shorthands.end(); ++iterator)
		delete (*iterator).second;
}

// Registers a property with a new definition.
PropertyDefinition& PropertySpecification::RegisterProperty(const String& property_name, const String& default_value, bool inherited, bool forces_layout)
{
	String lower_case_name = property_name.ToLower();

	// Create the property and validate the default value.
	PropertyDefinition* property_definition = new PropertyDefinition(default_value, inherited, forces_layout);

	// Delete any existing property.
	PropertyMap::iterator iterator = properties.find(lower_case_name);
	if (iterator != properties.end())
	{
		delete (*iterator).second;
	}
	else
	{
		property_names.insert(lower_case_name);
		if (inherited)
		{
			inherited_property_names.insert(lower_case_name);
		}
	}

	properties[lower_case_name] = property_definition;
	return *property_definition;
}

// Returns a property definition.
const PropertyDefinition* PropertySpecification::GetProperty(const String& property_name) const
{
	PropertyMap::const_iterator iterator = properties.find(property_name);
	if (iterator == properties.end())
		return NULL;

	return (*iterator).second;
}

// Fetches a list of the names of all registered property definitions.
const PropertyNameList& PropertySpecification::GetRegisteredProperties(void) const
{
	return property_names;
}

// Fetches a list of the names of all registered property definitions.
const PropertyNameList& PropertySpecification::GetRegisteredInheritedProperties(void) const
{
	return inherited_property_names;
}

// Registers a shorthand property definition.
bool PropertySpecification::RegisterShorthand(const String& shorthand_name, const String& property_names, ShorthandType type)
{
	StringList properties;
	StringUtilities::ExpandString(properties, property_names.ToLower());

	if (properties.empty())
		return false;

	String lower_case_name = shorthand_name.ToLower();

	// Construct the new shorthand definition and resolve its properties.
	PropertyShorthandDefinition* property_shorthand = new PropertyShorthandDefinition();
	for (size_t i = 0; i < properties.size(); i++)
	{
		const PropertyDefinition* property = GetProperty(properties[i]);
		if (property == NULL)
		{
			Log::Message(Log::LT_ERROR, "Shorthand property '%s' was registered with invalid property '%s'.", shorthand_name.CString(), properties[i].CString());
			delete property_shorthand;

			return false;
		}

		property_shorthand->properties.push_back(PropertyShorthandDefinition::PropertyDefinitionList::value_type(properties[i], property));
	}

	if (type == AUTO)
	{
		if (properties.size() == 4 &&
			properties[0].Find("-top") != String::npos &&
			properties[1].Find("-right") != String::npos &&
			properties[2].Find("-bottom") != String::npos &&
			properties[3].Find("-left") != String::npos)
			property_shorthand->type = BOX;
		else
			property_shorthand->type = FALL_THROUGH;
	}
	else
		property_shorthand->type = type;

	shorthands[lower_case_name] = property_shorthand;
	return true;
}

// Returns a shorthand definition.
const PropertyShorthandDefinition* PropertySpecification::GetShorthand(const String& shorthand_name) const
{
	ShorthandMap::const_iterator iterator = shorthands.find(shorthand_name);
	if (iterator == shorthands.end())
		return NULL;

	return (*iterator).second;
}

// Parses a property declaration, setting any parsed and validated properties on the given dictionary.
bool PropertySpecification::ParsePropertyDeclaration(PropertyDictionary& dictionary, const String& property_name, const String& property_value, const String& source_file, int source_line_number) const
{
	String lower_case_name = property_name.ToLower();

	// Attempt to parse as a single property.
	const PropertyDefinition* property_definition = GetProperty(lower_case_name);

	StringList property_values;
	if (!ParsePropertyValues(property_values, property_value, property_definition == NULL) || property_values.size() == 0)
		return false;

	if (property_definition != NULL)
	{
		Property new_property;
		new_property.source = source_file;
		new_property.source_line_number = source_line_number;
		if (property_definition->ParseValue(new_property, property_values[0]))
		{
			dictionary.SetProperty(lower_case_name, new_property);
			return true;
		}

		return false;
	}

	// Try as a shorthand.
	const PropertyShorthandDefinition* shorthand_definition = GetShorthand(lower_case_name);
	if (shorthand_definition != NULL)
	{
		// If this definition is a 'box'-style shorthand (x-top, x-right, x-bottom, x-left, etc) and there are fewer
		// than four values
		if (shorthand_definition->type == BOX &&
			property_values.size() < 4)
		{
			switch (property_values.size())
			{
				// Only one value is defined, so it is parsed onto all four sides.
				case 1:
				{
					for (int i = 0; i < 4; i++)
					{
						Property new_property;
						if (!shorthand_definition->properties[i].second->ParseValue(new_property, property_values[0]))
							return false;

						new_property.source = source_file;
						new_property.source_line_number = source_line_number;
						dictionary.SetProperty(shorthand_definition->properties[i].first, new_property);
					}
				}
				break;

				// Two values are defined, so the first one is parsed onto the top and bottom value, the second onto
				// the left and right.
				case 2:
				{
					// Parse the first value into the top and bottom properties.
					Property new_property;
					new_property.source = source_file;
					new_property.source_line_number = source_line_number;

					if (!shorthand_definition->properties[0].second->ParseValue(new_property, property_values[0]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[0].first, new_property);

					if (!shorthand_definition->properties[2].second->ParseValue(new_property, property_values[0]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[2].first, new_property);

					// Parse the second value into the left and right properties.
					if (!shorthand_definition->properties[1].second->ParseValue(new_property, property_values[1]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[1].first, new_property);

					if (!shorthand_definition->properties[3].second->ParseValue(new_property, property_values[1]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[3].first, new_property);
				}
				break;

				// Three values are defined, so the first is parsed into the top value, the second onto the left and
				// right, and the third onto the bottom.
				case 3:
				{
					// Parse the first value into the top property.
					Property new_property;
					new_property.source = source_file;
					new_property.source_line_number = source_line_number;

					if (!shorthand_definition->properties[0].second->ParseValue(new_property, property_values[0]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[0].first, new_property);

					// Parse the second value into the left and right properties.
					if (!shorthand_definition->properties[1].second->ParseValue(new_property, property_values[1]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[1].first, new_property);

					if (!shorthand_definition->properties[3].second->ParseValue(new_property, property_values[1]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[3].first, new_property);

					// Parse the third value into the bottom property.
					if (!shorthand_definition->properties[2].second->ParseValue(new_property, property_values[2]))
						return false;
					dictionary.SetProperty(shorthand_definition->properties[2].first, new_property);
				}
				break;

				default:	break;
			}
		}
		else
		{
			size_t value_index = 0;
			size_t property_index = 0;

			for (; value_index < property_values.size() && property_index < shorthand_definition->properties.size(); property_index++)
			{
				Property new_property;
				new_property.source = source_file;
				new_property.source_line_number = source_line_number;

				if (!shorthand_definition->properties[property_index].second->ParseValue(new_property, property_values[value_index]))
				{
					// This definition failed to parse; if we're falling through, try the next property. If there is no
					// next property, then abort!
					if (shorthand_definition->type == FALL_THROUGH)
					{
						if (property_index + 1 < shorthand_definition->properties.size())
							continue;
					}
					return false;
				}

				dictionary.SetProperty(shorthand_definition->properties[property_index].first, new_property);

				// Increment the value index, unless we're replicating the last value and we're up to the last value.
				if (shorthand_definition->type != REPLICATE ||
					value_index < property_values.size() - 1)
					value_index++;
			}
		}

		return true;
	}

	// Can't find it! Store as an unknown string value.
	Property new_property(property_value, Property::UNKNOWN);
	new_property.source = source_file;
	new_property.source_line_number = source_line_number;
	dictionary.SetProperty(lower_case_name, new_property);

	return true;
}

// Sets all undefined properties in the dictionary to their defaults.
void PropertySpecification::SetPropertyDefaults(PropertyDictionary& dictionary) const
{
	for (PropertyMap::const_iterator i = properties.begin(); i != properties.end(); ++i)
	{
		if (dictionary.GetProperty((*i).first) == NULL)
			dictionary.SetProperty((*i).first, *(*i).second->GetDefaultValue());
	}
}

bool PropertySpecification::ParsePropertyValues(StringList& values_list, const String& values, bool split_values) const
{
	String value;

	enum ParseState { VALUE, VALUE_PARENTHESIS, VALUE_QUOTE };
	ParseState state = VALUE;
	int open_parentheses = 0;

	size_t character_index = 0;
	char previous_character = 0;
	while (character_index < values.Length())
	{
		char character = values[character_index];
		character_index++;

		switch (state)
		{
			case VALUE:
			{
				if (character == ';')
				{
					value = StringUtilities::StripWhitespace(value);
					if (value.Length() > 0)
					{
						values_list.push_back(value);
						value.Clear();
					}
				}
				else if (StringUtilities::IsWhitespace(character))
				{
					if (split_values)
					{
						value = StringUtilities::StripWhitespace(value);
						if (value.Length() > 0)
						{
							values_list.push_back(value);
							value.Clear();
						}
					}
					else
						value.Append(character);
				}
				else if (character == '"')
				{
					if (split_values)
					{
						value = StringUtilities::StripWhitespace(value);
						if (value.Length() > 0)
						{
							values_list.push_back(value);
							value.Clear();
						}
						state = VALUE_QUOTE;
					}
					else
					{
						value.Append(' ');
						state = VALUE_QUOTE;
					}
				}
				else if (character == '(')
				{
					open_parentheses = 1;
					value.Append(character);
					state = VALUE_PARENTHESIS;
				}
				else
				{
					value.Append(character);
				}
			}
			break;

			case VALUE_PARENTHESIS:
			{
				if (previous_character == '/')
				{
					if (character == ')' || character == '(')
						value.Append(character);
					else
					{
						value.Append('/');
						value.Append(character);
					}
				}
				else
				{
					if (character == '(')
					{
						open_parentheses++;
						value.Append(character);
					}
					else if (character == ')')
					{
						open_parentheses--;
						value.Append(character);
						if (open_parentheses == 0)
							state = VALUE;
					}
					else if (character != '/')
					{
						value.Append(character);
					}
				}
			}
			break;

			case VALUE_QUOTE:
			{
				if (previous_character == '/')
				{
					if (character == '"')
						value.Append(character);
					else
					{
						value.Append('/');
						value.Append(character);
					}
				}
				else
				{
					if (character == '"')
					{
						if (split_values)
						{
							value = StringUtilities::StripWhitespace(value);
							if (value.Length() > 0)
							{
								values_list.push_back(value);
								value.Clear();
							}
						}
						else
							value.Append(' ');
						state = VALUE;
					}
					else if (character != '/')
					{
						value.Append(character);
					}
				}
			}
		}

		previous_character = character;
	}

	if (state == VALUE)
	{
		value = StringUtilities::StripWhitespace(value);
		if (value.Length() > 0)
			values_list.push_back(value);
	}

	return true;
}

}
}
