/*
 * Copyright (C) 2021-2022 Huawei Device Co., Ltd.
 * 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 "context.h"
#include <securec.h>
#include <stdlib.h>
#include <string.h>
#include <sys/un.h>
#include "common.h"
#include "net.h"

Context *CreateContext(int capacity)
{
    if (capacity < CONTEXT_BUFFER_MIN_SIZE) {
        capacity = CONTEXT_BUFFER_MIN_SIZE;
    } else if (capacity > CONTEXT_BUFFER_MAX_SIZE) {
        capacity = CONTEXT_BUFFER_MAX_SIZE;
    }
    Context *context = (Context *)calloc(1, sizeof(Context));
    if (context == NULL) {
        return NULL;
    }
    context->rCapacity = (uint32_t)capacity;
    context->wCapacity = (uint32_t)capacity;
    context->szRead = (char *)calloc(capacity, sizeof(char));
    if (context->szRead == NULL) {
        free(context);
        context = NULL;
        return NULL;
    }
    context->szWrite = (char *)calloc(capacity, sizeof(char));
    if (context->szWrite == NULL) {
        free(context->szRead);
        free(context);
        context = NULL;
        return NULL;
    }
    context->cSplit = '\t';
    context->cMsgEnd = "$$$$$$";
    return context;
}

void ReleaseContext(Context *context)
{
    if (context == NULL) {
        return;
    }

    if (context != NULL) {
        free(context->szRead);
        free(context->szWrite);
        free(context);
        context = NULL;
    }
}

/**
 * when context's read/write buffer is less then request len
 * expand the buffer size
 */
static int ExpandReadCache(Context *context, int len)
{
    if (context == NULL) {
        return -1;
    }

    int left = (int)((context->rBegin <= context->rEnd) ? (context->rCapacity - 1 - context->rEnd + context->rBegin)
                                                  : (context->rBegin - context->rEnd - 1));
    if (left < len) {
        uint32_t capacity = context->rCapacity;
        while (left < len) {
            capacity += context->rCapacity;
            left += context->rCapacity;
        }
        char *p = (char *)calloc(capacity, sizeof(char));
        if (p == NULL) {
            return -1;
        }
        if (memmove_s(p, capacity, context->szRead, context->rCapacity) != EOK) {
            free(p);
            p = NULL;
            return -1;
        }
        if (context->rBegin > context->rEnd &&
            memmove_s(p + context->rCapacity, context->rCapacity, p, context->rEnd) != EOK) {
            free(p);
            p = NULL;
            return -1;
        }
        char *pFree = context->szRead;
        context->szRead = p;
        if (context->rBegin > context->rEnd) {
            context->rEnd += context->rCapacity;
        }
        context->rCapacity = capacity;
        free(pFree);
        pFree = NULL;
    }
    return 0;
}

static int ExpandWriteCache(Context *context, int len)
{
    if (context == NULL) {
        return -1;
    }

    int left = (int)((context->wBegin <= context->wEnd) ? (context->wCapacity - 1 - context->wEnd + context->wBegin)
                                                  : (context->wBegin - context->wEnd - 1));
    if (left < len) {
        uint32_t capacity = context->wCapacity;
        while (left < len) {
            capacity += context->wCapacity;
            left += (int)context->wCapacity;
        }
        char *p = (char *)calloc(capacity, sizeof(char));
        if (p == NULL) {
            return -1;
        }
        if (memmove_s(p, capacity, context->szWrite, context->wCapacity) != EOK) {
            free(p);
            p = NULL;
            return -1;
        }
        if (context->wBegin > context->wEnd &&
            memmove_s(p + context->wCapacity, context->wCapacity, p, context->wEnd) != EOK) {
            free(p);
            p = NULL;
            return -1;
        }
        char *pFree = context->szWrite;
        context->szWrite = p;
        if (context->wBegin > context->wEnd) {
            context->wEnd += context->wCapacity;
        }
        context->wCapacity = capacity;
        free(pFree);
        pFree = NULL;
    }
    return 0;
}

