BEGIN TRANSACTION;

CREATE TABLE IF NOT EXISTS "category_belongs_to" (
	"category_id"	INTEGER NOT NULL,
	"path"	TEXT NOT NULL,
	PRIMARY KEY("category_id"),
	FOREIGN KEY("category_id") REFERENCES "category"
		ON DELETE CASCADE
		ON UPDATE RESTRICT
);

CREATE TABLE IF NOT EXISTS "entry" (
	"id"	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"activity_id"	INTEGER NOT NULL,
	"start"	INTEGER NOT NULL,
	"stop"	INTEGER CHECK(stop >= start),
	FOREIGN KEY("activity_id") REFERENCES "activity"
		ON DELETE RESTRICT
		ON UPDATE CASCADE
);

CREATE TABLE IF NOT EXISTS "activity_belongs_to" (
	"activity_id"	INTEGER NOT NULL,
	"category_id"	INTEGER NOT NULL,
	FOREIGN KEY("category_id") REFERENCES "category"
		ON DELETE RESTRICT
		ON UPDATE CASCADE,
	FOREIGN KEY("activity_id") REFERENCES "activity"
		ON DELETE CASCADE
		ON UPDATE CASCADE,
	PRIMARY KEY("activity_id","category_id")
);

CREATE TABLE IF NOT EXISTS "category" (
	"id"	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name"	TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS "activity" (
	"id"	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name"	TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS "time_zone" (
	"id"	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"name"	TEXT NOT NULL
);

CREATE TABLE IF NOT EXISTS "time_zone_encounter" (
	"timestamp"	INTEGER NOT NULL PRIMARY KEY,
	"time_zone_id"	INTEGER NOT NULL,
	FOREIGN KEY("time_zone_id") REFERENCES "time_zone"
		ON DELETE RESTRICT
		ON UPDATE CASCADE
);

CREATE TABLE IF NOT EXISTS "streak_definition" (
	"id"	INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	"interval"	INTEGER NOT NULL CHECK(interval > 0),
	"start"	INTEGER NOT NULL,
	"stop"	INTEGER CHECK(stop >= start),
	"added"	INTEGER NOT NULL,
	"removed"	INTEGER CHECK(removed >= added)
);

CREATE TABLE IF NOT EXISTS "activity_streak_rule" (
	"streak_definition_id"	INTEGER NOT NULL PRIMARY KEY,
	"activity_id"	INTEGER NOT NULL,
	"min_duration"	INTEGER NOT NULL,
	FOREIGN KEY("streak_definition_id") REFERENCES "streak_definition"
		ON DELETE RESTRICT
		ON UPDATE CASCADE,
	FOREIGN KEY("activity_id") REFERENCES "activity"
		ON DELETE RESTRICT
		ON UPDATE CASCADE
);
CREATE TABLE IF NOT EXISTS "category_streak_rule" (
	"streak_definition_id"	INTEGER NOT NULL PRIMARY KEY,
	"category_id"	INTEGER NOT NULL,
	"min_duration"	INTEGER NOT NULL,
	FOREIGN KEY("streak_definition_id") REFERENCES "streak_definition"
		ON DELETE RESTRICT
		ON UPDATE CASCADE,
	FOREIGN KEY("category_id") REFERENCES "category"
		ON DELETE RESTRICT
		ON UPDATE CASCADE
);

CREATE VIEW streak AS
	SELECT
		streak_definition.id,
		streak_definition.interval,
		streak_definition.start,
		streak_definition.stop,
		streak_definition.added,
		streak_definition.removed,
		'activity' AS rule_type,
		activity_streak_rule.activity_id,
		activity_streak_rule.min_duration AS min_duration,
		NULL AS category_id
	FROM activity_streak_rule
	JOIN streak_definition
		ON activity_streak_rule.streak_definition_id
		== streak_definition.id

	UNION SELECT
		streak_definition.id,
		streak_definition.interval,
		streak_definition.start,
		streak_definition.stop,
		streak_definition.added,
		streak_definition.removed,
		'category' AS rule_type,
		NULL AS activity_id,
		category_streak_rule.min_duration AS min_duration,
		category_id
	FROM category_streak_rule
	JOIN streak_definition
		ON category_streak_rule.streak_definition_id
		== streak_definition.id;

