/*
 * semaphore.c
 *
 * Copyright (C) 2016 Aleksandar Andrejevic <theflash@sdf.lonestar.org>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <semaphore.h>
#include <thread.h>
#include <heap.h>

void init_semaphore(semaphore_t *semaphore, dword_t init_count, dword_t max_count)
{
    ASSERT(init_count <= max_count);

    semaphore->count = init_count;
    semaphore->max_count = max_count;
}

sysret_t syscall_create_semaphore(const char *name, dword_t init_count, dword_t max_count, handle_t *handle)
{
    handle_t safe_handle;
    char *safe_name = NULL;
    if ((max_count == 0) || (init_count > max_count)) return ERR_INVALID;

    semaphore_t *semaphore = (semaphore_t*)malloc(sizeof(semaphore_t));
    if (semaphore == NULL) return ERR_NOMEMORY;

    if (name != NULL)
    {
        if (get_previous_mode() == USER_MODE) safe_name = copy_user_string(name);
        else safe_name = (char*)name;
    }

    init_semaphore(semaphore, init_count, max_count);
    init_object(&semaphore->header, name, OBJECT_SEMAPHORE);

    dword_t ret = create_object(&semaphore->header);
    if (ret != ERR_SUCCESS)
    {
        if (semaphore->header.name) free(semaphore->header.name);
        free(semaphore);
        return ret;
    }

    ret = open_object(&semaphore->header, 0, &safe_handle);
    dereference(&semaphore->header);

    if (ret == ERR_SUCCESS)
    {
        EH_TRY *handle = safe_handle;
        EH_CATCH ret = ERR_BADPTR;
        EH_DONE;
    }

    if (get_previous_mode() == USER_MODE) free(safe_name);
    return ret;
}

sysret_t syscall_open_semaphore(const char *name, handle_t *handle)
{
    handle_t safe_handle;
    char *safe_name = NULL;

    if (get_previous_mode() == USER_MODE)
    {
        dword_t name_length = 0;

        EH_TRY name_length = strlen(name);
        EH_CATCH EH_ESCAPE(return ERR_BADPTR);
        EH_DONE;

        if (!check_usermode(name, name_length + 1)) return ERR_BADPTR;
        if (!check_usermode(handle, sizeof(handle_t))) return ERR_BADPTR;

        safe_name = copy_user_string(name);
        if (safe_name == NULL) return ERR_BADPTR;
    }
    else safe_name = (char*)name;

    dword_t ret = open_object_by_name(safe_name, OBJECT_SEMAPHORE, 0, &safe_handle);

    EH_TRY *handle = safe_handle;
    EH_CATCH
    {
        syscall_close_object(safe_handle);
        ret = ERR_BADPTR;
    }
    EH_DONE;

    if (get_previous_mode() == USER_MODE) free(safe_name);
    return ret;
}

dword_t wait_semaphore(semaphore_t *semaphore, dword_t count, dword_t timeout)
{
    if (count > semaphore->max_count) return ERR_INVALID;

    if (semaphore->count < count)
    {
        wait_condition_t condition = { .type = WAIT_UNTIL_NOT_LESS, .pointer = &semaphore->count, .value = count };
        wait_result_t result = scheduler_wait(&condition, timeout);

        if (result == WAIT_TIMED_OUT) return ERR_TIMEOUT;
        else if (result == WAIT_CANCELED) return ERR_CANCELED;
    }

    semaphore->count -= count;
    return ERR_SUCCESS;
}

dword_t release_semaphore(semaphore_t *semaphore, dword_t count)
{
    if ((semaphore->count + count) <= semaphore->max_count)
    {
        semaphore->count += count;
        if (scheduler_enabled) syscall_yield_quantum();
        return ERR_SUCCESS;
    }
    else
    {
        return ERR_INVALID;
    }
}

dword_t semaphore_pre_wait(object_t *obj, void *parameter, wait_condition_t *condition)
{
    semaphore_t *semaphore = (semaphore_t*)obj;
    dword_t count = parameter ? (dword_t)parameter : 1;
    if (count > semaphore->max_count) return ERR_INVALID;

    condition->type = WAIT_UNTIL_NOT_LESS;
    condition->pointer = &semaphore->count;
    condition->value = count;
    return ERR_SUCCESS;
}

void semaphore_post_wait(object_t *obj, void *parameter, wait_result_t result)
{
    semaphore_t *semaphore = (semaphore_t*)obj;
    semaphore->count -= (dword_t)parameter;
}

sysret_t syscall_release_semaphore(handle_t semaphore, dword_t count)
{
    semaphore_t *obj;

    if (!reference_by_handle(semaphore, OBJECT_SEMAPHORE, (object_t**)&obj)) return ERR_INVALID;
    release_semaphore(obj, count);
    dereference(&obj->header);

    return ERR_SUCCESS;
}
