/*
 * 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.
 */

#if MBED_CONF_CELLULAR_USE_SMS

#include <time.h>
#include <stdlib.h>
#include <stdio.h>
#include "ThisThread.h"
#include "AT_CellularSMS.h"
#include "CellularUtil.h"
#include "CellularLog.h"

using namespace mbed_cellular_util;
using namespace mbed;
using namespace std;
using namespace rtos;

#define CTRL_Z  "\x1a"
#define ESC     "\x1b"

const uint8_t SMS_STATUS_SIZE = 12 + 1;
const uint8_t FIRST_OCTET_DELIVER_SUBMIT = 17;
const uint8_t TP_VALIDITY_PERIOD_24_HOURS = 167;
const uint8_t TP_PROTOCOL_IDENTIFIER = 0;
const uint8_t SMS_DATA_CODING_SCHEME = 0;

const uint8_t SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE = 134;
const uint8_t SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE = 153;
#define NVAM '?' // Not Valid ascii, ISO-8859-1 mark

// mapping table from 7-bit GSM to ascii (ISO-8859-1)
static const unsigned char gsm_to_ascii[] = {
    64,     // 0
    163,    // 1
    36,     // 2
    165,    // 3
    232,    // 4
    233,    // 5
    249,    // 6
    236,    // 7
    242,    // 8
    199,    // 9
    10,     // 10
    216,    // 11
    248,    // 12
    13,     // 13
    197,    // 14
    229,    // 15
    NVAM,   // 16
    95,     // 17
    NVAM,   // 18
    NVAM,   // 19
    NVAM,   // 20
    NVAM,   // 21
    NVAM,   // 22
    NVAM,   // 23
    NVAM,   // 24
    NVAM,   // 25
    NVAM,   // 26
    27,     // 27
    198,    // 28
    230,    // 29
    223,    // 30
    201,    // 31
    32,     // 32
    33,     // 33
    34,     // 34
    35,     // 35
    164,    // 36
    37,     // 37
    38,     // 38
    39,     // 39
    40,     // 40
    41,     // 41
    42,     // 42
    43,     // 43
    44,     // 44
    45,     // 45
    46,     // 46
    47,     // 47
    48,     // 48
    49,     // 49
    50,     // 50
    51,     // 51
    52,     // 52
    53,     // 53
    54,     // 54
    55,     // 55
    56,     // 56
    57,     // 57
    58,     // 58
    59,     // 59
    60,     // 60
    61,     // 61
    62,     // 62
    63,     // 63
    161,    // 64
    65,     // 65
    66,     // 66
    67,     // 67
    68,     // 68
    69,     // 69
    70,     // 70
    71,     // 71
    72,     // 72
    73,     // 73
    74,     // 74
    75,     // 75
    76,     // 76
    77,     // 77
    78,     // 78
    79,     // 79
    80,     // 80
    81,     // 81
    82,     // 82
    83,     // 83
    84,     // 84
    85,     // 85
    86,     // 86
    87,     // 87
    88,     // 88
    89,     // 89
    90,     // 90
    196,    // 91
    214,    // 92
    209,    // 93
    220,    // 94
    167,    // 95
    191,    // 96
    97,     // 97
    98,     // 98
    99,     // 99
    100,    // 100
    101,    // 101
    102,    // 102
    103,    // 103
    104,    // 104
    105,    // 105
    106,    // 106
    107,    // 107
    108,    // 108
    109,    // 109
    110,    // 110
    111,    // 111
    112,    // 112
    113,    // 113
    114,    // 114
    115,    // 115
    116,    // 116
    117,    // 117
    118,    // 118
    119,    // 119
    120,    // 120
    121,    // 121
    122,    // 122
    228,    // 123
    246,    // 124
    241,    // 125
    252,    // 126
    224     // 127
};

const int GSM_TO_ASCII_TABLE_SIZE = sizeof(gsm_to_ascii) / sizeof(gsm_to_ascii[0]);

AT_CellularSMS::AT_CellularSMS(ATHandler &at, AT_CellularDevice &device) : _cb(0), _mode(CellularSMSMmodeText),
    _use_8bit_encoding(false), _sim_wait_time(0), _sms_message_ref_number(1),
    _sms_info(NULL), _at(at), _device(device)
{
}

AT_CellularSMS::~AT_CellularSMS()
{
}

void AT_CellularSMS::cmt_urc()
{
    tr_debug("CMT_URC called");
    //+CMT: <oa>,[<alpha>],<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data>
    (void)_at.consume_to_stop_tag();
    // call user defined callback function
    if (_cb) {
        _cb();
    } else {
        tr_warn("cmt_urc, no user defined callback for receiving sms!");
    }
}

void AT_CellularSMS::cmti_urc()
{
    //+CMTI: <mem>,<index>,
    tr_debug("CMTI_URC called");
    // call user defined callback function
    if (_cb) {
        _cb();
    } else {
        tr_warn("cmti_urc, no user defined callback for receiving sms!");
    }
}

nsapi_error_t AT_CellularSMS::set_cnmi()
{
    if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CNMI)) {
        return NSAPI_ERROR_UNSUPPORTED;
    }

    return _at.at_cmd_discard("+CNMI", "=2,1");
}