CREATE TRIGGER category_belongs_to_path_integrity
	BEFORE DELETE ON category
	BEGIN
		SELECT RAISE(FAIL, "category contains other categories")
		FROM category_belongs_to
		WHERE category_belongs_to.path LIKE '%/' || OLD.id || '/%';
	END;

CREATE TRIGGER no_double_start_on_insert
	BEFORE INSERT ON entry
	WHEN NEW.stop IS NULL
	BEGIN
		SELECT RAISE(FAIL, "activity has been started already")
		FROM entry
		WHERE stop IS NULL AND activity_id == NEW.activity_id;
	END;

CREATE TRIGGER no_double_start_on_update
	BEFORE UPDATE ON entry
	WHEN NEW.stop IS NULL
	BEGIN
		SELECT RAISE(FAIL, "activity has been started already")
		FROM entry
		WHERE stop IS NULL AND activity_id == NEW.activity_id
		AND id != NEW.id;
	END;

--	Overlap: (^new^ |old|)
--		| ^ ^ |	inside
--		^ | ^ |	front
--		| ^ | ^	back
--		^ | | ^	around
--
--	"touching" is allowed:
--		BETWEEN operator is inclusive, so "AND NEW.start != stop" and
--		vice versa were added to allow for it
--		inside overlap does not need a special condition

CREATE TRIGGER overlapping_entry_insert_constraint
	BEFORE INSERT ON entry
	BEGIN
		SELECT RAISE(FAIL, "entry overlaps another entry of this activity")
		FROM entry
		WHERE activity_id == NEW.activity_id
		AND ((start > NEW.start AND NEW.stop > stop)	-- around overlap
			OR (NEW.stop BETWEEN start AND stop			-- front overlap
				AND NEW.stop != start)
			OR (NEW.start BETWEEN start AND stop		-- back overlap
				AND NEW.start != stop)
		);
	END;

CREATE TRIGGER overlapping_entry_update_constraint
	BEFORE UPDATE ON entry
	BEGIN
		SELECT RAISE(FAIL, "entry overlaps another entry of this activity")
		FROM entry
		WHERE activity_id == NEW.activity_id
		AND id != NEW.id
		AND ((start > NEW.start AND NEW.stop > stop)	-- around overlap
			OR (NEW.stop BETWEEN start AND stop			-- front overlap
				AND NEW.stop != start)
			OR (NEW.start BETWEEN start AND stop		-- back overlap
				AND NEW.start != stop)
		);
	END;

