/*!The Treasure Box Library
 *
 * 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.
 *
 * Copyright (C) 2009-present, TBOOX Open Source Group.
 *
 * @author      ruki
 * @file        coroutine.h
 * @ingroup     coroutine
 *
 */

/* //////////////////////////////////////////////////////////////////////////////////////
 * trace
 */
#define TB_TRACE_MODULE_NAME            "coroutine"
#define TB_TRACE_MODULE_DEBUG           (0)

/* //////////////////////////////////////////////////////////////////////////////////////
 * includes
 */
#include "coroutine.h"
#include "scheduler.h"
#include "../impl/impl.h"

/* //////////////////////////////////////////////////////////////////////////////////////
 * private implementation
 */
tb_lo_coroutine_t* tb_lo_coroutine_init(tb_lo_scheduler_ref_t scheduler, tb_lo_coroutine_func_t func, tb_cpointer_t priv, tb_lo_coroutine_free_t free)
{
    // check
    tb_assert_and_check_return_val(scheduler && func, tb_null);

    // done
    tb_bool_t           ok = tb_false;
    tb_lo_coroutine_t*  coroutine = tb_null;
    do
    {
        // make coroutine
        coroutine = tb_malloc0_type(tb_lo_coroutine_t);
        tb_assert_and_check_break(coroutine);

        // init core
        tb_lo_core_init(&coroutine->core);

        // save scheduler
        coroutine->scheduler = scheduler;

        // init function and user private data
        coroutine->func = func;
        coroutine->priv = priv;
        coroutine->free = free;

        // ok
        ok = tb_true;

    } while (0);

    // failed?
    if (!ok)
    {
        // exit it
        if (coroutine) tb_lo_coroutine_exit(coroutine);
        coroutine = tb_null;
    }

    // trace
    tb_trace_d("init %p", coroutine);

    // ok?
    return coroutine;
}
tb_bool_t tb_lo_coroutine_reinit(tb_lo_coroutine_t* coroutine, tb_lo_coroutine_func_t func, tb_cpointer_t priv, tb_lo_coroutine_free_t free)
{
    // check
    tb_assert_and_check_return_val(coroutine && func, tb_false);
    tb_assert_and_check_return_val(coroutine->scheduler && tb_lo_core_state(coroutine) == TB_STATE_END, tb_false);

    // init core
    tb_lo_core_init(&coroutine->core);

    // init function and user private data
    coroutine->func = func;
    coroutine->priv = priv;
    coroutine->free = free;

    // init rs data
    tb_memset(&coroutine->rs, 0, sizeof(coroutine->rs));

    // ok
    return tb_true;
}
tb_void_t tb_lo_coroutine_exit(tb_lo_coroutine_t* coroutine)
{
    // check
    tb_assert_and_check_return(coroutine && tb_lo_core_state(coroutine) == TB_STATE_END);

    // trace
    tb_trace_d("exit: %p", coroutine);

    // exit it
    tb_free(coroutine);
}
tb_lo_scheduler_ref_t tb_lo_coroutine_scheduler_(tb_lo_coroutine_ref_t self)
{
    // check
    tb_lo_coroutine_t* coroutine = (tb_lo_coroutine_t*)self;
    tb_assert(coroutine);

    // get scheduler
    return coroutine->scheduler;
}
tb_void_t tb_lo_coroutine_sleep_(tb_lo_coroutine_ref_t self, tb_long_t interval)
{
    // check
    tb_lo_coroutine_t* coroutine = (tb_lo_coroutine_t*)self;
    tb_assert(coroutine);

    // get scheduler
    tb_lo_scheduler_t* scheduler = (tb_lo_scheduler_t*)coroutine->scheduler;
    tb_assert(scheduler);

    // init io scheduler first
    if (!tb_lo_scheduler_io_need(scheduler)) return ;

    // sleep it
    tb_lo_scheduler_io_sleep(scheduler->scheduler_io, interval);
}
tb_bool_t tb_lo_coroutine_waitio_(tb_lo_coroutine_ref_t self, tb_poller_object_ref_t object, tb_size_t events, tb_long_t timeout)
{
    // check
    tb_lo_coroutine_t* coroutine = (tb_lo_coroutine_t*)self;
    tb_assert(coroutine);

    // get scheduler
    tb_lo_scheduler_t* scheduler = (tb_lo_scheduler_t*)coroutine->scheduler;
    tb_assert(scheduler);

    // init io scheduler first
    if (!tb_lo_scheduler_io_need(scheduler)) return tb_false;

    // wait it
    return tb_lo_scheduler_io_wait(scheduler->scheduler_io, object, events, timeout);
}
#ifndef TB_CONFIG_MICRO_ENABLE
tb_bool_t tb_lo_coroutine_waitproc_(tb_lo_coroutine_ref_t self, tb_poller_object_ref_t object, tb_long_t timeout)
{
    // check
    tb_lo_coroutine_t* coroutine = (tb_lo_coroutine_t*)self;
    tb_assert(coroutine);

    // get scheduler
    tb_lo_scheduler_t* scheduler = (tb_lo_scheduler_t*)coroutine->scheduler;
    tb_assert(scheduler);

    // init io scheduler first
    if (!tb_lo_scheduler_io_need(scheduler)) return tb_false;

    // wait it
    return tb_lo_scheduler_io_wait_proc(scheduler->scheduler_io, object, timeout);
}
tb_long_t tb_lo_coroutine_proc_status_(tb_lo_coroutine_ref_t self)
{
    // check
    tb_lo_coroutine_t* coroutine = (tb_lo_coroutine_t*)self;
    tb_assert(coroutine);

    // get process status
    return coroutine->rs.wait.object_event;
}
#endif
tb_long_t tb_lo_coroutine_waitret_(tb_lo_coroutine_ref_t self)
{
    // check
    tb_lo_coroutine_t* coroutine = (tb_lo_coroutine_t*)self;
    tb_assert(coroutine);

    // get wait result
    return coroutine->rs.wait.result;
}
tb_void_t tb_lo_coroutine_pass_free_(tb_cpointer_t priv)
{
    if (priv) tb_free(priv);
}
tb_pointer_t tb_lo_coroutine_pass1_make_(tb_size_t type_size, tb_cpointer_t value, tb_size_t offset, tb_size_t size)
{
    // check
    tb_assert(type_size && value && offset + size <= type_size);

    // make data
    tb_byte_t* data = tb_malloc0_bytes(type_size);
    if (data) tb_memcpy(data + offset, value, size);

    // ok?
    return data;
}

/* //////////////////////////////////////////////////////////////////////////////////////
 * public implementation
 */
tb_bool_t tb_lo_coroutine_start(tb_lo_scheduler_ref_t self, tb_lo_coroutine_func_t func, tb_cpointer_t priv, tb_lo_coroutine_free_t free)
{
    return tb_lo_scheduler_start((tb_lo_scheduler_t*)self, func, priv, free);
}
tb_void_t tb_lo_coroutine_resume(tb_lo_coroutine_ref_t self)
{
    tb_lo_scheduler_resume((tb_lo_scheduler_t*)tb_lo_coroutine_scheduler_(self), (tb_lo_coroutine_t*)self);
}
