/*
 * 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.
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include <qpid/dispatch/timer.h>
#include "test_case.h"
#include <qpid/dispatch/server.h>
#include <qpid/dispatch/user_fd.h>
#include <qpid/dispatch/threading.h>
#include <qpid/dispatch/log.h>

#define THREAD_COUNT 4
#define OCTET_COUNT  100

static sys_mutex_t *test_lock;

static void *expected_context;
static int   call_count;
static int   threads_seen[THREAD_COUNT];
static char  stored_error[512];

static int   write_count;
static int   read_count;
static int   fd[2];
static dx_user_fd_t *ufd_write;
static dx_user_fd_t *ufd_read;


static void thread_start(void *context, int thread_id)
{
    sys_mutex_lock(test_lock);
    if (context != expected_context && !stored_error[0])
        sprintf(stored_error, "Unexpected Context Value: %lx", (long) context);
    if (thread_id >= THREAD_COUNT && !stored_error[0])
        sprintf(stored_error, "Thread_ID too large: %d", thread_id);
    if (thread_id < 0 && !stored_error[0])
        sprintf(stored_error, "Thread_ID negative: %d", thread_id);

    call_count++;
    if (thread_id >= 0 && thread_id < THREAD_COUNT)
        threads_seen[thread_id]++;

    if (call_count == THREAD_COUNT)
        dx_server_stop();
    sys_mutex_unlock(test_lock);
}


static int conn_handler(void *context, dx_conn_event_t event, dx_connection_t *conn)
{
    return 0;
}


static void ufd_handler(void *context, dx_user_fd_t *ufd)
{
    long    dir = (long) context;
    char    buffer;
    ssize_t len;
    static  int in_read  = 0;
    static  int in_write = 0;

    if (dir == 0) { // READ
        in_read++;
        assert(in_read == 1);
        if (!dx_user_fd_is_readable(ufd_read)) {
            sprintf(stored_error, "Expected Readable");
            dx_server_stop();
        } else {
            len = read(fd[0], &buffer, 1);
            if (len == 1) {
                read_count++;
                if (read_count == OCTET_COUNT)
                    dx_server_stop();
            }
            dx_user_fd_activate_read(ufd_read);
        }
        in_read--;
    } else {        // WRITE
        in_write++;
        assert(in_write == 1);
        if (!dx_user_fd_is_writeable(ufd_write)) {
            sprintf(stored_error, "Expected Writable");
            dx_server_stop();
        } else {
            write(fd[1], "X", 1);

            write_count++;
            if (write_count < OCTET_COUNT)
                dx_user_fd_activate_write(ufd_write);
        }
        in_write--;
    }
}


static void fd_test_start(void *context)
{
    dx_user_fd_activate_read(ufd_read);
}


static char* test_start_handler(void *context)
{
    int i;

    dx_server_initialize(THREAD_COUNT);

    expected_context = (void*) 0x00112233;
    stored_error[0] = 0x0;
    call_count      = 0;
    for (i = 0; i < THREAD_COUNT; i++)
        threads_seen[i] = 0;

    dx_server_set_conn_handler(conn_handler);
    dx_server_set_start_handler(thread_start, expected_context);
    dx_server_run();
    dx_server_finalize();

    if (stored_error[0])            return stored_error;
    if (call_count != THREAD_COUNT) return "Incorrect number of thread-start callbacks";
    for (i = 0; i < THREAD_COUNT; i++)
        if (threads_seen[i] != 1)   return "Incorrect count on one thread ID";

    return 0;
}


static char* test_user_fd(void *context)
{
    int res;
    dx_timer_t *timer;

    dx_server_initialize(THREAD_COUNT);
    dx_server_set_conn_handler(conn_handler);
    dx_server_set_user_fd_handler(ufd_handler);
    timer = dx_timer(fd_test_start, 0);
    dx_timer_schedule(timer, 0);

    stored_error[0] = 0x0;
    res = pipe2(fd, O_NONBLOCK);
    if (res != 0) return "Error creating pipe2";

    ufd_write = dx_user_fd(fd[1], (void*) 1);
    ufd_read  = dx_user_fd(fd[0], (void*) 0);

    dx_server_run();
    dx_timer_free(timer);
    dx_server_finalize();
    close(fd[0]);
    close(fd[1]);

    if (stored_error[0])            return stored_error;
    if (write_count - OCTET_COUNT > 2) sprintf(stored_error, "Excessively high Write Count: %d", write_count);
    if (read_count != OCTET_COUNT)  sprintf(stored_error, "Incorrect Read Count: %d", read_count);;

    if (stored_error[0]) return stored_error;
    return 0;
}


int server_tests(void)
{
    int result = 0;
    test_lock = sys_mutex();
    dx_log_set_mask(LOG_NONE);

    TEST_CASE(test_start_handler, 0);
    TEST_CASE(test_user_fd, 0);

    sys_mutex_free(test_lock);
    return result;
}

