# SDK Pullenti Lingvo, version 4.33, fabruary 2026. Copyright (c) 2013-2026, Pullenti. All rights reserved.
# Non-Commercial Freeware and Commercial Software.
# This class is generated using the converter Unisharping (www.unisharping.ru) from Pullenti C# project.
# The latest version of the code is available on the site www.pullenti.ru

import typing
import threading
from pullenti.unisharp.Utils import Utils

from pullenti.ner.address.StreetReferent import StreetReferent
from pullenti.ner.address.AddressReferent import AddressReferent
from pullenti.ner.resume.ResumeItemReferent import ResumeItemReferent
from pullenti.ner.person.PersonIdentityReferent import PersonIdentityReferent
from pullenti.ner.date.DateReferent import DateReferent
from pullenti.ner.phone.PhoneReferent import PhoneReferent
from pullenti.ner.uri.UriReferent import UriReferent
from pullenti.ner.person.PersonPropertyReferent import PersonPropertyReferent
from pullenti.ner.person.PersonPropertyKind import PersonPropertyKind
from pullenti.morph.MorphCase import MorphCase
from pullenti.ner.core.MiscHelper import MiscHelper
from pullenti.ner.link.LinkType import LinkType
from pullenti.ner.ProcessorService import ProcessorService
from pullenti.ner.date.DateRangeReferent import DateRangeReferent
from pullenti.ner.person.internal.PersonAttrToken import PersonAttrToken
from pullenti.ner.resume.ResumeAnalyzer import ResumeAnalyzer
from pullenti.ner.core.NounPhraseParseAttr import NounPhraseParseAttr
from pullenti.ner.core.ComplexNumToken import ComplexNumToken
from pullenti.ner.core.NounPhraseHelper import NounPhraseHelper
from pullenti.ner.Referent import Referent
from pullenti.ner.link.LinkReferent import LinkReferent
from pullenti.ner.core.internal.ResourceHelper3 import ResourceHelper3
from pullenti.ner.Analyzer import Analyzer
from pullenti.ner.geo.GeoReferent import GeoReferent
from pullenti.ner.resume.ResumeItemType import ResumeItemType
from pullenti.ner.TextToken import TextToken
from pullenti.ner.ReferentToken import ReferentToken
from pullenti.ner.link.MetaLink import MetaLink
from pullenti.ner.person.internal.PersonHelper import PersonHelper
from pullenti.ner.person.PersonReferent import PersonReferent
from pullenti.ner.org.OrganizationReferent import OrganizationReferent

