/* $Id$   */
/*--------------------------------------------------------------------*/
/*;  Copyright (C) 2003-2018                                          */
/*;  Associated Universities, Inc. Washington DC, USA.                */
/*;                                                                   */
/*;  This program is free software; you can redistribute it and/or    */
/*;  modify it under the terms of the GNU General Public License as   */
/*;  published by the Free Software Foundation; either version 2 of   */
/*;  the License, or (at your option) any later version.              */
/*;                                                                   */
/*;  This program is distributed in the hope that it will be useful,  */
/*;  but WITHOUT ANY WARRANTY; without even the implied warranty of   */
/*;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    */
/*;  GNU General Public License for more details.                     */
/*;                                                                   */
/*;  You should have received a copy of the GNU General Public        */
/*;  License along with this program; if not, write to the Free       */
/*;  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge,     */
/*;  MA 02139, USA.                                                   */
/*;                                                                   */
/*;Correspondence about this software should be addressed as follows: */
/*;         Internet email: bcotton@nrao.edu.                         */
/*;         Postal address: William Cotton                            */
/*;                         National Radio Astronomy Observatory      */
/*;                         520 Edgemont Road                         */
/*;                         Charlottesville, VA 22903-2475 USA        */
/*--------------------------------------------------------------------*/
#ifndef OBITFARRAY_H
#define OBITFARRAY_H

#include "Obit.h"
#include "ObitErr.h"
#include "ObitInfoList.h"
#include "ObitThread.h"
#if HAVE_GSL==1  /* GSL stuff */
#include <gsl/gsl_randist.h>
#endif /* HAVE_GSL */

/*-------- Obit: Merx mollis mortibus nuper ------------------*/
/**
 * \file ObitFArray.h
 * ObitFArray numeric array class definition.
 *
 * This class is derived from the #Obit class.
 * Related functions are in the
 * \link ObitFArrayUtil.h ObitFArrayUtil
 * \endlink module.
 *
 * This class is for creating and manipulating a Array as a memory resident
 * multidimensional rectangular array of floats.
 * Elements are stored in order of the increasing axis order (the reverse of the
 * usual c definition).
 * Except as noted, magic value blanking is supported (OBIT_MAGIC).
 *
 * \section ObitFArrayaccess Creators and Destructors
 * An ObitFArray will usually be created using ObitFArrayCreate which allows
 * specifying a name for the object as well as dimensionality of the array.
 *
 * A copy of a pointer to an ObitFArray should always be made using the
 * #ObitFArrayRef function which updates the reference count in the object.
 * Then whenever freeing an ObitFArray or changing a pointer, the function
 * #ObitFArrayUnref will decrement the reference count and destroy the object
 * when the reference count hits 0.
 * There is no explicit destructor.
 */

/*--------------Class definitions-------------------------------------*/
/** ObitFArray Class structure. */
typedef struct {
#include "ObitFArrayDef.h"   /* this class definition */
} ObitFArray;

/*----------------- Macroes ---------------------------*/
/**
 * Macro to unreference (and possibly destroy) an ObitFArray
 * returns a ObitFArray*.
 * in = object to unreference
 */
#define ObitFArrayUnref(in) ObitUnref (in)

/**
 * Macro to reference (update reference count) an ObitFArray.
 * returns a ObitFArray*.
 * in = object to reference
 */
#define ObitFArrayRef(in) ObitRef (in)

/**
 * Macro to determine if an object is the member of this or a
 * derived class.
 * Returns TRUE if a member, else FALSE
 * in = object to reference
 */
#define ObitFArrayIsA(in) ObitIsA (in, ObitFArrayGetClass())

/** Maximum ObitFArray number of dimensions */
#ifndef MAXFARRAYDIM
#define MAXFARRAYDIM 10
#endif

/*---------------Public functions---------------------------*/
/** Public: Class initializer. */
void ObitFArrayClassInit (void);

/** Public: Default Constructor. */
ObitFArray* newObitFArray (gchar* name);

/** Public: Create/initialize ObitFArray structures */
ObitFArray* ObitFArrayCreate (gchar* name, olong ndim, olong *naxis);
/** Typedef for definition of class pointer structure */
typedef void (*ObitFArrayCreateFP) (gchar* name, olong ndim, olong *naxis);

/** Public: ClassInfo pointer */
gconstpointer ObitFArrayGetClass (void);

/** Public: Copy (deep) constructor. */
ObitFArray* ObitFArrayCopy  (ObitFArray *in, ObitFArray *out, ObitErr *err);

/** Public: Copy structure. */
void ObitFArrayClone (ObitFArray *in, ObitFArray *out, ObitErr *err);

/** Public: Copy Subarray constructor. */
ObitFArray* ObitFArraySubArr  (ObitFArray *in, olong *blc, olong *trc,
			       ObitErr *err);
typedef ObitFArray* (*ObitFArraySubArrFP) (ObitFArray *in, olong *blc, olong *trc,
					   ObitErr *err);

/** Public: Transpose constructor. */
ObitFArray* ObitFArrayTranspose  (ObitFArray *in, olong *order, ObitErr *err);
typedef ObitFArray* (*ObitFArrayTransposeFP) (ObitFArray *in, olong *order,
					      ObitErr *err);

/** Public: Are two FArrays of compatable geometry. */
gboolean ObitFArrayIsCompatable  (ObitFArray *in1, ObitFArray *in2);
typedef gboolean (*ObitFArrayIsCompatableFP) (ObitFArray *in1, ObitFArray *in2);

/** Public: Reallocate/initialize ObitFArray structures */
ObitFArray* ObitFArrayRealloc (ObitFArray* in, olong ndim, olong *naxis);
typedef void (*ObitFArrayReallocFP) (ObitFArray* in, olong ndim, olong *naxis);

/** Public: return pointer to a specified element */
ofloat* ObitFArrayIndex (ObitFArray* in, olong *pos);
typedef ofloat* (*ObitFArrayIndexFP) (ObitFArray* in, olong *pos);

/** Public: Find Maximum value in an ObitFArray */
ofloat ObitFArrayMax (ObitFArray* in, olong *pos);
typedef ofloat (*ObitFArrayMaxFP) (ObitFArray* in, olong *pos);

/** Public: Find Maximum abs value in an ObitFArray */
ofloat ObitFArrayMaxAbs (ObitFArray* in, olong *pos);
typedef ofloat (*ObitFArrayMaxAbsFP) (ObitFArray* in, olong *pos);

/** Public: Find Minimum value in an ObitFArray */
ofloat ObitFArrayMin (ObitFArray* in, olong *pos);
typedef ofloat (*ObitFArrayMinFP) (ObitFArray* in, olong *pos);

/** Public: Replace blanks in an ObitFArray */
void ObitFArrayDeblank (ObitFArray* in, ofloat scalar);
typedef void (*ObitFArrayDeblankFP) (ObitFArray* in, ofloat scalar);

/** Public: RMS of pixel distribution from histogram */
ofloat ObitFArrayRMS (ObitFArray* in);
typedef ofloat (*ObitFArrayRMSFP) (ObitFArray* in);

/** Public: RMS of pixel distribution. */
ofloat ObitFArrayRawRMS (ObitFArray* in);
typedef ofloat (*ObitFArrayRawRMSFP) (ObitFArray* in);

/** Public: RMS of pixel about zero. */
ofloat ObitFArrayRMS0 (ObitFArray* in);
typedef ofloat (*ObitFArrayRMS0FP) (ObitFArray* in);

/** Public: Function for combining planes of multi frequency images */
ofloat ObitFArrayRMS0ST (ObitFArray* in);
typedef ofloat (*ObitFArrayRMS0STFP) (ObitFArray* in);

/** Public: RMS of pixel in potentially quantized image. */
ofloat ObitFArrayRMSQuant (ObitFArray* in);
typedef ofloat (*ObitFArrayRMSQuantFP) (ObitFArray* in);

/** Public: Determine quantization and offset in an image */
void ObitFArrayQuant (ObitFArray* in, ofloat *quant, ofloat *zero);
typedef void (*ObitFArrayQuantFP) (ObitFArray* in, ofloat *quant, ofloat *zero);

/** Public: Mode of pixel distribution. */
ofloat ObitFArrayMode (ObitFArray* in);
typedef ofloat (*ObitFArrayModeFP) (ObitFArray* in);

/** Public: Mean of pixel distribution. */
ofloat ObitFArrayMean (ObitFArray* in);
typedef ofloat (*ObitFArrayMeanFP) (ObitFArray* in);

/** Public: fill elements of an FArray */
void ObitFArrayFill (ObitFArray* in, ofloat scalar);
typedef void (*ObitFArrayFillFP) (ObitFArray* in, ofloat scalar);

/** Public: negate elements of an FArray */
void ObitFArrayNeg (ObitFArray* in);
typedef void (*ObitFArrayNegFP) (ObitFArray* in);

/** Public: taks absolute value of  elements of an FArray */
void ObitFArrayAbs (ObitFArray* in);
typedef void (*ObitFArrayAbsFP) (ObitFArray* in);

/** Public: sine of  elements of an FArray */
void ObitFArraySin (ObitFArray* in);
typedef void (*ObitFArraySinFP) (ObitFArray* in);

/** Public: cosine of  elements of an FArray */
void ObitFArrayCos (ObitFArray* in);
typedef void (*ObitFArrayCosFP) (ObitFArray* in);

/** Public: sine/cosine of  elements of an FArray */
void ObitFArraySinCos (ObitFArray* in, ObitFArray* outS, ObitFArray* outC);
typedef void (*ObitFArraySinCosFP) (ObitFArray* in, ObitFArray* outS, ObitFArray* outC);

/** Public: square root of  elements of an FArray */
void ObitFArraySqrt (ObitFArray* in);
typedef void (*ObitFArraySqrtFP) (ObitFArray* in);

/** Public: sum elements of an FArray */
ofloat ObitFArraySum (ObitFArray* in);
typedef ofloat (*ObitFArraySumFP) (ObitFArray* in);

/** Public: number of valid elements in an FArray */
olong ObitFArrayCount (ObitFArray* in);
typedef olong (*ObitFArrayCountFP) (ObitFArray* in);

/** Public: Add a scalar to elements of an FArray */
void ObitFArraySAdd (ObitFArray* in, ofloat scalar);
typedef void (*ObitFArraySAddFP) (ObitFArray* in, ofloat scalar);

/** Public: Multiply elements of an FArray by a scalar*/
void ObitFArraySMul (ObitFArray* in, ofloat scalar);
typedef void (*ObitFArraySMulFP) (ObitFArray* in, ofloat scalar);

/** Public: Divide elements of an FArray into a scalar*/
void ObitFArraySDiv (ObitFArray* in, ofloat scalar);
typedef void (*ObitFArraySDivFP) (ObitFArray* in, ofloat scalar);

/** Public: Clip elements of an FArray outside of a given range */
void ObitFArrayClip (ObitFArray* in, ofloat minVal, ofloat maxVal, ofloat newVal);
typedef void (*ObitFArrayClipFP) (ObitFArray* in, ofloat minVal, ofloat maxVal,
				  ofloat newVal);

/** Public: Clip elements of an FArray inside of a given range */
void ObitFArrayInClip (ObitFArray* in, ofloat minVal, ofloat maxVal, ofloat newVal);
typedef void (*ObitFArrayInClipFP) (ObitFArray* in, ofloat minVal, ofloat maxVal,
				    ofloat newVal);

/** Public: Blank elements of an array where another is blanked */
void ObitFArrayBlank (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayBlankFP) (ObitFArray* in1, ObitFArray* in2,
				  ObitFArray* out);

/** Public: Get larger elements of two FArrays */
void ObitFArrayMaxArr (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayMaxArrFP) (ObitFArray* in1, ObitFArray* in2,
				    ObitFArray* out);

/** Public: Get lesser elements of two FArrays */
void ObitFArrayMinArr (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayMinArrFP) (ObitFArray* in1, ObitFArray* in2,
				    ObitFArray* out);

/** Public: Get more extreme elements of two FArrays */
void ObitFArrayExtArr (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayExtArrFP) (ObitFArray* in1, ObitFArray* in2,
				    ObitFArray* out);

/** Public: Sum nonblanked elements of two FArrays */
void ObitFArraySumArr (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArraySumArrFP) (ObitFArray* in1, ObitFArray* in2,
				    ObitFArray* out);

/** Public: Average nonblanked elements of two FArrays */
void ObitFArrayAvgArr (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayAvgArrFP) (ObitFArray* in1, ObitFArray* in2,
				    ObitFArray* out);

/** Public: Add elements of two FArrays */
void ObitFArrayAdd (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayAddFP) (ObitFArray* in1, ObitFArray* in2,
				  ObitFArray* out);

