#include "example.h"
#include "helper.h"
#if LITE_BUILD_WITH_MGE
#include "lite-c/global_c.h"
#include "lite-c/network_c.h"
#include "lite-c/tensor_c.h"

#include <thread>

#define LITE_CAPI_CHECK(_expr)                         \
    do {                                               \
        int _ret = (_expr);                            \
        if (_ret) {                                    \
            LITE_EXAMPLE_THROW(LITE_get_last_error()); \
        }                                              \
    } while (0)

bool basic_c_interface(const lite::example::Args& args) {
    std::string network_path = args.model_path;
    std::string input_path = args.input_path;

    //! read input data to lite::tensor
    auto src_tensor = lite::example::parse_npy(input_path);
    void* src_ptr = src_tensor->get_memory_ptr();

    //! create and load the network
    LiteNetwork c_network;
    LITE_CAPI_CHECK(
            LITE_make_network(&c_network, *default_config(), *default_network_io()));

    LITE_CAPI_CHECK(LITE_load_model_from_path(c_network, network_path.c_str()));

    //! set input data to input tensor
    LiteTensor c_input_tensor;
    LITE_CAPI_CHECK(LITE_get_io_tensor(c_network, "data", LITE_IO, &c_input_tensor));
    void* dst_ptr;
    size_t length_in_byte;
    LITE_CAPI_CHECK(
            LITE_get_tensor_total_size_in_byte(c_input_tensor, &length_in_byte));
    LITE_CAPI_CHECK(LITE_get_tensor_memory(c_input_tensor, &dst_ptr));
    //! copy or forward data to network
    memcpy(dst_ptr, src_ptr, length_in_byte);

    //! forward
    LITE_CAPI_CHECK(LITE_forward(c_network));
    LITE_CAPI_CHECK(LITE_wait(c_network));

    //! get the output data or read tensor data
    const char* output_name;
    LiteTensor c_output_tensor;
    //! get the first output tensor name
    LITE_CAPI_CHECK(LITE_get_output_name(c_network, 0, &output_name));
    LITE_CAPI_CHECK(
            LITE_get_io_tensor(c_network, output_name, LITE_IO, &c_output_tensor));
    void* output_ptr;
    size_t length_output_in_byte;
    LITE_CAPI_CHECK(LITE_get_tensor_memory(c_output_tensor, &output_ptr));
    LITE_CAPI_CHECK(LITE_get_tensor_total_size_in_byte(
            c_output_tensor, &length_output_in_byte));

    size_t out_length = length_output_in_byte / sizeof(float);
    printf("length=%zu\n", out_length);

    float max = -1.0f;
    float sum = 0.0f;
    for (size_t i = 0; i < out_length; i++) {
        float data = static_cast<float*>(output_ptr)[i];
        sum += data;
        if (max < data)
            max = data;
    }
    printf("max=%e, sum=%e\n", max, sum);
    return true;
}

bool device_io_c_interface(const lite::example::Args& args) {
    std::string network_path = args.model_path;
    std::string input_path = args.input_path;

    //! read input data to lite::tensor
    auto src_tensor = lite::example::parse_npy(input_path);
    void* src_ptr = src_tensor->get_memory_ptr();
    size_t length_read_in = src_tensor->get_tensor_total_size_in_byte();

    //! create and load the network
    LiteNetwork c_network;
    LITE_CAPI_CHECK(
            LITE_make_network(&c_network, *default_config(), *default_network_io()));
    LITE_CAPI_CHECK(LITE_load_model_from_path(c_network, network_path.c_str()));

    //! set input data to input tensor
    LiteTensor c_input_tensor;
    size_t length_tensor_in;
    LITE_CAPI_CHECK(LITE_get_io_tensor(c_network, "data", LITE_IO, &c_input_tensor));
    LITE_CAPI_CHECK(
            LITE_get_tensor_total_size_in_byte(c_input_tensor, &length_tensor_in));
    if (length_read_in != length_tensor_in) {
        LITE_EXAMPLE_THROW(
                "The input data size is not match the network input tensro "
                "size,\n");
    }
    LITE_CAPI_CHECK(
            LITE_reset_tensor_memory(c_input_tensor, src_ptr, length_tensor_in));

    //! reset the output tensor memory with user allocated memory
    size_t out_length = 1000;
    LiteLayout output_layout{{1, 1000}, 2, LiteDataType::LITE_FLOAT};
    std::shared_ptr<float> ptr(new float[out_length], [](float* ptr) { delete[] ptr; });
    const char* output_name;
    LiteTensor c_output_tensor;
    LITE_CAPI_CHECK(LITE_get_output_name(c_network, 0, &output_name));
    LITE_CAPI_CHECK(
            LITE_get_io_tensor(c_network, output_name, LITE_IO, &c_output_tensor));
    LITE_CAPI_CHECK(LITE_reset_tensor(c_output_tensor, output_layout, ptr.get()));

    //! forward
    LITE_CAPI_CHECK(LITE_forward(c_network));
    LITE_CAPI_CHECK(LITE_wait(c_network));

    printf("length=%zu\n", out_length);

    float max = -1.0f;
    float sum = 0.0f;
    void* out_data = ptr.get();
    for (size_t i = 0; i < out_length; i++) {
        float data = static_cast<float*>(out_data)[i];
        sum += data;
        if (max < data)
            max = data;
    }
    printf("max=%e, sum=%e\n", max, sum);
    return true;
}

