/************************************************************************************************************************
It's internal code!
This module contain implementation of standard functionality.

Copyright: Copyright © 2018-2019, Dawid Masiukiewicz, Michał Masiukiewicz
License: BSD 3-clause, see LICENSE file in project root folder.
*/
module bubel.ecs.std;

version(Emscripten)version = ECSEmscripten;

import std.traits;

version(ECSEmscripten)
{
    extern(C) struct pthread_mutex_t
    { 
        union 
        { 
            int[6] __i; 
            void[6] *__p; 
        }
    } 

    extern(C) struct pthread_mutexattr_t
    { 
        uint __attr; 
    }

    extern(C) int memcmp (const void *s1, const void *s2, size_t size);
    extern(C) void exit (int status) nothrow @nogc;
    extern(C) void __assert(const(char)* msg, const(char)* file, uint line) { exit(-20);}
    extern(C) void free(void*) @nogc nothrow @system;
    extern(C) void* malloc(size_t size) @nogc nothrow @system;
    extern(C) void* realloc(void*, size_t size) @nogc nothrow @system;
    extern(C) void* memcpy(return void*, scope const void*, size_t size) @nogc nothrow @system;
    extern(C) void* memset(void*, int val, size_t size) @nogc nothrow @system;
    extern(C) int posix_memalign(void**, size_t, size_t) @nogc nothrow @system;
    extern(C) void qsort(void* base, size_t num, size_t size, int function(const void*,const void*) compar) @nogc nothrow @system;

    extern(C) int pthread_mutex_lock(pthread_mutex_t *mutex) @nogc nothrow;
    extern(C) int pthread_mutex_trylock(pthread_mutex_t *mutex) @nogc nothrow;
    extern(C) int pthread_mutex_unlock(pthread_mutex_t *mutex) @nogc nothrow;
    extern(C) void pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type) @nogc nothrow;
    extern(C) void pthread_mutexattr_destroy(pthread_mutexattr_t *attr) @nogc nothrow;
    extern(C) int pthread_mutexattr_init(pthread_mutexattr_t *attr) @nogc nothrow;
    extern(C) int pthread_mutex_destroy(pthread_mutex_t *mutex) @nogc nothrow;
    extern(C) int pthread_mutex_init(pthread_mutex_t* mutex, const pthread_mutexattr_t* attr) @nogc nothrow;

}
else
{
    public import core.stdc.stdlib : malloc, free, realloc;
    public import core.stdc.string : memcpy, memset;
    public import core.stdc.stdlib : qsort;
}

version(ECSEmscripten)
{
}
else version (Windows)
{
    import core.sys.windows.windows;
    extern(Windows) void* _aligned_malloc(size_t size,size_t alignment) @nogc nothrow @system;
    extern(Windows) void _aligned_free(void* ptr) @nogc nothrow @system;
    
    version(LDC)
    {
        /*extern(Windows) void* __alloca(size_t size) @nogc nothrow @system;
        alias alloca = __alloca;*/

        extern(Windows) void ___chkstk_ms() @nogc nothrow @system;
    
        extern(Windows) void __chkstk()
        {
            ___chkstk_ms();
        }
    }
}
else version (Posix)
{
    import core.sys.posix.pthread;
    import core.sys.posix.stdlib;
}

version(ECSEmscripten)
{
    private const uint max_alloca = 10000; 
    private __gshared byte[max_alloca] alloca_array;
    private __gshared uint alloca_pos = 0;
    export extern(C) void* alloca(size_t length) @nogc nothrow  
    {
        if(alloca_pos + length > max_alloca)alloca_pos = 0;
        void* ret = &alloca_array[alloca_pos];
        alloca_pos += length;
        return ret;
    }
    //extern(C) void* alloca(size_t size) @nogc nothrow;
    /*export extern(C) void* alloca(size_t length) @nogc nothrow  
    {
        return null;
    }*/
}
else version(D_BetterC)
{
    private const uint max_alloca = 10000; 
    private __gshared byte[max_alloca] alloca_array;
    private uint alloca_pos = 0;
    export extern(C) void* alloca(size_t length) @nogc nothrow  
    {
        if(alloca_pos + length > max_alloca)alloca_pos = 0;
        void* ret = &alloca_array[alloca_pos];
        alloca_pos += length;
        return ret;
    }
}
else 
{
    public import core.stdc.stdlib : alloca;
}

static struct Mallocator
{
    static T[] makeArray(T)(size_t length) nothrow @nogc
    {
        T[] ret = (cast(T*)malloc(T.sizeof * length))[0 .. length];

        static if(__traits(isPOD, T))
        {
            static immutable T init = T.init;
            
            foreach(i;0..ret.length)
            {
                memcpy(&ret[i], &init, T.sizeof);
            }
        }
        else
        {
            static import std.conv;
            foreach(i;0..ret.length)
            {
                std.conv.emplace(&ret[i]);
            }
        }
        return ret;
    }

