/*
 +----------------------------------------------------------------------+
 | Swoole                                                               |
 +----------------------------------------------------------------------+
 | Copyright (c) 2012-2015 The Swoole Group                             |
 +----------------------------------------------------------------------+
 | This source file is subject to version 2.0 of the Apache license,    |
 | that is bundled with this package in the file LICENSE, and is        |
 | available through the world-wide-web at the following url:           |
 | http://www.apache.org/licenses/LICENSE-2.0.html                      |
 | If you did not receive a copy of the Apache2.0 license and are unable|
 | to obtain it through the world-wide-web, please send a note to       |
 | license@swoole.com so we can mail you a copy immediately.            |
 +----------------------------------------------------------------------+
 | Author: Tianfeng Han  <mikan.tenny@gmail.com>                        |
 +----------------------------------------------------------------------+
 */

#include "swoole.h"
#include "redis.h"
#include "connection.h"

typedef struct
{
    uint8_t state;

    int n_lines_total;
    int n_lines_received;

    int n_bytes_total;
    int n_bytes_received;

    int offset;

} swRedis_request;

int swRedis_recv(swProtocol *protocol, swConnection *conn, swString *buffer)
{
    char *p, *pe;
    int ret;
    char *buf_ptr;
    size_t buf_size;

    swRedis_request *request;
    swSocket *socket = conn->socket;

    if (conn->object == NULL)
    {
        request = sw_malloc(sizeof(swRedis_request));
        if (!request)
        {
            swWarn("malloc(%ld) failed", sizeof(swRedis_request));
            return SW_ERR;
        }
        bzero(request, sizeof(swRedis_request));
        conn->object = request;
    }
    else
    {
        request = (swRedis_request *) conn->object;
    }

    _recv_data:
    buf_ptr = buffer->str + buffer->length;
    buf_size = buffer->size - buffer->length;

    int n = swConnection_recv(socket, buf_ptr, buf_size, 0);
    if (n < 0)
    {
        switch (swConnection_error(errno))
        {
        case SW_ERROR:
            swSysWarn("recv from socket#%d failed", conn->fd);
            return SW_OK;
        case SW_CLOSE:
            return SW_ERR;
        default:
            return SW_OK;
        }
    }
    else if (n == 0)
    {
        return SW_ERR;
    }
    else
    {
        buffer->length += n;

        if (strncmp(buffer->str + buffer->length - SW_CRLF_LEN, SW_CRLF, SW_CRLF_LEN) != 0)
        {
            if (buffer->size < protocol->package_max_length)
            {
                uint32_t extend_size = swoole_size_align(buffer->size * 2, SwooleG.pagesize);
                if (extend_size > protocol->package_max_length)
                {
                    extend_size = protocol->package_max_length;
                }
                if (swString_extend(buffer, extend_size) < 0)
                {
                    return SW_ERR;
                }
            }
            else if (buffer->length == buffer->size)
            {
                _package_too_big:
                swWarn("Package is too big. package_length=%ld", buffer->length);
                return SW_ERR;
            }
            goto _recv_data;
        }

        p = buffer->str;
        pe = p + buffer->length;

        do
        {
            switch(request->state)
            {
            case SW_REDIS_RECEIVE_TOTAL_LINE:
                if (*p == '*' && (p = swRedis_get_number(p, &ret)))
                {
                    request->n_lines_total = ret;
                    request->state = SW_REDIS_RECEIVE_LENGTH;
                    break;
                }
                /* no break */

            case SW_REDIS_RECEIVE_LENGTH:
                if (*p == '$' && (p = swRedis_get_number(p, &ret)))
                {
                    if (ret < 0)
                    {
                        break;
                    }
                    if (ret + (p - buffer->str) > protocol->package_max_length)
                    {
                        goto _package_too_big;
                    }
                    request->n_bytes_total = ret;
                    request->state = SW_REDIS_RECEIVE_STRING;
                    break;
                }
                //integer
                else if (*p == ':' && (p = swRedis_get_number(p, &ret)))
                {
                    break;
                }
                /* no break */

            case SW_REDIS_RECEIVE_STRING:
                if (pe - p < request->n_bytes_total - request->n_bytes_received)
                {
                    request->n_bytes_received += pe - p;
                    return SW_OK;
                }
                else
                {
                    p += request->n_bytes_total + SW_CRLF_LEN;
                    request->n_bytes_total = 0;
                    request->n_lines_received++;
                    request->state = SW_REDIS_RECEIVE_LENGTH;

                    if (request->n_lines_received == request->n_lines_total)
                    {
                        if (protocol->onPackage(protocol, socket, buffer->str, buffer->length) < 0)
                        {
                            return SW_ERR;
                        }
                        if (socket->removed)
                        {
                            return SW_OK;
                        }
                        swString_clear(buffer);
                        bzero(request, sizeof(swRedis_request));
                        return SW_OK;
                    }
                }
                break;

            default:
                goto _failed;
            }
        } while(p < pe);
    }
    _failed:
    swWarn("redis protocol error");
    return SW_ERR;
}
