///////////////////////////////////////////////////////////////////////////////
//
//                          IMPORTANT NOTICE
//
// The following open source license statement does not apply to any
// entity in the Exception List published by FMSoft.
//
// For more information, please visit:
//
// https://www.fmsoft.cn/exception-list
//
//////////////////////////////////////////////////////////////////////////////
/*
** This file is a part of mg-demos package.
**
** Copyright (C) 2019 FMSoft (http://www.fmsoft.cn).
** Copyright (C) 2018 Beijing Joobot Technologies 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.
*/
/*!============================================================================
 * @file NetworkService.cc
 * @Synopsis
 * @author Vincent Wei
 * @version 1.0
 * @date 17/05/2018
 */

#include <unistd.h>
#include <jbus.h>

#include "global.h"
#include "resource.h"

#include "cJSON.h"
#include "NetworkService.hh"

NetworkService* NetworkService::s_service = NULL;

// get the single instance of NetworkService
NetworkService* NetworkService::singleton()
{
    if (NULL == NetworkService::s_service) {
        NetworkService::s_service = new NetworkService();
    }
    return NetworkService::s_service;
}

// private:
NetworkService::NetworkService ()
{
    memset (&m_ns, NETWORK_STATE_UNKNOWN, sizeof (NETWORK_STATE));
    m_ns.curr_ssid = NULL;
}

NetworkService::~NetworkService ()
{
}

void NetworkService::onStateChanged (const char* message)
{
    /* convert to C structure here */
    cJSON* root = cJSON_Parse (message);

    if (root) {
        cJSON* currentSSID = cJSON_GetObjectItem (root, "currentSSID");
        if (currentSSID == NULL || currentSSID->type != cJSON_String) {
            goto error;
        }
        if (currentSSID->valuestring) {
            m_ns.curr_ssid = strdup (currentSSID->valuestring);
        }
        else
            m_ns.curr_ssid = NULL;

        cJSON* globalState = cJSON_GetObjectItem (root, "globalState");
        if (globalState == NULL || globalState->type != cJSON_Number) {
            goto error;
        }
        m_ns.global = (BYTE)globalState->valueint;

        cJSON* subState = cJSON_GetObjectItem (root, "networkInfo");
        if (subState == NULL) {
            goto error;
        }

        cJSON* item = subState->child;
        while (item) {
            cJSON* type = cJSON_GetObjectItem (item, "type");
            cJSON* state = cJSON_GetObjectItem (item, "state");
            if (type == NULL || state == NULL) {
                goto error;
            }

            switch (type->valueint) {
            case NETWORK_TYPE_MOBILE:
                m_ns.lte = (BYTE)state->valueint;
                break;
            case NETWORK_TYPE_WIFI:
                m_ns.wifi = (BYTE)state->valueint;
                break;
            case NETWORK_TYPE_BLUETOOTH:
                m_ns.ble = (BYTE)state->valueint;
                break;
            case NETWORK_TYPE_ETHERNE:
                m_ns.eth = (BYTE)state->valueint;
                break;
            }

            item = item->next;
        }
    }

error:
    if (root)
        cJSON_Delete (root);
}

AsyncTaskGetNetworkState* NetworkService::getInstantState (HWND hwnd)
{
    AsyncTaskGetNetworkState* task = new AsyncTaskGetNetworkState ();

    task->createTask (hwnd, TASKID_NETWORK_STATE, 0);

    return task;
}

AsyncTaskGetHotspotList* NetworkService::getWifiList (HWND hwnd)
{
    AsyncTaskGetHotspotList* task = new AsyncTaskGetHotspotList ();

    task->createTask (hwnd, TASKID_NETWORK_WIFI_LIST, 0);

    return task;
}

AsyncTaskConnectHotspot* NetworkService::connectHotspot (HWND hwnd, const char* ssid, const char* password)
{
    AsyncTaskConnectHotspot* task = new AsyncTaskConnectHotspot ();
    task->m_conn_info.ssid = strdup (ssid);
    task->m_conn_info.password = strdup (password);
    task->createTask (hwnd, TASKID_NETWORK_CONN_WIFI, 0);
    return task;
}

