/*
 * Copyright (c) 2017 Cisco and/or its affiliates.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at:
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 */
#include <config.h>
#include <stdio.h>

#include <parc/algol/parc_Object.h>
#include <parc/algol/parc_DisplayIndented.h>
#include <parc/algol/parc_Memory.h>
#include <parc/algol/parc_Execution.h>

#include <parc/concurrent/parc_FutureTask.h>

struct PARCFutureTask {
    void *(*function)(PARCFutureTask *task, void *parameter);
    void *parameter;
    void *result;
    bool isRunning;
    bool isDone;
    bool isCancelled;
};

static void
_parcFutureTask_Initialise(PARCFutureTask *futureTask)
{
    futureTask->result = NULL;
    futureTask->isDone = false;
    futureTask->isCancelled = false;
    futureTask->isRunning = false;
}

static bool
_parcFutureTask_Destructor(PARCFutureTask **instancePtr)
{
    assertNotNull(instancePtr, "Parameter must be a non-null pointer to a PARCFutureTask pointer.");
    PARCFutureTask *task = *instancePtr;

    if (parcObject_IsInstanceOf(task->parameter, &PARCObject_Descriptor)) {
        parcObject_Release(&task->parameter);
    }

    return true;
}

parcObject_ImplementAcquire(parcFutureTask, PARCFutureTask);

parcObject_ImplementRelease(parcFutureTask, PARCFutureTask);

parcObject_Override(PARCFutureTask, PARCObject,
                    .isLockable = true,
                    .destructor = (PARCObjectDestructor *) _parcFutureTask_Destructor,
                    .copy = (PARCObjectCopy *) parcFutureTask_Copy,
                    .toString = (PARCObjectToString *) parcFutureTask_ToString,
                    .equals = (PARCObjectEquals *) parcFutureTask_Equals,
                    .compare = (PARCObjectCompare *) parcFutureTask_Compare,
                    .hashCode = (PARCObjectHashCode *) parcFutureTask_HashCode,
                    .display = (PARCObjectDisplay *) parcFutureTask_Display);

void
parcFutureTask_AssertValid(const PARCFutureTask *task)
{
    assertTrue(parcFutureTask_IsValid(task),
               "PARCFutureTask is not valid.");
}

PARCFutureTask *
parcFutureTask_Create(void *(*function)(PARCFutureTask *task, void *parameter), void *parameter)
{
    PARCFutureTask *result = parcObject_CreateInstance(PARCFutureTask);

    if (parcObject_IsInstanceOf(parameter, &PARCObject_Descriptor)) {
        parameter = parcObject_Acquire(parameter);
    }

    if (result != NULL) {
        result->function = function;
        result->parameter = parameter;
        _parcFutureTask_Initialise(result);
    }

    return result;
}

int
parcFutureTask_Compare(const PARCFutureTask *instance, const PARCFutureTask *other)
{
    int result = 0;

    return result;
}

PARCFutureTask *
parcFutureTask_Copy(const PARCFutureTask *original)
{
    PARCFutureTask *result = parcFutureTask_Create(original->function, original->parameter);

    return result;
}

void
parcFutureTask_Display(const PARCFutureTask *instance, int indentation)
{
    parcDisplayIndented_PrintLine(indentation, "PARCFutureTask@%p {", instance);
    /* Call Display() functions for the fields here. */
    parcDisplayIndented_PrintLine(indentation, "}");
}

bool
parcFutureTask_Equals(const PARCFutureTask *x, const PARCFutureTask *y)
{
    bool result = false;

    if (x == y) {
        result = true;
    } else if (x == NULL || y == NULL) {
        result = false;
    } else {
        if (x->function == y->function) {
            if (x->parameter == y->parameter) {
                result = true;
            }
        }
    }

    return result;
}

PARCHashCode
parcFutureTask_HashCode(const PARCFutureTask *instance)
{
    PARCHashCode result = 0;

    return result;
}

bool
parcFutureTask_IsValid(const PARCFutureTask *instance)
{
    bool result = false;

    if (instance != NULL) {
        result = true;
    }

    return result;
}

PARCJSON *
parcFutureTask_ToJSON(const PARCFutureTask *instance)
{
    PARCJSON *result = parcJSON_Create();

    if (result != NULL) {
    }

    return result;
}

char *
parcFutureTask_ToString(const PARCFutureTask *instance)
{
    char *result = parcMemory_Format("PARCFutureTask@%p\n", instance);

    return result;
}

bool
parcFutureTask_Cancel(PARCFutureTask *task, bool mayInterruptIfRunning)
{
    bool result = false;

    if (parcObject_Lock(task)) {
        if (task->isRunning) {
            if (mayInterruptIfRunning) {
                printf("Interrupting a running task is not implemented yet.\n");
            }
            result = false;
        } else {
            task->isCancelled = true;
            task->isDone = true;
            parcObject_Notify(task);
            result = true;
        }

        parcObject_Unlock(task);
    }

    return result;
}

PARCFutureTaskResult
parcFutureTask_Get(const PARCFutureTask *futureTask, const PARCTimeout *timeout)
{
    PARCFutureTaskResult result;

    result.execution = PARCExecution_Timeout;
    result.value = 0;

    if (parcTimeout_IsImmediate(timeout)) {
        if (futureTask->isDone) {
            result.execution = PARCExecution_OK;
            result.value = futureTask->result;
        }
    } else {
        result.execution = PARCExecution_Interrupted;
        result.value = 0;

        parcObject_Lock(futureTask);
        while (!futureTask->isDone) {
            if (parcTimeout_IsNever(timeout)) {
                parcObject_Wait(futureTask);
                result.execution = PARCExecution_OK;
                result.value = futureTask->result;
                break;
            } else {
                if (parcObject_WaitFor(futureTask, parcTimeout_InNanoSeconds(timeout))) {
                    result.execution = PARCExecution_OK;
                    result.value = futureTask->result;
                    break;
                }
            }
        }
        parcObject_Unlock(futureTask);
    }

    return result;
}

bool
parcFutureTask_IsCancelled(const PARCFutureTask *task)
{
    return task->isCancelled;
}

bool
parcFutureTask_IsDone(const PARCFutureTask *task)
{
    return task->isDone;
}

static void *
_parcFutureTask_Execute(PARCFutureTask *task)
{
    task->isRunning = true;
    void *result = task->function(task, task->parameter);
    task->isRunning = false;

    return result;
}

void *
parcFutureTask_Run(PARCFutureTask *task)
{
    if (parcFutureTask_Lock(task)) {
        if (!task->isCancelled) {
            task->result = _parcFutureTask_Execute(task);
            task->isDone = true;
            parcFutureTask_Notify(task);
        }
        parcFutureTask_Unlock(task);
    } else {
        trapCannotObtainLock("Cannot lock PARCFutureTask");
    }
    return task->result;
}

bool
parcFutureTask_RunAndReset(PARCFutureTask *task)
{
    bool result = false;

    if (parcObject_Lock(task)) {
        if (!task->isCancelled) {
            _parcFutureTask_Execute(task);
            parcFutureTask_Reset(task);
            result = true;
        }
        parcFutureTask_Unlock(task);
    } else {
        trapCannotObtainLock("Cannot lock PARCFutureTask");
    }

    return result;
}

void
parcFutureTask_Reset(PARCFutureTask *task)
{
    _parcFutureTask_Initialise(task);
}