extends Node
## Работа с целями.

const MSG9_RESEND_TIMEOUT = 1.0            ## Время повторной отправки пакета 9

## Режимы работы АФСП и БПО (Протокол ТНИИС-СТЦ, пункт 2.5.10)
enum AFSP_BPO_MODE {
    run = 1,
    test = 2 }

var msg9_num: int = 0                      ## Счётчик сообщений "тип 9"
var gos_flag = true                        ## Флаг работать по своим целям
var gos_protocols: PackedStringArray       ## Список протоколов своих целей
var bpo_mode: = AFSP_BPO_MODE.run          ## Режим работы АФСП и БПО
var bpo_mode_rx: = AFSP_BPO_MODE.run       ## Режим работы БПО полученный от

## Цель
class Threat:
    var alt: float                      ## Текущая высота, метры.
    var aoa: float                      ## Курсовой угол на источник сигнала, градусы.
    var atick: float                    ## Тик обнаружения.
    var baud: float                     ## Модуляционная скорость.
    var fflags: Dictionary              ## Флаги достоверности полей.
    var freq: float                     ## Частота несущей или центральная частота, МГц.
    var freqm: String                   ## Режим смены частоты.
    var id: int                         ## Идентификатор цели.
    var lat: float                      ## Текущие координаты, широта в градусах.
    var lon: float                      ## Текущие координаты, долгота в градусах.
    var pow: float                      ## Уровень принимаемого сигнала, дБм.
    var proto: String                   ## Тип протокола источника сигнала.
    var rkrp: String                    ## Рекомендуемый режим помехи.
    var rparams: Dictionary             ## Рекомендуемые параметры помехи.
    var salt: float                     ## Координаты старта, высота от уровня моря, м.
    var slat: float                     ## Координаты старта, широта в градусах.
    var slon: float                     ## Координаты старта, долгота в градусах.
    var tick: int                       ## Тик время приёма сигнала, мс.
    var tick_rx: int                    ## Тик время обновления цели, мс.
    var tmod: String                    ## Тип модуляции.
    var width: float                    ## Ширина занимаемого диапазона частот, МГц.
    var selected: bool                  ## Флаг, что цель выбрана.
    var auto_selected: bool = false     ## Флаг автомата для целей
    var emit_state: int = 0             ## 0 - подавление не назначено, 1 - назначено, 2 - выполняется
    var nmfs: int                       ## Индекс модуля ФС, в зону действия которого попала цель
    var priority: int                   ## Приоритет опасности цели
    var gos: bool = false               ## Своя или чужая цель

    func _init(): clear_fflags()
    func _to_string() -> String: return 'номер: %d пеленг: %0.1f частота: %0.1f МГц' % [id, aoa, freq]
    func clear_fflags(): tools.get_members_names(self).any(func(vn): fflags[vn] = false)

    func set_from_dic(dic: Dictionary):
        var items: = fflags.keys().filter(func(vn): return dic.has(vn))
        items.any(func(vn): set(vn, dic[vn]); fflags[vn] = true; return false)


signal threat_new(th: Threat)
signal threat_lost(th: Threat)
signal threat_update(th: Threat)
signal threats_resized(th: Dictionary)


func _ready() -> void:
    var unit_caps = network.get_unit_instance('уарэп-капсрпб')
    var unit_5p28 = network.get_unit_instance('уарэп-5п28')
    var timer_check_lost: = Timer.new()
    var timer_msg9: = Timer.new()
    var timer_aoa_update: = Timer.new()
    add_child(timer_check_lost)
    add_child(timer_aoa_update)
    add_child(timer_msg9)
    var threats = {}  ## Список целей. Индекс - <номер цели>
    unit_caps.connect('data_received', Callable(self, 'on_data_capsrpb_received').bind(threats))
    unit_caps.connect('line_changed', Callable(self, 'on_caps_line_changed').bind(timer_msg9))
    unit_5p28.connect('get_threats', Callable(self, 'on_get_threats').bind(unit_5p28, threats))
    timer_check_lost.connect('timeout', Callable(self, 'on_timer_check_lost').bind(settings.ThreatParams.time_lost, threats))
    timer_check_lost.start(settings.ThreatParams.time_lost / 1000.0)
    timer_aoa_update.connect('timeout', Callable(self, 'on_timer_aoa_update').bind(threats, timer_aoa_update))
    timer_aoa_update.start(3.0)
    timer_aoa_update.one_shot = true
    timer_msg9.connect('timeout', Callable(self, 'on_timer_msg9').bind(unit_caps, timer_msg9))

    signaller.connect('rto_threat_sel', Callable(self, 'on_rto_threat_sel').bind(threats))
    signaller.connect('rto_threat_unsel', Callable(self, 'on_rto_threat_unsel').bind(threats))
    signaller.connect('rto_threat_unsel_all', Callable(self, 'on_rto_threat_unsel_all').bind(threats))
    signaller.connect('threat_selected', Callable(self, 'on_threat_selected').bind(threats))
    signaller.connect('interfer_init', Callable(self, 'on_interfer_init').bind(threats))
    signaller.connect('connect_work_signal', Callable(self, 'on_connect_work_signal').bind(threats))
    signaller.connect('режим_контроль', Callable(self, 'on_mode_change').bind(unit_caps, timer_msg9, AFSP_BPO_MODE.test))
    signaller.connect('режим_работа', Callable(self, 'on_mode_change').bind(unit_caps, timer_msg9, AFSP_BPO_MODE.run))

    ProjectSettings.connect('settings_changed', on_settings_changed.bind(threats))