nsapi_error_t AT_CellularSMS::set_cmgf(int msg_format)
{
    if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CMGF)) {
        return NSAPI_ERROR_UNSUPPORTED;
    }

    return _at.at_cmd_discard("+CMGF", "=", "%d", msg_format);
}

nsapi_error_t AT_CellularSMS::set_csmp(int fo, int vp, int pid, int dcs)
{
    if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CSMP)) {
        return NSAPI_ERROR_UNSUPPORTED;
    }

    return _at.at_cmd_discard("+CSMP", "=", "%d%d%d%d", fo, vp, pid, dcs);
}

nsapi_error_t AT_CellularSMS::set_csdh(int show_header)
{
    if (!_device.get_property(AT_CellularDevice::PROPERTY_AT_CSDH)) {
        return NSAPI_ERROR_UNSUPPORTED;
    }

    return _at.at_cmd_discard("+CSDH", "=", "%d", show_header);
}

nsapi_error_t AT_CellularSMS::initialize(CellularSMSMmode mode,
                                         CellularSMSEncoding encoding)
{
    _use_8bit_encoding = (encoding == CellularSMSEncoding8Bit);

    _at.set_urc_handler("+CMTI:", callback(this, &AT_CellularSMS::cmti_urc));
    _at.set_urc_handler("+CMT:", callback(this, &AT_CellularSMS::cmt_urc));

    _at.lock();
    set_cnmi();     //set new SMS indication
    set_cmgf(mode); //set message format/PDU

    if (mode == CellularSMSMmodeText) {
        set_csmp(FIRST_OCTET_DELIVER_SUBMIT, TP_VALIDITY_PERIOD_24_HOURS, TP_PROTOCOL_IDENTIFIER,
                 SMS_DATA_CODING_SCHEME);    //set Set Text Mode Parameters(default values for SMS-SUBMIT and RECEIVE)
        set_csdh(1);//set header extra info as it's needed
    }

    _mode = mode;

    return _at.unlock_return_error();
}

void AT_CellularSMS::set_extra_sim_wait_time(int sim_wait_time)
{
    _sim_wait_time = sim_wait_time;
}

char *AT_CellularSMS::create_pdu(const char *phone_number, const char *message, uint8_t message_length, uint8_t msg_parts,
                                 uint8_t msg_part_number, uint8_t &header_size)
{
    int totalPDULength = 0;
    const bool is_number_international = (phone_number[0] == '+');
    if (is_number_international) {
        ++phone_number;
    }
    int number_len = strlen(phone_number);

    totalPDULength += number_len;
    if (number_len & 0x01) { // if phone number length is not even length we must pad it and so +1
        totalPDULength += 1;
    }

    totalPDULength += 16; // all other than phone number and message length
    if (msg_parts > 1) {// add more space for UDH
        totalPDULength += 12;
    }
    // there might be need for padding so some more space
    totalPDULength += 2;

    // 8-bit message, converted to hex so it will take twice as much space
    totalPDULength += message_length * 2;

    // terminating nullbyte, because callers use strlen() to find out PDU size
    totalPDULength += 1;

    char *pdu = new char[totalPDULength];
    memset(pdu, 0, totalPDULength);
    int x = 0;
    // See more how to create PDU from 3GPP specification 23040
    // first two define that we use service center number which is set with +CSCA
    pdu[x++] = '0';
    pdu[x++] = '0';
    // First Octet of the TPDU. 41 means SMS SUBMIT, no validity period, no status report, use User Data Header.
    // 01 means SMS SUBMIT, no validity period, no status report, NO User Data Header.
    if (msg_parts > 1) { // concatenated, must use UDH
        pdu[x++] = '4';
    } else {
        pdu[x++] = '0';
    }
    pdu[x++] = '1';
    // assign a message reference automatically. We have defined TP-RD bit as 0 so duplicates are not rejected.
    pdu[x++] = '0';
    pdu[x++] = '0';
    // [6] and [7] Length of the Destination Phone Number
    int_to_hex_str(number_len, pdu + x);
    x += 2;
    // Type of the Destination Phone Number
    if (is_number_international) {
        pdu[x++] = '9'; // international
    } else {
        pdu[x++] = '8'; // unknown
    }
    pdu[x++] = '1';

    // phone number as reverse nibble encoded
    int i = 0;
    for (; i < number_len; i += 2) {
        if (i + 1 == number_len) {
            pdu[x++] = 'f';
        } else {
            pdu[x++] = phone_number[i + 1];
        }
        pdu[x++] = phone_number[i];
    }

    // Protocol Identifier
    pdu[x++] = '0';
    pdu[x++] = '0';
    // Data Coding Scheme, GSM 7-bit default alphabet = '00', 8-bit '04'
    pdu[x++] = '0';

    if (_use_8bit_encoding) {
        pdu[x++] = '4';
    } else {
        pdu[x++] = '0';
    }

    uint8_t udhlen = 0;
    // Length can be update after we have created PDU, store position for later use.
    int lengthPos = x;
    x += 2;

    int paddingBits = 0;
    if (msg_parts > 1) { // concatenated, must use UDH
        // user data header length in chars
        pdu[x++] = '0';
        udhlen = 6; // udh length in chars (5) + udhl length in chars
        pdu[x++] = '5';
        // Information element identifier
        pdu[x++] = '0';
        pdu[x++] = '0';
        //  Information element data length
        pdu[x++] = '0';
        pdu[x++] = '3';
        //  A reference number (must be the same for all parts of the same larger messages)
        int_to_hex_str(_sms_message_ref_number & 0xFF, pdu + x);
        x += 2;
        // How many parts does this message have?
        int_to_hex_str(msg_parts, pdu + x);
        x += 2;
        // this is a part number
        int_to_hex_str(msg_part_number, pdu + x);
        x += 2;

        // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary
        paddingBits = (udhlen * 8) % 7;
        if (paddingBits) {
            paddingBits = 7 - paddingBits;
            udhlen += 1;
        }
    }

    if (_use_8bit_encoding) {
        char_str_to_hex_str(message, message_length, pdu + x);
    } else {
        // we might need to send zero length sms
        if (message_length) {
            if (pack_7_bit_gsm_and_hex(message, message_length, pdu + x, paddingBits) == 0) {
                delete [] pdu;
                return NULL;
            }
        }
    }

    // now we know the correct length of the UDL (User Data Length)
    int_to_hex_str(message_length + udhlen, pdu + lengthPos);
    header_size = x;

    return pdu;
}