namespace {
volatile bool finished = false;
int async_callback(void) {
#if !__DEPLOY_ON_XP_SP2__
    std::cout << "worker thread_id:" << std::this_thread::get_id() << std::endl;
#endif
    finished = true;
    return 0;
}
}  // namespace

bool async_c_interface(const lite::example::Args& args) {
    std::string network_path = args.model_path;
    std::string input_path = args.input_path;

    //! read input data to lite::tensor
    auto src_tensor = lite::example::parse_npy(input_path);
    void* src_ptr = src_tensor->get_memory_ptr();

    LiteNetwork c_network;
    LiteConfig config = *default_config();
    config.options.var_sanity_check_first_run = false;
    LITE_CAPI_CHECK(LITE_make_network(&c_network, config, *default_network_io()));
    LITE_CAPI_CHECK(LITE_load_model_from_path(c_network, network_path.c_str()));

    //! set input data to input tensor
    LiteTensor c_input_tensor;
    size_t length_tensor_in;
    LITE_CAPI_CHECK(LITE_get_io_tensor(c_network, "data", LITE_IO, &c_input_tensor));
    LITE_CAPI_CHECK(
            LITE_get_tensor_total_size_in_byte(c_input_tensor, &length_tensor_in));
    LITE_CAPI_CHECK(
            LITE_reset_tensor_memory(c_input_tensor, src_ptr, length_tensor_in));

#if !__DEPLOY_ON_XP_SP2__
    std::cout << "user thread_id:" << std::this_thread::get_id() << std::endl;
#endif

    LITE_CAPI_CHECK(LITE_set_async_callback(c_network, async_callback));
    //! forward
    LITE_CAPI_CHECK(LITE_forward(c_network));
    size_t count = 0;
    while (finished == false) {
        count++;
    }
    printf("The count is %zu\n", count);
    finished = false;

    //! get the output data or read tensor data
    const char* output_name;
    LiteTensor c_output_tensor;
    //! get the first output tensor name
    LITE_CAPI_CHECK(LITE_get_output_name(c_network, 0, &output_name));
    LITE_CAPI_CHECK(
            LITE_get_io_tensor(c_network, output_name, LITE_IO, &c_output_tensor));
    void* output_ptr;
    size_t length_output_in_byte;
    LITE_CAPI_CHECK(LITE_get_tensor_memory(c_output_tensor, &output_ptr));
    LITE_CAPI_CHECK(LITE_get_tensor_total_size_in_byte(
            c_output_tensor, &length_output_in_byte));

    size_t out_length = length_output_in_byte / sizeof(float);
    printf("length=%zu\n", out_length);

    float max = -1.0f;
    float sum = 0.0f;
    for (size_t i = 0; i < out_length; i++) {
        float data = static_cast<float*>(output_ptr)[i];
        sum += data;
        if (max < data)
            max = data;
    }
    printf("max=%e, sum=%e\n", max, sum);
    return true;
}

REGIST_EXAMPLE("basic_c_interface", basic_c_interface);
REGIST_EXAMPLE("device_io_c_interface", device_io_c_interface);
REGIST_EXAMPLE("async_c_interface", async_c_interface);

#endif
// vim: syntax=cpp.doxygen foldmethod=marker foldmarker=f{{{,f}}}
