/**
 * @file main.cpp
 * @author 上海前路有光数字科技
 * @date 2024-03-15
 * @brief XLTApi C++版使用示例主程序
 * 本程序演示如何使用XLTApi进行初始化、登录、下单等操作。
 * 用户可参考本程序，结合自己的业务需求，开发自己的交易程序。
 */

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <atomic>
#include "xlt_api.h"
#include "my_trade_spi.h"
#include "my_quote_spi.h"

using namespace x1;

XLTApi * my_xapi = XLTApi::create(); ///< API实例，全局唯一
uint64_t global_session_id = 0;                         ///< 会话ID
std::atomic<bool> global_login_success(false);           ///< 是否正在登录标识
std::atomic<bool> global_login_finished(false);          ///< 已建立会话标识
std::atomic<uint64_t> global_request_id(0);             ///< 全局请求id
MyTradeSpi my_spi;
MyQuoteSpi my_quote_spi;


/**
 * @brief 【演示】获取下一个请求ID
 * XLTApi中使用request_id用于用户在异步回调中定位对应的请求。
 * XLTApi所有接口使用到request_id时，不对request_id做任何验证及业务假设，在回调中原样返回给用户。
 * 此方法用于演示request_id，用户可根据实际业务场景，自定义request_id。
 * @return 下一个请求ID
 */
uint64_t next_request_id() {
    global_request_id++;
    return global_request_id.load();
}

/**
 * @brief 【演示】XLTApi初始化
 *
 * 本方法演示如何配置以及初始化API，步骤如下：
 * 1、配置API
 * 2、初始化：设置API配置，以及注册交易回调接口和行情回调接口
 * @return 初始化是否成功
 * @remark XLTApi必须在使用之前进行初始化，并且只能初始化一次
 */
