#include "stdafx.h"
#include "Fn.h"
#include "StrBuf.h"
#include "CloneEnv.h"
#include "Str.h"

namespace storm {

	RawFn::RawFn() : fn(null) {}

	void RawFn::call(FnBase *ptr, void *out, const void *params) const {
		typedef void (*Fn)(FnBase *ptr, void *out, const void *params);
		Fn fn = (Fn)this->fn;
		(*fn)(ptr, out, params);
	}

	/**
	 * Targets.
	 */

	RawFnTarget::RawFnTarget(const void *ptr) : data(ptr) {}

	void RawFnTarget::cloneTo(void *to, size_t size) const {
		STORM_ASSERT(size >= sizeof(*this));
		new (to) RawFnTarget(data);
	}

	const void *RawFnTarget::ptr() const {
		return data;
	}

	void RawFnTarget::toS(StrBuf *to, const RootObject *) const {
		*to << L"C++ function @" << hex(data);
	}

	const void *RawFnTarget::ctor(const RootObject *) const {
		return data;
	}


	/**
	 * The function pointer!
	 */

	FnBase::FnBase(const void *fn, const RootObject *thisPtr, Bool member, Thread *thread) {
		ptrMode = member ? modeMember : modeFreeFn;
		this->thisPtr = thisPtr;
		this->thread = thread;
		new (target()) RawFnTarget(fn);
	}

	FnBase::FnBase(const FnTarget &target, const RootObject *thisPtr, Nat mode, Thread *thread) {
		ptrMode = mode;
		this->thisPtr = thisPtr;
		this->thread = thread;
		target.cloneTo(this->target(), targetSize * sizeof(size_t));
	}

	FnBase::FnBase(const FnBase &o) {
		ptrMode = o.ptrMode;
		thisPtr = o.thisPtr;
		thread = o.thread;
		o.target()->cloneTo(target(), targetSize * sizeof(size_t));
	}

	void FnBase::deepCopy(CloneEnv *env) {
		if (thisPtr) {
			if (const Object *o = as<const Object>(thisPtr)) {
				// Yes, we need to clone it!
				cloned(o, env);
				thisPtr = o;
			}
		}
	}

	Byte FnBase::needsCopy(size_t firstInfo) const {
		Thread *t = runOn(firstInfo);
		if (t && (t->thread() != os::Thread::current())) {
			return 0x1 | ((~ptrMode) & 0x2); // Note: bit-pattern of ptrMode supports this!
		} else {
			return 0x0;
		}
	}

	Thread *FnBase::runOn(size_t firstInfo) const {
		if (thread)
			return thread;

		if (ptrMode == modeMember) {
			if (const TObject *tThis = as<const TObject>(thisPtr)) {
				return tThis->associatedThread();
			} else if (firstInfo != 0 && (firstInfo & 0x1) == 0) {
				const TObject *first = (const TObject *)firstInfo;
				return first->associatedThread();
			}
		} else if (ptrMode == modeActorCtor) {
			if (firstInfo & 0x1) {
				size_t noTag = firstInfo & ~size_t(0x1);
				return (Thread *)noTag;
			}
		}

		return null;
	}

	void FnBase::callRawI(void *out, const os::FnCallRaw &params, size_t firstInfo, CloneEnv *&env) const {
		const void *toCall = target()->ptr();

		Thread *thread = runOn(firstInfo);
		Bool spawn = (thread && thread->thread() != os::Thread::current());
		Bool callMember = ptrMode == modeMember;

		void *addFirst = null;
		if (thisPtr) {
			addFirst = (void *)thisPtr;
			if (spawn) {
				if (!env)
					env = new (this) CloneEnv();
				// In this case, 'p' has to be derived from Object.
				addFirst = clone((Object *)addFirst, env);
			}
		}

		// Dispatch to the proper thread.
		if (spawn) {
			os::FutureBase future;
			os::UThread::spawnRaw(toCall, callMember, addFirst, params, future, out, &thread->thread());
			future.result();
		} else {
			params.callRaw(toCall, callMember, addFirst, out);
		}
	}

	void FnBase::toS(StrBuf *to) const {
		target()->toS(to, thisPtr);
	}

	RawFn FnBase::rawCall() {
		// Note: Can not be made abstract to make the template usable from C++.
		STORM_FAIL_S("rawCall not overridden.");
		return RawFn();
	}

	const void *FnBase::rawCtor() const {
		if (ptrMode != modeCtor && ptrMode != modeActorCtor)
			return null;

		return target()->ctor(thisPtr);
	}

}
