#pragma once

// ------------------------------------------------------------------------------------------------
#include "Core/Common.hpp"

// ------------------------------------------------------------------------------------------------
namespace SqMod {

/* ------------------------------------------------------------------------------------------------
 * Helper class used to check if a certain operation is possible on the specified argument.
*/
template < typename U > struct SqDynArg
{
    static inline bool CanPop(HSQUIRRELVM vm)
    {
        return (sq_gettype(vm, 2) == OT_INSTANCE);
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM vm)
    {
        Var< U * > var(vm, 2);
        // Validate the obtained instance
        if (!var.value)
        {
            STHROWF("No such instance");
        }
        // Attempt to perform the requested operation
        return fn(*var.value);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM vm)
    {
        Var< U * > var(vm, 2);
        // Validate the obtained instance
        if (!var.value)
        {
            STHROWF("No such instance");
        }
        // Attempt to perform the requested operation
        return fn(*var.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Instance pointer specializations of the argument checking structure.
*/
template < typename U > struct SqDynArg< U * >
{
    static inline bool CanPop(HSQUIRRELVM vm)
    {
        return (sq_gettype(vm, 2) == OT_INSTANCE);
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM vm)
    {
        Var< U * > var(vm, 2);
        // Validate the obtained instance
        if (!var.value)
        {
            STHROWF("No such instance");
        }
        // Attempt to perform the requested operation
        return fn(var.value);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM vm)
    {
        Var< U * > var(vm, 2);
        // Validate the obtained instance
        if (!var.value)
        {
            STHROWF("No such instance");
        }
        // Attempt to perform the requested operation
        return fn(var.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Instance pointer specializations of the argument checking structure.
*/
template < typename U > struct SqDynArg< const U * >
{
    static inline bool CanPop(HSQUIRRELVM vm)
    {
        return (sq_gettype(vm, 2) == OT_INSTANCE);
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM vm)
    {
        Var< U * > var(vm, 2);
        // Validate the obtained instance
        if (!var.value)
        {
            STHROWF("No such instance");
        }
        // Attempt to perform the requested operation
        return fn(*var.value);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM vm)
    {
        Var< U * > var(vm, 2);
        // Validate the obtained instance
        if (!var.value)
        {
            STHROWF("No such instance");
        }
        // Attempt to perform the requested operation
        return fn(*var.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Inherited by the base specializations of the argument checking structure.
*/
template < typename U, SQObjectType SqT > struct SqDynArgBase
{
    static inline bool CanPop(HSQUIRRELVM vm)
    {
        return (sq_gettype(vm, 2) == SqT);
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM vm)
    {
        Var< U > var(vm, 2);
        // Attempt to perform the requested operation
        return fn(var.value);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM vm)
    {
        Var< U > var(vm, 2);
        // Attempt to perform the requested operation
        return fn(var.value);
    }
};

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< char >
    : public SqDynArgBase< char, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< signed char >
    : public SqDynArgBase< signed char, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< unsigned char >
    : public SqDynArgBase< unsigned char, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< signed short >
    : public SqDynArgBase< signed short, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< unsigned short >
    : public SqDynArgBase< unsigned short, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< signed int >
    : public SqDynArgBase< signed int, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< unsigned int >
    : public SqDynArgBase< unsigned int, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< signed long >
    : public SqDynArgBase< signed long, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< unsigned long >
    : public SqDynArgBase< unsigned long, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< signed long long >
    : public SqDynArgBase< signed long long, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< unsigned long long >
    : public SqDynArgBase< unsigned long long, OT_INTEGER >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< float >
    : public SqDynArgBase< float, OT_FLOAT >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< double >
    : public SqDynArgBase< double, OT_FLOAT >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< bool >
    : public SqDynArgBase< bool, OT_BOOL >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< SQChar * >
    : public SqDynArgBase< SQChar *, OT_STRING >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< const SQChar * >
    : public SqDynArgBase< const SQChar *, OT_STRING >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< String >
    : public SqDynArgBase< String, OT_STRING >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< Table >
    : public SqDynArgBase< Table, OT_TABLE >
{ /* ... */ };

// ------------------------------------------------------------------------------------------------
template < > struct SqDynArg< Array >
    : public SqDynArgBase< Array, OT_ARRAY >
{ /* ... */ };

/* ------------------------------------------------------------------------------------------------
 * Specialization of the argument checking structure for functions.
*/
template < > struct SqDynArg< Function >
{
    static inline bool CanPop(HSQUIRRELVM vm)
    {
        const SQObjectType type = sq_gettype(vm, 2);
        return (type == OT_CLOSURE || type == OT_NATIVECLOSURE);
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM vm)
    {
        Var< Function > var(vm, 2);
        // Attempt to perform the requested operation
        return fn(var.value);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM vm)
    {
        Var< Function > var(vm, 2);
        // Attempt to perform the requested operation
        return fn(var.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of the argument checking structure for objects.
*/
template < > struct SqDynArg< Object >
{
    static inline bool CanPop(HSQUIRRELVM /*vm*/)
    {
        return true; // Objects can use any type.
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM vm)
    {
        Var< Object > var(vm, 2);
        // Attempt to perform the requested operation
        return fn(var.value);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM vm)
    {
        Var< Object > var(vm, 2);
        // Attempt to perform the requested operation
        return fn(var.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Specialization of the argument checking structure for null.
*/
template < > struct SqDynArg< std::nullptr_t >
{
    static inline bool CanPop(HSQUIRRELVM vm)
    {
        return (sq_gettype(vm, 2) == OT_NULL);
    }

    template < typename F > static inline auto Do(F & fn, HSQUIRRELVM /*vm*/)
    {
        // Attempt to perform the requested operation
        return fn(nullptr);
    }

    template < typename F > static inline auto Do(const F & fn, HSQUIRRELVM /*vm*/)
    {
        // Attempt to perform the requested operation
        return fn(nullptr);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Used to perform an operation on multiple types specified at compile time.
*/
template < typename... Ts > struct SqDynArgImpl;

/* ------------------------------------------------------------------------------------------------
 * Zeroth case of the operation. No known operation of such pairs of types at this point.
*/
template < > struct SqDynArgImpl< >
{
    template < typename T > static SQInteger Try(const T & /*val*/, HSQUIRRELVM vm)
    {
        const String tn1(SqTypeName(vm, 1));
        const String tn2(SqTypeName(vm, 2));
        return sq_throwerrorf(vm, "Such operation is not possible between (%s) and (%s)",
                                        tn1.c_str(), tn2.c_str());
    }
};

/* ------------------------------------------------------------------------------------------------
 * Argument pack type pealing. Attempt to perform the operation with a specified type.
*/
template < typename U, typename... Ts > struct SqDynArgImpl< U, Ts... >
{
    template < typename F > static SQInteger Try(F & fn, HSQUIRRELVM vm)
    {
        typedef typename std::decay< U >::type ArgType;
        // Can the stack value be used with the current type?
        if (SqDynArg< ArgType >::CanPop(vm))
        {
            // If not an instance then don't use a try catch block
            if (sq_gettype(vm, 2) != OT_INSTANCE)
            {
                // Attempt to perform the operation and push the result on the stack
                PushVar(vm, SqDynArg< ArgType >::Do(fn, vm));
                // Specify that we completed the search and have a result
                return 1;
            }
            // Instances require a try/catch block since we can't differentiate types
            try
            {
                // Attempt to perform the operation and push the result on the stack
                PushVar(vm, SqDynArg< ArgType >::Do(fn, vm));
                // Specify that we completed the search and have a result
                return 1;
            }
            catch (const Sqrat::Exception & e)
            {
                // Probably the wrong type
            }
            catch (...)
            {
                // Something very bad happened. At this point, we just don't want to let
                // exceptions propagate to the virtual machine and we must end here.
                // Either way, reaching this point is bad and we just shuved it under
                // the rug. This is bad practice but circumstances forced me.
                // Don't do this at home kids!
                return -1;
            }
        }
        // On to the next type
        return SqDynArgImpl< Ts... >::Try(fn, vm);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Squirrel function that forwards the call to the actual implementation.
*/
template < typename F, typename U, typename... Ts > SQInteger SqDynArgFwd(HSQUIRRELVM vm)
{
    // Make sure that there are enough parameters on the stack
    if (sq_gettop(vm) < 2)
    {
        return sq_throwerror(vm, "Insufficient parameters for such operation");
    }
    // Attempt to create the functor that performs the operation
    F fn(vm);
    // Is this functor in a valid state?
    if (!fn)
    {
        return sq_throwerror(vm, "Functor failed to initialize");
    }
    // Attempt to perform the comparison
    try
    {
        return SqDynArgImpl< U, Ts... >::Try(fn, vm);
	}
	catch (const Poco::Exception& e)
	{
		return sq_throwerror(vm, e.displayText().c_str());
    }
	catch (const std::exception & e)
    {
        return sq_throwerror(vm, e.what());
    }
    catch (...)
    {
        return sq_throwerror(vm, "Unknown error occurred during comparison");
    }
    SQ_UNREACHABLE
    // We shouldn't really reach this point but something must be returned
    return sq_throwerror(vm, "Operation encountered unknown behavior");
}

/* ------------------------------------------------------------------------------------------------
 * Functor used to perform comparison.
*/
template < typename T > struct SqDynArgCmpFn
{
    // --------------------------------------------------------------------------------------------
    HSQUIRRELVM     mVM; // The squirrel virtual machine.
    Var< T * >      mVar; // The instance of the base type.

    /* --------------------------------------------------------------------------------------------
     * Base constructor. (required)
    */
    explicit SqDynArgCmpFn(HSQUIRRELVM vm)
        : mVM(vm), mVar(vm, 1)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean. (required)
    */
    operator bool () const // NOLINT(hicpp-explicit-conversions,google-explicit-constructor)
    {
        return (mVar.value != nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for references. (required)
    */
    template < typename U > SQInteger operator () (U & v) const
    {
        return mVar.value->Cmp(v);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const references. (required)
    */
    template < typename U > SQInteger operator () (const U & v) const
    {
        return mVar.value->Cmp(v);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for pointers. (required)
    */
    template < typename U > SQInteger operator () (U * v) const
    {
        return mVar.value->Cmp(v);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const pointers. (required)
    */
    template < typename U > SQInteger operator () (const U * v) const
    {
        return mVar.value->Cmp(v);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for null pointers. (required)
    */
    SQInteger operator () (std::nullptr_t) const
    {
        return mVar.value->Cmp(nullptr);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Functor used to perform addition.
*/
template < typename T > struct SqDynArgAddFn
{
    // --------------------------------------------------------------------------------------------
    HSQUIRRELVM     mVM; // The squirrel virtual machine.
    Var< T * >      mVar; // The instance of the base type.

    /* --------------------------------------------------------------------------------------------
     * Base constructor. (required)
    */
    explicit SqDynArgAddFn(HSQUIRRELVM vm)
        : mVM(vm), mVar(vm, 1)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean. (required)
    */
    operator bool () const // NOLINT(hicpp-explicit-conversions,google-explicit-constructor)
    {
        return (mVar.value != nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for references. (required)
    */
    template < typename U > T operator () (U & v) const
    {
        return (*mVar.value) + v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const references. (required)
    */
    template < typename U > T operator () (const U & v) const
    {
        return (*mVar.value) + v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for pointers. (required)
    */
    template < typename U > T operator () (U * v) const
    {
        return (*mVar.value) + v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const pointers. (required)
    */
    template < typename U > T operator () (const U * v) const
    {
        return (*mVar.value) + v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for null pointers. (required)
    */
    T operator () (std::nullptr_t) const
    {
        return (*mVar.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Functor used to perform subtraction.
*/
template < typename T > struct SqDynArgSubFn
{
    // --------------------------------------------------------------------------------------------
    HSQUIRRELVM     mVM; // The squirrel virtual machine.
    Var< T * >      mVar; // The instance of the base type.

    /* --------------------------------------------------------------------------------------------
     * Base constructor. (required)
    */
    explicit SqDynArgSubFn(HSQUIRRELVM vm)
        : mVM(vm), mVar(vm, 1)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean. (required)
    */
    operator bool () const // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
    {
        return (mVar.value != nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for references. (required)
    */
    template < typename U > T operator () (U & v) const
    {
        return (*mVar.value) - v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const references. (required)
    */
    template < typename U > T operator () (const U & v) const
    {
        return (*mVar.value) - v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for pointers. (required)
    */
    template < typename U > T operator () (U * v) const
    {
        return (*mVar.value) - v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const pointers. (required)
    */
    template < typename U > T operator () (const U * v) const
    {
        return (*mVar.value) - v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for null pointers. (required)
    */
    T operator () (std::nullptr_t) const
    {
        return (*mVar.value);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Functor used to perform multiplication.
*/
template < typename T > struct SqDynArgMulFn
{
    // --------------------------------------------------------------------------------------------
    HSQUIRRELVM     mVM; // The squirrel virtual machine.
    Var< T * >      mVar; // The instance of the base type.

    /* --------------------------------------------------------------------------------------------
     * Base constructor. (required)
    */
    explicit SqDynArgMulFn(HSQUIRRELVM vm)
        : mVM(vm), mVar(vm, 1)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean. (required)
    */
    operator bool () const // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
    {
        return (mVar.value != nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for references. (required)
    */
    template < typename U > T operator () (U & v) const
    {
        return (*mVar.value) * v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const references. (required)
    */
    template < typename U > T operator () (const U & v) const
    {
        return (*mVar.value) * v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for pointers. (required)
    */
    template < typename U > T operator () (U * v) const
    {
        return (*mVar.value) * v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const pointers. (required)
    */
    template < typename U > T operator () (const U * v) const
    {
        return (*mVar.value) * v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for null pointers. (required)
    */
    T operator () (std::nullptr_t) const
    {
        return (*mVar.value) * static_cast< SQInteger >(0);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Functor used to perform division.
*/
template < typename T > struct SqDynArgDivFn
{
    // --------------------------------------------------------------------------------------------
    HSQUIRRELVM     mVM; // The squirrel virtual machine.
    Var< T * >      mVar; // The instance of the base type.

    /* --------------------------------------------------------------------------------------------
     * Base constructor. (required)
    */
    explicit SqDynArgDivFn(HSQUIRRELVM vm)
        : mVM(vm), mVar(vm, 1)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean. (required)
    */
    operator bool () const // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
    {
        return (mVar.value != nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for references. (required)
    */
    template < typename U > T operator () (U & v) const
    {
        return (*mVar.value) / v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const references. (required)
    */
    template < typename U > T operator () (const U & v) const
    {
        return (*mVar.value) / v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for pointers. (required)
    */
    template < typename U > T operator () (U * v) const
    {
        return (*mVar.value) / v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const pointers. (required)
    */
    template < typename U > T operator () (const U * v) const
    {
        return (*mVar.value) / v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for null pointers. (required)
    */
    T operator () (std::nullptr_t) const
    {
        return (*mVar.value) / static_cast< SQInteger >(0);
    }
};

/* ------------------------------------------------------------------------------------------------
 * Functor used to perform modulus.
*/
template < typename T > struct SqDynArgModFn
{
    // --------------------------------------------------------------------------------------------
    HSQUIRRELVM     mVM; // The squirrel virtual machine.
    Var< T * >      mVar; // The instance of the base type.

    /* --------------------------------------------------------------------------------------------
     * Base constructor. (required)
    */
    explicit SqDynArgModFn(HSQUIRRELVM vm)
        : mVM(vm), mVar(vm, 1)
    {
        /* ... */
    }

    /* --------------------------------------------------------------------------------------------
     * Implicit conversion to boolean. (required)
    */
    operator bool () const // NOLINT(google-explicit-constructor,hicpp-explicit-conversions)
    {
        return (mVar.value != nullptr);
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for references. (required)
    */
    template < typename U > T operator () (U & v) const
    {
        return (*mVar.value) % v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const references. (required)
    */
    template < typename U > T operator () (const U & v) const
    {
        return (*mVar.value) % v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for pointers. (required)
    */
    template < typename U > T operator () (U * v) const
    {
        return (*mVar.value) % v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for const pointers. (required)
    */
    template < typename U > T operator () (const U * v) const
    {
        return (*mVar.value) % v;
    }

    /* --------------------------------------------------------------------------------------------
     * Function call operator for null pointers. (required)
    */
    T operator () (std::nullptr_t) const
    {
        return (*mVar.value) % static_cast< SQInteger >(0);
    }
};

} // Namespace:: SqMod
