#include <stdint.h>
#include <stdlib.h>

#include <pthread.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <mach/mach_time.h>


extern "C"
{

void* g_malloc(size_t size)
{
    return malloc(size);
}

void g_free(void* ptr)
{
    free(ptr);
}


struct GError
{
    uint32_t domain;
    int code;
    char* message;
};

void g_clear_error(GError** err)
{
    if (nullptr != err && nullptr != *err)
    {
        free(*err);
        *err = nullptr;
    }
}


void g_return_if_fail_warning(const char* domain, const char* function, const char* expression)
{
}


enum GFileTest
{
    G_FILE_TEST_IS_REGULAR    = 1 << 0,
    G_FILE_TEST_IS_SYMLINK    = 1 << 1,
    G_FILE_TEST_IS_DIR        = 1 << 2,
    G_FILE_TEST_IS_EXECUTABLE = 1 << 3,
    G_FILE_TEST_EXISTS        = 1 << 4
};

int g_file_test(const char* filename, int test)
{
    if (filename == nullptr)
    {
        return 0;
    }

    if ((test & G_FILE_TEST_EXISTS) && (access(filename, F_OK) == 0))
    {
        return 1;
    }

    if ((test & G_FILE_TEST_IS_EXECUTABLE) && (access(filename, X_OK) == 0))
    {
        if (getuid() != 0)
        {
            return 1;
        }
    }
    else
    {
        test &= ~G_FILE_TEST_IS_EXECUTABLE;
    }

    if (test & G_FILE_TEST_IS_SYMLINK)
    {
        struct stat s;

        if ((lstat(filename, &s) == 0) && S_ISLNK(s.st_mode))
        {
            return 1;
        }
    }

    if (test & (G_FILE_TEST_IS_REGULAR | G_FILE_TEST_IS_DIR | G_FILE_TEST_IS_EXECUTABLE))
    {
        struct stat s;

        if (stat(filename, &s) == 0)
        {
            if ((test & G_FILE_TEST_IS_REGULAR) && S_ISREG(s.st_mode))
            {
                return 1;
            }

            if ((test & G_FILE_TEST_IS_DIR) && S_ISDIR(s.st_mode))
            {
                return 1;
            }

            if ((test & G_FILE_TEST_IS_EXECUTABLE) && ((s.st_mode & S_IXOTH) || (s.st_mode & S_IXUSR) || (s.st_mode & S_IXGRP)))
            {
                return 1;
            }
        }
    }

    return 0;
}


int64_t g_get_monotonic_time()
{
    static mach_timebase_info_data_t timebase_info;

    if (timebase_info.denom == 0)
    {
        mach_timebase_info(&timebase_info);

        if (timebase_info.numer % 1000 == 0)
        {
            timebase_info.numer /= 1000;
        }
        else
        {
            timebase_info.denom *= 1000;
        }

        if (timebase_info.denom % timebase_info.numer == 0)
        {
            timebase_info.denom /= timebase_info.numer;
            timebase_info.numer = 1;
        }
    }

    return mach_absolute_time() / timebase_info.denom;
}


void g_usleep(unsigned long microseconds)
{
    usleep(microseconds);
}


struct GMutex
{
    pthread_mutex_t* p;
};

static pthread_mutex_t* g_mutex_impl_new()
{
    return new pthread_mutex_t(PTHREAD_MUTEX_INITIALIZER);
}

static void g_mutex_impl_free(pthread_mutex_t* mutex)
{
    pthread_mutex_destroy(mutex);
    delete mutex;
}

static pthread_mutex_t* g_mutex_get_impl(GMutex* mutex)
{
    pthread_mutex_t* impl = mutex->p;
    __sync_synchronize();

    if (nullptr == impl)
    {
        impl = g_mutex_impl_new();

        if (__sync_val_compare_and_swap(&mutex->p, nullptr, impl) != nullptr)
        {
            g_mutex_impl_free(impl);
            impl = mutex->p;
        }
    }

    return impl;
}

void g_mutex_init(GMutex* mutex)
{
    mutex->p = g_mutex_impl_new();
}

void g_mutex_clear(GMutex* mutex)
{
    g_mutex_impl_free(mutex->p);
}

void g_mutex_lock(GMutex* mutex)
{
    pthread_mutex_lock(g_mutex_get_impl(mutex));
}

void g_mutex_unlock(GMutex* mutex)
{
    pthread_mutex_unlock(g_mutex_get_impl(mutex));
}


struct GRecMutex
{
    pthread_mutex_t* p;
    unsigned int i[2];
};

static pthread_mutex_t* g_rec_mutex_impl_new()
{
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);

    pthread_mutex_t* mutex = new pthread_mutex_t;
    pthread_mutex_init(mutex, &attr);

    pthread_mutexattr_destroy(&attr);

    return mutex;
}

