#define FD_SETSIZE 1
#include "channel.h"
#include "ltrapi.h"


#include <string.h>
#include <stdio.h>






#if defined _WIN32
#include <winsock2.h>
typedef int socklen_t;

    #define SOCK_ERR_SIGBREAK() 0
#else
    #define SOCK_ERR_SIGBREAK() (EINTR == errno)

#ifndef HAVE_SOCKLEN_T
    typedef size_t socklen_t;
#endif


#define closesocket(sock) close(sock)

#include <stdlib.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sys/types.h>
#include <arpa/inet.h>


#endif

#ifndef MSG_NOSIGNAL
    #define MSG_NOSIGNAL 0
#endif

/*================================================================================================*/
/*------------------------------------------------------------------------------------------------*/
static LINLINE void f_set_timeval_left(t_ltimer* tmr, struct timeval* tval) {
    t_lclock_ticks left = ltimer_expiration(tmr);
    tval->tv_sec = left / LCLOCK_TICKS_PER_SECOND;
    tval->tv_usec = (left % LCLOCK_TICKS_PER_SECOND) * 1000000/LCLOCK_TICKS_PER_SECOND;
}


/*------------------------------------------------------------------------------------------------*/



TLTR_CHANNEL* ltr_ch_create(void) {
    TLTR_CHANNEL* ch = calloc(1, sizeof(TLTR_CHANNEL));
    if (ch!=NULL) {
        int i;
        ch->default_tout = LTR_DEFAULT_SEND_RECV_TIMEOUT;
        ch->sock = INVALID_SOCKET;

        for (i=0; i < LTR_CHANNEL_PRIV_BUF_MAX; i++) {
            ch->priv_buf[i] = NULL;
        }
    }
    return ch;
}

void ltr_ch_free(TLTR_CHANNEL* ch) {
    /* очищаем приватные буферы, если они есть */
    int i;
    ltr_ch_close(ch);
    for (i=0; i < LTR_CHANNEL_PRIV_BUF_MAX; i++) {
        if (ch->priv_buf[i]!=NULL) {
            free(ch->priv_buf[i]);
        }
    }
    free(ch);
}

INT ltr_ch_priv_alloc(TLTR_CHANNEL* ch, DWORD id, DWORD size) {
    INT err = id >= LTR_CHANNEL_PRIV_BUF_MAX ? LTR_ERROR_PARAMETERS :
                         ch->priv_buf[id]!=NULL ? LTR_ERROR_PARAMETERS : 0;
    if (!err) {
        ch->priv_buf[id] = malloc(size);
        if (ch->priv_buf[id]==NULL)
            err = LTR_ERROR_MEMORY_ALLOC;
    }
    return err;
}

INT ltr_ch_priv_free(TLTR_CHANNEL* ch, DWORD id) {
    INT err = id >= LTR_CHANNEL_PRIV_BUF_MAX ? LTR_ERROR_PARAMETERS : 0;
    if (!err) {
        if (ch->priv_buf[id]!=NULL) {
            free(ch->priv_buf[id]);
            ch->priv_buf[id]=NULL;
        }
    }
    return err;
}



/*------------------------------------------------------------------------------------------------*/
/** Закрытие канала связи (сокета) с LTR-сервером */
void ltr_ch_close(TLTR_CHANNEL *ch) {
    if (ch->sock != INVALID_SOCKET) {
        shutdown(ch->sock, SHUT_RDWR);
        closesocket(ch->sock);
#ifdef LTRAPI_USE_WSA_SOCKS
         if (ch->ovRead.hEvent != WSA_INVALID_EVENT) {
             WSACloseEvent(ch->ovRead.hEvent);
             ch->ovRead.hEvent = WSA_INVALID_EVENT;
         }
         if (ch->ovWrite.hEvent != WSA_INVALID_EVENT) {
             WSACloseEvent(ch->ovWrite.hEvent);
             ch->ovWrite.hEvent = WSA_INVALID_EVENT;
         }
#endif
        ch->sock = INVALID_SOCKET;
    }
}

void ltr_ch_tx_shutdown(TLTR_CHANNEL *ch) {
    if (ch->sock != INVALID_SOCKET) {
        shutdown(ch->sock, SHUT_WR);
    }
}

/*------------------------------------------------------------------------------------------------*/

