-- @author tigre@ybos.net
-- @author jennie@ybos.net

begin
     	acs_object_type.create_type (
			  supertype => 'acs_object',
			  object_type => 'mp3',
			  pretty_name => 'MP3',
			  pretty_plural => 'MP3s',
			  table_name => 'MP3_MP3S',
			  id_column => 'MP3_ID'
	);

     	acs_object_type.create_type (
			  supertype => 'acs_object',
			  object_type => 'mp3_playlist',
			  pretty_name => 'Playlist',
			  pretty_plural => 'Playlists',
			  table_name => 'MP3_PLAYLISTS',
			  id_column => 'PLAYLIST_ID'
	);
end;
/

create table mp3_mp3s (
	mp3_id		constraint mp3_mp3_id_fk
			references acs_objects (object_id)
			constraint mp3_mp3s_pk
			primary key,
	file_path	varchar(200)
			constraint mp3_mp3s_nn
			not null
			constraint mp3_mp3s_file_unique
			unique,
	title		varchar(200)
			constraint mp3_mp3s_title_nn
			not null,
	artist		varchar(100),
	bitrate		varchar(50),
	album		varchar(200),
	tracknum	integer,
	genre		varchar(100),
	year		varchar(10),
	layer		integer,
	copyright_p	char(1) default 'f' 
			constraint mp3_mp3s_copyright_p_ck
			check(copyright_p in ('t','f')),
	version		integer,
	mode_num	integer,
	stereo_p	char(1) default 'f' 
			constraint mp3_mp3s_stereo_p_ck
			check(stereo_p in ('t','f')),
	frequency	number,
	minutes		integer,
	seconds		integer,
	comments        varchar(200),
	vbr_p		char(1) default 'f' 
			constraint mp3_mp3s_vbr_p_ck
			check(vbr_p in ('t','f')),
	state		varchar(50),
	deleted_p	char(1) default 'f' 
			constraint mp3_mp3s_deleted_p_ck
			check(deleted_p in ('t','f'))
);

create table mp3_playlists (
	playlist_id	constraint mp3_playlist_id_fk
			references acs_objects (object_id)
			constraint mp3_playlists_pk
			primary key,
	name 		varchar(100),
	shuffle_p	char(1) default 'f'
			constraint mp3_playlists_shuffle_p_ck
			check(shuffle_p in ('f','t')),
	remove_threshold	integer
);

create table mp3_mp3_playlist_map (
	mp3_id		constraint mp3_map_mp3_id_fk
			references mp3_mp3s (mp3_id),
	playlist_id	constraint mp3_map_playlist_id_fk
			references mp3_playlists (playlist_id),
	sort_key	integer,
	-- This pk constraint will have ot be changed, maybe add a column
	-- for the pk, thus allowing multiple instances of a given song
	-- on a playlist
	constraint mp3_map_pk
	primary key (mp3_id,playlist_id)
);

create table mp3_mp3_stats (
	mp3_id		constraint mp3_mp3_stats_mp3_id_fk
			references mp3_mp3s (mp3_id),
	user_id		constraint mp3_mp3_stats_user_id_fk
			references users(user_id),
	access_date	date
			constraint mp3_mp3_stats_access_date_nn
			not null
);

create table mp3_playlist_stats (
	playlist_id	constraint mp3_playlist_stats_playlist_fk
			references mp3_playlists (playlist_id),
	user_id		constraint mp3_playlists_stats_user_id_fk
			references users(user_id),
	access_date	date
			constraint mp3_playlist_stats_date_nn
			not null
);

create table mp3_votes (
	mp3_id		constraint mp3_votes_mp3_id_fk
			references mp3_mp3s (mp3_id),
	playlist_id	constraint mp3_votes_playlist_id_fk
			references mp3_playlists (playlist_id),
	user_id		constraint mp3_votes_user_id_fk
			references users(user_id),
	vote		integer
			constraint mp3_votes_vote_nn
			not null,
	vote_date	date
);

create or replace view mp3_mp3_playlist_map_view as
	select m.mp3_id,		
	       m.playlist_id,
	       m.sort_key,
	       nvl(v.total,0) as total
	from   mp3_mp3_playlist_map m,
	       (select mp3_id,
	               playlist_id,
	               sum(vote) as total
	        from   mp3_votes
                group by mp3_id, playlist_id) v
	where  m.mp3_id = v.mp3_id (+)
	and    m.playlist_id = v.playlist_id (+);

