#include "lip/app/ptp/lip_ptp_cfg_defs.h"
#if LIP_PTP_ENABLE
#include "lip_ptp_bmca_state_sel.h"
#include "lip_ptp_port_announce.h"
#include "lip/app/ptp/sync/lip_ptp_local_clk_sync.h"
#include "lip/app/ptp/sync/lip_ptp_site_sync.h"
#include "lip/app/ptp/master/lip_ptp_master_sync.h"
#include <string.h>

static void f_gm_changed(t_lip_ptp_inst_ctx *inst) {
    /* Если изменился gm, то сбрасываем состояние синхронизации локального клока
     * и удаленного */
    lip_ptp_local_clk_sync_init(inst);
    lip_ptp_master_sync_send_enable(inst, (inst->gm_stats.flags & LIP_PTP_GM_STATS_FLAG_REMOTE) == 0);
    lip_ptp_site_sync_gm_change(inst);

}


void lip_ptp_bmca_state_sel_init(t_lip_ptp_inst_ctx *inst) {
    memset(&inst->bmca, 0, sizeof(inst->bmca));
    memset(&inst->bmca.gm_prio, 0xFF, LIP_PTP_BMCA_PRIO_VECTOR_SIZE);
    inst->gm_stats.flags = 0;
    inst->gm_stats.sys_id = &inst->bmca.gm_prio.rootSystemIdentity;
    inst->gm_stats.time_info = NULL;
    lip_ptp_bmca_state_sel_system_clkid_update(inst);
}

void lip_ptp_bmca_state_sel_system_clkid_update(t_lip_ptp_inst_ctx *inst) {
    uint8_t *opt = inst->bmca.ann_sys_opts;
    opt = f_set_tlv_hdr(opt, LIP_PTP_TLV_CODE_PATH_TRACE, LIP_PTP_CLOCK_IDENTITY_SIZE);
    memcpy(opt, &inst->sys_id.clockIdentity, LIP_PTP_CLOCK_IDENTITY_SIZE);
    inst->bmca.ann_sys_opts_size = LIP_PTP_ANNONCE_SYS_OPT_SIZE;
    lip_ptp_bmca_state_sel_update_req(inst);
}


