#include <memory.h>
#include <stdio.h>
#include "BitStream.h"

const int MIN_FUNC_STACK_ALIGNMENT=4;

// Class should be the basemost class you want called
#define GET_OBJECT_MEMBER_PTR(_OUT_, _CLASS_, _FUNCTION_, _PARAMS_) \
{ \
    union \
    { \
        void (_cdecl _CLASS_::*__memberFunctionPtr)_PARAMS_; \
        void* __voidFunc; \
    }; \
    __memberFunctionPtr=&_CLASS_::_FUNCTION_; \
    (_OUT_) = __voidFunc; \
}

#define OBJECT_MEMBER_SIGNATURE(_CLASS_, _FUNCTION_, _PARAMS_) #_CLASS_ #_FUNCTION_ #_PARAMS_

template <class P1, class P2>
unsigned int SerializeParameters(char *out, unsigned int outMaxLen, P1 p1, P2 p2)
{
    const unsigned char NUM_PARAMS=2;
    unsigned int writeOffset=0;
    unsigned int paramLength;
    unsigned int bytesRequired = sizeof(unsigned char)+sizeof(unsigned int)*NUM_PARAMS+sizeof(P1)+sizeof(P2);

    // Verify output size
    if (bytesRequired > outMaxLen)
        return 0;

    // Num parameters
    out[writeOffset]=NUM_PARAMS;
    writeOffset+=sizeof(unsigned char);

    // Write parameter sizes
    paramLength=(unsigned int) sizeof(P1);
    memcpy(out+writeOffset, &paramLength, sizeof(unsigned int) );
    writeOffset+=sizeof(unsigned int);
    paramLength=(unsigned int) sizeof(P2);
    memcpy(out+writeOffset, &paramLength, sizeof(unsigned int) );
    writeOffset+=sizeof(unsigned int);

    // Write parameter data
    memcpy(out+writeOffset,&p1,sizeof(P1));
    writeOffset+=sizeof(P1);
    memcpy(out+writeOffset,&p2,sizeof(P2));
    writeOffset+=sizeof(P2);

    return bytesRequired;
}

// in is packed with actual data, modified for endian swap (change code if in should be const)
// out contains data, expanded to a multiple of MIN_FUNC_STACK_ALIGNMENT bytes, padded with 0 according to endianness
bool DeserializeParameters(char *out, unsigned int outLength, unsigned int *bytesWritten,
                            char *in, unsigned int inLength,
                            unsigned char *numParameters,
                            unsigned int *parameterLengths, unsigned int maxParameters)
{
    unsigned int readOffset=0;
    unsigned char parameterIndex;
    unsigned int alignedBytesRequired;
    // Read number of parameters
    *numParameters=in[0];
    *bytesWritten=0;
    readOffset+=sizeof(unsigned char);
    // Is the list parameterLengths long enough?
    if (*numParameters > maxParameters) return false;
    // Is there enough data to read all the stated parameters?
    if (readOffset+*numParameters*sizeof(unsigned int) > inLength) return false;
    // Read out all parameters lengths, write to parameterLengths
    for (parameterIndex=0; parameterIndex < *numParameters; parameterIndex++)
    {
        memcpy(parameterLengths+parameterIndex, in+readOffset, sizeof(unsigned int));
        readOffset+=sizeof(unsigned int);
    }
    // Read out each parameter, unpacking, rounding up to REGISTER_MIN bytes
    for (parameterIndex=0; parameterIndex < *numParameters; parameterIndex++)
    {
        alignedBytesRequired = parameterLengths[parameterIndex];
        if (alignedBytesRequired % MIN_FUNC_STACK_ALIGNMENT!=0)
        {
            // Not already divisible by REGISTER_MIN, round up to nearest multiple of REGISTER_MIN
            alignedBytesRequired+=MIN_FUNC_STACK_ALIGNMENT-(alignedBytesRequired % MIN_FUNC_STACK_ALIGNMENT);
        }
        if (outLength < *bytesWritten + alignedBytesRequired )
            return false;
        // EndianSwap(in+readOffset,parameterLengths[parameterIndex]);
        if (RakNet::BitStream::IsBigEndian())
        {
            // Write data to rightmost
            memcpy(out+*bytesWritten+alignedBytesRequired-parameterLengths[parameterIndex],in+readOffset,parameterLengths[parameterIndex]);
            // Fill out left with 0
            memset(out+*bytesWritten,0,alignedBytesRequired-parameterLengths[parameterIndex]);
        }
        else
        {
            // Write data to leftmost
            memcpy(out+*bytesWritten,in+readOffset,parameterLengths[parameterIndex]);
            // Fill out right with 0
            memset(out+*bytesWritten+parameterLengths[parameterIndex],0,alignedBytesRequired-parameterLengths[parameterIndex]);
        }
        readOffset+=parameterLengths[parameterIndex];
        *bytesWritten+=alignedBytesRequired;
    }
    return true;
}

void _cdecl func2(int a, char b)
{
    printf("%i %i", a, b);
}

class MyClass
{
public:
    virtual void _cdecl func6(int a, char b)
    {
        printf("func6 2 base\n");
    }
};

class DerivedClass : public MyClass
{
public:
    virtual void _cdecl func6(int a, char b)
    {
        printf("func6 2 derived\n");
    }
};

bool DoFunctionCall(const char *inputStack, unsigned int numBytes, void *functionPtr, void *thisPtr)
{
    if (numBytes > MAX_ALLOCA_STACK_ALLOCATION)
        return false;

    const int loopCount = numBytes/4;
    __asm
    {
        // Allocate data stack
        sub         esp,numBytes
        // Number of times to move MIN_FUNC_STACK_ALIGNMENT bytes
        mov         ecx,loopCount
        // source is esi
        mov         esi,inputStack
        // destination is esp, stored in edi
        mov         edi,esp
        // Copy data to function stack, until ecx is 0
        rep movs    dword ptr es:[edi],dword ptr [esi]
        // Push this pointer if not 0
        cmp            thisPtr,0
        // If passed thisPtr, do a class member call.
        je            no_this_ptr
        push        thisPtr
        call        functionPtr
        pop         thisPtr
        jmp            done
    no_this_ptr:
        // Regular C call
        call        functionPtr
    done:
        add         esp,numBytes
    }
}


void main(void)
{
    char in[256];
    unsigned int inLength;
    inLength=SerializeParameters(in, sizeof(in), 9, 6);
    DerivedClass myClass;

    char out[256];
    unsigned int bytesWritten;
    unsigned char numParameters;
    unsigned int parameterLengths[10];
    DeserializeParameters(out, sizeof(out), &bytesWritten,
        in,inLength,
        &numParameters,
        parameterLengths, sizeof(parameterLengths)/sizeof(unsigned int));
    
    void *v;
    GET_OBJECT_MEMBER_PTR(v, MyClass, func6, (int a, char b));
    printf("%s\n", OBJECT_MEMBER_SIGNATURE(MyClass, func6, (int a, char b)));
    DoFunctionCall(out, bytesWritten, v, &myClass);
    DoFunctionCall(out, bytesWritten, func2, 0);
}