// Copyright 2015-2020 The NATS Authors
// 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.

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

#include "natsp.h"
#include "conn.h"
#include "util.h"
#include "mem.h"

// cloneMsgArg is used when the split buffer scenario has the pubArg in the existing read buffer, but
// we need to hold onto it into the next read.
static natsStatus
_cloneMsgArg(natsConnection *nc)
{
    natsStatus  s;
    int         subjLen = natsBuf_Len(nc->ps->ma.subject);

    s = natsBuf_InitWithBackend(&(nc->ps->argBufRec),
                                nc->ps->scratch,
                                0,
                                sizeof(nc->ps->scratch));
    if (s == NATS_OK)
    {
        nc->ps->argBuf = &(nc->ps->argBufRec);

        s = natsBuf_Append(nc->ps->argBuf,
                           natsBuf_Data(nc->ps->ma.subject),
                           natsBuf_Len(nc->ps->ma.subject));
        if (s == NATS_OK)
        {
            natsBuf_Destroy(nc->ps->ma.subject);
            nc->ps->ma.subject = NULL;

            s = natsBuf_InitWithBackend(&(nc->ps->ma.subjectRec),
                                        nc->ps->scratch,
                                        subjLen,
                                        subjLen);
            if (s == NATS_OK)
                nc->ps->ma.subject = &(nc->ps->ma.subjectRec);
        }
    }
    if ((s == NATS_OK) && (nc->ps->ma.reply != NULL))
    {
        s = natsBuf_Append(nc->ps->argBuf,
                           natsBuf_Data(nc->ps->ma.reply),
                           natsBuf_Len(nc->ps->ma.reply));
        if (s == NATS_OK)
        {
            int replyLen = natsBuf_Len(nc->ps->ma.reply);

            natsBuf_Destroy(nc->ps->ma.reply);
            nc->ps->ma.reply = NULL;

            s = natsBuf_InitWithBackend(&(nc->ps->ma.replyRec),
                                        nc->ps->scratch + subjLen,
                                        replyLen,
                                        replyLen);
            if (s == NATS_OK)
                nc->ps->ma.reply = &(nc->ps->ma.replyRec);
        }
    }

    return s;
}

struct slice
{
    char    *start;
    int     len;
};

static natsStatus
_processMsgArgs(natsConnection *nc, char *buf, int bufLen)
{
    natsStatus      s       = NATS_OK;
    int             start   = -1;
    int             index   = 0;
    int             i;
    char            b;
    struct slice    slices[5];
    char            errTxt[256];
    int             indexLimit = 3;
    int             minArgs    = 3;
    int             maxArgs    = 4;
    bool            hasHeaders = (nc->ps->hdr >= 0 ? true : false);

    // If headers, the content should be:
    // <subject> <sid> [reply] <hdr size> <overall size>
    // otherwise:
    // <subject> <sid> [reply] <overall size>
    if (hasHeaders)
    {
        indexLimit = 4;
        minArgs    = 4;
        maxArgs    = 5;
    }

    for (i = 0; i < bufLen; i++)
    {
        b = buf[i];

        if (((b == ' ') || (b == '\t') || (b == '\r') || (b == '\n')))
        {
            if (start >=0)
            {
                if (index > indexLimit)
                {
                    s = NATS_PROTOCOL_ERROR;
                    break;
                }
                slices[index].start = buf + start;
                slices[index].len   = i - start;
                index++;
                start = -1;
            }
        }
        else if (start < 0)
        {
            start = i;
        }
    }
    if ((s == NATS_OK) && (start >= 0))
    {
        if (index > indexLimit)
        {
            s = NATS_PROTOCOL_ERROR;
        }
        else
        {
            slices[index].start = buf + start;
            slices[index].len   = i - start;
            index++;
        }
    }
    if ((s == NATS_OK) && ((index == minArgs) || (index == maxArgs)))
    {
        int maSizeIndex  = index-1; // position of size is always last.
        int hdrSizeIndex = index-2; // position of hdr size is always before last.

        s = natsBuf_InitWithBackend(&(nc->ps->ma.subjectRec),
                                    slices[0].start,
                                    slices[0].len,
                                    slices[0].len);
        if (s == NATS_OK)
        {
            nc->ps->ma.subject = &(nc->ps->ma.subjectRec);

            nc->ps->ma.sid   = nats_ParseInt64(slices[1].start, slices[1].len);

            if (index == minArgs)
            {
                nc->ps->ma.reply = NULL;
            }
            else
            {
                s = natsBuf_InitWithBackend(&(nc->ps->ma.replyRec),
                                            slices[2].start,
                                            slices[2].len,
                                            slices[2].len);
                if (s == NATS_OK)
                {
                    nc->ps->ma.reply = &(nc->ps->ma.replyRec);
                }
            }
        }
        if (s == NATS_OK)
        {
            if (hasHeaders)
            {
                nc->ps->ma.hdr = (int) nats_ParseInt64(slices[hdrSizeIndex].start,
                                                       slices[hdrSizeIndex].len);
            }
            nc->ps->ma.size = (int) nats_ParseInt64(slices[maSizeIndex].start,
                                                    slices[maSizeIndex].len);
        }
    }
    else
    {
        snprintf(errTxt, sizeof(errTxt), "%s", "processMsgArgs Parse Error: wrong number of arguments");
        s = NATS_PROTOCOL_ERROR;
    }
    if (nc->ps->ma.sid < 0)
    {
        snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Sid: '%.*s'",
                 bufLen, buf);
        s = NATS_PROTOCOL_ERROR;
    }
    if (nc->ps->ma.size < 0)
    {
        snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Size: '%.*s'",
                 bufLen, buf);
        s = NATS_PROTOCOL_ERROR;
    }
    if (hasHeaders && ((nc->ps->ma.hdr < 0) || (nc->ps->ma.hdr > nc->ps->ma.size)))
    {
        snprintf(errTxt, sizeof(errTxt), "processMsgArgs Bad or Missing Header Size: '%.*s'",
                 bufLen, buf);
        s = NATS_PROTOCOL_ERROR;
    }

    if (s != NATS_OK)
    {
        natsConn_Lock(nc);
        snprintf(nc->errStr, sizeof(nc->errStr), "%s", errTxt);
        nc->err = s;
        natsConn_Unlock(nc);
    }

    return s;
}

