/*
 * Open Pixel Control and HTTP server for Fadecandy.
 *
 * This is a TCP server which accepts either Open Pixel Control, HTTP, or WebSockets
 * connections on a single port. We use a fork of libwebsockets which supports serving
 * external non-HTTP protocols via a low-level receive callback.
 * 
 * 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 "netserver.h"
#include "version.h"
#include "libwebsockets.h"
#include <iostream>
#include <algorithm>


NetServer::NetServer(OPC::callback_t messageCallback, void *context, bool verbose)
    : mMessageCallback(messageCallback), mUserContext(context), mThread(0), mVerbose(verbose)
{}

bool NetServer::start(const char *host, int port)
{
    const int llNormal = LLL_ERR | LLL_WARN;
    const int llVerbose = llNormal | LLL_NOTICE;

    static struct libwebsocket_protocols protocols[] = {
        // Only one protocol for now. Handles HTTP as well as our default WebSockets protocol.
        {
            "fcserver",             // Name
            lwsCallback,            // Callback
            sizeof(Client),         // Protocol-specific data size
            sizeof(OPC::Message),   // Max frame size / rx buffer
        },

        { NULL, NULL, 0, 0 }    // terminator
    };

    struct lws_context_creation_info info;
    memset(&info, 0, sizeof info);
    info.gid = -1;
    info.uid = -1;
    info.host = host;
    info.port = port;
    info.protocols = protocols;
    info.user = this;

    // Quieter during create_context, since it's kind of chatty.
    lws_set_log_level(llNormal, NULL);

    struct libwebsocket_context *context = libwebsocket_create_context(&info);
    if (!context) {
        lwsl_err("libwebsocket init failed\n");
        return false;
    }

    // Maybe set up a more verbose log level now.
    if (mVerbose) {
        lws_set_log_level(llVerbose, NULL);
    }

    lwsl_notice("Server listening on %s:%d\n", host ? host : "*", port);

    // Note that we pass ownership of all libwebsockets state to this new thread.
    // We shouldn't access it on the other threads afterwards.
    mThread = new tthread::thread(threadFunc, context);

    return true;
}

void NetServer::threadFunc(void *arg)
{
    struct libwebsocket_context *context = (libwebsocket_context*) arg;
    while (libwebsocket_service(context, 1000) >= 0);
    libwebsocket_context_destroy(context);
}

int NetServer::lwsCallback(libwebsocket_context *context, libwebsocket *wsi,
    enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len)
{
    /*
     * Protocol callback for libwebsockets.
     *
     * Until we have a reason to support a non-default WebSockets protocol, this handles
     * everything: plain HTTP, Open Pixel Control, and WebSockets.
     *
     * For HTTP, this serves simple static HTML documents which are included at compile-time
     * from the 'http' directory. Our web UI interacts with the server via the public WebSockets API.
     */

    NetServer *self = (NetServer*) libwebsocket_context_user(context);
    Client *client = (Client*) user;

    switch (reason) {
        case LWS_CALLBACK_CLOSED:
        case LWS_CALLBACK_CLOSED_HTTP:
            if (client && client->opcBuffer) {
                free(client->opcBuffer);
                client->opcBuffer = NULL;
            }
            break;

        case LWS_CALLBACK_HTTP:
            return self->httpBegin(context, wsi, *client, (const char*) in);

        case LWS_CALLBACK_HTTP_FILE_COMPLETION:
            // Only serve one file per connect
            return -1;

        case LWS_CALLBACK_HTTP_WRITEABLE:
            return self->httpWrite(context, wsi, *client);

        case LWS_CALLBACK_SOCKET_READ:
            // Low-level socket read. We may trap these for OPC protocol handling.
            if (client->state != CLIENT_STATE_HTTP) {
                return self->opcRead(context, wsi, *client, (uint8_t*)in, len);
            }
            break;

        case LWS_CALLBACK_RECEIVE:
            // WebSockets data received
            return self->wsRead(context, wsi, *client, (uint8_t*)in, len);

        default:
            break;
    }

    return 0;
}