void* AsyncTaskGetNetworkState::execute_task (pthread_t thread_id, int task_id, DWORD add_data)
{
    NETWORK_STATE* ns = new NETWORK_STATE;
    memset (ns, NETWORK_STATE_UNKNOWN, sizeof (NETWORK_STATE));
    ns->curr_ssid = NULL;

    char* res = jbus_invoke ("cbplus.network", "getState", "{}");
    if (res == NULL) {
        return ns;
    }

    /* convert to C structure here */
    cJSON* root = cJSON_Parse (res);
    jbus_free (res);

    if (root == NULL) {
        goto error;
    }
    else {
        cJSON* retCode = cJSON_GetObjectItem (root, "retCode");
        if (retCode == NULL || retCode->valueint != 0) {
            goto error;
        }

        cJSON* data = cJSON_GetObjectItem (root, "data");
        if (data == NULL) {
            goto error;
        }

        cJSON* currentSSID = cJSON_GetObjectItem (data, "currentSSID");
        if (currentSSID == NULL) {
            goto error;
        }
        if (currentSSID->valuestring) {
            _MG_PRINTF ("currentSSID: %s\n", currentSSID->valuestring);
            ns->curr_ssid = strdup (currentSSID->valuestring);
        }
        else
            ns->curr_ssid = NULL;

        cJSON* globalState = cJSON_GetObjectItem (data, "globalState");
        if (globalState == NULL) {
            goto error;
        }
        ns->global = (BYTE)globalState->valueint;

        cJSON* subState = cJSON_GetObjectItem (data, "networkInfo");
        if (subState == NULL) {
            goto error;
        }

        cJSON* item = subState->child;
        while (item) {
            cJSON* type = cJSON_GetObjectItem (item, "type");
            cJSON* state = cJSON_GetObjectItem (item, "state");
            if (type == NULL || state == NULL) {
                goto error;
            }

            switch (type->valueint) {
            case NETWORK_TYPE_MOBILE:
                ns->lte = (BYTE)state->valueint;
                break;
            case NETWORK_TYPE_WIFI:
                ns->wifi = (BYTE)state->valueint;
                break;
            case NETWORK_TYPE_BLUETOOTH:
                ns->ble = (BYTE)state->valueint;
                break;
            case NETWORK_TYPE_ETHERNE:
                ns->eth = (BYTE)state->valueint;
                break;
            }

            item = item->next;
        }
    }

error:
    cJSON_Delete (root);

    return (void*) ns;
}

void AsyncTaskGetNetworkState::cleanup_task (pthread_t thread_id, void* res)
{
    NETWORK_STATE* info = (NETWORK_STATE*) res;
    if (info) {
        if (info->curr_ssid)
            free (info->curr_ssid);
        delete info;
    }

    delete this;
}

#define MIN_RSSI -100
#define MAX_RSSI -55

static int signal_rssi_to_level (int rssi)
{
    if (rssi <= MIN_RSSI)
        return 0;
    else if (rssi >= MAX_RSSI)
        return 100;

    return (int)((rssi - MIN_RSSI) * 100.0 / (MAX_RSSI - MIN_RSSI));
}

static bool check_is_open (const char* cap)
{
    if (strstr (cap, "WPA") || strstr (cap, "WPS")) {
        return false;
    }

    return true;
}

