#include "stdafx.h"
#include "TypeTransform.h"
#include "Variable.h"
#include "Type.h"
#include "Engine.h"

namespace storm {

	TypeTransform::TypeTransform(Type *oldType, Type *newType)
		: oldT(oldType), newT(newType) {}

	void TypeTransform::added(MemberVar *var) {
		grow();

		variables->v[variables->filled].oldVar = null;
		variables->v[variables->filled].newVar = var;
		variables->v[variables->filled].cache = null;
		variables->filled++;
	}

	void TypeTransform::removed(MemberVar *var) {
		grow();

		variables->v[variables->filled].oldVar = var;
		variables->v[variables->filled].newVar = null;
		variables->v[variables->filled].cache = null;
		variables->filled++;
	}

	void TypeTransform::same(MemberVar *o, MemberVar *n) {
		grow();

		variables->v[variables->filled].oldVar = o;
		variables->v[variables->filled].newVar = n;
		variables->v[variables->filled].cache = null;
		variables->filled++;
	}

	void TypeTransform::grow() {
		const size_t scale = sizeof(MapItem) / sizeof(void *);

		if (!variables) {
			variables = runtime::allocArray<MapItem>(engine(), &pointerArrayType, 10 * scale);
			return;
		}

		Nat oldSize = Nat(variables->count() / scale);
		if (variables->filled * 2 < oldSize)
			return;

		Nat newSize = Nat(variables->filled * 2);
		GcArray<MapItem> *copy = runtime::allocArray<MapItem>(engine(), &pointerArrayType, newSize * scale);
		memcpy(copy->v, variables->v, sizeof(MapItem) * oldSize);
		copy->filled = variables->filled;
		variables = copy;
	}

	RootObject *TypeTransform::apply(RootObject *old) {
		STORM_ASSERT_S(newT->isClass(), "Only classes are supported currently.");

		// TODO: We might want to generate code for parts of this. The cache makes it acceptable to
		// do even without compiled code.

		RootObject *copy = (RootObject *)runtime::allocObject(newT->size().current(), newT);

		// Set VTable:
		if (VTable *vtable = newT->vtable())
			vtable->insert(copy);

		// If it is a TObject, copy the thread. We don't expect that to be in the transforms.
		if (newT->isA(StormInfo<TObject>::type(engine()))) {
			size_t threadOffset = size_t(engine().ref(builtin::TObjectOffset).address());
			void *oldThread = (byte *)old + threadOffset;
			void *newThread = (byte *)copy + threadOffset;
			*(void **)newThread = *(void **)oldThread;
		}

		// Copy remaining things according to what is in 'variables':
		for (size_t i = 0; i < variables->filled; i++) {
			MapItem &var = variables->v[i];

			if (!var.newVar) {
				// Skip removed variables.
			} else if (!var.oldVar) {
				// Initialize new variables.
				initVar(copy, var.newVar, var.cache);
			} else {
				// Copy the rest.
				copyVar(copy, var.newVar, old, var.oldVar, var.cache);
			}
		}

		return copy;
	}

	void TypeTransform::initVar(RootObject *object, MemberVar *var, Function *&defaultCtor) {
		void *dest = (byte *)object + var->rawOffset().current();
		if (Function *init = var->initializer()) {
			// Call the initializer.
			os::CallThunk thunk = init->callThunk();
			void *params[1] = { null };
			(*thunk)(init->ref().address(), false, params, null, dest);
		} else {
			// Call the ctor. Cache the result to avoid multiple lookups.
			if (!defaultCtor)
				defaultCtor = var->type.type->defaultCtor();
			if (defaultCtor) {
				typedef void (*CtorFn)(void *);
				CtorFn c = (CtorFn)defaultCtor->ref().address();
				(*c)(dest);
			}
		}
	}

	void TypeTransform::copyVar(RootObject *to, MemberVar *toVar,
								RootObject *from, MemberVar *fromVar,
								Function *&copyCtor) {

		void *dest = (byte *)to + toVar->rawOffset().current();
		void *src = (byte *)from + fromVar->rawOffset().current();

		if (!toVar->type.isPrimitive()) {
			if (!copyCtor)
				copyCtor = toVar->type.type->copyCtor();
			if (copyCtor) {
				typedef void (*CtorCall)(void *, void *);
				CtorCall copy = (CtorCall)copyCtor->ref().address();
				(*copy)(dest, src);
				return;
			}
		}

		// Fall back to memcpy!
		memcpy(dest, src, toVar->type.size().current());
	}

	TypeTransform::Summary TypeTransform::summary() const {
		vector<Summary::Var> vars;

		for (size_t i = 0; i < variables->filled; i++) {
			const MapItem &var = variables->v[i];

			// Skip variables that did not exist previously.
			if (!var.oldVar)
				continue;

			Summary::Var v = {
				size_t(var.oldVar->rawOffset().current()),
				size_t(var.oldVar->type.size().current()),
				0
			};

			// Note: Removed variables are intentionally mapped to zero. Might not be ideal, but
			// currently we don't allow removing member variables, so it is fine.
			if (var.newVar)
				v.newStart = var.newVar->rawOffset().current();

			vars.push_back(v);
		}

		return Summary(oldT->size().current(), vars);
	}

	TypeTransform::Summary::Summary(size_t size, const vector<Var> &vars)
		: size(size), variables(vars) {

		std::sort(variables.begin(), variables.end());
	}

	size_t TypeTransform::Summary::translate(size_t x) const {
		vector<Var>::const_iterator found = std::lower_bound(variables.begin(), variables.end(), x);
		if (found != variables.end() && found->start == x) {
			// Use 'found' as it is.
		} else if (found == variables.begin()) {
			// No previous element, bail out. This should not happen.
			return x;
		} else {
			// Use the previous element.
			--found;
		}

		size_t varOffset = x - found->start;

		// Inside the variable? If not, should not really happen...
		if (varOffset > found->size)
			return x;

		return found->newStart + varOffset;
	}

}