/*------------------------------------------------------------------------------------------------*/
/** Открываем канала связи с ltr-сервером */
INT ltr_ch_open(TLTR_CHANNEL *ch, DWORD addr, WORD port, t_ltimer *ptmr) {
    INT err = 0;
    SOCKET s;
    struct sockaddr_in peer;
    fd_set fd_w, fd_e;
    BOOL connected = FALSE;

    ch->esc.data_len = ch->esc.data_count = 0;
    ch->unixtime.u.LowPart = ch->unixtime.u.HighPart = 0;

      /* если канал был открыт - закрываем */
    ltr_ch_close(ch);

#ifdef LTRAPI_USE_WSA_SOCKS
    s = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    ZeroMemory(&ch->ovRead, sizeof(ch->ovRead));
    ch->ovRead.hEvent = WSACreateEvent();
    ZeroMemory(&ch->ovWrite, sizeof(ch->ovWrite));
    ch->ovWrite.hEvent = WSACreateEvent();
#else
    s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
#endif
    if (s == INVALID_SOCKET)
        err = LTR_ERROR_OPEN_SOCKET;

    /* Переводим сокет в неблокирующий режим работы */
    if (err == LTR_OK) {
#ifdef _WIN32
        ULONG nonblocking = 1;
        if (SOCKET_ERROR == ioctlsocket(s, FIONBIO, &nonblocking))
            err = LTR_ERROR_OPEN_SOCKET;
#else
        int n = fcntl(s, F_GETFL, 0);
        if (fcntl(s, F_SETFL, n|O_NONBLOCK)==-1) {
            err = LTR_ERROR_OPEN_SOCKET;
        }
#endif
    }

    if (err == LTR_OK) {
        int flag = 1;
        if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (char*)&flag, sizeof(flag))==-1)
            err = LTR_ERROR_OPEN_SOCKET;
    }


    if (err == LTR_OK) {
        /* заполняем структуру с адресом LTR-сервера */
        memset(&peer, 0, sizeof(peer));
        peer.sin_family = AF_INET;
        peer.sin_port = htons(port);
        peer.sin_addr.s_addr = htonl(addr);
    }

    while (!connected && (err==LTR_OK)) {
        if (SOCKET_ERROR == connect(s, (struct sockaddr*)&peer, sizeof(peer))) {
            struct timeval tout;
            int sockerr = 0;
#ifdef _WIN32
            if (WSAEWOULDBLOCK != WSAGetLastError()) {
#else
            if (errno != EINPROGRESS)  {
#endif
                err = LTR_ERROR_OPEN_SOCKET;
            } else {
                FD_ZERO(&fd_w);
                FD_SET(s, &fd_w);
                FD_ZERO(&fd_e);
                FD_SET(s, &fd_e);
                f_set_timeval_left(ptmr, &tout);

                if (select((int)s+1, NULL, &fd_w, &fd_e, &tout) < 1)
                    err = LTR_ERROR_OPEN_SOCKET;
            }

            if (err == LTR_OK) {
                /* судя по man - если произошла ошибка, то сокет становится writable!
                    так что в fd_w тоже нужно проверять ошибку */
                socklen_t optlen = sizeof(sockerr);
                if (SOCKET_ERROR == getsockopt(s, SOL_SOCKET, SO_ERROR,
                                               (char*)&sockerr, &optlen)) {
                    err = LTR_ERROR_OPEN_SOCKET;
                } else if (sockerr) {
                    err = LTR_ERROR_OPEN_SOCKET;
                }
            }

            /* проверяем, что соединились успешно */
            if ((err == LTR_OK) && !sockerr && (FD_ISSET(s, &fd_w)))
                connected = TRUE;
        } else {
            /* удалось соединится без ожидания */
            connected = TRUE;
        }
    } /* while (!connected && !err) */

    if (!connected || (err!=LTR_OK)) {
        ltr_ch_close(ch);
        if (err == LTR_OK)
            err = LTR_ERROR_OPEN_SOCKET;
    } else {
        ch->sock = s;
        ch->send_part_size = 0;
        ch->recv_part_size = 0;
    }
    return err;
}
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/

/* В Windows сделан вариант приема через WSA сокеты с Overlapped, который включается
 * только при разрешенной опции.
 * Ранее этот вариант использовался, т.к. вроде были ошибки на некоторых
 * редких машинах WinXP при частых отменах приема по таймауту.
 * Тем не менее на некоторых редких системах
 * (один из вариантов Win7 на Thinkpad core-i7) при отмене приема через CancelIO
 * могут теряться данные (или зависать ожидание результата), поэтому
 * вариант WSA оставлен как опция и основным снова сделан
 * кроссплатформенный на стандартных сокетах. */