CREATE TRIGGER valid_activity_streak_rule_insert
	BEFORE INSERT ON activity_streak_rule
	BEGIN
		SELECT RAISE(FAIL, "streak_definition already has a streak_rule")
		FROM streak
		WHERE NEW.streak_definition_id = streak.id;

		SELECT RAISE(FAIL, "min_duration larger than interval")
		FROM streak_definition
		WHERE NEW.streak_definition_id == streak_definition.id
		AND NEW.min_duration > streak_definition.interval;

		SELECT
			(SELECT start
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_start,
			(SELECT stop
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "activity has an active streak_definition already")
		FROM streak
		WHERE streak.activity_id = NEW.activity_id
		AND (new_stop IS NULL AND stop IS NULL);

		SELECT
			(SELECT start
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_start,
			(SELECT stop
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "overlapping streak_definitions")
		FROM streak
		WHERE streak.activity_id = NEW.activity_id
		AND ((start > new_start AND new_stop > stop)	-- around overlap
			OR (new_stop BETWEEN start AND stop			-- front overlap
				AND new_stop != start)
			OR (new_start BETWEEN start AND stop		-- back overlap
				AND new_start != stop)
		);
	END;


CREATE TRIGGER valid_category_streak_rule_insert
	BEFORE INSERT ON category_streak_rule
	BEGIN
		SELECT RAISE(FAIL, "streak_definition already has a streak_rule")
		FROM streak
		WHERE NEW.streak_definition_id = streak.id;

		SELECT RAISE(FAIL, "min_duration larger than interval")
		FROM streak_definition
		WHERE NEW.streak_definition_id == streak_definition.id
		AND NEW.min_duration > streak_definition.interval;

		SELECT
			(SELECT start
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_start,
			(SELECT stop
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "category has an active streak_definition already")
		FROM streak
		WHERE streak.category_id = NEW.category_id
		AND (new_stop IS NULL AND stop IS NULL);

		SELECT
			(SELECT start
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_start,
			(SELECT stop
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "overlapping streak_definitions")
		FROM streak
		WHERE streak.category_id = NEW.category_id
		AND ((start > new_start AND new_stop > stop)	-- around overlap
			OR (new_stop BETWEEN start AND stop			-- front overlap
				AND new_stop != start)
			OR (new_start BETWEEN start AND stop		-- back overlap
				AND new_start != stop)
		);
	END;

CREATE TRIGGER valid_activity_streak_rule_update
	BEFORE UPDATE ON activity_streak_rule
	BEGIN
		SELECT RAISE(FAIL, "streak_definition already has a streak_rule")
		FROM streak
		WHERE NEW.streak_definition_id = streak.id
		AND NEW.streak_definition_id != OLD.streak_definition_id;

		SELECT RAISE(FAIL, "min_duration larger than interval")
		FROM streak_definition
		WHERE NEW.streak_definition_id = streak_definition.id
		AND NEW.min_duration > streak_definition.interval;

		SELECT
			(SELECT stop
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "activity has an active streak_definition already")
		FROM streak
		WHERE streak.activity_id == NEW.activity_id
		AND (new_stop IS NULL AND stop IS NULL)
		AND NEW.streak_definition_id != streak.id;

		SELECT
			(SELECT start
				FROM streak_definition
				WHERE id == NEW.streak_definition_id
			) AS new_start,
			(SELECT stop
				FROM streak_definition
				WHERE id == NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "overlapping streak_definitions")
		FROM streak
		WHERE streak.activity_id == NEW.activity_id
		AND ((start > new_start AND new_stop > stop)	-- around overlap
			OR (new_stop BETWEEN start AND stop			-- front overlap
				AND new_stop != start)
			OR (new_start BETWEEN start AND stop		-- back overlap
				AND new_start != stop)
		)
		AND NEW.streak_definition_id != streak.id;
	END;

CREATE TRIGGER valid_category_streak_rule_update
	BEFORE UPDATE ON category_streak_rule
	BEGIN
		SELECT RAISE(FAIL, "streak_definition already has a streak_rule")
		FROM streak
		WHERE NEW.streak_definition_id = streak.id
		AND NEW.streak_definition_id != OLD.streak_definition_id;

		SELECT RAISE(FAIL, "min_duration larger than interval")
		FROM streak_definition
		WHERE NEW.streak_definition_id = streak_definition.id
		AND NEW.min_duration > streak_definition.interval;

		SELECT
			(SELECT stop
				FROM streak_definition
				WHERE id = NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "category has an active streak_definition already")
		FROM streak
		WHERE streak.category_id = NEW.category_id
		AND (new_stop IS NULL AND stop IS NULL)
		AND NEW.streak_definition_id != streak.id;

		SELECT
			(SELECT start
				FROM streak_definition
				WHERE id == NEW.streak_definition_id
			) AS new_start,
			(SELECT stop
				FROM streak_definition
				WHERE id == NEW.streak_definition_id
			) AS new_stop,
			RAISE(FAIL, "overlapping streak_definitions")
		FROM streak
		WHERE streak.category_id == NEW.category_id
		AND ((start > new_start AND new_stop > stop)	-- around overlap
			OR (new_stop BETWEEN start AND stop			-- front overlap
				AND new_stop != start)
			OR (new_start BETWEEN start AND stop		-- back overlap
				AND new_start != stop)
		)
		AND NEW.streak_definition_id != streak.id;
	END;

CREATE TRIGGER valid_streak_definition_update
	BEFORE UPDATE ON streak_definition
	BEGIN
		SELECT RAISE(FAIL, "min_duration larger than interval")
		FROM streak
		WHERE NEW.id = streak.id
		AND streak.min_duration > NEW.interval;

		SELECT
			(SELECT activity_id
				FROM streak
				WHERE id = NEW.id
			) AS updated_activity_id,
			RAISE(FAIL, "activity has an active streak_definition already")
		FROM streak
		WHERE streak.activity_id = updated_activity_id
		AND stop IS NULL
		AND NEW.stop IS NULL
		AND NEW.id != streak.id;

		SELECT
			(SELECT category_id
				FROM streak
				WHERE id = NEW.id
			) AS updated_category_id,
			RAISE(FAIL, "category has an active streak_definition already")
		FROM streak
		WHERE streak.category_id = updated_category_id
		AND stop IS NULL
		AND NEW.stop IS NULL
		AND NEW.id != streak.id;

		SELECT
			(SELECT activity_id
				FROM streak
				WHERE id = NEW.id
			) AS updated_activity_id,
			RAISE(FAIL, "overlapping streak_definitions")
		FROM streak
		WHERE streak.activity_id = updated_activity_id
		AND ((start > NEW.start AND NEW.stop > stop)	-- around overlap
			OR (NEW.stop BETWEEN start AND stop			-- front overlap
				AND NEW.stop != start)
			OR (NEW.start BETWEEN start AND stop		-- back overlap
				AND NEW.start != stop)
		)
		AND NEW.id != streak.id;

		SELECT
			(SELECT category_id
				FROM streak
				WHERE id = NEW.id
			) AS updated_category_id,
			RAISE(FAIL, "overlapping streak_definitions")
		FROM streak
		WHERE streak.category_id = updated_category_id
		AND ((start > NEW.start AND NEW.stop > stop)	-- around overlap
			OR (NEW.stop BETWEEN start AND stop			-- front overlap
				AND NEW.stop != start)
			OR (NEW.start BETWEEN start AND stop		-- back overlap
				AND NEW.start != stop)
		)
		AND NEW.id != streak.id;
	END;

CREATE TRIGGER only_new_time_zone_encounters
	BEFORE INSERT ON time_zone_encounter
	BEGIN
		SELECT RAISE(FAIL, "outdated time_zone_encounter")
		FROM time_zone_encounter
		WHERE timestamp >= NEW.timestamp;
	END;


CREATE VIEW entry_list AS
	SELECT
		activity.name AS 'Activity',
		datetime(entry.start, 'unixepoch', 'localtime') AS Start,
		datetime(entry.stop, 'unixepoch', 'localtime') AS Stop
	FROM entry
	JOIN activity ON entry.activity_id == activity.id;

CREATE TRIGGER unique_activities_in_category_insert
	BEFORE INSERT ON activity_belongs_to
	BEGIN
		SELECT
			(SELECT name FROM activity WHERE id = NEW.activity_id) AS new_name,
			RAISE(FAIL, "activity with this name already exists in category")
		FROM activity_belongs_to
		JOIN activity ON activity.id == activity_id
		WHERE name == new_name AND category_id == NEW.category_id;
	END;

CREATE TRIGGER unique_activities_in_category_update
	BEFORE UPDATE ON activity
	BEGIN
		SELECT
			(
				SELECT category_id
				FROM activity_belongs_to WHERE NEW.id = activity_id
			) AS new_category_id,
			RAISE(FAIL, "activity with this name already exists in category")
		FROM activity_belongs_to
		JOIN activity ON activity.id == activity_id
		WHERE name == NEW.name
			AND category_id == new_category_id
			AND id != NEW.id;
	END;

CREATE TRIGGER unique_categories_in_category_insert
	BEFORE INSERT ON category_belongs_to
	BEGIN
		SELECT
			(
				SELECT name
				FROM category WHERE NEW.category_id = category.id
			) AS new_category_name,
			RAISE(FAIL, "category with this name already exists in category")
		FROM category_belongs_to
		JOIN category ON category.id == category_id
		WHERE category.name == new_category_name
			AND path == NEW.path
			AND category.id != NEW.category_id;
	END;


COMMIT;