/*
 * Copyright (C) 2021 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 "vendor_channel.h"

#include <stddef.h>
#include <sys/types.h>

#include "bits/errno.h"
#include "errno.h"
#include "securec.h"
#include "string.h"
#include "telephony_log_c.h"
#include "unistd.h"
#include "vendor_util.h"

static char g_buffer[MAX_RESPONSE_LEN + 1];
static char *g_bufferCur = g_buffer;

static char *FindEndOfLine(char *cur)
{
    char *endLine = cur;
    int32_t count = 2;
    if (endLine == NULL) {
        return NULL;
    }
    if (endLine[0] == '>' && endLine[1] == ' ' && endLine[count] == '\0') {
        return endLine + count;
    }
    while (*endLine != '\0' && *endLine != '\r' && *endLine != '\n') {
        endLine++;
    }
    return (*endLine != '\0') ? endLine : NULL;
}

static void SkipUnUseChar(void)
{
    while (*g_bufferCur == '\r' || *g_bufferCur == '\n') {
        g_bufferCur++;
    }
}

static void ClearCurBuffer(char **processed)
{
    if (processed == NULL) {
        return;
    }
    g_buffer[0] = '\0';
    g_bufferCur = g_buffer;
    *processed = g_bufferCur;
}

static char *ProcessLastResponse(char **processed)
{
    if (processed == NULL) {
        return NULL;
    }
    char *endLine = NULL;
    if (*g_bufferCur == '\0') {
        ClearCurBuffer(processed);
    } else {
        SkipUnUseChar();
        endLine = FindEndOfLine(g_bufferCur);
        if (endLine == NULL) {
            TELEPHONY_LOGE("g_bufferCur endLine is null\n");
            size_t len;
            len = strlen(g_bufferCur);
            (void)memmove_s(g_buffer, MAX_RESPONSE_LEN + 1, g_bufferCur, len + 1);
            *processed = g_buffer + len;
            g_bufferCur = g_buffer;
        }
    }
    return endLine;
}

const char *ReadResponse(int32_t atFd)
{
    ssize_t size;
    char *processed = NULL;
    char *endEol = NULL;
    char *ret = NULL;
    // process last response data
    endEol = ProcessLastResponse(&processed);
    while (endEol == NULL) {
        if (MAX_RESPONSE_LEN == (processed - &g_buffer[0])) {
            ClearCurBuffer(&processed);
        }
        do {
            size = read(atFd, processed, (MAX_RESPONSE_LEN - (processed - &g_buffer[0])));
        } while (size < 0 && errno == EINTR);
        if (size > 0) {
            processed[size] = '\0';
            SkipUnUseChar();
            endEol = FindEndOfLine(g_bufferCur);
            processed += size;
        } else if (size == 0) {
            TELEPHONY_LOGE("atchannel: EOF reached");
            return NULL;
        } else {
            TELEPHONY_LOGE("read error %{public}s", strerror(errno));
            return NULL;
        }
    }
    *endEol = '\0';
    ret = g_bufferCur;
    g_bufferCur = endEol + 1;
    return ret;
}

int32_t WriteATCommand(const char *s, int32_t isPdu, int32_t atFd)
{
    if (s == NULL) {
        return AT_ERR_GENERIC;
    }
    ssize_t ret;
    size_t i = 0;
    size_t len = strlen(s);
    if (atFd < 0) {
        return AT_ERR_CHANNEL_CLOSED;
    }

    while (i < len) {
        do {
            ret = write(atFd, s + i, len - i);
        } while (ret < 0 && errno == EINTR);

        if (ret < 0) {
            return AT_ERR_GENERIC;
        }
        i += ret;
    }
    if (isPdu != 0) {
        do {
            ret = write(atFd, "\x1A", 1);
        } while ((ret < 0 && errno == EINTR) || (ret == 0));
    } else {
        do {
            ret = write(atFd, "\r", 1);
        } while ((ret < 0 && errno == EINTR) || (ret == 0));
    }
    if (ret < 0) {
        return AT_ERR_GENERIC;
    }
    return VENDOR_SUCCESS;
}