#ifdef LTRAPI_USE_WSA_SOCKS
INT ltr_ch_recv(TLTR_CHANNEL *ch, LPBYTE buf, DWORD size, t_ltimer *ptmr) {
    DWORD offset = 0;
    BOOL timed_out = FALSE;
    INT err = buf==NULL ?LTR_ERROR_PARAMETERS : LTR_OK;

    if ((err == LTR_OK) && !ltr_ch_is_open(ch)) {
        err = LTR_ERROR_CHANNEL_CLOSED;
    }
    if (err == LTR_OK) {
        WSABUF wb;
        DWORD flags;

        while (!err && !timed_out && (offset < size)) {
            DWORD xfer_size = 0;
            wb.buf = buf + offset;
            wb.len = size - offset;
            flags = 0;
            if (SOCKET_ERROR == WSARecv(ch->sock, &wb, 1, NULL, &flags, &ch->ovRead, NULL)) {

                int win_err_code = WSAGetLastError();
                if (win_err_code != WSA_IO_PENDING) {
                    err = LTR_ERROR_RECV;
                } else {
                    switch (WSAWaitForMultipleEvents(1, &ch->ovRead.hEvent, FALSE,
                                                     LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(ptmr)),
                                                     FALSE)) {
                        case WSA_WAIT_EVENT_0:
                            if (WSAGetOverlappedResult(ch->sock, &ch->ovRead, &xfer_size, FALSE, &flags)) {
                                if ((offset < size) && (xfer_size == 0)) {
                                    err = LTR_ERROR_CONNECTION_CLOSED;
                                }
                            } else {
                                err = LTR_ERROR_RECV;
                            }
                            break;
                        case WSA_WAIT_TIMEOUT:
                            timed_out = TRUE;
                            CancelIo((HANDLE)ch->sock);
                            WSAGetOverlappedResult(ch->sock, &ch->ovRead, &xfer_size, TRUE, &flags);
                            break;
                        default:
                            err = LTR_ERROR_RECV;
                            CancelIo((HANDLE)ch->sock);
                            WSAGetOverlappedResult(ch->sock, &ch->ovRead, &xfer_size, TRUE, &flags);
                            break;
                    }
                }
            } else {
                WSAGetOverlappedResult(ch->sock, &ch->ovRead, &xfer_size, FALSE, &flags);
                if (xfer_size == 0)
                    err = LTR_ERROR_CONNECTION_CLOSED;
            }
            offset += xfer_size;
        }
    }
    return err ? err : (INT)offset;
}

INT ltr_ch_send(TLTR_CHANNEL *ch,  LPCBYTE buf, DWORD size, t_ltimer *ptmr) {
    INT err = buf==NULL ?LTR_ERROR_PARAMETERS : LTR_OK;
    DWORD offset = 0;
    BOOL timed_out = FALSE;

    if ((err == LTR_OK) && !ltr_ch_is_open(ch)) {
        err = LTR_ERROR_CHANNEL_CLOSED;
    }

    if ((err == LTR_OK) && (size != 0)) {
        WSABUF wb;
        DWORD flags;

        while (!err && !timed_out && (offset < size)) {
            DWORD xfer_size = 0;
            wb.buf = (CHAR*)(buf + offset);
            wb.len = size - offset;
            flags = 0;
            if (SOCKET_ERROR == WSASend(ch->sock, &wb, 1, NULL, flags, &ch->ovWrite, NULL)) {

                int win_err_code = WSAGetLastError();
                if (win_err_code != WSA_IO_PENDING) {
                    err = LTR_ERROR_SEND;
                } else {
                    switch (WSAWaitForMultipleEvents(1, &ch->ovWrite.hEvent, FALSE,
                                                     LTIMER_CLOCK_TICKS_TO_MS(ltimer_expiration(ptmr)),
                                                     FALSE)) {
                        case WSA_WAIT_EVENT_0:
                            if (WSAGetOverlappedResult(ch->sock, &ch->ovWrite, &xfer_size, FALSE, &flags)) {
                                if ((offset < size) && (xfer_size == 0)) {
                                    err = LTR_ERROR_CONNECTION_CLOSED;
                                }
                            } else {
                                err = LTR_ERROR_SEND;
                            }
                            break;
                        case WSA_WAIT_TIMEOUT:
                            timed_out = TRUE;
                            CancelIo((HANDLE)ch->sock);
                            WSAGetOverlappedResult(ch->sock, &ch->ovWrite, &xfer_size, TRUE, &flags);
                            break;
                        default:
                            err = LTR_ERROR_SEND;
                            CancelIo((HANDLE)ch->sock);
                            WSAGetOverlappedResult(ch->sock, &ch->ovWrite, &xfer_size, TRUE, &flags);
                            break;
                    }
                }
            } else {
                WSAGetOverlappedResult(ch->sock, &ch->ovWrite, &xfer_size, FALSE, &flags);
                if (xfer_size == 0)
                    err = LTR_ERROR_SEND;
            }
            offset += xfer_size;
        }
    }

    return (err) ? err : (INT)offset;
}


