CREATE TABLE nodes (
    id INTEGER NOT NULL PRIMARY KEY,
    parent_id INTEGER,
    project_id INTEGER,
    name VARCHAR NOT NULL,
    path VARCHAR NOT NULL UNIQUE,
    uuid char(40) NOT NULL UNIQUE,
    first_change_id INTEGER NOT NULL,
    last_change_id INTEGER NOT NULL,
    kind VARCHAR NOT NULL,
    ctime INTEGER NOT NULL,
    ctimetz INTEGER NOT NULL,
    ctimetzhm VARCHAR, -- NOT NULL
    mtime INTEGER NOT NULL,
    mtimetz INTEGER NOT NULL,
    mtimetzhm VARCHAR, -- NOT NULL
    lang VARCHAR(8) NOT NULL DEFAULT 'en',
    hash VARCHAR,
    num_changes INTEGER,
    sync VARCHAR, -- TODO NOT NULL,
    CONSTRAINT valid_name CHECK(
        replace(name,'/','') = name AND replace(name,'..','') = name
    ),
    CONSTRAINT sync_type CHECK(
        sync IN ('hub','shallow','deep')
    ),
    FOREIGN KEY(parent_id) REFERENCES nodes(id) ON DELETE CASCADE,
    FOREIGN KEY(project_id) REFERENCES projects(id) ON DELETE SET NULL,
    FOREIGN KEY(first_change_id,last_change_id)
        REFERENCES changes_tree(parent,child) ON DELETE CASCADE
);

SELECT create_sequence('nodes');

CREATE TRIGGER
    nodes_ai_1
AFTER INSERT ON
    nodes
FOR EACH ROW
BEGIN
    UPDATE
        nodes
    SET
        ctimetzhm = printf(
            "%+.2d%.2d",
            CAST(NEW.ctimetz / 3600 AS INTEGER),
            (
                abs(NEW.ctimetz) -
                CAST(abs(NEW.ctimetz) / 3600 AS INTEGER) * 3600
            ) / 60
        ),
        mtimetzhm = printf(
            "%+.2d%.2d",
            CAST(NEW.mtimetz / 3600 AS INTEGER),
            (
                abs(NEW.mtimetz) -
                CAST(abs(NEW.mtimetz) / 3600 AS INTEGER) * 3600
            ) / 60
        ),
        path =
            CASE WHEN
                NEW.parent_id IS NOT NULL
            THEN
                (SELECT
                    n.path
                 FROM
                    nodes n
                 WHERE
                    n.id = NEW.parent_id
                ) || '/' || NEW.name
            ELSE
                NEW.name
            END
    WHERE
        id = NEW.id
    ;
END;


CREATE TRIGGER
    nodes_au_2
AFTER UPDATE OF
    name,parent_id
ON
    nodes
FOR EACH ROW
BEGIN

    /*
        First of all the current row
    */

    UPDATE
        nodes
    SET
        -- This sort of works here? Should perhaps be inside its
        -- own AFTER UPDATE ON parent_id TRIGGER?
        project_id = (
            SELECT
                nt.parent
            FROM
                nodes_tree nt
            WHERE
                nt.child = NEW.id and nt.depth = (
                    -- Find parent project with lowest depth
                    SELECT
                        MIN(nt2.depth)
                    FROM
                        nodes_tree nt2
                    INNER JOIN
                        projects p
                    ON
                        p.id = nt2.parent
                    WHERE
                        nt2.child = NEW.id
                )
        ),
        path = (
            CASE WHEN
                NEW.parent_id IS NOT NULL
            THEN
                (SELECT
                    n.path
                 FROM
                    nodes n
                 WHERE
                    n.id = NEW.parent_id
                ) || '/' || name
            ELSE
                name
            END
        )
    WHERE
        id = OLD.id
    ;

    /*
        Trigger an update to our children
    */
    UPDATE
        nodes
    SET
        name = name
    WHERE
        parent_id = NEW.id
    ;

END;


CREATE TRIGGER
    nodes_au_1
AFTER UPDATE OF
    mtimetz
ON
    nodes
FOR EACH ROW
BEGIN
    UPDATE
        nodes
    SET
        mtimetzhm = printf(
            "%+.2d%.2d",
            CAST(NEW.mtimetz / 3600 AS INTEGER),
            (
                abs(NEW.mtimetz) -
                CAST(abs(NEW.mtimetz) / 3600 AS INTEGER) * 3600
            ) / 60
        )
    WHERE
        id = NEW.id
    ;
END;


CREATE TRIGGER
    nodes_bi_1