// parse is the fast protocol parser engine.
natsStatus
natsParser_Parse(natsConnection *nc, char* buf, int bufLen)
{
    natsStatus  s = NATS_OK;
    int         i;
    char        b;

    for (i = 0; (s == NATS_OK) && (i < bufLen); i++)
    {
        b = buf[i];

        switch (nc->ps->state)
        {
            case OP_START:
            {
                switch (b)
                {
                    case 'M':
                    case 'm':
                        nc->ps->state  = OP_M;
                        nc->ps->hdr    = -1;
                        nc->ps->ma.hdr = -1;
                        break;
                    case 'H':
                    case 'h':
                        nc->ps->state  = OP_H;
                        nc->ps->hdr    = 0;
                        nc->ps->ma.hdr = 0;
                        break;
                    case 'P':
                    case 'p':
                        nc->ps->state = OP_P;
                        break;
                    case '+':
                        nc->ps->state = OP_PLUS;
                        break;
                    case '-':
                        nc->ps->state = OP_MINUS;
                        break;
                    case 'I':
                    case 'i':
                        nc->ps->state = OP_I;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_H:
            {
                switch (b)
                {
                    case 'M':
                    case 'm':
                        nc->ps->state = OP_M;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_M:
            {
                switch (b)
                {
                    case 'S':
                    case 's':
                        nc->ps->state = OP_MS;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MS:
            {
                switch (b)
                {
                    case 'G':
                    case 'g':
                        nc->ps->state = OP_MSG;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MSG:
            {
                switch (b)
                {
                    case ' ':
                    case '\t':
                        nc->ps->state = OP_MSG_SPC;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MSG_SPC:
            {
                switch (b)
                {
                    case ' ':
                    case '\t':
                        continue;
                    default:
                        nc->ps->state      = MSG_ARG;
                        nc->ps->afterSpace = i;
                        break;
                }
                break;
            }
            case MSG_ARG:
            {
                switch (b)
                {
                    case '\r':
                        nc->ps->drop = 1;
                        break;
                    case '\n':
                    {
                        char    *start = NULL;
                        int     len    = 0;

                        if (nc->ps->argBuf != NULL)
                        {
                            start = natsBuf_Data(nc->ps->argBuf);
                            len   = natsBuf_Len(nc->ps->argBuf);
                        }
                        else
                        {
                            start = buf + nc->ps->afterSpace;
                            len   = (i - nc->ps->drop) - nc->ps->afterSpace;
                        }

                        s = _processMsgArgs(nc, start, len);
                        if (s == NATS_OK)
                        {
                            nc->ps->drop        = 0;
                            nc->ps->afterSpace  = i+1;
                            nc->ps->state       = MSG_PAYLOAD;

                            // jump ahead with the index. If this overruns
                            // what is left we fall out and process split
                            // buffer.
                            i = nc->ps->afterSpace + nc->ps->ma.size - 1;
                        }
                        break;
                    }
                    default:
                    {
                        if (nc->ps->argBuf != NULL)
                            s = natsBuf_AppendByte(nc->ps->argBuf, b);
                        break;
                    }
                }
                break;
            }
            case MSG_PAYLOAD:
            {
                bool done = false;

                if (nc->ps->msgBuf != NULL)
                {
                    if (natsBuf_Len(nc->ps->msgBuf) >= nc->ps->ma.size)
                    {
                        s = natsConn_processMsg(nc,
                                                natsBuf_Data(nc->ps->msgBuf),
                                                natsBuf_Len(nc->ps->msgBuf));
                        done = true;
                    }
                    else
                    {
                        // copy as much as we can to the buffer and skip ahead.
                        int toCopy = nc->ps->ma.size - natsBuf_Len(nc->ps->msgBuf);
                        int avail  = bufLen - i;

                        if (avail < toCopy)
                            toCopy = avail;

                        if (toCopy > 0)
                        {
                            s = natsBuf_Append(nc->ps->msgBuf, buf+i, toCopy);
                            if (s == NATS_OK)
                                i += toCopy - 1;
                        }
                        else
                        {
                            s = natsBuf_AppendByte(nc->ps->msgBuf, b);
                        }
                    }
                }
                else if (i-nc->ps->afterSpace >= nc->ps->ma.size)
                {
                    char    *start  = NULL;
                    int     len     = 0;

                    start = buf + nc->ps->afterSpace;
                    len   = (i - nc->ps->afterSpace);

                    s = natsConn_processMsg(nc, start, len);

                    done = true;
                }

                if (done)
                {
                    natsBuf_Destroy(nc->ps->argBuf);
                    nc->ps->argBuf = NULL;
                    natsBuf_Destroy(nc->ps->msgBuf);
                    nc->ps->msgBuf = NULL;
                    nc->ps->state = MSG_END;
                }

                break;
            }
            case MSG_END:
            {
                switch (b)
                {
                    case '\n':
                        nc->ps->drop        = 0;
                        nc->ps->afterSpace  = i+1;
                        nc->ps->state       = OP_START;
                        break;
                    default:
                        continue;
                }
                break;
            }
            case OP_PLUS:
            {
                switch (b)
                {
                    case 'O':
                    case 'o':
                        nc->ps->state = OP_PLUS_O;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PLUS_O:
            {
                switch (b)
                {
                    case 'K':
                    case 'k':
                        nc->ps->state = OP_PLUS_OK;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PLUS_OK:
            {
                switch (b)
                {
                    case '\n':
                        natsConn_processOK(nc);
                        nc->ps->drop  = 0;
                        nc->ps->state = OP_START;
                        break;
                }
                break;
            }
            case OP_MINUS:
            {
                switch (b)
                {
                    case 'E':
                    case 'e':
                        nc->ps->state = OP_MINUS_E;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MINUS_E:
            {
                switch (b)
                {
                    case 'R':
                    case 'r':
                        nc->ps->state = OP_MINUS_ER;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MINUS_ER:
            {
                switch (b)
                {
                    case 'R':
                    case 'r':
                        nc->ps->state = OP_MINUS_ERR;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MINUS_ERR:
            {
                switch (b)
                {
                    case ' ':
                    case '\t':
                        nc->ps->state = OP_MINUS_ERR_SPC;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_MINUS_ERR_SPC:
            {
                switch (b)
                {
                    case ' ':
                    case '\t':
                        continue;
                    default:
                        nc->ps->state       = MINUS_ERR_ARG;
                        nc->ps->afterSpace  = i;
                        break;
                }
                break;
            }
            case MINUS_ERR_ARG:
            {
                switch (b)
                {
                    case '\r':
                        nc->ps->drop = 1;
                        break;
                    case '\n':
                    {
                        char    *start = NULL;
                        int     len    = 0;

                        if (nc->ps->argBuf != NULL)
                        {
                            start = natsBuf_Data(nc->ps->argBuf);
                            len   = natsBuf_Len(nc->ps->argBuf);
                        }
                        else
                        {
                            start = buf + nc->ps->afterSpace;
                            len   = (i - nc->ps->drop) - nc->ps->afterSpace;
                        }

                        natsConn_processErr(nc, start, len);

                        nc->ps->drop        = 0;
                        nc->ps->afterSpace  = i+1;
                        nc->ps->state       = OP_START;

                        if (nc->ps->argBuf != NULL)
                        {
                            natsBuf_Destroy(nc->ps->argBuf);
                            nc->ps->argBuf = NULL;
                        }

                        break;
                    }
                    default:
                    {
                        if (nc->ps->argBuf != NULL)
                            s = natsBuf_AppendByte(nc->ps->argBuf, b);

                        break;
                    }
                }
                break;
            }
            case OP_P:
            {
                switch (b)
                {
                    case 'I':
                    case 'i':
                        nc->ps->state = OP_PI;
                        break;
                    case 'O':
                    case 'o':
                        nc->ps->state = OP_PO;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PO:
            {
                switch (b)
                {
                    case 'N':
                    case 'n':
                        nc->ps->state = OP_PON;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PON:
            {
                switch (b)
                {
                    case 'G':
                    case 'g':
                        nc->ps->state = OP_PONG;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PONG:
            {
                switch (b)
                {
                    case '\n':
                        natsConn_processPong(nc);

                        nc->ps->drop        = 0;
                        nc->ps->afterSpace  = i+1;
                        nc->ps->state       = OP_START;
                        break;
                }
                break;
            }
            case OP_PI:
            {
                switch (b)
                {
                    case 'N':
                    case 'n':
                        nc->ps->state = OP_PIN;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PIN:
            {
                switch (b)
                {
                    case 'G':
                    case 'g':
                        nc->ps->state = OP_PING;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_PING:
            {
                switch (b)
                {
                    case '\n':
                        natsConn_processPing(nc);

                        nc->ps->drop        = 0;
                        nc->ps->afterSpace  = i+1;
                        nc->ps->state       = OP_START;
                        break;
                }
                break;
            }
            case OP_I:
            {
                switch (b)
                {
                    case 'N':
                    case 'n':
                        nc->ps->state = OP_IN;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_IN:
            {
                switch (b)
                {
                    case 'F':
                    case 'f':
                        nc->ps->state = OP_INF;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_INF:
            {
                switch (b)
                {
                    case 'O':
                    case 'o':
                        nc->ps->state = OP_INFO;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_INFO:
            {
                switch (b)
                {
                    case ' ':
                    case '\t':
                        nc->ps->state = OP_INFO_SPC;
                        break;
                    default:
                        goto parseErr;
                }
                break;
            }
            case OP_INFO_SPC:
            {
                switch (b)
                {
                    case ' ':
                    case '\t':
                        continue;
                    default:
                        nc->ps->state = INFO_ARG;
                        nc->ps->afterSpace = i;
                        break;
                }
                break;
            }
            case INFO_ARG:
            {
                switch (b)
                {
                    case '\r':
                        nc->ps->drop = 1;
                        break;
                    case '\n':
                    {
                        char    *start = NULL;
                        int     len    = 0;

                        if (nc->ps->argBuf != NULL)
                        {
                            start = natsBuf_Data(nc->ps->argBuf);
                            len   = natsBuf_Len(nc->ps->argBuf);
                        }
                        else
                        {
                            start = buf + nc->ps->afterSpace;
                            len   = (i - nc->ps->drop) - nc->ps->afterSpace;
                        }
                        natsConn_processAsyncINFO(nc, start, len);
                        nc->ps->drop        = 0;
                        nc->ps->afterSpace  = i+1;
                        nc->ps->state       = OP_START;

                        if (nc->ps->argBuf != NULL)
                        {
                            natsBuf_Destroy(nc->ps->argBuf);
                            nc->ps->argBuf = NULL;
                        }
                        break;
                    }
                    default:
                    {
                        if (nc->ps->argBuf != NULL)
                            s = natsBuf_AppendByte(nc->ps->argBuf, b);
                        break;
                    }
                }
                break;
            }
            default:
                goto parseErr;
        }
    }

    // Check for split buffer scenarios
    if ((s == NATS_OK)
        && ((nc->ps->state == MSG_ARG)
                || (nc->ps->state == MINUS_ERR_ARG)
                || (nc->ps->state == INFO_ARG))
        && (nc->ps->argBuf == NULL))
    {
        s = natsBuf_InitWithBackend(&(nc->ps->argBufRec),
                                    nc->ps->scratch,
                                    0,
                                    sizeof(nc->ps->scratch));
        if (s == NATS_OK)
        {
            nc->ps->argBuf = &(nc->ps->argBufRec);
            s = natsBuf_Append(nc->ps->argBuf,
                               buf + nc->ps->afterSpace,
                               (i - nc->ps->drop) - nc->ps->afterSpace);
        }
    }
    // Check for split msg
    if ((s == NATS_OK)
        && (nc->ps->state == MSG_PAYLOAD) && (nc->ps->msgBuf == NULL))
    {
        // We need to clone the msgArg if it is still referencing the
        // read buffer and we are not able to process the msg.
        if (nc->ps->argBuf == NULL)
            s = _cloneMsgArg(nc);

        if (s == NATS_OK)
        {
            int remainingInScratch;
            int toCopy;

#ifdef _WIN32
// Suppresses the warning that nc->ps->argBuf may be NULL.
// If nc->ps->argBuf is NULL above, then _cloneMsgArg() will set it. If 's'
// is NATS_OK here, then nc->ps->argBuf can't be NULL.
#pragma warning(suppress: 6011)
#endif

            // If we will overflow the scratch buffer, just create a
            // new buffer to hold the split message.
            remainingInScratch = sizeof(nc->ps->scratch) - natsBuf_Len(nc->ps->argBuf);
            toCopy = bufLen - nc->ps->afterSpace;

            if (nc->ps->ma.size > remainingInScratch)
            {
                s = natsBuf_Create(&(nc->ps->msgBuf), nc->ps->ma.size);
            }
            else
            {
                s = natsBuf_InitWithBackend(&(nc->ps->msgBufRec),
                                            nc->ps->scratch + natsBuf_Len(nc->ps->argBuf),
                                            0, remainingInScratch);
                if (s == NATS_OK)
                    nc->ps->msgBuf = &(nc->ps->msgBufRec);
            }
            if (s == NATS_OK)
                s = natsBuf_Append(nc->ps->msgBuf,
                                   buf + nc->ps->afterSpace,
                                   toCopy);
        }
    }

    if (s != NATS_OK)
    {
        // Let's clear all our pointers...
        natsBuf_Destroy(nc->ps->argBuf);
        nc->ps->argBuf = NULL;
        natsBuf_Destroy(nc->ps->msgBuf);
        nc->ps->msgBuf = NULL;
        natsBuf_Destroy(nc->ps->ma.subject);
        nc->ps->ma.subject = NULL;
        natsBuf_Destroy(nc->ps->ma.reply);
        nc->ps->ma.reply = NULL;
    }

    return s;

parseErr:
    if (s == NATS_OK)
        s = NATS_PROTOCOL_ERROR;

    natsMutex_Lock(nc->mu);

    snprintf(nc->errStr, sizeof(nc->errStr),
             "Parse Error [%u]: '%.*s'",
             nc->ps->state,
             bufLen - i,
             buf + i);

    natsMutex_Unlock(nc->mu);

    return s;
}

natsStatus
natsParser_Create(natsParser **newParser)
{
    natsParser  *parser = (natsParser *) NATS_CALLOC(1, sizeof(natsParser));

    if (parser == NULL)
        return NATS_NO_MEMORY;

    *newParser = parser;

    return NATS_OK;
}

void
natsParser_Destroy(natsParser *parser)
{
    if (parser == NULL)
        return;

    natsBuf_Cleanup(&(parser->ma.subjectRec));
    natsBuf_Cleanup(&(parser->ma.replyRec));
    natsBuf_Cleanup(&(parser->argBufRec));
    natsBuf_Cleanup(&(parser->msgBufRec));

    NATS_FREE(parser);
}
