function a_doc = reportNeuron(a_bundle, an_index, props)

% reportNeuron - Generates a report of neuron at given an_index of a_bundle.
%
% Usage:
% a_doc = reportNeuron(a_bundle, an_index, props)
%
% Description:
%   Generates a report document with preset layouts of annotated plots of
% the selected neuron. See reportLayout below for presets.
%
% Parameters:
%	a_bundle: a dataset_db_bundle object which contains the neuron
%	an_index: The index to pass to ctFromRows method of a_bundle.
%	props: A structure with any optional properties.
%	  reportLayout: Allows choosing one of predefined report types (strings):
%		1: Only +/- 100 pA traces in one plot (default).
%		1a/b: Either one of the +/- 100 pA traces in one plot.
%		2: Only +/- 100 pA traces and spike shapes in one horiz. plot.
%		2a: +/- 100 pA traces, f-I curve and spike shapes in one horiz. plot.
%		3: +100 pA raw trace and rate profile stacked vertically.
%		3b: -100 pA raw trace and rate profile stacked vertically.
%		4: Horiz stack of +/- 100 pA raw trace with rate profiles underneath.
%		5: 5-piece trace, spike shape, f-I curve, f-t curve quad-plot.
%	  numTraces: Limit number of traces to show in plot (>=1).
%	  traces: List of acceptable traces to load.
%	  traceAxisLimits: If given, use these limits for trace plots.
%	  rateAxisLimits: If given, use these limits for rate plots.
%	  fIAxisLimits: If given, use these limits for fIcurve plots.
%	  fIstats: Add a fI-stats plot in addition to the curve.
%	  sshapeAxisLimits: If given, use these limits for spike shape plots.
%	  sshapeResults: If 1, plot measures on the spike shape (default=1).
%
% Returns:
% 	a_doc: A doc_generate object, or a subclass, that can be plotted, or
%		printed as a PS or PDF file.
%
% Example:
% >> printTeXFile(reportNeuron(mbundle, 2222), 'a.tex')
% or:
% >> plotFigure(get(reportNeuron(mbundle, 2222), 'plot'))
% or if the result is one or many doc_plot objects:
% >> plot(reportNeuron(mbundle, 2222:2224, struct('reportLayout', '5')))
%
% See also: doc_multi, doc_generate, doc_generate/printTeXFile
%
% $Id$
%
% Author: Cengiz Gunay <cgunay@emory.edu>, 2006/01/24

% Copyright (c) 2007 Cengiz Gunay <cengique@users.sf.net>.
% This work is licensed under the Academic Free License ("AFL")
% v. 3.0. To view a copy of this license, please look at the COPYING
% file distributed with this software or visit
% http://opensource.org/licenses/afl-3.0.php.

% TODO: add a prop (clearPageAtEnd: If given, a page break is inserted at end of document.)

if ~ exist('props', 'var')
  props = struct;
end

if isa(an_index, 'tests_db')
  num_items = dbsize(an_index, 1);
else
  num_items = length(an_index);
end

% If input is an array, then also return array
if num_items > 1 
  % Create array of outputs
  for item_num = 1:num_items
    if isa(an_index, 'tests_db')
      a_doc(item_num) = ...
	  reportNeuron(a_bundle, onlyRowsTests(an_index, item_num, ':', ':'), props);
    else
      a_doc(item_num) = ...
	  reportNeuron(a_bundle, an_index(item_num), props);
    end
  end
  return;
end

if ~ isfield(props, 'reportLayout')
  props.reportLayout = 1;		% default report type
end

% convert to string from numeric
if isnumeric(props.reportLayout)
  props.reportLayout = num2str(props.reportLayout);
end

if ~ isfield(props, 'horizRow')
  props.horizRow = 1; % Default due to laziness
end

db_id = properTeXLabel(get(get(a_bundle, 'joined_db'), 'id'));

