-- ################################################################
--  Copyright notice
--
--  (c) Ecole des Sciences Criminelles
--	   University of Lausanne - Switzerland
--
--  All rights reserved
--
--  This copyright notice MUST APPEAR in all copies of the script!
-- ################################################################

--SET client_min_messages TO 'WARNING';

CREATE OR REPLACE FUNCTION nullifempty(text) RETURNS text AS $$
	BEGIN
		IF $1 IS NULL OR $1 = '' THEN
			RETURN NULL;
		ELSE
			RETURN $1;
		END IF;
	END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION falseIfNull(boolean) RETURNS boolean AS $$
	BEGIN
		IF $1 IS NULL THEN
			RETURN FALSE;
		ELSE
			RETURN $1;
		END IF;
	END;
$$ LANGUAGE plpgsql;
	
CREATE OR REPLACE FUNCTION aggregate_first_of_t(text,text) RETURNS text AS $$
	BEGIN
		IF $1 IS NULL OR $1 = '' THEN
			RETURN $2;
		ELSE
			RETURN $1;
		END IF;
	END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION aggregate_first_of_i(int,int) RETURNS int AS $$
	BEGIN
		IF $1 IS NULL THEN
			RETURN $2;
		ELSE
			RETURN $1;
		END IF;
	END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION aggregate_first_of_b(boolean,boolean) RETURNS boolean AS $$
	BEGIN
		IF $1 IS NULL THEN
			RETURN $2;
		ELSE
			RETURN $1;
		END IF;
	END;
$$ LANGUAGE plpgsql;

-- Function form of || operator
CREATE OR REPLACE FUNCTION aggregate_concatenate(text,text) RETURNS text AS $$
  SELECT COALESCE($1 || ', ' || $2,$1,$2,NULL)
$$ LANGUAGE SQL;



CREATE OR REPLACE FUNCTION array_append_if_non_null(anyarray,anyelement) RETURNS anyarray AS $$
	BEGIN
		IF $2 IS NOT NULL THEN	
			RETURN array_append($1,$2);
		ELSE
			RETURN $1;
		END IF;
	END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION safe_current_setting(param text) RETURNS text AS $$
	DECLARE
		value text;
	BEGIN
		SELECT INTO value current_setting(param);
		RETURN value;
	EXCEPTION
		WHEN UNDEFINED_OBJECT THEN
			RETURN NULL;
END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION microDB_template_safe_recreate_aggregates() RETURNS void AS $$
	BEGIN
		BEGIN
			IF (SELECT count(*) FROM pg_aggregate WHERE aggtransfn = 'aggregate_first_of_t'::regproc) THEN
				DROP AGGREGATE first_of(text);
			END IF;
			CREATE AGGREGATE first_of(basetype=text, sfunc=aggregate_first_of_t, stype=text, initcond='' );
		EXCEPTION
			WHEN OTHERS THEN RAISE NOTICE 'aggregate first_of(text) will not be recreated (%)',SQLERRM;
		END;
		BEGIN
			IF (SELECT count(*) FROM pg_aggregate WHERE aggtransfn = 'aggregate_first_of_i'::regproc) THEN
				DROP AGGREGATE first_of(int);
			END IF;
			CREATE AGGREGATE first_of(basetype=int, sfunc=aggregate_first_of_i, stype=int);
		EXCEPTION
			WHEN OTHERS THEN RAISE NOTICE 'aggregate first_of(int) will not be recreated (%)',SQLERRM;
		END;
		BEGIN
			IF (SELECT count(*) FROM pg_aggregate WHERE aggtransfn = 'aggregate_first_of_b'::regproc) THEN
				DROP AGGREGATE first_of(boolean);
			END IF;
			CREATE AGGREGATE first_of(basetype=boolean, sfunc=aggregate_first_of_b, stype=boolean);
		EXCEPTION
			WHEN OTHERS THEN RAISE NOTICE 'aggregate first_of(boolean) will not be recreated (%)',SQLERRM;
		END;
		BEGIN
			IF (SELECT count(*) FROM pg_aggregate WHERE aggtransfn = 'aggregate_concatenate'::regproc) THEN
				DROP AGGREGATE concatenate(text);
			END IF;
			-- How to concatenate strings in an aggregate
			CREATE AGGREGATE concatenate (
			  sfunc = aggregate_concatenate,
			  basetype = text,
			  stype = text,
			  initcond = ''
			);
		EXCEPTION
			WHEN OTHERS THEN RAISE NOTICE 'aggregate concatenate(text) will not be recreated (%)',SQLERRM;
		END;
		BEGIN
			IF (SELECT count(*) FROM pg_aggregate WHERE aggtransfn = 'array_append'::regproc) THEN
				DROP AGGREGATE to_array(anyelement);
			END IF;
			CREATE AGGREGATE to_array (
			  sfunc = array_append,
			  basetype = anyelement,
			  stype = anyarray,
			  initcond = '{}'
			);
		EXCEPTION
			WHEN OTHERS THEN RAISE NOTICE 'aggregate to_array(anyelement) will not be recreated (%)',SQLERRM;
		END;
		BEGIN
			IF (SELECT count(*) FROM pg_aggregate WHERE aggtransfn = 'array_append_if_non_null'::regproc) THEN
				DROP AGGREGATE to_array_if_non_null(anyelement);
			END IF;
			CREATE AGGREGATE to_array_if_non_null(
			  sfunc = array_append_if_non_null,
			  basetype = anyelement,
			  stype = anyarray,
			  initcond = '{}'
			);
		EXCEPTION
			WHEN OTHERS THEN RAISE NOTICE 'aggregate to_array_if_non_null(anyelement) will not be recreated (%)',SQLERRM;
		END;
	END;
