/* Metallurgy - Higher Level Interface to Chordata
 * Copyright (C) 2001-2002  Jay Freeman (saurik)
*/

/*
 *        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 ``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 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 "stdafx.h"

#include "metallurgy/Element.h"
#include "metallurgy/Signature.h"
#include "metallurgy/Type.h"

#include "chordata/Signature.h"

#include "chordata/Utils.h"
using namespace Chordata::Utils;

namespace Metallurgy {

Element::Element(const Module *module, const Chordata::Element::Type corType) :
	module(module),
	array(NULL),
	extra(NULL),
	nested(NULL),
	type(NULL),
	intrinsic(corType)
{}

Element::Element(const IHasElement *type) :
	module(dynamic_cast<const ModuleToken *>(type)->getModule()),
	array(NULL),
	extra(NULL),
	nested(NULL),
	type(type),
	intrinsic(Chordata::Element::Class)
{
}

Element::Element(const Module *module, const Chordata::Signature * &blob) :
	module(module),
	array(NULL),
	extra(NULL),
	nested(NULL),
	type(NULL)
{
	intrinsic = Uncompress<Chordata::Element::Type>(blob);

	while (intrinsic & Chordata::Element::Modifier) {
		mods.push_back(intrinsic);
		intrinsic = Uncompress<Chordata::Element::Type>(blob);
	}

	if (IsPrimitive(intrinsic))
		return;
	
	switch (intrinsic) {
		case Chordata::Element::Ptr:
		case Chordata::Element::ByRef:
			extra = new Element(module, blob);
		break;

		case Chordata::Element::TypedByRef:
		case Chordata::Element::Object:
		case Chordata::Element::I:
		case Chordata::Element::U:
			return;
		break;

		case Chordata::Element::ValueType:
		case Chordata::Element::Class:
			type = dynamic_cast<IHasElement *>(module->ResolveToken(DecodeToken(Uncompress<uint32_t>(blob))));
		break;

		case Chordata::Element::CMod_Reqd:
		case Chordata::Element::CMod_Opt:
			type = dynamic_cast<IHasElement *>(module->ResolveToken(DecodeToken(Uncompress<uint32_t>(blob))));
			extra = new Element(module, blob);
		break;

		case Chordata::Element::FnPtr:
			nested = new Signature(module, blob);
		break;

		case Chordata::Element::SZArray:
			extra = new Element(module, blob);
			array = new DimensionVector();
		break;

		case Chordata::Element::Array: {
			extra = new Element(module, blob);
			array = new DimensionVector(Uncompress<uint32_t>(blob));

			uint32_t dims = Uncompress<uint32_t>(blob);
			for (uint32_t i(0); i < dims; i++)
				array->operator [](i).size = Uncompress<uint32_t>(blob);

			dims = Uncompress<uint32_t>(blob);
			for (i = 0; i < dims; i++)
				array->operator [](i).bound = Uncompress<int32_t>(blob);
		} break;

		default:
			throw std::wstring(L"unsupported element type");
		break;
	}
}

Element::~Element() {
	delete nested;
	delete extra;
	delete array;
}

void Element::Render(std::wstring &builder) const {
	switch (intrinsic) {
		case Chordata::Element::Void:		builder.insert(0, L"void");		break;
		case Chordata::Element::Boolean:	builder.insert(0, L"bool");		break;
		case Chordata::Element::Char:		builder.insert(0, L"char");		break;
		case Chordata::Element::I1:			builder.insert(0, L"sbyte");	break;
		case Chordata::Element::U1:			builder.insert(0, L"byte");		break;
		case Chordata::Element::I2:			builder.insert(0, L"short");	break;
		case Chordata::Element::U2:			builder.insert(0, L"ushort");	break;
		case Chordata::Element::I4:			builder.insert(0, L"int");		break;
		case Chordata::Element::U4:			builder.insert(0, L"uint");		break;
		case Chordata::Element::I8:			builder.insert(0, L"long");		break;
		case Chordata::Element::U8:			builder.insert(0, L"ulong");	break;
		case Chordata::Element::R4:			builder.insert(0, L"float");	break;
		case Chordata::Element::R8:			builder.insert(0, L"double");	break;
		case Chordata::Element::String:		builder.insert(0, L"string");	break;
		case Chordata::Element::Object:		builder.insert(0, L"object");	break;

		case Chordata::Element::I:			builder.insert(0, L"IntPtr");	break;
		case Chordata::Element::U:			builder.insert(0, L"UIntPtr");	break;

		case Chordata::Element::ByRef:
			extra->Render(builder);
			builder.insert(0, L"ref ");
		break;

		case Chordata::Element::Ptr:
			extra->Render(builder);
			builder.append(L" *");
		break;

		case Chordata::Element::Class:
		case Chordata::Element::ValueType:
		{
			const Metallurgy::Type *defref = dynamic_cast<const Metallurgy::Type *>(type);

			if (defref == NULL)
				type->getElement()->Render(builder);
			else {
				std::wstring name = defref->getName().getFull();
				if (name == L"System.String")
					builder.insert(0, L"string");
				else
					builder.insert(0, name.c_str());
			}
		} break;

		case Chordata::Element::SZArray:
			builder.insert(0, L"[]");
			extra->Render(builder);
		break;

		case Chordata::Element::Array: {
			wchar_t buff[16];
			extra->Render(builder);

			builder.append(L"[");
			for (uint32_t i(0); i < array->size(); i++) {
				if (i != 0) builder.append(L",");
				uint32_t size = (*array)[i].size;
				if (size != 0)
					builder.append(_itow(size, buff, 10));
			}
			builder.append(L"]");
		} break;

		/*default:
			std::cerr << std::endl << "Unable to build type :(. [" << std::setbase(16) << element->Intrinsic << std::setbase(10) << "]" << std::endl;
			exit(1);
		break;*/
	}
}

