/*
 * Copyright 2022-2023 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

#include <stdlib.h>
#include "internal/event_queue.h"
#include "ssl_local.h"

struct ossl_event_queue_st {
    PRIORITY_QUEUE_OF(OSSL_EVENT) *timed_events;
    PRIORITY_QUEUE_OF(OSSL_EVENT) *now_events;
};

static int event_compare_times(const OSSL_EVENT *a, const OSSL_EVENT *b)
{
    return ossl_time_compare(a->when, b->when);
}

static int event_compare_priority(const OSSL_EVENT *a, const OSSL_EVENT *b)
{
    if (a->priority > b->priority)
        return -1;
    if (a->priority < b->priority)
        return 1;
    return 0;
}

OSSL_EVENT_QUEUE *ossl_event_queue_new(void)
{
    OSSL_EVENT_QUEUE *r = OPENSSL_malloc(sizeof(*r));

    if (r != NULL) {
        r->timed_events = ossl_pqueue_OSSL_EVENT_new(&event_compare_times);
        r->now_events = ossl_pqueue_OSSL_EVENT_new(&event_compare_priority);
        if (r->timed_events == NULL || r->now_events == NULL) {
            ossl_event_queue_free(r);
            return NULL;
        }
    }
    return r;
}

void ossl_event_free(OSSL_EVENT *event)
{
    if (event != NULL) {
        if (event->flag_dynamic)
            OPENSSL_free(event);
        else
            event->queue = NULL;
    }
}

static void event_queue_free(PRIORITY_QUEUE_OF(OSSL_EVENT) *queue)
{
    OSSL_EVENT *e;

    if (queue != NULL) {
        while ((e = ossl_pqueue_OSSL_EVENT_pop(queue)) != NULL)
            ossl_event_free(e);
        ossl_pqueue_OSSL_EVENT_free(queue);
    }
}

void ossl_event_queue_free(OSSL_EVENT_QUEUE *queue)
{
    if (queue != NULL) {
        event_queue_free(queue->now_events);
        event_queue_free(queue->timed_events);
        OPENSSL_free(queue);
    }
}

static ossl_inline
int event_queue_add(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event)
{
    PRIORITY_QUEUE_OF(OSSL_EVENT) *pq =
            ossl_time_compare(event->when, ossl_time_now()) <= 0
            ? queue->now_events
            : queue->timed_events;

    if (ossl_pqueue_OSSL_EVENT_push(pq, event, &event->ref)) {
        event->queue = pq;
        return 1;
    }
    return 0;
}

static ossl_inline
void ossl_event_set(OSSL_EVENT *event, uint32_t type, uint32_t priority,
                    OSSL_TIME when, void *ctx,
                    void *payload, size_t payload_size)
{
    event->type = type;
    event->priority = priority;
    event->when = when;
    event->ctx = ctx;
    event->payload = payload;
    event->payload_size = payload_size;
}

OSSL_EVENT *ossl_event_queue_add_new(OSSL_EVENT_QUEUE *queue, 
                                     uint32_t type, uint32_t priority,
                                     OSSL_TIME when, void *ctx,
                                     void *payload, size_t payload_size)
{
    OSSL_EVENT *e = OPENSSL_malloc(sizeof(*e));

    if (e == NULL || queue == NULL) {
        OPENSSL_free(e);
        return NULL;
    }

    ossl_event_set(e, type, priority, when, ctx, payload, payload_size);
    e->flag_dynamic = 1;
    if (event_queue_add(queue, e))
        return e;
    OPENSSL_free(e);
    return NULL;
}

int ossl_event_queue_add(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event,
                         uint32_t type, uint32_t priority,
                         OSSL_TIME when, void *ctx,
                         void *payload, size_t payload_size)
{
    if (event == NULL || queue == NULL)
        return 0;
    ossl_event_set(event, type, priority, when, ctx, payload, payload_size);
    event->flag_dynamic = 0;
    return event_queue_add(queue, event);
}

int ossl_event_queue_remove(OSSL_EVENT_QUEUE *queue, OSSL_EVENT *event)
{
    if (event != NULL && event->queue != NULL) {
        ossl_pqueue_OSSL_EVENT_remove(event->queue, event->ref);
        event->queue = NULL;
    }
    return 1;
}

OSSL_TIME ossl_event_time_until(const OSSL_EVENT *event)
{
    if (event == NULL)
        return ossl_time_infinite();
    return ossl_time_subtract(event->when, ossl_time_now());
}

OSSL_TIME ossl_event_queue_time_until_next(const OSSL_EVENT_QUEUE *queue)
{
    if (queue == NULL)
        return ossl_time_infinite();
    if (ossl_pqueue_OSSL_EVENT_num(queue->now_events) > 0)
        return ossl_time_zero();
    return ossl_event_time_until(ossl_pqueue_OSSL_EVENT_peek(queue->timed_events));
}

int ossl_event_queue_postpone_until(OSSL_EVENT_QUEUE *queue,
                                    OSSL_EVENT *event,
                                    OSSL_TIME when)
{
    if (ossl_event_queue_remove(queue, event)) {
        event->when = when;
        return event_queue_add(queue, event);
    }
    return 0;
}

int ossl_event_queue_get1_next_event(OSSL_EVENT_QUEUE *queue,
                                     OSSL_EVENT **event)
{
    OSSL_TIME now = ossl_time_now();
    OSSL_EVENT *e;

    /* Check for expired timer based events and convert them to now events */
    while ((e = ossl_pqueue_OSSL_EVENT_peek(queue->timed_events)) != NULL
           && ossl_time_compare(e->when, now) <= 0) {
        e = ossl_pqueue_OSSL_EVENT_pop(queue->timed_events);
        if (!ossl_pqueue_OSSL_EVENT_push(queue->now_events, e, &e->ref)) {
            e->queue = NULL;
            return 0;
        }
    }

    /*
     * Get next event from the now queue.
     * The pop returns NULL when there is none.
     */
    *event = ossl_pqueue_OSSL_EVENT_pop(queue->now_events);
    return 1;
}
