/*
 * Copyright (c) 2017, Arm Limited and affiliates.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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 <ctype.h>
#include <stdio.h>
#include <limits.h>
#include <errno.h>
#include "ATHandler.h"
#include "mbed_poll.h"
#include "FileHandle.h"
#include "mbed_debug.h"
#include "rtos/ThisThread.h"
#include "Kernel.h"
#include "CellularUtil.h"
#include "SingletonPtr.h"
#include "ScopedLock.h"

using namespace mbed;
using namespace events;
using namespace mbed_cellular_util;

#include "CellularLog.h"

// URCs should be handled fast, if you add debug traces within URC processing then you also need to increase this time
#define PROCESS_URC_TIME 20

// Suppress logging of very big packet payloads, maxlen is approximate due to write/read are cached
#define DEBUG_MAXLEN 60
#define DEBUG_END_MARK "..\r"

const char *mbed::OK = "OK\r\n";
const uint8_t OK_LENGTH = 4;
const char *mbed::CRLF = "\r\n";
const uint8_t CRLF_LENGTH = 2;
const char *CME_ERROR = "+CME ERROR:";
const uint8_t CME_ERROR_LENGTH = 11;
const char *CMS_ERROR = "+CMS ERROR:";
const uint8_t CMS_ERROR_LENGTH = 11;
const char *ERROR_ = "ERROR\r\n";
const uint8_t ERROR_LENGTH = 7;
const uint8_t MAX_RESP_LENGTH = CMS_ERROR_LENGTH;
const char DEFAULT_DELIMITER = ',';

static const uint8_t map_3gpp_errors[][2] =  {
    { 103, 3 },  { 106, 6 },  { 107, 7 },  { 108, 8 },  { 111, 11 }, { 112, 12 }, { 113, 13 }, { 114, 14 },
    { 115, 15 }, { 122, 22 }, { 125, 25 }, { 172, 95 }, { 173, 96 }, { 174, 97 }, { 175, 99 }, { 176, 111 },
    { 177, 8 },  { 126, 26 }, { 127, 27 }, { 128, 28 }, { 129, 29 }, { 130, 30 }, { 131, 31 }, { 132, 32 },
    { 133, 33 }, { 134, 34 }, { 140, 40 }, { 141, 41 }, { 142, 42 }, { 143, 43 }, { 144, 44 }, { 145, 45 },
    { 146, 46 }, { 178, 65 }, { 179, 66 }, { 180, 48 }, { 181, 83 }, { 171, 49 },
};

ATHandler *ATHandler::_atHandlers = NULL;

// each parser is associated with one filehandle (that is UART)
ATHandler *ATHandler::get_instance(FileHandle *fileHandle, events::EventQueue &queue, uint32_t timeout,
                                   const char *delimiter, uint16_t send_delay, bool debug_on)
{
    if (!fileHandle) {
        return NULL;
    }

    singleton_lock();
    ATHandler *atHandler = _atHandlers;
    while (atHandler) {
        if (atHandler->get_file_handle() == fileHandle) {
            atHandler->inc_ref_count();
            singleton_unlock();
            return atHandler;
        }
        atHandler = atHandler->_nextATHandler;
    }

    atHandler = new ATHandler(fileHandle, queue, timeout, delimiter, send_delay);
    if (debug_on) {
        atHandler->set_debug(debug_on);
    }
    atHandler->_nextATHandler = _atHandlers;
    _atHandlers = atHandler;

    singleton_unlock();
    return atHandler;
}

nsapi_error_t ATHandler::close()
{
    if (get_ref_count() == 0) {
        return NSAPI_ERROR_PARAMETER;
    }

    singleton_lock();
    dec_ref_count();
    if (get_ref_count() == 0) {
        // we can delete this at_handler
        ATHandler *atHandler = _atHandlers;
        ATHandler *prev = NULL;
        while (atHandler) {
            if (atHandler == this) {
                if (prev == NULL) {
                    _atHandlers = _atHandlers->_nextATHandler;
                } else {
                    prev->_nextATHandler = atHandler->_nextATHandler;
                }
                delete this;
                break;
            } else {
                prev = atHandler;
                atHandler = atHandler->_nextATHandler;
            }
        }
    }
    singleton_unlock();
    return NSAPI_ERROR_OK;
}

void ATHandler::set_at_timeout_list(uint32_t timeout_milliseconds, bool default_timeout)
{
    ATHandler *atHandler = _atHandlers;
    singleton_lock();
    while (atHandler) {
        atHandler->set_at_timeout(timeout_milliseconds, default_timeout);
        atHandler = atHandler->_nextATHandler;
    }
    singleton_unlock();
}

void ATHandler::set_debug_list(bool debug_on)
{
    ATHandler *atHandler = _atHandlers;
    singleton_lock();
    while (atHandler) {
        atHandler->set_debug(debug_on);
        atHandler = atHandler->_nextATHandler;
    }
    singleton_unlock();
}

bool ATHandler::ok_to_proceed()
{
    if (_last_err != NSAPI_ERROR_OK) {
        return false;
    }

    if (!_is_fh_usable) {
        _last_err = NSAPI_ERROR_BUSY;
        return false;
    }
    return true;
}

ATHandler::ATHandler(FileHandle *fh, EventQueue &queue, uint32_t timeout, const char *output_delimiter, uint16_t send_delay) :
    _nextATHandler(0),
#if defined AT_HANDLER_MUTEX && defined MBED_CONF_RTOS_PRESENT
    _oobCv(_fileHandleMutex),
#endif
    _fileHandle(NULL), // filehandle is set by set_file_handle()
    _queue(queue),
    _last_err(NSAPI_ERROR_OK),
    _last_3gpp_error(0),
    _oob_string_max_length(0),
    _oobs(NULL),
    _at_timeout(timeout),
    _previous_at_timeout(timeout),
    _at_send_delay(send_delay),
    _last_response_stop(0),
    _ref_count(1),
    _is_fh_usable(false),
    _stop_tag(NULL),
    _delimiter(DEFAULT_DELIMITER),
    _prefix_matched(false),
    _urc_matched(false),
    _error_found(false),
    _max_resp_length(MAX_RESP_LENGTH),
    _debug_on(MBED_CONF_CELLULAR_DEBUG_AT),
    _cmd_start(false),
    _use_delimiter(true),
    _start_time(0),
    _event_id(0)
{
    clear_error();

    if (output_delimiter) {
        _output_delimiter = new char[strlen(output_delimiter) + 1];
        memcpy(_output_delimiter, output_delimiter, strlen(output_delimiter) + 1);
    } else {
        _output_delimiter = NULL;
    }

    reset_buffer();
    memset(_recv_buff, 0, sizeof(_recv_buff));
    memset(_info_resp_prefix, 0, sizeof(_info_resp_prefix));

    _current_scope = NotSet;
    set_tag(&_resp_stop, OK);
    set_tag(&_info_stop, CRLF);
    set_tag(&_elem_stop, ")");

    set_file_handle(fh);
}

void ATHandler::set_debug(bool debug_on)
{
    _debug_on = debug_on;
}

bool ATHandler::get_debug() const
{
    return _debug_on;
}

ATHandler::~ATHandler()
{
    ScopedLock <ATHandler> lock(*this);
    set_file_handle(NULL);

    if (_event_id != 0 && _queue.cancel(_event_id)) {
        _event_id = 0;
    }

    while (_event_id != 0) {
#if defined AT_HANDLER_MUTEX && defined MBED_CONF_RTOS_PRESENT
        _oobCv.wait();
#else
        // Cancel will always work in a single threaded environment
        MBED_ASSERT(false);
#endif // AT_HANDLER_MUTEX
    }

    while (_oobs) {
        struct oob_t *oob = _oobs;
        _oobs = oob->next;
        delete oob;
    }
    if (_output_delimiter) {
        delete [] _output_delimiter;
    }
}

void ATHandler::inc_ref_count()
{
    _ref_count++;
}

void ATHandler::dec_ref_count()
{
    _ref_count--;
}

int ATHandler::get_ref_count()
{
    return _ref_count;
}

FileHandle *ATHandler::get_file_handle()
{
    return _fileHandle;
}

void ATHandler::set_file_handle(FileHandle *fh)
{
    ScopedLock<ATHandler> lock(*this);
    if (_fileHandle) {
        set_is_filehandle_usable(false);
    }
    _fileHandle = fh;
    if (_fileHandle) {
        set_is_filehandle_usable(true);
    }
}

void ATHandler::set_is_filehandle_usable(bool usable)
{
    ScopedLock<ATHandler> lock(*this);
    if (_fileHandle) {
        if (usable) {
            _fileHandle->set_blocking(false);
            _fileHandle->sigio(Callback<void()>(this, &ATHandler::event));
        } else {
            _fileHandle->set_blocking(true); // set back to default state
            _fileHandle->sigio(NULL);
        }
        _is_fh_usable = usable;
    }
}

void ATHandler::set_urc_handler(const char *prefix, Callback<void()> callback)
{
    if (!callback) {
        remove_urc_handler(prefix);
        return;
    }

    if (find_urc_handler(prefix)) {
        tr_warn("URC already added with prefix: %s", prefix);
        return;
    }

    struct oob_t *oob = new struct oob_t;
    size_t prefix_len = strlen(prefix);
    if (prefix_len > _oob_string_max_length) {
        _oob_string_max_length = prefix_len;
        if (_oob_string_max_length > _max_resp_length) {
            _max_resp_length = _oob_string_max_length;
        }
    }

    oob->prefix = prefix;
    oob->prefix_len = prefix_len;
    oob->cb = callback;
    oob->next = _oobs;
    _oobs = oob;
}

void ATHandler::remove_urc_handler(const char *prefix)
{
    struct oob_t *current = _oobs;
    struct oob_t *prev = NULL;
    while (current) {
        if (strcmp(prefix, current->prefix) == 0) {
            if (prev) {
                prev->next = current->next;
            } else {
                _oobs = current->next;
            }
            delete current;
            break;
        }
        prev = current;
        current = prev->next;
    }
}

bool ATHandler::find_urc_handler(const char *prefix)
{
    struct oob_t *oob = _oobs;
    while (oob) {
        if (strcmp(prefix, oob->prefix) == 0) {
            return true;
        }
        oob = oob->next;
    }

    return false;
}

void ATHandler::event()
{
    if (_event_id == 0) {
        _event_id = _queue.call(callback(this, &ATHandler::process_oob));
    }
}

void ATHandler::lock()
{
#if defined AT_HANDLER_MUTEX && defined MBED_CONF_RTOS_PRESENT
    _fileHandleMutex.lock();
#endif
    clear_error();
    _start_time = rtos::Kernel::get_ms_count();
}

void ATHandler::unlock()
{
    if (_is_fh_usable && (_fileHandle->readable() || (_recv_pos < _recv_len))) {
        _event_id = _queue.call(callback(this, &ATHandler::process_oob));
    }
#if defined AT_HANDLER_MUTEX && defined MBED_CONF_RTOS_PRESENT
    _fileHandleMutex.unlock();
#endif
}

nsapi_error_t ATHandler::unlock_return_error()
{
    nsapi_error_t err = _last_err;
    unlock();
    return err;
}

void ATHandler::set_at_timeout(uint32_t timeout_milliseconds, bool default_timeout)
{
    lock();
    if (default_timeout) {
        _previous_at_timeout = timeout_milliseconds;
        _at_timeout = timeout_milliseconds;
    } else if (timeout_milliseconds != _at_timeout) {
        _previous_at_timeout = _at_timeout;
        _at_timeout = timeout_milliseconds;
    }
    unlock();
}

void ATHandler::restore_at_timeout()
{
    lock();
    if (_previous_at_timeout != _at_timeout) {
        _at_timeout = _previous_at_timeout;
    }
    unlock();
}

void ATHandler::process_oob()
{
    ScopedLock<ATHandler> lock(*this);
    if (!_is_fh_usable) {
        tr_debug("process_oob, filehandle is not usable, return...");
        _event_id = 0;
#if defined AT_HANDLER_MUTEX && defined MBED_CONF_RTOS_PRESENT
        _oobCv.notify_all();
#endif
        return;
    }
    if (_fileHandle->readable() || (_recv_pos < _recv_len)) {
        tr_debug("AT OoB readable %d, len %u", _fileHandle->readable(), _recv_len - _recv_pos);
        _current_scope = NotSet;
        uint32_t timeout = _at_timeout;
        while (true) {
            _at_timeout = timeout;
            if (match_urc()) {
                if (!(_fileHandle->readable() || (_recv_pos < _recv_len))) {
                    break; // we have nothing to read anymore
                }
            } else if (mem_str(_recv_buff, _recv_len, CRLF, CRLF_LENGTH)) { // If no match found, look for CRLF and consume everything up to CRLF
                _at_timeout = PROCESS_URC_TIME;
                consume_to_tag(CRLF, true);
            } else {
                _at_timeout = PROCESS_URC_TIME;
                if (!fill_buffer()) {
                    reset_buffer(); // consume anything that could not be handled
                    break;
                }
            }
            _start_time = rtos::Kernel::get_ms_count();
        }
        _at_timeout = timeout;
        tr_debug("AT OoB done");
    }
    _event_id = 0;
#if defined AT_HANDLER_MUTEX && defined MBED_CONF_RTOS_PRESENT
    _oobCv.notify_all();
#endif
}

void ATHandler::reset_buffer()
{
    _recv_pos = 0;
    _recv_len = 0;
}

void ATHandler::rewind_buffer()
{
    if (_recv_pos > 0 && _recv_len >= _recv_pos) {
        _recv_len -= _recv_pos;
        // move what is not read to beginning of buffer
        memmove(_recv_buff, _recv_buff + _recv_pos, _recv_len);
        _recv_pos = 0;
    }
}

int ATHandler::poll_timeout(bool wait_for_timeout)
{
    int timeout;
    if (wait_for_timeout) {
        uint64_t now = rtos::Kernel::get_ms_count();
        if (now >= _start_time + _at_timeout) {
            timeout = 0;
        } else if (_start_time + _at_timeout - now > INT_MAX) {
            timeout = INT_MAX;
        } else {
            timeout = _start_time + _at_timeout - now;
        }
    } else {
        timeout = 0;
    }
    return timeout;
}

bool ATHandler::fill_buffer(bool wait_for_timeout)
{
    // Reset buffer when full
    if (sizeof(_recv_buff) == _recv_len) {
        tr_error("AT overflow");
        debug_print(_recv_buff, _recv_len, AT_ERR);
        reset_buffer();
    }

    pollfh fhs;
    fhs.fh = _fileHandle;
    fhs.events = POLLIN;
    int count = poll(&fhs, 1, poll_timeout(wait_for_timeout));
    if (count > 0 && (fhs.revents & POLLIN)) {
        ssize_t len = _fileHandle->read(_recv_buff + _recv_len, sizeof(_recv_buff) - _recv_len);
        if (len > 0) {
            debug_print(_recv_buff + _recv_len, len, AT_RX);
            _recv_len += len;
            return true;
        }
    }

    return false;
}

int ATHandler::get_char()
{
    if (_recv_pos == _recv_len) {
        reset_buffer(); // try to read as much as possible
        if (!fill_buffer()) {
            tr_warn("AT timeout");
            set_error(NSAPI_ERROR_DEVICE_ERROR);
            return -1; // timeout to read
        }
    }

    return _recv_buff[_recv_pos++];
}

void ATHandler::skip_param(uint32_t count)
{
    if (!ok_to_proceed() || !_stop_tag || _stop_tag->found) {
        return;
    }

    for (uint32_t i = 0; (i < count && !_stop_tag->found); i++) {
        size_t match_pos = 0;
        while (true) {
            int c = get_char();
            if (c == -1) {
                set_error(NSAPI_ERROR_DEVICE_ERROR);
                return;
            } else if (c == _delimiter) {
                break;
            } else if (_stop_tag->len && c == _stop_tag->tag[match_pos]) {
                match_pos++;
                if (match_pos == _stop_tag->len) {
                    _stop_tag->found = true;
                    break;
                }
            } else if (match_pos) {
                match_pos = 0;
                if (c == _stop_tag->tag[match_pos]) {
                    match_pos++;
                }
            }
        }
    }
    return;
}

void ATHandler::skip_param(ssize_t len, uint32_t count)
{
    if (!ok_to_proceed() || !_stop_tag || _stop_tag->found) {
        return;
    }

    for (uint32_t i = 0; i < count; i++) {
        ssize_t read_len = 0;
        while (read_len < len) {
            int c = get_char();
            if (c == -1) {
                set_error(NSAPI_ERROR_DEVICE_ERROR);
                return;
            }
            read_len++;
        }
    }
    return;
}

ssize_t ATHandler::read_bytes(uint8_t *buf, size_t len)
{
    if (!ok_to_proceed()) {
        return -1;
    }

    bool debug_on = _debug_on;
    bool disabled_debug = false;
    if (len > DEBUG_MAXLEN) {
        _debug_on = false;
        disabled_debug = true;
    }

    size_t read_len = 0;
    for (; read_len < len; read_len++) {
        int c = get_char();
        if (c == -1) {
            set_error(NSAPI_ERROR_DEVICE_ERROR);
            _debug_on = debug_on;
            return -1;
        }
        buf[read_len] = c;
    }

#if MBED_CONF_CELLULAR_DEBUG_AT
    if (debug_on && disabled_debug) {
        tr_info("read_bytes trace suppressed (total length %d)", read_len);
    }
#else
    (void)disabled_debug; // Remove compiler warning
#endif

    _debug_on = debug_on;
    return read_len;
}

ssize_t ATHandler::read_string(char *buf, size_t size, bool read_even_stop_tag)
{
    if (!ok_to_proceed() || !_stop_tag || (_stop_tag->found && read_even_stop_tag == false)) {
        return -1;
    }

    unsigned int len = 0;
    size_t match_pos = 0;
    bool delimiter_found = false;

    for (; len < (size - 1 + match_pos); len++) {
        int c = get_char();
        if (c == -1) {
            set_error(NSAPI_ERROR_DEVICE_ERROR);
            return -1;
        } else if (c == _delimiter) {
            buf[len] = '\0';
            delimiter_found = true;
            break;
        } else if (c == '\"') {
            match_pos = 0;
            len--;
            continue;
        } else if (_stop_tag->len && c == _stop_tag->tag[match_pos]) {
            match_pos++;
            if (match_pos == _stop_tag->len) {
                _stop_tag->found = true;
                // remove tag from string if it was matched
                len -= (_stop_tag->len - 1);
                buf[len] = '\0';
                break;
            }
        } else if (match_pos) {
            match_pos = 0;
            if (c == _stop_tag->tag[match_pos]) {
                match_pos++;
            }
        }

        buf[len] = c;
    }

    if (len && (len == size - 1 + match_pos)) {
        buf[len] = '\0';
    }

    // Consume to delimiter or stop_tag
    if (!delimiter_found && !_stop_tag->found) {
        match_pos = 0;
        while (1) {
            int c = get_char();
            if (c == -1) {
                set_error(NSAPI_ERROR_DEVICE_ERROR);
                break;
            } else if (c == _delimiter) {
                break;
            } else if (_stop_tag->len && c == _stop_tag->tag[match_pos]) {
                match_pos++;
                if (match_pos == _stop_tag->len) {
                    _stop_tag->found = true;
                    break;
                }
            }
        }
    }

    return len;
}

ssize_t ATHandler::read_hex_string(char *buf, size_t size)
{
    if (!ok_to_proceed() || !_stop_tag ||  _stop_tag->found) {
        return -1;
    }

    size_t match_pos = 0;

    consume_char('\"');

    if (_last_err) {
        return -1;
    }

    size_t read_idx = 0;
    size_t buf_idx = 0;
    char hexbuf[2];

    bool debug_on = _debug_on;
    bool disabled_debug = false;
    if (size > DEBUG_MAXLEN) {
        _debug_on = false;
        disabled_debug = true;
    }

    for (; read_idx < size * 2 + match_pos; read_idx++) {
        int c = get_char();

        if (match_pos) {
            buf_idx++;
        } else {
            buf_idx = read_idx / 2;
        }

        if (c == -1) {
            set_error(NSAPI_ERROR_DEVICE_ERROR);
            return -1;
        }
        if (c == _delimiter) {
            break;
        } else if (c == '\"') {
            match_pos = 0;
            read_idx--;
            continue;
        } else if (_stop_tag->len && c == _stop_tag->tag[match_pos]) {
            match_pos++;
            if (match_pos == _stop_tag->len) {
                _stop_tag->found = true;
                // remove tag from string if it was matched
                buf_idx -= (_stop_tag->len - 1);
                break;
            }
        } else if (match_pos) {
            match_pos = 0;
            if (c == _stop_tag->tag[match_pos]) {
                match_pos++;
            }
        }

        if (match_pos) {
            buf[buf_idx] = c;
        } else {
            hexbuf[read_idx % 2] = c;
            if (read_idx % 2 == 1) {
                hex_to_char(hexbuf, *(buf + buf_idx));
            }
        }
    }

    if (read_idx && (read_idx == size * 2 + match_pos)) {
        buf_idx++;
    }

#if MBED_CONF_CELLULAR_DEBUG_AT
    if (debug_on && disabled_debug) {
        tr_info("read_hex_string trace suppressed (total length %d)", buf_idx);
    }
#else
    (void)disabled_debug; // Remove compiler warning
#endif

    _debug_on = debug_on;

    return buf_idx;
}

int32_t ATHandler::read_int()
{
    if (!ok_to_proceed() || !_stop_tag ||  _stop_tag->found) {
        return -1;
    }

    char buff[BUFF_SIZE];
    if (read_string(buff, sizeof(buff)) == 0) {
        return -1;
    }

    errno = 0;
    long result = std::strtol(buff, NULL, 10);
    if ((result == LONG_MIN || result == LONG_MAX) && errno == ERANGE) {
        return -1; // overflow/underflow
    }
    if (result < 0) {
        return -1; // negative values are unsupported
    }
    if (*buff == '\0') {
        return -1; // empty string
    }
    return (int32_t) result;
}

void ATHandler::set_delimiter(char delimiter)
{
    _delimiter = delimiter;
}

void ATHandler::set_default_delimiter()
{
    _delimiter = DEFAULT_DELIMITER;
}

void ATHandler::use_delimiter(bool use_delimiter)
{
    _use_delimiter = use_delimiter;
}

void ATHandler::set_tag(tag_t *tag_dst, const char *tag_seq)
{
    if (tag_seq) {
        size_t tag_len = strlen(tag_seq);
        memcpy(tag_dst->tag, tag_seq, tag_len);
        tag_dst->tag[tag_len] = '\0';
        tag_dst->len = tag_len;
        tag_dst->found = false;
    } else {
        _stop_tag = NULL;
    }
}

void ATHandler::set_stop_tag(const char *stop_tag_seq)
{
    if (_last_err || !_stop_tag) {
        return;
    }

    set_tag(_stop_tag, stop_tag_seq);
}

void ATHandler::set_scope(ScopeType scope_type)
{
    if (_current_scope != scope_type) {
        _current_scope = scope_type;
        switch (_current_scope) {
            case RespType:
                _stop_tag = &_resp_stop;
                _stop_tag->found = false;
                break;
            case InfoType:
                _stop_tag = &_info_stop;
                _stop_tag->found = false;
                consume_char(' ');
                break;
            case ElemType:
                _stop_tag = &_elem_stop;
                _stop_tag->found = false;
                break;
            case NotSet:
                _stop_tag = NULL;
                return;
            default:
                break;
        }
    }
}

// should match from recv_pos?
bool ATHandler::match(const char *str, size_t size)
{
    rewind_buffer();

    if ((_recv_len - _recv_pos) < size) {
        return false;
    }

    if (str && memcmp(_recv_buff + _recv_pos, str, size) == 0) {
        // consume matching part
        _recv_pos += size;
        return true;
    }
    return false;
}

bool ATHandler::match_urc()
{
    rewind_buffer();
    size_t prefix_len = 0;
    for (struct oob_t *oob = _oobs; oob; oob = oob->next) {
        prefix_len = oob->prefix_len;
        if (_recv_len >= prefix_len) {
            if (match(oob->prefix, prefix_len)) {
                set_scope(InfoType);
                if (oob->cb) {
                    oob->cb();
                }
                information_response_stop();
                return true;
            }
        }
    }
    return false;
}

bool ATHandler::match_error()
{
    if (match(CME_ERROR, CME_ERROR_LENGTH)) {
        at_error(true, DeviceErrorTypeErrorCME);
        return true;
    } else if (match(CMS_ERROR, CMS_ERROR_LENGTH)) {
        at_error(true, DeviceErrorTypeErrorCMS);
        return true;
    } else if (match(ERROR_, ERROR_LENGTH)) {
        at_error(false, DeviceErrorTypeNoError);
        return true;
    }

    return false;
}

void ATHandler::clear_error()
{
    _last_err = NSAPI_ERROR_OK;
    _last_at_err.errCode = 0;
    _last_at_err.errType = DeviceErrorTypeNoError;
    _last_3gpp_error = 0;
}

nsapi_error_t ATHandler::get_last_error() const
{
    return _last_err;
}

device_err_t ATHandler::get_last_device_error() const
{
    return _last_at_err;
}

void ATHandler::set_error(nsapi_error_t err)
{
    if (err != NSAPI_ERROR_OK) {
        tr_debug("AT error %d", err);
    }
    if (_last_err == NSAPI_ERROR_OK) {
        _last_err = err;
    }

}

int ATHandler::get_3gpp_error()
{
    return _last_3gpp_error;
}

void ATHandler::set_3gpp_error(int err, DeviceErrorType error_type)
{
    if (_last_3gpp_error) { // don't overwrite likely root cause error
        return;
    }

    if (error_type == DeviceErrorTypeErrorCMS && err < 128) {
        // CMS errors 0-127 maps straight to 3GPP errors
        _last_3gpp_error = err;
    } else {
        for (size_t i = 0; i < sizeof(map_3gpp_errors) / sizeof(map_3gpp_errors[0]); i++) {
            if (map_3gpp_errors[i][0] == err) {
                _last_3gpp_error = map_3gpp_errors[i][1];
                tr_error("AT3GPP error code %d", get_3gpp_error());
                break;
            }
        }
    }
}

void ATHandler::at_error(bool error_code_expected, DeviceErrorType error_type)
{
    if (error_code_expected && (error_type == DeviceErrorTypeErrorCMS || error_type == DeviceErrorTypeErrorCME)) {
        set_scope(InfoType);
        int32_t err = read_int();

        if (err != -1) {
            set_3gpp_error(err, error_type);
            _last_at_err.errCode = err;
            _last_at_err.errType = error_type;
            tr_warn("AT error code %ld", err);
        } else {
            tr_warn("ATHandler ERROR reading failed");
        }
    }

    set_error(NSAPI_ERROR_DEVICE_ERROR);
}

void ATHandler::resp(const char *prefix, bool check_urc)
{
    _prefix_matched = false;
    _urc_matched = false;
    _error_found = false;

    while (!get_last_error()) {

        (void)match(CRLF, CRLF_LENGTH);

        if (match(OK, OK_LENGTH)) {
            set_scope(RespType);
            _stop_tag->found = true;
            return;
        }

        if (match_error()) {
            _error_found = true;
            return;
        }

        if (prefix && strlen(prefix) && match(prefix, strlen(prefix))) {
            _prefix_matched = true;
            return;
        }

        if (check_urc && match_urc()) {
            _urc_matched = true;
            clear_error();
            continue;
        }

        // If no match found, look for CRLF and consume everything up to and including CRLF
        if (mem_str(_recv_buff, _recv_len, CRLF, CRLF_LENGTH)) {
            // If no prefix, return on CRLF - means data to read
            if (!prefix || (prefix && !strlen(prefix))) {
                return;
            }
            consume_to_tag(CRLF, true);
        } else {
            // If no prefix, no CRLF and no more chance to match for OK, ERROR or URC(since max resp length is already in buffer)
            // return so data could be read
            if ((!prefix || (prefix && !strlen(prefix))) && ((_recv_len - _recv_pos) >= _max_resp_length)) {
                return;
            }
            if (!fill_buffer()) {
                // if we don't get any match and no data within timeout, set an error to indicate need for recovery
                set_error(NSAPI_ERROR_DEVICE_ERROR);
            }
        }
    }

    return;
    // something went wrong so application need to recover and retry
}

void ATHandler::resp_start(const char *prefix, bool stop)
{
    if (!ok_to_proceed()) {
        return;
    }

    set_scope(NotSet);
    // Try get as much data as possible
    rewind_buffer();
    (void)fill_buffer(false);

    if (prefix) {
        MBED_ASSERT(strlen(prefix) < BUFF_SIZE);
        strcpy(_info_resp_prefix, prefix); // copy prefix so we can later use it without having to provide again for info_resp
    }

    set_scope(RespType);

    resp(prefix, true);

    if (!stop && prefix && _prefix_matched) {
        set_scope(InfoType);
    }
}

// check urc because of error as urc
bool ATHandler::info_resp()
{
    if (!ok_to_proceed() || _resp_stop.found) {
        return false;
    }

    if (_prefix_matched) {
        _prefix_matched = false;
        return true;
    }

    // If coming here after another info response was started(looping), stop the previous one.
    // Trying to handle stopping in this level instead of doing it in upper level.
    if (get_scope() == InfoType) {
        information_response_stop();
    }

    resp(_info_resp_prefix, false);

    if (_prefix_matched) {
        set_scope(InfoType);
        _prefix_matched = false;
        return true;
    }

    // On mismatch go to response scope
    set_scope(RespType);
    return false;
}

bool ATHandler::info_elem(char start_tag)
{
    if (!ok_to_proceed()) {
        return false;
    }

    // If coming here after another info response element was started(looping), stop the previous one.
    // Trying to handle stopping in this level instead of doing it in upper level.
    if (get_scope() == ElemType) {
        information_response_element_stop();
    }

    consume_char(_delimiter);

    if (consume_char(start_tag)) {
        _prefix_matched = true;
        set_scope(ElemType);
        return true;
    }

    // On mismatch go to information response scope
    set_scope(InfoType);
    return false;
}

bool ATHandler::consume_char(char ch)
{
    int read_char = get_char();
    if (read_char == -1) {
        return false;
    }
    // If we read something else than ch, recover it
    if (read_char != ch) {
        _recv_pos--;
        return false;
    }
    return true;
}

bool ATHandler::consume_to_tag(const char *tag, bool consume_tag)
{
    size_t match_pos = 0;
    size_t tag_length = strlen(tag);

    while (true) {
        int c = get_char();
        if (c == -1) {
            tr_debug("consume_to_tag not found");
            return false;
        }
        if (c == tag[match_pos]) {
            match_pos++;
        } else if (match_pos != 0) {
            match_pos = 0;
            if (c == tag[match_pos]) {
                match_pos++;
            }
        }
        if (match_pos == tag_length) {
            break;
        }
    }

    if (!consume_tag) {
        _recv_pos -= tag_length;
    }
    return true;
}

bool ATHandler::consume_to_stop_tag()
{
    if (!_stop_tag || (_stop_tag && _stop_tag->found) || _error_found) {
        return true;
    }

    if (!_is_fh_usable) {
        _last_err = NSAPI_ERROR_BUSY;
        return true;
    }

    if (consume_to_tag((const char *)_stop_tag->tag, true)) {
        return true;
    }

    tr_debug("AT stop tag not found");
    set_error(NSAPI_ERROR_DEVICE_ERROR);
    return false;
}

// consume by size needed?

void ATHandler::resp_stop()
{
    if (_is_fh_usable) {
        // Do not return on error so that we can consume whatever there is in the buffer

        if (_current_scope == ElemType) {
            information_response_element_stop();
            set_scope(InfoType);
        }

        if (_current_scope == InfoType) {
            information_response_stop();
        }

        // Go for response stop_tag
        if (_stop_tag && !_stop_tag->found && !_error_found) {
            // Check for URC for every new line
            while (!get_last_error()) {

                if (match(_stop_tag->tag, _stop_tag->len)) {
                    break;
                }

                if (match_urc()) {
                    continue;
                }

                // If no URC nor stop_tag found, look for CRLF and consume everything up to and including CRLF
                if (mem_str(_recv_buff, _recv_len, CRLF, CRLF_LENGTH)) {
                    consume_to_tag(CRLF, true);
                    // If stop tag is CRLF we have to stop reading/consuming the buffer
                    if (!strncmp(CRLF, _stop_tag->tag, _stop_tag->len)) {
                        break;
                    }
                    // If no URC nor CRLF nor stop_tag -> fill buffer
                } else {
                    if (!fill_buffer()) {
                        // if we don't get any match and no data within timeout, set an error to indicate need for recovery
                        set_error(NSAPI_ERROR_DEVICE_ERROR);
                    }
                }
            }
        }
    } else {
        _last_err = NSAPI_ERROR_BUSY;
    }

    set_scope(NotSet);

    // Restore stop tag to OK
    set_tag(&_resp_stop, OK);
    // Reset info resp prefix
    memset(_info_resp_prefix, 0, sizeof(_info_resp_prefix));

    _last_response_stop = rtos::Kernel::get_ms_count();
}

void ATHandler::information_response_stop()
{
    if (consume_to_stop_tag()) {
        set_scope(RespType);
    }
}

void ATHandler::information_response_element_stop()
{
    if (consume_to_stop_tag()) {
        set_scope(InfoType);
    }
}

ATHandler::ScopeType ATHandler::get_scope()
{
    return _current_scope;
}

const char *ATHandler::mem_str(const char *dest, size_t dest_len, const char *src, size_t src_len)
{
    if (dest_len >= src_len) {
        for (size_t i = 0; i < dest_len - src_len + 1; ++i) {
            if (memcmp(dest + i, src, src_len) == 0) {
                return dest + i;
            }
        }
    }
    return NULL;
}

void ATHandler::cmd_start(const char *cmd)
{
    if (!ok_to_proceed()) {
        return;
    }

    if (_at_send_delay) {
        rtos::ThisThread::sleep_until(_last_response_stop + _at_send_delay);
    }

    (void)write(cmd, strlen(cmd));

    _cmd_start = true;
}

void ATHandler::handle_args(const char *format, std::va_list list)
{
    while (*format != '\0') {
        if (*format == 'd') {
            int32_t i = va_arg(list, int32_t);
            write_int(i);
        } else if (*format == 's') {
            char *str = (char *)va_arg(list, char *);
            write_string(str);
        } else if (*format == 'b') {
            uint8_t *bytes = va_arg(list, uint8_t *);
            int size = va_arg(list, int);
            write_bytes(bytes, size);
        }
        ++format;
    }
}

void ATHandler::handle_start(const char *cmd, const char *cmd_chr)
{
    int len = 0;
    memcpy(_cmd_buffer, "AT", 2);
    len += 2;
    int cmd_char_len = 0;
    if (cmd_chr) {
        cmd_char_len = strlen(cmd_chr);
    }
    MBED_ASSERT((3 + strlen(cmd) + cmd_char_len) < BUFF_SIZE);

    memcpy(_cmd_buffer + len, cmd, strlen(cmd));
    len += strlen(cmd);

    if (cmd_char_len) {
        memcpy(_cmd_buffer + len, cmd_chr, cmd_char_len);
        len += cmd_char_len;
    }
    _cmd_buffer[len] = '\0';

    const bool temp_state = get_debug();
    set_debug(true);

    cmd_start(_cmd_buffer);

    set_debug(temp_state);
}

void ATHandler::cmd_start_stop(const char *cmd, const char *cmd_chr, const char *format, ...)
{
    handle_start(cmd, cmd_chr);

    va_list list;
    va_start(list, format);
    handle_args(format, list);
    va_end(list);

    cmd_stop();
}

nsapi_error_t ATHandler::at_cmd_str(const char *cmd, const char *cmd_chr, char *resp_buf, size_t buf_size, const char *format, ...)
{
    MBED_ASSERT(strlen(cmd) < BUFF_SIZE);
    lock();

    handle_start(cmd, cmd_chr);

    va_list list;
    va_start(list, format);
    handle_args(format, list);
    va_end(list);

    cmd_stop();

    if (strlen(cmd) > 0) {
        memcpy(_cmd_buffer, cmd, strlen(cmd));
        _cmd_buffer[strlen(cmd)] = ':';
        _cmd_buffer[strlen(cmd) + 1] = '\0';
        resp_start(_cmd_buffer);
    } else {
        resp_start();
    }

    resp_buf[0] = '\0';
    read_string(resp_buf, buf_size);
    resp_stop();
    return unlock_return_error();
}

nsapi_error_t ATHandler::at_cmd_int(const char *cmd, const char *cmd_chr, int &resp, const char *format, ...)
{
    lock();

    handle_start(cmd, cmd_chr);

    va_list list;
    va_start(list, format);
    handle_args(format, list);
    va_end(list);

    cmd_stop();
    char temp[16];
    size_t len = strlen(cmd);
    memcpy(temp, cmd, len);
    temp[len] = ':';
    temp[len + 1] = '\0';
    resp_start(temp);

    resp = read_int();
    resp_stop();
    return unlock_return_error();
}

nsapi_error_t ATHandler::at_cmd_discard(const char *cmd, const char *cmd_chr, const char *format, ...)
{
    lock();

    handle_start(cmd, cmd_chr);

    va_list list;
    va_start(list, format);
    handle_args(format, list);
    va_end(list);

    cmd_stop_read_resp();
    return unlock_return_error();
}

void ATHandler::write_int(int32_t param)
{
    // do common checks before sending subparameter
    if (check_cmd_send() == false) {
        return;
    }

    // write the integer subparameter
    const int32_t str_len = 12;
    char number_string[str_len];
    int32_t result = sprintf(number_string, "%" PRIi32, param);
    if (result > 0 && result < str_len) {
        (void)write(number_string, strlen(number_string));
    }
}

void ATHandler::write_string(const char *param, bool useQuotations)
{
    // do common checks before sending subparameter
    if (check_cmd_send() == false) {
        return;
    }

    // we are writing string, surround it with quotes
    if (useQuotations && write("\"", 1) != 1) {
        return;
    }

    (void)write(param, strlen(param));

    if (useQuotations) {
        // we are writing string, surround it with quotes
        (void)write("\"", 1);
    }
}

void ATHandler::cmd_stop()
{
    if (!ok_to_proceed()) {
        return;
    }
    // Finish with CR
    (void)write(_output_delimiter, strlen(_output_delimiter));
}

void ATHandler::cmd_stop_read_resp()
{
    cmd_stop();
    resp_start();
    resp_stop();
}

size_t ATHandler::write_bytes(const uint8_t *data, size_t len)
{
    if (!ok_to_proceed()) {
        return 0;
    }

    return write(data, len);
}

size_t ATHandler::write(const void *data, size_t len)
{
    pollfh fhs;
    fhs.fh = _fileHandle;
    fhs.events = POLLOUT;
    size_t write_len = 0;

#if MBED_CONF_CELLULAR_DEBUG_AT
    bool suppress_traced = false;
#endif

    for (; write_len < len;) {
        int count = poll(&fhs, 1, poll_timeout());
        if (count <= 0 || !(fhs.revents & POLLOUT)) {
            set_error(NSAPI_ERROR_DEVICE_ERROR);
            return 0;
        }
        ssize_t ret = _fileHandle->write((uint8_t *)data + write_len, len - write_len);
        if (ret < 0) {
            set_error(NSAPI_ERROR_DEVICE_ERROR);
            return 0;
        }

#if MBED_CONF_CELLULAR_DEBUG_AT
        if (write_len + ret > DEBUG_MAXLEN) {
            if (_debug_on && !suppress_traced) {
                debug_print((char *)data + write_len, DEBUG_MAXLEN, AT_TX);
                tr_debug("write trace suppressed (total length %d)", len);
            }
            suppress_traced = true;
        } else {
            debug_print((char *)data + write_len, ret, AT_TX);
        }
#endif

        write_len += (size_t)ret;
    }

    return write_len;
}

// do common checks before sending subparameters
bool ATHandler::check_cmd_send()
{
    if (!ok_to_proceed()) {
        return false;
    }


    // Don't write delimiter if flag was set so

    // Don't write delimiter if this is the first subparameter
    if (_cmd_start) {
        _cmd_start = false;
    } else {
        if (_use_delimiter && write(&_delimiter, 1) != 1) {
            // writing of delimiter failed, return. write() already have set the _last_err
            return false;
        }
    }

    return true;
}

void ATHandler::flush()
{
    if (!_is_fh_usable) {
        _last_err = NSAPI_ERROR_BUSY;
        return;
    }
    tr_debug("AT flush");
    reset_buffer();
    while (fill_buffer(false)) {
        reset_buffer();
    }
}

void ATHandler::debug_print(const char *p, int len, ATType type)
{
#if MBED_CONF_CELLULAR_DEBUG_AT
    if (_debug_on) {
        const int buf_size = len * 4 + 1; // x4 -> reserve space for extra characters, +1 -> terminating null
        char *buffer = new char [buf_size];
        if (buffer) {
            memset(buffer, 0, buf_size);

            char *pbuf = buffer;
            for (ssize_t i = 0; i < len; i++) {
                const char c = *p++;
                if (isprint(c)) {
                    *pbuf++ = c;
                } else if (c == '\r') {
                    sprintf(pbuf, "<cr>");
                    pbuf += 4;
                } else if (c == '\n') {
                    sprintf(pbuf, "<ln>");
                    pbuf += 4;
                } else {
                    sprintf(pbuf, "<%02X>", c);
                    pbuf += 4;
                }
            }
            MBED_ASSERT((int)(pbuf - buffer) <= buf_size); // Check for buffer overflow

            if (type == AT_RX) {
                tr_info("AT RX (%2d): %s", len, buffer);
            } else if (type == AT_TX) {
                tr_info("AT TX (%2d): %s", len, buffer);
            } else {
                tr_info("AT ERR (%2d): %s", len, buffer);
            }

            delete [] buffer;
        } else {
            tr_error("AT trace unable to allocate buffer!");
        }
    }
#endif // MBED_CONF_CELLULAR_DEBUG_AT
}

bool ATHandler::sync(int timeout_ms)
{
    if (!_is_fh_usable) {
        _last_err = NSAPI_ERROR_BUSY;
        return false;
    }
    tr_debug("AT sync");
    lock();
    uint32_t timeout = _at_timeout;
    _at_timeout = timeout_ms;
    // poll for 10 seconds
    for (int i = 0; i < 10; i++) {
        // For sync use an AT command that is supported by all modems and likely not used frequently,
        // especially a common response like OK could be response to previous request.
        clear_error();
        _start_time = rtos::Kernel::get_ms_count();
        cmd_start("AT+CMEE?");
        cmd_stop();
        resp_start();
        set_stop_tag("+CMEE:");
        consume_to_stop_tag();
        set_stop_tag(OK);
        consume_to_stop_tag();
        if (!_last_err) {
            _at_timeout = timeout;
            unlock();
            return true;
        }
    }
    tr_error("AT sync failed");
    _at_timeout = timeout;
    unlock();
    return false;
}

void ATHandler::set_send_delay(uint16_t send_delay)
{
    _at_send_delay = send_delay;
}

void ATHandler::write_hex_string(char *str, size_t size)
{
    // do common checks before sending subparameter
    if (check_cmd_send() == false) {
        return;
    }

    (void) write("\"", 1);
    char hexbuf[2];
    for (size_t i = 0; i < size; i++) {
        hexbuf[0] = hex_values[((str[i]) >> 4) & 0x0F];
        hexbuf[1] = hex_values[(str[i]) & 0x0F];
        write(hexbuf, 2);
    }
    (void) write("\"", 1);
}

void ATHandler::set_baud(int baud_rate)
{
    static_cast<UARTSerial *>(_fileHandle)->set_baud(baud_rate);
}

