#ifdef HAVE_CONFIG_H
#  include "config.h"
#endif

#include "asyncpipe.h"

#if defined(WIN32) && defined(HAVE_WINDOWS_H)

static volatile long pipe_serial_nunber;

int async_pipe_create(hamlib_async_pipe_t **pipe_out, unsigned long pipe_buffer_size, unsigned long pipe_connect_timeout_millis)
{
    DWORD error_code;
    CHAR pipe_name[MAX_PATH];
    hamlib_async_pipe_t *pipe;

    pipe = calloc(1, sizeof(hamlib_async_pipe_t));
    if (pipe == NULL)
    {
        return -RIG_ENOMEM;
    }

    if (pipe_buffer_size == 0)
    {
        pipe_buffer_size = PIPE_BUFFER_SIZE_DEFAULT;
    }

    sprintf(pipe_name,
            "\\\\.\\Pipe\\Hamlib.%08lx.%08lx",
            GetCurrentProcessId(),
            InterlockedIncrement(&pipe_serial_nunber)
            );

    pipe->read = CreateNamedPipe(
                        pipe_name,
                        PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
                        PIPE_TYPE_BYTE | PIPE_WAIT,
                        1,                           // Number of pipes
                        pipe_buffer_size,            // Out buffer size
                        pipe_buffer_size,            // In buffer size
                        pipe_connect_timeout_millis, // Timeout in ms
                        NULL);

    if (!pipe->read)
    {
        return -RIG_EINTERNAL;
    }

    pipe->write = CreateFile(
                        pipe_name,
                        GENERIC_WRITE,
                        0, // No sharing
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
                        NULL // Template file
                    );

    if (pipe->write == INVALID_HANDLE_VALUE)
    {
        error_code = GetLastError();
        CloseHandle(pipe->read);
        free(pipe);
        SetLastError(error_code);
        return -RIG_EINTERNAL;
    }

    pipe->read_overlapped.hEvent = CreateEvent(
            NULL,    // default security attribute
            TRUE,    // manual-reset event
            FALSE,   // initial state = not signaled
            NULL);   // unnamed event object

    if (pipe->read_overlapped.hEvent == NULL)
    {
        error_code = GetLastError();
        CloseHandle(pipe->read);
        CloseHandle(pipe->write);
        free(pipe);
        SetLastError(error_code);
        return -RIG_EINTERNAL;
    }

    pipe->write_overlapped.hEvent = CreateEvent(
            NULL,    // default security attribute
            TRUE,    // manual-reset event
            FALSE,   // initial state = not signaled
            NULL);   // unnamed event object

    if (pipe->write_overlapped.hEvent == NULL)
    {
        error_code = GetLastError();
        CloseHandle(pipe->read_overlapped.hEvent);
        CloseHandle(pipe->read);
        CloseHandle(pipe->write);
        free(pipe);
        SetLastError(error_code);
        return -RIG_EINTERNAL;
    }

    *pipe_out = pipe;

    return RIG_OK;
}

void async_pipe_close(hamlib_async_pipe_t *pipe)
{
    if (pipe->read != NULL)
    {
        CloseHandle(pipe->read);
        pipe->read = NULL;
    }
    if (pipe->write != NULL)
    {
        CloseHandle(pipe->write);
        pipe->write = NULL;
    }

    if (pipe->read_overlapped.hEvent != NULL)
    {
        CloseHandle(pipe->read_overlapped.hEvent);
        pipe->read_overlapped.hEvent = NULL;
    }
    if (pipe->write_overlapped.hEvent != NULL)
    {
        CloseHandle(pipe->write_overlapped.hEvent);
        pipe->write_overlapped.hEvent = NULL;
    }

    free(pipe);
}