nsapi_size_or_error_t AT_CellularSMS::send_sms(const char *phone_number, const char *message, int msg_len)
{
    int single_sms_max_length = _use_8bit_encoding ?
                                SMS_MAX_SIZE_8BIT_SINGLE_SMS_SIZE :
                                SMS_MAX_SIZE_GSM7_SINGLE_SMS_SIZE;
    if ((_mode == CellularSMSMmodeText && msg_len > single_sms_max_length) || !phone_number) {
        return NSAPI_ERROR_PARAMETER;
    }

    _at.lock();

    int write_size = 0;

    ThisThread::sleep_for(_sim_wait_time);

    if (_mode == CellularSMSMmodeText) {
        _at.cmd_start_stop("+CMGS", "=", "%s", phone_number);

        ThisThread::sleep_for(_sim_wait_time);
        _at.resp_start("> ", true);

        if (_at.get_last_error() == NSAPI_ERROR_OK) {
            write_size = _at.write_bytes((uint8_t *)message, msg_len);
            if (write_size < msg_len) {
                // sending can be cancelled by giving <ESC> character (IRA 27).
                _at.cmd_start(ESC);
                _at.cmd_stop();
                _at.unlock();
                return write_size;
            }
            // <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body.
            _at.cmd_start(CTRL_Z);
            _at.cmd_stop();
            _at.resp_start("+CMGS:");
            _at.resp_stop();
        }
    } else {
        // supports uncompressed 8 bit data and GSM 7 bit default alphabet data. Current implementation uses only
        // GSM 7 bit default but support is done for 8 bit data.
        int sms_count;
        int concatenated_sms_length = _use_8bit_encoding ?
                                      SMS_MAX_8BIT_CONCATENATED_SINGLE_SMS_SIZE :
                                      SMS_MAX_GSM7_CONCATENATED_SINGLE_SMS_SIZE;

        if (msg_len <= single_sms_max_length) {
            // single message
            sms_count = 1;
        } else {
            // concatenated message
            sms_count = msg_len / concatenated_sms_length;
            if (msg_len % concatenated_sms_length != 0) {
                sms_count++;
            }
        }

        int remaining_len = msg_len;
        int pdu_len;
        int msg_write_len = 0;
        uint8_t header_len;
        char *pdu_str;
        for (int i = 0; i < sms_count; i++) {

            header_len = 0;
            if (sms_count == 1) {
                pdu_len = msg_len;
            } else {
                pdu_len = remaining_len > concatenated_sms_length ? concatenated_sms_length : remaining_len;
            }

            pdu_str = create_pdu(phone_number, message + i * concatenated_sms_length, pdu_len,
                                 sms_count, i + 1, header_len);
            if (!pdu_str) {
                _at.unlock();
                return NSAPI_ERROR_NO_MEMORY;
            }
            pdu_len = strlen(pdu_str);

            // specification says that service center number should not be included so we subtract -2 from pdu_len as we use '00' for automatic service center number

            _at.cmd_start_stop("+CMGS", "=", "%d", (pdu_len - 2) / 2);

            ThisThread::sleep_for(_sim_wait_time);
            _at.resp_start("> ", true);

            if (_at.get_last_error() == NSAPI_ERROR_OK) {
                write_size = _at.write_bytes((uint8_t *)pdu_str, pdu_len);
                if (write_size < pdu_len) {
                    // calculate exact size of what we have send
                    if (write_size <= header_len) {
                        // managed only to write header or some of it so actual msg write size in this iteration is 0
                        write_size = 0;
                    } else {
                        write_size = (write_size - header_len) / 2; // as hex encoded so divide by two
                    }
                    msg_write_len += write_size;

                    // sending can be cancelled by giving <ESC> character (IRA 27).
                    _at.cmd_start(ESC);
                    _at.cmd_stop();
                    _at.unlock();
                    delete [] pdu_str;
                    return msg_write_len;
                }

                // <ctrl-Z> (IRA 26) must be used to indicate the ending of the message body.
                _at.cmd_start(CTRL_Z);
                _at.cmd_stop();
                _at.resp_start("+CMGS:");
                _at.resp_stop();
            }
            delete [] pdu_str;
            remaining_len -= concatenated_sms_length;
            if (_at.get_last_error() != NSAPI_ERROR_OK) {
                return _at.unlock_return_error();
            }
        }
    }

    _sms_message_ref_number++;
    nsapi_error_t ret = _at.get_last_error();
    _at.unlock();

    return (ret == NSAPI_ERROR_OK) ? msg_len : ret;
}

