/**
  ******************************************************************************
  * @file   lv_obj_datasubs.c
  * @author Sifli software development team
  ******************************************************************************
*/
/**
 * @attention
 * Copyright (c) 2019 - 2022,  Sifli Technology
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form, except as embedded into a Sifli integrated circuit
 *    in a product or a software update for such product, must reproduce the above
 *    copyright notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of Sifli nor the names of its contributors may be used to endorse
 *    or promote products derived from this software without specific prior written permission.
 *
 * 4. This software, with or without modification, must only be used with a
 *    Sifli integrated circuit.
 *
 * 5. Any software provided in binary form under this license must not be reverse
 *    engineered, decompiled, modified and/or disassembled.
 *
 * THIS SOFTWARE IS PROVIDED BY SIFLI TECHNOLOGY "AS IS" AND ANY EXPRESS
 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL SIFLI TECHNOLOGY OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "lv_obj_datasubs.h"
#include "app_mem.h"
#ifndef DBG_LVL
    #define DBG_LVL  DBG_LOG
#endif
#include "log.h"
#include "rtdbg.h"

/**
 * Subscribe to list nesting operation check, nested operations are not supported.
 */
static int lv_datasubs_nested_check = 0;
#define DATASUBS_NESTED_CHECK() \
    RT_ASSERT(!lv_datasubs_nested_check);\
    lv_datasubs_nested_check = 1;

#define DATASUBS_NESTED_CHECK_RESET() \
    RT_ASSERT(lv_datasubs_nested_check);\
    lv_datasubs_nested_check = 0;

/**
 * Data subscription manager list
 */
static rt_list_t obj_datasubs_list;

/**
 * log open flag : 1 open, 0 close
 */
static uint8_t datasubs_log = 0;

extern void lvgl_host_thread_check(const char *file, const char *func);
extern rt_err_t lv_timer_mutex_take(void);
extern rt_err_t lv_timer_mutex_rel(void);

/**
 * Gets the specified obj node.
 * @param obj subscript obj.
 * @param idx subscript index.
 * @return info node from 'obj_datasubs_list'
 */
static lv_obj_datasubs_t *lv_obj_datasubs_get_node(lv_obj_t *obj, const char *id, uint32_t type)
{
    lv_obj_datasubs_t *node = NULL;
    rt_list_t *pos;
    DATASUBS_NESTED_CHECK();
    rt_list_for_each(pos, (&obj_datasubs_list))
    {
        node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        RT_ASSERT(node);
        if (!rt_strcmp(id, node->id) && type == node->type && obj == node->obj)
        {
            DATASUBS_NESTED_CHECK_RESET();
            return node;
        }
    }
    DATASUBS_NESTED_CHECK_RESET();
    return NULL;
}

/**
 * Deletes the specified subscription node
 * @param del_node the node to be deleted.
 * @return RT_EOK : success; RT_ERROR : can not find this node.
 */
static int lv_obj_datasubs_del_node(lv_obj_datasubs_t *del_node)
{
    int err = RT_ERROR;
    lv_obj_datasubs_t *node = NULL;
    rt_list_t *pos, *next;
    DATASUBS_NESTED_CHECK();

    rt_list_for_each_safe(pos, next, (&obj_datasubs_list))
    {
        node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        if (del_node == node)
        {
            rt_list_remove(&node->list);
            app_free(node);
            err = RT_EOK;
            break;
        }
    }
    DATASUBS_NESTED_CHECK_RESET();

    if (RT_EOK != err)
        LOG_I("%s: node %p is not exit!", __func__, del_node);

    return err;
}

/**
 * Deletes the all subscription node
 * @return RT_EOK : success.
 */
static int lv_obj_datasubs_del_all(void)
{
    int err = RT_EOK;
    lv_obj_datasubs_t *node = NULL;
    rt_list_t *pos, *next;
    DATASUBS_NESTED_CHECK();

    rt_list_for_each_safe(pos, next, (&obj_datasubs_list))
    {
        node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        RT_ASSERT(node);
        rt_list_remove(&node->list);
        app_free(node);
    }
    DATASUBS_NESTED_CHECK_RESET();
    return err;
}

int lv_obj_datasubs_notify(const char *id, uint32_t type, const void *data, uint16_t len, void *user_data)
{
    rt_list_t *pos, *next;
    int err = RT_ERROR;

    lv_timer_mutex_take();

    DATASUBS_NESTED_CHECK();

    rt_list_for_each_safe(pos, next, (&obj_datasubs_list))
    {
        lv_obj_datasubs_t *node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        RT_ASSERT(node);
        if ((NULL == id || 0 == rt_strcmp(id, node->id)) && node->type == type)
        {
            if (datasubs_log)
                LOG_I("%s: node %p obj %p id %s type %x data %p len %d!", __func__, node, node->obj, node->id, node->type, data, len);

            if (node->cb)
            {
                node->cb(node->obj, type, data, len, user_data ? user_data : node->user_data);
            }
            err = RT_EOK;
        }
    }
    DATASUBS_NESTED_CHECK_RESET();
    if (RT_EOK != err && datasubs_log)
        LOG_I("%s: id %s is not subscribed!", __func__, id);

    lv_timer_mutex_rel();

    return err;
}