ssize_t async_pipe_read(hamlib_async_pipe_t *pipe, void *buf, size_t count, int timeout)
{
    HANDLE event_handles[1] = {
        pipe->read_overlapped.hEvent,
    };
    HANDLE read_handle = pipe->read;
    LPOVERLAPPED overlapped = &pipe->read_overlapped;
    DWORD wait_result;
    int result;
    ssize_t bytes_read;

    result = ReadFile(read_handle, buf, count, NULL, overlapped);
    if (!result)
    {
        result = GetLastError();
        switch (result)
        {
            case ERROR_SUCCESS:
                // No error?
                break;
            case ERROR_IO_PENDING:
                wait_result = WaitForMultipleObjects(1, event_handles, FALSE, timeout);

                switch (wait_result)
                {
                    case WAIT_OBJECT_0 + 0:
                        break;

                    case WAIT_TIMEOUT:
                        if (count == 0)
                        {
                            // Zero-length reads are used to wait for incoming data,
                            // so the I/O operation needs to be cancelled in case of a timeout
                            CancelIo(read_handle);
                            return -RIG_ETIMEOUT;
                        }
                        else
                        {
                            // Should not happen, as reads with count > 0 are used only when there is data available in the pipe
                            return -RIG_EINTERNAL;
                        }

                    default:
                        result = GetLastError();
                        rig_debug(RIG_DEBUG_ERR, "%s: WaitForMultipleObjects() error: %d\n", __func__, result);
                        return -RIG_EINTERNAL;
                }
                break;
            default:
                rig_debug(RIG_DEBUG_ERR, "%s: ReadFile() error: %d\n", __func__, result);
                return -RIG_EIO;
        }
    }

    result = GetOverlappedResult(read_handle, overlapped, (LPDWORD) &bytes_read, FALSE);
    if (!result)
    {
        result = GetLastError();
        switch (result)
        {
            case ERROR_SUCCESS:
                // No error?
                break;
            case ERROR_IO_PENDING:
                // Shouldn't happen?
                return -RIG_ETIMEOUT;
            default:
                rig_debug(RIG_DEBUG_ERR, "%s: GetOverlappedResult() error: %d\n", __func__, result);
                return -RIG_EIO;
        }
    }

    return bytes_read;
}

int async_pipe_wait_for_data(hamlib_async_pipe_t *pipe, int timeout)
{
    unsigned char data;
    int result;

    // Use a zero-length read to wait for data in pipe
    result = async_pipe_read(pipe, &data, 0, timeout);

    if (result > 0)
    {
        return RIG_OK;
    }

    return result;
}

ssize_t async_pipe_write(hamlib_async_pipe_t *pipe, const unsigned char *buf, size_t count, int timeout)
{
    HANDLE event_handles[1] = {
        pipe->write_overlapped.hEvent,
    };
    HANDLE write_handle = pipe->write;
    LPOVERLAPPED overlapped = &pipe->write_overlapped;
    DWORD wait_result;
    int result;
    ssize_t bytes_written;

    result = WriteFile(write_handle, buf, count, NULL, overlapped);
    if (!result)
    {
        result = GetLastError();
        switch (result)
        {
            case ERROR_SUCCESS:
                // No error?
                break;
            case ERROR_IO_PENDING:
                wait_result = WaitForMultipleObjects(1, event_handles, FALSE, timeout);

                switch (wait_result)
                {
                    case WAIT_OBJECT_0 + 0:
                        break;

                    case WAIT_TIMEOUT:
                        CancelIo(write_handle);
                        return -RIG_ETIMEOUT;

                    default:
                        result = GetLastError();
                        rig_debug(RIG_DEBUG_ERR, "%s: WaitForMultipleObjects() error: %d\n", __func__, result);
                        return -RIG_EINTERNAL;
                }
                break;
            default:
                rig_debug(RIG_DEBUG_ERR, "%s: WriteFile() error: %d\n", __func__, result);
                return -RIG_EIO;
        }
    }

    result = GetOverlappedResult(write_handle, overlapped, (LPDWORD) &bytes_written, FALSE);
    if (!result)
    {
        result = GetLastError();
        switch (result)
        {
            case ERROR_SUCCESS:
                // No error?
                break;
            case ERROR_IO_PENDING:
                // Shouldn't happen?
                return -RIG_ETIMEOUT;
            default:
                rig_debug(RIG_DEBUG_ERR, "%s: GetOverlappedResult() error: %d\n", __func__, result);
                return -RIG_EIO;
        }
    }

    return bytes_written;
}

#endif