void AT_CellularSMS::set_sms_callback(Callback<void()> func)
{
    _cb = func;
}

nsapi_error_t AT_CellularSMS::set_cpms(const char *memr, const char *memw, const char *mems)
{
    return _at.at_cmd_discard("+CPMS", "=", "%s%s%s", memr, memw, mems);
}

nsapi_error_t AT_CellularSMS::set_csca(const char *sca, int type)
{
    return _at.at_cmd_discard("+CSCA", "=", "%s%d", sca, type);
}

nsapi_size_or_error_t AT_CellularSMS::set_cscs(const char *chr_set)
{
    return _at.at_cmd_discard("+CSCS", "=", "%s", chr_set);
}

nsapi_error_t AT_CellularSMS::delete_sms(sms_info_t *sms)
{
    _at.lock();
    for (int i = 0; i < sms->parts; i++) {
        _at.at_cmd_discard("+CMGD", "=", "%d", sms->msg_index[i]);
    }

    return _at.unlock_return_error();
}

// we need this as for example concatenated sms can get different sms reference numbers
// if for example last part take much more time to arrive. This situation happened while testing.
// What this means that after this we can't read another sms because we always read the oldest sms
// that was corrupted. So we need to have delete all messages.
nsapi_error_t AT_CellularSMS::delete_all_messages()
{
    return _at.at_cmd_discard("+CMGD", "=1,4");
}

// read msg in text mode
nsapi_size_or_error_t AT_CellularSMS::read_sms_from_index(int msg_index, char *buf, uint16_t len, char *phone_num,
                                                          char *time_stamp)
{
    /*
     * +CMGR: <stat>,<oa>,<alpha>,<scts>[,<tooa>,<fo>,<pid>,<dcs>,<sca>,<tosca>,<length>]<CR><LF><data><CR><LF>OK<CR><LF>
     */
    ThisThread::sleep_for(_sim_wait_time);
    _at.cmd_start_stop("+CMGR", "=", "%d", msg_index);

    // TODO: NOTE:    If the selected <mem1> can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs),
    // the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter.
    // for now we support sms reading of received messages
    int buf_len = 0;
    if (_at.get_last_error() == NSAPI_ERROR_OK) {
        char status[SMS_STATUS_SIZE];
        // first we read msg status and with that we can decide how the rest of message format
        _at.resp_start("+CMGR:");

        if (_at.info_resp()) {
            _at.read_string(status, SMS_STATUS_SIZE);
            uint16_t status_len = strlen(status);
            if (((status_len == sizeof("REC READ") - 1) && memcmp("REC READ", status, status_len) == 0)
                    || ((status_len == sizeof("REC UNREAD") - 1) && memcmp("REC UNREAD", status, status_len) == 0)) {
                // Received message
                if (phone_num) {
                    _at.read_string(phone_num, SMS_MAX_PHONE_NUMBER_SIZE);
                } else {
                    _at.skip_param(); // <oa>,<alpha>
                }
                _at.skip_param(); // <alpha>
                if (time_stamp) {
                    int len = _at.read_string(time_stamp, SMS_MAX_TIME_STAMP_SIZE);
                    if (len < (SMS_MAX_TIME_STAMP_SIZE - 2)) {
                        time_stamp[len++] = ',';
                        _at.read_string(&time_stamp[len], SMS_MAX_TIME_STAMP_SIZE - len);
                    }
                }
                (void)_at.consume_to_stop_tag(); // consume until <CR><LF>
                if (buf) {
                    _at.read_string(buf, len);
                    buf_len = strlen(buf);
                }
            }
        }
        _at.resp_stop();
    }

    return (_at.get_last_error() == NSAPI_ERROR_OK) ? buf_len : _at.get_last_error();
}