int NetServer::opcRead(libwebsocket_context *context, libwebsocket *wsi,
    Client &client, uint8_t *in, size_t len)
{
    /*
     * Open Pixel Control packet dispatch, and protocol detection.
     *
     * Store the new packet in our protocol buffer. This should be large enough to never overflow,
     * since our OPC buffer is large enough for two packets and the OPC max packet size is much larger
     * than the network receive buffer.
     *
     * If we have no buffered data yet, we can do this without copying.
     */

    OPCBuffer *opcb;
    uint8_t *buffer;
    unsigned bufferLength;    

    // Allocate the buffer we use for OPC reassembly and protocol-detect.
    if (client.opcBuffer == NULL) {
        opcb = (OPCBuffer*) malloc(sizeof *client.opcBuffer);
        if (opcb == NULL) {
            lwsl_err("ERROR: Out of memory allocating OPC reassembly buffer.\n");
            return -1;
        }
        opcb->bufferLength = 0;
        client.opcBuffer = opcb;
    } else {
        opcb = client.opcBuffer;
    }

    if (len + opcb->bufferLength > sizeof opcb->buffer) {
        return -1;
    }

    if (opcb->bufferLength) {
        memcpy(opcb->bufferLength + opcb->buffer, in, len);
        buffer = opcb->buffer;
        bufferLength = opcb->bufferLength + len;
    } else {
        buffer = in;
        bufferLength = len;
    }

    if (client.state == CLIENT_STATE_PROTOCOL_DETECT) {
        /*
         * It's a new connection, and we aren't sure yet whether it's native OPC
         * or HTTP / WebSockets. We examine the first four bytes received. If it's
         * "GET ", we assume this is HTTP. (Other HTTP methods are not needed).
         * If it's anything else, we interpret the connection as native OPC and these
         * are the first four bytes of the first OPC packet.
         */

        if (bufferLength < 4) {
            // Not enough data for protocol detect yet. Save this data for later.

            if (buffer != opcb->buffer) {
                memcpy(opcb->buffer, buffer, bufferLength);
                opcb->bufferLength = bufferLength;
            }

            // Do not pass this data on to libwebsocket yet
            return 1;
        }

        if (buffer[0] == 'G' && buffer[1] == 'E' && buffer[2] == 'T' && buffer[3] == ' ') {
            // Detected HTTP. Convert this to an HTTP client, and let libwebsockets handle
            // all data received so far. We can jettison the OPC buffer at this point.

            free(client.opcBuffer);
            client.opcBuffer = 0;
            client.state = CLIENT_STATE_HTTP;

            if (libwebsocket_read(context, wsi, buffer, bufferLength) < 0) {
                return -1;
            }
            return 1;
        }

        // Not HTTP. Handle this as an OPC socket.
        client.state = CLIENT_STATE_OPEN_PIXEL_CONTROL;
        lwsl_notice("New Open Pixel Control connection\n");
    }

    // Process any and all complete packets from our buffer
    while (1) {

        if (bufferLength < OPC::HEADER_BYTES) {
            // Still waiting for a header
            break;
        }

        OPC::Message *msg = (OPC::Message*) buffer;
        unsigned msgLength = OPC::HEADER_BYTES + msg->length();

        if (bufferLength < msgLength) {
            // Waiting for more data
            break;
        }

        // Complete packet.
        mMessageCallback(*msg, mUserContext);

        buffer += msgLength;
        bufferLength -= msgLength;
    }

    // If we have any residual data, save it for later.
    if (bufferLength && buffer != opcb->buffer) {
        memmove(opcb->buffer, buffer, bufferLength);
    }
    opcb->bufferLength = bufferLength;

    // Don't pass data on to libwebsockets
    return 1;
}

bool NetServer::httpPathEqual(const char *a, const char *b)
{
    // HTTP path comparison. Stop at '?' or '#', to ignore query/fragment portions.
    for (;;) {
        char ca = *a;
        char cb = *b;
        if (ca == '?' || cb == '?' || ca == '#' || cb == '#')
            return true;
        if (ca != cb)
            return false;
        if (ca == '\0')
            return true;
        a++;
        b++;
    }
}

int NetServer::httpBegin(libwebsocket_context *context, libwebsocket *wsi,
    Client &client, const char *path)
{
    /*
     * We have a new plain HTTP request. Match it against our document list, and send
     * back headers for the response.
     */

    HTTPDocument *doc = httpDocumentList;

    // Look for this path in the document list. If it isn't found, we'll serve the 404 doc.
    while (doc->path && !httpPathEqual(doc->path, path))
        doc++;

    if (!doc->path) {
        lwsl_notice("HTTP document not found, \"%s\"\n", path);
    }

    char buffer[1024];
    int size = snprintf(buffer, sizeof buffer,
        "HTTP/1.1 %d %s\r\n"
        "Server: %s\r\n"
        "Content-Type: %s\r\n"
        "Content-Length: %u\r\n"
        "Connection: close\r\n"
        "\r\n",
        doc->path ? 200 : 404,
        doc->path ? "OK" : "Not Found",
        kFCServerVersion,
        doc->contentType,
        (int) strlen(doc->body)
    );

    if (libwebsocket_write(wsi, (unsigned char*) buffer, size, LWS_WRITE_HTTP) < 0) {
        return -1;
    }

    // Write the body asynchronously
    client.httpBody = doc->body;
    libwebsocket_callback_on_writable(context, wsi);

    return 0;
}

int NetServer::httpWrite(libwebsocket_context *context, libwebsocket *wsi, Client &client)
{
    if (!client.httpBody) {
        return -1;
    }

    do {
        int len = (int) strlen(client.httpBody);
        if (len == 0) {
            // End of document
            return -1;
        }
        int m = libwebsocket_write(wsi, (unsigned char *) client.httpBody, len, LWS_WRITE_HTTP);
        if (m < 0) {
            return -1;
        }
        client.httpBody += m;
    } while (!lws_send_pipe_choked(wsi));

    libwebsocket_callback_on_writable(context, wsi);
    return 0;
}

int NetServer::wsRead(libwebsocket_context *context, libwebsocket *wsi, Client &client, uint8_t *in, size_t len)
{
    // WebSockets data! Binary frames are in OPC format, text frames are JSON.

    if (lws_frame_is_binary(wsi)) {
        OPC::Message *msg = (OPC::Message*) in;

        if (len < OPC::HEADER_BYTES) {
            lwsl_notice("NOTICE: Received binary WebSockets packet, but it's too small for an OPC header.\n");
            return 0;
        }

        if (msg->lenLow != 0 || msg->lenHigh != 0) {
            lwsl_notice("NOTICE: Received OPC packet over WebSockets with nonzero reserved (length) fields.\n");
        }

        if (len > sizeof *msg) {
            lwsl_notice("NOTICE: Received oversized OPC packet over WebSockets. Truncating.\n");
            len = sizeof *msg;
        }

        msg->setLength(len - OPC::HEADER_BYTES);
        mMessageCallback(*msg, mUserContext);

        return 0;
    }

    // XXX: Implement JSON messages
    lwsl_notice("NOTICE: Received text frame over WebSockets. Not yet implemented!\n");

    return 0;
}