void* AsyncTaskGetHotspotList::execute_task (pthread_t thread_id, int task_id, DWORD add_data)
{
    m_info = new WIFI_HOTSPOTS_INFO;
    m_info->status = TS_WIFILIST_UNKNOWN;
    m_info->current = -1;
    m_info->hotspots = new HotspotCollection;

    char* res = jbus_invoke ("cbplus.network", "scanWiFi", "{}");
    if (res == NULL) {
        m_info->status = TS_WIFILIST_SYSERROR;
        return m_info;
    }

    /* convert to C structure here */
    cJSON* root = cJSON_Parse (res);
    jbus_free (res);

    if (root == NULL) {
        goto error;
    }
    else {

        cJSON* retCode = cJSON_GetObjectItem (root, "retCode");
        if (retCode == NULL) {
            m_info->status = TS_WIFILIST_BADDATA;
            goto error;
        }

        if (retCode->valueint == 40043) {
            m_info->status = TS_WIFILIST_DISABLED;
            goto error;
        }

        if (retCode->valueint == 40044) {
            m_info->status = TS_WIFILIST_BADDATA;
            goto error;
        }

        if (retCode->valueint != 0) {
            m_info->status = TS_WIFILIST_SYSERROR;
            goto error;
        }

        cJSON* data = cJSON_GetObjectItem (root, "data");
        if (data == NULL) {
            m_info->status = TS_WIFILIST_BADDATA;
            goto error;
        }

        cJSON* currentSSID = cJSON_GetObjectItem (data, "currentSSID");
        if (currentSSID == NULL) {
            m_info->status = TS_WIFILIST_BADDATA;
            goto error;
        }

        cJSON* scanResult = cJSON_GetObjectItem (data, "scanResult");
        if (scanResult == NULL) {
            m_info->status = TS_WIFILIST_BADDATA;
            goto error;
        }

        int i = 0;
        cJSON* item = scanResult->child;
        while (item) {
            cJSON* ssid = cJSON_GetObjectItem (item, "ssid");
            cJSON* bssid = cJSON_GetObjectItem (item, "bssid");
            cJSON* cap = cJSON_GetObjectItem (item, "capabilities");
            cJSON* freq = cJSON_GetObjectItem (item, "frequency");
            cJSON* level = cJSON_GetObjectItem (item, "level");

            if (ssid == NULL || bssid == NULL || cap == NULL || freq == NULL || level == NULL) {
                /* skip the bad data */
                item = item->next;
                i++;
                continue;
            }

            if (currentSSID->valuestring &&
                    (strcasecmp (ssid->valuestring, currentSSID->valuestring) == 0)) {
                m_info->current = i;
            }

            WifiHotspot* hotspot = new WifiHotspot;

            hotspot->ssid = strdup (ssid->valuestring);
            hotspot->bssid = strdup (bssid->valuestring);
            hotspot->capabilities = strdup (cap->valuestring);
            hotspot->freq = atoi (freq->valuestring);
            hotspot->signal = signal_rssi_to_level (atoi (level->valuestring));
            hotspot->is_open = check_is_open (cap->valuestring);

            m_info->hotspots->push_back (hotspot);

            item = item->next;
            i++;
        }
    }

    m_info->status = TS_WIFILIST_OK;

error:
    cJSON_Delete (root);
    return (void*) m_info;
}

AsyncTaskGetHotspotList::~AsyncTaskGetHotspotList ()
{
    if (m_info) {
        int size = m_info->hotspots->size ();
        for (int i = 0; i < size; i++) {
            WifiHotspot* hotspot = m_info->hotspots->at (i);

            if (hotspot->ssid) free (hotspot->ssid);
            if (hotspot->bssid) free (hotspot->bssid);
            if (hotspot->capabilities) free (hotspot->capabilities);
            delete hotspot;
        }

        m_info->hotspots->clear ();

        delete m_info->hotspots;
        delete m_info;
    }
}