static void g_rec_mutex_impl_free(pthread_mutex_t* mutex)
{
    pthread_mutex_destroy(mutex);
    delete mutex;
}

static pthread_mutex_t* g_rec_mutex_get_impl(GRecMutex* mutex)
{
    pthread_mutex_t* impl = mutex->p;
    __sync_synchronize();

    if (nullptr == impl)
    {
        impl = g_rec_mutex_impl_new();

        if (__sync_val_compare_and_swap(&mutex->p, nullptr, impl) != nullptr)
        {
            g_rec_mutex_impl_free(impl);
            impl = mutex->p;
        }
    }

    return impl;
}

void g_rec_mutex_init(GRecMutex* mutex)
{
    mutex->p = g_rec_mutex_impl_new();
}

void g_rec_mutex_clear(GRecMutex* mutex)
{
    g_rec_mutex_impl_free(mutex->p);
}

void g_rec_mutex_lock(GRecMutex* mutex)
{
    pthread_mutex_lock(g_rec_mutex_get_impl(mutex));
}

void g_rec_mutex_unlock(GRecMutex* mutex)
{
    pthread_mutex_unlock(g_rec_mutex_get_impl(mutex));
}


struct GCond
{
    pthread_cond_t* p;
    unsigned int i[2];
};

static pthread_cond_t* g_cond_impl_new()
{
    return new pthread_cond_t(PTHREAD_COND_INITIALIZER);
}

static void g_cond_impl_free(pthread_cond_t* cond)
{
    pthread_cond_destroy(cond);
    delete cond;
}

static pthread_cond_t* g_cond_get_impl(GCond* cond)
{
    pthread_cond_t* impl = cond->p;
    __sync_synchronize();

    if (nullptr == impl)
    {
        impl = g_cond_impl_new();

        if (__sync_val_compare_and_swap(&cond->p, nullptr, impl) != nullptr)
        {
            g_cond_impl_free(impl);
            impl = cond->p;
        }
    }

    return impl;
}

void g_cond_init(GCond* cond)
{
    cond->p = g_cond_impl_new();
}

void g_cond_clear(GCond* cond)
{
    g_cond_impl_free(cond->p);
}

void g_cond_wait(GCond* cond, GMutex* mutex)
{
    pthread_cond_wait(g_cond_get_impl(cond), g_mutex_get_impl(mutex));
}

void g_cond_signal(GCond* cond)
{
    pthread_cond_signal(g_cond_get_impl(cond));
}

void g_cond_broadcast(GCond* cond)
{
    pthread_cond_broadcast(g_cond_get_impl(cond));
}


using GDestroyNotify = void (*)(void* data);

struct GPrivate
{
    pthread_key_t* p;
    GDestroyNotify notify;
    void* future[2];
};

static pthread_key_t* g_private_impl_new(GDestroyNotify notify)
{
    pthread_key_t* key = new pthread_key_t;
    pthread_key_create(key, notify);
    return key;
}

static void g_private_impl_free(pthread_key_t* key)
{
    pthread_key_delete(*key);
    delete key;
}

static inline pthread_key_t* g_private_get_impl(GPrivate* key)
{
    pthread_key_t* impl = key->p;
    __sync_synchronize();

    if (nullptr == impl)
    {
        impl = g_private_impl_new(key->notify);

        if (__sync_val_compare_and_swap(&key->p, nullptr, impl) != nullptr)
        {
            g_private_impl_free(impl);
            impl = key->p;
        }
    }

  return impl;
}

void* g_private_get(GPrivate* key)
{
    return pthread_getspecific(*g_private_get_impl(key));
}

void g_private_set(GPrivate* key, void* value)
{
    pthread_setspecific(*g_private_get_impl(key), value);
}


struct GThread
{
    pthread_t thread;
    int ref_count;
};

using GThreadFunc = void* (*)(void* data);

GThread* g_thread_try_new(const char* name, GThreadFunc func, void* data, GError** error)
{
    if (error != nullptr)
    {
        *error = nullptr;
    }

    GThread* thread = new GThread;
    pthread_create(&thread->thread, nullptr, func, data);
    thread->ref_count = 1;

    return thread;
}

void* g_thread_join(GThread* thread)
{
    void* result = nullptr;
    pthread_join(thread->thread, &result);
    return result;
}

void g_thread_unref(GThread* thread)
{
    if (__sync_sub_and_fetch(&thread->ref_count, 1) == 0)
    {
        g_thread_join(thread);
        delete thread;
    }
}

}