// read msg in PDU mode
nsapi_size_or_error_t AT_CellularSMS::read_sms(sms_info_t *sms, char *buf, char *phone_num, char *time_stamp)
{
    // +CMGR: <stat>,[<alpha>],<length><CR><LF><pdu>
    int index;
    if (sms->parts == sms->parts_added) {
        char *pdu; // we need a temp buffer as payload is hexencoded ---> can't use buf as it might be enough for message but not hexenconded pdu.
        int status;
        int msg_len;
        index = 0;
        int pduSize;

        for (int i = 0; i < sms->parts; i++) {
            ThisThread::sleep_for(_sim_wait_time);
            _at.cmd_start_stop("+CMGR", "=", "%d", sms->msg_index[i]);
            _at.resp_start("+CMGR:");

            if (_at.info_resp()) {
                status = _at.read_int();
                _at.skip_param(); // <alpha>
                if ((_at.get_last_error() == NSAPI_ERROR_OK) && (status == 0 || status == 1)) {
                    msg_len = _at.read_int();
                    if (msg_len > 0) {
                        pduSize = msg_len * 2 + 20; // *2 as it's hex encoded and +20 as service center number is not included in size given by CMGR
                        pdu = new char[pduSize];
                        memset(pdu, 0, pduSize);
                        if (!pdu) {
                            _at.resp_stop();
                            return NSAPI_ERROR_NO_MEMORY;
                        }
                        _at.read_string(pdu, pduSize, true);
                        if (_at.get_last_error() == NSAPI_ERROR_OK) {
                            msg_len = get_data_from_pdu(pdu, NULL, NULL, phone_num, buf + index);
                            if (msg_len >= 0) { // we need to allow zero length messages
                                index += msg_len;
                            } else {
                                delete [] pdu;
                                _at.resp_stop();
                                return -1;
                            }
                        }
                        delete [] pdu;
                    }
                }
            }
            _at.resp_stop();
        }

        if (_at.get_last_error() == NSAPI_ERROR_OK) {
            if (time_stamp) {
                strcpy(time_stamp, sms->date);
            }
            buf[index] = '\0';
        }
    } else {
        tr_warn("NOT all concatenated parts were received...");
        index = SMS_ERROR_MULTIPART_ALL_PARTS_NOT_READ;
    }

    return index;
}

nsapi_size_or_error_t AT_CellularSMS::get_sms(char *buf, uint16_t len, char *phone_num, uint16_t phone_len,
                                              char *time_stamp, uint16_t time_len, int *buf_size)
{
    // validate buffer sizes already here to avoid any necessary function calls and locking of _at
    if ((phone_num && phone_len < SMS_MAX_PHONE_NUMBER_SIZE) || (time_stamp && time_len < SMS_MAX_TIME_STAMP_SIZE) ||
            buf == NULL) {
        return NSAPI_ERROR_PARAMETER;
    }

    _at.lock();

    nsapi_size_or_error_t err = list_messages();
    if (err == NSAPI_ERROR_OK) {
        // we return the oldest sms and delete it after successful read
        sms_info_t *info = get_oldest_sms_index();

        if (info) {
            if (info->msg_size > len) {
                tr_warn("Given buf too small, len is: %d but is must be: %d", len, info->msg_size);
                if (buf_size) {
                    *buf_size = info->msg_size;
                }
                free_linked_list();
                _at.unlock();
                return NSAPI_ERROR_PARAMETER;
            }

            if (_mode == CellularSMSMmodePDU) {
                err = read_sms(info, buf, phone_num, time_stamp);
            } else {
                err = read_sms_from_index(info->msg_index[0], buf, len, phone_num, time_stamp);
            }

            if (err > 0) {
                int delerr = delete_sms(info);
                if (delerr) {
                    err = delerr;
                }
            }
        } else {
            // No messages were found, return -1
            err = -1;
        }
    }

    free_linked_list();

    _at.unlock();

    // update error only when there really was an error, otherwise we return the length
    if (_at.get_last_error()) {
        err = _at.get_last_error();
    }
    return err;
}

