/*
 * Copyright (c) 2019 TOYOTA MOTOR CORPORATION
 *
 * 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 <unistd.h>
#include "hs-appinfo.h"
#include "hmi-debug.h"
#include "hs-clientmanager.h"

#define RETRY_CNT 10

const char _keyName[] = "name";
const char _keyVersion[] = "version";
const char _keyInstall[] = "install";
const char _keyUninstall[] = "uninstall";
const char _keyOperation[] = "operation";
const char _keyRunnables[] = "runnables";
const char _keyStart[] = "start";
const char _keyApplistChanged[] = "application-list-changed";

HS_AppInfo* HS_AppInfo::me = nullptr;

/**
 * get application property function
 *
 * #### Parameters
 *  - key : retrieve keyword
 *
 * #### Return
 * retrieved property
 *
 */
std::string AppDetail::getProperty(std::string key) const
{
    struct json_object *j_obj;
    struct json_object *j_detail = json_tokener_parse(this->detail.c_str());
    if(json_object_object_get_ex(j_detail, key.c_str(), &j_obj) == 0) {
        HMI_ERROR("homescreen-service","can't find key=%s.", key.c_str());
        return std::string();
    }
    return std::string(json_object_get_string(j_obj));
}

/**
 * HS_AppInfo destruction function
 *
 * #### Parameters
 *  - Nothing
 *
 * #### Return
 * None
 *
 */
HS_AppInfo::~HS_AppInfo()
{
    if(afmmain)
        delete afmmain;
}

/**
 * get instance
 *
 * #### Parameters
 *  - Nothing
 *
 * #### Return
 * HS_AppInfo instance pointer
 *
 */
HS_AppInfo* HS_AppInfo::instance(void)
{
    if(me == nullptr)
        me = new HS_AppInfo();

    return me;
}

/**
 * HS_AppInfo initialize function
 *
 * #### Parameters
 *  - api : the api serving the request
 *
 * #### Return
 * 0 : init success
 * 1 : init fail
 *
 */
int HS_AppInfo::init(afb_api_t api)
{
    afmmain = new HS_AfmMainProxy();
    if(afmmain == nullptr) {
        HMI_ERROR("homescreen-service","Fatal Error:new HS_AfmMainProxy failed");
        return -1;
    }

    struct json_object* j_runnable = nullptr;
    int retry = 0;
    do {
        if(afmmain->runnables(api, &j_runnable) == 0) {
            createAppDetailList(j_runnable);
            json_object_put(j_runnable);
            break;
        }

        ++retry;
        if(retry == RETRY_CNT) {
            HMI_ERROR("homescreen-service","get runnables list failed");
            json_object_put(j_runnable);
            return -1;
        }
        HMI_NOTICE("homescreen-service","retry to get runnables list %d", retry);
        usleep(100000); // 100ms
    } while(1);

    return 0;
}