bool initialize() {
    // 配置
    XLTApiConfig config;
    config.set_trading_day(20240320);                   ///< （必填）设置交易日，格式YYYYMMDD，如20230901。用户需明确指定交易日，生产环境为当前交易日，测试环境为测试柜台指定的交易日。若与柜台交易日不一致，登录将报错。
    config.set_local_addr(                              ///< （必填）设置本地网卡IP地址。生产环境中，trade及quote对应网卡建议为solarflare网卡，solarflare网卡将自动使用ef_vi，以便达到最优的性能，非solarflare网卡则回退到内核sock模式
        "172.16.10.175",                           ///< agw 连接agw的本地网卡IP，生产环境需为AGW接入网段IP
        "172.16.10.175",                          ///< trade 连接快速报单的本地网卡IP，生产环境需为快速报单网段IP
        "172.16.10.175"                           ///< quote 接收行情数据的本地网卡IP，生产环境需为行情接收网段IP
        );
    config.add_agw_addr("192.168.5.73", 6060);     ///< （必填）添加网关地址, ip_addr agw IP地址, port agw端口。请向券商获取agw地址和端口，可重复调用添加多个agw地址，每次登录会随机选取当前可用的agw
    config.set_path(".");                               ///< （选填）设置API生成文件存放路径，如日志文件等，默认为当前路径
    config.set_log_level(x1::ApiLogLevel::LOG_TRACE);   ///< （选填）设置日志级别，默认为LOG_INFO
    config.set_request_timeout(10);                     ///< （选填）设置请求超时时间，单位秒，默认10秒。查询请求时，当达到此超时时间没有任何响应，将触发on_request_timeout，提示用户查询或委托超时
    config.set_order_timeout(10);                       ///< （选填）设置订单超时时间，单位秒，默认10秒。委托请求时，当达到此超时时间没有收到任何响应，将触发on_order_timeout回调，提示用户在此时间内未收到任何订单响应或回报，但不代表委托失败！！用户可选择调用resend_order_request接口重发订单。
    config.set_order_seq_self_define(false);            ///< （选填）设置是否启用用户自定义订单序号，默认false。
                                                        ///< 注意！！除非您非常清楚自定义序号规则，否则请勿开启。
                                                        ///< 默认情况下，API会自动生成订单序号，用户无需关心订单序号的生成规则；
                                                        ///< 若用户启用自定义订单序号，则用户在每次下单时，必须在xlt_order_insert_info_t或xlt_cancel_order_t结构体中指定order_sequence字段
                                                        ///< 注意！！启用自定义订单序号后，用户必须确保每次下单时，order_sequence字段值唯一，否则可能导致重单，重单将没有任何响应！！！order_sequence字段取值范围[0, 2^20-1]
                                                        ///< 注意！！启用自定义订单序号后，建议委托和撤单使用不同的订单序号分段，以便管理委托和撤单，例如委托使用0~500000，撤单使用500001~1000000
    // ====以下为行情专用配置，若您不需要行情，可忽略===================================
    // ====以下三种行情配置模式，根据需要，配置一种即可，如果都配置，则以最后一个配置为准=====
    // 配置行情接收，level1(含上海及深圳)，上海level2，深圳level2分别在独立线程运行；
    // 若level2行情需要更细粒度的配置，请使用receive_quote_in_snap_tick_thread或receive_quote_in_channel_per_thread接口
    config.receive_quote_in_single_thread(
        {true, -1},                ///< 启用level1行情，包括：上海、深圳level1快照；行情接收线程绑核至指定cpu, -1表示不绑核
        {true, -1},             ///< 启用上海level2行情接收线程，包括：竞价快照、指数、债券快照、逐笔委托、逐笔成交；行情接收线程绑核至指定cpu, -1表示不绑核
        {true, -1}             ///< 启用深圳level2行情，深圳level2所有行情接收在一个线程中；深圳level2行情接收线程绑核至指定cpu, -1表示不绑核
        );
    // 配置行情接收，level1(含上海及深圳)， 上海level2快照，上海level2逐笔，深圳level2快照，深圳level2逐笔，分别在独立线程运行
    // 若level2行情需要更细粒度的配置，请使用receive_quote_in_channel_per_thread接口
    config.receive_quote_in_snap_tick_thread(
        {true, -1},                 ///< 启用level1行情快照行情接收线程，包括：上海、深圳level1快照；行情接收线程绑核至指定cpu, -1表示不绑核
        {true, -1},         ///< 启用上海level2快照行情接收线程，包括：竞价快照、指数、债券快照；行情收线程绑核至指定cpu, -1表示不绑核
        {true, -1},          ///< 启用上海level2逐笔行情接收线程，包括：逐笔委托、逐笔成交；行情收线程绑核至指定cpu, -1表示不绑核
        {true, -1},        ///< 启用深圳level2快照行情，深圳level2所有快照行情接收在一个线程中，包括：竞价快照、指数、债券快照；深圳level2快照行情收线程绑核至指定cpu, -1表示不绑核
        {true, -1}          ///< 启用深圳level2逐笔行情，深圳level2所有逐笔行情接收在一个线程中，包括：逐笔委托、逐笔成交；深圳level2逐笔行情收线程绑核至指定cpu, -1表示不绑核
        );
    // 配置行情接收，level1， 上海level2各行情类型，深圳level2各行情类型，分别在独立线程运行
    config.receive_quote_in_channel_per_thread(
        {true, -1},                 ///< 启用level1行情快照行情接收线程，包括：上海、深圳level1快照；行情接收线程绑核至指定cpu, -1表示不绑核
        {
            {true, -1},              ///< 启用上海level2竞价快照行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1},              ///< 启用上海level2竞价逐笔行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1},              ///< 启用上海level2指数行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1},            ///< 启用上海level2债券快照行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1}             ///< 启用上海level2债券逐笔行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
        },
        {
            {true, -1},              ///< 启用深圳level2竞价快照行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1},              ///< 启用深圳level2竞价逐笔行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1},              ///< 启用深圳level2指数行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1},            ///< 启用深圳level2债券快照行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
            {true, -1}             ///< 启用深圳level2债券逐笔行情接收线程；行情收线程绑核至指定cpu, -1表示不绑核
        }
        );
    // ==== 行情配置结束 ==============================================================

    // API初始化，在使用之前调用，只需调用一次。
    if (!my_xapi->initialize(&config, &my_spi, &my_quote_spi)){
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
                .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Api initialized successfully!\n");
    return true;
}

/**
 * @brief 【演示】登录请求
 * 本方法演示XLTApi登录过程
 * XLTApi登录响应为异步响应，本方法封装为同步过程，用户根据实际业务场景进行调整。
 * 流程如下：
 * 1、发起登录请求
 * 2、等待异步响应
 * 3、判断是否登录成功，若成功，保存会话ID global_session_id
 * 后续接口调用都需要用到本次登录返回的global_session_id
 * @return 登录是否成功
 * @remark 登录时，用户可自定义client_id，在本次登录中的所有委托及回报都将打上此标记，用于用户进行委托隔离
 */
bool login() {
    if (global_session_id != 0) {
        my_xapi->logout(global_session_id);
    }
    global_session_id = 0;
    global_login_success.store(false);
    global_login_finished.store(false);

    // 登录账号信息
    auto request_id = next_request_id();                ///< request_id，用于定位本次请求
    uint8_t client_id = 1;                                      ///< 客户端标识ID（委托源ID），用户可自定义，取值为[0, 255]。
                                                                ///< 在本次登录session中进行的委托以及回报，系统都将打上此client_id标记，用于用户进行委托隔离。
    char pub_key_base64[256] = "XXXXXXXXXXXXXX";                ///< 用户公钥(长度64字节)，v1.5暂未启用
    char account_name[CONST_ACC_NAME_LEN] = "00100000000501";   ///< 交易账号
    char password[256] = "12345678";                            ///< 登录密码

    // 登录请求
    auto result = my_xapi->login(request_id, client_id, account_name, password, pub_key_base64);
    if ( !result ) {
        auto error_info = my_xapi->get_last_error();
        printf("Login failed! error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
        .sub_error_code, error_info.error_msg);
        return false;
    }
    // 等待登录响应
    while (!global_login_finished.load()) {
        usleep(1000);
    }
    return global_login_success.load();
}

/**
 * @brief 【演示】订阅交易回报
 * 订阅交易数据(需要在登录成功后调用)
 * @return 订阅发送是否成功
 * @remark 只用订阅了交易回报，才能收到订单状态更新和成交回报等信息
 */
bool subscribe_report() {
    if (!my_xapi->subscribe_report(global_session_id, TradeResumeType::QUICK, 0)) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("subscribe request send success!!!\n");
    return true;
}

/**
 * @brief 【演示】启动行情
 * 本方法演示如何启动行情
 * 结果将从XLTQuoteSpi::on_start_quote回调中返回
 * @return 启动行情是否成功
 * @remark 启动行情前提是在配置中启用了行情接收
 */
bool start_quote() {
    // 以下订阅为股票代码过滤规则，默认不订阅任何合约行情，用户需要显式订阅需要的合约行情。
    // 可以在启动行情接收之前订阅，也可以在启动行情之后订阅
    // 订阅全部沪深A股行情
    my_xapi->subscribe_all_quote(ExchangeIndex::SH);
    my_xapi->subscribe_all_quote(ExchangeIndex::SZ);
    // 订阅指定合约行情，若不需要订阅全部合约，可使用此方法订阅指定合约
    // my_xapi->subscribe_quote(ExchangeIndex::SH, "000072");
    // my_xapi->subscribe_quote(ExchangeIndex::SZ, "000001");

    // 启动行情接收
    if (!my_xapi->start_quote(global_session_id))
    {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
                .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Start quote!!\n");
    return true;
}

/**
 * @brief 【演示】查询资金信息
 * 本方法演示如何查询资金信息
 * 结果将从on_query_assets回调中返回
 * @return 查询请求发送是否成功
 */
bool query_assets() {
    // 查询资金信息
    if ( !my_xapi->query_assets(global_session_id, next_request_id())) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query assets info request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询持仓信息
 * 本方法演示如何查询持仓信息
 * 结果将从on_query_positions回调中返回
 * @return 查询请求发送是否成功
 */
bool query_positions() {
    // 查询持仓信息
    char security_code[CONST_STR_SECURITY_CODE_LEN] = "";
    // 当security_code为空，ExchangeIndex::INIT时，表示查询所有持仓
    if ( !my_xapi->query_positions(global_session_id, next_request_id(), security_code, ExchangeIndex::INIT,
                                   0)) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query positions request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询订单信息
 * 本方法演示如何查询订单信息
 * 结果将从on_query_orders回调中返回
 * @return 查询请求发送是否成功
 */
bool query_orders() {
    // 查询订单（根据条件查询）
    xlt_order_query_param_t order_query_param = {};
    order_query_param.xid = 0;    ///< 需要查询的委托编号，如果为0则根据后续条件进行查询，如果不为0，则只查询指定订单，忽略后续查询条件
    strcpy(order_query_param.security_code, "000001"); ///<证券代码，可以为空，如果不为空，请不带空格，并以'\0'结尾，则默认查询时间段内的所有成交回报
    if ( !my_xapi->query_orders(global_session_id, next_request_id(), &order_query_param)) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query orders request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】分页查询订单信息
 * 本方法演示如何分页查询订单信息
 * 结果将从on_query_orders_by_page回调中返回
 * @return 查询请求发送是否成功
 */
bool query_orders_by_page() {
    // 分页查询订单
    xlt_query_by_page_param_t order_query_by_page_param={};
    order_query_by_page_param.req_count = 100;
    order_query_by_page_param.reference = 0;
    if ( !my_xapi->query_orders_by_page(global_session_id, next_request_id(), &order_query_by_page_param)) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query orders by page send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询成交信息
 * 本方法演示如何查询成交信息
 * 结果将从on_query_trades回调中返回
 * @return 查询请求发送是否成功
 */
bool query_trades() {
    // 查询成交, xid若填0表示查询所有
    if ( !my_xapi->query_trades(global_session_id, next_request_id(), 0)) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query trades request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】插入订单
 * 本方法演示如何插入订单
 * 订单响应将从on_order_response、on_trade_report、on_order_error_response、on_order_end回调中返回
 * @return 插入订单请求是否发送成功，返回xid表示发送成功，返回0表示发送失败
 */
uint64_t insert_order() {
    // 订单委托
    xlt_order_insert_info_t order_info = {};
    strcpy(order_info.security_code, "600010"); // 证券代码
    order_info.exchange_index = ExchangeIndex::SH;  // 交易市场
    order_info.price = 100000;                      // 委托价格，放大1万倍
    order_info.quantity = 4400;                      // 委托数量
    order_info.order_type = OrderType::LIMIT_PRICE; // 报价类型，限价委托
    order_info.side = Side::BUY;                    // 买卖方向，卖出
    order_info.business_type = BusinessType::CASH;  // 业务类型，股票现货
    uint64_t xid = my_xapi->insert_order(global_session_id, &order_info);
    if (xid == 0) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);

    }
    printf("Insert order request send success! Wait for response!\n");
    return xid;
}

/**
 * @brief 【演示】撤单
 * 本方法演示如何撤单
 * 撤单响应将从on_cancel_response、on_order_error_response回调中返回
 * @return 撤单请求是否发送成功，返回cancel_xid表示发送成功，返回0表示发送失败
 */
uint64_t cancel_order(uint64_t xid) {
    // 撤单
    xlt_cancel_order_info_t cancel_order = {0, xid};
    uint64_t cancel_xid = my_xapi->cancel_order(global_session_id, &cancel_order);
    if (cancel_xid == 0) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
    }
    printf("Cancel order request send success! Wait for response! cancel_xid: %lud\n", cancel_xid);
    return cancel_xid;
}

/**
 * @brief 【演示】查询静态行情信息
 * 本方法演示如何查询静态行情信息
 * 结果将从on_query_static_quote_info回调中返回
 * @return 查询请求发送是否成功
 */
bool query_static_quote_info() {
    // 查询静态行情信息
    if ( !my_xapi->query_static_quote_info(global_session_id, next_request_id())) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query static quote info requests request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询最新上证Level1指数行情信息
 * 本方法演示如何查询最新上证Level1指数行情信息
 * 结果将从on_query_sse_l1_index回调中返回
 * @return 查询请求发送是否成功
 */
bool query_sse_l1_index() {
    if ( !my_xapi->query_sse_l1_index(global_session_id, next_request_id(), "000001")) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query sse l1 index requests request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询最新上证Level1快照行情信息
 * 本方法演示如何查询最新上证Level1快照行情信息
 * 结果将从on_query_sse_l1_snapshot回调中返回
 * @return 查询请求发送是否成功
 */
bool query_sse_l1_snapshot() {
    if ( !my_xapi->query_sse_l1_snapshot(global_session_id, next_request_id(), "600000")) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query sse l1 snapshot requests request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询最新深证Level1指数行情信息
 * 本方法演示如何查询最新深证Level1指数行情信息
 * 结果将从on_query_szse_l1_index回调中返回
 * @return 查询请求发送是否成功
 */
bool query_szse_l1_index() {
    if ( !my_xapi->query_szse_l1_index(global_session_id, next_request_id(), "399001")) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query szse l1 index requests request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询最新深证Level1快照行情信息
 * 本方法演示如何查询最新深证Level1快照行情信息
 * 结果将从on_query_szse_l1_snapshot回调中返回
 * @return 查询请求发送是否成功
 */
bool query_szse_l1_snapshot() {
    if ( !my_xapi->query_szse_l1_snapshot(global_session_id, next_request_id(), "000001")) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query szse l1 snapshot requests request send success! Wait for response!\n");
    return true;
}

/**
 * @brief 【演示】查询最新深证Level1债券快照行情信息
 * 本方法演示如何查询最新深证Level1债券快照行情信息
 * 结果将从on_query_szse_l1_snapshot_bond回调中返回
 * @return 查询请求发送是否成功
 */
bool query_szse_l1_snapshot_bond() {
    if ( !my_xapi->query_szse_l1_snapshot_bond(global_session_id, next_request_id(), "000072")) {
        auto error_info = my_xapi->get_last_error();
        printf("error_code: %lu, sub_error_code: %lu, error_msg: %s \n",  error_info.error_code, error_info
               .sub_error_code, error_info.error_msg);
        return false;
    }
    printf("Query szse l1 snapshot bond requests request send success! Wait for response!\n");
    return true;
}

int main() {
    printf("API version: %s\n", my_xapi->get_api_version());
    if (!initialize()) return -1;
    if (!login()) return -1;
    printf("Trading day: %u\n", my_xapi->get_trading_day());
    if (!subscribe_report()) return -1;
    if (!start_quote()) return -1;
    if (!query_assets()) return -1;
    if (!query_positions()) return -1;
    if (!query_orders()) return -1;
    if (!query_orders_by_page()) return -1;
    if (!query_trades()) return -1;
    if (!query_static_quote_info()) return -1;
    if (!query_sse_l1_index()) return -1;
    if (!query_sse_l1_snapshot()) return -1;
    if (!query_szse_l1_index()) return -1;
    if (!query_szse_l1_snapshot()) return -1;
    if (!query_szse_l1_snapshot_bond()) return -1;

    uint64_t xid = insert_order();
    if (xid == 0) return -1;
    usleep(500);
    uint64_t cancel_xid = cancel_order(xid);
    if (cancel_xid == 0) return -1;

    while (true) {
        usleep(1000 * 1000);
    }
    my_xapi->logout(global_session_id);
    return 0;

}