static int ContextAppendRead(Context *context, const char *buf, int len)
{
    if (context == NULL) {
        return -1;
    }

    if (ExpandReadCache(context, len) < 0) {
        return -1;
    }
    if (context->rCapacity < context->rEnd || len < 0) {
        return -1;
    }
    unsigned int tmp = context->rCapacity - context->rEnd;
    if ((unsigned int)len < tmp) {
        if (memmove_s(context->szRead + context->rEnd, context->rCapacity - context->rEnd, buf, len) != EOK) {
            return -1;
        }
        context->rEnd += len;
    } else {
        if (tmp > 0 && memmove_s(context->szRead + context->rEnd, tmp, buf, tmp) != EOK) {
            return -1;
        }
        if (tmp < len && memmove_s(context->szRead, len - tmp, buf + tmp, len - tmp) != EOK) {
            return -1;
        }
        context->rEnd = (unsigned int)len - tmp;
    }
    return 0;
}

int ContextAppendWrite(Context *context, const char *buf, int len)
{
    if (context == NULL) {
        return -1;
    }

    if (ExpandWriteCache(context, len) < 0) {
        return -1;
    }
    if (context->rCapacity < context->wEnd || len < 0) {
        return -1;
    }
    unsigned int tmp = context->wCapacity - context->wEnd;
    if ((unsigned int)len < tmp) {
        if (memmove_s(context->szWrite + context->wEnd, context->wCapacity - context->wEnd, buf, len) != EOK) {
            return -1;
        }
        context->wEnd += len;
    } else {
        if (tmp > 0 && memmove_s(context->szWrite + context->wEnd, tmp, buf, tmp) != EOK) {
            return -1;
        }
        if (tmp < len && memmove_s(context->szWrite, len - tmp, buf + tmp, len - tmp) != EOK) {
            return -1;
        }
        context->wEnd = (unsigned int)len - tmp;
    }
    return 0;
}

char *ContextGetReadRecord(Context *context)
{
    if (context == NULL) {
        return NULL;
    }

    if (context->rBegin == context->rEnd) {
        return NULL;
    }
    uint32_t len = (context->rBegin <= context->rEnd) ? (context->rEnd - context->rBegin)
                                                 : (context->rCapacity - context->rBegin + context->rEnd);
    char *buf = (char *)calloc(len + 1, sizeof(char));
    if (buf == NULL) {
        return NULL;
    }
    if (context->rBegin < context->rEnd) {
        if (memmove_s(buf, len, context->szRead + context->rBegin, len) != EOK) {
            free(buf);
            return NULL;
        }
    } else {
        int tmp = (int)(context->rCapacity - context->rBegin);
        if (tmp > 0 && memmove_s(buf, len + 1, context->szRead + context->rBegin, tmp) != EOK) {
            free(buf);
            return NULL;
        }
        if (context->rEnd > 0 && memmove_s(buf + tmp, len + 1 - tmp, context->szRead, context->rEnd) != EOK) {
            free(buf);
            return NULL;
        }
    }
    buf[len] = 0;
    char *p = strstr(buf, context->cMsgEnd);
    if (p == NULL) {
        free(buf);
        return NULL;
    }
    *p = 0;
    uint32_t num = p - buf + strlen(context->cMsgEnd);
    context->rBegin += num;
    if (context->rBegin >= context->rCapacity) {
        context->rBegin -= context->rCapacity;
    }
    return buf;
}

int ContextReadNet(Context *context)
{
    if (context == NULL) {
        return -1;
    }

    char line[MAX_ONE_LINE_SIZE] = {0};
    int ret = MyRead(context->fd, line, sizeof(line) - 1);
    if (ret == SOCK_ERR) {
        return SOCK_ERR;
    }
    int len = strlen(line);
    if (len > 0 && ContextAppendRead(context, line, len) < 0) {
        return -1;
    }
    return ret;
}

int ContextWriteNet(Context *context)
{
    if (context == NULL) {
        return -1;
    }

    if (context->wBegin == context->wEnd) {
        return 0;
    }
    if (context->wBegin < context->wEnd) {
        int ret = MyWrite(context->fd, context->szWrite + context->wBegin, context->wEnd - context->wBegin);
        if (ret > 0) {
            context->wBegin += ret;
        }
        return ret;
    }
    int len = (int)(context->wCapacity - context->wBegin);
    int ret = MyWrite(context->fd, context->szWrite + context->wBegin, len);
    if (ret < 0) {
        return ret;
    }
    if (ret < len) {
        context->wBegin += ret;
        return ret;
    }
    context->wBegin = 0;
    ret = MyWrite(context->fd, context->szWrite, context->wEnd);
    if (ret > 0) {
        context->wBegin = (uint32_t)ret;
    }
    return ret;
}