CREATE TABLE hub_related_changes (
    hub_id INTEGER NOT NULL,
    -- Collate below for LIKE/index support
    prefix VARCHAR NOT NULL COLLATE NOCASE,
    change_id INTEGER, -- Leaf nodes only
    sha1 VARCHAR(40) NOT NULL,
    num_changes INTEGER NOT NULL DEFAULT 1,
    PRIMARY KEY(prefix,hub_id),
    CONSTRAINT prefix_length CHECK (LENGTH(prefix) > 0),
    FOREIGN KEY(hub_id) REFERENCES hubs(id) ON DELETE CASCADE,
    FOREIGN KEY(change_id) REFERENCES changes(id) ON DELETE CASCADE
);


/*
    If prefix is already a leaf node then move it down one level and
    put the current node _next_ to it.

    exists: a with change_id
        insert  ab with change_id
        insert  ac with change_id
*/

CREATE TRIGGER
    hub_related_changes_bi_1
BEFORE INSERT ON
    hub_related_changes
FOR EACH ROW WHEN
    EXISTS (SELECT
        1
    FROM
        hub_related_changes hrc
    WHERE
        hrc.hub_id = NEW.hub_id AND
        hrc.prefix = NEW.prefix AND
        -- prevent recursion on multiple insert of same hub/change
        hrc.change_id != NEW.change_id
    )
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );

    -- Push existing node one level down
    INSERT INTO
        hub_related_changes(
            hub_id,
            prefix,
            change_id,
            sha1,
            num_changes
        )
    SELECT
        hrc.hub_id,
        SUBSTR(c.uuid, 1, LENGTH(NEW.prefix) + 1),
        c.id,
        c.uuid,
        1
    FROM
        hub_related_changes hrc
    INNER JOIN
        changes c
    ON
        c.id = hrc.change_id
    WHERE
        hrc.hub_id = NEW.hub_id AND
        hrc.prefix = NEW.prefix
    ;


    -- Insert this node next level down
    INSERT INTO
        hub_related_changes(
            hub_id,
            prefix,
            change_id,
            sha1,
            num_changes
        )
    VALUES(
        NEW.hub_id,
        SUBSTR(NEW.sha1, 1, LENGTH(NEW.prefix) + 1),
        NEW.change_id,
        NEW.sha1,
        1
    );

    SELECT RAISE(IGNORE);
END;


/*
    If prefix is already a branch node then insert one level down

    exists: a with no change_id
        insert  ab with change_id
*/

CREATE TRIGGER
    hub_related_changes_bi_2
BEFORE INSERT ON
    hub_related_changes
FOR EACH ROW WHEN
    EXISTS (SELECT
        1
    FROM
        hub_related_changes hrc
    WHERE
        hrc.hub_id = NEW.hub_id AND
        hrc.prefix = NEW.prefix AND
        hrc.change_id IS NULL
    )
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );

    -- Insert next level down (triggers ai_1 above)
    INSERT INTO
        hub_related_changes(
            hub_id,
            prefix,
            change_id,
            sha1,
            num_changes
        )
    VALUES(
        NEW.hub_id,
        SUBSTR(NEW.sha1, 1, LENGTH(NEW.prefix) + 1),
        NEW.change_id,
        NEW.sha1,
        1
    );

    SELECT RAISE(IGNORE);
END;

/*
    Node got inserted.  If top-level leaf node then just update node
    else update parent
*/
CREATE TRIGGER
    hub_related_changes_ai_1
AFTER INSERT ON
    hub_related_changes
FOR EACH ROW WHEN
    NEW.change_id IS NOT NULL
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );

    UPDATE
        hubs
    SET
        hash = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND hrc.prefix LIKE '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND hrc.prefix LIKE '_'
        )
    WHERE
        LENGTH(NEW.prefix) = 1 AND
        id = NEW.hub_id
    ;


    -- Update parent (branch) node
    UPDATE
        hub_related_changes
    SET
        change_id = NULL,
        sha1 = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(NEW.prefix, 1, LENGTH(NEW.prefix) - 1) || '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(NEW.prefix, 1, LENGTH(NEW.prefix) - 1) || '_'
        )
    WHERE
        LENGTH(NEW.prefix) > 1 AND
        hub_id = NEW.hub_id AND
        prefix = SUBSTR(NEW.prefix,1,LENGTH(NEW.prefix) - 1)
    ;

END;



/*
    Node updates:

        Root Branch updated.
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update node
            With num_changes > 1: update node

        Non-Root Branch updated
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update parent
            With num_changes > 1: update parent

        Branch node becomes leaf (appears as an update)
            Root node:      update node
            Non-Root node:  update parent

    But implemented using the following three actions for efficiency
        - Move child up
        - update parent
        - update node
*/

/*
    Move child up:

        Root Branch updated.
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update node
            With num_changes > 1: update node

        Non-Root Branch updated
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update parent
            With num_changes > 1: update parent
*/

CREATE TRIGGER
    hub_related_changes_bu_1
BEFORE UPDATE ON
    hub_related_changes