create or replace view mp3_mp3s_not_deleted as
	select *
	from   mp3_mp3s
	where  deleted_p = 'f';

create or replace view mp3_playlist_song_count as
	select mpv.playlist_id, count(*) as songs
	from   mp3_mp3_playlist_map_view mpv,
               mp3_mp3s_not_deleted mnd,
	       mp3_playlists mp
        where  mpv.mp3_id = mnd.mp3_id
        and    mpv.playlist_id = mp.playlist_id
	and    mpv.total > mp.remove_threshold
        group by mpv.playlist_id;

create or replace package mp3
as
	function new (
		mp3_id		in mp3_mp3s.mp3_id%TYPE		default null,
		file_path 	in mp3_mp3s.file_path%TYPE,
		title 		in mp3_mp3s.title%TYPE,
		artist		in mp3_mp3s.artist%TYPE		default null,
		bitrate		in mp3_mp3s.bitrate%TYPE	default null,
		album		in mp3_mp3s.album%TYPE		default null,
		tracknum	in mp3_mp3s.tracknum%TYPE	default null,
		genre		in mp3_mp3s.genre%TYPE		default null,
		year		in mp3_mp3s.year%TYPE		default null,
		layer		in mp3_mp3s.layer%TYPE		default null,
		copyright_p	in mp3_mp3s.copyright_p%TYPE	default null,
		version		in mp3_mp3s.version%TYPE	default null,
		mode_num	in mp3_mp3s.mode_num%TYPE	default null,
		stereo_p	in mp3_mp3s.stereo_p%TYPE		default null,
		frequency	in mp3_mp3s.frequency%TYPE	default null,
		minutes		in mp3_mp3s.minutes%TYPE	default null,
		seconds		in mp3_mp3s.seconds%TYPE	default null,
		comments	in mp3_mp3s.comments%TYPE	default null,
		vbr_p		in mp3_mp3s.vbr_p%TYPE		default null,
		state		in mp3_mp3s.state%TYPE		default null,
		deleted_p	in mp3_mp3s.deleted_p%TYPE	default 'f',
		object_type   	in acs_objects.object_type%TYPE     default 'mp3',
		creation_date 	in acs_objects.creation_date%TYPE   default sysdate,
		creation_user 	in acs_objects.creation_user%TYPE   default null,
		creation_ip   	in acs_objects.creation_ip%TYPE     default null,
		context_id    	in acs_objects.context_id%TYPE      default null
        ) return acs_objects.object_id%TYPE;

	procedure delete (
		mp3_id in mp3_mp3s.mp3_id%TYPE
	);

	procedure mark_as_deleted (
		mp3_id in mp3_mp3s.mp3_id%TYPE
	);

end mp3;
/
show errors

create or replace package body mp3
as
	function new (
		mp3_id		in mp3_mp3s.mp3_id%TYPE		default null,
		file_path 	in mp3_mp3s.file_path%TYPE,
		title 		in mp3_mp3s.title%TYPE,
		artist		in mp3_mp3s.artist%TYPE		default null,
		bitrate		in mp3_mp3s.bitrate%TYPE	default null,
		album		in mp3_mp3s.album%TYPE		default null,
		tracknum	in mp3_mp3s.tracknum%TYPE	default null,
		genre		in mp3_mp3s.genre%TYPE		default null,
		year		in mp3_mp3s.year%TYPE		default null,
		layer		in mp3_mp3s.layer%TYPE		default null,
		copyright_p	in mp3_mp3s.copyright_p%TYPE	default null,
		version		in mp3_mp3s.version%TYPE	default null,
		mode_num	in mp3_mp3s.mode_num%TYPE	default null,
		stereo_p	in mp3_mp3s.stereo_p%TYPE	default null,
		frequency	in mp3_mp3s.frequency%TYPE	default null,
		minutes		in mp3_mp3s.minutes%TYPE	default null,
		seconds		in mp3_mp3s.seconds%TYPE	default null,
		comments	in mp3_mp3s.comments%TYPE	default null,
		vbr_p		in mp3_mp3s.vbr_p%TYPE		default null,
		state		in mp3_mp3s.state%TYPE		default null,
		deleted_p	in mp3_mp3s.deleted_p%TYPE	default 'f',
		object_type   	in acs_objects.object_type%TYPE     default 'mp3',
		creation_date 	in acs_objects.creation_date%TYPE   default sysdate,
		creation_user 	in acs_objects.creation_user%TYPE   default null,
		creation_ip   	in acs_objects.creation_ip%TYPE     default null,
		context_id    	in acs_objects.context_id%TYPE      default null
        ) return acs_objects.object_id%TYPE
	is
		v_mp3_id	mp3_mp3s.mp3_id%TYPE;
	begin
		v_mp3_id := acs_object.new (
			object_id => mp3_id,
			object_type => object_type,
			creation_date => creation_date,
			creation_user => creation_user,
			creation_ip => creation_ip,
			context_id => context_id
	    	);		
		insert into mp3_mp3s
			(mp3_id, file_path, title, artist, bitrate, album, tracknum, genre, year, layer,
			copyright_p, version, mode_num, stereo_p, frequency, minutes, seconds, comments,
			vbr_p, state, deleted_p)
		values
			(v_mp3_id, file_path, title, artist, bitrate, album, tracknum, genre,
			year, layer, copyright_p, version, mode_num, stereo_p, frequency,
			minutes, seconds, comments, vbr_p, state, deleted_p);

		return v_mp3_id;	
	end new;



	procedure delete (
		mp3_id in mp3_mp3s.mp3_id%TYPE
	)	
	is
	begin
		delete from mp3_mp3s where mp3_id = mp3.delete.mp3_id;
		acs_object.delete(mp3_id);
	end delete;



	procedure mark_as_deleted (
		mp3_id in mp3_mp3s.mp3_id%TYPE
	)
	is
	begin

	update mp3_mp3s 
		set deleted_p ='t'
		where mp3_id = mp3.mark_as_deleted.mp3_id;

	end mark_as_deleted;