switch (props.reportLayout)
    % Only trace plots
    case '1'
      % Get raw data traces from bundles
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      
      a_doc = ...
	  horizRowTraces(trace_plot, [0 0 4 3], plot_title, caption, short_caption);

    case '1a'
      % Get raw data traces from bundles
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;

      a_doc = doc_plot(a_d100_plot, caption, short_caption, ...
			     struct('floatType', 'figure', 'center', 1, ...
				    'width', '.8\textwidth', 'shortCaption', short_caption), ...
			     'raw +100pA trace figure');
      
    case '1b'
      % Get raw data traces from bundles
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      
      a_doc = doc_plot(a_h100_plot, caption, short_caption, ...
			     struct('floatType', 'figure', 'center', 1, ...
				    'width', '.8\textwidth', 'shortCaption', short_caption), ...
			     'raw +100pA trace figure');

    case '2'
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      % spike shape comparisons 
      plot_title = '';
      [spont_sshape_plot, pulse_sshape_plot, both_sshape_plots] = ...
	  spikeShapePlots(plot_title, trace_d100, props);

      a_doc = ...
	  doc_plot(plot_stack({set(trace_plot, 'title', ''), ...
			       setProp(pulse_sshape_plot, 'noYLabel', 1)}, [], 'x', ...
			      short_caption, ...
			      struct('PaperPosition', [0 0 6 3], ...
				     'relativeSizes', [2 1])), ... 
		   caption, properTeXFilename([ trace_id '_raw_traces_sshapes']), ...
		   struct('floatType', 'figure', 'center', 1, ...
			  'height', '.8\textheight', 'shortCaption', short_caption), ...
		   'raw trace sshape figure');

    case '2a'
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      % spike shape comparisons 
      plot_title = '';
      [spont_sshape_plot, pulse_sshape_plot, both_sshape_plots] = ...
	  spikeShapePlots(plot_title, trace_d100, props);

      if isfield(props, 'fIAxisLimits')
	fI_axis_limits = props.fIAxisLimits;
      else
	fI_axis_limits = [0 200 0 100];
      end

      fIcurve_plot = plotfICurve(a_bundle, an_index, ...
				 mergeStructs(props, struct('axisLimits', fI_axis_limits)));

      a_doc = ...
	  doc_plot(plot_stack({set(trace_plot, 'title', ''), fIcurve_plot, ...
			       setProp(pulse_sshape_plot, 'noYLabel', 1)}, [], 'x', ...
			      short_caption, ...
			      struct('PaperPosition', [0 0 8 3], ...
				     'relativeSizes', [2 1 1])), ... 
		   caption, properTeXFilename([ trace_id '_raw_traces_sshapes']), ...
		   struct('floatType', 'figure', 'center', 1, ...
			  'height', '.8\textheight', 'shortCaption', short_caption), ...
		   'raw trace sshape figure');

    case '3'
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      a_trace_rate_plot = traceRatePlot(trace_d100, a_d100_plot, trace_id, props);
      short_caption = ['Raw traces and corresponding rate profile of ' trace_id ...
		       ' for +100 pA injected current.' ];
      caption = short_caption;
      a_doc = doc_plot(a_trace_rate_plot, caption, short_caption, ...
			     struct('floatType', 'figure', 'center', 1, ...
				    'width', '.8\textwidth', 'shortCaption', short_caption), ...
			     'raw trace and rate profile figure');

    case '3b'
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      a_trace_rate_plot = traceRatePlot(trace_h100, a_h100_plot, trace_id, props);
      short_caption = ['Raw traces and corresponding rate profile of ' trace_id ...
		       ' for -100 pA injected current.' ];
      caption = short_caption;
      a_doc = doc_plot(a_trace_rate_plot, caption, short_caption, ...
			     struct('floatType', 'figure', 'center', 1, ...
				    'width', '.8\textwidth', 'shortCaption', short_caption), ...
			     'raw trace and rate profile figure');
      
    case '4'
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      short_caption = ['Raw traces and corresponding rate profiles of ' trace_id ...
		       ' for +/- 100 pA injected currents.' ];
      caption = short_caption;
      a_doc = ...
	  doc_plot(plot_stack({traceRatePlot(trace_d100, a_d100_plot, trace_id, props), ...
			       traceRatePlot(trace_h100, a_h100_plot, trace_id, props)}, ...
			      [], 'x', '', ...
			      mergeStructs(props, struct('yLabelsPos', 'left', ...
							 'yTicksPos', 'left'))), ...
		   caption, short_caption, ...
		   struct('floatType', 'figure', 'center', 1, ...
			  'width', '.8\textwidth', 'shortCaption', short_caption), ...
		   'raw trace and rate profile figure');

    case '5'
      [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData;
      [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots;
      trace_plot_quad = ...
	  plot_stack({traceRatePlot(trace_d100, a_d100_plot, trace_id, props), ...
		      traceRatePlot(trace_h100, a_h100_plot, trace_id, props)}, ...
		     [], 'x', '', ...
		     mergeStructs(props, struct('yLabelsPos', 'left', ...
						'yTicksPos', 'left')));
      [spont_sshape_plot, pulse_sshape_plot, both_sshape_plots] = ...
	  spikeShapePlots(plot_title, trace_d100, props);
      if isfield(props, 'fIAxisLimits')
	fI_axis_limits = props.fIAxisLimits;
      else
	fI_axis_limits = [0 200 0 100];
      end

      fIcurve_plot = plotfICurve(a_bundle, an_index, ...
				 mergeStructs(props, struct('axisLimits', fI_axis_limits)));

      if isfield(props, 'fIstats')
	fIcurve_plot = ...
	    plot_superpose({fIcurve_plot, ...
			    plotfICurveStats(a_bundle, 'avg.', struct('quiet', 1))});
      end

      sshape_ratecurve_plot = ...
	  plot_stack({pulse_sshape_plot, fIcurve_plot}, ...
		     [], 'y', '', mergeStructs(props, struct));

      short_caption = ...
	  ['Raw traces with corresponding rate profiles of ' trace_id ...
	   ' for +/- 100 pA injected currents, close-up view of a spike shape ' ...
	   'from the current-injection period, and the current-rate relationship plots.' ];
      caption = short_caption;
      a_doc = ...
	  doc_plot(plot_stack({trace_plot_quad, sshape_ratecurve_plot}, ...
			      [], 'x', trace_id, ...
			      mergeStructs(props, ...
					   struct('yLabelsPos', 'left', ...
						  'relativeSizes', [2 1]))), ...
		   caption, [ trace_id '_raw_traces_sshape_fIcurve' ], ...
		   struct('floatType', 'figure', 'center', 1, ...
			  'width', '.8\textwidth', 'shortCaption', short_caption), ...
		   'raw trace and rate profile figure');


      % rest not implemented yet.
      otherwise
	error(['reportLayout "' props.reportLayout '" not defined!']);
  end

  % The following are nested functions, sharing this functions workspace:

  % Not used:
function sshape_doc = bothSShapeDoc
  % freq
  short_caption = [ 'Spike shapes of ' trace_id '.' ];
  caption = [ short_caption ];
  
  sshape_doc = ...
      doc_plot(both_sshape_plots, ...
	       caption, ['spike shapes of ' trace_id  ...
			 ' from ' db_id ], ...
	       struct('floatType', 'figure', 'center', 1, ...
		      'width', '.9\textwidth', 'shortCaption', short_caption), ...
	       'spike shape comparison', struct);
end

function [a_d100_plot, a_h100_plot, trace_plot, trace_axis_limits] = tracePlots()
  if isfield(props, 'traceAxisLimits')
    trace_axis_limits = props.traceAxisLimits;
  else
    trace_axis_limits = [0 3 -150 50];
  end

  if ~ isempty(trace_d100)
    a_d100_plot = ...
	superposePlots(plotData(trace_d100, '', ...
				mergeStructs(props, struct('axisLimits', trace_axis_limits, ...
							   'timeScale', 's'))), ...
		       {}, '+100 pA');
  else
    a_d100_plot = plot_abstract;
  end

  if ~ isempty(trace_h100)
    a_h100_plot = ...
	superposePlots(plotData(trace_h100, '', ...
				mergeStructs(props, struct('axisLimits', trace_axis_limits, ...
							   'timeScale', 's'))), ...
		       {}, '-100 pA');
  else
    a_h100_plot = plot_abstract;
  end
  
  % remove legends
  a_d100_plot.legend = {};
  a_h100_plot.legend = {};

  plot_title = short_caption;
  trace_plot = ...
      plot_stack([a_d100_plot, a_h100_plot], ...
		 trace_axis_limits, 'x', plot_title, ...
		 struct('xLabelsPos', 'bottom', 'yLabelsPos', 'left', ...
			'yTicksPos', 'left', 'PaperPosition', [0 0 4 3]));
end

function [trace_d100, trace_h100, trace_id, short_caption, caption] = traceData
  trace_d100 = ctFromRows(a_bundle, an_index, 100, props);
  trace_h100 = ctFromRows(a_bundle, an_index, -100, props);
  trace_id = getNeuronLabel(a_bundle, an_index);
  
  short_caption = ['Raw traces of ' trace_id '.' ];

  % If specified, only include desired number of the available phys. traces
  % Mostly to allow showing only one trace, to avoid cluttered displays.
  if isfield(props, 'numTraces')
    if iscell(props.numTraces)
      % Take traces specified in the numTraces cell array
      trace_d100 = trace_d100(props.numTraces{1});
      trace_h100 = trace_h100(props.numTraces{2});
      caption = [ short_caption ...
		 ' Only ' num2str(props.numTraces{1}) ' of ' num2str(length(trace_d100)) ...
		 ' for +100pA and ' num2str(props.numTraces{1}) ' of ' ...
		 num2str(length(trace_h100)) ' for -100pA raw traces are shown.' ];
    else
      trace_d100 = trace_d100(1:max(1, min(length(trace_d100), props.numTraces)));
      trace_h100 = trace_h100(1:max(1, min(length(trace_h100), props.numTraces)));
      caption = [ short_caption ...
		 ' Only ' props.numTraces ' out of available ' length(trace_d100) ...
		 ' for +100pA and ' length(trace_h100) ...
		 ' for -100pA raw traces are shown.' ];
    end
  else
    caption = [ short_caption ...
	       ' All available raw traces from the neuron are shown.' ];
  end
end

end

% The following functions are not nested functions:

function [spont_sshape_plot, pulse_sshape_plot, both_sshape_plots] = ...
      spikeShapePlots(plot_title, trace_d100, props)
  if isfield(props, 'sshapeAxisLimits')
    sshape_axis_limits = props.sshapeAxisLimits;
  else
    sshape_axis_limits = [0 20 -100 50];
  end
  
  if isfield(props, 'sshapeResults') && props.sshapeResults == 0
    plot_func = 'plotData'
  else
    plot_func = 'plotResults'
  end
  
  spont_sshape_plot = ...
      feval(plot_func, get2ndSpike(trace_d100(1), @periodIniSpont), '2nd spont. spike', ...
	    mergeStructs(props, struct('axisLimits', sshape_axis_limits)));
  pulse_sshape_plot = ...
      feval(plot_func, get2ndSpike(trace_d100(1), @periodPulse), '2nd pulse spike', ...
	    mergeStructs(props, struct('axisLimits', sshape_axis_limits)));
  both_sshape_plots = ...
      plot_stack([spont_sshape_plot, pulse_sshape_plot], ...
		 sshape_axis_limits, 'x', plot_title, ...
		 struct('yLabelsPos', 'left', 'yTicksPos', 'left'));
end

function a_plot_doc = horizRowTraces(plots, paper_position, plot_title, caption, short_caption)
  a_plot_doc = ...
      doc_plot(plot_stack(plots, [], 'x', '', struct('PaperPosition', paper_position)), ... 
	       caption, short_caption, ...
	       struct('floatType', 'figure', 'center', 1, ...
		      'height', '.8\textheight', 'shortCaption', short_caption), ...
	       'raw trace figure');
end

function sshape = get2ndSpike(ct, period_func)
  spks = spikes(ct);
  num_spikes = ...
      length(get(withinPeriod(spks, feval(period_func, ct)), 'times'));
  if  num_spikes > 0
    sshape = setProp(getSpike(ct, spks, min(num_spikes, 2)), 'quiet', 1);
  else
    sshape = spike_shape;
  end
end

function a_trace_rate_plot = traceRatePlot(a_trace, a_trace_plot, trace_id, props)
  a_spikes_d100 = spikes(a_trace);

  if isfield(props, 'rateAxisLimits')
    rate_axis_limits = props.rateAxisLimits;
  else
    rate_axis_limits = [0 3 0 100];
  end

  a_trace_rate_plot = ...
      plot_stack({a_trace_plot, ...
		  plotFreqVsTime(a_spikes_d100, '', ...
				 mergeStructs(props, struct('axisLimits', rate_axis_limits, ...
							    'timeScale', 's')))}, ...
		 [0 3 NaN NaN], 'y', get(a_trace_plot, 'title'), ...
		 struct('titlesPos', 'none', 'xLabelsPos', 'bottom', 'xTicksPos', 'bottom', ...
			'relativeSizes', [2 1]));

end