function out = detector_scan(ext, sound, scan, channels, log, context)

% detector_scan - scan sound with detector
% ----------------------------------------
%
% out = detector_scan(ext, sound, scan, channels, log, context)
%
% out = detector_scan(context)
% 
% Input:
% ------
%  ext - detector
%  sound - sound
%  scan - scan (def: default sound scan)
%  channels - channels to scan (def: [])
%  log - log to append events to (def: [])
%  context - context
%
% Output:
% -------
%  out - events or updated log

%--------------------------------------------
% HANDLE INPUT
%--------------------------------------------

%--
% CONTEXT INPUT
%--

% NOTE: simply rename and unpack context

if (nargin < 2)
	
	context = ext;
	
	try
		ext = context.ext; sound = context.sound; scan = context.scan; channels = context.channels; log = context.log;
	catch	
		error('Problems unpacking context.');
	end
	
%--
% COMPONENT INPUT
%--

else
	
	%--
	% get default scan
	%--

	if (nargin < 3) || isempty(scan)
		scan = get_sound_scan(sound);
	end

	%--
	% set and check channels
	%--

	if (nargin < 4)
		channels = [];
	end

	if ~proper_channels(channels, sound)
		error('Improper scan channels input.');
	end

	%--
	% set default empty log
	%--

	if (nargin < 5)
		log = [];
	end

	%--
	% build best context we can
	%--

	if (nargin < 6) || isempty(context)
		context = detector_context(ext, sound, scan, log);
	end
	
end

%--------------------------------------------
% SETUP
%--------------------------------------------

%--
% get grid from parent if possible
%--

if ~isempty(context.par)
	data = get_browser(context.par); grid = data.browser.grid; axes = data.browser.axes;
else
	grid = []; axes = [];
end
	
%--
% set explain and waitbar flags
%--

explaining = ext.control.explain && ~isempty(ext.fun.explain.display);

waiting = ~explaining;

%--
% waitbar setup
%--

if waiting
	
	pal = detector_waitbar(context);

	waitbar_update(pal, 'PROGRESS', 'message', 'Starting ...');

	handles = get_control(pal, 'events', 'handles');

end

%--
% explain setup
%--

% NOTE: create the explain figure, parameters, and layout figure if needed

if explaining
	
	%--
	% create explain figure
	%--
	
	context.explain.figure = explain_figure(context.par, context.ext);
	
	%--
	% get explain parameters
	%--

	if ~isempty(ext.fun.explain.parameter.create)
		context.explain.parameter = ext.fun.explain.parameter.create(context);
	else
		context.explain.parameter = struct([]);
	end
		
	%--
	% layout explain figure
	%--
	
	if ~isempty(ext.fun.explain.layout)
		
		% NOTE: how do we expect to use these ?
		
		context.grid = grid; context.axes = axes;
		
		ext.fun.explain.layout(context.explain.figure, context.explain.parameter, context);
		
	end
	
end

%--------------------------------------------
% DETECTOR SCAN
%--------------------------------------------

%--
% get first page
%--

[page, context.scan] = get_scan_page(context.scan);

%--
% page through sound
%--

event = cell(0); contexts = [];

while ~isempty(page)
	
	%--------------------------------------------
	% COMPUTE
	%--------------------------------------------
	
	start = clock;
	
	%--
	% READ PAGE
	%--
	
	page = read_sound_page(sound, page, channels); 
	
	%--
	% FILTER PAGE
	%--

	page = filter_sound_page(page, context);
	
	%--
	% FEATURE PAGE
	%--
	
	% TODO:
	% -----
	%
	% 1. only read data from sound files in read_sound_page
	%
	% 2. filter signal (source signal) ???
	%
	% 3. compute features (signals or images (or sampled on a grid))
	%
	% 4. filter features ???
	%
	% 5. detect events
	%
	% 6. measure events
	%
	% 7. classify events
	
	%--
	% compute considering whether extension handles multiple channels
	%--
	
	if ext.multichannel
	
		%--
		% compute all channels
		%--
		
		context.page = rmfield(page, {'samples', 'filtered'});
		
		[page_events, context] = ext.fun.compute(page, ext.parameter, context);
	
	else
	
		%--
		% loop computation over channels
		%--
		
		M = length(page.channels);
		
		if isempty(contexts)
			for k = 1:M
				contexts{k} = context;
			end
		end
		
		page_events = cell(1, M);
		
		for k = 1:M
			
			channel_page = get_channel_page(page, k);
			
			contexts{k}.page = rmfield(channel_page, {'samples', 'filtered'});
			
			[page_events{k}, contexts{k}] = ext.fun.compute(channel_page, ext.parameter, contexts{k});
			
		end
		
		page_events = collapse_cell(page_events);
		
	end
	
	elapsed = etime(clock, start); 

	%--------------------------------------------
	% EXPLAIN
	%--------------------------------------------
	
	if explaining
		
		% NOTE: explain considering whether the extension handled multiple channels
		
		if ~isempty(contexts)
			
			for k = 1:M
				
				explain = contexts{k}.explain;
				
				explain.handles{k} = ext.fun.explain.display(explain.figure, explain.data, explain.parameter, contexts{k});
				
			end

		else

			explain = context.explain;
			
			explain.handles = ext.fun.explain.display(explain.figure, explain.data, explain.parameter, context);
		
		end

		% NOTE: waitbar update calls drawnow
		
		if ~waiting
			drawnow;
		end
		
	end
	
	%--------------------------------------------
	% COLLECT
	%--------------------------------------------
	
	%--
	% update event output with page events
	%--
	
	if ~isempty(page_events)
	
		%--
		% consider page boundaries
		%--
		
		% NOTE: when start is session boundary don't allow negative page times
		
		if mod(page.interval, 2)
			
			for k = 1:numel(page_events)
				page_events(k).time = max(page_events(k).time, 0);
			end
			
		end
	
		% NOTE: when stop is session boundary don't allow time to exceed page duration
		
		if page.interval > 1
			
			for k = 1:numel(page_events)
				page_events(k).time = min(page_events(k).time, page.duration);
			end
			
		end
		
		%--
		% add page offset
		%--
		
		for k = 1:numel(page_events)
			page_events(k).time = page_events(k).time + page.start;
		end

		event{end + 1} = page_events;
		
	end
	
	%--------------------------------------------
	% WAITBAR
	%--------------------------------------------
	
	if waiting
		
		waitbar_update(pal, 'PROGRESS', ...
			'value', context.scan.position, 'message', page_time_string(page, sound, grid) ...
		);