int lv_obj_data_subscribe(lv_obj_t *obj, const char *id, uint32_t type, lv_obj_datasubs_cb_t cb, void *user_data)
{
    int err = RT_EOK;

    lvgl_host_thread_check(__FILE__, __func__);

    lv_obj_datasubs_t *src_node = lv_obj_datasubs_get_node(obj, id, type);
    if (src_node)
    {
        src_node->cb = cb;
        src_node->user_data = user_data;
        LOG_I("%s: id %s type %x obj %p already subscribed!", __func__, id, type, obj);
        return err;
    }

    lv_obj_datasubs_t *node_desc = (lv_obj_datasubs_t *) app_calloc(1, sizeof(lv_obj_datasubs_t));
    RT_ASSERT(node_desc);

    strncpy(node_desc->id, id, MAX_DATASUBS_NAME_LEN - 1);
    node_desc->id[MAX_DATASUBS_NAME_LEN - 1] = 0;

    node_desc->type = type;
    node_desc->obj = obj;
    node_desc->cb = cb;
    node_desc->user_data = user_data;

    rt_list_init(&node_desc->list);
    rt_list_insert_after(&obj_datasubs_list, &node_desc->list);

    if (datasubs_log)
        LOG_I("%s: id %s type %x node_desc %p obj %p!", __func__, id, type, node_desc, obj);

    return err;
}

int lv_obj_data_unsubscribe(lv_obj_t *obj, const char *id, uint32_t type)
{
    lv_obj_datasubs_t *node = NULL;
    rt_list_t *pos, *next;
    int err = RT_ERROR;

    lvgl_host_thread_check(__FILE__, __func__);
    DATASUBS_NESTED_CHECK();
    rt_list_for_each_safe(pos, next, (&obj_datasubs_list))
    {
        node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        RT_ASSERT(node);
        if (0 == rt_strcmp(id, node->id) && obj == node->obj && node->type == type)
        {
            if (datasubs_log)
                LOG_I("%s: node %p id %s obj %p!", __func__, node, node->id, obj);

            rt_list_remove(&node->list);
            app_free(node);
            err = RT_EOK;
            break;
        }
    }
    DATASUBS_NESTED_CHECK_RESET();
    if (RT_EOK != err && datasubs_log)
        LOG_I("lv_obj_data_unsubscribe: id %s obj %p is not subscribed!", id, obj);
    return err;
}

int lv_obj_data_unsubscribe_all(const char *id)
{
    lv_obj_datasubs_t *node = NULL;
    rt_list_t *pos, *next;
    int err = RT_ERROR;

    lvgl_host_thread_check(__FILE__, __func__);
    DATASUBS_NESTED_CHECK();
    rt_list_for_each_safe(pos, next, (&obj_datasubs_list))
    {
        node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        RT_ASSERT(node);
        if (0 == rt_strcmp(id, node->id))
        {
            if (datasubs_log)
                LOG_I("%s: node %p id %s type %x obj %p!", __func__, node, node->id, node->type, node->obj);

            rt_list_remove(&node->list);
            app_free(node);
            err = RT_EOK;
        }
    }
    DATASUBS_NESTED_CHECK_RESET();
    if (RT_EOK != err && datasubs_log)
        LOG_I("lv_obj_data_unsubscribe: id %s is not subscribed!", id);

    return err;
}

int lv_obj_data_unbind(lv_obj_t *obj)
{
    lv_obj_datasubs_t *node = NULL;
    rt_list_t *pos, *next;
    int err = RT_ERROR;
    lvgl_host_thread_check(__FILE__, __func__);
    DATASUBS_NESTED_CHECK();
    rt_list_for_each_safe(pos, next, (&obj_datasubs_list))
    {
        node = rt_list_entry(pos, lv_obj_datasubs_t, list);
        RT_ASSERT(node);
        if (obj == node->obj)
        {
            if (datasubs_log)
                LOG_I("%s: node %p idx %x obj %p! \n", __func__, node, node->type, obj);

            rt_list_remove(&node->list);
            app_free(node);
            err = RT_EOK;
        }
    }
    DATASUBS_NESTED_CHECK_RESET();
    if (RT_EOK != err && datasubs_log)
        LOG_I("lv_obj_data_unsubscribe: obj %p is not subscribed! \n", obj);

    return err;
}


/**
 * List initialization
 */
static int lv_obj_datasubs_init(void)
{
    rt_list_init(&obj_datasubs_list);
    LOG_I("lv_obj_datasubs_init");

    return 0;
}

INIT_APP_EXPORT(lv_obj_datasubs_init);


#ifdef RT_USING_FINSH
#include <finsh.h>
static int lv_datasubs_log(void)
{
    datasubs_log = (datasubs_log + 1) & 0x01;
    LOG_I("lv_datasubs_log: %d", datasubs_log);
    return 0;
}
MSH_CMD_EXPORT_ALIAS(lv_datasubs_log, lv_datasubs, lv_datasubs: open or close lv_datasubs log);

#endif