    static T[] makeArray(T)(size_t length, T initializer) nothrow @nogc
    {
        T[] ret = (cast(T*)malloc(T.sizeof * length))[0 .. length];
        foreach(ref v; ret)v = initializer;
        return ret;
    }

    static T[] expandArray(T)(T[] array, size_t length) nothrow @nogc
    {
        size_t new_length = array.length + length;
        return (cast(T*)realloc(array.ptr, T.sizeof * new_length))[0 .. new_length];
    }

    static T[] makeArray(T)(T[] array) nothrow @nogc
    {
        T[] ret = (cast(T*)malloc(T.sizeof * array.length))[0 .. array.length];//Mallocator.makeArray!(T)(array.length);
        foreach(i, ref v;ret)v = array[i];
        return ret;
    }

    static T* make(T, Args...)(Args args) 
    {
        T* ret = cast(T*)malloc(T.sizeof);
        static import std.conv;
        static if(__traits(isPOD, T))
        {
            static immutable T init = T.init;
            memcpy(ret, &init, T.sizeof);
        }
        else static if(is(T == struct))std.conv.emplace(ret, args);
        return ret;
    }

    static void* alignAlloc(size_t length, size_t alignment) nothrow @nogc
    {
        void* ret;
        version(Posix)posix_memalign(&ret, alignment, length);//ret = aligned_alloc(alignment, length);
        else version(Windows)ret = _aligned_malloc(length, alignment);
        else version(ECSEmscripten)posix_memalign(&ret, alignment, length);//malloc(length);
        else static assert(0, "Unimplemented platform!");
        return ret;
    }

    static void dispose(T)(T object) nothrow @nogc
    {
        static if(__traits(hasMember, T, "__xdtor"))object.__xdtor();
        else static if(__traits(hasMember, T, "__dtor"))object.__dtor();
        free(cast(void*)object);
    }

    static void alignDispose(T)(T object)
    {
        static if(__traits(hasMember, T, "__xdtor"))object.__xdtor();
        else static if(__traits(hasMember, T, "__dtor"))object.__dtor();
        version(Posix)free(cast(void*)object);
        else version(Windows)_aligned_free(cast(void*)object);
        else version(ECSEmscripten)free(cast(void*)object);
        else static assert(0, "Unimplemented platform!");
    }
}

struct Mutex
{

    version(ECSEmscripten)
    {
        void initialize() nothrow @nogc
        {
            pthread_mutexattr_t attr = void;

            //pthread_mutexattr_init(&attr);
            
            //pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
            pthread_mutex_init(cast(pthread_mutex_t*) &m_handle, &attr);

            //pthread_mutexattr_destroy(&attr);
        }

        void destroy() nothrow @nogc
        {
            pthread_mutex_destroy(&m_handle);
        }

        void lock() nothrow @nogc
        {
            pthread_mutex_lock(&m_handle);
        }

        void unlock() nothrow @nogc
        {
            pthread_mutex_unlock(&m_handle);
        }

        int tryLock() nothrow @nogc
        {
            return pthread_mutex_trylock(&m_handle) == 0;
        }

        private pthread_mutex_t m_handle;
    }
    else version (Windows)
    {
        void initialize() nothrow @nogc
        {
            InitializeCriticalSection(cast(CRITICAL_SECTION*) &m_handle);
        }

        void destroy() nothrow @nogc
        {
            DeleteCriticalSection(&m_handle);
        }

        void lock() nothrow @nogc
        {
            EnterCriticalSection(&m_handle);
        }

        void unlock() nothrow @nogc
        {
            LeaveCriticalSection(&m_handle);
        }

        int tryLock() nothrow @nogc
        {
            return TryEnterCriticalSection(&m_handle) != 0;
        }

        CRITICAL_SECTION    m_handle;
    }
    else version (Posix)
    {
        void initialize() nothrow @nogc
        {
            pthread_mutexattr_t attr = void;

            pthread_mutexattr_init(&attr);
            
            pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
            pthread_mutex_init(cast(pthread_mutex_t*) &m_handle, &attr);

            pthread_mutexattr_destroy(&attr);
        }

        void destroy() nothrow @nogc
        {
            pthread_mutex_destroy(&m_handle);
        }

        void lock() nothrow @nogc
        {
            pthread_mutex_lock(&m_handle);
        }

        void unlock() nothrow @nogc
        {
            pthread_mutex_unlock(&m_handle);
        }

        int tryLock() nothrow @nogc
        {
            return pthread_mutex_trylock(&m_handle) == 0;
        }

        private pthread_mutex_t m_handle;
    }
    else static assert(0, "unsupported platform!");
}