const Module *Element::getModule() const {
	return module;
}

const IHasElement *Element::getType() const {
	return type;
}

const Element *Element::getExtra() const {
	return extra;
}

const Signature *Element::getNested() const {
	return nested;
}

Chordata::Element::Type Element::getIntrinsic() const {
	return intrinsic;
}

const Element::DimensionVector *Element::getArray() const {
	return array;
}

std::wstring Element::getXmlMemberKey() const {
	switch (intrinsic) {
		case Chordata::Element::Void:		return L"System.Void";		break;
		case Chordata::Element::Boolean:	return L"System.Boolean";	break;
		case Chordata::Element::Char:		return L"System.Char";		break;
		case Chordata::Element::I1:			return L"System.SByte";		break;
		case Chordata::Element::U1:			return L"System.Byte";		break;
		case Chordata::Element::I2:			return L"System.Int16";		break;
		case Chordata::Element::U2:			return L"System.UInt16";	break;
		case Chordata::Element::I4:			return L"System.Int32";		break;
		case Chordata::Element::U4:			return L"System.UInt32";	break;
		case Chordata::Element::I8:			return L"System.Int64";		break;
		case Chordata::Element::U8:			return L"System.UInt64";	break;
		case Chordata::Element::R4:			return L"System.Single";	break;
		case Chordata::Element::R8:			return L"System.Double";	break;
		case Chordata::Element::String:		return L"System.String";	break;
		case Chordata::Element::Object:		return L"System.Object";	break;

		case Chordata::Element::I:			return L"System.IntPtr";	break;
		case Chordata::Element::U:			return L"System.UIntPtr";	break;

		case Chordata::Element::ByRef:
			return extra->getXmlMemberKey() + L"@";
		break;

		case Chordata::Element::Ptr:
			return extra->getXmlMemberKey() + L"*";
		break;

		case Chordata::Element::Class:
		case Chordata::Element::ValueType:
		{
			const Metallurgy::Type *defref = dynamic_cast<const Metallurgy::Type *>(type);
			return (defref == NULL) ? type->getElement()->getXmlMemberKey() : defref->getName().getFull();
		} break;

		case Chordata::Element::SZArray:
			return extra->getXmlMemberKey() + L"[]";
		break;

		case Chordata::Element::Array: {
			std::wstring key = extra->getXmlMemberKey() + L"[";

			wchar_t buff[16];
			for (uint32_t i(0); i < array->size(); i++) {
				if (i != 0) key += L",";
				key += L"0:";
				uint32_t size = (*array)[i].size;
				if (size != 0)
					key += _itow(size, buff, 10);
			}

			return key + L"]";
		} break;

		default:
			::MessageBox(NULL, L"Unable to render XmlMemberKey.", L"Error", MB_OK | MB_ICONERROR);
			return L"{Error}";
		break;
	}
}

bool Element::isVoid() const {
    switch (intrinsic) {
        case Chordata::Element::CMod_Opt:
            return extra->isVoid();
        default:
            return mods.size() == 0 && intrinsic == Chordata::Element::Void;
    }
}

}