// Description: Functions to retrieve scalar Matlab function arguments
#ifndef PASTELMATLAB_MATLAB_SCALAR_H
#define PASTELMATLAB_MATLAB_SCALAR_H
#include "pastel/sys/mytypes.h"
#include "pastel/sys/vector.h"
#include "pastel/sys/ensure.h"
#include "pastel/sys/sequence/copy_n.h"
#include "pastel/math/matrix.h"
#include "pastelmatlab/matlab_mex.h"
#include "pastelmatlab/matlab_class_ids.h"
namespace Pastel
{
    //! Creates a Matlab scalar.
    template <typename Type>
    Type* matlabCreateScalar(
        mxArray*& output)
    {
        output = mxCreateNumericMatrix(1, 1,
            typeToMatlabClassId<Type>(), mxREAL);
        return (Type*)mxGetData(output);
    }
    //! Retrieves a copy of a scalar number.
    /*!
   Preconditions:
   mxIsNumeric(input)
   index >= 0
   index < mxGetNumberOfElements(input)
   Note: Conversion is done automatically
   from the type of 'input' to type 'Type'.
   */
    template <typename Type>
    Type matlabAsScalar(const mxArray* input, integer index = 0)
    {
        ENSURE(mxIsNumeric(input));
        integer n = mxGetNumberOfElements(input);
        ENSURE_OP(index, >=, 0);
        ENSURE_OP(index, <, n);
        Type result = 0;
        switch(mxGetClassID(input))
        {
        case mxSINGLE_CLASS:
            result = *((real32*)mxGetData(input) + index);
            break;
        case mxDOUBLE_CLASS:
            result = *((real64*)mxGetData(input) + index);
            break;
        case mxINT8_CLASS:
            result = *((int8*)mxGetData(input) + index);
            break;
        case mxUINT8_CLASS:
            result = *((uint8*)mxGetData(input) + index);
            break;
        case mxINT16_CLASS:
            result = *((int16*)mxGetData(input) + index);
            break;
        case mxUINT16_CLASS:
            result = *((uint16*)mxGetData(input) + index);
            break;
        case mxINT32_CLASS:
            result = *((int32*)mxGetData(input) + index);
            break;
        case mxUINT32_CLASS:
            result = *((uint32*)mxGetData(input) + index);
            break;
        case mxINT64_CLASS:
            result = *((int64*)mxGetData(input) + index);
            break;
        case mxUINT64_CLASS:
            result = *((uint64*)mxGetData(input) + index);
            break;
        default:
            // This should not be possible, since
            // the above covers all numeric types.
            {
                bool reachedHere = true;
                ENSURE(!reachedHere);
            }
            break;
        };
        return result;
    }
    //! Retrieves a copy of a string.
    /*!
   Preconditions:
   mxIsChar(input)
   */
    inline std::string matlabAsString(const mxArray* input)
    {
        ENSURE(mxIsChar(input));
        char* text = mxArrayToString(input);
        std::string result(text);
        mxFree(text);
        return result;
    }
    namespace MatlabStringAsEnum_
    {
        template <
            typename Type,
            typename... ArgumentSet>
        Type matlabStringAsEnum(
            const std::string input)
        {
            bool unknownEnum = true;
            ENSURE(!unknownEnum);
            return Type();
        }
        template <
            typename Type,
            typename... ArgumentSet>
        Type matlabStringAsEnum(
            const std::string input,
            const std::string& key,
            NoDeduction<Type> value,
            ArgumentSet&&... argumentSet)
        {
            if (input == key)
            {
                return value;
            }
            return matlabStringAsEnum<Type>(input, 
                std::forward<ArgumentSet>(argumentSet)...);
        }
    }
    //! Retrieves an enum given as strings.
    /*!
   input:
   A matlab array which contains the enum
   value as a string.
   argumentSet:
   Subsequent key-value arguments,
   with the key convertible to std::string,
   and the value with type 'Type'.
   returns:
   The value of the key which matches the
   string in 'input'.
   */
    template <
        typename Type,
        typename... ArgumentSet>
    Type matlabStringAsEnum(
        const mxArray* input,
        ArgumentSet&&... argumentSet)
    {
        return MatlabStringAsEnum_::matlabStringAsEnum<Type>(
            matlabAsString(input),
            std::forward<ArgumentSet>(argumentSet)...);
    }
    //! Reports all elements in a numeric array.
    /*!
   Preconditions:
   mxIsNumeric(input)
   offset >= 0
   Offset:
   The index of the element from which
   to start.
   Returns:
   The number of elements in the array.
   Note: The elements are reported in Matlab's linearized
   order. Conversions are done automatically from the
   type of 'input' to the iterators value-type.
   */
    template <typename Scalar_Iterator>
    integer matlabGetScalars(
        const mxArray* input,
        Scalar_Iterator output,
        integer offset = 0)
    {
        ENSURE(mxIsNumeric(input));
        ENSURE_OP(offset, >=, 0);
        integer n = 
            mxGetNumberOfElements(input);
        if (offset >= n)
        {
            return n;
        }
        integer m = n - offset;
        switch(mxGetClassID(input))
        {
        case mxSINGLE_CLASS:
            copy_n((real32*)mxGetData(input) + offset, m, output);
            break;
        case mxDOUBLE_CLASS:
            copy_n((real64*)mxGetData(input) + offset, m, output);
            break;
        case mxINT8_CLASS:
            copy_n((int8*)mxGetData(input) + offset, m, output);
            break;
        case mxUINT8_CLASS:
            copy_n((uint8*)mxGetData(input) + offset, m, output);
            break;
        case mxINT16_CLASS:
            copy_n((int16*)mxGetData(input) + offset, m, output);
            break;
        case mxUINT16_CLASS:
            copy_n((uint16*)mxGetData(input) + offset, m, output);
            break;
        case mxINT32_CLASS:
            copy_n((int32*)mxGetData(input) + offset, m, output);
            break;
        case mxUINT32_CLASS:
            copy_n((uint32*)mxGetData(input) + offset, m, output);
            break;
        case mxINT64_CLASS:
            copy_n((int64*)mxGetData(input) + offset, m, output);
            break;
        case mxUINT64_CLASS:
            copy_n((uint64*)mxGetData(input) + offset, m, output);
            break;
        default:
            // This should not be possible, since
            // the above covers all numeric types.
            ENSURE(false);
            break;
        }
        return n;
    }
}
#endif