nsapi_size_or_error_t AT_CellularSMS::get_data_from_pdu(const char *pdu, sms_info_t *info, int *part_number,
                                                        char *phone_number, char *msg)
{
    int index = 0;
    int tmp;
    bool userDataHeader;
    int oaLength;
    int dataScheme;
    nsapi_size_or_error_t err = NSAPI_ERROR_OK;

    // read Length of the SMSC information
    oaLength = hex_str_to_int(pdu, 2);
    index += 2; // length we just read
    index += oaLength * 2; // skip service center number

    // read first the lower part of first octet as there is message type
    index++;
    tmp = hex_str_to_int(pdu + index, 1);
    //ThisThread::sleep_for(200);
    if ((tmp & 0x03) == 0) {// SMS-DELIVER type, last two bits should be zero
        // UDH present? Check from first octets higher part
        tmp = hex_str_to_int(pdu + (--index), 1);
        userDataHeader = ((tmp & 0x04) == 0) ? false : true;

        index += 2; // we just read the high bits of first octet so move +2
        // originating address length
        oaLength = hex_str_to_int(pdu + index, 2);
        index += 2; // add  index over address length
        int type = hex_str_to_int(pdu + index, 1);
        index += 2; // add  index over type
        if (phone_number) {
            // phone number as reverse nibble encoded
            int a = 0, field_length = oaLength;

            if (type == 9) {
                //add the plus sign in case of international number (23.040 chapter address fields)
                phone_number[a++] = '+';
                field_length++;
            }

            for (; a < field_length; a += 2) {
                if ((a + 1) == field_length) {
                    phone_number[a] = pdu[index + 1];
                    index++;
                } else {
                    phone_number[a] = pdu[index + 1];
                    phone_number[a + 1] = pdu[index];
                    index += 2;
                }
            }
            phone_number[field_length] = '\0';
        } else {
            index += oaLength;
        }


        if (oaLength & 0x01) { // if phone number length is odd then it has padded F so skip that
            index++;
        }
        index += 2; // skip TP-Protocol identifier

        dataScheme = hex_str_to_int(pdu + index, 2);
        index += 2; // skip TP-Data-Coding-Scheme

        // next one is date, it's length is 7 octets according to 3GPP TS 23.040
        // create time string
        if (info) {
            int i = 0;
            // year
            info->date[i++] = pdu[index + 1];
            info->date[i++] = pdu[index];
            index += 2;
            info->date[i++] = '/';
            // month
            info->date[i++] = pdu[index + 1];
            info->date[i++] = pdu[index];
            index += 2;
            info->date[i++] = '/';
            // Day
            info->date[i++] = pdu[index + 1];
            info->date[i++] = pdu[index];
            index += 2;
            info->date[i++] = ',';
            // Hour
            info->date[i++] = pdu[index + 1];
            info->date[i++] = pdu[index];
            index += 2;
            info->date[i++] = ':';
            // Minute
            info->date[i++] = pdu[index + 1];
            info->date[i++] = pdu[index];
            index += 2;
            info->date[i++] = ':';
            // Second
            info->date[i++] = pdu[index + 1];
            info->date[i++] = pdu[index];
            index += 2;
            // timezone related to GMT. pdu[index+1] most significant bit indicates the sign related to gmt
            tmp = hex_str_to_int(pdu + index + 1, 1);
            if (tmp & 0x08) {
                info->date[i++] = '-';
            } else {
                info->date[i++] = '+';
            }

            // pdu[index+1 & 0x07 is the most significant bits of the timezone
            // pdu [index] is the least significant bits
            info->date[i++] = '0' + (tmp & 0x07);
            info->date[i++] = pdu[index];
            info->date[i] = '\0';
            index += 2;
        } else {
            index += 14;
        }

        int udl =  hex_str_to_int(pdu + index, 2);
        index += 2;

        int paddingBits = 0;
        int partnro = 1;
        if (userDataHeader) {
            // we need to read User Defined Header to know what part number this message is.
            index += read_udh_from_pdu(pdu + index, info, partnro, paddingBits);
        }

        if (part_number) {
            *part_number = partnro;
        }

        if (msg) {
            // we are reading the message
            err = read_pdu_payload(pdu + index, udl, dataScheme, msg, paddingBits);
        } else {
            if (dataScheme == 0x00) {
                // when listing messages we need to calculated length. Other way would be unpacking the whole message.
                err = strlen(pdu + index) >> 1;
                err *= 8;
                err /= 7;
            } else if (dataScheme == 0x04) {
                err = strlen(pdu + index) >> 1;
            } else {
                return NSAPI_ERROR_UNSUPPORTED;
            }
        }

        return err;
    } else {
        // message was not DELIVER so discard it
        return NSAPI_ERROR_UNSUPPORTED;
    }
}

// read params from User Defined Header
int AT_CellularSMS::read_udh_from_pdu(const char *pdu, sms_info_t *info, int &part_number, int &padding_bits)
{

    int index = 0;
    int udhLength = hex_str_to_int(pdu, 2);
    index += 2;

    // if there is padding bits then udhlen is octet bigger as we need to keep septet boundary
    padding_bits = ((udhLength + 1) * 8) % 7; // +1 is for udhLength itself

    if (padding_bits) {
        padding_bits = 7 - padding_bits;
    } else {
        padding_bits = 0;
    }

    int tmp = hex_str_to_int(pdu + index, 2);
    index += 4;

    if (tmp == 0) { // 8-bit reference number
        if (info) {
            info->msg_ref_number = (uint16_t)hex_str_to_int(pdu + index, 2);
        }
        index += 2;
    } else {                  // 16-bit reference number
        if (info) {
            info->msg_ref_number = (uint16_t)hex_str_to_int(pdu + index + 2, 2);
            tmp = hex_str_to_int(pdu + index, 2);
            info->msg_ref_number |= (tmp << 8);
        }
        index += 4;
    }

    if (info) {
        info->parts = hex_str_to_int(pdu + index, 2);
    }
    index += 2;

    part_number = hex_str_to_int(pdu + index, 2);
    index += 2;

    return (udhLength * 2 + 2); // udh in hex and udhl
}

nsapi_size_or_error_t AT_CellularSMS::read_pdu_payload(const char *pdu, int msg_len, int scheme, char *msg, int padding_bits)
{
    if (scheme == 0x00) {
        // 7 bit gsm encoding, must do the conversions from hex to 7-bit encoding and to ascii
        return unpack_7_bit_gsm_to_str(pdu, strlen(pdu) / 2, msg, padding_bits, msg_len);
    } else if (scheme == 0x04) {
        // 8bit scheme so just convert hexstring to charstring
        return hex_str_to_char_str(pdu, strlen(pdu), msg);
    } else {
        tr_error("Received unsupported data coding scheme: 0x%02x", scheme);
        return NSAPI_ERROR_UNSUPPORTED;
    }
}