end mp3;
/
show errors


create or replace package mp3_playlist
as

	function new (
		playlist_id	in mp3_playlists.playlist_id%TYPE	default null,
		name		in mp3_playlists.name%TYPE		default null,
		shuffle_p	in mp3_playlists.shuffle_p%TYPE		default 'f',
		remove_threshold in mp3_playlists.remove_threshold%TYPE	default null,
		object_type	in acs_objects.object_type%TYPE		default 'mp3_playlist',
		creation_date	in acs_objects.creation_date%TYPE	default sysdate,
		creation_user	in acs_objects.creation_user%TYPE	default null,
		creation_ip	in acs_objects.creation_ip%TYPE		default null,
		context_id	in acs_objects.context_id%TYPE		
        ) return acs_objects.object_id%TYPE;

	procedure delete (
		playlist_id	in mp3_playlists.playlist_id%TYPE
	);

	procedure item_add (
		mp3_id		in mp3_mp3s.mp3_id%TYPE,
		playlist_id	in mp3_playlists.playlist_id%TYPE,
		sort_key	in mp3_mp3_playlist_map.sort_key%TYPE	default null
	);

	procedure item_remove (
		mp3_id		in mp3_mp3s.mp3_id%TYPE,
		playlist_id	in mp3_playlists.playlist_id%TYPE
	);

	function vote_cast (
		user_id		in users.user_id%TYPE,
		playlist_id	in mp3_playlists.playlist_id%TYPE,
		mp3_id		in mp3_mp3s.mp3_id%TYPE,
		vote		in mp3_votes.vote%TYPE,
		vote_date	in mp3_votes.vote_date%TYPE		default sysdate
	) return mp3_mp3_playlist_map_view.total%TYPE;

	procedure item_order_swap (
		playlist_id	in mp3_playlists.playlist_id%TYPE,
		sort_key	in mp3_mp3_playlist_map.sort_key%TYPE
	);

end mp3_playlist;
/
show errors