% 		detector_waitbar_update();
		
		%-------------------------

		str = get(handles.uicontrol.listbox, 'string');

		new_str = [page_time_string(page, sound, grid, 1), '  ', integer_unit_string(length(page_events), 'event')];
		
		if ~isempty(str)
			str{end + 1} = new_str;
		else
			str = {new_str};
		end

		set(handles.uicontrol.listbox, 'string', str, 'value', length(str));

		%-------------------------
    
	end
	
	%--
	% adapt scan and get next page
	%--
	
	context.scan = adapt_page_duration(context.scan, elapsed);
    
	[page, context.scan] = get_scan_page(context.scan);
	
end

%--------------------------------------------
% FINISH SCAN
%--------------------------------------------
	
%--
% make sure explain objects are visible
%--

if explaining
	
	%--
	% get explain handles
	%--
	
	explain_all = findobj(context.explain.figure); 
	
	explain_axes = findobj(context.explain.figure, 'type', 'axes');
	
	%--
	% make sure objects are displayed and axes grids visible
	%--
	
	set(setdiff(explain_all, explain_axes), 'visible', 'on');
	
	set(explain_axes, 'layer', 'top');
	
end

%--------------------------------------------
% OUTPUT
%--------------------------------------------
	
%--
% concatenate and sort events
%--

if isempty(event)
    out = log; return;
end

event = collapse_cell(event);

event = sort_events(event);

%--
% output events or append to log and output log
%--

if isempty(log)
	out = event; return;
end

% TODO: consider updating log in open browsers if needed

out = log_append(log, event);

%--------------------------------------------
% FINISH
%--------------------------------------------

%--
% update waitbar and close if needed
%--

if waiting
	
	waitbar_update(pal, 'PROGRESS', ...
		'value', context.scan.position, 'message', 'Done.' ...
	);

	if get_control(pal, 'close_after_completion', 'value')
		close(pal);
	end

end


%-------------------------------------------------------------------------
% PROPER_CHANNELS
%-------------------------------------------------------------------------

function value = proper_channels(channels, sound)

if isempty(channels)
	value = 1; return;
end

value = ~(any(channels ~= floor(channels)) || any(channels < 1) || any(channels > sound.channels));


%-------------------------------------------------------------------------
% PAGE_TIME_STRING
%-------------------------------------------------------------------------

function str = page_time_string(page, sound, grid, interval)

if nargin < 4
	interval = 0;
end

if (nargin < 3) || isempty(grid)
	grid = [];
end

start_time = get_sound_time(sound, page.start, 1);

if isempty(grid)
	start_str = sec_to_clock(start_time);
else
	start_str = get_grid_time_string(grid, start_time, sound.realtime);
end

if ~interval
	str = start_str; return;
end

end_time = get_sound_time(sound, page.start + page.duration, 1);

if isempty(grid)
	end_str = sec_to_clock(end_time);
else
	end_str = get_grid_time_string(grid, end_time, sound.realtime);
end

str = ['[' start_str, ' - ', end_str, ']']; 
	

%-------------------------------------------------------------------------
% SORT_EVENTS
%-------------------------------------------------------------------------

function event = sort_events(event)

%--
% get event times and channels
%--

% TODO: this needs some protection from inconsistent events

time = struct_field(event, 'time'); time = time(:,1);

channel = struct_field(event, 'channel');

%--
% sort on time then channel
%--

[ignore, ix] = sortrows([time(:), channel(:)]);

event = event(ix);


%-------------------------------------------------------------------------
% GET_CHANNEL_PAGE
%-------------------------------------------------------------------------

function page = get_channel_page(page, k);

%--
% create channel specific page from multiple channel page
%--

page.channels = page.channels(k);

page.samples = page.samples(:, k);

% TODO: in the general scan we will also need to pull out channel features

if ~isempty(page.filtered)
	page.filtered = page.filtered(:, k);
end


%-------------------------------------------------------------------------
% COLLAPSE_CELL
%-------------------------------------------------------------------------

function out = collapse_cell(in)

%--
% check for cell input
%--

if ~iscell(in)
	error('Input must be cell array');
end

if numel(in) == 0
    out = []; return;
end

%--
% remove empty cells and align content arrays
%--

for k = numel(in):-1:1
	
	if isempty(in{k})
		in(k) = []; continue;
	end

	% NOTE: make contents row vector to permit simple collapse
	
	in{k} = in{k}(:)';

end

%--
% collapse content arrays and output
%--

% NOTE: why are we using the transpose here?

out = [in{:}]';