#else
INT ltr_ch_recv(TLTR_CHANNEL *ch, LPBYTE buf, DWORD size, t_ltimer *ptmr) {
    DWORD offset = 0;
    BOOL timed_out = FALSE;
    fd_set fd_r;
    struct timeval tval;
    INT err = buf==NULL ?LTR_ERROR_PARAMETERS : LTR_OK;

    if ((err == LTR_OK) && !ltr_ch_is_open(ch)) {
        err = LTR_ERROR_CHANNEL_CLOSED;
    }

    if (err == LTR_OK) {
        while (!err && !timed_out && (offset < size)) {
            FD_ZERO(&fd_r);
            FD_SET(ch->sock, &fd_r);
            f_set_timeval_left(ptmr, &tval);
            switch (select((int)ch->sock+1, &fd_r, NULL, NULL, &tval)) {
                case SOCKET_ERROR:
                    /* Если пришел сигнал, то это не ошибка приема.
                     * Но скорее всего управление стоит вернуть сразу, хотя
                     * может сделать опцию... */
                    if (!SOCK_ERR_SIGBREAK()) {
                        err = LTR_ERROR_RECV;
#ifdef LTRAPI_STOP_ON_SIGNAL
                    } else {
                        ltimer_set(ptmr, 0);
                            timed_out = TRUE;
#endif
                    }
                    break;
                case 0: // таймаут
                    timed_out = TRUE;
                    break;
                default: { /* дождались готовности на чтение */
                        int res = recv(ch->sock, buf + offset, size - offset, 0);
                        if (SOCKET_ERROR == res) {
                            /* по идее проверка не обязательна:
                                если select вернул что в сокете есть данные -
                                recv должен всегда что-нибудь вернуть, если это не eof
                                или соединение закрыто */
#ifdef _WIN32
                            if (WSAEWOULDBLOCK != WSAGetLastError()) {
#else
                            if ((EAGAIN != errno) && (EWOULDBLOCK != errno)
                                    && (EINTR != errno)) {
#endif
                                err = LTR_ERROR_RECV;
                            }
                        } else if (0 == res) {
                            /* соединение закрыто */
                            err = LTR_ERROR_CONNECTION_CLOSED;
                        } else {
                            offset += res;
                        }
                    }
                    break;
            }
        } /* switch (select(ch->sock+1, &fd_r, NULL, NULL, &tval)) */
    }

    return err ? err : (INT)offset;
}
/*----------------------------------------------------------------------------*/

/*----------------------------------------------------------------------------*/
INT ltr_ch_send(TLTR_CHANNEL *ch,  LPCBYTE buf, DWORD size, t_ltimer *ptmr) {
    INT err = buf==NULL ?LTR_ERROR_PARAMETERS : LTR_OK;
    DWORD offset = 0;
    BOOL timed_out = FALSE;
    fd_set fd_w;

    if ((err == LTR_OK) && !ltr_ch_is_open(ch)) {
        err = LTR_ERROR_CHANNEL_CLOSED;
    }

    if ((err == LTR_OK) && (size != 0)) {

        while (!err && !timed_out && (offset < size)) {
            /* Сначала пробуем сделать запись без ожидания */
            int res = send(ch->sock, buf + offset, size - offset, MSG_NOSIGNAL);
            if (SOCKET_ERROR == res) {
                struct timeval tval;
#ifdef _WIN32
                if (WSAEWOULDBLOCK == WSAGetLastError()) {
#else
                if ((EAGAIN==errno) || (EWOULDBLOCK==errno) || (EINTR == errno)) {
#endif

                    /* Надо ждать освобождения сокета */
                    FD_ZERO(&fd_w);
                    FD_SET(ch->sock, &fd_w);

                    f_set_timeval_left(ptmr, &tval);
                    switch (select((int)ch->sock+1, NULL, &fd_w, NULL, &tval)) {
                        case SOCKET_ERROR:
                            if (!SOCK_ERR_SIGBREAK()) {
                                err = LTR_ERROR_SEND;
#ifdef LTRAPI_STOP_ON_SIGNAL
                            } else {
                                ltimer_set(ptmr, 0);
                                timed_out = TRUE;
#endif
                            }
                            break;
                        case 0: // таймаут
                            timed_out = TRUE;
                            break;
                        default:
                            if (ltimer_expired(ptmr))
                                timed_out = TRUE;
                            break;
                    }
                } else {

                    err = LTR_ERROR_SEND;
                }
            } else { // no error
                offset += res;
            }
        }
    }

    return (err) ? err : (INT)offset;
}

#endif
/*----------------------------------------------------------------------------*/


/*============================================================================*/