create or replace package body mp3_playlist
as
	function new (
		playlist_id	in mp3_playlists.playlist_id%TYPE	default null,
		name		in mp3_playlists.name%TYPE		default null,
		shuffle_p	in mp3_playlists.shuffle_p%TYPE		default 'f',
		remove_threshold in mp3_playlists.remove_threshold%TYPE	default null,
		object_type	in acs_objects.object_type%TYPE		default 'mp3_playlist',
		creation_date	in acs_objects.creation_date%TYPE	default sysdate,
		creation_user	in acs_objects.creation_user%TYPE	default null,
		creation_ip	in acs_objects.creation_ip%TYPE		default null,
		context_id	in acs_objects.context_id%TYPE		
        ) return acs_objects.object_id%TYPE
	is
		v_playlist_id	mp3_playlists.playlist_id%TYPE;
	begin
		v_playlist_id := acs_object.new (
			object_id => playlist_id,
			object_type => object_type,
			creation_date => creation_date,
			creation_user => creation_user,
			creation_ip => creation_ip,
			context_id => context_id
		);
		insert into mp3_playlists
			(playlist_id, name, shuffle_p, remove_threshold)
		values
			(v_playlist_id, name, shuffle_p, remove_threshold);
		return v_playlist_id;
	end new;

	procedure delete (
		playlist_id	in mp3_playlists.playlist_id%TYPE
	)
	is
	begin
		delete from mp3_votes
			where playlist_id = mp3_playlist.delete.playlist_id;
		delete from mp3_mp3_playlist_map
			where playlist_id = mp3_playlist.delete.playlist_id;
		delete from mp3_playlists
			where playlist_id = mp3_playlist.delete.playlist_id;
		acs_object.delete(playlist_id);
	end delete;

	-- Note: item_add uses "insert after" ordering, so "order" points to
	-- the last record _not_ to be updated
	procedure item_add (
		mp3_id		in mp3_mp3s.mp3_id%TYPE,
		playlist_id	in mp3_playlists.playlist_id%TYPE,
		sort_key	in mp3_mp3_playlist_map.sort_key%TYPE	default 0
	)
	is
		cursor map_cur is
			select mmpm.mp3_id, mmpm.playlist_id, mmpm.sort_key
			from   mp3_mp3_playlist_map mmpm, mp3_mp3s_not_deleted mmnd
			where  mmpm.mp3_id = mmnd.mp3_id
			and    mmpm.playlist_id = mp3_playlist.item_add.playlist_id
			and    mmpm.sort_key > mp3_playlist.item_add.sort_key
			order by mmpm.sort_key;
		l_sort_key	mp3_mp3_playlist_map.sort_key%TYPE;
		l_max_sort_key	mp3_mp3_playlist_map.sort_key%TYPE;
		l_new_sort_key	mp3_mp3_playlist_map.sort_key%TYPE;
	begin
		select nvl(max(sort_key),0) into l_max_sort_key
			from mp3_mp3_playlist_map
			where playlist_id = mp3_playlist.item_add.playlist_id;
		if sort_key > l_max_sort_key
		then
			l_sort_key := l_max_sort_key;
		else
			l_sort_key := sort_key;
		end if;
		l_new_sort_key := l_sort_key + 2;
		for map_rec in map_cur
		loop
			update mp3_mp3_playlist_map
				set    sort_key = l_new_sort_key
				where  mp3_id = map_rec.mp3_id
				and    playlist_id = map_rec.playlist_id;
			l_new_sort_key := l_new_sort_key + 1;
		end loop;
		insert into mp3_mp3_playlist_map
			(mp3_id, playlist_id, sort_key)
			values
			(mp3_id, playlist_id, l_sort_key + 1);
	end item_add;

	-- We'll just delete the mapping and it's corresponding votes
	-- We won't worry about gaps in the order
	procedure item_remove (
		mp3_id		in mp3_mp3s.mp3_id%TYPE,
		playlist_id	in mp3_playlists.playlist_id%TYPE
	)
	is
	begin
		delete from mp3_votes
			where  mp3_id = mp3_playlist.item_remove.mp3_id
			and    playlist_id = mp3_playlist.item_remove.playlist_id;
		delete from mp3_mp3_playlist_map
			where  mp3_id = mp3_playlist.item_remove.mp3_id
			and    playlist_id = mp3_playlist.item_remove.playlist_id;
	end item_remove;

	function vote_cast (
		user_id		in users.user_id%TYPE,
		playlist_id	in mp3_playlists.playlist_id%TYPE,
		mp3_id		in mp3_mp3s.mp3_id%TYPE,
		vote		in mp3_votes.vote%TYPE,
		vote_date	in mp3_votes.vote_date%TYPE		default sysdate
	) return mp3_mp3_playlist_map_view.total%TYPE
	is
		l_vote_sum	mp3_votes.vote%TYPE;
	begin
		insert into mp3_votes
			(mp3_id, playlist_id, user_id, vote, vote_date)
			values
			(mp3_id, playlist_id, user_id, vote, vote_date);
		select total into l_vote_sum
			from   mp3_mp3_playlist_map_view
			where  mp3_id = mp3_playlist.vote_cast.mp3_id
			and    playlist_id = mp3_playlist.vote_cast.playlist_id;
		return l_vote_sum;
	end vote_cast;

	-- "swap w/ next"
	procedure item_order_swap (
		playlist_id	in mp3_playlists.playlist_id%TYPE,
		sort_key	in mp3_mp3_playlist_map.sort_key%TYPE
	)
	is
		l_next_order	mp3_mp3_playlist_map.sort_key%TYPE;
	begin
		select min(sort_key) into l_next_order
			from   mp3_mp3_playlist_map m, mp3_mp3s_not_deleted d
			where  m.mp3_id = d.mp3_id
			and    m.sort_key > mp3_playlist.item_order_swap.sort_key;

		update mp3_mp3_playlist_map mmpm
			set    sort_key = decode(mmpm.sort_key,
			                      mp3_playlist.item_order_swap.sort_key,
			                      mp3_playlist.item_order_swap.sort_key + 1,
			                      mp3_playlist.item_order_swap.sort_key)
			where  sort_key in (mp3_playlist.item_order_swap.sort_key,
			                    l_next_order);
	end item_order_swap;

