/*
 * 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 <stdio.h>
#include <qpid/dispatch/timer.h>
#include "dispatch_private.h"
#include "alloc.h"
#include "timer_private.h"
#include "test_case.h"
#include <qpid/dispatch/threading.h>


static unsigned long    fire_mask;
static qd_timer_list_t  pending_timers;
static sys_mutex_t     *lock;
static long             time;
static qd_timer_t      *timers[16];


void qd_server_timer_pending_LH(qd_timer_t *timer)
{
    DEQ_INSERT_TAIL(pending_timers, timer);
}


void qd_server_timer_cancel_LH(qd_timer_t *timer)
{
    if (timer->state == TIMER_PENDING)
        DEQ_REMOVE(pending_timers, timer);
}


static int fire_head()
{
    sys_mutex_lock(lock);
    int         result = DEQ_SIZE(pending_timers);
    qd_timer_t *timer  = DEQ_HEAD(pending_timers);
    if (timer) {
        DEQ_REMOVE_HEAD(pending_timers);
        qd_timer_idle_LH(timer);
        fire_mask |= (unsigned long) timer->context;
    }
    sys_mutex_unlock(lock);
    return result;
}


static char* test_quiet(void *context)
{
    fire_mask = 0;

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);

    while(fire_head());

    if (fire_mask != 0)
        return "Expected zero timers fired";
    return 0;
}

static char* test_immediate(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 0);

    if (fire_mask != 0)  return "Premature firing";
    if (fire_head() > 1) return "Too many firings";
    if (fire_mask != 1)  return "Incorrect fire mask";

    return 0;
}


static char* test_immediate_reschedule(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 0);
    qd_timer_schedule(timers[0], 0);

    if (fire_mask != 0)  return "pass 1 - Premature firing";
    if (fire_head() > 1) return "pass 1 - Too many firings";
    if (fire_mask != 1)  return "pass 1 - Incorrect fire mask";

    fire_mask = 0;
    qd_timer_schedule(timers[0], 0);
    qd_timer_schedule(timers[0], 0);

    if (fire_mask != 0)  return "pass 2 - Premature firing";
    if (fire_head() > 1) return "pass 2 - Too many firings";
    if (fire_mask != 1)  return "pass 2 - Incorrect fire mask";

    return 0;
}


static char* test_immediate_plus_delayed(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 0);
    qd_timer_schedule(timers[1], 5);

    if (fire_mask != 0)  return "Premature firing";
    if (fire_head() > 1) return "Too many firings";
    if (fire_mask != 1)  return "Incorrect fire mask 1";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    time += 8;
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);

    if (fire_head() < 1) return "Delayed Failed to fire";
    if (fire_mask != 3)  return "Incorrect fire mask 3";

    return 0;
}


static char* test_single(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 2);
    if (fire_head() > 0) return "Premature firing 1";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    if (fire_head() > 0) return "Premature firing 2";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    if (fire_head() < 1) return "Failed to fire";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    if (fire_head() != 0) return "Spurious fires";

    if (fire_mask != 1)  return "Incorrect fire mask";
    if (timers[0]->state != TIMER_IDLE) return "Expected idle timer state";

    return 0;
}


static char* test_two_inorder(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 2);
    qd_timer_schedule(timers[1], 4);

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    int count = fire_head();
    if (count < 1) return "First failed to fire";
    if (count > 1) return "Second fired prematurely";
    if (fire_mask != 1) return "Incorrect fire mask 1";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    if (fire_head() < 1) return "Second failed to fire";
    if (fire_mask != 3)  return "Incorrect fire mask 3";

    return 0;
}


static char* test_two_reverse(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 4);
    qd_timer_schedule(timers[1], 2);

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    int count = fire_head();
    if (count < 1) return "First failed to fire";
    if (count > 1) return "Second fired prematurely";
    if (fire_mask != 2) return "Incorrect fire mask 2";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    if (fire_head() < 1) return "Second failed to fire";
    if (fire_mask != 3)  return "Incorrect fire mask 3";

    return 0;
}


static char* test_two_duplicate(void *context)
{
    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 2);
    qd_timer_schedule(timers[1], 2);

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    int count = fire_head();
    if (count != 2) return "Expected two firings";
    fire_head();
    if (fire_mask != 3) return "Incorrect fire mask 3";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    if (fire_head() > 0) return "Spurious timer fires";

    return 0;
}


static char* test_separated(void *context)
{
    int count;

    while(fire_head());
    fire_mask = 0;

    qd_timer_schedule(timers[0], 2);
    qd_timer_schedule(timers[1], 4);

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    count = fire_head();
    if (count < 1) return "First failed to fire";
    if (count > 1) return "Second fired prematurely";
    if (fire_mask != 1) return "Incorrect fire mask 1";

    qd_timer_schedule(timers[2], 2);
    qd_timer_schedule(timers[3], 4);

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    count = fire_head();
    fire_head();
    if (count < 1) return "Second failed to fire";
    if (count < 2) return "Third failed to fire";
    if (fire_mask != 7)  return "Incorrect fire mask 7";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    count = fire_head();
    if (count < 1) return "Fourth failed to fire";
    if (fire_mask != 15) return "Incorrect fire mask 15";

    sys_mutex_lock(lock);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    qd_timer_visit_LH(time++);
    sys_mutex_unlock(lock);
    count = fire_head();
    if (count > 0) return "Spurious fire";

    return 0;
}


static char* test_big(void *context)
{
    while(fire_head());
    fire_mask = 0;

    long durations[16] =
        { 5,  8,  7,  6,
         14, 10, 16, 15,
         11, 12,  9, 12,
          1,  2,  3,  4};
    unsigned long masks[18] = {
    0x1000,
    0x3000,
    0x7000,
    0xf000,
    0xf001,
    0xf009,
    0xf00d,
    0xf00f,
    0xf40f,
    0xf42f,
    0xf52f,
    0xff2f,
    0xff2f,
    0xff3f,
    0xffbf,
    0xffff,
    0xffff,
    0xffff
    };

    int i;
    for (i = 0; i < 16; i++)
        qd_timer_schedule(timers[i], durations[i]);
    for (i = 0; i < 18; i++) {
        sys_mutex_lock(lock);
        qd_timer_visit_LH(time++);
        sys_mutex_unlock(lock);
        while(fire_head());
        if (fire_mask != masks[i]) {
            static char error[100];
            sprintf(error, "Iteration %d: expected mask %04lx, got %04lx", i, masks[i], fire_mask);
            return error;
        }
    }

    return 0;
}


int timer_tests(void)
{
    int result = 0;

    fire_mask = 0;
    DEQ_INIT(pending_timers);
    lock = qd_timer_lock();
    time = 1;

    timers[0]  = qd_timer(0, 0, (void*) 0x00000001);
    timers[1]  = qd_timer(0, 0, (void*) 0x00000002);
    timers[2]  = qd_timer(0, 0, (void*) 0x00000004);
    timers[3]  = qd_timer(0, 0, (void*) 0x00000008);
    timers[4]  = qd_timer(0, 0, (void*) 0x00000010);
    timers[5]  = qd_timer(0, 0, (void*) 0x00000020);
    timers[6]  = qd_timer(0, 0, (void*) 0x00000040);
    timers[7]  = qd_timer(0, 0, (void*) 0x00000080);
    timers[8]  = qd_timer(0, 0, (void*) 0x00000100);
    timers[9]  = qd_timer(0, 0, (void*) 0x00000200);
    timers[10] = qd_timer(0, 0, (void*) 0x00000400);
    timers[11] = qd_timer(0, 0, (void*) 0x00000800);
    timers[12] = qd_timer(0, 0, (void*) 0x00001000);
    timers[13] = qd_timer(0, 0, (void*) 0x00002000);
    timers[14] = qd_timer(0, 0, (void*) 0x00004000);
    timers[15] = qd_timer(0, 0, (void*) 0x00008000);

    TEST_CASE(test_quiet, 0);
    TEST_CASE(test_immediate, 0);
    TEST_CASE(test_immediate_reschedule, 0);
    TEST_CASE(test_immediate_plus_delayed, 0);
    TEST_CASE(test_single, 0);
    TEST_CASE(test_two_inorder, 0);
    TEST_CASE(test_two_reverse, 0);
    TEST_CASE(test_two_duplicate, 0);
    TEST_CASE(test_separated, 0);
    TEST_CASE(test_big, 0);

    int i;
    for (i = 0; i < 16; i++)
        qd_timer_free(timers[i]);

    return result;
}