$$ LANGUAGE plpgsql;

SELECT microDB_template_safe_recreate_aggregates();
DROP FUNCTION microDB_template_safe_recreate_aggregates();




CREATE TABLE annotationtype (
	id serial UNIQUE NOT NULL,
	name text UNIQUE NOT NULL,
	known boolean NOT NULL DEFAULT false,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO annotationtype(name,known) VALUES (E'minutia',true);
INSERT INTO annotationtype(name,known) VALUES (E'ridge',true);
INSERT INTO annotationtype(name,known) VALUES (E'area',true);
INSERT INTO annotationtype(name,known) VALUES (E'comment',true);
CREATE TABLE messagevisibility (
	id serial UNIQUE NOT NULL,
	name text UNIQUE NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO messagevisibility(name) VALUES (E'hidden');
INSERT INTO messagevisibility(name) VALUES (E'tutors');
INSERT INTO messagevisibility(name) VALUES (E'users');
INSERT INTO messagevisibility(name) VALUES (E'all');
CREATE TABLE resulttype (
	id serial UNIQUE NOT NULL,
	short text UNIQUE NOT NULL,
	name text NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO resulttype(short,name) VALUES (E'CURRENT',E'Current');
INSERT INTO resulttype(short,name) VALUES (E'AUTOSAVE',E'Autosave');
INSERT INTO resulttype(short,name) VALUES (E'VALIDATED',E'Submission');
CREATE TABLE minutiatype (
	id serial UNIQUE NOT NULL,
	name text UNIQUE NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO minutiatype(name) VALUES (E'bifurcation');
INSERT INTO minutiatype(name) VALUES (E'ridge_ending');
INSERT INTO minutiatype(name) VALUES (E'unknown');
INSERT INTO minutiatype(name) VALUES (E'unknown2');
INSERT INTO minutiatype(name) VALUES (E'difference');
INSERT INTO minutiatype(name) VALUES (E'center');
CREATE TABLE imagetype (
	id serial UNIQUE NOT NULL,
	short text UNIQUE NOT NULL,
	name text NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO imagetype(short,name) VALUES (E'MARK',E'Mark');
INSERT INTO imagetype(short,name) VALUES (E'PRINT',E'Print');
INSERT INTO imagetype(short,name) VALUES (E'THUMBNAIL',E'Thumbnail');
CREATE TABLE resultstep (
	id serial UNIQUE NOT NULL,
	short text UNIQUE NOT NULL,
	name text NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO resultstep(short,name) VALUES (E'ANALYSIS',E'Analysis');
INSERT INTO resultstep(short,name) VALUES (E'COMPARISON',E'Comparison');
INSERT INTO resultstep(short,name) VALUES (E'EVALUATION',E'Evaluation');
CREATE TABLE messagetype (
	id serial UNIQUE NOT NULL,
	name text UNIQUE NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO messagetype(name) VALUES (E'home');
CREATE TABLE observationtype (
	id serial UNIQUE NOT NULL,
	name text UNIQUE,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO observationtype(name) VALUES (E'conclusion');
INSERT INTO observationtype(name) VALUES (E'mark-needed-ridges');
INSERT INTO observationtype(name) VALUES (E'mark-value');
INSERT INTO observationtype(name) VALUES (E'mark-degradation');
INSERT INTO observationtype(name) VALUES (E'mark-details');
INSERT INTO observationtype(name) VALUES (E'mark-suitability-approach');
INSERT INTO observationtype(name) VALUES (E'mark-gp');
INSERT INTO observationtype(name) VALUES (E'comparison-decision');
INSERT INTO observationtype(name) VALUES (E'comparison-decision-ex');
INSERT INTO observationtype(name) VALUES (E'comparison-need-better-known');
INSERT INTO observationtype(name) VALUES (E'comparison-l3details');
INSERT INTO observationtype(name) VALUES (E'comparison-qualquant');
INSERT INTO observationtype(name) VALUES (E'mark-pfsp');
CREATE TABLE object (
	id serial UNIQUE NOT NULL,
	createdon timestamptz NOT NULL DEFAULT now(),
	updatedon timestamptz NOT NULL DEFAULT now(),
	creator int,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX object_creator_Index ON object(creator);
CREATE OR REPLACE FUNCTION object_on_store_trigger_before_insert_or_update_hook() RETURNS trigger AS $$
BEGIN
	IF NEW.creator IS NULL THEN SELECT INTO NEW.creator safe_current_setting('unil.user'); END IF;
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER object_on_store_trigger_before_insert_or_update
	before insert or update ON object
	FOR EACH ROW
	EXECUTE PROCEDURE object_on_store_trigger_before_insert_or_update_hook();

CREATE OR REPLACE FUNCTION Generic_object_Inherit_Trigger_Hook() RETURNS trigger AS $$
	DECLARE
		parentid int;
		entityid int;
	BEGIN
		IF NEW.ID IS NULL THEN
			INSERT INTO object DEFAULT VALUES RETURNING id INTO parentid;
			NEW.ID := parentid;
		ELSE
			parentid := NEW.ID;
		END IF;
		
		RETURN NEW;
	END;
$$ LANGUAGE plpgsql;
		CREATE TABLE image (
	FOREIGN KEY (id) REFERENCES object(id),
	id int UNIQUE NOT NULL,
	FOREIGN KEY (type) REFERENCES imagetype(id),
	type int NOT NULL,
	name text NOT NULL,
	mimetype text NOT NULL,
	info text,
	resolution int NOT NULL,
	width int NOT NULL,
	height int NOT NULL,
	data bytea NOT NULL,
	FOREIGN KEY (thumb) REFERENCES image(id),
	thumb int,
	checksum text,
	fts tsvector NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX image_fts_Index ON image USING gin(fts);
CREATE TRIGGER image_object_Inherit_Trigger
 BEFORE INSERT ON image
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_object_Inherit_Trigger_Hook();

					CREATE OR REPLACE FUNCTION image_tsvector_update_func() RETURNS trigger AS $$
					BEGIN
					  NEW.fts := setweight(to_tsvector(coalesce(NEW.name,'')), 'B') || setweight(to_tsvector(coalesce(NEW.info,'')), 'B');
					  RETURN NEW;
					END
					$$ LANGUAGE plpgsql;					
					
					CREATE TRIGGER image_tsvector_update_trigger
						BEFORE INSERT OR UPDATE ON image 
						FOR EACH ROW EXECUTE PROCEDURE image_tsvector_update_func();CREATE TABLE message (
	FOREIGN KEY (id) REFERENCES object(id),
	id int UNIQUE NOT NULL,
	FOREIGN KEY (type) REFERENCES messagetype(id),
	type int NOT NULL,
	FOREIGN KEY (visibility) REFERENCES messagevisibility(id),
	visibility int NOT NULL,
	FOREIGN KEY (parent) REFERENCES message(id),
	parent int,
	title text NOT NULL,
	value text NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE TRIGGER message_object_Inherit_Trigger
 BEFORE INSERT ON message
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_object_Inherit_Trigger_Hook();
CREATE TABLE trustee (
	FOREIGN KEY (id) REFERENCES object(id),
	id int UNIQUE NOT NULL,
	isgroup boolean NOT NULL DEFAULT false,
	PRIMARY KEY (id)
) WITHOUT OIDs;

CREATE OR REPLACE FUNCTION Generic_trustee_Inherit_Trigger_Hook() RETURNS trigger AS $$
	DECLARE
		parentid int;
		entityid int;
	BEGIN
		IF NEW.ID IS NULL THEN
			INSERT INTO trustee DEFAULT VALUES RETURNING id INTO parentid;
			NEW.ID := parentid;
		ELSE
			parentid := NEW.ID;
		END IF;
		
		RETURN NEW;
	END;
$$ LANGUAGE plpgsql;
		CREATE TRIGGER trustee_object_Inherit_Trigger
 BEFORE INSERT ON trustee
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_object_Inherit_Trigger_Hook();
CREATE VIEW imagev AS (SELECT image.id,imagetype.id AS typeid,imagetype.short AS type,image.name,mimetype,info,resolution,width,height,thumb FROM image,imagetype WHERE image.type = imagetype.id);CREATE TABLE pgroup (
	FOREIGN KEY (id) REFERENCES trustee(id),
	id int UNIQUE NOT NULL,
	name text NOT NULL,
	tutors boolean NOT NULL DEFAULT false,
	fts tsvector NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX pgroup_fts_Index ON pgroup USING gin(fts);
CREATE OR REPLACE FUNCTION pgroup_on_store_trigger_after_insert_hook() RETURNS trigger AS $$
BEGIN
	UPDATE trustee SET isgroup = true WHERE id = NEW.id;
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER pgroup_on_store_trigger_after_insert
	after insert ON pgroup
	FOR EACH ROW
	EXECUTE PROCEDURE pgroup_on_store_trigger_after_insert_hook();
CREATE TRIGGER pgroup_trustee_Inherit_Trigger
 BEFORE INSERT ON pgroup
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_trustee_Inherit_Trigger_Hook();

					CREATE OR REPLACE FUNCTION pgroup_tsvector_update_func() RETURNS trigger AS $$
					BEGIN
					  NEW.fts := setweight(to_tsvector(coalesce(NEW.name,'')), 'B');
					  RETURN NEW;
					END
					$$ LANGUAGE plpgsql;					
					
					CREATE TRIGGER pgroup_tsvector_update_trigger
						BEFORE INSERT OR UPDATE ON pgroup 
						FOR EACH ROW EXECUTE PROCEDURE pgroup_tsvector_update_func();INSERT INTO pgroup(name,tutors) VALUES (E'Administrators',true);
INSERT INTO pgroup(name,tutors) VALUES (E'Default users',false);
CREATE TABLE language (
	id serial UNIQUE NOT NULL,
	short text UNIQUE NOT NULL,
	name text NOT NULL,
	country text NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
INSERT INTO language(short,name,country) VALUES (E'en_US',E'English',E'United States');
CREATE TABLE person (
	FOREIGN KEY (id) REFERENCES trustee(id),
	id int UNIQUE NOT NULL,
	username text UNIQUE NOT NULL,
	name text NOT NULL,
	password text NOT NULL,
	active boolean NOT NULL DEFAULT true,
	email text,
	FOREIGN KEY (language) REFERENCES language(id),
	language int NOT NULL,
	last_login timestamptz,
	suitability_approach int NOT NULL,
	must_change_pwd boolean NOT NULL DEFAULT false,
	fts tsvector NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX person_fts_Index ON person USING gin(fts);
CREATE TRIGGER person_trustee_Inherit_Trigger
 BEFORE INSERT ON person
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_trustee_Inherit_Trigger_Hook();

					CREATE OR REPLACE FUNCTION person_tsvector_update_func() RETURNS trigger AS $$
					BEGIN
					  NEW.fts := setweight(to_tsvector(coalesce(NEW.name,'')), 'A') || setweight(to_tsvector(coalesce(NEW.username,'')), 'B') || setweight(to_tsvector(coalesce(NEW.email,'')), 'B');
					  RETURN NEW;
					END
					$$ LANGUAGE plpgsql;					
					
					CREATE TRIGGER person_tsvector_update_trigger
						BEFORE INSERT OR UPDATE ON person 
						FOR EACH ROW EXECUTE PROCEDURE person_tsvector_update_func();INSERT INTO person(username,name,password,active,email,language,last_login,suitability_approach,must_change_pwd) VALUES (E'admin',E'Administrator',E'd2abaa37a7c3db1137d385e1d8c15fd2',true,E'',(SELECT id FROM language WHERE short = 'en_US'),NULL,2,false);
INSERT INTO person(username,name,password,active,email,language,last_login,suitability_approach,must_change_pwd) VALUES (E'jdoe',E'John Doe',E'268a19d89a1e2e213dc2bf767c4bf089',true,E'',(SELECT id FROM language WHERE short = 'en_US'),NULL,2,false);
CREATE TABLE folder (
	FOREIGN KEY (id) REFERENCES object(id),
	id int UNIQUE NOT NULL,
	name text NOT NULL,
	FOREIGN KEY (personalof) REFERENCES person(id),
	personalof int,
	childof int,
	fts tsvector NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX folder_fts_Index ON folder USING gin(fts);
CREATE TRIGGER folder_object_Inherit_Trigger
 BEFORE INSERT ON folder
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_object_Inherit_Trigger_Hook();

					CREATE OR REPLACE FUNCTION folder_tsvector_update_func() RETURNS trigger AS $$
					BEGIN
					  NEW.fts := setweight(to_tsvector(coalesce(NEW.name,'')), 'B');
					  RETURN NEW;
					END
					$$ LANGUAGE plpgsql;					
					
					CREATE TRIGGER folder_tsvector_update_trigger
						BEFORE INSERT OR UPDATE ON folder 
						FOR EACH ROW EXECUTE PROCEDURE folder_tsvector_update_func();CREATE TABLE exercise (
	FOREIGN KEY (id) REFERENCES object(id),
	id int UNIQUE NOT NULL,
	name text NOT NULL,
	description text,
	FOREIGN KEY (folder) REFERENCES folder(id),
	folder int NOT NULL,
	FOREIGN KEY (mark) REFERENCES image(id),
	mark int NOT NULL,
	FOREIGN KEY (print) REFERENCES image(id),
	print int NOT NULL,
	hidden_information text,
	fts tsvector NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX exercise_fts_Index ON exercise USING gin(fts);
CREATE TRIGGER exercise_object_Inherit_Trigger
 BEFORE INSERT ON exercise
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_object_Inherit_Trigger_Hook();

					CREATE OR REPLACE FUNCTION exercise_tsvector_update_func() RETURNS trigger AS $$
					BEGIN
					  NEW.fts := setweight(to_tsvector(coalesce(NEW.name,'')), 'B') || setweight(to_tsvector(coalesce(NEW.description,'')), 'C');
					  RETURN NEW;
					END
					$$ LANGUAGE plpgsql;					
					
					CREATE TRIGGER exercise_tsvector_update_trigger
						BEFORE INSERT OR UPDATE ON exercise 
						FOR EACH ROW EXECUTE PROCEDURE exercise_tsvector_update_func();CREATE TABLE assignment (
	FOREIGN KEY (id) REFERENCES object(id),
	id int UNIQUE NOT NULL,
	FOREIGN KEY (exercise) REFERENCES exercise(id),
	exercise int NOT NULL,
	ispreview boolean NOT NULL DEFAULT false,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX assignment_exercise_Index ON assignment(exercise);
CREATE TRIGGER assignment_object_Inherit_Trigger
 BEFORE INSERT ON assignment
 FOR EACH ROW
 EXECUTE PROCEDURE Generic_object_Inherit_Trigger_Hook();
CREATE TABLE membership (
	FOREIGN KEY (person) REFERENCES person(id),
	person int NOT NULL,
	FOREIGN KEY (pgroup) REFERENCES pgroup(id),
	pgroup int NOT NULL,
	PRIMARY KEY (person,pgroup)
) WITHOUT OIDs;
CREATE INDEX membership_person_Index ON membership(person);
CREATE INDEX membership_pgroup_Index ON membership(pgroup);
INSERT INTO membership(person,pgroup) VALUES ((SELECT id FROM person WHERE username = 'admin'),(SELECT id FROM pgroup WHERE name = 'Administrators'));
INSERT INTO membership(person,pgroup) VALUES ((SELECT id FROM person WHERE username = 'jdoe'),(SELECT id FROM pgroup WHERE name = 'Default users'));
CREATE VIEW ftsv AS (SELECT 'person' AS type, person.id, person.name, CASE person.active WHEN true THEN 'Active' WHEN false THEN 'Inactive' END || ', In group(s): '||array_to_string(array_agg(pgroup.name), ',') AS desc, person.fts 
FROM person,membership,pgroup 
WHERE person.id = membership.person 
	AND membership.pgroup = pgroup.id 
GROUP BY person.id, person.name, person.fts, person.active

UNION 

SELECT 'pgroup' AS type, pgroup.id, pgroup.name, '' AS desc, pgroup.fts
FROM pgroup 

UNION

SELECT 'image' AS type, image.id, image.name, image.info AS desc, image.fts
FROM image

UNION

SELECT 'folder' AS type, folder.id, folder.name, count(exercise.id)::text || ' exercise(s)' AS desc, folder.fts
FROM folder LEFT OUTER JOIN exercise ON exercise.folder = folder.id
GROUP BY folder.id, folder.name, folder.fts

UNION

SELECT 'exercise' AS type, exercise.id, exercise.name, exercise.description AS desc, exercise.fts
FROM exercise

);CREATE TABLE resulthistory (
	id serial UNIQUE NOT NULL,
	FOREIGN KEY (assignment) REFERENCES assignment(id),
	assignment int NOT NULL,
	FOREIGN KEY (type) REFERENCES resulttype(id),
	type int NOT NULL,
	FOREIGN KEY (step) REFERENCES resultstep(id),
	step int NOT NULL,
	createdon timestamptz NOT NULL DEFAULT now(),
	data text NOT NULL,
	PRIMARY KEY (id)
) WITHOUT OIDs;
CREATE INDEX resulthistory_assignment_Index ON resulthistory(assignment);
CREATE OR REPLACE FUNCTION resulthistory_on_store_trigger_before_update_hook() RETURNS trigger AS $$
BEGIN
	RAISE EXCEPTION 'Updating the result history is not allowed';
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER resulthistory_on_store_trigger_before_update
	before update ON resulthistory
	FOR EACH ROW
	EXECUTE PROCEDURE resulthistory_on_store_trigger_before_update_hook();
CREATE VIEW messagev AS (SELECT message.id,messagetype.id AS typeid,messagetype.name AS type,message.visibility AS visibilityid,messagevisibility.name AS visibility,message.title,message.value,message.parent,object.createdon,object.creator AS creatorid,person.name AS creator FROM message,messagevisibility,messagetype,object,person WHERE person.id=object.creator AND object.id=message.id AND message.visibility=messagevisibility.id AND message.type=messagetype.id);CREATE TABLE security (
	FOREIGN KEY (object) REFERENCES object(id),
	object int NOT NULL,
	FOREIGN KEY (trustee) REFERENCES trustee(id),
	trustee int NOT NULL,
	PRIMARY KEY (object,trustee)
) WITHOUT OIDs;
CREATE INDEX security_object_Index ON security(object);
CREATE INDEX security_trustee_Index ON security(trustee);
CREATE TABLE result (
	FOREIGN KEY (assignment) REFERENCES assignment(id),
	assignment int NOT NULL,
	FOREIGN KEY (type) REFERENCES resulttype(id),
	type int NOT NULL,
	FOREIGN KEY (step) REFERENCES resultstep(id),
	step int NOT NULL,
	updatedon timestamptz NOT NULL DEFAULT now(),
	data text NOT NULL,
	PRIMARY KEY (assignment,type,step)
) WITHOUT OIDs;
CREATE INDEX result_assignment_Index ON result(assignment);
CREATE OR REPLACE FUNCTION result_on_store_trigger_after_insert_or_update_hook() RETURNS trigger AS $$
BEGIN
	PERFORM checkAssignmentConstraints(NEW.assignment);
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER result_on_store_trigger_after_insert_or_update
	after insert or update ON result
	FOR EACH ROW
	EXECUTE PROCEDURE result_on_store_trigger_after_insert_or_update_hook();
CREATE OR REPLACE FUNCTION result_on_store_trigger_before_update_hook() RETURNS trigger AS $$
BEGIN
	IF OLD.type = (SELECT id FROM resulttype WHERE short = 'VALIDATED') THEN RAISE EXCEPTION 'Cannot update VALIDATED result'; END IF;
	RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER result_on_store_trigger_before_update
	before update ON result
	FOR EACH ROW
	EXECUTE PROCEDURE result_on_store_trigger_before_update_hook();
CREATE VIEW resultv AS (SELECT result.assignment, person.name AS "user", result.type AS typeid, resulttype.name AS type, result.step AS stepid, resultstep.name AS step, result.updatedon FROM result,resulttype,resultstep,object,person WHERE result.type = resulttype.id AND result.step = resultstep.id AND result.assignment = object.id AND object.creator = person.id);CREATE TABLE annotation (
	id int NOT NULL,
	assignment int NOT NULL,
	restype int NOT NULL,
	resstep int NOT NULL,
	FOREIGN KEY (type) REFERENCES annotationtype(id),
	type int NOT NULL,
	FOREIGN KEY (image) REFERENCES imagetype(id),
	image int NOT NULL,
	createdon timestamptz NOT NULL,
	updatedon timestamptz NOT NULL,
	pairing int,
	subtype text,
	value int,
	groups int NOT NULL,
	PRIMARY KEY (id,assignment,restype,resstep),
	FOREIGN KEY (assignment,restype,resstep) REFERENCES result(assignment,type,step)
) WITHOUT OIDs;
CREATE TABLE minutia (
	id int NOT NULL,
	assignment int NOT NULL,
	restype int NOT NULL,
	resstep int NOT NULL,
	x double precision NOT NULL,
	y double precision NOT NULL,
	theta double precision NOT NULL,
	FOREIGN KEY (type) REFERENCES minutiatype(id),
	type int NOT NULL,
	PRIMARY KEY (id,assignment,restype,resstep),
	FOREIGN KEY (id,assignment,restype,resstep) REFERENCES annotation(id,assignment,restype,resstep)
) WITHOUT OIDs;
CREATE TABLE observation (
	assignment int NOT NULL,
	restype int NOT NULL,
	resstep int NOT NULL,
	FOREIGN KEY (type) REFERENCES observationtype(id),
	type int NOT NULL,
	value text NOT NULL,
	PRIMARY KEY (type,assignment,restype,resstep),
	FOREIGN KEY (assignment,restype,resstep) REFERENCES result(assignment,type,step)
) WITHOUT OIDs;
CREATE VIEW resultvex2 AS (SELECT result.assignment, 
	result.type AS typeid, 
	resulttype.name AS type, 
	result.step AS stepid, 
	resultstep.name AS step, 
	result.updatedon,
	obs_concl.value AS conclusion,
	obs_mark_value.value AS mark_value,
	obs_mark_degradation.value AS mark_degradation,
	obs_mark_details.value AS mark_details,
	obs_decision.value AS decision,
	obs_decision_ex.value AS decision_ex,
	obs_comparison_need_better_known.value AS comparison_need_better_known,
	obs_comparison_l3details.value AS comparison_l3details,
	obs_comparison_qualquant.value AS comparison_qualquant
FROM resulttype,resultstep,result 
	LEFT OUTER JOIN observation AS obs_concl ON obs_concl.assignment = result.assignment 
		AND obs_concl.restype = result.type 
		AND obs_concl.resstep = result.step 
		AND obs_concl.type = (SELECT id FROM observationtype WHERE name = 'conclusion') 
	LEFT OUTER JOIN observation AS obs_mark_value ON obs_mark_value.assignment = result.assignment 
		AND obs_mark_value.restype = result.type 
		AND obs_mark_value.resstep = result.step 
		AND obs_mark_value.type = (SELECT id FROM observationtype WHERE name = 'mark-value') 
	LEFT OUTER JOIN observation AS obs_mark_degradation ON obs_mark_degradation.assignment = result.assignment 
		AND obs_mark_degradation.restype = result.type 
		AND obs_mark_degradation.resstep = result.step 
		AND obs_mark_degradation.type = (SELECT id FROM observationtype WHERE name = 'mark-degradation') 
	LEFT OUTER JOIN observation AS obs_mark_details ON obs_mark_details.assignment = result.assignment 
		AND obs_mark_details.restype = result.type 
		AND obs_mark_details.resstep = result.step 
		AND obs_mark_details.type = (SELECT id FROM observationtype WHERE name = 'mark-details') 
	LEFT OUTER JOIN observation AS obs_decision ON obs_decision.assignment = result.assignment 
		AND obs_decision.restype = result.type 
		AND obs_decision.resstep = result.step 
		AND obs_decision.type = (SELECT id FROM observationtype WHERE name = 'comparison-decision') 
	LEFT OUTER JOIN observation AS obs_decision_ex ON obs_decision_ex.assignment = result.assignment 
		AND obs_decision_ex.restype = result.type 
		AND obs_decision_ex.resstep = result.step 
		AND obs_decision_ex.type = (SELECT id FROM observationtype WHERE name = 'comparison-decision-ex') 
	LEFT OUTER JOIN observation AS obs_comparison_need_better_known ON obs_comparison_need_better_known.assignment = result.assignment 
		AND obs_comparison_need_better_known.restype = result.type 
		AND obs_comparison_need_better_known.resstep = result.step 
		AND obs_comparison_need_better_known.type = (SELECT id FROM observationtype WHERE name = 'comparison-need-better-known') 
	LEFT OUTER JOIN observation AS obs_comparison_l3details ON obs_comparison_l3details.assignment = result.assignment 
		AND obs_comparison_l3details.restype = result.type 
		AND obs_comparison_l3details.resstep = result.step 
		AND obs_comparison_l3details.type = (SELECT id FROM observationtype WHERE name = 'comparison-l3details') 
	LEFT OUTER JOIN observation AS obs_comparison_qualquant ON obs_comparison_qualquant.assignment = result.assignment 
		AND obs_comparison_qualquant.restype = result.type 
		AND obs_comparison_qualquant.resstep = result.step 
		AND obs_comparison_qualquant.type = (SELECT id FROM observationtype WHERE name = 'comparison-qualquant') 
WHERE result.type = resulttype.id 
	AND result.step = resultstep.id
);CREATE VIEW observationv AS (SELECT observation.*,observationtype.name FROM observation,observationtype WHERE observation.type = observationtype.id);CREATE VIEW resultvex AS (SELECT result.assignment, 
	result.type AS typeid, 
	resulttype.name AS type, 
	result.step AS stepid, 
	resultstep.name AS step, 
	result.updatedon,
	obs_concl.value AS conclusion,
	obs_mark.value AS mark_value,
	obs_decision.value AS decision,
	obs_decision_ex.value AS decision_ex
FROM resulttype,resultstep,result 
	LEFT OUTER JOIN observation AS obs_concl ON obs_concl.assignment = result.assignment 
		AND obs_concl.restype = result.type 
		AND obs_concl.resstep = result.step 
		AND obs_concl.type = (SELECT id FROM observationtype WHERE name = 'conclusion') 
	LEFT OUTER JOIN observation AS obs_mark ON obs_mark.assignment = result.assignment 
		AND obs_mark.restype = result.type 
		AND obs_mark.resstep = result.step 
		AND obs_mark.type = (SELECT id FROM observationtype WHERE name = 'mark-value') 
	LEFT OUTER JOIN observation AS obs_decision ON obs_decision.assignment = result.assignment 
		AND obs_decision.restype = result.type 
		AND obs_decision.resstep = result.step 
		AND obs_decision.type = (SELECT id FROM observationtype WHERE name = 'comparison-decision') 
	LEFT OUTER JOIN observation AS obs_decision_ex ON obs_decision_ex.assignment = result.assignment 
		AND obs_decision_ex.restype = result.type 
		AND obs_decision_ex.resstep = result.step 
		AND obs_decision_ex.type = (SELECT id FROM observationtype WHERE name = 'comparison-decision-ex') 
WHERE result.type = resulttype.id 
	AND result.step = resultstep.id
);ALTER TABLE object ADD CONSTRAINT fk_object_creator FOREIGN KEY (creator) REFERENCES person(id);UPDATE object SET creator = (SELECT id FROM person ORDER BY id ASC LIMIT 1) WHERE creator IS NULL;ALTER TABLE object ALTER COLUMN creator SET NOT NULL;


CREATE OR REPLACE FUNCTION checkACL(int,int) RETURNS boolean AS $$
	DECLARE
		pObjectID ALIAS FOR $1;
		pTrusteeID ALIAS FOR $2;
		groupid int;
	BEGIN
		-- Check for explicit access of the trustee to the object
		IF (SELECT count(*) FROM Security WHERE Object = pObjectID AND Trustee = pTrusteeID) > 0 THEN
			RETURN true;
		END IF;
		
		-- If the trustee is a user
		IF (SELECT IsGroup FROM Trustee WHERE ID = pTrusteeID) IS false THEN
			
			-- Iterate over all its person groups and recursively call checkACL
			FOR groupid IN (SELECT pgroup FROM membership WHERE Person = pTrusteeID) LOOP
				IF (SELECT checkACL(pObjectID, groupid)) IS true THEN
					RETURN true;
				END IF;
			END LOOP;
			
			-- Tutors always have access to all the data
			IF (SELECT bool_or(pgroup.tutors) FROM membership,pgroup WHERE membership.Person = pTrusteeID AND membership.pgroup = pgroup.ID) IS true THEN
				RETURN true;
			END IF;
		
		END IF;
		
		RETURN false;
	END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION checkACL(int) RETURNS boolean AS $$
	DECLARE
		userid int;
		res boolean;
	BEGIN
		SELECT INTO userid current_setting('unil.user')::integer;
		SELECT INTO res checkACL($1,userid);
		RETURN res;
	END;
$$ LANGUAGE plpgsql;

CREATE OR REPLACE FUNCTION applyACL(int, int[], int[]) RETURNS void AS $$
	DECLARE
		pObjectID ALIAS FOR $1;
		pGroupACL ALIAS FOR $2;
		pUserACL ALIAS FOR $3;
	BEGIN
		
		IF pGroupACL IS NOT NULL THEN
			
			DELETE FROM Security WHERE Object = pObjectID AND Trustee IN (SELECT ID FROM pgroup);
		
			IF array_lower(pGroupACL,1) IS NOT NULL THEN
				FOR i IN array_lower(pGroupACL,1)..array_upper(pGroupACL,1) LOOP
					IF (SELECT Tutor FROM pgroup WHERE ID = pGroupACL[i]) IS false THEN
						INSERT INTO Security(Object,Trustee) VALUES
							(pObjectID, pGroupACL[i]);
					END IF;
				END LOOP;
			END IF;
		END IF;

		IF pUserACL IS NOT NULL THEN

			DELETE FROM Security WHERE Object = pObjectID AND Trustee IN (SELECT ID FROM Person);

			IF array_lower(pUserACL,1) IS NOT NULL THEN
				FOR i IN array_lower(pUserACL,1)..array_upper(pUserACL,1) LOOP
					INSERT INTO Security(Object,Trustee) VALUES
						(pObjectID, pUserACL[i]);
				END LOOP;
			END IF;
		END IF;

	END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION assertACL(pObjectID int) RETURNS void AS $$
	BEGIN
		IF (NOT checkACL(pObjectID)) THEN
			RAISE EXCEPTION 'Access denied (ID=%)', pObjectID;
		END IF;
	END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION assertACL(pObjectID int,pTrusteeID int) RETURNS void AS $$
	BEGIN
		IF (NOT checkACL(pObjectID,pTrusteeID)) THEN
			RAISE EXCEPTION 'Access denied (ID=%)', pObjectID;
		END IF;
	END;
$$ LANGUAGE plpgsql;



CREATE OR REPLACE FUNCTION checkImageReadACL(imageid int, trusteeid int) RETURNS boolean AS $$
	SELECT bool_or(checkACL(markfolder.id, $2)) 
			OR bool_or(checkACL(printfolder.id, $2))
			OR checkACL(image.ID, $2)
	FROM image
		LEFT OUTER JOIN exercise AS markexercise ON markexercise.mark = image.id 
		LEFT OUTER JOIN folder AS markfolder ON markfolder.id = markexercise.folder
		LEFT OUTER JOIN exercise AS printexercise ON printexercise.print = image.id 
		LEFT OUTER JOIN folder AS printfolder ON printfolder.id = printexercise.folder
	WHERE image.id = $1
	GROUP BY image.id;
$$ LANGUAGE SQL;
CREATE OR REPLACE FUNCTION checkImageReadACL(imageid int) RETURNS boolean AS $$
	SELECT bool_or(checkACL(markfolder.id)) 
			OR bool_or(checkACL(printfolder.id))
			OR checkACL(image.ID)
	FROM image
		LEFT OUTER JOIN exercise AS markexercise ON markexercise.mark = image.id 
		LEFT OUTER JOIN folder AS markfolder ON markfolder.id = markexercise.folder
		LEFT OUTER JOIN exercise AS printexercise ON printexercise.print = image.id 
		LEFT OUTER JOIN folder AS printfolder ON printfolder.id = printexercise.folder
	WHERE image.id = $1
	GROUP BY image.id;
$$ LANGUAGE SQL;

CREATE OR REPLACE FUNCTION checkAssignmentConstraints(pAID int) RETURNS void AS $$
	DECLARE
		i int;
		vid int;
	BEGIN
		SELECT INTO vid id FROM resulttype WHERE short = 'VALIDATED';
		-- At each step (i.e. ANALYSIS, COMPARISON, EVALUATION), a VALIDATED result
		-- (besides not being allowed to be overwritten) may not be anterior to any
		-- CURRENT or AUTOSAVE result - that is, you can't save after you have submitted.
		-- In other words, the latest result saved must be either VALIDATED or no VALIDATED
		-- result must be present at all.
		FOR i IN (SELECT id FROM resultstep ORDER BY id ASC) LOOP
			IF (((SELECT type FROM result WHERE assignment = pAID AND step = i ORDER BY updatedon DESC LIMIT 1) <> vid) AND ((SELECT count(*) FROM result WHERE assignment = pAID AND step = i AND type = vid) > 0)) THEN
				RAISE EXCEPTION 'Assignment (ID=%) integrity error: cannot save to the CURRENT or AUTOSAVE result after the step (ID=%) has been VALIDATED', pAID, i;
			END IF;
		END LOOP;
		-- One cannot save anything without the previous step being validated.
		FOR i IN (SELECT id FROM resultstep ORDER BY id DESC) LOOP
			IF ((i > 1) AND ((SELECT count(*) FROM result WHERE assignment = pAID AND step = i) > 0)) THEN
				IF ((SELECT count(*) FROM result WHERE assignment = pAID AND step = i-1 AND type = vid) = 0) THEN
					RAISE EXCEPTION 'Assignment (ID=%) integrity error: cannot save a result without the previous step being validated', pAID;
				END IF;
			END IF;
		END LOOP;
		RETURN;
	END;
$$ LANGUAGE plpgsql;

-- Assignment progress is measured by the number of VALIDATED results and, if any, the number
-- of intermediate results (i.e. CURRENT/AUTOSAVE)
-- 0: exercise started (assignment created) but no results at all
-- 1: analysis started (i.e. in analysis, CURRENT or AUTOSAVE result present, no VALIDATED)
-- 2: analysis finished (i.e. in analysis, a VALIDATED result)
-- 3: comparison started
-- 4: comparison finished
-- 5: evaluation started
-- 6: evaluation finished
-- Note that due to the fact that the web application saves 2 times upon validation, steps
-- 2 and 3, as well as steps 4 and 5, are the same: we'll never see steps 2 or step 4, because
-- the results are immediately copied into the next result, bumping the step by one.
-- Note that this is done only to help the user: how frustrating it would be if you had to start
-- all over again when submitting a result, and having to start the next step from scratch.
CREATE OR REPLACE FUNCTION getAssignmentStatus(pAID int) RETURNS int AS $$
	DECLARE
		i int;
		vid int;
	BEGIN
		IF (SELECT count(*) FROM assignment WHERE id = pAID) <> 1 THEN
			RAISE EXCEPTION 'No such assignment (ID=%)', pAID;
		END IF;
		SELECT INTO vid id FROM resulttype WHERE short = 'VALIDATED';
		FOR i IN (SELECT id FROM resultstep ORDER BY id DESC) LOOP
			IF (SELECT count(*) FROM result WHERE assignment = pAID AND step = i AND type = vid) > 0 THEN
				RETURN 2*i;
			ELSIF (SELECT count(*) FROM result WHERE assignment = pAID AND step = i) > 0 THEN
				RETURN 2*i-1;
			END IF;
		END LOOP;
		RETURN 0;
	END;
$$ LANGUAGE plpgsql;


CREATE OR REPLACE FUNCTION getAnnotationType(pTextType text) RETURNS int AS $$
	DECLARE
		res int;
	BEGIN
		SELECT INTO res id FROM annotationtype WHERE name = pTextType;
		IF res IS NULL THEN
			INSERT INTO annotationtype(name) VALUES (pTextType) RETURNING id INTO res;
		END IF;
		RETURN res;
	END;
$$ LANGUAGE plpgsql;

-- Drop table

-- DROP TABLE public.usertrack;

CREATE TABLE public.usertrack (
	id serial NOT NULL,
	"type" int4 NOT NULL,
	exid numeric NOT NULL,
	userid numeric NOT NULL,
	step numeric NOT NULL,
	"data" jsonb NOT NULL,
	CONSTRAINT usertrack_pkey PRIMARY KEY (id)
);
CREATE INDEX usertrack_exid_idx ON public.usertrack USING btree (exid);
CREATE INDEX usertrack_step_idx ON public.usertrack USING btree (step);
CREATE INDEX usertrack_type_idx ON public.usertrack USING btree (type);
CREATE INDEX usertrack_userid_idx ON public.usertrack USING btree (userid);

-- Drop table

-- DROP TABLE public.usertracktype;

CREATE TABLE public.usertracktype (
	id serial NOT NULL,
	"name" text NOT NULL,
	CONSTRAINT usertracktype_pkey PRIMARY KEY (id)
);

INSERT INTO public.usertracktype ("name") VALUES ('view');
INSERT INTO public.usertracktype ("name") VALUES ('mouse');