end mp3_playlist;
/
show errors


-- permissions don't quite work with the file system we've set up; most
-- would be useful under /admin, where the admin permission is required. 

--declare
--      default_context acs_objects.object_id%TYPE;
--      registered_users acs_objects.object_id%TYPE;
--      the_public acs_objects.object_id%TYPE;

--begin 
--      acs_privilege.create_privilege('mp3_view_mp3');
--      acs_privilege.create_privilege('mp3_create_mp3');
--      acs_privilege.create_privilege('mp3_delete_mp3');
--      acs_privilege.create_privilege('mp3_modify_mp3');
--      acs_privilege.create_privilege('mp3_view_playlist');
--      acs_privilege.create_privilege('mp3_create_playlist');
--      acs_privilege.create_privilege('mp3_delete_playlist');
--      acs_privilege.create_privilege('mp3_modify_playlist');
--      acs_privilege.create_privilege('mp3_admin_mp3');
--      acs_privilege.create_privilege('mp3_admin_playlist');
--      acs_privilege.create_privilege('mp3_admin');
--      acs_privilege.add_child('mp3_admin_mp3', 'mp3_view_mp3');
--      acs_privilege.add_child('mp3_admin_mp3', 'mp3_create_mp3');
--      acs_privilege.add_child('mp3_admin_mp3', 'mp3_delete_mp3');
--      acs_privilege.add_child('mp3_admin_mp3', 'mp3_modify_mp3');
--      acs_privilege.add_child('mp3_admin_playlist', 'mp3_view_playlist');
--      acs_privilege.add_child('mp3_admin_playlist', 'mp3_create_playlist');
--      acs_privilege.add_child('mp3_admin_playlist', 'mp3_delete_playlist');
--      acs_privilege.add_child('mp3_admin_playlist', 'mp3_modify_playlist'); 
--      acs_privilege.add_child('mp3_admin', 'mp3_admin_playlist');
--      acs_privilege.add_child('mp3_admin', 'mp3_admin_mp3');

--      -- bind privileges to global names

--      acs_privilege.add_child('create','mp3_create_mp3');
--      acs_privilege.add_child('create','mp3_create_playlist');
--      acs_privilege.add_child('write','mp3_modify_mp3');
--      acs_privilege.add_child('write','mp3_modify_playlist');
--      acs_privilege.add_child('read','mp3_view_mp3');
--      acs_privilege.add_child('read','mp3_view_playlist');
--      acs_privilege.add_child('delete','mp3_delete_mp3');
--      acs_privilege.add_child('delete','mp3_delete_playlist');
--      acs_privilege.add_child('admin','mp3_admin');  

--      default_context := acs.magic_object_id('default_context');
--      registered_users := acs.magic_object_id('registered_users');
--      the_public := acs.magic_object_id('the_public'); 

--      -- give the public the power to view playlists by default

--      acs_permission.grant_permission (
--         object_id => acs.magic_object_id('default_context'),
--   	 grantee_id => acs.magic_object_id('the_public'),
--         privilege => 'mp3_view_playlist'
--       );


--      -- give the public the power to view mp3s by default

--      acs_permission.grant_permission (
--         object_id => acs.magic_object_id('default_context'),
--	 grantee_id => acs.magic_object_id('the_public'),
--         privilege => 'mp3_view_mp3'
--      );

-- end;
-- /