void AT_CellularSMS::free_linked_list()
{
    sms_info_t *info = _sms_info;
    sms_info_t *old;
    while (info) {
        old = info;
        info = info->next_info;
        delete old;
    }
    _sms_info = NULL;
}

void AT_CellularSMS::add_info(sms_info_t *info, int index, int part_number)
{
    // check for same message reference id. If found, update it and delete the given info.
    // if NOT found then add to the end of the list.

    if (!_sms_info) {
        info->msg_index[part_number - 1] = index; // part numbering starts from 1 so -1 to put to right index
        _sms_info = info;
        return;
    }
    sms_info_t *current = _sms_info;
    sms_info_t *prev = NULL;
    bool found_msg = false;
    while (current) {
        prev = current;
        // sms messages can have same reference number so additional checks are needed.
        // TODO: should we include phone number also?
        if (current->msg_ref_number == info->msg_ref_number && current->parts > current->parts_added &&
                info->parts > info->parts_added) {
            // multipart sms, update msg size and index
            current->msg_size += info->msg_size;
            current->msg_index[part_number - 1] = index; // part numbering starts from 1 so -1 to put to right index
            current->parts_added++;
            // update oldest part as date
            if (compare_time_strings(info->date, current->date) == -1) {
                strcpy(current->date, info->date);
            }
            found_msg = true;
            break;
        }
        current = current->next_info;
    }

    if (found_msg) {
        // info was added to existing item in linked list, must be deleted
        delete info;
    } else {
        // message not found, add to linked list
        info->msg_index[part_number - 1] = index;
        prev->next_info = info;
    }
}

// reads all the messages to the linked list AT_CellularSMS::_sms_info
nsapi_error_t AT_CellularSMS::list_messages()
{
    // TODO: NOTE:  If the selected <mem1> can contain different types of SMs (e.g. SMS-DELIVERs, SMS-SUBMITs, SMS-STATUS-REPORTs and SMS-COMMANDs),
    // the response may be a mix of the responses of different SM types. TE application can recognize the response format by examining the third response parameter.
    // for now we assume that only SMS-DELIVER messages are read.
    if (_mode == CellularSMSMmodePDU) {
        _at.cmd_start_stop("+CMGL", "=4");
    } else {
        _at.cmd_start_stop("+CMGL", "=\"ALL\"");
    }

    sms_info_t *info = NULL;
    // init for 1 so that in text mode we will add to the correct place without any additional logic in addInfo() in text mode
    int part_number = 1;
    int index = 0;
    int length = 0;
    char *pdu = NULL;

    _at.resp_start("+CMGL:");
    while (_at.info_resp()) {
        info = new sms_info_t();
        if (_mode == CellularSMSMmodePDU) {
            //+CMGL: <index>,<stat>,[<alpha>],<length><CR><LF><pdu>[<CR><LF>
            // +CMGL:<index>,<stat>,[<alpha>],<length><CR><LF><pdu>
            //[...]]
            index = _at.read_int();
            _at.skip_param(2); // <stat>,[<alpha>]
            length = _at.read_int();
            length = length * 2 + 20; // *2 as it's hex encoded and +20 as service center number is not included in size given by CMGL
            pdu = new char[length];
            memset(pdu, 0, length);
            _at.read_string(pdu, length, true);
            if (_at.get_last_error() == NSAPI_ERROR_OK) {
                info->msg_size = get_data_from_pdu(pdu, info, &part_number);
            }
        } else {
            // +CMGL: <index>,<stat>,<oa/da>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[<CR><LF>
            // +CMGL: <index>,<stat>,<da/oa>,[<alpha>],[<scts>][,<tooa/toda>,<length>]<CR><LF><data>[...]]
            index = _at.read_int();
            (void)_at.consume_to_stop_tag(); // consume until <CR><LF>
            (void)_at.consume_to_stop_tag(); // consume until <CR><LF>
        }

        if (index >= 0) {
            add_info(info, index, part_number);
        } else {
            delete info;
            info = NULL;
        }
        delete [] pdu;
        pdu = NULL;
    }


    _at.resp_stop();

    return _at.get_last_error();
}

AT_CellularSMS::sms_info_t *AT_CellularSMS::get_oldest_sms_index()
{
    /*
     * Different scenarios when finding the oldest concatenated sms
     *
     * 1. Find first parts first and it was received first
     * 2. Find first parts first and it was NOT received first -> older timestamp might exist in some other part
     * 3. Find other than first part first and it was received first
     * 4. Find other than first part first and it was NOT received first -> older timestamp might exist in some other part
     *
     * So must take all message to a linked list and loop that for the oldest
     */

    // if text mode we need to read sms with +CMGR because time stamp is optional while looping with +CMGL
    sms_info_t *retVal = NULL;
    sms_info_t *current = _sms_info;
    nsapi_size_or_error_t err = 0;
    while (current) {
        if (_mode == CellularSMSMmodeText) {
            ThisThread::sleep_for(_sim_wait_time);
            err = read_sms_from_index(current->msg_index[0], NULL, 0, NULL, current->date);
            if (err != 0) {
                return NULL;
            }
        }

        if (retVal == NULL) {
            retVal = current;
        } else if (compare_time_strings(current->date, retVal->date) == -1) {
            // found older sms, update return value to oldest
            retVal = current;
        }
        current = current->next_info;
    }

    return retVal;
}