static void f_bmca_states_update(t_lip_ptp_inst_ctx *inst) {
    /* updtStatesTree() - 10.3.13.2.4 IEEE 802.1AS-2020 */
    t_lip_ptp_bcma_state_sel_ctx *ctx = &inst->bmca;

    t_lip_ptp_bmca_prio_vector system_prio;
    memcpy(&system_prio.rootSystemIdentity, &inst->sys_id, LIP_PTP_SYSTEM_IDENTITY_SIZE);
    system_prio.stepsRemoved = 0;
    memcpy(&system_prio.sourcePortIdentity.clockIdentity, inst->sys_id.clockIdentity, LIP_PTP_CLOCK_IDENTITY_SIZE);
    system_prio.sourcePortIdentity.portNum = 0;
    system_prio.portNum = 0;

    /* b) выбор лучшего вектора из system и gmPath портов */
    t_lip_ptp_bmca_prio_vector *gm_prio = &system_prio;
    t_lip_ptp_port_ctx *gm_port = NULL;
    for (int port_idx = 0; port_idx < LIP_PORT_MAX_CNT; ++port_idx) {
        t_lip_ptp_port_ctx *p = &inst->ports[port_idx];
        if (lip_ptp_port_is_en(p)) {
            /* clockIdentity of maseter port != thisClock ? */
            /* has PrioVector && !announce tout && !sync_tout */
            if (p->announce.info_state == LIP_PTP_PORT_ANNOUNCE_INFO_STATE_RECEIVED) {
                int cmp = f_cmp_prio(&p->announce.port_prio, gm_prio);
                if (cmp < 0) {
                    gm_prio = &p->announce.port_prio;
                    gm_port = p;
                }
            }
        }
    }




    if (gm_port) {
        /* с) выбор текущей информации о времени из локальной или удаленного мастера  */
        inst->gm_stats.time_info = &gm_port->announce.ann_time_info;
        /* e) masterStepsRemoved - от порта +1 или 0 (если сами GM) */
        ctx->maseter_steps_removed_be = NTOH16(NTOH16(gm_port->announce.port_prio.stepsRemoved) + 1);
        lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma - gm port %d, steps %d!\n",
                   LIP_PTP_PRINT_PORT_NUM(gm_port), NTOH16(ctx->maseter_steps_removed_be));
    } else {
        inst->gm_stats.time_info = &inst->sys_time_info;
        ctx->maseter_steps_removed_be = 0;
        lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma - self gm\n");
    }
    /* d) вычисление masterPrio без заполнения порта */
    t_lip_ptp_bmca_prio_vector master_prio;
    f_cpy_prio(&master_prio, gm_prio);


    bool has_slave_port = false;

    for (int port_idx = 0; port_idx < LIP_PORT_MAX_CNT; ++port_idx) {
        t_lip_ptp_port_ctx *p = &inst->ports[port_idx];
        if (lip_ptp_port_is_en(p)) {
            bool updInfo = false;
            /* довычисляем masterPortPriority для порта, подставляя его номер */
            master_prio.sourcePortIdentity.portNum = master_prio.portNum = p->pkt_port_num_be;

            if (p->announce.info_state == LIP_PTP_PORT_ANNOUNCE_INFO_STATE_AGED) {
                /* f.3) для eged -> Master + update*/
                p->stats.state = LIP_PTP_PORT_STATE_MASTER;
                updInfo = true;
                lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma select - port %d - aged master\n", LIP_PTP_PRINT_PORT_NUM(p));
            } else if (p->announce.info_state == LIP_PTP_PORT_ANNOUNCE_INFO_STATE_MINE) {
                /* f.4 для mine -> Master, update только при отличии */
                p->stats.state = LIP_PTP_PORT_STATE_MASTER;
                updInfo = f_cmp_prio(&master_prio, &p->announce.port_prio) != 0;
                if (updInfo) {
                    lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma select - port %d - update master info\n", LIP_PTP_PRINT_PORT_NUM(p));
                }
            } else if (p->announce.info_state == LIP_PTP_PORT_ANNOUNCE_INFO_STATE_RECEIVED) {
                if (p == gm_port) {
                    lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma select - port %d - slave\n", LIP_PTP_PRINT_PORT_NUM(p));
                    /* f.5 для recvd если gm_port, то  -> Slave, update = false, копия pathTrace */
                    p->stats.state = LIP_PTP_PORT_STATE_SLAVE;
                    has_slave_port = true;
                    /* назначаем указатель для опций announce (используем общий
                     * механизм для всех опций, а не только для pathTrace) */
                    ctx->ann_tx_opts_ptr = ctx->ann_rx_opts;
                    ctx->ann_tx_opts_size_ptr = &ctx->ann_rx_opts_size;
                    if (p != ctx->ann_rx_opts_port) {
                        ctx->ann_rx_opts_size = 0;
                    }
                } else  {
                    int mst_prio_cmp = f_cmp_prio(&master_prio, &p->announce.port_prio);
                    if (mst_prio_cmp >= 0) {
                        /* f.6 если masterPortPrio не лушче и sourcePortIdentity != thisClock (?) */
                        /** @todo в чем различие f.6 и f.7 ??? */
                        p->stats.state = LIP_PTP_PORT_STATE_PASSIVE;
                        lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma select - port %d - passive\n", LIP_PTP_PRINT_PORT_NUM(p));
                    } else {
                        p->stats.state = LIP_PTP_PORT_STATE_MASTER;
                        updInfo = true;
                        lip_printf(LIP_LOG_MODULE_PTP, LIP_LOG_LVL_DETAIL, 0, "lip ptp: bcma select - port %d - master (worse info)\n", LIP_PTP_PRINT_PORT_NUM(p));
                    }
                }
            }

            if (updInfo) {
                lip_ptp_port_announce_prio_update(p, &master_prio);
            }
        }
    }

    /* если сами являемся GM, то опции для передачи в Announce подготовлены отдельно */
    if (!has_slave_port) {
        ctx->ann_tx_opts_ptr = ctx->ann_sys_opts;
        ctx->ann_tx_opts_size_ptr = &ctx->ann_sys_opts_size;
    }

    bool gm_changed = !lip_ptp_clock_id_is_equal(&ctx->gm_prio.rootSystemIdentity.clockIdentity, &gm_prio->rootSystemIdentity.clockIdentity);
    f_cpy_prio(&ctx->gm_prio, gm_prio);
    if (gm_changed) {
        /* g) gmPresent -> prio1 != 255 */

        bool gm_present = system_prio.rootSystemIdentity.priority1 != LIP_PTP_SYSTEM_PRIO1_NOT_GMCAP;
        if (gm_present) {
            inst->gm_stats.flags |= LIP_PTP_GM_STATS_FLAG_PRESENT;
        } else {
            inst->gm_stats.flags &= ~LIP_PTP_GM_STATS_FLAG_PRESENT;
        }
        /* h) вместо отдельной переменной под порт 0 сохраняем признак удаленного GM */
        if (has_slave_port) {
            inst->gm_stats.flags |= LIP_PTP_GM_STATS_FLAG_REMOTE;
        } else {
            inst->gm_stats.flags &= ~LIP_PTP_GM_STATS_FLAG_REMOTE;
        }
        f_gm_changed(inst);
    }
}

void lip_ptp_bmca_state_sel_pull(t_lip_ptp_inst_ctx *inst) {
    if (inst->bmca.update_req) {
        inst->bmca.update_req = false;
        f_bmca_states_update(inst);
    }
}

void lip_ptp_bmca_state_sel_update_req(t_lip_ptp_inst_ctx *inst) {
    inst->bmca.update_req = true;
}

#endif