BEFORE INSERT ON nodes
FOR EACH ROW
BEGIN
    SELECT debug(
        NEW.id,
        NEW.uuid,
        NEW.first_change_id,
        NEW.last_change_id,
        NEW.kind
    );

END;


CREATE TRIGGER
    nodes_bu_1
BEFORE UPDATE OF
    ctime,ctimetz,first_change_id,uuid
ON
    nodes
FOR EACH ROW
BEGIN
    SELECT RAISE(ABORT, 'cannot modify nodes.ctime*,first_change_id,uuid');
END;


CREATE TRIGGER
    nodes_bd_1
BEFORE DELETE ON nodes
FOR EACH ROW
BEGIN
    SELECT debug(
        OLD.id,
        OLD.first_change_id,
        OLD.kind
    );

    DELETE FROM
        changes
    WHERE
        id = OLD.first_change_id
    ;

    /*
        The following is necessary, because although FK relationships
        do result in the remove of rows from nodes_tomerge,
        the deletion of rows from node_deltas just inserts
        more rows. So we remove those trigger-happy rows first.
    */

    DELETE FROM
        node_deltas
    WHERE
        node_id = OLD.id
    ;
END;



/*
 Triggers in SQLite run in the reverse order to which they are defined.
 Actions happen from the bottom up.
 */



CREATE TRIGGER
    tree_ai_nodes_1
AFTER INSERT ON
    nodes
FOR EACH ROW 
BEGIN

    /*
     Insert a matching row in nodes_tree where both parent and child
     are set to the id of the newly inserted object. Depth is set to 0
     as both child and parent are on the same level.
     */

    INSERT INTO
        nodes_tree (
            parent,
            child,
            depth
        )
    VALUES (
        NEW.id,
        NEW.id,
        0
    );

    /*
     Copy all rows that our parent had as its parents, but we modify
     the child id in these rows to be the id of currently inserted row,
     and increase depth by one.
     */

    INSERT INTO
        nodes_tree (
            parent,
            child,
            depth
        )
    SELECT
        x.parent,
        NEW.id,
        x.depth + 1
    FROM
        nodes_tree x
    WHERE
        x.child = NEW.parent_id
    ;

END;

/*
    Handle parent_id changes

    3. Insert the new tree data relating to the new parent
*/

CREATE TRIGGER
    tree_au_nodes_4
AFTER UPDATE OF
    parent_id
ON
    nodes
FOR EACH ROW WHEN
    NEW.parent_id IS NOT NULL AND 
    ( OLD.parent_id IS NULL OR NEW.parent_id != OLD.parent_id )
BEGIN

    INSERT INTO
        nodes_tree (parent, child, depth)
    SELECT
        r1.parent, r2.child, r1.depth + r2.depth + 1
    FROM
        nodes_tree r1
    INNER JOIN
        nodes_tree r2
    ON
        r2.parent = NEW.id
    WHERE
        r1.child = NEW.parent_id
    ;

END;

/*
    Handle parent_id changes

    2. Remove the tree data relating to the old parent
*/

CREATE TRIGGER
    tree_au_nodes_3
AFTER UPDATE OF
    parent_id
ON
    nodes
FOR EACH ROW WHEN
    OLD.parent_id IS NOT NULL AND 
    ( NEW.parent_id IS NULL OR NEW.parent_id != OLD.parent_id)
BEGIN
    DELETE FROM
        nodes_tree
    WHERE
        treeid IN (
            SELECT
                r2.treeid
            FROM
                nodes_tree r1
            INNER JOIN
                nodes_tree r2
            ON
                r1.child = r2.child AND r2.depth > r1.depth
            WHERE
                r1.parent = NEW.id
        )
    ;
END;

/*
 FIXME: Also trigger when column 'path_from' changes. For the moment,
 the user work-around is to temporarily re-parent the row.
*/

/*
 Forbid moves that would create loops:
*/

CREATE TRIGGER
    tree_bu_nodes_2
BEFORE UPDATE OF
    parent_id
ON
    nodes
FOR EACH ROW WHEN
    NEW.parent_id IS NOT NULL AND
    (SELECT
        COUNT(child) > 0
     FROM
        nodes_tree
     WHERE
        child = NEW.parent_id AND parent = NEW.id
    )
BEGIN
    SELECT RAISE (ABORT,
        'Update blocked, because it would create loop in tree.');
END;

/*
 This implementation doesn't support changes to the primary key
*/

CREATE TRIGGER
    tree_bu_nodes_1
BEFORE UPDATE OF
    id
ON
    nodes
FOR EACH ROW WHEN
    OLD.id != NEW.id
BEGIN
    SELECT RAISE (ABORT, 'Changing ids is forbidden.');
END;