// if time_string_1 is greater (more fresh date) then return 1, same 0, smaller -1. Error -2
int AT_CellularSMS::compare_time_strings(const char *time_string_1, const char *time_string_2)
{
    time_t t1;
    time_t t2;

    bool success = create_time(time_string_1, &t1) && create_time(time_string_2, &t2);
    int retVal = -2;

    if (success) {
        time_t diff = t1 - t2;

        if (diff > 0) {
            retVal = 1;
        } else if (diff == 0) {
            retVal = 0;
        } else {
            retVal = -1;
        }
    }

    return retVal;
}

bool AT_CellularSMS::create_time(const char *time_string, time_t *time)
{
    const int kNumberOfElements = 8;
    tm time_struct = { 0 };
    int gmt = 0;
    char sign;
    bool retVal = false;

    if (sscanf(time_string, "%d/%d/%d,%d:%d:%d%c%d", &time_struct.tm_year, &time_struct.tm_mon, &time_struct.tm_mday,
               &time_struct.tm_hour, &time_struct.tm_min, &time_struct.tm_sec, &sign, &gmt) == kNumberOfElements) {
        *time = mktime(&time_struct);
        // add timezone as seconds. gmt is in quarter of hours.
        int x = (60 / 4) * 60 * gmt;
        if (sign == '+') {
            *time += x;
        } else {
            *time -= x;
        }
        retVal = true;
    }

    return retVal;
}

uint16_t AT_CellularSMS::pack_7_bit_gsm_and_hex(const char *str, uint16_t len, char *buf,
                                                int number_of_padding_bit)
{
    uint16_t strCnt = 0;
    uint16_t i = 0;
    uint8_t shift;
    char tmp;

    if (len == 0) {
        return 0;
    }
    // convert to 7bit gsm first
    char *gsm_str = new char[len];
    for (uint16_t y = 0; y < len; y++) {
        for (int x = 0; x < GSM_TO_ASCII_TABLE_SIZE; x++) {
            if (gsm_to_ascii[x] == static_cast<unsigned char>(str[y])) {
                gsm_str[y] = x;
            }
        }
    }

    // then packing and converting to hex
    if (number_of_padding_bit) {
        tmp = gsm_str[strCnt] << number_of_padding_bit;
        strCnt++;
        char_str_to_hex_str(&tmp, 1, &buf[i * 2]);
        i++;
    }

    while (strCnt < len) {
        if (number_of_padding_bit) {
            shift = (i + number_of_padding_bit - 2) % 7;
        } else {
            shift = i % 7;
        }

        if (strCnt + 1 == len) {
            tmp = (gsm_str[strCnt] >> shift);
        } else {
            tmp = (gsm_str[strCnt] >> shift) | (gsm_str[strCnt + 1] << (7 - shift));
        }

        char_str_to_hex_str(&tmp, 1, buf + (i * 2));

        if (shift == 6) {
            strCnt++;
        }
        strCnt++;
        i++;
    }

    delete [] gsm_str;

    return i;
}

uint16_t AT_CellularSMS::unpack_7_bit_gsm_to_str(const char *str, int len, char *buf, int padding_bits,
                                                 int msg_len)
{
    int strCount = 0;
    uint16_t decodedCount = 0;
    uint8_t shift;
    char tmp;
    char tmp1;

    if (padding_bits) {
        hex_to_char(str, tmp);
        buf[decodedCount] = gsm_to_ascii[(tmp >> padding_bits) & 0x7F];
        strCount++;
        decodedCount++;
    }

    while (strCount < len) {
        shift = (strCount - padding_bits) % 7;
        hex_to_char(str + strCount * 2, tmp);
        if (shift == 0) {
            buf[decodedCount] = gsm_to_ascii[tmp & 0x7F];
        } else if (shift == 6) {
            hex_to_char(str + (strCount - 1) * 2, tmp1);
            buf[decodedCount] = gsm_to_ascii[(((tmp1 >> 2)) | (tmp << 6)) & 0x7F];
            if (decodedCount + 1 < msg_len) {
                hex_to_char(str + strCount * 2, tmp);
                decodedCount++;
                buf[decodedCount] = gsm_to_ascii[(tmp >> 1) & 0x7F];
            }
        } else {
            hex_to_char(str + (strCount - 1) * 2, tmp1);
            buf[decodedCount] = gsm_to_ascii[(((tmp1 >> (8 - shift))) | ((tmp << shift))) & 0x7F];
        }

        strCount++;
        decodedCount++;
    }

    return decodedCount;
}

ATHandler &AT_CellularSMS::get_at_handler()
{
    return _at;
}

#endif //MBED_CONF_CELLULAR_USE_SMS
