/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 "rwlock/rwlock.h"
#include "rwlock_test.h"

#define RTCB_READ_TASK_PRIO     10
#define RTCB_WRITE_TASK_PRIO    11

#define RTCB_STACK_SIZE         OS_STACK_ALIGN(1024)

static void rtcb_evcb_read(struct os_event *ev);
static void rtcb_evcb_write(struct os_event *ev);

static int rtcb_num_readers;
static int rtcb_num_writers;

static struct os_eventq rtcb_evq_read;
static struct os_eventq rtcb_evq_write;

static struct os_task rtcb_task_read;
static struct os_task rtcb_task_write;

static os_stack_t rtcb_stack_read[RTCB_STACK_SIZE];
static os_stack_t rtcb_stack_write[RTCB_STACK_SIZE];

static struct rwlock rtcb_rwlock;

static struct os_event rtcb_ev_read = {
    .ev_cb = rtcb_evcb_read,
};

static struct os_event rtcb_ev_write = {
    .ev_cb = rtcb_evcb_write,
};

static void
rtcb_evcb_read(struct os_event *ev)
{
    rwlock_acquire_read(&rtcb_rwlock);
    rtcb_num_readers++;
}

static void
rtcb_evcb_write(struct os_event *ev)
{
    rwlock_acquire_write(&rtcb_rwlock);
    rtcb_num_writers++;
}

static void
rtcb_enqueue_read(void)
{
    os_eventq_put(&rtcb_evq_read, &rtcb_ev_read);
}

static void
rtcb_enqueue_write(void)
{
    os_eventq_put(&rtcb_evq_write, &rtcb_ev_write);
}

static void
rtcb_read_task_handler(void *arg)
{
    while (1) {
        os_eventq_run(&rtcb_evq_read);
    }
}

static void
rtcb_write_task_handler(void *arg)
{
    while (1) {
        os_eventq_run(&rtcb_evq_write);
    }
}

TEST_CASE_TASK(rwlock_test_case_basic)
{
    int rc;

    os_eventq_init(&rtcb_evq_read);
    os_eventq_init(&rtcb_evq_write);

    rc = os_task_init(&rtcb_task_read, "read", rtcb_read_task_handler, NULL,
                      RTCB_READ_TASK_PRIO, OS_WAIT_FOREVER, rtcb_stack_read,
                      RTCB_STACK_SIZE);
    TEST_ASSERT_FATAL(rc == 0);

    rc = os_task_init(&rtcb_task_write, "write", rtcb_write_task_handler, NULL,
                      RTCB_WRITE_TASK_PRIO, OS_WAIT_FOREVER, rtcb_stack_write,
                      RTCB_STACK_SIZE);
    TEST_ASSERT_FATAL(rc == 0);

    rwlock_init(&rtcb_rwlock);

    /* Enqueue one read; ensure it acquires the lock. */
    rtcb_enqueue_read();
    TEST_ASSERT_FATAL(rtcb_num_readers == 1);
    TEST_ASSERT_FATAL(rtcb_num_writers == 0);

    /* Enqueue one write; ensure it does *not* acquire the lock. */
    rtcb_enqueue_write();
    TEST_ASSERT_FATAL(rtcb_num_readers == 1);
    TEST_ASSERT_FATAL(rtcb_num_writers == 0);

    /* Enqueue two more readers; ensure both do *not* acquire the lock. */
    rtcb_enqueue_read();
    rtcb_enqueue_read();
    TEST_ASSERT_FATAL(rtcb_num_readers == 1);
    TEST_ASSERT_FATAL(rtcb_num_writers == 0);

    /* Release reader; ensure lock given to writer. */
    rwlock_release_read(&rtcb_rwlock);
    TEST_ASSERT_FATAL(rtcb_num_readers == 1);
    TEST_ASSERT_FATAL(rtcb_num_writers == 1);

    /* Release writer; ensure lock given to both pending readers. */
    rwlock_release_write(&rtcb_rwlock);
    TEST_ASSERT_FATAL(rtcb_num_readers == 3);
    TEST_ASSERT_FATAL(rtcb_num_writers == 1);

    /*** Reset state. */
    rwlock_init(&rtcb_rwlock);
    rtcb_num_readers = 0;
    rtcb_num_writers = 0;

    /* Enqueue two writers; ensure lock given to one writer. */
    rtcb_enqueue_write();
    rtcb_enqueue_write();
    TEST_ASSERT_FATAL(rtcb_num_readers == 0);
    TEST_ASSERT_FATAL(rtcb_num_writers == 1);

    /* Enqueue two readers; ensure lock not given. */
    rtcb_enqueue_read();
    rtcb_enqueue_read();
    TEST_ASSERT_FATAL(rtcb_num_readers == 0);
    TEST_ASSERT_FATAL(rtcb_num_writers == 1);

    /* Release writer; ensure lock given to second writer. */
    rwlock_release_write(&rtcb_rwlock);
    TEST_ASSERT_FATAL(rtcb_num_readers == 0);
    TEST_ASSERT_FATAL(rtcb_num_writers == 2);

    /* Release writer; ensure lock given to both readers. */
    rwlock_release_write(&rtcb_rwlock);
    TEST_ASSERT_FATAL(rtcb_num_readers == 2);
    TEST_ASSERT_FATAL(rtcb_num_writers == 2);
}