FOR EACH ROW WHEN
    OLD.change_id IS NULL AND -- was a branch
    NEW.change_id IS NULL AND -- still a branch
    NEW.num_changes = 1       -- only one child
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );

    /*
        Make current node same as child, but only if child is actually
        a leaf node. If a node gets inserted with more than one
        matching prefix character then it can happen that the child we
        would otherwise move up is in fact another branch node.

    */

    UPDATE
        hub_related_changes
    SET
        change_id = (
            SELECT
                hrc.change_id
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE NEW.prefix || '_'
        ),
        sha1 = (
            SELECT
                hrc.sha1
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE NEW.prefix || '_'
        ),
        num_changes = 1
    WHERE
        hub_id = NEW.hub_id AND
        prefix = NEW.prefix AND
        change_id IS NOT NULL
    ;


    DELETE FROM
        hub_related_changes
    WHERE
        hub_id = NEW.hub_id AND
        prefix LIKE NEW.prefix || '_' AND
        change_id IS NOT NULL
    ;

    SELECT RAISE(IGNORE);
END;


/*
    Delete node, update parent

        Root Branch updated.
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update node
            With num_changes > 1: update node

        Non-Root Branch updated
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update parent
            With num_changes > 1: update parent
*/

CREATE TRIGGER
    hub_related_changes_bu_2
BEFORE UPDATE ON
    hub_related_changes
FOR EACH ROW WHEN
    OLD.change_id IS NULL AND -- was a branch
    NEW.change_id IS NULL AND -- still a branch
    NEW.num_changes = 0       -- no children
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );

    DELETE FROM
        hub_related_changes
    WHERE
        hub_id = NEW.hub_id AND
        prefix = NEW.prefix
    ;

    UPDATE
        hub_related_changes
    SET
        sha1 = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(NEW.prefix, 1, LENGTH(NEW.prefix) - 1) || '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(NEW.prefix, 1, LENGTH(NEW.prefix) - 1) || '_'
        )
    WHERE
        hub_id = NEW.hub_id AND
        prefix = SUBSTR(NEW.prefix,1,LENGTH(NEW.prefix) - 1)
    ;

    SELECT RAISE(IGNORE);

END;


/*
    Update parent

        Non-Root Branch updated
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update parent
            With num_changes > 1: update parent

        Branch node becomes leaf (appears as an update)
            Root node:      update node
            Non-Root node:  update parent
*/


CREATE TRIGGER
    hub_related_changes_au_1
AFTER UPDATE OF
    sha1
ON
    hub_related_changes
FOR EACH ROW WHEN
    LENGTH(NEW.prefix) > 1 -- non-root node
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );

    UPDATE
        hub_related_changes
    SET
        sha1 = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(NEW.prefix, 1, LENGTH(NEW.prefix) - 1) || '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(NEW.prefix, 1, LENGTH(NEW.prefix) - 1) || '_'
        )
    WHERE
        hub_id = NEW.hub_id AND
        prefix = SUBSTR(NEW.prefix,1,LENGTH(NEW.prefix) - 1)
    ;

END;


/*
    Update node
        Root Branch updated.
            With num_changes = 0: delete node, update parent
            With num_changes = 1: move child up, update node
            With num_changes > 1: update node

        Branch node becomes leaf (appears as an update)
            Root node:      update node
            Non-Root node:  update parent
*/
CREATE TRIGGER
    hub_related_changes_au_2
AFTER UPDATE OF
    sha1
ON
    hub_related_changes
FOR EACH ROW WHEN
    LENGTH(NEW.prefix) = 1 -- root node
BEGIN
    SELECT debug(
        NEW.hub_id,
        NEW.prefix,
        NEW.change_id,
        NEW.sha1,
        NEW.num_changes
    );


    UPDATE
        hubs
    SET
        hash = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND hrc.prefix LIKE '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = NEW.hub_id AND hrc.prefix LIKE '_'
        )
    WHERE
        id = NEW.hub_id
    ;

END;


/*
    Leaf got removed.  If top-level leaf then just update node else
    update parent, except when we were deleted by moving us up one
    level
*/

CREATE TRIGGER
    hub_related_changes_ad_1
AFTER DELETE ON
    hub_related_changes
FOR EACH ROW WHEN
    OLD.change_id IS NOT NULL
BEGIN
    SELECT debug(
        OLD.hub_id,
        OLD.prefix,
        OLD.change_id,
        OLD.sha1,
        OLD.num_changes
    );

    UPDATE
        hubs
    SET
        hash = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = OLD.hub_id AND hrc.prefix LIKE '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = OLD.hub_id AND hrc.prefix LIKE '_'
        )
    WHERE
        LENGTH(OLD.prefix) = 1 AND
        id = OLD.hub_id
    ;


    -- Update parent (branch) node
    UPDATE
        hub_related_changes
    SET
        sha1 = (
            SELECT
                agg_sha1_hex(hrc.sha1, hrc.sha1)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = OLD.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(OLD.prefix, 1, LENGTH(OLD.prefix) - 1) || '_'
        ),
        num_changes = (
            SELECT
                CAST(TOTAL(hrc.num_changes) AS INTEGER)
            FROM
                hub_related_changes hrc
            WHERE
                hrc.hub_id = OLD.hub_id AND
                hrc.prefix LIKE
                    SUBSTR(OLD.prefix, 1, LENGTH(OLD.prefix) - 1) || '_'
        )
    WHERE
        LENGTH(OLD.prefix) > 1 AND
        hub_id = OLD.hub_id AND
        prefix = SUBSTR(OLD.prefix,1,LENGTH(OLD.prefix) - 1) AND
        change_id IS NULL
    ;

END;
