class_name trassa_unit extends Node

const ONLINE_TIMEOUT: int = 5000     # Время ожидания данных
const REQUEST_PERIOD: int = 3000     # Период отправки, мс
const WORDS_TOTAL: int = 75
const NOINFO: int = 0x80000000

class Trassa extends unit.Unit:
    static var json_conv: = JSON.new()  ## Аппарат для разбора JSON сообщений
    var source_addr: = []
    var unit_caps = network.get_unit_instance('уарэп-капсрпб')
    var tx_stack: Array[int] = []
    var msg_counter: int = 0
    var ecms: Dictionary = {} # Словарь всех активных помех
    var srn = 0
    var jtids = 0
    var prinud_but: Array = []
    var forced_buttons: Array = []  # Для хранения ссылок на кнопки
    var json_dic: Dictionary = {}   # Для хранения распарсенного JSON
    var target: bool = false  # Флаг наличия значения 21930
    var data_type: String = "unknown"  # Тип полученных данных: "json" или "binary"
    var current_index: int = 0
    var set_klaster = [
        [390, 510, 224, 50],
        [390, 1065, 250, 100],
        [800, 1065, 274, 50],
        [800, 1210, 300, 100],
        [1065, 1210, 324, 50],
        [1065, 1380, 350, 100],
        [1150, 1380, 374, 50],
        [1150, 2000, 400, 100],
        [1380, 2000, 424, 50],
        [1380, 3500, 450, 100],
        [2000, 3500, 474, 50],
        [2000, 6000, 500, 100],
        [3500, 6000, 524, 50],
        [390, 6000, 374, 50*7]]
    var lfb = 0
    var hfb = 0
    var dap = 0
    var daa = 0

    # Инициализация
    func _init(nm: StringName, buttons: Array = []) -> void:
        self.name = nm
        self.forced_buttons = buttons
        tx_stack.resize(WORDS_TOTAL)
        tx_stack.fill(NOINFO)
        interfer.connect('interfer_resized', Callable(self, 'on_interfer_resized'))


    # Обработка полученного пакета - определение типа
    func parse(rx_data: PackedByteArray, tick: int):
        rx_tick = tick
        if not online:
            online = true
            emit_signal('line_changed', self)
        data_type = detect_data(rx_data)
        if data_type == 'json':
            parse_json(rx_data)
        elif data_type == 'binary':
            parse_byte(rx_data)
        else:
            emit_signal('parse_failed', self)


    # Определение типа данных
    func detect_data(data: PackedByteArray) -> String:
        if data.size() > 0:
            var first_char = data[0]
            if first_char == 123 or first_char == 91:
                return "json"
        if data.size() >= 2:
            var potential_json = data.slice(0, min(100, data.size()))
            var test_string = potential_json.get_string_from_ascii()
            if test_string and (test_string.begins_with("{") or test_string.begins_with("[")):
                return "json"
        return "binary"


    # Обработка JSON данных
    func parse_json(rx_data: PackedByteArray) -> void:
        var json_string = rx_data.get_string_from_utf8()
        if json_string:
            if Error.OK == json_conv.parse(json_string):
                json_dic = json_conv.get_data()
                target = check_target(json_dic)
                sector_draw(json_dic)
                emit_signal('data_received', self)
            else:
                emit_signal('parse_failed', self)
        else:
            emit_signal('parse_failed', self)


    func sector_draw(data: Dictionary) -> void:
        var sector_data = {
            "draw": false,
            "color": Color.GREEN * Color(1, 1, 1, 0.1),
            "rect": Rect2(),
            "index": 0 }
        if data.get("clean_all", false) == true:
            current_index = 0
            signaller.emit_signal('clear_klaster')
            return
        if data.has("txe") and data.has("rxe"):
            var txe = data["txe"]
            var rxe = data["rxe"]
            if txe == true or rxe == true:
                sector_data["draw"] = true
                if data.has("lfb") and data.has("hfb") and data.has("dap") and data.has("daa"):
                    lfb = int(data["lfb"])
                    hfb = int(data["hfb"])
                    dap = int(data["dap"])
                    daa = int(data["daa"])
                    for i in range(set_klaster.size()):
                        var mapping = set_klaster[i]
                        if lfb == mapping[0] and hfb == mapping[1]:
                            lfb = mapping[2]
                            hfb = mapping[3]
                            break
                    sector_data["rect"] = Rect2(lfb, daa, dap, hfb)
                    sector_data["index"] = sector_index()
            signaller.emit_signal('sector_klaster', sector_data)


    func sector_index() -> int:
        current_index += 1
        return current_index - 1


    # Обработка бинарных данных
    func parse_byte(rx_data: PackedByteArray):
        var words: Array[int] = []
        var offset: int = 0
        if rx_data.size() < 40:
            emit_signal('parse_failed', self)
            return
        while offset < rx_data.size():
            if offset + 4 <= rx_data.size():
                var word = rx_data.decode_u32(offset)
                words.append(word)
                offset += 4
            else:
                break
        if words.size() < 10:
            emit_signal('parse_failed', self)
            return
        tx_stack.fill(NOINFO)
        tx_stack[0] = words[0]                # ID
        tx_stack[1] = msg_counter            # Nc
        tx_stack[2] = 0x01                    # Nб
        tx_stack[3] = 0x00                    # Ск
        tx_stack[4] = 0x0194                  # НТС
        tx_stack[5] = (0x0001 << 16) | 0x0080 # Ии
        tx_stack[6] = 0x01                    # РКС
        srn = words[7]                       # SRN
        jtids = words[8]                     # JTIDS
        btn_trassa_pressed()
        interfer_data()
        tx_stack[74] = _calc_crc(tx_stack)
        emit_signal("data_received", self)


    # Проверка на нужный JSON файл
    func check_target(data, current_path: String = "root") -> bool:
        if data == null:
            return false
        if data is int or data is float:
            if int(data) == 21930:
                return true
            return false
        var found = false
        if data is Dictionary:
            for key in data:
                var new_path = current_path + "." + str(key)
                if check_target(data[key], new_path):
                    found = true
        elif data is Array:
            for i in range(data.size()):
                var new_path = current_path + "[" + str(i) + "]"
                if check_target(data[i], new_path):
                    found = true
        return found


    func interfer_data() -> void:
        for i in range(67, 74):
            tx_stack[i] = 0
        for ecm in ecms.values():
            if ecm.svk == 1:
                var nmfs_value = ecm.nmfs
                var fs_key = get_fs_key(nmfs_value)
                if fs_key != NOINFO:
                    var string_index = fs_key / 4
                    var bit_position = fs_key % 4
                    if string_index >= 0 and string_index < 7:
                        var target_string = 67 + string_index
                        tx_stack[target_string] |= (1 << bit_position)


    func get_fs_key(nmfs_value: int) -> int:
        if prd.fs_caps_id.has(nmfs_value):
            return prd.fs_caps_id[nmfs_value]
        return NOINFO


    # Отправка пакета от uarep-ctl
    func process(now_tick: int) -> int:
        if online and ((now_tick - rx_tick) > ONLINE_TIMEOUT):
            online = false
            emit_signal('line_changed', self)
        if (now_tick - tx_tick) > REQUEST_PERIOD:
            tx_tick = now_tick
            msg_counter += 1
            interfer_uarep()
            interfer_data()
            var data = PackedByteArray()
            data.resize(WORDS_TOTAL * 4)
            for i in range(WORDS_TOTAL):
                data.encode_u32(i * 4, tx_stack[i])
            tx_data = data
            tx_len = WORDS_TOTAL * 4
            return Error.OK
        return Error.ERR_BUSY


    # Контрольная сумма
    func _calc_crc(data: Array) -> int:
        var acc: int = 0
        for i in range(data.size() - 1): # кроме CRC
            if i in [0, 1]:
                continue
            acc = (acc + (data[i] & 0xFFFFFFFF)) & 0xFFFFFFFF
        return acc


    # Обновление всех активных помех
    func on_interfer_resized(ecms_dic: Dictionary) -> void: ecms = ecms_dic


    func interfer_uarep() -> void:
        tx_stack[65] = 0
        tx_stack[66] = 0
        for ecm in ecms.values():
            if ecm.krp == 'srni1' and ecm.svk == 1:
                tx_stack[65] = 1
            if ecm.krp == 'svi1' and ecm.svk == 1:
                tx_stack[66] = 1
        tx_stack[1] = msg_counter


    # Выполнение указаний по кнопке Трасса
    func btn_trassa_pressed() -> void:
        var state = ProjectSettings.get_setting('application/config/Внешнее указание Трасса', false)
        if state == true:
            var tree: SceneTree = Engine.get_main_loop()
            if tree is SceneTree:
                var nodes: Array = tree.get_nodes_in_group('группа-принудительно')
                for btn in nodes:
                    if btn.name == 'btn_srn':
                        btn.pressed = (srn == 1)
                    elif btn.name == 'btn_jtids':
                        btn.pressed = (jtids == 1)
            if srn == 1:
                tx_stack[65] = 1
                signaller.emit_signal('interfer_prinuditelno', frm_ecm_list.srn_sectors)
            else:
                tx_stack[65] = 0
                for ecm in ecms.values():
                    if ecm.krp == "srni1" and ecm.svk == 1:
                        signaller.emit_signal('interfer_off', ecm)
            if jtids == 1:
                tx_stack[66] = 1
                signaller.emit_signal('interfer_prinuditelno', frm_ecm_list.jtids_sectors)
            else:
                tx_stack[66] = 0
                for ecm in ecms.values():
                    if ecm.krp == 'svi1' and ecm.svk == 1:
                        signaller.emit_signal('interfer_off', ecm)
