/**
 * @file lv_fragment_manager.c
 *
 */

/*********************
 *      INCLUDES
 *********************/

#include "lv_fragment.h"

#if LV_USE_FRAGMENT

#include "../../misc/lv_ll.h"

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/
typedef struct _lv_fragment_stack_item_t {
    lv_fragment_managed_states_t * states;
} lv_fragment_stack_item_t;

struct _lv_fragment_manager_t {
    lv_fragment_t * parent;
    /**
     * Linked list to store attached fragments
     */
    lv_ll_t attached;
    /**
     * Linked list to store fragments in stack
     */
    lv_ll_t stack;
};


/**********************
 *  STATIC PROTOTYPES
 **********************/

static void item_create_obj(lv_fragment_managed_states_t * item);

static void item_del_obj(lv_fragment_managed_states_t * item);

static void item_del_fragment(lv_fragment_managed_states_t * item);

static lv_fragment_managed_states_t * fragment_attach(lv_fragment_manager_t * manager, lv_fragment_t * fragment,
                                                      lv_obj_t * const * container);

/**********************
 *  STATIC VARIABLES
 **********************/

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 **********************/

lv_fragment_manager_t * lv_fragment_manager_create(lv_fragment_t * parent)
{
    lv_fragment_manager_t * instance = lv_malloc(sizeof(lv_fragment_manager_t));
    lv_memzero(instance, sizeof(lv_fragment_manager_t));
    instance->parent = parent;
    _lv_ll_init(&instance->attached, sizeof(lv_fragment_managed_states_t));
    _lv_ll_init(&instance->stack, sizeof(lv_fragment_stack_item_t));
    return instance;
}

void lv_fragment_manager_del(lv_fragment_manager_t * manager)
{
    LV_ASSERT_NULL(manager);
    lv_fragment_managed_states_t * states;
    _LV_LL_READ_BACK(&manager->attached, states) {
        item_del_obj(states);
        item_del_fragment(states);
    }
    _lv_ll_clear(&manager->attached);
    _lv_ll_clear(&manager->stack);
    lv_free(manager);
}

void lv_fragment_manager_create_obj(lv_fragment_manager_t * manager)
{
    LV_ASSERT_NULL(manager);
    lv_fragment_stack_item_t * top = _lv_ll_get_tail(&manager->stack);
    lv_fragment_managed_states_t * states = NULL;
    _LV_LL_READ(&manager->attached, states) {
        if(states->in_stack && top->states != states) {
            /*Only create obj for top item in stack*/
            continue;
        }
        item_create_obj(states);
    }
}

void lv_fragment_manager_del_obj(lv_fragment_manager_t * manager)
{
    LV_ASSERT_NULL(manager);
    lv_fragment_managed_states_t * states = NULL;
    _LV_LL_READ_BACK(&manager->attached, states) {
        item_del_obj(states);
    }
}

void lv_fragment_manager_add(lv_fragment_manager_t * manager, lv_fragment_t * fragment, lv_obj_t * const * container)
{
    lv_fragment_managed_states_t * states = fragment_attach(manager, fragment, container);
    if(!manager->parent || manager->parent->managed->obj_created) {
        item_create_obj(states);
    }
}

void lv_fragment_manager_remove(lv_fragment_manager_t * manager, lv_fragment_t * fragment)
{
    LV_ASSERT_NULL(manager);
    LV_ASSERT_NULL(fragment);
    LV_ASSERT_NULL(fragment->managed);
    LV_ASSERT(fragment->managed->manager == manager);
    lv_fragment_managed_states_t * states = fragment->managed;
    lv_fragment_managed_states_t * prev = NULL;
    bool was_top = false;
    if(states->in_stack) {
        void * stack_top = _lv_ll_get_tail(&manager->stack);
        lv_fragment_stack_item_t * item = NULL;
        _LV_LL_READ_BACK(&manager->stack, item) {
            if(item->states == states) {
                was_top = stack_top == item;
                void * stack_prev = _lv_ll_get_prev(&manager->stack, item);
                if(!stack_prev) break;
                prev = ((lv_fragment_stack_item_t *) stack_prev)->states;
                break;
            }
        }
        if(item) {
            _lv_ll_remove(&manager->stack, item);
            lv_free(item);
        }
    }
    item_del_obj(states);
    item_del_fragment(states);
    _lv_ll_remove(&manager->attached, states);
    lv_free(states);
    if(prev && was_top) {
        item_create_obj(prev);
    }
}

