// Copyright 2019 The SwiftShader Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//	http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "Reactor.hpp"

#include <memory>

#ifndef rr_ReactorCoroutine_hpp
#	define rr_ReactorCoroutine_hpp

namespace rr {

// Base class for the template Stream<T>
class StreamBase
{
protected:
	StreamBase(const std::shared_ptr<Routine> &routine, Nucleus::CoroutineHandle handle)
	    : routine(routine)
	    , handle(handle)
	{}

	~StreamBase()
	{
		auto pfn = (Nucleus::CoroutineDestroy *)routine->getEntry(Nucleus::CoroutineEntryDestroy);
		pfn(handle);
	}

	bool await(void *out)
	{
		auto pfn = (Nucleus::CoroutineAwait *)routine->getEntry(Nucleus::CoroutineEntryAwait);
		return pfn(handle, out);
	}

private:
	std::shared_ptr<Routine> routine;
	Nucleus::CoroutineHandle handle;
};

// Stream is the interface to a running Coroutine instance.
// A Coroutine may Yield() values of type T, which can be retrieved with
// await().
template<typename T>
class Stream : public StreamBase
{
public:
	inline Stream(const std::shared_ptr<Routine> &routine, Nucleus::CoroutineHandle handle)
	    : StreamBase(routine, handle)
	{}

	// await() retrieves the next yielded value from the coroutine.
	// Returns true if the coroutine yieled a value and out was assigned a
	// new value. If await() returns false, the coroutine has finished
	// execution and await() will return false for all future calls.
	inline bool await(T &out) { return StreamBase::await(&out); }
};

template<typename FunctionType>
class Coroutine;

// Coroutine constructs a reactor Coroutine function.
// rr::Coroutine is similar to rr::Function in that it builds a new
// executable function, but Coroutines have the following differences:
//  (1) Coroutines do not support Return() statements.
//  (2) Coroutines support Yield() statements to suspend execution of the
//      coroutine and pass a value up to the caller. Yield can be called
//      multiple times in a single execution of a coroutine.
//  (3) The template argument T to Coroutine<T> is a C-style function
//      signature.
//  (4) Coroutine::operator() returns a rr::Stream<T> instead of an
//      rr::Routine.
//  (5) operator() starts execution of the coroutine immediately.
//  (6) operator() uses the Coroutine's template function signature to
//      ensure the argument types match the generated function signature.
//
// Example usage:
//
//   // Build the coroutine function
//   Coroutine<int()> coroutine;
//   {
//       Yield(Int(0));
//       Yield(Int(1));
//       Int current = 1;
//       Int next = 1;
//       While(true) {
//           Yield(next);
//           auto tmp = current + next;
//           current = next;
//           next = tmp;
//       }
//   }
//
//   // Start the execution of the coroutine.
//   auto s = coroutine();
//
//   // Grab the first 20 yielded values and print them.
//   for(int i = 0; i < 20; i++)
//   {
//       int val = 0;
//       s->await(val);
//       printf("Fibonacci(%d): %d", i, val);
//   }
//
template<typename Return, typename... Arguments>
class Coroutine<Return(Arguments...)>
{
public:
	Coroutine();

	template<int index>
	using CArgumentType = typename std::tuple_element<index, std::tuple<Arguments...>>::type;

	template<int index>
	using RArgumentType = CToReactorT<CArgumentType<index>>;

	// Return the argument value with the given index.
	template<int index>
	Argument<RArgumentType<index>> Arg() const
	{
		Value *arg = Nucleus::getArgument(index);
		return Argument<RArgumentType<index>>(arg);
	}

	// Completes building of the coroutine and generates the coroutine's
	// executable code. After calling, no more reactor functions may be
	// called without building a new rr::Function or rr::Coroutine.
	// While automatically called by operator(), finalize() should be called
	// as soon as possible once the coroutine has been fully built.
	// finalize() *must* be called explicitly on the same thread that
	// instantiates the Coroutine instance if operator() is to be invoked on
	// different threads.
	inline void finalize(const char *name = "coroutine", const Config::Edit &cfg = Config::Edit::None);

	// Starts execution of the coroutine and returns a unique_ptr to a
	// Stream<> that exposes the await() function for obtaining yielded
	// values.
	std::unique_ptr<Stream<Return>> operator()(Arguments...);

protected:
	std::unique_ptr<Nucleus> core;
	std::shared_ptr<Routine> routine;
	std::vector<Type *> arguments;
};

template<typename Return, typename... Arguments>
Coroutine<Return(Arguments...)>::Coroutine()
    : core(new Nucleus())
{
	std::vector<Type *> types = { CToReactorT<Arguments>::type()... };
	for(auto type : types)
	{
		if(type != Void::type())
		{
			arguments.push_back(type);
		}
	}

	Nucleus::createCoroutine(CToReactorT<Return>::type(), arguments);
}

template<typename Return, typename... Arguments>
void Coroutine<Return(Arguments...)>::finalize(const char *name /*= "coroutine"*/, const Config::Edit &cfg /* = Config::Edit::None */)
{
	if(core != nullptr)
	{
		routine = core->acquireCoroutine(name, cfg);
		core.reset(nullptr);
	}
}

template<typename Return, typename... Arguments>
std::unique_ptr<Stream<Return>>
Coroutine<Return(Arguments...)>::operator()(Arguments... args)
{
	finalize();

	// TODO(b/148400732): Go back to just calling the CoroutineEntryBegin function directly.
	std::function<Nucleus::CoroutineHandle()> coroutineBegin = [=] {
		using Sig = Nucleus::CoroutineBegin<Arguments...>;
		auto pfn = (Sig *)routine->getEntry(Nucleus::CoroutineEntryBegin);
		auto handle = pfn(args...);
		return handle;
	};

	auto handle = Nucleus::invokeCoroutineBegin(*routine, coroutineBegin);

	return std::make_unique<Stream<Return>>(routine, handle);
}

#	ifdef Yield  // Defined in WinBase.h
#		undef Yield
#	endif

// Suspends execution of the coroutine and yields val to the caller.
// Execution of the coroutine will resume after val is retrieved.
template<typename T>
inline void Yield(const T &val)
{
	Nucleus::yield(ValueOf(val));
}

}  // namespace rr

#endif  // rr_ReactorCoroutine_hpp