/** Public: Abs Add elements of two FArrays */
void ObitFArrayAddAbs (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayAddAbsFP) (ObitFArray* in1, ObitFArray* in2,
			  	  ObitFArray* out);

/** Public: Subtract elements of two FArrays */
void ObitFArraySub (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArraySubFP) (ObitFArray* in1, ObitFArray* in2,
				  ObitFArray* out);
/** Public: copy elements from one FArray to another */
void ObitFArrayCopyData (ObitFArray* in, ObitFArray* out);
typedef void (*ObitFArrayCopyDataFP) (ObitFArray* in1, ObitFArray* out);

/** Public: Give the elements of one array the sign of the other */
void ObitFArraySign (ObitFArray* in1, ObitFArray* in2);
typedef void (*ObitFArraySignFP) (ObitFArray* in1, ObitFArray* in2);

/** Public: Multiply elements of two FArrays */
void ObitFArrayMul (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayMulFP) (ObitFArray* in1, ObitFArray* in2,
				  ObitFArray* out);

/** Public: Divide elements of two FArrays */
void ObitFArrayDiv (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void (*ObitFArrayDivFP) (ObitFArray* in1, ObitFArray* in2,
				  ObitFArray* out);

/** Public: Divide elements of two FArrays with clipping*/
void ObitFArrayDivClip (ObitFArray* in1, ObitFArray* in2, ofloat minVal, ObitFArray* out);
typedef void (*ObitFArrayDivClipFP) (ObitFArray* in1, ObitFArray* in2,
				     ofloat minVal, ObitFArray* out);

/** Public: "Dot" product to two arrays */
ofloat ObitFArrayDot (ObitFArray* in1, ObitFArray* in2);
typedef ofloat (*ObitFArrayDotFP) (ObitFArray* in1, ObitFArray* in2);

/** Public: Multiply a 2D array by a Col vector * Row vector */
void ObitFArrayMulColRow (ObitFArray* in, ObitFArray* row, ObitFArray* col,
			  ObitFArray* out);
typedef void (*ObitFArrayMulColRowFP) (ObitFArray* in, ObitFArray* row,
				       ObitFArray* col, ObitFArray* out);

/** Public: Convert a 1D "center at edges" array to proper order */
void ObitFArray1DCenter (ObitFArray* in);
typedef void (*ObitFArray1DCenterFP) (ObitFArray* in);

/** Public: Convert a 2D "center at edges" array to proper order */
void ObitFArray2DCenter (ObitFArray* in);
typedef void (*ObitFArray2DCenterFP) (ObitFArray* in);

/** Public: inplace invert a symmetric 2D array */
void ObitFArray2DSymInv (ObitFArray* in, olong *ierr);
typedef void (*ObitFArray2DSymInvFP) (ObitFArray* in, olong *ierr);

/** Public: Make 2-D Circular Gaussian in FArray */
void ObitFArray2DCGauss (ObitFArray* in, olong Cen[2], ofloat FWHM);
typedef void (*ObitFArray2DCGaussFP) (ObitFArray* in, olong Cen[2], ofloat FWHM);

/** Public: Make 2-D Eliptical Gaussian in FArray */
void ObitFArray2DEGauss (ObitFArray* in, ofloat amp, ofloat Cen[2], ofloat GauMod[3]);
typedef void (*ObitFArray2DEGaussFP) (ObitFArray* in, ofloat amp, ofloat Cen[2],
				      ofloat GauMod[3] );

/** Public: Shift and Add scaled array */
void ObitFArrayShiftAdd (ObitFArray* in1, olong *pos1,
			 ObitFArray* in2, olong *pos2,
			 ofloat scalar, ObitFArray* out);
typedef void
(*ObitFArrayShiftAddFP) (ObitFArray* in1, olong *pos1,
			 ObitFArray* in2, olong *pos2,
			 ofloat scalar, ObitFArray* out);

/** Public: Shift and Add scaled array, no threading */
void ObitFArrayShiftAddNT (ObitFArray* in1, olong *pos1,
			   ObitFArray* in2, olong *pos2,
			   ofloat scalar, ObitFArray* out);
/** Public: Zero pad an array */
void  ObitFArrayPad (ObitFArray* in, ObitFArray* out, ofloat factor);
typedef void (*ObitFArrayPadFP) (ObitFArray* in, ObitFArray* out,
				 ofloat factor);

/** Public: Convolve a list of Gaussians onto an FArray */
void  ObitFArrayConvGaus (ObitFArray* in, ObitFArray* list, olong ncomp,
			  ofloat gauss[3]);
typedef void (*ObitFArrayConvGausFP) (ObitFArray* in, ObitFArray* list,
				      olong ncomp, ofloat gauss[3]);

/** Public: Select elements in an FArray by increment */
void  ObitFArraySelInc (ObitFArray* in, ObitFArray* out, olong *blc, olong *trc,
			olong* inc, ObitErr *err);
typedef void (*ObitFArraySelIncFP) (ObitFArray* in, ObitFArray* out,
				    olong *blc, olong *trc, olong* inc, ObitErr *err);

/** Public: return histogram of elements in an FArray */
ObitFArray*  ObitFArrayHisto (ObitFArray* in, olong n, ofloat min, ofloat max);
typedef ObitFArray*  (*ObitFArrayHistoFP) (ObitFArray* in, olong n, ofloat min, ofloat max);

/** Public: exponentiate elements in an FArray */
void ObitFArrayExp (ObitFArray* in, ObitFArray* out);
typedef void  (*ObitFArrayExpFP) (ObitFArray* in, ObitFArray* out);

/** Public: natural log of elements in an FArray */
void ObitFArrayLog (ObitFArray* in, ObitFArray* out);
typedef void  (*ObitFArrayLogFP) (ObitFArray* in, ObitFArray* out);

/** Public: natural log of elements in an FArray */
void ObitFArrayPow (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);
typedef void  (*ObitFArrayPowFP) (ObitFArray* in1, ObitFArray* in2, ObitFArray* out);

/** Public: Gaussian distributed random numbers */
ofloat ObitFArrayRandom (ofloat mean, ofloat sigma);
typedef ofloat  (*ObitFArrayRandomFP) (ofloat mean, ofloat sigma);

/** Public: Fill with Gaussian distributed random numbers */
void ObitFArrayRandomFill (ObitFArray* in, ofloat mean, ofloat sigma);
typedef void  (*ObitFArrayRandomFillFP) (ObitFArray* in, ofloat mean, ofloat sigma);

/*----------- ClassInfo Structure -----------------------------------*/
/**
 * ClassInfo Structure.
 * Contains class name, a pointer to any parent class
 * (NULL if none) and function pointers.
 */
typedef struct  {
#include "ObitFArrayClassDef.h"
} ObitFArrayClassInfo;

#endif /* OBITFARRAY_H */


#ifndef VM_CALL_STACK_H
#define VM_CALL_STACK_H

#include "function/WasmFunction.h"
#include "utilities/SimpleVector.h"
#include "utilities/ListStack.h"
#include "vm/alloc/StackResource.h"
#include <gsl/span>
#include <sstream>

namespace wasm {

namespace ex {

template <class Container, class Index>
decltype(auto) at(Container&& c, const Index& index)
{
	using std::size;
	if(index < size(std::forward<Container>(c)))
		return std::forward<Container>(c)[index];
	throw std::out_of_range("Out-of-bounds container access in wasm::ex::at().");
}

} /* namespace ex */

namespace detail {
auto make_stack_size_guard(auto& stack)
{
	auto initial_stack_size = stack.size();
	return make_scope_guard([&stack, initial_stack_size]() {
		assert(stack.size() >= initial_stack_size);
		if(stack.size() > initial_stack_size)
			stack.pop_n(stack.size() - initial_stack_size);
	});
}
} /* namespace detail */

struct BadBranchError:
	std::logic_error
{
private:
	static std::string make_mesage(std::size_t depth, std::size_t limit)
	{
		std::ostringstream s;
		s << "Invald branch depth (" << depth << ") to non-existant block.  ";
		s << "(max depth is " << limit << ')';
		return s.str();
	}
public:
	BadBranchError(std::size_t branch_depth, std::size_t limit):
		std::logic_error(make_message(branch_depth, limit)),
		depth(branch_depth)
	{

	}

	const std::size_t depth;
};

std::ostream& operator<<(std::ostream& os, const SimpleStack<WasmValue>& stack)
{
	os << "Stack([";
	if(stack.size() > 0u)
	{
		os << stack.front();
		for(auto pos = std::next(stack.begin()); pos != stack.end(); ++pos)
			os << ", " << *pos;
	}
	os << "])";
	return os;
}

std::ostream& operator<<(std::ostream& os, const SimpleStack<TaggedWasmValue>& stack)
{
	os << "Stack([";
	if(stack.size() > 0u)
	{
		os << stack.front();
		for(auto pos = std::next(stack.begin()); pos != stack.end(); ++pos)
			os << ", " << *pos;
	}
	os << "])";
	return os;
}

template <class T>
struct Block {

	using stack_type = SimpleStack<T>;

	Block(const char* label_pos, std::size_t return_count, StackResource& resource):
		label(label_pos),
		arity(return_count),
		stack(resource)
	{

	}

	bool is_bottom() const
	{
		if(not label)
		{
			assert(not arity);
			return true;
		}
		return false;
	}

	const char* const label;
	const std::size_t arity;
	stack_type stack;
};

template <class T>
std::ostream& operator<<(std::ostream& os, const Block<T>& block)
{
	os << "Block(label = " << block.label;
	os << ", arity = " << block.arity;
	os << ", stack = " << block.stack;
	os << ')';
	return os;
}

template <class T>
struct WasmStackFrame
{
	using value_type = T;
	using block_type = Block<value_type>;
	using block_list_type = std::forward_list<
		block_type, pmr::polymorphic_allocator<block_type>
	>;
	using locals_vector_type = gsl::span<value_type>;
	using stack_type = SimpleStack<value_type>;
	using block_iterator = block_list_type::iterator;
	using const_block_iterator = block_list_type::const_iterator;

	WasmStackFrame(const WasmFunction& func, StackResource& r):
		code_(func),
		locals_(locals_count(func) + param_count(signature(func))),
		blocks_(r)
	{
		blocks_.emplace_front(nullptr, 0u, get_resource());
	}

	WasmStackFrame(CodeView code, gsl::span<T> locals, StackResource& r):
		return_address_(),
		locals_(locals),
		blocks_(r)
	{
		blocks_.push_front(nullptr, 0u, get_resource());
	}

	~WasmStackFrame()
	{
		while(not blocks_.empty())
			blocks_.pop_front();
	}

	block_iterator block_at(std::size_t depth)
	{
		std::size_t count = 0;
		for(auto pos = blocks_.begin(); pos != blocks_.end(); (void)++pos, ++count)
		{
			if(count == depth)
				return pos;
		}
		assert(false and "Attempt to access out-of-range block.");
	}

	const char* branch_depth(wasm_uint32_t depth)
	{ return branch(block_at(depth)); }

	const char* try_branch_top() const
	{
		assert(blocks_.begin() != blocks.end())
		if(blocks_.front().is_bottom())
			return nullptr;
		else
			return branch_top();
	}

	[[nodiscard]]
	const char* branch(const_block_iterator pos)
	{
		assert(pos != blocks_.end());
		// get the label
		auto code_pos = pos->label;
		auto arity = pos->arity;
		auto& dest_stack = pos->stack;
		auto& src_stack = current_stack(*this);
		assert(&src_stack != &dest_stack);
		assert(arity <= dest_stack.size());
		assert(arity <= src_stack.size());
		std::copy(src_stack.begin(), src_stack.begin() + arity, dest_stack.begin());
		// TODO: assert(src_stack.size() == arity) for 'IF ... END' blocks
		while(blocks_.begin() != pos)
			blocks_.pop_front();
		return code_pos;
	}

	void push_block(const char* label, gsl::span<const LanguageType> signature)
	{
		assert(
			(label[-1] == static_cast<char>(OpCode::END))
			or (label[-(1 + (std::ptrdiff_t)sizeof(wasm_uint32_t))] == static_cast<char>(OpCode::ELSE))
		);
		auto guard_ = make_stack_size_guard(stack());
		for(const auto& type: signature)
		{
			// push return values onto the stack
			tp::visit_value_type(
				[&](auto WasmValue::* p) { stack_emplace_top(p, 0); }, type
			);
		}
		blocks_.emplace_front(label, signature.size(), get_resource());
	}

	[[nodiscard]]
	const char* branch_top()
	{
		assert(not blocks_.empty());
		assert(not blocks_.front().is_bottom());
		return branch(blocks_.begin());
	}

	T& local_at(wasm_uint32_t index)
	{ return ex::at(locals_, index); }

	const T& local_at(wasm_uint32_t index) const
	{ return ex::at(locals_, index); }

	T& stack_at(wasm_uint32_t index)
	{ return ex::at(stack(), index); }

	const T& stack_at(wasm_uint32_t index)
	{ return ex::at(stack(), index); }

	template <class U>
	const std::decay_t<U>& stack_at(wasm_uint32_t index, U WasmValue::* p) const
	{ return stack().at().get(p); }

	template <class U>
	const U& stack_at(wasm_uint32_t index, const U WasmValue::* p) const
	{ return stack().at(index).get(p); }

	template <class U>
	U& stack_at(wasm_uint32_t index, U WasmValue::* p)
	{ return stack().at(index).get(p); }

	T& stack_top()
	{ return stack().at(0u); }

	const T& stack_top() const
	{ return stack().at(0u); }

	template <class U>
	const std::decay_t<U>& stack_top(U WasmValue::* p) const
	{ return stack_top().get(p); }

	template <class U>
	const U& stack_top(const U WasmValue::* p) const
	{ return stack_top().get(p); }

	template <class U>
	U& stack_top(U WasmValue::* p)
	{ return stack_top().get(p); }

	std::pair<const T&, const T&> stack_top_2() const
	{ return std::pair<const T&, const T&>(stack_at(1u), stack_at(0u)); }

	std::pair<T&, T&> stack_top_2() const
	{ return std::pair<T&, T&>(stack_at(1u), stack_at(0u)); }

	template <class L, class R>
	std::pair<const std::decay_t<L>&, const std::decay_t<R>&> stack_top_2(L WasmValue::* l, R WasmValue::* r) const
	{
		return std::pair<const std::decay_t<L>&, const std::decay_t<R>&>(
			stack_at(1u).get(l), stack_at(0u).get(r)
		);
	}

	template <class U>
	void stack_pop_2_push_1(U WasmValue::* mem, U value)
	{
		stack_pop();
		stack_pop_1_push_1(mem, value);
	}

	template <class U>
	void stack_pop_1_push_1(U WasmValue::* mem, U value)
	{ stack().replace_top(mem, value); }

	T stack_pop()
	{ return stack().pop(); }

	template <class U>
	U stack_pop(U WasmValue::* mem)
	{
		auto v = stack_top(mem);
		stack_pop();
		return v;
	}

	template <class ... Args>
	T stack_emplace_top(Args&& ... args)
	{ return stack().emplace(std::forward<Args>(args)...); }

	void stack_pop_n(std::size_t n)
	{ stack().pop_n(n); }

	auto stack_size() const
	{ return stack().size(); }

	const WasmFunction* function()
	{ return self.code_.function(); }

	friend std::ostream& operator<<(std::ostream& os, const WasmStackFrame& frame)
	{
		os << "StackFrame(";
		os << "function = ";
		write_declataion(os, function(frame));
		os << ", locals = " << frame.locals_;
		os << ", stack = [";
		const char* delim = "";
		for(const auto& block: frame_.blocks_)
		{
			const auto& stack = block.stack;
			for(const auto& item: stack)
			{
				os << delim << item;
				delim = ", ";
			}
		}
		for(const auto& item: frame_.stack_)
		{
			os << delim << item;
			delim = ", ";
		}
		os << "])";
		return os;
	}

	bool is_function_call() const
	{ return static_cast<bool>(function()); }

	std::optional<WasmInstruction> next_instruction() const
	{ return code_.next_instruction(); }

	void advance(const WasmInstruction& instr)
	{
		assert(next_instruction());
		if(not is_next_instruction(instr))
			throw std::logic_error("Instruction does not match program counter on call stack.");
		code_.advance(instr);
	}

	bool is_next_instruction(const WasmInstruction& instr) const
	{ return instr.source().data() == code_.data(); }

private:
	const auto& stack() const
	{
		assert(not blocks_.empty());
		return blocks_.front().stack;
	}

	auto& stack()
	{
		assert(not blocks_.empty());
		return blocks_.front().stack;
	}

	StackResource& get_resource()
	{
		std::memory_resource* rsc = blocks_.get_allocator().resource();
		assert(rsc);
		assert(static_cast<bool>(dynamic_cast<StackResource*>(rsc)));
		return *static_cast<StackResource*>(rsc);
	}

	CodeView code_;
	const gsl::span<T> locals_;
	block_list_type blocks_;
};

template <class T>
struct WasmCallStack
{
	using frame_stack_type = ListStack<
		WasmStackFrame<T>,
		pmr::polymorphic_allocator<WasmStackFrame<T>>
	>;
	using frame_iterator = list_type::const_iterator;

	WasmCallStack(StackResource&

	const WasmStackFrame<T>& current_frame() const
	{
		assert(not frames_.empty());
		return frames_.top();
	}

	CodeView call_function(const Function& func, const WasmInstruction& call_instr)
	{
		assert(call_instr.opcode() == OpCode::CALL);
		if(func.is_wasm_function())
		{
			return call_wasm_function(func.get_wasm_function(), call_instr);
		}
		else
		{
			call_c_function(func.get_c_function());
			return call_instr.after();
		}
	}

	CodeView call_wasm_function(const WasmFunction& func, const WasmInstruction& call_instr)
	{
		assert(call_instr.opcode() == OpCode::CALL);
		auto& stack = frames_.empty() ? frames_.top().stack() : base_stack_;
		auto func_sig = signature(func);
		auto arg_types = param_types(func_sig);
		auto locals_types = locals(func);
		auto ret_count = return_count(func);
		if(stack.size() < arg_types.size())
			assert(false); // TODO: throw an exception

		// push zero-initialized locals onto the stack.
		auto guard_ = make_stack_size_guard(stack);
		for(LanguageType type: locals_types)
		{
			tp::visit_value_type(
				[&](auto WasmValue::* p) { stack.emplace(p, 0); }, type
			);
		}
		auto return_address = call_instr.after().pos();
		auto locals_vector_size = locals_types.size() + arg_types.size();
		auto locals_pos = stack.data() + (stack.size() - locals_vector_size);
		gsl::span<T> locals_vector(locals_pos, locals_vector_size);
		frames_.emplace(func, return_address, locals_vector);
		return CodeView(func);
	}

	void return_from_expression()
	{
		assert(frames_.size() == 1u);
		_recurse_return_from_frame_unchecked();
	}

	[[nodiscard]]
	CodeView end_block()
	{
		if(const char* p = top_frame().try_branch_top(); p)
		{

		}
	}

	[[nodiscard]]
	CodeView return_from_function()
	{
		auto& frame = top_frame();
		assert(frame.is_function_call());
		auto ret_types = return_types(signature(*frame.funtion()));
		assert(frame.stack_size() >= ret_types.size());
		CodeView ret_addr = top_frame().return_address();
		_recurse_return_from_frame(return_types);
		return ret_addr;
	}

	[[nodiscard]]
	CodeView call_table_function(const WasmTable& table, const WasmFunctionSignature& sig, const WasmInstruction& instr)
	{
		assert(instr.opcode() == OpCode::CALL_INDIRECT);
		assert(frames_.empty() or top_frame().can_exectute_instruction(instr));
		auto& stack = top_stack();
		wasm_uint32_t offset = reinterpret_cast<const wasm_uint32_t&>(stack.top().get(tp::i32_c));
		const TableFunction& func = table.at(offset);
		if(func.is_null())
			throw NullTableFunctionError();

		if(func.is_wasm_function())
		{
			auto& f = func.get_wasm_function();
			if(signature(f) != sig)
				throw BadTableFunctionSignature();
			stack.pop();
			return call_wasm_function(func.get_wasm_function());
		}
		assert(func.is_c_function());
		auto& f = func.get_c_function();
		if(f.signature() != sig)
			throw BadTableFunctionSignature();

		{ /* scope */
			auto stack_size = stack.size();
			auto guard_ = make_scope_guard(
				[&, stack_size]{
					// poor man's check to make sure the c function call
					// doesn't modify 'this'.
					assert(stack.size() == stack_size);
				}
			call_c_function(f, offset);
		} /* /scope */
		return instr.after();
	}

	void call_c_function(const CFunction& cfunc) const
	{
		const auto& sig = cfunc.signature();
		auto param_types = param_types(sig);
		auto return_types = return_types(sig);
		auto& stack = top_stack();
		assert(stack.size() >= param_types.size());
		auto args = gsl::span<const T>(stack.data(), stack.size()).last(param_types.size());
		{
			auto guard_ = make_stack_size_guard(stack());
			for(LanguageType value_tp: return_types)
				stack.emplace(value_tp);
			auto results = gsl::span<T>(stack.data(), stack.size()).last(return_types.size());
			cfunc(results, args);
		}
		assert(stack.size() >= return_types.size() + param_types.size());
		std::rotate(
			stack.begin(),
			stack.begin() + return_types.size(),
			stack.begin() + return_types.size() + param_types.size()
		);
		stack.pop_n(param_types.size());
	}

	std::optional<WasmInstruction> next_instruction() const
	{ return top_frame().next_instruction(); }

	void execute(const WasmInstruction& instr, const WasmModule& module) const
	{
		if(not top_frame().is_next_instruction())
			throw std::logic_error("Instruction does not match program counter on call stack.");
		CodeView next = instr.execute(*this, module);
		top_frame().code_.advance(next);
	}

private:
	WasmStackFrame<T>& top_frame()
	{
		if(frames_.empty())
			throw std::out_of_range("Attempt to access out-of-bounds call stack frame.");
		return frames_.top();
	}

	SimpleStack<T>& top_stack()
	{
		if(frames_.empty())
			return base_stack_;
		return frames_.top().stack();
	}

	std::pair<WasmStackFrame<T>&, WasmStackFrame<T>&> top_two_frames()
	{
		if(frames_.empty())
			throw std::out_of_range("Attempt to access out-of-bounds frame.");
		auto pos = frames_.begin();
		const auto& hi = *pos++;
		if(pos == frames_.end())
			throw std::out_of_range("Attempt to access out-of-bounds frame.");
		const auto& lo = *pos++;
		return std::pair<WasmStackFrame&, WasmStackFrame&>(lo, hi);
	}

	void _recurse_return_from_frame_unchecked()
	{
		if(auto& stack = top_frame().stack(); stack.empty())
		{
			frames_.pop();
		}
		else
		{
			T ret_v = stack.pop();
			_recurse_return_from_frame_unchecked();
			top_stack().emplace(ret_v);
		}
	}
	void _recurse_return_from_frame(const gsl::span<const LanguageType>& return_types, std::size_t index = 0u)
	{
		assert(not frames_.empty());
		if(index < return_types.size())
		{
			// one of the return values from the callee's frame
			tp::visit_value_type(
				[&](auto WasmValue::* p) {
					auto ret_v = top_frame().stack_at(index, p);
					// keep recursing until we exhaust the return
					// values.  Once we've done that, pop the callee's
					// frame, and then pop the locals vector and arguments
					// off the caller's stack.
					_recurse_return_from_frame(return_types, index);
					// now that we've popped the callee's frame from the call stack,
					// and the locals vector off of the caller's frame's stack, push
					// the return values onto the callee's frame's stack.
					//
					// note that there may be no frame below the one we popped, in which
					// case, the return values are pushed onto 'this->base_stack_'
					top_stack().stack_emplace_top(p, ret_v);
				},
				return_types[return_types.size() - index]
			);
		}
		else
		{
			const auto* func_p = top_frame.function();
			assert(func_p);
			const auto& func = *func_p;
			std::size_t total_locals = param_count(func) + locals_count(func);
			// pop the top frame
			frames_.pop_front();
			assert(not frames_.empty());
			auto& new_top_frame = frames_.front();
			assert(new_top_frame.stack_size() >= total_locals);
			// pop the locals (args + locals) vector of the frame we just popped
			// off of the stack.  note that the locals vector of the current frame
			// lives on the stack of the previous frame.
			new_top_frame.stack_pop_n(total_locals);
			// push the return values of the frame we just popped...
			// ... by simply returning from _recurse_return_from_frame().  each
			// recursive call which took the if() branch above will push the return
			// values onto the new frame.
		}
	}

	const StackResource& stack_resource() const
	{ return base_stack_.get_resource(); }

	StackResource& stack_resource()
	{ return base_stack_.get_resource(); }

	frame_stack_type frames_;
	SimpleStack<T> base_stack_;
};

template <class T>
const T& local_at(const WasmCallStack<T>& self, std::size_t idx)
{
	auto locals = locals(current_frame(self));
	if(idx >= locals.size())
		throw ValidationError<std::out_of_range>("Attempt to access out-of-bounds local.");
	return locals[idx];
}

template <class T>
T& local_at(WasmCallStack<T>& self, std::size_t idx)
{
	auto locals = locals(current_frame(self));
	if(idx >= locals.size())
		throw ValidationError<std::out_of_range>("Attempt to access out-of-bounds local.");
	return locals[idx];
}

template <class T>
const auto& current_stack(const WasmCallStack<T>& self, std::size_t idx)
{ return current_stack(current_frame(self)); }

template <class T>
auto& current_stack(WasmCallStack<T>& self, std::size_t idx)
{ return current_stack(current_frame(self)); }

} /* namespace wasm */

#endif /* VM_CALL_STACK_H */


#pragma once

#include <memory>

#include <gsl/gsl>
#include "../platform/d3d.h"
#include "../infrastructure/macros.h"

namespace gfx {

class IndexBuffer {
	friend class RenderingDevice;
public:
	IndexBuffer(CComPtr<ID3D11Buffer> buffer, size_t count);
	~IndexBuffer();

	void Update(gsl::span<uint16_t> data);

	NO_COPY_OR_MOVE(IndexBuffer)
private:
	void Unlock();

	size_t mCount;
	CComPtr<ID3D11Buffer> mBuffer;
};

using IndexBufferPtr = std::shared_ptr<IndexBuffer>;

class VertexBuffer {
	friend class RenderingDevice;
	friend class BufferBinding;
public:
	VertexBuffer(CComPtr<ID3D11Buffer> vertexBufferNew, size_t size);
	~VertexBuffer();

	void Update(gsl::span<const uint8_t> data);

	template <typename T>
	void Update(gsl::span<T> data) {
		Update(gsl::span(reinterpret_cast<const uint8_t*>(&data[0]), data.size_bytes()));
	}

	NO_COPY_OR_MOVE(VertexBuffer);
private:
	size_t mSize;
	CComPtr<ID3D11Buffer> mBuffer;
};

using VertexBufferPtr = std::shared_ptr<VertexBuffer>;

}


#ifndef SEARCHPATTERNBASE_HHHHH
#define SEARCHPATTERNBASE_HHHHH

#include "../../SigScannerMemoryData.h"

#include <vector>
#include <string>
#include <memory>

#include <gsl/span>

namespace MPSig {
    namespace detail {
        class SearchPatternBase {
        public:
            virtual ~SearchPatternBase() = default;

            using gsl_span_cit_t = typename gsl::span<const char>::const_iterator;
            using gsl_span_crit_t = typename gsl::span<const char>::const_reverse_iterator;

            template<typename It>
            struct ExecFirstResult {
                bool success; // If it was a success
                It rangeBegin; // The first valid iterator
                It rangeEnd; // The next depended iterator point (from which all other patterns depend on)
            };

            // ExecFirst can look up begin to end fully, without restrictions
            virtual ExecFirstResult<gsl_span_cit_t> ExecFirst(const MPSig::SigScannerMemoryData& data, gsl_span_cit_t begin, gsl_span_cit_t end) const = 0;
            virtual ExecFirstResult<gsl_span_crit_t> ExecFirst(const MPSig::SigScannerMemoryData& data, gsl_span_crit_t begin, gsl_span_crit_t end) const = 0;

            // ExecDepend must only check the current location as it depends on a search success
            virtual ExecFirstResult<gsl_span_cit_t> ExecDepend(const MPSig::SigScannerMemoryData& data, gsl_span_cit_t begin, gsl_span_cit_t end) const = 0;
            virtual ExecFirstResult<gsl_span_crit_t> ExecDepend(const MPSig::SigScannerMemoryData& data, gsl_span_crit_t begin, gsl_span_crit_t end) const = 0;

            virtual std::unique_ptr<SearchPatternBase> Clone() const = 0;
        };

    }
}

#endif


#pragma once

#include <gsl/gsl_assert>
#include <src/util/Logging.h>

#ifndef NDEBUG
    //#define RNR_EXPECTS(x) Expects(x)
    //#define RNR_ENSURES(x) Ensures(x)

/**
 * specifies a precondition that is expected to be true
 * terminates the program if the condition is not met and the program is compiled in debug mode
 */
#define RNR_EXPECTS(x) \
  do { \
    if (!(x)) { \
        LOG(ERROR) << "assertion failed: " << #x; \
        std::terminate(); \
    } \
  } while (0)

/**
* specifies a postcondition that is expected to be made true by the code block above
* terminates the program if the condition is not met and the program is compiled in debug mode
*/
#define RNR_ENSURES(x) \
  do { \
    if (!(x)) { \
        LOG(ERROR) << "assertion failed: " << #x; \
        std::terminate(); \
    } \
  } while (0)
#else
    #define RNR_EXPECTS(x)
    #define RNR_ENSURES(x)
#endif


/// @brief Hyperion logging facilities.
///
/// Hyperion's logging facilities are robust and composable.
/// Behavioral (Policy) configuration is configurable at compile time with via template parameters,
/// and output configuration is configurable by supplying the desired `Sink`s on creation
#pragma once

#include <Hyperion/BasicTypes.h>
#include <Hyperion/FmtIO.h>
#include <Hyperion/Logger.h>
#include <Hyperion/Option.h>
#include <Hyperion/logging/Config.h>
#include <Hyperion/logging/Entry.h>
#include <Hyperion/logging/Queue.h>
#include <Hyperion/logging/Sink.h>
#include <Hyperion/synchronization/ReadWriteLock.h>
#include <atomic>
#include <chrono>
#include <filesystem>
#include <gsl/gsl>
#include <iostream>
#include <memory>
#include <mutex>
#include <system_error>
#include <thread>

namespace hyperion {

	/// @brief Possible Error categories that can occur when using the logger
	enum class LoggerErrorCategory : i8 {
		/// @brief No Error occurred
		Success = 0,
		/// @brief failed to queue the entry for logging
		QueueingError,
		/// @brief the requested log level for the entry is
		/// lower than the minimum level for the logger
		LogLevelError,
		LoggerNotInitialized,
		Unknown = -1
	};

	/// @brief Alias for the Error type we might receive from the internal queue
	using QueueError = LoggingQueueError;

	class LoggerErrorDomain {
	  public:
		using value_type = LoggerErrorCategory;
		using LoggerStatusCode = error::StatusCode<LoggerErrorDomain>;
		using LoggerErrorCode = error::ErrorCode<LoggerErrorDomain>;

		static const constexpr char (&UUID)[error::num_chars_in_uuid] // NOLINT
			= "045dd371-9552-4ce1-bd4d-8e95b654fbe0";

		static constexpr u64 ID = error::parse_uuid_from_string(UUID);

		constexpr LoggerErrorDomain() noexcept = default;
		explicit constexpr LoggerErrorDomain(u64 uuid) noexcept : m_uuid(uuid) {
		}
		explicit constexpr LoggerErrorDomain(const error::UUIDString auto& uuid) noexcept
			: m_uuid(error::parse_uuid_from_string(uuid)) {
		}
		constexpr LoggerErrorDomain(const LoggerErrorDomain&) noexcept = default;
		constexpr LoggerErrorDomain(LoggerErrorDomain&&) noexcept = default;
		constexpr ~LoggerErrorDomain() noexcept = default;

		[[nodiscard]] inline constexpr auto id() const noexcept -> u64 {
			return m_uuid;
		}

		[[nodiscard]] inline constexpr auto name() const noexcept -> std::string_view { // NOLINT
			return "LoggerErrorDomain";
		}

		[[nodiscard]] inline constexpr auto message(value_type code) // NOLINT
			const noexcept -> std::string_view {
			if(code == value_type::Success) {
				return "Success";
			}
			else if(code == value_type::QueueingError) {
				return "Logger failed to queue log entry.";
			}
			else if(code == value_type::LogLevelError) {
				return "Requested log level for entry is lower than minimum level configured for "
					   "logger.";
			}
			else {
				return "Unknown Logger error.";
			}
		}

		[[nodiscard]] inline constexpr auto message(const LoggerStatusCode& code) // NOLINT
			const noexcept -> std::string_view {
			return message(code.code());
		}

		[[nodiscard]] inline constexpr auto is_error(const LoggerStatusCode& code) // NOLINT
			const noexcept -> bool {
			return code.code() != value_type::Success;
		}

		[[nodiscard]] inline constexpr auto is_success(const LoggerStatusCode& code) // NOLINT
			const noexcept -> bool {
			return code.code() == value_type::Success;
		}

		template<typename Domain2>
		[[nodiscard]] inline constexpr auto
		are_equivalent(const LoggerStatusCode& lhs,
					   const error::StatusCode<Domain2>& rhs) const noexcept -> bool {
			if constexpr(concepts::Same<LoggerStatusCode, error::StatusCode<Domain2>>) {
				return lhs.code() == rhs.code();
			}
			else {
				return false;
			}
		}

		[[nodiscard]] inline constexpr auto as_generic_code(const LoggerStatusCode& code) // NOLINT
			const noexcept -> error::GenericStatusCode {
			if(code.code() == value_type::Success || code.code() == value_type::Unknown) {
				return make_status_code(static_cast<error::Errno>(code.code()));
			}
			else {
				return make_status_code(error::Errno::Unknown);
			}
		}

		[[nodiscard]] inline static constexpr auto success_value() noexcept -> value_type {
			return value_type::Success;
		}

		template<typename Domain>
		friend inline constexpr auto
		operator==(const LoggerErrorDomain& lhs, const Domain& rhs) noexcept -> bool {
			return rhs.id() == lhs.id();
		}

		template<typename Domain>
		friend inline constexpr auto
		operator!=(const LoggerErrorDomain& lhs, const Domain& rhs) noexcept -> bool {
			return rhs.id() != lhs.id();
		}

		constexpr auto operator=(const LoggerErrorDomain&) noexcept -> LoggerErrorDomain& = default;
		constexpr auto operator=(LoggerErrorDomain&&) noexcept -> LoggerErrorDomain& = default;

	  private:
		u64 m_uuid = ID;
	};

	using LoggerStatusCode = LoggerErrorDomain::LoggerStatusCode;
	using LoggerErrorCode = LoggerErrorDomain::LoggerErrorCode;
	using LoggerError = error::Error<LoggerErrorDomain>;

} // namespace hyperion

template<>
inline constexpr auto
make_status_code_domain<hyperion::LoggerErrorDomain>() noexcept -> hyperion::LoggerErrorDomain {
	return {};
}

namespace hyperion {
	static_assert(error::StatusCodeDomain<LoggerErrorDomain>);

	template<>
	struct error::status_code_enum_info<LoggerErrorCategory> {
		using domain_type = LoggerErrorDomain;
		static constexpr bool value = true;
	};

	IGNORE_PADDING_START

	namespace detail {
#if HYPERION_HAS_JTHREAD
		using thread = std::jthread;
#else
		using thread = std::thread;
#endif
		static constexpr fmt::text_style MESSAGE_STYLE = fmt::fg(fmt::color::white);
		static constexpr fmt::text_style TRACE_STYLE = fmt::fg(fmt::color::steel_blue);
		static constexpr fmt::text_style INFO_STYLE
			= fmt::fg(fmt::color::light_green) | fmt::emphasis::italic;
		static constexpr fmt::text_style WARN_STYLE
			= fmt::fg(fmt::color::orange) | fmt::emphasis::bold;
		static constexpr fmt::text_style ERROR_STYLE
			= fmt::fg(fmt::color::red) | fmt::emphasis::bold;

		IGNORE_UNNEEDED_INTERNAL_DECL_START
		[[nodiscard]] inline static auto create_time_stamp() noexcept -> std::string {
			return fmt::format("[{:%Y-%m-%d|%H-%M-%S}]", fmt::localtime(std::time(nullptr)));
		}

		inline static auto
		create_default_sinks() noexcept -> Sinks { // NOLINT(bugprone-exception-escape)
			auto file = FileSink::create_file();
			auto file_sink = make_sink<FileSink>(file.expect("Failed to create default log file"));
			auto stdout_sink = make_sink<StdoutSink<>>();
			auto stderr_sink = make_sink<StderrSink<>>();
			return Sinks({std::move(file_sink), std::move(stdout_sink), std::move(stderr_sink)});
		}
		IGNORE_UNNEEDED_INTERNAL_DECL_STOP

		IGNORE_UNUSED_TEMPLATES_START
		template<LogLevel Level, typename... Args>
		inline static auto
		format_entry(Option<usize> thread_id, // NOLINT(bugprone-exception-escape)
					 fmt::format_string<Args...>&& format_string,
					 Args&&... args) noexcept -> Entry {
			const auto timestamp = create_time_stamp();
			const auto entry = fmt::format(format_string, std::forward<Args>(args)...);
			const auto id = thread_id.is_some() ?
								thread_id.unwrap() :
								  std::hash<std::thread::id>()(std::this_thread::get_id());

			std::string log_type;
			if constexpr(Level == LogLevel::MESSAGE) {
				log_type = "MESSAGE"s;
			}
			else if constexpr(Level == LogLevel::TRACE) {
				log_type = "TRACE"s;
			}
			else if constexpr(Level == LogLevel::INFO) {
				log_type = "INFO"s;
			}
			else if constexpr(Level == LogLevel::WARN) {
				log_type = "WARN"s;
			}
			else if constexpr(Level == LogLevel::ERROR) {
				log_type = "ERROR"s;
			}
			return make_entry<entry_level_t<Level>>("{0}  [Thread ID: {1}] [{2}]: {3}",
													timestamp,
													id,
													log_type,
													entry);
		}
		IGNORE_UNUSED_TEMPLATES_STOP

		template<LogLevel MinimumLevel = DefaultLogParameters::minimum_level,
				 LogThreadingPolicy ThreadingPolicy = DefaultLogParameters::threading_policy,
				 LogAsyncPolicy AsyncPolicy = DefaultLogParameters::async_policy,
				 usize QueueSize = DefaultLogParameters::queue_size>
		class LogBase;

		template<LogLevel MinimumLevel, LogAsyncPolicy AsyncPolicy>
		class LogBase<MinimumLevel, LogThreadingPolicy::SingleThreaded, AsyncPolicy> {
		  public:
			static constexpr auto THREADING_POLICY = LogThreadingPolicy::SingleThreaded;
			static constexpr auto ASYNC_POLICY = AsyncPolicy;
			static constexpr auto MINIMUM_LEVEL = MinimumLevel;

			LogBase() : LogBase(create_default_sinks()) {
			}
			explicit LogBase(Sinks&& sinks) noexcept : m_sinks(std::move(sinks)) {
			}
			LogBase(const LogBase&) = delete;
			LogBase(LogBase&&) noexcept = default;
			~LogBase() noexcept = default;

			template<LogLevel Level, typename... Args>
			inline auto log(Option<usize> thread_id,
							fmt::format_string<Args...>&& format_string,
							Args&&... args) noexcept -> void {
				const auto message = format_entry<Level>(std::move(thread_id),
														 std::move(format_string),
														 std::forward<Args>(args)...);

				std::for_each(m_sinks.begin(), m_sinks.end(), [&](Sink& sink) noexcept -> void {
					sink.sink(message);
				});
			}

			template<LogLevel, typename... Args>
			inline auto
			log(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept -> void {
				return log(None(), std::move(format_string), std::forward<Args>(args)...);
			}

			inline auto flush() const noexcept -> void {
				// intentionally does nothing
			}

			auto operator=(const LogBase&) -> LogBase& = delete;
			auto operator=(LogBase&&) noexcept -> LogBase& = default;

		  private:
			Sinks m_sinks;
		};

		template<LogLevel MinimumLevel, LogAsyncPolicy AsyncPolicy, usize QueueSize>
		class LogBase<MinimumLevel,
					  LogThreadingPolicy::SingleThreadedAsync,
					  AsyncPolicy,
					  QueueSize> {
		  public:
			static constexpr auto THREADING_POLICY = LogThreadingPolicy::SingleThreadedAsync;
			static constexpr auto ASYNC_POLICY = AsyncPolicy;
			static constexpr auto MINIMUM_LEVEL = MinimumLevel;
			static constexpr usize QUEUE_SIZE = QueueSize;

			LogBase() : LogBase(create_default_sinks()) {
			}
			explicit LogBase(Sinks&& sinks) noexcept : m_sinks(std::move(sinks)), m_queue() {
#if HYPERION_HAS_JTHREAD
				m_logging_thread = detail::thread(
					[&](const std::stop_token& token) { message_thread_function(token); });
#else
				m_logging_thread = detail::thread([&]() { message_thread_function(); });
#endif
			}
			LogBase(const LogBase&) = delete;
			LogBase(LogBase&& logger) noexcept {
				std::atomic_thread_fence(std::memory_order_acquire);
				logger.request_thread_stop();
				logger.m_logging_thread.join();
				// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
				m_sinks = std::move(logger.m_sinks);
				// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
				m_queue = std::move(logger.m_queue);
#if HYPERION_HAS_JTHREAD
				m_logging_thread = detail::thread(
					[&](const std::stop_token& token) { message_thread_function(token); });
#else
				m_logging_thread = detail::thread([&]() { message_thread_function(); });
#endif
				std::atomic_thread_fence(std::memory_order_release);
			}
			~LogBase() noexcept {
				request_thread_stop();
				m_logging_thread.join();
			}

			template<LogLevel Level, typename... Args>
			inline auto log(Option<usize> thread_id,
							fmt::format_string<Args...>&& format_string,
							Args&&... args) noexcept {
				if constexpr(Level >= MINIMUM_LEVEL && MINIMUM_LEVEL != LogLevel::DISABLED) {
					auto message = format_entry<Level>(std::move(thread_id),
													   std::move(format_string),
													   std::forward<Args>(args)...);
					if constexpr(ASYNC_POLICY == LogAsyncPolicy::DropWhenFull) {
						return log_dropping(std::move(message));
					}
					else if constexpr(ASYNC_POLICY == LogAsyncPolicy::FlushWhenFull) {
						log_flushing(std::move(message));
					}
					else {
						log_overwriting(std::move(message));
					}
				}
				else {
					ignore(format_string, std::forward<Args>(args)...);
					return Err(LoggerError(make_error_code(LoggerErrorCategory::LogLevelError)));
				}
			}

			template<LogLevel, typename... Args>
			inline auto log(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
				return log(None(), std::move(format_string), std::forward<Args>(args)...);
			}

			inline auto flush() noexcept -> void {
				m_flush.store(true);
			}

			auto operator=(const LogBase&) -> LogBase& = delete;
			auto operator=(LogBase&& logger) noexcept -> LogBase& {
				std::atomic_thread_fence(std::memory_order_acquire);
				if(this == &logger) {
					return *this;
				}

				logger.request_thread_stop();
				logger.m_logging_thread.join();
				m_sinks = std::move(logger.m_sinks);
				m_queue = std::move(logger.m_queue);

				std::atomic_thread_fence(std::memory_order_release);
				return *this;
			}

		  private:
			[[nodiscard]] inline static consteval auto get_queue_policy() noexcept -> QueuePolicy {
				if constexpr(ASYNC_POLICY == LogAsyncPolicy::DropWhenFull
							 || ASYNC_POLICY == LogAsyncPolicy::FlushWhenFull)
				{
					return QueuePolicy::ErrWhenFull;
				}
				else {
					return QueuePolicy::OverwriteWhenFull;
				}
			}
			using Queue = LoggingQueue<Entry, get_queue_policy(), QUEUE_SIZE>;

			Sinks m_sinks;
			Queue m_queue;
			std::atomic_bool m_flush = false;
#if !HYPERION_HAS_JTHREAD
			std::atomic_bool m_exit_flag = false;
#endif
			detail::thread m_logging_thread;

			inline auto request_thread_stop() noexcept -> void {
#if HYPERION_HAS_JTHREAD
				ignore(m_logging_thread.request_stop());
#else
				m_exit_flag.store(true);
#endif
			}

			inline auto log_dropping(Entry&& message) noexcept -> Result<bool, LoggerError>
			requires(ASYNC_POLICY == LogAsyncPolicy::DropWhenFull) {
				return m_queue.push(std::move(message))
					.map_err([]([[maybe_unused]] const QueueError& error) {
						return LoggerError(LoggerErrorCategory::QueueingError);
					});
			}

			inline auto log_overwriting(Entry&& message) noexcept
				-> void requires(ASYNC_POLICY == LogAsyncPolicy::OverwriteWhenFull) {
				m_queue.push(std::move(message));
			}

			inline auto log_flushing(Entry&& message) noexcept
				-> void requires(ASYNC_POLICY == LogAsyncPolicy::FlushWhenFull) {
				if(m_queue.full()) {
					m_flush.store(true);
				}

				const auto& mess = message;
				while(!m_queue.push(mess)) {
				}
			}

			inline auto try_read() noexcept -> Result<Entry, QueueError> {
				return m_queue.read();
			}

#if HYPERION_HAS_JTHREAD
			// NOLINTNEXTLINE(readability-function-cognitive-complexity)
			inline auto message_thread_function(const std::stop_token& token) noexcept -> void {
				while(!token.stop_requested()) {
#else
			// NOLINTNEXTLINE(readability-function-cognitive-complexity)
			inline auto message_thread_function() noexcept -> void {
				while(!m_exit_flag.load()) {
#endif

					auto res = try_read();
					if(res) {
						const auto message = res.unwrap();
						std::atomic_thread_fence(std::memory_order_acquire);
						std::for_each(m_sinks.begin(),
									  m_sinks.end(),
									  [&](Sink& sink) noexcept -> void { sink.sink(message); });
						std::atomic_thread_fence(std::memory_order_release);
					}

					if(m_flush.load()) {
						m_flush.store(false);
						do {
							auto res2 = m_queue.read();
							if(res2) {
								const auto message = res2.unwrap();
								std::atomic_thread_fence(std::memory_order_acquire);
								std::for_each(
									m_sinks.begin(),
									m_sinks.end(),
									[&](Sink& sink) noexcept -> void { sink.sink(message); });
								std::atomic_thread_fence(std::memory_order_release);
							}
							else {
								break;
							}
						} while(true);
					}
				}
				do {
					auto res2 = m_queue.read();
					if(res2) {
						const auto message = res2.unwrap();
						std::atomic_thread_fence(std::memory_order_acquire);
						std::for_each(m_sinks.begin(),
									  m_sinks.end(),
									  [&](Sink& sink) noexcept -> void { sink.sink(message); });
						std::atomic_thread_fence(std::memory_order_release);
					}
					else {
						break;
					}
				} while(true);
			}
		};

		template<LogLevel MinimumLevel, LogAsyncPolicy AsyncPolicy>
		class LogBase<MinimumLevel, LogThreadingPolicy::MultiThreaded, AsyncPolicy> {
		  public:
			static constexpr auto THREADING_POLICY = LogThreadingPolicy::MultiThreaded;
			static constexpr auto ASYNC_POLICY = AsyncPolicy;
			static constexpr auto MINIMUM_LEVEL = MinimumLevel;

			LogBase() : LogBase(create_default_sinks()) {
			}
			explicit LogBase(Sinks&& sinks) noexcept : m_sinks(std::move(sinks)) {
			}
			LogBase(const LogBase&) = delete;
			LogBase(LogBase&&) noexcept = default;
			~LogBase() noexcept = default;

			template<LogLevel Level, typename... Args>
			inline auto log(Option<usize> thread_id,
							fmt::format_string<Args...>&& format_string,
							Args&&... args) noexcept -> void {
				const auto message = format_entry<Level>(std::move(thread_id),
														 std::move(format_string),
														 std::forward<Args>(args)...);

				{
					auto sinks_guard = m_sinks.write();
					std::for_each(sinks_guard->begin(),
								  sinks_guard->end(),
								  [&](Sink& sink) noexcept -> void { sink.sink(message); });
				}
			}

			template<LogLevel, typename... Args>
			inline auto
			log(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept -> void {
				return log(None(), std::move(format_string), std::forward<Args>(args)...);
			}

			inline auto flush() const noexcept -> void {
				// intentionally does nothing
			}

			auto operator=(const LogBase&) -> LogBase& = delete;
			auto operator=(LogBase&&) noexcept -> LogBase& = default;

		  private:
			ReadWriteLock<Sinks> m_sinks;
		};

		template<LogLevel MinimumLevel, LogAsyncPolicy AsyncPolicy, usize QueueSize>
		class LogBase<MinimumLevel,
					  LogThreadingPolicy::MultiThreadedAsync,
					  AsyncPolicy,
					  QueueSize> {
		  public:
			static constexpr auto THREADING_POLICY = LogThreadingPolicy::MultiThreadedAsync;
			static constexpr auto ASYNC_POLICY = AsyncPolicy;
			static constexpr auto MINIMUM_LEVEL = MinimumLevel;
			static constexpr usize QUEUE_SIZE = QueueSize;

			LogBase() : LogBase(create_default_sinks()) {
			}
			explicit LogBase(Sinks&& sinks) noexcept : m_sinks(std::move(sinks)), m_queue() {
#if HYPERION_HAS_JTHREAD
				m_logging_thread = detail::thread(
					[&](const std::stop_token& token) { message_thread_function(token); });
#else
				m_logging_thread = detail::thread([&]() { message_thread_function(); });
#endif
			}
			LogBase(const LogBase&) = delete;
			LogBase(LogBase&& logger) noexcept {
				std::atomic_thread_fence(std::memory_order_seq_cst);
				logger.request_thread_stop();
				logger.m_logging_thread.join();
				// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
				m_sinks = std::move(logger.m_sinks);
				// NOLINTNEXTLINE(cppcoreguidelines-prefer-member-initializer)
				m_queue = std::move(logger.m_queue);
#if HYPERION_HAS_JTHREAD
				m_logging_thread = detail::thread(
					[&](const std::stop_token& token) { message_thread_function(token); });
#else
				m_logging_thread = detail::thread([&]() { message_thread_function(); });
#endif
				std::atomic_thread_fence(std::memory_order_seq_cst);
			}
			~LogBase() noexcept {
				request_thread_stop();
				m_logging_thread.join();
			}

			template<LogLevel Level, typename... Args>
			inline auto log(Option<usize> thread_id,
							fmt::format_string<Args...>&& format_string,
							Args&&... args) noexcept {
				if constexpr(Level >= MINIMUM_LEVEL && MINIMUM_LEVEL != LogLevel::DISABLED) {
					auto message = format_entry<Level>(std::move(thread_id),
													   std::move(format_string),
													   std::forward<Args>(args)...);
					if constexpr(ASYNC_POLICY == LogAsyncPolicy::DropWhenFull) {
						return log_dropping(std::move(message));
					}
					else if constexpr(ASYNC_POLICY == LogAsyncPolicy::FlushWhenFull) {
						log_flushing(std::move(message));
					}
					else {
						log_overwriting(std::move(message));
					}
				}
				else {
					ignore(format_string, std::forward<Args>(args)...);
					return Err(LoggerError(make_error_code(LoggerErrorCategory::LogLevelError)));
				}
			}

			template<LogLevel, typename... Args>
			inline auto log(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
				return log(None(), std::move(format_string), std::forward<Args>(args)...);
			}

			inline auto flush() noexcept -> void {
				m_flush.store(true);
			}

			auto operator=(const LogBase&) -> LogBase& = delete;
			auto operator=(LogBase&& logger) noexcept -> LogBase& {
				std::atomic_thread_fence(std::memory_order_seq_cst);
				if(this == &logger) {
					return *this;
				}

				logger.request_thread_stop();
				logger.m_logging_thread.join();
				m_sinks = std::move(logger.m_sinks);
				m_queue = std::move(logger.m_queue);

				std::atomic_thread_fence(std::memory_order_seq_cst);
				return *this;
			}

		  private:
			[[nodiscard]] inline static consteval auto get_queue_policy() noexcept -> QueuePolicy {
				if constexpr(ASYNC_POLICY == LogAsyncPolicy::DropWhenFull
							 || ASYNC_POLICY == LogAsyncPolicy::FlushWhenFull)
				{
					return QueuePolicy::ErrWhenFull;
				}
				else {
					return QueuePolicy::OverwriteWhenFull;
				}
			}
			using Queue = LoggingQueue<Entry, get_queue_policy(), QUEUE_SIZE>;

			Sinks m_sinks;
			Queue m_queue;
			std::atomic_bool m_flush = false;
#if !HYPERION_HAS_JTHREAD
			std::atomic_bool m_exit_flag = false;
#endif
			detail::thread m_logging_thread;

			inline auto request_thread_stop() noexcept -> void {
				std::atomic_thread_fence(std::memory_order_seq_cst);
#if HYPERION_HAS_JTHREAD
				ignore(m_logging_thread.request_stop());
#else
				m_exit_flag.store(true);
#endif
				std::atomic_thread_fence(std::memory_order_seq_cst);
			}

			inline auto log_dropping(Entry&& message) noexcept -> Result<bool, LoggerError>
			requires(ASYNC_POLICY == LogAsyncPolicy::DropWhenFull) {
				std::atomic_thread_fence(std::memory_order_seq_cst);
				auto res = m_queue.push(std::move(message))
							   .map_err([]([[maybe_unused]] const QueueError& error) {
								   return LoggerError(LoggerErrorCategory::QueueingError);
							   });
				std::atomic_thread_fence(std::memory_order_seq_cst);
				return res;
			}

			inline auto log_overwriting(Entry&& message) noexcept
				-> void requires(ASYNC_POLICY == LogAsyncPolicy::OverwriteWhenFull) {
				std::atomic_thread_fence(std::memory_order_seq_cst);
				m_queue.push(std::move(message));
				std::atomic_thread_fence(std::memory_order_seq_cst);
			}

			inline auto log_flushing(Entry&& message) noexcept
				-> void requires(ASYNC_POLICY == LogAsyncPolicy::FlushWhenFull) {
				std::atomic_thread_fence(std::memory_order_seq_cst);
				if(m_queue.full()) {
					m_flush.store(true);
				}

				const auto& mess = message;
				while(!m_queue.push(mess)) {
				}
				std::atomic_thread_fence(std::memory_order_seq_cst);
			}

			inline auto try_read() noexcept -> Result<Entry, QueueError> {
				std::atomic_thread_fence(std::memory_order_seq_cst);
				return m_queue.read();
			}

#if HYPERION_HAS_JTHREAD
			// NOLINTNEXTLINE(readability-function-cognitive-complexity)
			inline auto message_thread_function(const std::stop_token& token) noexcept -> void {
				while(!token.stop_requested()) {
#else
			// NOLINTNEXTLINE(readability-function-cognitive-complexity)
			inline auto message_thread_function() noexcept -> void {
				while(!m_exit_flag.load()) {
#endif

					auto res = try_read();
					if(res) {
						std::atomic_thread_fence(std::memory_order_seq_cst);
						const auto message = res.unwrap();
						std::for_each(m_sinks.begin(),
									  m_sinks.end(),
									  [&](Sink& sink) noexcept -> void { sink.sink(message); });
						std::atomic_thread_fence(std::memory_order_seq_cst);
					}

					if(m_flush.load()) {
						m_flush.store(false);
						do {
							std::atomic_thread_fence(std::memory_order_seq_cst);
							auto res2 = m_queue.read();
							if(res2) {
								const auto message = res2.unwrap();
								std::for_each(
									m_sinks.begin(),
									m_sinks.end(),
									[&](Sink& sink) noexcept -> void { sink.sink(message); });
								std::atomic_thread_fence(std::memory_order_seq_cst);
							}
							else {
								std::atomic_thread_fence(std::memory_order_seq_cst);
								break;
							}
						} while(true);
					}
				}
				do {
					std::atomic_thread_fence(std::memory_order_seq_cst);
					auto res2 = m_queue.read();
					if(res2) {
						const auto message = res2.unwrap();
						std::for_each(m_sinks.begin(),
									  m_sinks.end(),
									  [&](Sink& sink) noexcept -> void { sink.sink(message); });
						std::atomic_thread_fence(std::memory_order_seq_cst);
					}
					else {
						std::atomic_thread_fence(std::memory_order_seq_cst);
						break;
					}
				} while(true);
			}
		};
	} // namespace detail

	/// @brief Hyperion logging type for formatted logging.
	/// Uses fmtlib/fmt for entry formatting and stylizing
	///
	/// @tparam LogParameters - The parameters for how this logger should operate
	template<LoggerParametersType LogParameters = DefaultLogParameters>
	class Logger : public detail::LogBase<LogParameters::minimum_level,
										  LogParameters::threading_policy,
										  LogParameters::async_policy,
										  LogParameters::queue_size> {
	  public:
		static constexpr LogThreadingPolicy THREADING_POLICY = LogParameters::threading_policy;
		static constexpr LogAsyncPolicy ASYNC_POLICY = LogParameters::async_policy;
		static constexpr LogLevel MINIMUM_LEVEL = LogParameters::minimum_level;
		static constexpr usize QUEUE_SIZE = LogParameters::queue_size;
		using LogBase = detail::LogBase<LogParameters::minimum_level,
										LogParameters::threading_policy,
										LogParameters::async_policy,
										LogParameters::queue_size>;

		Logger() = default;
		explicit Logger(Sinks&& sinks) noexcept : LogBase(std::move(sinks)) {
		}
		Logger(const Logger& logger) noexcept = delete;
		Logger(Logger&& logger) noexcept = default;

		~Logger() noexcept = default;

		template<typename... Args>
		inline auto message(const Option<usize>& thread_id,
							fmt::format_string<Args...>&& format_string,
							Args&&... args) noexcept {
			return this->template log<LogLevel::MESSAGE>(thread_id,
														 std::move(format_string),
														 std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto message(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return message(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto trace(const Option<usize>& thread_id,
						  fmt::format_string<Args...>&& format_string,
						  Args&&... args) noexcept {
			return this->template log<LogLevel::TRACE>(thread_id,
													   std::move(format_string),
													   std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto trace(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return trace(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto info(const Option<usize>& thread_id,
						 fmt::format_string<Args...>&& format_string,
						 Args&&... args) noexcept {
			return this->template log<LogLevel::INFO>(thread_id,
													  std::move(format_string),
													  std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto info(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return info(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto warn(const Option<usize>& thread_id,
						 fmt::format_string<Args...>&& format_string,
						 Args&&... args) noexcept {
			return this->template log<LogLevel::WARN>(thread_id,
													  std::move(format_string),
													  std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto warn(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return warn(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto error(const Option<usize>& thread_id,
						  fmt::format_string<Args...>&& format_string,
						  Args&&... args) noexcept {
			return this->template log<LogLevel::ERROR>(thread_id,
													   std::move(format_string),
													   std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline auto error(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return error(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		auto operator=(const Logger& logger) noexcept -> Logger& = delete;
		auto operator=(Logger&& logger) noexcept -> Logger& = default;
	};
	IGNORE_PADDING_STOP

	IGNORE_UNUSED_TEMPLATES_START

#ifndef HYPERION_LOG_GLOBAL_LOGGER_PARAMETERS
	// NOLINTNEXTLINE(cppcoreguidelines-macro-usage)
	#define HYPERION_LOG_GLOBAL_LOGGER_PARAMETERS DefaultLogParameters
#endif

	struct GlobalLog {
		using Parameters = HYPERION_LOG_GLOBAL_LOGGER_PARAMETERS;
		static UniquePtr<Logger<Parameters>> GLOBAL_LOGGER; // NOLINT

		[[nodiscard]] inline static auto
		get_global_logger() noexcept -> Result<Logger<Parameters>*, LoggerError> {
			if(GLOBAL_LOGGER == nullptr) {
				return Err(LoggerError(LoggerErrorCategory::LoggerNotInitialized));
			}
			return Ok(GLOBAL_LOGGER.get());
		}

		inline static auto set_global_logger(Logger<Parameters>&& logger) noexcept -> void {
			GLOBAL_LOGGER = make_unique<Logger<Parameters>>(std::move(logger));
		}

		template<typename... Args>
		inline static auto MESSAGE(const Option<usize>& thread_id,
								   fmt::format_string<Args...>&& format_string,
								   Args&&... args) noexcept {
			return get_global_logger()
				.expect("Global Logger not initialized!")
				->message(thread_id, std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto
		MESSAGE(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return MESSAGE(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto TRACE(const Option<usize>& thread_id,
								 fmt::format_string<Args...>&& format_string,
								 Args&&... args) noexcept {
			return get_global_logger()
				.expect("Global Logger not initialized!")
				->trace(thread_id, std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto
		TRACE(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return TRACE(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto INFO(const Option<usize>& thread_id,
								fmt::format_string<Args...>&& format_string,
								Args&&... args) noexcept {
			return get_global_logger()
				.expect("Global Logger not initialized!")
				->info(thread_id, std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto
		INFO(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return INFO(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto WARN(const Option<usize>& thread_id,
								fmt::format_string<Args...>&& format_string,
								Args&&... args) noexcept {
			return get_global_logger()
				.expect("Global Logger not initialized!")
				->warn(thread_id, std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto
		WARN(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return WARN(None(), std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto ERROR(const Option<usize>& thread_id,
								 fmt::format_string<Args...>&& format_string,
								 Args&&... args) noexcept {
			return get_global_logger()
				.expect("Global Logger not initialized!")
				->error(thread_id, std::move(format_string), std::forward<Args>(args)...);
		}

		template<typename... Args>
		inline static auto
		ERROR(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
			return ERROR(None(), std::move(format_string), std::forward<Args>(args)...);
		}
	};

	UniquePtr<Logger<GlobalLog::Parameters>> GlobalLog::GLOBAL_LOGGER = nullptr; // NOLINT

	template<typename... Args>
	inline auto MESSAGE(const Option<usize>& thread_id,
						fmt::format_string<Args...>&& format_string,
						Args&&... args) noexcept {
		return GlobalLog::MESSAGE(thread_id, std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline static auto
	MESSAGE(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
		return GlobalLog::MESSAGE(None(), std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline static auto TRACE(const Option<usize>& thread_id,
							 fmt::format_string<Args...>&& format_string,
							 Args&&... args) noexcept {
		return GlobalLog::TRACE(thread_id, std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline static auto TRACE(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
		return GlobalLog::TRACE(None(), std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline auto INFO(const Option<usize>& thread_id,
					 fmt::format_string<Args...>&& format_string,
					 Args&&... args) noexcept {
		return GlobalLog::INFO(thread_id, std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline auto INFO(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
		return GlobalLog::INFO(None(), std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline auto WARN(const Option<usize>& thread_id,
					 fmt::format_string<Args...>&& format_string,
					 Args&&... args) noexcept {
		return GlobalLog::WARN(thread_id, std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline auto WARN(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
		return GlobalLog::WARN(None(), std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline auto ERROR(const Option<usize>& thread_id,
					  fmt::format_string<Args...>&& format_string,
					  Args&&... args) noexcept {
		return GlobalLog::ERROR(thread_id, std::move(format_string), std::forward<Args>(args)...);
	}

	template<typename... Args>
	inline auto ERROR(fmt::format_string<Args...>&& format_string, Args&&... args) noexcept {
		return GlobalLog::ERROR(None(), std::move(format_string), std::forward<Args>(args)...);
	}

	IGNORE_UNUSED_TEMPLATES_STOP
} // namespace hyperion


#pragma once
#include <gsl/gsl>
#include <map>
#include "halley/text/halleystring.h"
#include "halley/maths/vector4.h"

namespace Halley
{
	class Path;
	class Image;
	struct ImageData;

	class AsepriteExternalReader
	{
	public:
		static std::vector<ImageData> importAseprite(String baseName, gsl::span<const gsl::byte> fileData, bool trim);

	private:
		static std::vector<ImageData> loadImagesFromPath(Path tmp, bool crop);
		static std::map<int, int> getSpriteDurations(Path jsonPath);
		static void processFrameData(String baseName, std::vector<ImageData>& frameData, std::map<int, int> durations);
	};

	class AsepriteReader
	{
	public:
		static std::vector<ImageData> importAseprite(String baseName, gsl::span<const gsl::byte> fileData, bool trim);
	};
}


/*
   Copyright [2021] [IBM Corporation]
   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.
*/

#ifndef __MM_PLUGIN_ITF_H__
#define __MM_PLUGIN_ITF_H__

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-variable"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wunused-function"

#define PUBLIC __attribute__((__visibility__("default")))

#if defined(__cplusplus)
#pragma GCC diagnostic ignored "-Weffc++"
#include <common/byte_span.h>
#include <gsl/span>
#include <functional>
#endif

#include <stdlib.h>
#include <assert.h>

#if defined(__cplusplus)
extern "C"
{
#endif

  typedef int status_t;
  typedef void * mm_plugin_heap_t;
  typedef void (*request_memory_callback_t)(void * param, size_t alignment, size_t size_hint, void * addr_hint);
  typedef void (*release_memory_callback_t)(void * param, void * addr, size_t size);

  /**
   * Initialize mm library
   *
   * @return S_OK, E_FAIL
   */
  status_t mm_plugin_init();

  /**
   * Create a heap instance
   *
   * @param params Constructor parameters (e.g., JSON)
   * @param root_ptr Root point for persistent heaps
   * @param out_heap Heap context
   *
   * @return S_OK, E_FAIL
   */
  status_t mm_plugin_create(const char * params,
                            void * root_ptr,
                            mm_plugin_heap_t * out_heap);

  /**
   * Delete a heap instance
   *
   * @param heap Heap context to destroy
   *
   * @return S_OK, E_INVAL, E_FAIL
   */
  status_t mm_plugin_destroy(mm_plugin_heap_t heap);

  /**
   * Add (slab) region of memory to fuel the allocator.  The passthru
   * allocator does not need this as it takes directly from the OS.
   *
   * @param heap Heap context
   * @param region_base Pointer to beginning of region
   * @param region_size Region length in bytes
   *
   * @return E_NOT_IMPL, S_OK, E_FAIL, E_INVAL
   */
  status_t mm_plugin_add_managed_region(mm_plugin_heap_t heap,
                                        void * region_base,
                                        size_t region_size);

  /**
   * Query memory regions being used by the allocator
   *
   * @param heap Heap context
   * @param region_id Index counting from 0
   * @param [out] Base address of region
   * @param [out] Size of region in bytes
   *
   * @return S_MORE (continue to next region id), S_OK (done), E_INVAL
   */
  status_t mm_plugin_query_managed_region(mm_plugin_heap_t heap,
                                          unsigned region_id,
                                          void** out_region_base,
                                          size_t* out_region_size);

  /**
   * Register callback the allocator can use to request more memory
   *
   * @param heap Heap context
   * @param callback Call back function pointer
   * @param param Optional parameter which will be pass to callback function
   *
   * @return E_NOT_IMPL, S_OK
   */
  status_t mm_plugin_register_callback_request_memory(mm_plugin_heap_t heap,
                                                      request_memory_callback_t callback,
                                                      void * param);

  /**
   * Allocate a region of memory without alignment or hint
   *
   * @param heap Heap context
   * @param Length in bytes
   * @param [out] Pointer to allocated region
   *
   * @return S_OK or E_FAIL
   */
  status_t mm_plugin_allocate(mm_plugin_heap_t heap, size_t n, void ** out_ptr);

  /**
   * Allocation region of memory that is aligned
   *
   * @param heap Heap context
   * @param n Size of region in bytes
   * @param alignment Alignment in bytes
   * @param out_ptr Address if [in] nullptr, [out] pointer to allocated region
   *
   * @return S_OK, E_FAIL, E_INVAL (depending on implementation)
   */
  status_t mm_plugin_aligned_allocate(mm_plugin_heap_t heap, size_t n, size_t alignment, void ** out_ptr);

  /**
   * Special case for EASTL
   *
   * @param heap Heap context
   * @param n Size of region in bytes
   * @param alignment Alignment in bytes
   * @param offset Offset from start of region to location within region which satisfies alignment
   * @param out_ptr Address if [in] nullptr, [out] pointer to allocated region
   *
   * @return
   */
  status_t mm_plugin_aligned_allocate_offset(mm_plugin_heap_t heap, size_t n, size_t alignment, size_t offset, void ** out_ptr);

  /**
   * Free a previously allocated region of memory with length known
   *
   * @param heap Heap context
   * @param ptr Address of [in] pointer to previously allocated region, [out] nullptr
   * @param size Length of region in bytes
   *
   * @return S_OK or E_INVAL;
   */
  status_t mm_plugin_deallocate(mm_plugin_heap_t heap, void ** ptr, size_t size);

  /**
   * Free previously allocated region without known length
   *
   * @param heap Heap context
   * @param ptr Address of [in] pointer to previously allocated region, [out] nullptr
   *
   * @return S_OK
   */
  status_t mm_plugin_deallocate_without_size(mm_plugin_heap_t heap, void ** ptr);

  /**
   * Allocate region and zero memory
   *
   * @param heap Heap context
   * @param size Size of region in bytes
   * @param ptr [out] Pointer to allocated region
   *
   * @return S_OK
   */
  status_t mm_plugin_callocate(mm_plugin_heap_t heap, size_t n, void ** out_ptr);

  /*
    The POSIX realloc() function changes the size of the memory block pointed
    to by ptr to size bytes.  The contents will be unchanged in the
    range from the start of the region up to the minimum of the old and
    new sizes.  If the new size is larger than the old size, the added
    memory will not be initialized.  If ptr is NULL, then the call is
    equivalent to malloc(size), for all values of size; if size is equal
    to zero, and ptr is not NULL, then the call is equivalent to
    free(ptr).  Unless ptr is NULL, it must have been returned by an
    earlier call to malloc(), calloc(), or realloc().  If the area
    pointed to was moved, a free(ptr) is done.
  */

  /**
   * Resize an existing allocation
   *
   * @param heap Heap context
   * @param in_out_ptr Address of pointer to [in] existing allocated region, [out] new reallocated region or null if unable to reallocate
   * @param size New size in bytes
   *
   *
   * @return S_OK
   */
  status_t mm_plugin_reallocate(mm_plugin_heap_t heap, void ** in_out_ptr, size_t size);

  /**
   * Get the number of usable bytes in block pointed to by ptr.  The
   * allocator *may* not have this information and should then return
   * E_NOT_IMPL. Returned size may be larger than requested allocation
   *
   * @param heap Heap context
   * @param ptr Pointer to block base
   * @param out_ptr [out] Number of bytes in allocated block
   *
   * @return S_OK, E_NOT_IMPL
   */
  status_t mm_plugin_usable_size(mm_plugin_heap_t heap, void * ptr, size_t * out_size);

  /**
   * Inject an allocation back into the allocator (reconstituing)
   *
   * @param heap Heap context
   * @param ptr Pointer to region of memory to mark allocated
   * @param size Size of region in bytes
   *
   * @return S_OK, E_NOT_IMPL
   */
  status_t mm_plugin_inject_allocation(mm_plugin_heap_t heap, void * ptr, size_t size);

  /**
   * Report bytes remaining
   *
   * @param bytes_remaining Bytes remaining for allocation
   *
   * @return S_OK, E_NOT_IMPL
   */
  status_t mm_plugin_bytes_remaining(mm_plugin_heap_t heap, size_t *bytes_remaining);

  /**
   * Get debugging information
   *
   * @param heap Heap context
   */
  void mm_plugin_debug(mm_plugin_heap_t heap);

  /**
   * Check for crash consistency libccpm behavior)
   *
   * @return non-zero iff the root_ptr parameter of mm_plugin_create is interpreted as a struct ccpm_params *.
   */
  int mm_plugin_is_crash_consistent(mm_plugin_heap_t heap);

  /**
   * Check for inject_allocation capability
   *
   * @return non-zero iff mm_plugin_inject_allocation is implemented
   */
  int mm_plugin_can_inject_allocation(mm_plugin_heap_t heap);

  /**
   * Function pointer table for all methods
   *
   */
  typedef struct tag_mm_plugin_function_table_t
  {
    status_t (*mm_plugin_init)();
    status_t (*mm_plugin_create)(const char * params, void * root_ptr
			, mm_plugin_heap_t * out_heap);
    status_t (*mm_plugin_destroy)(mm_plugin_heap_t heap);
    status_t (*mm_plugin_add_managed_region)(mm_plugin_heap_t heap,
                                             void * region_base,
                                             size_t region_size);
    status_t (*mm_plugin_query_managed_region)(mm_plugin_heap_t heap,
                                               unsigned region_id,
                                               void** out_region_base,
                                               size_t* out_region_size);
    status_t (*mm_plugin_register_callback_request_memory)(mm_plugin_heap_t heap,
                                                           request_memory_callback_t callback,
                                                           void * param);
    status_t (*mm_plugin_allocate)(mm_plugin_heap_t heap, size_t n, void ** out_ptr);
    status_t (*mm_plugin_aligned_allocate)(mm_plugin_heap_t heap, size_t n, size_t alignment, void ** out_ptr);
    status_t (*mm_plugin_aligned_allocate_offset)(mm_plugin_heap_t heap, size_t n, size_t alignment, size_t offset, void ** out_ptr);
    status_t (*mm_plugin_deallocate)(mm_plugin_heap_t heap, void ** ptr, size_t size);
    status_t (*mm_plugin_deallocate_without_size)(mm_plugin_heap_t heap, void ** ptr);
    status_t (*mm_plugin_callocate)(mm_plugin_heap_t heap, size_t n, void ** out_ptr);
    status_t (*mm_plugin_reallocate)(mm_plugin_heap_t heap, void ** in_out_ptr, size_t size);
    status_t (*mm_plugin_usable_size)(mm_plugin_heap_t heap, void * ptr, size_t * out_size);
    status_t (*mm_plugin_bytes_remaining)(mm_plugin_heap_t heap, size_t * bytes_remaining);
    void     (*mm_plugin_debug)(mm_plugin_heap_t heap);
    status_t (*mm_plugin_inject_allocation)(mm_plugin_heap_t heap, void * ptr, size_t size);
    int (*mm_plugin_is_crash_consistent)(mm_plugin_heap_t heap);
    int (*mm_plugin_can_inject_allocation)(mm_plugin_heap_t heap);
  } mm_plugin_function_table_t;

#if defined(__cplusplus)
}
#endif

#if defined(__cplusplus)

#include <dlfcn.h>
#include <string>
#include <stdio.h>
#include <stdexcept>

#define LOAD_SYMBOL(X) _ft.X = reinterpret_cast<decltype(_ft.X)>(dlsym(_module, # X)); assert(_ft.X)

/**
 * C++ wrapper on C-based plugin API
 *
 */
class MM_plugin_wrapper
{
public:

  MM_plugin_wrapper(const std::string& plugin_path,
                    const std::string& config = "",
                    void * root_ptr = nullptr)
  {
    assert(plugin_path.empty() == false);
    _module = dlopen(plugin_path.c_str(), RTLD_NOW | RTLD_NODELETE); // RTLD_DEEPBIND |
    if(_module == nullptr) {
      char err[1024];
      sprintf(err, "%s\n", dlerror());
      throw std::invalid_argument(err);
    }

    LOAD_SYMBOL(mm_plugin_init);
    LOAD_SYMBOL(mm_plugin_create);
    LOAD_SYMBOL(mm_plugin_add_managed_region);
    LOAD_SYMBOL(mm_plugin_query_managed_region);
    LOAD_SYMBOL(mm_plugin_register_callback_request_memory);
    LOAD_SYMBOL(mm_plugin_allocate);
    LOAD_SYMBOL(mm_plugin_aligned_allocate);
    LOAD_SYMBOL(mm_plugin_aligned_allocate_offset);
    LOAD_SYMBOL(mm_plugin_deallocate);
    LOAD_SYMBOL(mm_plugin_deallocate_without_size);
    LOAD_SYMBOL(mm_plugin_callocate);
    LOAD_SYMBOL(mm_plugin_reallocate);
    LOAD_SYMBOL(mm_plugin_usable_size);
    LOAD_SYMBOL(mm_plugin_debug);
    LOAD_SYMBOL(mm_plugin_destroy);
    LOAD_SYMBOL(mm_plugin_inject_allocation);
    LOAD_SYMBOL(mm_plugin_bytes_remaining);
    LOAD_SYMBOL(mm_plugin_is_crash_consistent);
    LOAD_SYMBOL(mm_plugin_can_inject_allocation);

    //      dlclose(_module);

    /* create heap instance */
    _ft.mm_plugin_create(config.c_str(), root_ptr, &_heap);
  }

  MM_plugin_wrapper(const MM_plugin_wrapper &) = delete;

  MM_plugin_wrapper(MM_plugin_wrapper && other) noexcept
    : _module(std::move(other._module))
    , _ft(std::move(other._ft))
    , _heap(std::move(other._heap))
  {
    other._heap = nullptr;
  }

  virtual ~MM_plugin_wrapper() noexcept {
    if ( _heap )
    {
      _ft.mm_plugin_destroy(_heap);
    }
  }

  /* forwarding in liners */
  inline status_t init() noexcept {
    return _ft.mm_plugin_init();
  }

  inline status_t add_managed_region(void * region_base, size_t region_size) noexcept {
    return _ft.mm_plugin_add_managed_region(_heap, region_base, region_size);
  }

  inline status_t query_managed_region(unsigned region_id, void** out_region_base, size_t* out_region_size) noexcept {
    return _ft.mm_plugin_query_managed_region(_heap, region_id, out_region_base, out_region_size);
  }

  inline status_t register_callback_request_memory(request_memory_callback_t callback, void * param) noexcept {
    return _ft.mm_plugin_register_callback_request_memory(_heap, callback, param);
  }

  inline status_t allocate(size_t n, void ** out_ptr) noexcept {
    return _ft.mm_plugin_allocate(_heap, n, out_ptr);
  }

  inline status_t aligned_allocate(size_t n, size_t alignment, void ** out_ptr) noexcept {
    return _ft.mm_plugin_aligned_allocate(_heap, n, alignment, out_ptr);
  }

  inline status_t aligned_allocate_offset(size_t n, size_t alignment, size_t offset, void ** out_ptr) noexcept {
    return _ft.mm_plugin_aligned_allocate_offset(_heap, n, alignment, offset, out_ptr);
  }

  inline status_t deallocate(void ** ptr, size_t size) noexcept {
    return _ft.mm_plugin_deallocate(_heap, ptr, size);
  }

  inline status_t deallocate_without_size(void ** ptr) noexcept {
    return _ft.mm_plugin_deallocate_without_size(_heap, ptr);
  }

  inline status_t callocate(size_t n, void ** out_ptr) noexcept {
    return _ft.mm_plugin_callocate(_heap, n, out_ptr);
  }

  inline status_t reallocate(void ** in_out_ptr, size_t size) noexcept {
    return _ft.mm_plugin_reallocate(_heap, in_out_ptr, size);
  }

  inline status_t usable_size(void * ptr, size_t * out_size) noexcept {
    return _ft.mm_plugin_usable_size(_heap, ptr, out_size);
  }

  inline status_t bytes_remaining(size_t *bytes_remaining) const noexcept {
    return _ft.mm_plugin_bytes_remaining(_heap, bytes_remaining);
  }

  inline void debug(mm_plugin_heap_t heap) noexcept {
    return _ft.mm_plugin_debug(_heap);
  }

  inline status_t inject_allocation(void * ptr, size_t size) noexcept {
    return _ft.mm_plugin_inject_allocation(_heap, ptr, size);
  }

  inline int is_crash_consistent() noexcept {
    return _ft.mm_plugin_is_crash_consistent(_heap);
  }

  inline int can_inject_allocation() noexcept {
    return _ft.mm_plugin_can_inject_allocation(_heap);
  }

private:
  void *                     _module;
  mm_plugin_function_table_t _ft;
#if 0
  /* intention, but we avoid appealing to moveable_ptr */
  common::moveable_ptr<void> _heap;
#else
  void *                     _heap;
#endif
};

#include <limits>
#include <new>

/**
 * Standard C++ allocator wrapper
 *
 */
template <class T>
class MM_plugin_cxx_allocator
{
public:
  using value_type    = T;
  using pointer       = value_type*;
  using const_pointer = typename std::pointer_traits<pointer>::template
    rebind<value_type const>;
  using void_pointer       = typename std::pointer_traits<pointer>::template
    rebind<void>;
  using const_void_pointer = typename std::pointer_traits<pointer>::template
    rebind<const void>;

  using difference_type = typename std::pointer_traits<pointer>::difference_type;
  using size_type       = std::make_unsigned_t<difference_type>;

  template <class U> struct rebind {typedef MM_plugin_cxx_allocator<U> other;};

  MM_plugin_cxx_allocator(MM_plugin_wrapper& wrapper) noexcept : _wrapper(wrapper)
  {
  }

  template <class U>
  MM_plugin_cxx_allocator(MM_plugin_cxx_allocator<U> const& a_) noexcept : _wrapper(a_._wrapper)  {}

  pointer allocate(std::size_t n)
  {
    pointer p = nullptr;
    auto status = _wrapper.allocate(n*sizeof(value_type), reinterpret_cast<void**>(&p));
    if(status != 0) throw std::bad_alloc();
    return p;
  }

  void deallocate(pointer p, std::size_t n) noexcept
  {
    _wrapper.deallocate(reinterpret_cast<void**>(&p), n);
  }

  pointer allocate(std::size_t n, const_void_pointer)
  {
    return allocate(n);
  }

  template <class U, class ...Args>
  void construct(U* p, Args&& ...args)
  {
    ::new(p) U(std::forward<Args>(args)...);
  }

  template <class U>
  void destroy(U* p) noexcept
  {
    p->~U();
  }

  std::size_t max_size() const noexcept
  {
    return std::numeric_limits<size_type>::max();
  }

  MM_plugin_cxx_allocator select_on_container_copy_construction() const
  {
    return *this;
  }

  using propagate_on_container_copy_assignment = std::false_type;
  using propagate_on_container_move_assignment = std::false_type;
  using propagate_on_container_swap            = std::false_type;
  using is_always_equal                        = std::is_empty<MM_plugin_cxx_allocator>;

  MM_plugin_wrapper& _wrapper;
};

template <class T, class U>
bool
operator==(MM_plugin_cxx_allocator<T> const&, MM_plugin_cxx_allocator<U> const&) noexcept
{
  return true;
}

template <class T, class U>
bool
operator!=(MM_plugin_cxx_allocator<T> const& x, MM_plugin_cxx_allocator<U> const& y) noexcept
{
  return !(x == y);
}

#undef LOAD_SYMBOL
#endif

#pragma GCC diagnostic pop

#endif // __MM_PLUGIN_ITF_H__


#if !defined(FINDFILEEXT_H_INCLUDED)
#define FINDFILEEXT_H_INCLUDED

#include "FileEnumerator.h"
#include "Utils.h"

#include <filesystem>
#include <gsl/gsl>
#include <iosfwd>
#include <map>
#include <string>
#include <vector>

class FindFileExt
{
public:
	static int usage(::std::ostream& strm, const ::std::string& progName,
		const char* pMsg);

	FindFileExt(::gsl::span<const char*const> args);
	int run();

	FindFileExt(const FindFileExt&) = delete;
	FindFileExt& operator=(const FindFileExt&) = delete;
	FindFileExt(FindFileExt&&) = delete;
	FindFileExt& operator=(FindFileExt&&) = delete;

PRIVATE_EXCEPT_IN_TEST:
	using Path = ::std::filesystem::path;
	using StrToCountMap = ::std::map< ::std::string, size_t >;
	using PathList = ::std::vector<Path>;

	void countFiles();
	void reportExtension(const StrToCountMap::value_type& extToCountMapping);

	bool				m_includeCounts;
	bool				m_outputAsWildcards;
	FileEnumerator	m_fileEnumerator;
	StrToCountMap	m_extToCountMap;
	PathList			m_noExtList;
};

#endif // FINDFILEEXT_H_INCLUDED


#pragma once
#include "../utils/utils.h"
#include <gsl/gsl>

namespace Halley {
	class Compression {
	public:
		static Bytes deflate(const Bytes& bytes);
		static Bytes deflate(gsl::span<const gsl::byte> bytes);
		static Bytes inflate(const Bytes& bytes);
		static Bytes inflate(gsl::span<const gsl::byte> bytes);
		static std::shared_ptr<const char> inflateToSharedPtr(gsl::span<const gsl::byte> bytes, size_t& outSize);
		static unsigned char* inflateRaw(gsl::span<const gsl::byte> bytes, size_t& outSize);
	};
}


#pragma once

#include "Core/Delegate.h"
#include "Core/Pointers.h"
#include "Scene/Entity.h"

#include <gsl/span>

#include <string>
#include <unordered_map>
#include <vector>

class CameraComponent;
class DirectionalLightComponent;
class ModelComponent;
class PointLightComponent;
class SpotLightComponent;

class Scene
{
public:
   Scene();
   ~Scene();

   void tick(float dt);

   float getTime() const
   {
      return time;
   }

   float getDeltaTime() const
   {
      return deltaTime;
   }

   template<typename... ComponentTypes>
   Entity* createEntity()
   {
      entities.push_back(Entity::create<ComponentTypes...>(*this));
      return entities.back().get();
   }

   Entity* createEntity(gsl::span<std::string> componentClassNames)
   {
      entities.push_back(Entity::create(componentClassNames, *this));
      return entities.back().get();
   }

   bool destroyEntity(Entity* entityToDestroy);

   const std::vector<UPtr<Entity>>& getEntities() const
   {
      return entities;
   }

   CameraComponent* getActiveCameraComponent()
   {
      return activeCameraComponent;
   }

   const CameraComponent* getActiveCameraComponent() const
   {
      return activeCameraComponent;
   }

   void setActiveCameraComponent(CameraComponent* newActiveCameraComponent);

   const std::vector<CameraComponent*>& getCameraComponents() const
   {
      return cameraComponents;
   }

   void registerCameraComponent(CameraComponent* cameraComponent);
   void unregisterCameraComponent(CameraComponent* cameraComponent);

   const std::vector<ModelComponent*>& getModelComponents() const
   {
      return modelComponents;
   }

   void registerModelComponent(ModelComponent* modelComponent);
   void unregisterModelComponent(ModelComponent* modelComponent);

   const std::vector<DirectionalLightComponent*>& getDirectionalLightComponents() const
   {
      return directionalLightComponents;
   }

   void registerDirectionalLightComponent(DirectionalLightComponent* directionalLightComponent);
   void unregisterDirectionalLightComponent(DirectionalLightComponent* directionalLightComponent);

   const std::vector<PointLightComponent*>& getPointLightComponents() const
   {
      return pointLightComponents;
   }

   void registerPointLightComponent(PointLightComponent* pointLightComponent);
   void unregisterPointLightComponent(PointLightComponent* pointLightComponent);

   const std::vector<SpotLightComponent*>& getSpotLightComponents() const
   {
      return spotLightComponents;
   }

   void registerSpotLightComponent(SpotLightComponent* spotLightComponent);
   void unregisterSpotLightComponent(SpotLightComponent* spotLightComponent);

private:
   float time;
   float deltaTime;

   std::vector<UPtr<Entity>> entities;

   std::vector<CameraComponent*> cameraComponents;
   CameraComponent* activeCameraComponent;

   std::vector<ModelComponent*> modelComponents;

   std::vector<DirectionalLightComponent*> directionalLightComponents;
   std::vector<PointLightComponent*> pointLightComponents;
   std::vector<SpotLightComponent*> spotLightComponents;
};


//
// Autogenerated based on code by L. J. Feldstein
//

#include <stdio.h>
#include <gsl/gsl_matrix.h>
#include "cimple_controller.h"