/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 *     Copyright 2013 Couchbase, Inc.
 *
 *   Licensed 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.
 */

/**
 * New-Style v2 plugin for Windows, Using IOCP.
 * This file contains various utility functions used by the plugin
 * @author Mark Nunberg
 */

#include "iocp_iops.h"
#include <sys/types.h>
#include <sys/timeb.h>
#include <time.h>
#include "config.h"
#include <libcouchbase/plugins/io/wsaerr-inl.c>

#if defined(__MINGW32__) && !defined(_ftime_s)
#define _ftime_s _ftime /** Mingw doens't have the _s variant */
#endif

int iocp_w32err_2errno(DWORD error)
{
    return wsaerr_map_impl(error);
}

DWORD iocp_set_last_error(lcb_io_opt_t io, SOCKET sock)
{
    int werr = GetLastError();
    io->v.v2.error = iocp_w32err_2errno(werr);
    return werr;
}

lcb_uint32_t iocp_micros(void)
{
    return (lcb_uint32_t)(gethrtime() / 1000);
}

LPFN_CONNECTEX iocp_initialize_connectex(SOCKET sock)
{
    LPFN_CONNECTEX ret = NULL;
    DWORD dwBytes;
    GUID ce_guid = WSAID_CONNECTEX;

    WSAIoctl(sock,
             SIO_GET_EXTENSION_FUNCTION_POINTER,
             &ce_guid,
             sizeof(ce_guid),
             &ret,
             sizeof(&ret),
             &dwBytes,
             NULL,
             NULL);

    return ret;
}

int iocp_just_scheduled(iocp_t *io, iocp_overlapped_t *ol, int status)
{
    DWORD err = GetLastError();
    IOCP_LOG(IOCP_TRACE, "Pending count: %d", io->n_iopending);
    if ((status != 0 && err == WSA_IO_PENDING) || status == 0) {
        io->n_iopending++;
        ol->sd->refcount++;
        return 0;
    }

    /**
     * Otherwise, there's something wrong
     */
    IOCP_LOG(IOCP_ERR, "Got non-harmless error for %p: %d", ol, (int)err);
    io->base.v.v2.error = iocp_w32err_2errno(err);
    return -1;
}

void iocp_socket_decref(iocp_t *io, iocp_sockdata_t *sd)
{
    if (--sd->refcount) {
        return;
    }

    if (sd->sSocket != INVALID_SOCKET) {
        closesocket(sd->sSocket);
    }

    lcb_list_delete(&sd->list);

    (void)io;
    free(sd);
}

void iocp_on_dequeued(iocp_t *io, iocp_sockdata_t *sd, int action)
{
    IOCP_LOG(IOCP_TRACE, "Dequeing. A=%d, Pending=%d", action, io->n_iopending);
    iocp_socket_decref(io, sd);
}

/**This following function was copied from libuv.
 * See http://github.com/joyent/libuv for more details */
int iocp_overlapped_status(OVERLAPPED *lpOverlapped)
{
    NTSTATUS status = (NTSTATUS)lpOverlapped->Internal;
    switch (status) {
    case 0:
        return ERROR_SUCCESS;

    case STATUS_PENDING:
        return ERROR_IO_PENDING;

    case STATUS_INVALID_HANDLE:
    case STATUS_OBJECT_TYPE_MISMATCH:
        return WSAENOTSOCK;

    case STATUS_INSUFFICIENT_RESOURCES:
    case STATUS_PAGEFILE_QUOTA:
    case STATUS_COMMITMENT_LIMIT:
    case STATUS_WORKING_SET_QUOTA:
    case STATUS_NO_MEMORY:
    case STATUS_CONFLICTING_ADDRESSES:
    case STATUS_QUOTA_EXCEEDED:
    case STATUS_TOO_MANY_PAGING_FILES:
    case STATUS_REMOTE_RESOURCES:
    case STATUS_TOO_MANY_ADDRESSES:
        return WSAENOBUFS;

    case STATUS_SHARING_VIOLATION:
    case STATUS_ADDRESS_ALREADY_EXISTS:
        return WSAEADDRINUSE;

    case STATUS_LINK_TIMEOUT:
    case STATUS_IO_TIMEOUT:
    case STATUS_TIMEOUT:
        return WSAETIMEDOUT;

    case STATUS_GRACEFUL_DISCONNECT:
        return WSAEDISCON;

    case STATUS_REMOTE_DISCONNECT:
    case STATUS_CONNECTION_RESET:
    case STATUS_LINK_FAILED:
    case STATUS_CONNECTION_DISCONNECTED:
    case STATUS_PORT_UNREACHABLE:
    case STATUS_HOPLIMIT_EXCEEDED:
        return WSAECONNRESET;

    case STATUS_LOCAL_DISCONNECT:
    case STATUS_TRANSACTION_ABORTED:
    case STATUS_CONNECTION_ABORTED:
        return WSAECONNABORTED;

    case STATUS_BAD_NETWORK_PATH:
    case STATUS_NETWORK_UNREACHABLE:
    case STATUS_PROTOCOL_UNREACHABLE:
        return WSAENETUNREACH;

    case STATUS_HOST_UNREACHABLE:
        return WSAEHOSTUNREACH;

    case STATUS_CANCELLED:
    case STATUS_REQUEST_ABORTED:
        return WSAEINTR;

    case STATUS_BUFFER_OVERFLOW:
    case STATUS_INVALID_BUFFER_SIZE:
        return WSAEMSGSIZE;

    case STATUS_BUFFER_TOO_SMALL:
    case STATUS_ACCESS_VIOLATION:
        return WSAEFAULT;

    case STATUS_DEVICE_NOT_READY:
    case STATUS_REQUEST_NOT_ACCEPTED:
        return WSAEWOULDBLOCK;

    case STATUS_INVALID_NETWORK_RESPONSE:
    case STATUS_NETWORK_BUSY:
    case STATUS_NO_SUCH_DEVICE:
    case STATUS_NO_SUCH_FILE:
    case STATUS_OBJECT_PATH_NOT_FOUND:
    case STATUS_OBJECT_NAME_NOT_FOUND:
    case STATUS_UNEXPECTED_NETWORK_ERROR:
        return WSAENETDOWN;

    case STATUS_INVALID_CONNECTION:
        return WSAENOTCONN;

    case STATUS_REMOTE_NOT_LISTENING:
    case STATUS_CONNECTION_REFUSED:
        return WSAECONNREFUSED;

    case STATUS_PIPE_DISCONNECTED:
        return WSAESHUTDOWN;

    case STATUS_INVALID_ADDRESS:
    case STATUS_INVALID_ADDRESS_COMPONENT:
        return WSAEADDRNOTAVAIL;

    case STATUS_NOT_SUPPORTED:
    case STATUS_NOT_IMPLEMENTED:
        return WSAEOPNOTSUPP;

    case STATUS_ACCESS_DENIED:
        return WSAEACCES;

    default:
        if ((status & (FACILITY_NTWIN32 << 16)) == (FACILITY_NTWIN32 << 16) &&
                (status & (ERROR_SEVERITY_ERROR | ERROR_SEVERITY_WARNING))) {
            /* It's a windows error that has been previously mapped to an */
            /* ntstatus code. */
            return (DWORD)(status & 0xffff);
        } else {
            /* The default fallback for unmappable ntstatus codes. */
            return WSAEINVAL;
        }
    }
}