void lv_fragment_manager_push(lv_fragment_manager_t * manager, lv_fragment_t * fragment, lv_obj_t * const * container)
{
    lv_fragment_stack_item_t * top = _lv_ll_get_tail(&manager->stack);
    if(top != NULL) {
        item_del_obj(top->states);
    }
    lv_fragment_managed_states_t * states = fragment_attach(manager, fragment, container);
    states->in_stack = true;
    /*Add fragment to the top of the stack*/
    lv_fragment_stack_item_t * item = _lv_ll_ins_tail(&manager->stack);
    lv_memzero(item, sizeof(lv_fragment_stack_item_t));
    item->states = states;
    item_create_obj(states);
}

bool lv_fragment_manager_pop(lv_fragment_manager_t * manager)
{
    lv_fragment_t * top = lv_fragment_manager_get_top(manager);
    if(top == NULL) return false;
    lv_fragment_manager_remove(manager, top);
    return true;
}

void lv_fragment_manager_replace(lv_fragment_manager_t * manager, lv_fragment_t * fragment,
                                 lv_obj_t * const * container)
{
    lv_fragment_t * top = lv_fragment_manager_find_by_container(manager, *container);
    if(top != NULL) {
        lv_fragment_manager_remove(manager, top);
    }
    lv_fragment_manager_add(manager, fragment, container);
}

bool lv_fragment_manager_send_event(lv_fragment_manager_t * manager, int code, void * userdata)
{
    LV_ASSERT_NULL(manager);
    lv_fragment_managed_states_t * p = NULL;
    _LV_LL_READ_BACK(&manager->attached, p) {
        if(!p->obj_created || p->destroying_obj) continue;
        lv_fragment_t * instance = p->instance;
        if(!instance) continue;
        if(lv_fragment_manager_send_event(instance->child_manager, code, userdata)) return true;
        if(p->cls->event_cb && p->cls->event_cb(instance, code, userdata)) return true;
    }
    return false;
}

size_t lv_fragment_manager_get_stack_size(lv_fragment_manager_t * manager)
{
    LV_ASSERT_NULL(manager);
    return _lv_ll_get_len(&manager->stack);
}

lv_fragment_t * lv_fragment_manager_get_top(lv_fragment_manager_t * manager)
{
    LV_ASSERT(manager);
    lv_fragment_stack_item_t * top = _lv_ll_get_tail(&manager->stack);
    if(!top)return NULL;
    return top->states->instance;
}

lv_fragment_t * lv_fragment_manager_find_by_container(lv_fragment_manager_t * manager, const lv_obj_t * container)
{
    LV_ASSERT(manager);
    lv_fragment_managed_states_t * states;
    _LV_LL_READ(&manager->attached, states) {
        if(*states->container == container) return states->instance;
    }
    return NULL;
}

lv_fragment_t * lv_fragment_manager_get_parent_fragment(lv_fragment_manager_t * manager)
{
    LV_ASSERT_NULL(manager);
    return manager->parent;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/

static void item_create_obj(lv_fragment_managed_states_t * item)
{
    LV_ASSERT(item->instance);
    lv_fragment_create_obj(item->instance, item->container ? *item->container : NULL);
}

static void item_del_obj(lv_fragment_managed_states_t * item)
{
    lv_fragment_del_obj(item->instance);
}

/**
 * Detach, then destroy fragment
 * @param item fragment states
 */
static void item_del_fragment(lv_fragment_managed_states_t * item)
{
    lv_fragment_t * instance = item->instance;
    if(instance->cls->detached_cb) {
        instance->cls->detached_cb(instance);
    }
    instance->managed = NULL;
    lv_fragment_del(instance);
    item->instance = NULL;
}


static lv_fragment_managed_states_t * fragment_attach(lv_fragment_manager_t * manager, lv_fragment_t * fragment,
                                                      lv_obj_t * const * container)
{
    LV_ASSERT(manager);
    LV_ASSERT(fragment);
    LV_ASSERT(fragment->managed == NULL);
    lv_fragment_managed_states_t * states = _lv_ll_ins_tail(&manager->attached);
    lv_memzero(states, sizeof(lv_fragment_managed_states_t));
    states->cls = fragment->cls;
    states->manager = manager;
    states->container = container;
    states->instance = fragment;
    fragment->managed = states;
    if(fragment->cls->attached_cb) {
        fragment->cls->attached_cb(fragment);
    }
    return states;
}

#endif /*LV_USE_FRAGMENT*/
