/*
 * Open Pixel Control server for Fadecandy
 * 
 * Copyright (c) 2013 Micah Elizabeth Scott
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "opcsink.h"
#include "util.h"
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <iostream>


OPCSink::OPCSink(callback_t cb, void *context, bool verbose)
    : mVerbose(verbose), mCallback(cb), mContext(context) {}

void OPCSink::start(struct ev_loop *loop, struct addrinfo *listenAddr)
{
    int sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        perror("socket");
        return;
    }

    int arg = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &arg, sizeof arg);

    if (bind(sock, listenAddr->ai_addr, listenAddr->ai_addrlen)) {
        perror("bind");
        return;
    }

    if (listen(sock, 4) < 0) {
        perror("listen");
        return;
    }

    // Get a callback when we're ready to accept a new connection
    ev_io_init(&mIOAccept, cbAccept, sock, EV_READ);
    ev_io_start(loop, &mIOAccept);

    if (mVerbose) {
        struct sockaddr_in *sin = (struct sockaddr_in*) listenAddr->ai_addr;
        std::clog << "Listening on " << inet_ntoa(sin->sin_addr) << ":" << ntohs(sin->sin_port) << "\n";
    }
}

void OPCSink::cbAccept(struct ev_loop *loop, struct ev_io *watcher, int revents)
{
    OPCSink *self = container_of(watcher, OPCSink, mIOAccept);
    struct sockaddr_in clientAddr;
    socklen_t clientAddrLen = sizeof clientAddr;

    int sock = accept(watcher->fd, (struct sockaddr *)&clientAddr, &clientAddrLen);
    if (sock < 0) {
        perror("accept");
        return;
    }

    int arg = 1;
    setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &arg, sizeof arg);

    Client *cli = new Client();
    cli->bufferPos = 0;
    cli->self = self;

    ev_io_init(&cli->ioRead, cbRead, sock, EV_READ);
    ev_io_start(loop, &cli->ioRead);

    if (self->mVerbose) {
        std::clog << "Client connected from " << inet_ntoa(clientAddr.sin_addr) << "\n";
    }
}

void OPCSink::cbRead(struct ev_loop *loop, struct ev_io *watcher, int revents)
{
    Client *cli = container_of(watcher, Client, ioRead);
    OPCSink *self = cli->self;

    int r = recv(watcher->fd, cli->bufferPos + (uint8_t*)&cli->buffer,
        sizeof(cli->buffer) - cli->bufferPos, 0);

    if (r < 0) {
        perror("read error");
        return;
    }

    if (r == 0) {
        // Client disconnecting

        if (self->mVerbose) {
            std::clog << "Client disconnected\n";
        }

        ev_io_stop(loop, watcher);
        delete cli;
        return;
    }

    cli->bufferPos += r;
    if (cli->bufferPos >= offsetof(Message, data)) {
        // We have a header, at least.

        unsigned length = offsetof(Message, data) + cli->buffer.length();
        if (cli->bufferPos >= length) {
            // Complete packet.
            self->mCallback(cli->buffer, self->mContext);

            // Save any part of the following packet we happened to grab.
            memmove(&cli->buffer, length + (uint8_t*)&cli->buffer, cli->bufferPos - length);
            cli->bufferPos -= length;
        }
    }
}