class LinkAnalyzer(Analyzer):
    """ Анализатор связей (специфический анализатор) """
    
    ANALYZER_NAME = "LINK"
    """ Имя анализатора ("LINK") """
    
    @property
    def name(self) -> str:
        return LinkAnalyzer.ANALYZER_NAME
    
    @property
    def caption(self) -> str:
        return "Связи"
    
    @property
    def description(self) -> str:
        return "Связи выделенных сущностй (досье)"
    
    def clone(self) -> 'Analyzer':
        return LinkAnalyzer()
    
    @property
    def is_specific(self) -> bool:
        """ Специфический анализатор """
        return True
    
    @property
    def type_system(self) -> typing.List['ReferentClass']:
        return [MetaLink.GLOBAL_META]
    
    @property
    def images(self) -> typing.List[tuple]:
        res = dict()
        res[str(MetaLink.IMAGE_ID)] = ResourceHelper3.get_bytes("link.png")
        return res
    
    def create_referent(self, type0_ : str) -> 'Referent':
        if (type0_ == LinkReferent.OBJ_TYPENAME): 
            return LinkReferent()
        return None
    
    @property
    def progress_weight(self) -> int:
        return 1
    
    def process(self, kit : 'AnalysisKit') -> None:
        ad = kit.get_analyzer_data(self)
        t0 = kit.first_token
        cou = 10
        t = t0
        first_pass4297 = True
        while True:
            if first_pass4297: first_pass4297 = False
            else: t = t.next0_
            if (not (t is not None and cou > 0)): break
            if (not t.is_newline_before): 
                continue
            cou -= 1
            num = ComplexNumToken.try_parse(t, None, False, False)
            if (num is not None): 
                break
            npt = NounPhraseHelper.try_parse(t, NounPhraseParseAttr.NO, 0, None)
            if (npt is None or not npt.is_newline_after): 
                continue
            if (npt.end_token.is_value("ЗАПИСКА", None) or npt.end_token.is_value("СПРАВКА", None) or npt.end_token.is_value("ОТЧЕТ", None)): 
                t0 = npt.end_token.next0_
                break
        main_pers = None
        cur_pers = None
        cur_org = None
        cur_typ = ResumeItemType.UNDEFINED
        tmp_orgs = list()
        t = t0
        first_pass4298 = True
        while True:
            if first_pass4298: first_pass4298 = False
            else: t = t.next0_
            if (not (t is not None)): break
            nl = False
            if (t.is_newline_before): 
                nl = True
                num = ComplexNumToken.try_parse(t, None, False, False)
                if (num is not None and num.end_token.next0_ is not None): 
                    t = num.end_token.next0_
                    nl = True
            if (cur_org is not None and main_pers is not None and len(cur_org.occurrence) > 0): 
                if ((t.begin_char - cur_org.occurrence[len(cur_org.occurrence) - 1].end_char) > 100): 
                    cur_org = (None)
            r = t.get_referent()
            p = Utils.asObjectOrNull(r, PersonReferent)
            if (p is not None): 
                cur_org = (None)
                if (LinkAnalyzer.__is_object(p)): 
                    cur_pers = p
                    continue
                if (p == main_pers or p == cur_pers): 
                    continue
                if (main_pers is None or nl): 
                    main_pers = p
                    LinkAnalyzer.__process_person(p, ad)
                    continue
                if (main_pers is not None): 
                    nam = p.get_string_value(PersonReferent.ATTR_FIRSTNAME)
                    if (nam is None or (len(nam) < 2)): 
                        continue
                    LinkAnalyzer.__process_person(p, ad)
                    li = LinkReferent._new2298(main_pers, p)
                    li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                    li.add_occurence_of_ref_tok(ReferentToken(li, t, t))
                    continue
                cur_pers = p
                LinkAnalyzer.__process_person(p, ad)
                continue
            p = (Utils.ifNotNull(cur_pers, main_pers))
            if (p is None): 
                if (isinstance(r, OrganizationReferent)): 
                    cur_org = (Utils.asObjectOrNull(r, OrganizationReferent))
                    LinkAnalyzer.__set_object(r)
                    continue
                elif (cur_org is None): 
                    continue
            elif (((p.is_female and not p.is_male)) or ((not p.is_female and p.is_male))): 
                pass
            else: 
                PersonHelper.create_sex(p, t)
            geo_ = Utils.asObjectOrNull(r, GeoReferent)
            if (geo_ is not None and geo_.is_city and p is not None): 
                tt = t.previous
                first_pass4299 = True
                while True:
                    if first_pass4299: first_pass4299 = False
                    else: tt = tt.previous
                    if (not (tt is not None)): break
                    te = Utils.asObjectOrNull(tt, TextToken)
                    if (te is not None): 
                        if (te.term.startswith("УР") or te.term.startswith("РОДИ") or te.term.startswith("РОЖД")): 
                            LinkAnalyzer.__set_object(geo_)
                            li = LinkReferent._new2299(LinkType.BORN, p, geo_)
                            li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                            li.add_occurence_of_ref_tok(ReferentToken(li, tt, t))
                            break
                    if (isinstance(tt.get_referent(), DateReferent)): 
                        continue
                    if (tt.length_char > 1): 
                        break
            if (p is not None and p.find_slot(PersonReferent.ATTR_BORN, None, True) is None and (isinstance(t.get_referent(), DateReferent))): 
                tt = t.previous
                first_pass4300 = True
                while True:
                    if first_pass4300: first_pass4300 = False
                    else: tt = tt.previous
                    if (not (tt is not None)): break
                    te = Utils.asObjectOrNull(tt, TextToken)
                    if (te is not None): 
                        if (te.term.startswith("УРОЖ") or te.term.startswith("РОДИ") or te.term.startswith("РОЖД")): 
                            p.add_slot(PersonReferent.ATTR_BORN, t.get_referent(), False, 0)
                            break
                    if (isinstance(tt.get_referent(), GeoReferent)): 
                        continue
                    if (tt.length_char > 1): 
                        break
                if (isinstance(t.next0_, TextToken)): 
                    if (t.next0_.term.startswith("РОДИ") or t.next0_.term.startswith("РОЖД")): 
                        p.add_slot(PersonReferent.ATTR_BORN, t.get_referent(), False, 0)
            if (isinstance(r, PhoneReferent)): 
                LinkAnalyzer.__set_object(r)
                li = LinkReferent._new2300(LinkType.CONTACT, r)
                if (cur_org is not None): 
                    li.object1 = cur_org
                else: 
                    li.object1 = p
                li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                li.add_occurence_of_ref_tok(ReferentToken(li, t, t))
                continue
            if (isinstance(r, UriReferent)): 
                sh = r.scheme
                if (sh == "http" or sh == "https" or sh == "www"): 
                    continue
                LinkAnalyzer.__set_object(r)
                li = LinkReferent._new2299(LinkType.CONTACT, p, r)
                if (cur_org is not None): 
                    li.object1 = cur_org
                else: 
                    li.object1 = p
                li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                li.add_occurence_of_ref_tok(ReferentToken(li, t, t))
                continue
            if (isinstance(r, PersonIdentityReferent)): 
                if (p is None): 
                    continue
                LinkAnalyzer.__set_object(r)
                li = LinkReferent._new2299(LinkType.DOCUMENT, p, r)
                for s in r.slots: 
                    if (isinstance(s.value, DateReferent)): 
                        li.date_from = Utils.asObjectOrNull(s.value, DateReferent)
                        break
                li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                li.add_occurence_of_ref_tok(ReferentToken(li, t, t))
                continue
            if ((isinstance(r, StreetReferent)) or (isinstance(r, AddressReferent))): 
                LinkAnalyzer.__set_object(r)
                li = LinkReferent._new2300(LinkType.ADDRESS, r)
                if (cur_org is not None): 
                    li.object1 = cur_org
                else: 
                    li.object1 = p
                LinkAnalyzer.__corr_addr_typ(li, t)
                li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                li.add_occurence_of_ref_tok(ReferentToken(li, t, t))
                continue
            if (r is not None and p is not None): 
                if (r.type_name == "WEAPON" or r.type_name == "TRANSPORT"): 
                    LinkAnalyzer.__set_object(r)
                    li = LinkReferent._new2298(p, r)
                    li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                    li.add_occurence_of_ref_tok(ReferentToken(li, t, t))
                    continue
            if (t.is_value("ОБРАЗОВАНИЕ", None)): 
                cur_typ = ResumeItemType.STUDY
            if ((t.is_value2("ОПЫТ", "РАБОТЫ") or t.is_value2("МЕСТО", "РАБОТЫ") or t.is_value2("ПЕРИОД", "РАБОТЫ")) or t.is_value("РАБОТАТЬ", None)): 
                cur_typ = ResumeItemType.ORGANIZATION
            if (p is None): 
                continue
            if (t.is_newline_before): 
                rt = ResumeAnalyzer._parse_org(t, cur_typ)
                if (rt is not None): 
                    cur_typ = rt.referent.typ
                    li = LinkAnalyzer.__process_resume_org(p, ad, rt)
                    if (li is not None): 
                        ad.register_referent(li)
                        li.add_occurence_of_ref_tok(rt)
                        cur_org = (Utils.asObjectOrNull(li.object2, OrganizationReferent))
                    t = rt.end_token
                    continue
            tmp_orgs.clear()
            ResumeAnalyzer._parse_org2(t, None, tmp_orgs)
            if (len(tmp_orgs) > 0): 
                for rt1 in tmp_orgs: 
                    li = LinkAnalyzer.__process_resume_org(p, ad, rt1)
                    if (li is not None): 
                        ad.register_referent(li)
                        li.add_occurence_of_ref_tok(rt1)
                        cur_org = (Utils.asObjectOrNull(li.object2, OrganizationReferent))
                    t = rt1.end_token
                continue
            if (isinstance(r, OrganizationReferent)): 
                cur_org = (Utils.asObjectOrNull(r, OrganizationReferent))
                LinkAnalyzer.__process_org(Utils.asObjectOrNull(r, OrganizationReferent), ad, 0)
                li = LinkReferent._new2298(p, r)
                rt1 = LinkAnalyzer.__corr_org_typ(t, t, li)
                if (li.typ != LinkType.UNDEFINED or li.param is not None): 
                    li = (Utils.asObjectOrNull(ad.register_referent(li), LinkReferent))
                    li.add_occurence_of_ref_tok(rt1)
                continue
            if (isinstance(r, PersonPropertyReferent)): 
                li = LinkAnalyzer.__process_person_attr(p, Utils.asObjectOrNull(r, PersonPropertyReferent), ad)
                if (li is not None): 
                    rt1 = LinkAnalyzer.__corr_org_typ(t, t, li)
                    ad.register_referent(li)
                    li.add_occurence_of_ref_tok(rt1)
                    cur_org = (Utils.asObjectOrNull(li.object2, OrganizationReferent))
                continue
            pat = PersonAttrToken.try_attach(t, PersonAttrToken.PersonAttrAttachAttrs.NO)
            if (pat is not None and pat.prop_ref is not None): 
                li = LinkAnalyzer.__process_person_attr(p, pat.prop_ref, ad)
                if (li is not None): 
                    rt1 = LinkAnalyzer.__corr_org_typ(t, pat.end_token, li)
                    ad.register_referent(li)
                    li.add_occurence_of_ref_tok(rt1)
                    cur_org = (Utils.asObjectOrNull(li.object2, OrganizationReferent))
                    t = pat.end_token
                    continue
        for r in ad.referents: 
            li = Utils.asObjectOrNull(r, LinkReferent)
            if (li is None): 
                continue
            if (li.typ != LinkType.BORN): 
                continue
            if (not (isinstance(li.object1, PersonReferent))): 
                continue
            dt = Utils.asObjectOrNull(li.object1.get_slot_value(PersonReferent.ATTR_BORN), DateReferent)
            if (dt is not None and li.date_from is None): 
                li.date_from = dt
    
    @staticmethod
    def __is_object(r : 'Referent') -> bool:
        return r.get_string_value("OBJECT") == "true"
    
    @staticmethod
    def __set_object(r : 'Referent') -> None:
        r.add_slot("OBJECT", "true", True, 0)
    
    @staticmethod
    def __process_person(p : 'PersonReferent', ad : 'AnalyzerData') -> None:
        if (LinkAnalyzer.__is_object(p)): 
            return
        LinkAnalyzer.__set_object(p)
        for i in range(len(p.slots) - 1, -1, -1):
            sl = p.slots[i]
            r = Utils.asObjectOrNull(sl.value, Referent)
            if ((isinstance(r, UriReferent)) or (isinstance(r, PhoneReferent))): 
                LinkAnalyzer.__set_object(r)
                ad.register_referent(LinkReferent._new2306(p, r, LinkType.CONTACT))
                del p.slots[i]
                continue
            if (isinstance(r, PersonPropertyReferent)): 
                li = LinkAnalyzer.__process_person_attr(p, Utils.asObjectOrNull(r, PersonPropertyReferent), ad)
                if (li is not None): 
                    del p.slots[i]
                    ad.register_referent(li)
    
    @staticmethod
    def __process_person_attr(p : 'PersonReferent', pr : 'PersonPropertyReferent', ad : 'AnalyzerData') -> 'LinkReferent':
        nam = pr.name
        if (pr.higher is not None): 
            nam0 = MiscHelper.get_text_morph_var_by_case(pr.higher.name, MorphCase.GENITIVE, False)
            nam = "{0} {1}".format(nam, Utils.ifNotNull(nam0, pr.higher.name))
            pr = pr.higher
        li = LinkReferent._new2307(p)
        r = Utils.asObjectOrNull(pr.get_slot_value(PersonPropertyReferent.ATTR_REF), Referent)
        if (isinstance(r, OrganizationReferent)): 
            LinkAnalyzer.__process_org(Utils.asObjectOrNull(r, OrganizationReferent), ad, 0)
            li.object2 = r
            if (pr.kind == PersonPropertyKind.BOSS): 
                li.typ = LinkType.WORK
        elif (isinstance(r, PersonReferent)): 
            LinkAnalyzer.__process_person(Utils.asObjectOrNull(r, PersonReferent), ad)
            li.object2 = r
            if (pr.kind == PersonPropertyKind.KIN): 
                li.typ = LinkType.FAMILY
        elif (isinstance(r, GeoReferent)): 
            if (r.is_city and nam == "уроженец"): 
                LinkAnalyzer.__set_object(r)
                li.object2 = r
                li.typ = LinkType.BORN
                return li
            else: 
                return None
        else: 
            return None
        li.param = nam
        return li
    
    @staticmethod
    def __process_resume_org(p : 'PersonReferent', ad : 'AnalyzerData', rt : 'ReferentToken') -> 'LinkReferent':
        rr = Utils.asObjectOrNull(rt.referent, ResumeItemReferent)
        if (not (isinstance(rr.ref, OrganizationReferent))): 
            return None
        LinkAnalyzer.__process_org(Utils.asObjectOrNull(rr.ref, OrganizationReferent), ad, 0)
        li = LinkReferent._new2299((LinkType.STUDY if rr.typ == ResumeItemType.STUDY else LinkType.WORK), p, rr.ref)
        li.param = rr.value
        dr = Utils.asObjectOrNull(rr.date_range, DateRangeReferent)
        if (dr is not None): 
            li.date_from = dr.date_from
            li.date_to = dr.date_to
        if (isinstance(rr.date_range, DateReferent)): 
            li.date_from = li.date_to = Utils.asObjectOrNull(rr.date_range, DateReferent)
        return li
    
    @staticmethod
    def __process_org(org0_ : 'OrganizationReferent', ad : 'AnalyzerData', lev : int=0) -> None:
        if (lev > 4): 
            return
        if (LinkAnalyzer.__is_object(org0_)): 
            return
        LinkAnalyzer.__set_object(org0_)
        if (org0_.higher is None): 
            return
        LinkAnalyzer.__process_org(org0_.higher, ad, lev + 1)
        ad.register_referent(LinkReferent._new2306(org0_, org0_.higher, LinkType.UNIT))
        org0_.higher = None
    
    @staticmethod
    def __corr_org_typ(tb : 'Token', te : 'Token', li : 'LinkReferent') -> 'ReferentToken':
        dt = None
        dr = None
        cou = 5
        t0 = tb
        t1 = te
        tt = tb.previous
        first_pass4301 = True
        while True:
            if first_pass4301: first_pass4301 = False
            else: tt = tt.previous
            if (not (tt is not None and cou > 0)): break
            if (tt.is_newline_after): 
                break
            if (tt.length_char == 1): 
                continue
            if (isinstance(tt.get_referent(), DateReferent)): 
                dt = (Utils.asObjectOrNull(tt.get_referent(), DateReferent))
                t0 = tt
            elif (isinstance(tt.get_referent(), DateRangeReferent)): 
                dr = (Utils.asObjectOrNull(tt.get_referent(), DateRangeReferent))
            elif (isinstance(tt, ReferentToken)): 
                break
            elif ((tt.is_value("ОБРАЗОВАНИЕ", None) or tt.is_value("ОКОНЧИТЬ", None) or tt.is_value("ОБУЧАТЬСЯ", None)) or tt.is_value("ЗАКОНЧИТЬ", None)): 
                li.typ = LinkType.STUDY
            elif (tt.is_value("РАБОТАТЬ", None) or tt.is_value2("ПРОХОДИТЬ", "ПРАКТИКА")): 
                li.typ = LinkType.WORK
            elif (tt.is_value("ПОСТУПИТЬ", None)): 
                if (tt.next0_ is not None and tt.next0_.is_value2("НА", "РАБОТА")): 
                    li.typ = LinkType.WORK
                else: 
                    li.typ = LinkType.STUDY
            else: 
                cou -= 1
        if (li.typ != LinkType.UNDEFINED and dt is None and dr is None): 
            tt = te.next0_
            first_pass4302 = True
            while True:
                if first_pass4302: first_pass4302 = False
                else: tt = tt.next0_
                if (not (tt is not None)): break
                if (tt.is_newline_before): 
                    break
                if (MiscHelper.can_be_start_of_sentence(tt)): 
                    break
                if (tt.length_char == 1 or tt.is_value("ПЕРИОД", None)): 
                    continue
                if (isinstance(tt.get_referent(), DateReferent)): 
                    dt = (Utils.asObjectOrNull(tt.get_referent(), DateReferent))
                    t1 = tt
                    break
                elif (isinstance(tt.get_referent(), DateRangeReferent)): 
                    dr = (Utils.asObjectOrNull(tt.get_referent(), DateRangeReferent))
                    t1 = tt
                    break
        if (dr is not None): 
            li.date_from = dr.date_from
            li.date_to = dr.date_to
        elif (dt is not None): 
            li.date_from = li.date_to = dt
        return ReferentToken(li, t0, t1)
    
    @staticmethod
    def __corr_addr_typ(li : 'LinkReferent', t : 'Token') -> None:
        is_org = isinstance(li.object1, OrganizationReferent)
        typ = LinkAnalyzer.__check_addr_typ(t.begin_token, is_org)
        if (typ is not None): 
            li.param = typ
            return
        tt = t.previous
        while tt is not None: 
            typ = LinkAnalyzer.__check_addr_typ(tt, is_org)
            if (typ is not None): 
                li.param = typ
                return
            if (tt.is_newline_before or MiscHelper.can_be_start_of_sentence(tt) or tt.is_comma): 
                break
            tt = tt.previous
    
    @staticmethod
    def __check_addr_typ(t : 'Token', is_org : bool) -> str:
        tt = Utils.asObjectOrNull(t, TextToken)
        if (tt is None): 
            return None
        if (tt.is_value("АДРЕС", None)): 
            return LinkAnalyzer.__check_addr_typ(t.next0_, is_org)
        if (tt.term.startswith("ЮР") and is_org): 
            return "юридический"
        if (tt.term.startswith("ФАКТ") and is_org): 
            return "фактический"
        if (not is_org): 
            if (tt.term.startswith("ЗАРЕГ") or tt.term.startswith("РЕГИСТР") or tt.term.startswith("ПРОПИС")): 
                return "регистрация"
            if (tt.term.startswith("ФАКТИЧ") or tt.term.startswith("ПРОЖИВ")): 
                return "проживание"
        return None
    
    __m_initialized = False
    
    __m_lock = None
    
    @staticmethod
    def initialize() -> None:
        with LinkAnalyzer.__m_lock: 
            if (LinkAnalyzer.__m_initialized): 
                return
            LinkAnalyzer.__m_initialized = True
            MetaLink.initialize()
            ProcessorService.register_analyzer(LinkAnalyzer())
    
    # static constructor for class LinkAnalyzer
    @staticmethod
    def _static_ctor():
        LinkAnalyzer.__m_lock = threading.Lock()

LinkAnalyzer._static_ctor()