/**
 * onEvent function
 *
 * #### Parameters
 *  - api : the api serving the request
 *  - event  : event name
 *  - object : event json object
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::onEvent(afb_api_t api, const char *event, struct json_object *object)
{
    auto ip = concerned_event_list.find(std::string(event));
    if(ip != concerned_event_list.end()) {
        HMI_NOTICE("homescreen-service","[%s] event received.", event);
        (this->*(ip->second))(api, object);
    }
}

/**
 * create application detail list function
 *
 * #### Parameters
 *  - object : the detail of all applications
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::createAppDetailList(struct json_object *object)
{
    HMI_NOTICE("homescreen-service","applist:%s", json_object_to_json_string(object));

    if(json_object_get_type(object) ==  json_type_array) {
        int array_len = json_object_array_length(object);
        for (int i = 0; i < array_len; ++i) {
            struct json_object *obj = json_object_array_get_idx(object, i);
            addAppDetail(obj);
        }
    }
    else {
        HMI_ERROR("homescreen-service","Apps information input error.");
    }
}

/**
 * update application detail function
 *
 * #### Parameters
 *  - object : the detail of all applications
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::updateAppDetailList(afb_api_t api, struct json_object *object)
{
    HMI_NOTICE("homescreen-service","update:%s", json_object_to_json_string(object));
    if(json_object_get_type(object) != json_type_object) {
        HMI_ERROR("homescreen-service","input detail object error.");
        return;
    }

    struct json_object *obj_oper, *obj_data;
    if(json_object_object_get_ex(object, _keyOperation, &obj_oper) == 0
    ||  json_object_object_get_ex(object, _keyData, &obj_data) == 0) {
        HMI_ERROR("homescreen-service","can't find key=%s, %s.", _keyOperation, _keyData);
        return;
    }

    std::string id = json_object_get_string(obj_data);
    std::string appid = id2appid(id);
    if(isPeripheryApp(appid.c_str())) {
        HMI_NOTICE("homescreen-service", "install/uninstall application is periphery.");
        return;
    }

    std::string oper = json_object_get_string(obj_oper);
    if(oper == _keyInstall) {
        struct json_object* j_runnable = nullptr;
        int ret = afmmain->runnables(api, &j_runnable);
        if(!ret) {
            struct json_object *j_found = retrieveRunnables(j_runnable, id);
            if(j_found == nullptr) {
                HMI_NOTICE("homescreen-service", "installed application isn't runnables.");
                json_object_put(j_runnable);
                return;
            }
            addAppDetail(j_found);
            pushAppListChangedEvent(_keyInstall, j_found);
        }
        else {
            HMI_ERROR("homescreen-service","get runnalbes failed.");
        }
        json_object_put(j_runnable);
    }
    else if(oper == _keyUninstall) {
        std::string appid_checked = checkAppId(appid);
        if(appid_checked.empty()) {
            HMI_NOTICE("homescreen-service","uninstalled application isn't in runnables list, appid=%s.", appid.c_str());
            return;
        }
        pushAppListChangedEvent(_keyUninstall, obj_data);
        removeAppDetail(appid);
    }
    else {
        HMI_ERROR("homescreen-service","operation error.");
    }
}

/**
 * parse application detail function
 *
 * #### Parameters
 *  - object : [IN] the detail of application
 *  - info   : [OUT] parsed application detail
 *
 * #### Return
 * the appid of application liked "dashboard"
 *
 */
std::string HS_AppInfo::parseAppDetail(struct json_object *object, AppDetail &info) const
{
    struct json_object *name, *id;
    if(json_object_object_get_ex(object, _keyName, &name) == 0
    || json_object_object_get_ex(object, _keyId, &id) == 0) {
        HMI_ERROR("homescreen-service","can't find key=%s, %s.", _keyName, _keyId);
        return std::string();
    }
    std::string appid = id2appid(json_object_get_string(id));
    bool periphery = isPeripheryApp(appid.c_str());

    info = { json_object_get_string(name),
             json_object_get_string(id),
             json_object_to_json_string(object),
             periphery
    };
    return appid;
}

/**
 * add application detail to list function
 *
 * #### Parameters
 *  - object : application detail
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::addAppDetail(struct json_object *object)
{
    AppDetail info;
    std::string appid = parseAppDetail(object, info);
    if(appid.empty()) {
        HMI_ERROR("homescreen-service","application id error");
        return;
    }

    std::lock_guard<std::mutex> lock(this->mtx);
    appid2name[appid] = info.name;
    name2appid[info.name] = appid;
    app_detail_list[appid] = std::move(info);
}

/**
 * remove application detail from list function
 *
 * #### Parameters
 *  - appid : application id
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::removeAppDetail(std::string appid)
{
    std::lock_guard<std::mutex> lock(this->mtx);
    auto it = app_detail_list.find(appid);
    if(it != app_detail_list.end()) {
        appid2name.erase(appid);
        name2appid.erase(it->second.name);
        app_detail_list.erase(it);
    }
    else {
        HMI_WARNING("homescreen-service","erase application(%s) wasn't in applist.", appid.c_str());
    }
}

/**
 * push app_list_changed event function
 *
 * #### Parameters
 *  - oper: install/uninstall
 *  - object: event data
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::pushAppListChangedEvent(const char *oper, struct json_object *object)
{
    HMI_NOTICE("homescreen-service","called.");
    struct json_object *push_obj = json_object_new_object();
    json_object_object_add(push_obj, _keyOperation, json_object_new_string(oper));
    json_object_object_add(push_obj, _keyData, object);

    HS_ClientManager::instance()->pushEvent(_keyApplistChanged, push_obj);
}

/**
 * retrieve runnables function
 *
 * #### Parameters
 *  - obj_runnables: runnables array
 *  - id: application id
 *
 * #### Return
 * found application detail
 *
 */