## Периодически удаляет устаревшие цели.
func on_timer_check_lost(time_lost: int, threats: Dictionary):
    var tick: = Time.get_ticks_msec()
    var sz: = threats.size()
    for th in threats.values():
        if (tick - th.tick_rx) >= time_lost:
            threats.erase(th.id)
            call_deferred('emit_signal', 'threat_lost', th)
    if sz != threats.size():
        call_deferred('emit_signal', 'threats_resized', threats)


func on_timer_aoa_update(threats:Dictionary, tmr):
    signaller.emit_signal('th_aoa_update', threats)
    tmr.start(1.0)

## отправляет словарь целей в юнит, для запаковки и отправки в 5П28.
func on_get_threats(unit_instance, threats): unit_instance.pack_threats(threats)

## Снимает выделение с цели, если нажать по отметке цели.
func on_rto_threat_unsel(th_id, threats): threats[th_id].selected = false


## Снимает выделение со всех целей.
func on_rto_threat_unsel_all(threats):
    for th in threats.values():
        signaller.emit_signal('rto_threat_unsel', th.id)


## Выбирает цель, если нажать по отметке цели.
func on_rto_threat_sel(th_id, threats): on_threat_selected(threats[th_id], threats)


## Выбирает цель, если выбрать в таблице.
func on_threat_selected(th, threats):
    for threat in threats.values():
        threat.selected = false
    th.selected = true


## Формирует список выбранных целей для включения помехи.
func on_interfer_init(interfer_name, fs_arr, power, threats):
    var sel_threats: Array = []
    for threat in threats.values():
        if interfer.auto_enabled:
            if threat.auto_selected:
                sel_threats.append(threat)
        else:
            if threat.selected:
                sel_threats.append(threat)
    if len(sel_threats) or len(fs_arr):
        signaller.emit_signal('interfer_create', interfer_name, sel_threats, fs_arr, power)


## Производит приём целей.
func on_data_capsrpb_received(unit: capsrpb.CapsRpb, threats: Dictionary):
    if not unit.json_dic.has('ts'):  return
    if unit.json_dic['ts'] != 5:     return
    if not unit.json_dic.has('mis'): return
    if not unit.json_dic.has('sa'):  return

    var mis = unit.json_dic['mis']
    bpo_mode_rx = int(unit.json_dic['sa']) as AFSP_BPO_MODE
    var sz: = threats.size()
    for dic_th in mis:
        if dic_th is not Dictionary:
            continue
        if not dic_th.has('id'):
            continue
        var id = int(dic_th['id'])
        if threats.has(id):
            var th = threats[id]
            th.clear_fflags()
            th.set_from_dic(dic_th)
            th.tick_rx = unit.tick
            if th.fflags.proto and th.proto != '':
                th.priority = 1
            else:
                th.priority = 0
            set_gos(th)
            call_deferred('emit_signal', 'threat_update', th)

        else:
            var th: = Threat.new()
            th.set_from_dic(dic_th)
            threats[id] = th
            th.tick_rx = unit.tick
            set_gos(th)
            call_deferred('emit_signal', 'threat_new', th)
    if sz != threats.size():
        call_deferred('emit_signal', 'threats_resized', threats)


func on_settings_changed(ths: Dictionary):
    gos_flag = ProjectSettings.get_setting('application/config/Включить работу по своим целям', true)
    var protocols: String = ProjectSettings.get_setting('application/config/Список своих протоколов', '')
    gos_protocols = protocols.split(",", true)
    for th in ths.values():
        set_gos(th)


func set_gos(th: Threat):
    if gos_flag: th.gos = false
    elif (th.proto in gos_protocols) and th.fflags.proto: th.gos = true


func on_connect_work_signal(ths: Dictionary):
    emit_signal('threats_resized', ths)


func on_mode_change(unit_caps, timer_msg9, state):
    bpo_mode = state
    send_msg9(unit_caps)
    timer_msg9.start(MSG9_RESEND_TIMEOUT)


func send_msg9(unit_caps):
    msg9_num += 1
    var json_data : Dictionary = {}
    json_data['ts'] = 9
    json_data['ss'] = msg9_num
    json_data['sa'] = bpo_mode
    var pac = JSON.stringify(json_data).to_utf8_buffer()
    unit_caps.tx_stack.append(pac)


func on_timer_msg9(unit_caps, timer_msg9):
    if bpo_mode != bpo_mode_rx:
        send_msg9(unit_caps)
    else:
        timer_msg9.stop()


func on_caps_line_changed(unit_caps, timer_msg9):
    if unit_caps.online:
        send_msg9(unit_caps)
        timer_msg9.start(MSG9_RESEND_TIMEOUT)
