/**
 *  wWidgets - Lightweight UI Toolkit.
 *  Copyright (C) 2009-2011 Evgeny Andreeshchev <eugene.andreeshchev@gmail.com>
 *                          Alexander Kotliar <alexander.kotliar@gmail.com>
 * 
 *  This code is distributed under the MIT License:
 *                          http://www.opensource.org/licenses/MIT
 */

#include "stdafx.h"
#include "Serialization.h"
#include "Serialization/Enum.h"
#include "Serialization/Callback.h"
#include "PropertyTreeModel.h"
#include "PropertyIArchive.h"
#include "PropertyRowBool.h"
#include "PropertyRowString.h"
#include "PropertyRowNumber.h"
#include "PropertyRowPointer.h"
#include "PropertyRowObject.h"
#include "Unicode.h"

using Serialization::TypeID;

PropertyIArchive::PropertyIArchive(PropertyTreeModel* model, PropertyRow* root)
: IArchive(INPUT | EDIT)
, model_(model)
, currentNode_(0)
, lastNode_(0)
, root_(root)
{
	stack_.push_back(Level());

	if (!root_)
		root_ = model_->root();
	else
		currentNode_ = root;
}

bool PropertyIArchive::operator()(Serialization::IString& value, const char* name, const char* label)
{
	if(openRow(name, label, "string")){
		if(PropertyRowString* row = static_cast<PropertyRowString*>(currentNode_))
 			value.set(fromWideChar(row->value().c_str()).c_str());
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(Serialization::IWString& value, const char* name, const char* label)
{
	if(openRow(name, label, "string")){
		if(PropertyRowString* row = static_cast<PropertyRowString*>(currentNode_)) {
			value.set(row->value().c_str());
		}
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(bool& value, const char* name, const char* label)
{
	if(openRow(name, label, "bool")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(char& value, const char* name, const char* label)
{
	if(openRow(name, label, "char")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

// Signed types
bool PropertyIArchive::operator()(int8& value, const char* name, const char* label)
{
	if(openRow(name, label, "int8")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(int16& value, const char* name, const char* label)
{
	if(openRow(name, label, "int16")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(int32& value, const char* name, const char* label)
{
	if(openRow(name, label, "int32")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(int64& value, const char* name, const char* label)
{
	if(openRow(name, label, "int64")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

// Unsigned types
bool PropertyIArchive::operator()(uint8& value, const char* name, const char* label)
{
	if(openRow(name, label, "uint8")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(uint16& value, const char* name, const char* label)
{
	if(openRow(name, label, "uint16")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(uint32& value, const char* name, const char* label)
{
	if(openRow(name, label, "uint32")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(uint64& value, const char* name, const char* label)
{
	if(openRow(name, label, "uint64")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(float& value, const char* name, const char* label)
{
	if(openRow(name, label, "float")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(double& value, const char* name, const char* label)
{
	if(openRow(name, label, "double")){
		currentNode_->assignToPrimitive(&value, sizeof(value));
		closeRow(name);
		return true;
	}
	else
		return false;
}

bool PropertyIArchive::operator()(Serialization::IContainer& ser, const char* name, const char* label)
{
	const char* typeName = ser.containerType().name();
	if(!openRow(name, label, typeName))
		return false;

	size_t size = 0;
	if(currentNode_->multiValue())
		size = ser.size();
	else{
		size = currentNode_->count();
		size = ser.resize(size);
	}

	stack_.push_back(Level());

	size_t index = 0;
	if(ser.size() > 0)
		while(index < size)
		{
			ser(*this, "", "<");
			ser.next();
			++index;
		}

	stack_.pop_back();

	closeRow(name);
	return true;
}

bool PropertyIArchive::operator()(const Serialization::SStruct& ser, const char* name, const char* label)
{
	PropertyRow* nonLeafNode = 0;
	if(openRow(name, label, ser.type().name())){
		if (currentNode_->isLeaf()) {
			if(!currentNode_->isRoot()){
				currentNode_->assignTo(ser);
				closeRow(name);
				return true;
			}
		}
		else 
			nonLeafNode = currentNode_;
	}
	else
		return false;

	stack_.push_back(Level());

	ser(*this);

	stack_.pop_back();

	if (nonLeafNode)
		nonLeafNode->closeNonLeaf(ser, *this);
	closeRow(name);
	return true;
}


bool PropertyIArchive::operator()(Serialization::IPointer& ser, const char* name, const char* label)
{
	const char* baseName = ser.baseType().name();

	if(openRow(name, label, baseName)){
		if (!currentNode_->isPointer()) {
			closeRow(name);
			return false;
		}

		YASLI_ASSERT(currentNode_);
		PropertyRowPointer* row = static_cast<PropertyRowPointer*>(currentNode_);
		if(!row){
			closeRow(name);
			return false;
		}
		row->assignTo(ser);
	}
	else
		return false;

	stack_.push_back(Level());

	if(ser.get() != 0)
		ser.serializer()( *this );

	stack_.pop_back();

	closeRow(name);
	return true;
}

bool PropertyIArchive::operator()(Serialization::ICallback& callback, const char* name, const char* label)
{
	return callback.SerializeValue(*this, name, label);
}

bool PropertyIArchive::operator()(Serialization::Object& obj, const char* name, const char* label)
{
	if(openRow(name, label, obj.type().name())){
		bool result = false;
		if (currentNode_->isObject()) {
			PropertyRowObject* rowObj = static_cast<PropertyRowObject*>(currentNode_);
			result = rowObj->assignTo(&obj);
		}
		closeRow(name);
		return result;
	}
	else
		return false;
}

bool PropertyIArchive::OpenBlock(const char* name, const char* label)
{
	if(openRow(name, label, "block")){
		stack_.push_back(Level());
		return true;
	}
	else
		return false;
}

void PropertyIArchive::CloseBlock()
{
	closeRow("block");
	stack_.pop_back();
}

bool PropertyIArchive::openRow(const char* name, const char* label, const char* typeName)
{
	if(!name)
		return false;

	if(!currentNode_){
		lastNode_ = currentNode_ = model_->root();
		YASLI_ASSERT(currentNode_);
		if (currentNode_ && strcmp(currentNode_->typeName(), typeName) != 0)
			return false;
		return true;
	}

	YASLI_ESCAPE(currentNode_, return false);
	
	if(currentNode_->empty())
		return false;

	Level& level = stack_.back();

	PropertyRow* node = 0;
	if(currentNode_->isContainer()){
		if (level.rowIndex < int(currentNode_->children_.size()))
			node = currentNode_->children_[level.rowIndex];
		++level.rowIndex;
	}
	else {
		node = currentNode_->findFromIndex(&level.rowIndex, name, typeName, level.rowIndex);
		++level.rowIndex;
	}

	if(node){
		lastNode_ = node;
		if(node->isContainer() || !node->multiValue()){
			currentNode_ = node;
			if (currentNode_ && strcmp(currentNode_->typeName(), typeName) != 0)
				return false;
			return true;
		}
	}
	return false;
}

void PropertyIArchive::closeRow(const char* name)
{
	YASLI_ESCAPE(currentNode_, return);
	currentNode_ = currentNode_->parent();
}