void* AsyncTaskConnectHotspot::execute_task (pthread_t thread_id, int task_id, DWORD add_data)
{
    WIFI_CONN_RESULT* info = new WIFI_CONN_RESULT;
    memset (info, 0, sizeof (WIFI_CONN_RESULT));

    cJSON* root = cJSON_CreateObject ();
    cJSON_AddStringToObject (root, "ssid", m_conn_info.ssid);
    cJSON_AddStringToObject (root, "password", m_conn_info.password);

    char* args = cJSON_PrintUnformatted (root);
    _MG_PRINTF ("AsyncTaskConnectHotspot::execute_task > args: %s\n", args);
    char* res = jbus_invoke ("cbplus.network", "connectWiFi", args);
    free (args);

    if (res == NULL) {
        info->status = TS_CONN_WIFI_STATUS_SYSERR;
        cJSON_Delete (root);
        return info;
    }
    cJSON_Delete (root);

    /* convert to C structure here */
    root = cJSON_Parse (res);
    jbus_free (res);

    info->status = TS_CONN_WIFI_STATUS_SYSERR;
    if (root) {
        cJSON* retCode = cJSON_GetObjectItem (root, "retCode");
        if (retCode) {
            info->status = TS_CONN_WIFI_STATUS_OK;
            info->result = retCode->valueint;
        }

        cJSON_Delete (root);
    }

    return info;
}

void AsyncTaskConnectHotspot::cleanup_task (pthread_t thread_id, void* res)
{
    WIFI_CONN_RESULT* info = (WIFI_CONN_RESULT*) res;
    if (info) {
        delete info;
    }

    delete this;
}

void* AsyncTaskEnableLTE::execute_task (pthread_t thread_id, int task_id, DWORD add_data)
{
    cJSON* root = cJSON_CreateObject ();
    const char* cmd;

    if (add_data)
        cmd = "enableLte";
    else
        cmd = "disableLte";

    char* res = jbus_invoke ("cbplus.network", cmd, "{}");
    if (res == NULL) {
        cJSON_Delete (root);
        return (void*)MAKELONG (add_data, TS_ENABLE_LTE_STATUS_SYSERR);
    }
    cJSON_Delete (root);

    /* convert to C structure here */
    root = cJSON_Parse (res);
    jbus_free (res);

    DWORD status = TS_ENABLE_LTE_STATUS_UNKNOWN;
    if (root) {
        cJSON* retCode = cJSON_GetObjectItem (root, "retCode");
        if (retCode && retCode->valueint == 0) {
            status = TS_ENABLE_LTE_STATUS_OK;
        }

        cJSON_Delete (root);
    }

    return (void*)MAKELONG (add_data, status);
}

AsyncTaskEnableLTE* NetworkService::enableLTE (HWND hwnd, BOOL enable)
{
    AsyncTaskEnableLTE* task = new AsyncTaskEnableLTE ();
    task->createTask (hwnd, TASKID_NETWORK_ENABLE_LTE, (DWORD)enable);
    return task;
}

void* AsyncTaskEnableWiFi::execute_task (pthread_t thread_id, int task_id, DWORD add_data)
{
    cJSON* root = cJSON_CreateObject ();
    const char* cmd;

    if (add_data)
        cmd = "enableWiFi";
    else
        cmd = "disableWiFi";

    char* res = jbus_invoke ("cbplus.network", cmd, "{}");
    if (res == NULL) {
        cJSON_Delete (root);
        return (void*)MAKELONG (add_data, TS_ENABLE_WIFI_STATUS_SYSERR);
    }
    cJSON_Delete (root);

    /* convert to C structure here */
    root = cJSON_Parse (res);
    jbus_free (res);

    DWORD status = TS_ENABLE_WIFI_STATUS_UNKNOWN;
    if (root) {
        cJSON* retCode = cJSON_GetObjectItem (root, "retCode");
        if (retCode && retCode->valueint == 0) {
            status = TS_ENABLE_WIFI_STATUS_OK;
        }

        cJSON_Delete (root);
    }

    return (void*)MAKELONG (add_data, status);
}

AsyncTaskEnableWiFi* NetworkService::enableWiFi (HWND hwnd, BOOL enable)
{
    AsyncTaskEnableWiFi* task = new AsyncTaskEnableWiFi ();
    task->createTask (hwnd, TASKID_NETWORK_ENABLE_WIFI, (DWORD)enable);
    return task;
}