struct json_object* HS_AppInfo::retrieveRunnables(struct json_object *obj_runnables, std::string id)
{
    struct json_object *j_found = nullptr;
    if(json_object_get_type(obj_runnables) ==  json_type_array) {
        int array_len = json_object_array_length(obj_runnables);
        for (int i = 0; i < array_len; ++i) {
            struct json_object *obj = json_object_array_get_idx(obj_runnables, i);
            struct json_object *j_id;
            if(json_object_object_get_ex(obj, _keyId, &j_id) == 0) {
                HMI_WARNING("homescreen-service","can't find id.");
                continue;
            }
            if(id == json_object_get_string(j_id)) {
                j_found = obj;
                break;
            }
        }
    }
    else {
        HMI_ERROR("homescreen-service","Apps information input error.");
    }
    return j_found;
}

/**
 * convert id to appid function
 *
 * #### Parameters
 *  - id : the id of application liked "dashboard@0.1"
 *
 * #### Return
 * the appid of application liked "dashboard"
 *
 */
std::string HS_AppInfo::id2appid(const std::string &id) const
{
    std::string appid;
    std::size_t pos = id.find("@");
    if(pos != std::string::npos) {
        appid = id.substr(0,pos);
    }
    else {
        HMI_ERROR("homescreen-service","input id error.");
    }
    return appid;
}

/**
 * get runnables list
 *
 * #### Parameters
 *  - object : runnables list,json array
 *
 * #### Return
 * None
 *
 */
void HS_AppInfo::getRunnables(struct json_object **object)
{
    if(json_object_get_type(*object) !=  json_type_array) {
        HMI_ERROR("homescreen-service","json type error.");
        return;
    }

    std::lock_guard<std::mutex> lock(this->mtx);
    for(auto it : app_detail_list) {
        if(!it.second.periphery)
            json_object_array_add(*object, json_tokener_parse(it.second.detail.c_str()));
    }
}

/**
 * check appid function
 *
 * #### Parameters
 *  - appid : appid liked "dashboard"
 *
 * #### Return
 * success : the correct appid
 * fail : empty string
 *
 */
std::string HS_AppInfo::checkAppId(const std::string &appid)
{
    std::lock_guard<std::mutex> lock(this->mtx);
    auto it_appid = appid2name.find(appid);
    if(it_appid != appid2name.end())
        return it_appid->first;

    auto it_name = name2appid.find(appid);
    if(it_name != name2appid.end())
        return it_name->second;

    return std::string();
}

/**
 * check if application is a runnable periphery application function
 *
 * #### Parameters
 *  - appid : appid liked "launcher"
 *
 * #### Return
 * true : periphery
 * false : not periphery
 *
 */
bool HS_AppInfo::isPeripheryApp(const char *appid) const
{
    bool ret = false;
    for(auto m : periphery_app_list) {
        if(strcasecmp(appid, m) == 0) {
            ret = true;
            break;
        }
    }
    return ret;
}

/**
 * get application specific property
 *
 * #### Parameters
 *  - appid : appid liked "launcher"
 *  - key : the keyword
 *
 * #### Return
 * application property
 *
 */
std::string HS_AppInfo::getAppProperty(const std::string appid, std::string key) const
{
    std::string value = "";
    auto it = app_detail_list.find(appid);
    if(it != app_detail_list.end()) {
        value = it->second.getProperty(key);
    }
    return value;
}