-- --------------------------------------------------------------------------------
-- _020_nodeLink Group Routines
-- --------------------------------------------------------------------------------
DELIMITER $$$

create function nodeLink_UpdateStart() returns int unsigned
not deterministic modifies sql data
begin
    return __nodeWorkset_Start();
end$$$


create function __nodeLink_UpdateCreateExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    repeat
        insert nodeLinkT(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l1.nodeLinkTypeId, l1.srcNodeId, l2.dstNodeId, (l1.metric + l2.metric)
        from nodeLinkT l1
        join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId
        join zzzNodeWorksetT ws1 on l1.srcNodeId = ws1.nodeId
        join zzzNodeWorksetT ws2 on l2.dstNodeId = ws2.nodeId
        left join nodeLinkT l3 on l1.nodeLinkTypeId = l3.nodeLinkTypeId
            and l3.srcNodeId = l1.srcNodeId
            and l3.dstNodeId = l2.dstNodeId
            and l3.metric <= l1.metric + l2.metric
        where l1.srcNodeId != l2.dstNodeId
            and l1.metric > 0
            and l2.metric > 0
            and ws1.connId = connection_id()
            and ws2.connId = connection_id()
            and l3.srcNodeId is null
        on duplicate key update
            nodeLinkT.metric = least(nodeLinkT.metric, l1.metric + l2.metric);
    until row_count() < 1 end repeat;

    return 1;
end$$$


create function __nodeLink_UpdateDeleteExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare row_cnt int unsigned default 0;

    create temporary table if not exists zzNodeLinkDelete (
        nodeLinkTypeId int unsigned not null,
        srcNodeId int unsigned not null,
        dstNodeId int unsigned not null,
        metric int not null default 1,
        primary key(nodeLinkTypeId, srcNodeId, dstNodeId),
        unique(nodeLinkTypeId, dstNodeId, srcNodeId)
    ) engine=MEMORY;

    create temporary table if not exists zzNodeLinkDelete2 like zzNodeLinkDelete;
    delete from zzNodeLinkDelete;
    delete from zzNodeLinkDelete2;

    insert zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
    select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
    from nodeLinkT l
    join zzzNodeWorksetT ws1 on l.srcNodeId = ws1.nodeId
    join zzzNodeWorksetT ws2 on l.dstNodeId = ws2.nodeId
    where l.metric > 1
        and ws1.connId = connection_id()
        and ws2.connId = connection_id()
        and not exists (
            select 1
            from nodeLinkT l1
            join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId
            where l1.nodeLinkTypeId = l.nodeLinkTypeId
                and l1.srcNodeId = l.srcNodeId
                and l2.dstNodeId = l.dstNodeId
                and l1.metric > 0
                and l2.metric > 0
                and l.metric = l1.metric + l2.metric);
    set row_cnt = row_count();

    while row_cnt > 0 do
        insert ignore zzNodeLinkDelete2
        select * from zzNodeLinkDelete ld;

        insert ignore zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        join zzzNodeWorksetT ws1 on l.srcNodeId = ws1.nodeId
        join zzzNodeWorksetT ws2 on l.dstNodeId = ws2.nodeId
        join zzNodeLinkDelete2 l1 on l.nodeLinkTypeId = l1.nodeLinkTypeId and l.srcNodeId = l1.srcNodeId
        join nodeLinkT l2 on l.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId and l.dstNodeId = l2.dstNodeId
        where l.metric > 1
            and l2.metric > 0
            and l.metric = l1.metric + l2.metric
            and ws1.connId = connection_id()
            and ws2.connId = connection_id();

        set row_cnt = row_count();

        insert ignore zzNodeLinkDelete2
        select * from zzNodeLinkDelete ld;

        insert ignore zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        join zzzNodeWorksetT ws1 on l.srcNodeId = ws1.nodeId
        join zzzNodeWorksetT ws2 on l.dstNodeId = ws2.nodeId
        join nodeLinkT l1 on l.nodeLinkTypeId = l1.nodeLinkTypeId and l.srcNodeId = l1.srcNodeId
        join zzNodeLinkDelete2 l2 on l.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId and l.dstNodeId = l2.dstNodeId
        where l.metric > 1
            and l1.metric > 0
            and l.metric = l1.metric + l2.metric
            and ws1.connId = connection_id()
            and ws2.connId = connection_id();

        set row_cnt = row_cnt + row_count();
    end while;

    delete from nodeLinkT
    using nodeLinkT
    join zzNodeLinkDelete l on nodeLinkT.nodeLinkTypeId = l.nodeLinkTypeId
        and nodeLinkT.srcNodeId = l.srcNodeId
        and nodeLinkT.dstNodeId = l.dstNodeId
        and nodeLinkT.metric = l.metric;

    return 1;
end$$$


create function __nodeLink_UpdateRefreshExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare deleted, added int unsigned;
    set deleted = __nodeLink_UpdateDeleteExtraLinks();
    set added = __nodeLink_UpdateCreateExtraLinks();
    return deleted + added;
end$$$


create function nodeLink_UpdateEnd() returns int unsigned
not deterministic modifies sql data
begin
    if __nodeWorkset_GetLevel() = 1 then
        set @1 = __nodeLink_UpdateRefreshExtraLinks();
        set @2 = __nodeAncestry_Update();
        -- FORWARD: __object_UpdateRatings is defined in _080_object
        set @3 = __object_UpdateRatings();
        -- FORWARD: __object_UpdateAddresses is defined in _080_object
        set @4 = __object_UpdateAddresses();
    end if;
    set @5 = __nodeWorkset_End();
    return 1;
end$$$


create function nodeLink_IsUpdateOn() returns int unsigned
not deterministic
begin 
    return (__nodeWorkset_GetLevel() > 0);
end$$$


create function __nodeLink_UpdateWorkSet(node_id int unsigned, direction int) returns int unsigned
comment 'direction=-1 - downwards; direction=1 - upwards; direction=0 - both; others - undefined'
not deterministic
begin
    return __nodeWorkset_Update(node_id, direction);
end$$$


create function __nodeLink_Create(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;

    if not exists (select 1 
                    from allowedNodeLinkT anl 
                    join nodeT src on anl.srcNodeTypeId = src.nodeTypeId 
                    join nodeT dst on anl.dstNodeTypeId = dst.nodeTypeId
                    where src.id = src_id and dst.id = dst_id) 
    then
        call raiseError2('nodeLink', 'create', src_id, dst_id, 'cannot create link between');
    end if;
    
    insert nodeLinkT(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
    select nlt.id, src_id, dst_id, 1
    from nodeLinkTypeT nlt
    join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code, nltdef.code)
        and not exists (select 1 
                        from nodeLinkT l 
                        where l.nodeLinkTypeId = nlt.id 
                            and l.srcNodeId = src_id 
                            and l.dstNodeId = dst_id 
                            and l.metric <= 1)
    on duplicate key update nodeLinkT.metric = 1;
    
    set res = row_count();
    if nodeLink_IsUpdateOn() = 1 and res > 0 then
        set @1 = __nodeLink_UpdateWorkSet(src_id, -1) and __nodeLink_UpdateWorkSet(dst_id, 1);
    end if;

    return res;
end$$$


create function __nodeLink_Delete(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;

    delete from nodeLinkT
    using nodeLinkT
    join nodeLinkTypeT nlt on nodeLinkT.nodeLinkTypeId = nlt.id
    join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code, nltdef.code)
        and nodeLinkT.srcNodeId = src_id 
        and nodeLinkT.dstNodeId = dst_id 
        and nodeLinkT.metric = 1;

    set res = row_count();
    if nodeLink_IsUpdateOn() = 1 and res > 0 then
        set @1 = __nodeLink_UpdateWorkSet(src_id, -1) and __nodeLink_UpdateWorkSet(dst_id, 1);
    end if;

    return res;
end$$$


create function __nodeLink_CreateExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare row_cnt int unsigned default 0;
    repeat
        insert nodeLinkT(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l1.nodeLinkTypeId, l1.srcNodeId, l2.dstNodeId, (l1.metric + l2.metric)
        from nodeLinkT l1
        join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId
        left join nodeLinkT l3 on l3.nodeLinkTypeId = l1.nodeLinkTypeId 
            and l3.srcNodeId = l1.srcNodeId
            and l3.dstNodeId = l2.dstNodeId
            and l3.metric <= l1.metric + l2.metric
        where l1.srcNodeId != l2.dstNodeId
            and l1.metric > 0
            and l2.metric > 0
            and l3.srcNodeId is null
        on duplicate key update 
            nodeLinkT.metric = least(nodeLinkT.metric, l1.metric + l2.metric);

        set row_cnt = row_cnt + row_count();
    until row_count() < 1 end repeat;
    return row_cnt;
end$$$


create function __nodeLink_DeleteExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare row_cnt int unsigned default 0;
    create temporary table if not exists zzNodeLinkDelete (
        nodeLinkTypeId int unsigned not null,
        srcNodeId int unsigned not null, 
        dstNodeId int unsigned not null, 
        metric int not null default 1,
        primary key(nodeLinkTypeId, srcNodeId, dstNodeId),
        unique(nodeLinkTypeId, dstNodeId, srcNodeId)
    ) engine=MEMORY;
    
    repeat
        delete from zzNodeLinkDelete;
        
        insert zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        where l.metric > 1
            and not exists (
                select 1 
                from nodeLinkT l1 
                join nodeLinkT l2 on l1.nodeLinkTypeId = l2.nodeLinkTypeId and l1.dstNodeId = l2.srcNodeId 
                where l1.nodeLinkTypeId = l.nodeLinkTypeId
                    and l1.srcNodeId = l.srcNodeId 
                    and l2.dstNodeId = l.dstNodeId
                    and l1.metric > 0
                    and l2.metric > 0
                    and l.metric = l1.metric + l2.metric);

        insert ignore zzNodeLinkDelete(nodeLinkTypeId, srcNodeId, dstNodeId, metric)
        select l.nodeLinkTypeId, l.srcNodeId, l.dstNodeId, l.metric
        from nodeLinkT l
        join nodeLinkT l2 on l.nodeLinkTypeId = l2.nodeLinkTypeId 
            and l.srcNodeId = l2.srcNodeId 
            and l.dstNodeId = l2.dstNodeId 
            and l.metric > l2.metric
        where l.metric > 1;
        
        delete from nodeLinkT
        using nodeLinkT 
        join zzNodeLinkDelete l on nodeLinkT.nodeLinkTypeId = l.nodeLinkTypeId 
            and nodeLinkT.srcNodeId = l.srcNodeId 
            and nodeLinkT.dstNodeId = l.dstNodeId 
            and nodeLinkT.metric = l.metric;

        set row_cnt = row_cnt + row_count();
    until row_count() < 1 end repeat;
    return row_cnt;
end$$$


create function __nodeLink_RefreshExtraLinks() returns int unsigned
not deterministic modifies sql data
begin
    declare deleted, added int unsigned;
    set deleted = __nodeLink_DeleteExtraLinks();
    set added = __nodeLink_CreateExtraLinks();
    return deleted + added;
end$$$


create function nodeLink_Create(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;
    set @1 = nodeLink_UpdateStart();
    set res = __nodeLink_Create(link_code, src_id, dst_id);
    set @2 = nodeLink_UpdateEnd();
    return 1;
end$$$


create function nodeLink_Create2(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    set @1 = nodeLink_Create(link_code, src_id, dst_id);
    return dst_id;
end$$$


create function nodeLink_Delete(
    link_code varchar(30) binary, 
    src_id int unsigned, 
    dst_id int unsigned
) returns int unsigned
not deterministic modifies sql data
begin
    declare res int unsigned;
    set @1 = nodeLink_UpdateStart();
    set res = __nodeLink_Delete(link_code, src_id, dst_id);
    set @2 = nodeLink_UpdateEnd();
    return 1;
end$$$


-- ---------------------------------
-- nodeLink smart linking
-- ---------------------------------

create function nodeLink_Relink(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'links src_id and dst_id, detects and removes unnecessary linking'
not deterministic modifies sql data
begin
    declare link_code varchar(30) binary;
    declare link_id int unsigned;
    
    select nlt.id, nlt.code into link_id, link_code
    from nodeLinkTypeT nlt join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code_, nltdef.code);
    
    if src_id_ = dst_id_ 
        or exists(
            select 1 
            from nodeLinkT nl 
            where nl.nodeLinkTypeId = link_id
                and nl.srcNodeId = src_id_
                and nl.dstNodeId = dst_id_
                and nl.metric = 1)
    then
        return 1;
    end if;

    drop temporary table if exists nodeLinkRelinkInserts;

-- link to nodes, which are parents for src_id and children for parents of dst_id
    create temporary table nodeLinkRelinkInserts(srcNodeId int unsigned not null primary key) engine=MEMORY;

    insert nodeLinkRelinkInserts(srcNodeId)
    select distinct trglink.dstNodeId
    from nodeLinkT dstlink 
    join nodeLinkT trglink on dstlink.nodeLinkTypeId = trglink.nodeLinkTypeId
        and dstlink.srcNodeId = trglink.srcNodeId
    join allowedNodeLinkByNodeV dstallow on dstlink.srcNodeId = dstallow.srcNodeId 
        and dstlink.dstNodeId = dstallow.dstNodeId
    join allowedNodeLinkByNodeV trgallow on trglink.srcNodeId = trgallow.srcNodeId 
        and trglink.dstNodeId = trgallow.dstNodeId
    left join nodeLinkT exilink on dstlink.nodeLinkTypeId = exilink.nodeLinkTypeId
        and trglink.dstNodeId = exilink.srcNodeId
        and dst_id_ = exilink.dstNodeId
        and exilink.metric = 1
    where dstlink.nodeLinkTypeId = link_id
        and dstlink.dstNodeId = dst_id_
        and dstlink.metric = 1
        and trglink.dstNodeId != src_id_
        and trglink.metric = 1
        and dstallow.singleSrc = 'y'
        and trgallow.singleSrc = 'y'
        and exists (
                select 1
                from nodeLinkT srclink
                join allowedNodeLinkByNodeV srcallow on srclink.srcNodeId = srcallow.srcNodeId
                    and srclink.dstNodeId = srcallow.dstNodeId
                where srclink.nodeLinkTypeId = dstlink.nodeLinkTypeId
                    and srclink.srcNodeId = trglink.dstNodeId
                    and srclink.dstNodeId = src_id_
                    and srclink.metric = 1
                    and srcallow.singleSrc = 'y')
        and exilink.metric is null;
    
    set @1 = nodeLink_UpdateStart();
    
    insert zzzDevNullLinkT(n)
    select __nodeLink_Create(link_code, nlri.srcNodeId, dst_id_)
    from nodeLinkRelinkInserts nlri;

    set @2 = __nodeLink_Create(link_code, src_id_, dst_id_);
    
    set @3 = nodeLink_UpdateEnd();
    
    drop temporary table nodeLinkRelinkInserts;

    return 1; 
end$$$


create function nodeLink_Relink2(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'calls nodeLink_Relink and returns dst_id'
not deterministic modifies sql data
begin
    set @1 = nodeLink_Relink(link_code_, src_id_, dst_id_);
    return dst_id_;
end$$$


create function nodeLink_Unlink(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'unlinks src_id and dst_id, links dst_id to parents of src_id'
not deterministic modifies sql data
begin
    declare link_code varchar(30) binary;
    declare link_id int unsigned;
    declare min_metric, link_to_parent int;
    
    select nlt.id, nlt.code into link_id, link_code
    from nodeLinkTypeT nlt join nodeLinkTypeT nltdef on nltdef.isDefault = 'y'
    where nlt.code = coalesce(link_code_, nltdef.code);
    
    if src_id_ = dst_id_ 
        or not exists(
            select 1 
            from nodeLinkT nl 
            where nl.nodeLinkTypeId = link_id
                and nl.srcNodeId = src_id_
                and nl.dstNodeId = dst_id_)
    then
        return 1;
    end if;

    drop temporary table if exists nodeLinkUnlinkDeletes;
    create temporary table nodeLinkUnlinkDeletes(srcNodeId int unsigned not null primary key) engine=MEMORY;

-- add nodes to remove list - child for src_id and parent for dst_id, but with special condition
    insert nodeLinkUnlinkDeletes(srcNodeId)
    select nl.srcNodeId
    from nodeLinkT nl
    where nl.nodeLinkTypeId = link_id
        and nl.dstNodeId = dst_id_
        and nl.metric = 1
        and exists (
            select 1 
            from nodeLinkT nl1
            where nl1.nodeLinkTypeId = link_id
                and nl1.srcNodeId = src_id_
                and nl1.dstNodeId = nl.srcNodeId
                and nl1.metric > 0)
-- check if node in question is not linked to other nodes of different types - if linked to more than 1 we cannot remove it
        and (select count(distinct n2.nodeTypeId)
            from nodeLinkT nl21
            join nodeLinkT nl22 on nl21.nodeLinkTypeId = nl22.nodeLinkTypeId and nl21.srcNodeId = nl22.srcNodeId
            join nodeT n2 on nl21.srcNodeId = n2.id
            where nl21.nodeLinkTypeId = link_id
                and nl21.srcNodeId != src_id_
                and nl21.dstNodeId = dst_id_
                and nl21.metric = 1
                and nl22.dstNodeId = nl.srcNodeId
                and nl22.metric = 1
                and n2.isRoot = 'n') < 2; -- 0 or 1

    set @1 = nodeLink_UpdateStart();
    
    set @2 = __nodeLink_Delete(link_code, src_id_, dst_id_);
    
    insert zzzDevNullLinkT(n)
    select __nodeLink_Delete(link_code, nlud.srcNodeId, dst_id_)
    from nodeLinkUnlinkDeletes nlud;

    -- FORWARD: __node_LinkDefaults is defined in _03_node
    set @3 = __node_LinkDefaults(link_code, dst_id_);
    
    set @4 = nodeLink_UpdateEnd();
    
    drop temporary table nodeLinkUnlinkDeletes;
    
    return 1;
end$$$


create function nodeLink_Unlink2(
    link_code_ varchar(30) binary, 
    src_id_ int unsigned, 
    dst_id_ int unsigned
) returns int unsigned
comment 'calls nodeLink_Unlink and returns dst_id'
not deterministic modifies sql data
begin
    set @1 = nodeLink_Unlink(link_code_, src_id_, dst_id_);
    return dst_id_;
end$$$

