/* EMW3162 Example
 * Copyright (c) 2015 ARM Limited
 *
 * 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 "EMW3162.h"

EMW3162::EMW3162(PinName tx, PinName rx, bool debug)
    : _serial(tx, rx, 1024), _parser(_serial)
    , _packets(0), _packets_end(&_packets)
{
    _serial.baud(115200);
    _parser.debugOn(debug);
}

bool EMW3162::startup(void)
{
    bool success = reset("FACTORY");

    _parser.oob("+EVENT=SOCKET", this, &EMW3162::_packet_handler);

    return success;
}

bool EMW3162::reset(const char *reset)
{
    for (int i = 0; i < 2; i++) {
        if (_parser.send("AT+%s", reset)
            && _parser.recv("+OK")) {
            return true;
        }
    }

    return false;
}

bool EMW3162::dhcp(bool enabled)
{
    return _parser.send("AT+DHCP=%s", enabled ? "ON":"OFF")
        && _parser.recv("+OK");
}

bool EMW3162::connect(const char *ap, const char *passPhrase)
{
    return _parser.send("AT+WSTA=%s,%s", ap, passPhrase)
        && _parser.recv("+OK")
        && _parser.send("AT+EVENT=ON")
        && _parser.recv("+OK")
        && _parser.recv("+EVENT=WIFI_LINK,STATION_UP");
}

bool EMW3162::disconnect(void)
{
    
    return _parser.send("AT+WLANF=STA,OFF")
        && _parser.recv("+OK")
        && _parser.recv("+EVENT=WIFI_LINK,STATION_DOWN")
        && _parser.send("AT+EVENT=OFF")
        && _parser.recv("+OK");
}

const char *EMW3162::getIPAddress(void)
{
    if (!(_parser.send("AT+IPCONFIG")
        && _parser.recv("%*[^,],%*[^,],%*[^,],%[^,]%*[^#]#", _ip_buffer))) {
        return 0;
    }

    return _ip_buffer;
}

const char *EMW3162::getMACAddress(void)
{
    if (!(_parser.send("AT+WMAC")
        && _parser.recv("%*[^=]=%[^#]#", _mac_buffer))) {
        return 0;
    }

    return _mac_buffer;
}

bool EMW3162::isConnected(void)
{
    return getIPAddress() != 0;
}

int EMW3162::open(const char *type, int id, const char* addr, int port)
{
    int state1 = 0, state2 = 0;
    state1 = _parser.send("AT+CON1=%s,%d,%d,%s", type, id, port, addr)
        && _parser.recv("+OK")
        && _parser.send("AT+SAVE")
        && _parser.recv("+OK")
        && reset("REBOOT")
        && _parser.recv("+EVENT=REBOOT");
    
    wait(3);
    char conType[10];       // UDP/TCP connection type: SERVER, CLIENT, BROADCAST, UNICAST
    int socketId;           // UDP/TCP socket id
    
    // Receive wifi connect event
    state2 = _parser.recv("+EVENT=WIFI_LINK,STATION_UP")
        && _parser.recv("+EVENT=%[^,],CONNECT,%d", conType, &socketId);
    
    if (state1 && state2)
        return socketId;
    else
        return -1;
}

bool EMW3162::send(int id, const void *data, uint32_t amount)
{
    //May take a second try if device is busy
    for (unsigned i = 0; i < 2; i++) {
        if (_parser.send("AT+SSEND=%d,%d", id, amount)
            && _parser.recv(">")
            && _parser.write((char*)data, (int)amount) >= 0
            && _parser.recv("+OK")) {
            //wait(3);
            return true;
        }
    }

    return false;
}

void EMW3162::_packet_handler()
{
    int id;
    uint32_t amount;

    // parse out the packet
    if (!_parser.recv(",%d,%d,", &id, &amount)) {
        return;
    }

    struct packet *packet = (struct packet*)malloc(
            sizeof(struct packet) + amount);
    if (!packet) {
        return;
    }

    packet->id = id;
    packet->len = amount;
    packet->next = 0;

    if (!(_parser.read((char*)(packet + 1), amount))) {
        free(packet);
        return;
    }

    // append to packet list
    *_packets_end = packet;
    _packets_end = &packet->next;
}

int32_t EMW3162::recv(int id, void *data, uint32_t amount)
{
    while (true) {
        // check if any packets are ready for us
        for (struct packet **p = &_packets; *p; p = &(*p)->next) {
            if ((*p)->id == id) {
                struct packet *q = *p;

                if (q->len <= amount) { // Return and remove full packet
                    memcpy(data, q+1, q->len);

                    if (_packets_end == &(*p)->next) {
                        _packets_end = p;
                    }
                    *p = (*p)->next;

                    uint32_t len = q->len;
                    free(q);
                    return len;
                } else { // return only partial packet
                    memcpy(data, q+1, amount);

                    q->len -= amount;
                    memmove(q+1, (uint8_t*)(q+1) + amount, q->len);

                    return amount;
                }
            }
        }

        // Wait for inbound packet
        if (!_parser.recv("#")) {
            return -1;
        }
    }
}

bool EMW3162::close(int id)
{
    //May take a second try if device is busy
    for (unsigned i = 0; i < 2; i++) {
        if (_parser.send("AT+CONF=1,OFF")
            && _parser.recv("+OK")) {
            char conType[10];  // Used for UDP/TCP connection type: SERVER, CLIENT, BROADCAST, UNICAST
            int socketId;      // Used for UDP/TCP socket id
            
            _parser.recv("+EVENT=%[^,],DISCONNECT,%d", conType, &socketId);
            return true;
        }
    }

    return false;
}

void EMW3162::setTimeout(uint32_t timeout_ms)
{
    _parser.setTimeout(timeout_ms);
}

bool EMW3162::readable()
{
    return _serial.readable();
}

bool EMW3162::writeable()
{
    return _serial.writeable();
}

void EMW3162::attach(Callback<void()> func)
{
    _serial.attach(func);
}
