% SPLIT split an analysis object into the specified segments.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DESCRIPTION: SPLIT split an analysis object into the specified segments.
%
% CALL:        b = split(a, pl)
%
% INPUTS:      a  - input analysis object
%              pl - input parameter list (see below for parameters)
%
% OUTPUTS:     b  - array of analysis objects
%
%
% EXAMPLES:    1.) Split method by frequency. Get the values from 10-100 Hz
%                  pl = plist('frequencies', [10 100]);
%                  ao_new = split(a1, pl);
%
%              2.) Split method by time.
%                  Get the values from 0.0 to 1.0 Seconds AND from 1.0 to 2.5 seconds
%                  pl = plist('times', [0.0 1.0 1.0 2.5]);
%                  ao_new = split(a1, pl);
%
%              3.) Split method by samples.
%                  Get the samples from 1 to 50 AND from 150 to 200.
%                  pl = plist('samples',     [1 50 150 200]);
%                  ao_new = split(a1, pl);
%
%              4.1) Select an interval with strings
%                   --> t0 = time('14:00:00')
%                   pl = plist('start_time', '14:00:01', ...
%                              'end_time',   '14:00:02');
%                   ao_new = split(a1, pl);
%
%                   --> t0 = time('14:00:00')
%                   pl = plist('start_time', '14:00:01', ...
%                              'duration',   '00:00:02');
%                   ao_new = split(a1, pl);
%
%                   Select an interval with seconds
%                   --> t0 = time(3)
%                   pl = plist('start_time', 5, ...
%                              'end_time',   7);
%                   ao_new = split(a1, pl);
%
%              4.2) Select an interval with time objects
%                   --> t0 = time('14:00:00')
%                   pl = plist('start_time', time('14:00:01'), ...
%                              'end_time',   time('14:00:03'));
%                   ao_new = split(a1, pl);
%
%                   --> t0 = time(3)
%                   pl = plist('start_time', time(5), ...
%                              'duration',   time(2));
%                   ao_new = split(a1, pl);
%
%              4.3) Select an interval with a time span object
%                   --> t0 = time('14:00:00')
%                   pl = plist('timespan', timespan('14:00:00', '14:00:05'));
%                   ao_new = split(a1, pl);
%
% <a href="matlab:utils.helper.displayMethodInfo('ao', 'split')">Parameters Description</a>
%
% VERSION:     $Id: split.m,v 1.112 2011/10/05 15:43:36 ingo Exp $
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function varargout = split(varargin)
  
  callerIsMethod = utils.helper.callerIsMethod;
  
  %%% Check if this is a call for parameters
  if utils.helper.isinfocall(varargin{:})
    varargout{1} = getInfo(varargin{3});
    return
  end
  
  import utils.const.*
  utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename);
  
  if nargout == 0
    error('### split cannot be used as a modifier. Please give an output variable.');
  end
  
  % Collect input variable names
  in_names = cell(size(varargin));
  for ii = 1:nargin,in_names{ii} = inputname(ii);end
  
  % Collect all AOs
  [as, ao_invars] = utils.helper.collect_objects(varargin(:), 'ao', in_names);
  pli             = utils.helper.collect_objects(varargin(:), 'plist', in_names);
  
  % copy input plist
  pl = combine(pli, plist);
  % combine input plists (if the input plists are more than one)
  pl = parse(pl);
  
  % Unpack parameter list
  split_type = find(pl, 'split_type');
  
  % Set 'split_type' if some other key-word is set.
  if pl.isparam('samples')
    split_type = 'samples';
  elseif pl.isparam('times') || pl.isparam('frequencies')
    split_type = 'times';
  elseif pl.isparam('chunks') || pl.isparam('N')
    split_type = 'chunks';
  elseif pl.isparam('start_time') || pl.isparam('timespan')
    split_type = 'interval';
  end
  
  if isempty(split_type)
    error('### please specify the key ''split_type'' in the parameter list');
  end
  
  %%% go through analysis objects
  bo = [];
  inhists = [];
  
  for jj=1:numel(as)
    
    % gather the input history objects
    inhists = [inhists as(jj).hist];
    
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %                       splitting by time or frequency                        %
    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    switch lower(split_type)
      case {'times', 'frequencies'}
        
        times       = find(pl, 'times');
        frequencies = find(pl, 'frequencies');
        
        if ~isempty(times)
          utils.helper.msg(msg.PROC1, 'splitting by time');
          split_x_axis.type  = 'times';
          split_x_axis.value =  times;
          if ~(isa(as(jj).data, 'tsdata') || isa(as(jj).data, 'xydata'))
            error('### I can only split time-series by times');
          end
        else
          utils.helper.msg(msg.PROC1, 'splitting by frequency');
          split_x_axis.type  = 'frequencies';
          split_x_axis.value =  frequencies;
          if ~isa(as(jj).data, 'fsdata')
            error('### I can only split frequency-series by frequencies');
          end
        end
        
        % examine time list
        ntimes = length(split_x_axis.value);
        if mod(ntimes, 2) ~= 0
          error('### please specify a start and stop for each interval.')
        end
        % go over each interval now
        for ii=1:2:ntimes
          is = split_x_axis.value(ii);
          ie = split_x_axis.value(ii+1);
          ish = is; % Backup the start time for the history
          ieh = ie; % Backup the end time for the history
          
          if ie < 0 % indicates count from end
            if isa(as(jj).data, 'tsdata')
              ie = as(jj).data.nsecs + as(jj).toffset + ie;
            else
              ie = as(jj).x(end) + ie;
            end
            if ie < is
              error('### End time is before the start time.');
            end
          elseif ie == 0 % Go to end of vector
            % x(end) is to small because the find command compares only to
            % 'less' and not to 'less or equal'
            ie = as(jj).x(end)+1/as(jj).fs;
          else
            ie = ie;
          end
          
          % copy the data-object because we change the values.
          d = copy(as(jj).data, nargout);
          
          % create index of the interval
          idx = as(jj).x >= is & as(jj).x < ie;
          
          % set output data
          if isempty(as(jj).data.x)
            % set t0 rounding at a multiplier of the sampling interval
            if is < as(jj).toffset
              % Don't change the toffset because the start time is smaller
              % than the toffset and this means that we collect all data
              % from the begin of the samples.
            else
              d.setToffset((ceil(is*d.fs)/d.fs)*1e3);
            end
          else
            d.setX(as(jj).x(idx));
          end
          d.setY(as(jj).data.y(idx));
          if numel(as(jj).data.dx) > 1
            d.setDx(as(jj).data.dx(idx));
          end
          if numel(as(jj).data.dy) > 1
            d.setDy(as(jj).data.dy(idx));
          end
          if isprop(as(jj).data, 'enbw')
            if numel(as(jj).data.enbw) > 1
              d.setEnbw(as(jj).data.enbw(idx));
            end
          end
          
          % Set nsecs for tsdata
          if isa(d, 'tsdata')
            d.collapseX;
            if ~isempty(d.x)
              d.setNsecs(d.x(end) - d.x(1) + 1/d.fs);
            else
              d.setNsecs(length(d.y)/d.fs);
            end
          end
          
          % Copy input AO
          b = copy(as(jj), nargout);
          b.data = d;
          
          if ~callerIsMethod
            % create new output history
            b.addHistory(getInfo('None'), pl.pset(split_x_axis.type, [ish ieh]), ao_invars(jj), inhists(jj));
            % set name
            b.name = sprintf('split(%s)', ao_invars{jj});
          end
          % Add to output array
          bo = [bo b];
        end
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %                            splitting by samples                             %
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      case 'samples'
        
        utils.helper.msg(msg.PROC1, 'splitting by samples');
        
        % examine time list
        samples = find(pl, 'samples');
        npairs  = length(samples);
        if mod(npairs, 2) ~= 0
          error('### please specify a start and stop for each interval.')
        end
        
        % check data
        if isa(as(jj).data, 'data2D') && length(as(jj).x) ~= length(as(jj).y)
          error('### Something is wrong with the x/y vectors. I can''t split this data.');
        end
        
        % go over each interval now
        for ii=1:2:npairs
          is = samples(ii);
          ie = samples(ii+1);
          
          utils.helper.msg(msg.PROC1, sprintf('Split: %03d [%d..%d]', (ii+1)/2, is, ie));
          
          % copy the data object.
          d = copy(as(jj).data, nargout);
          if is > length(d.getY)
            idx = [];
          else
            idx = is:min(ie, length(d.getY));
          end
          if isa(d, 'cdata')
            d.setY(d.getY(idx));
            if numel(d.getDy) > 1
              d.setDy(d.getDy(idx));
            end
          else
            % set new samples
            
            d.setXY(d.getX(idx), d.getY(idx));
            % set 'dx' and 'dy' and 'enbw'
            if numel(d.getDx) > 1
              d.setDx(d.getDx(idx));
            end
            if numel(d.getDy) > 1
              d.setDy(d.getDy(idx));
            end
            if isprop(d, 'enbw')
              if numel(d.enbw) > 1
                d.setEnbw(d.enbw(idx));
              end
            end
            % if this is tsdata, we can collapse it again, maybe
            if isa(d, 'tsdata')
              [fs,t0,fitted] = tsdata.fitfs(d.x);
              if ~fitted
                d.collapseX();
              end
            end
          end
          
          % Copy input AO
          b = copy(as(jj), nargout);
          b.data = d;
          
          if ~callerIsMethod
            % create new output history
            b.addHistory(getInfo('None'), pl.pset('samples', [is ie]), ao_invars(jj), inhists(jj));
            % set name
            b.name = sprintf('split(%s)[%d]', ao_invars{jj},(ii+1)/2);
          end
          % Add to output array
          bo = [bo b];
        end
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %                            splitting into chunks                            %
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      case 'chunks'
        
        pl = combine(pl, getDefaultPlist('By chunks'));
        N = find(pl, 'N');
        if isempty(N)
          N = pl.find('chunks');
        end
        match = pl.find('match');
        utils.helper.msg(msg.PROC1, 'splitting into %d chunks', N);
        
        y = as(jj).y;
        
        % chunk size
        csize = floor(length(y)/N);
        % generate list of indices
        is = 1:csize:length(y);
        ie = csize:csize:length(y);
        
        idx = sort([is(1:N) ie(1:N)]);
        
        if match == true
          idx(end) = length(y);
        end
        
        % one call to split with these samples
        b = ltpda_run_method(@split, as(jj), plist('split_type', 'samples', 'samples', idx));
        
        % Add to output array
        bo = [bo b];
        
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %                           splitting into interval                           %
        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      case 'interval'
        
        %%% Skip an AO if the data is not a time-series object
        if ~isa(as(jj).data, 'tsdata')
          continue
        end
        
        %%% get values from the parameter list
        duration   = find(pl, 'duration');
        start_time = find(pl, 'start_time');
        end_time   = find(pl, 'stop_time', find(pl, 'end_time'));
        time_span  = find(pl, 'timespan');
        
        %%% Some checks
        if (~isempty(start_time) || ~isempty(end_time)) && ~isempty(time_span)
          error('### Please specify only a timespan and not additionally the start/end time');
        end
        
        if isa(time_span, 'history')
          % The timespan object may have been replaced with its history in
          % the previous loop exection in the call to ao/addHistory
          time_span = rebuild(time_span);
          pl.pset('timespan', time_span);
        end
        
        %%% Convert the start_time into a time object
        if ~isempty(start_time) && ~isa(start_time, 'time')
          start_time = time(start_time);
        end
        
        %%% Convert the end_time into a time object
        if ~isempty(end_time) && ~isa(end_time, 'time')
          end_time = time(end_time);
        end
        
        %%% Convert the duration
        if ~isempty(end_time) && ~isempty(duration)
          error('### Please specify only a duration or an end time');
        end
        if ~isempty(duration)
          duration = time(duration);
          end_time = start_time + duration;
          end_time = time(end_time);
        end
        
        %%% Set start/end time with a timespan object
        
        if ~isempty(time_span)
          if ~isa(time_span, 'timespan')
            error('### The timespan must be a timespan object')
          end
          if ~isempty(start_time) || ~isempty(end_time)
            error('### Please specify only a timespan OR a start/end time');
          end
          
          start_time = time_span.startT;
          end_time   = time_span.endT;
        end
        
        t0_time = as(jj).data.t0;
        
        %%% Compute the start/end time
        ts = double(start_time) - double(t0_time);
        te = double(end_time)   - double(t0_time);
        
        idx = as(jj).x >= ts & as(jj).x < te;
        
        %%% create new output data
        d = copy(as(jj).data, nargout);
        
        % set output data
        if isempty(d.x)
          % set t0 rounding at a multiplier of the sampling interval
          if any(ts < d.toffset/1e3)
            % Don't change the toffset because the start time is smaller
            % than the toffset and this means that we collect all data from
            % the begin of the samples.
          else
            % d.setToffset(d.toffset + (ceil(ts*d.fs)/d.fs)*1e3);
            d.setToffset((ceil(ts*d.fs)/d.fs)*1e3);
          end
        else
          d.setX(d.getX(idx));
        end
        d.setY(d.y(idx));
        
        if (numel(d.getDx) > 1)
          d.setDx(d.getDx(idx));
        end
        if (numel(d.getDy) > 1)
          d.setDy(d.getDy(idx));
        end
        if isprop(d, 'enbw')
          if numel(d.enbw) > 1
            d.setEnbw(d.enbw(idx));
          end
        end
        
        % Set nsecs for tsdata
        if isa(d, 'tsdata')
          d.collapseX;
          if ~isempty(d.x)
            d.setNsecs(d.x(end) - d.x(1) + 1/d.fs);
          else
            d.setNsecs(length(d.y)/d.fs);
          end
        end
        
        % Copy input AO
        b = copy(as(jj), nargout);
        b.data = d;
        
        if ~callerIsMethod
          % create new output history
          b.addHistory(getInfo('None'), pl, ao_invars(jj), inhists(jj));
          % set name
          b.name = sprintf('split(%s)', ao_invars{jj});
        end
        % Add to output array
        bo = [bo b];
      otherwise
        error('### Unknown split type %s', split_type);
        
    end % switch lower(split_type)
    
  end % for jj = 1:numel(as)
  
  % Set output
  if nargout == numel(bo)
    % List of outputs
    for ii = 1:numel(bo)
      varargout{ii} = bo(ii);
    end
  else
    % Single output
    varargout{1} = bo;
  end
end

%--------------------------------------------------------------------------
% Get Info Object
%--------------------------------------------------------------------------

function ii = getInfo(varargin)
  if nargin == 1 && strcmpi(varargin{1}, 'None')
    sets = {};
    pls   = [];
  elseif nargin == 1 && ~isempty(varargin{1}) && ischar(varargin{1})
    sets{1} = varargin{1};
    pls = getDefaultPlist(sets{1});
  else
    sets = {...
      'Default', ...
      'By Times', ...
      'By Frequencies', ...
      'By Samples', ...
      'By Chunks', ...
      'By Interval Start/End', ...
      'By Interval Start/Duration', ...
      'By Interval Timespan'};
    pls = [];
    for kk=1:numel(sets)
      pls = [pls getDefaultPlist(sets{kk})];
    end
  end
  % Build info object
  ii = minfo(mfilename, 'ao', 'ltpda', utils.const.categories.sigproc, '$Id: split.m,v 1.112 2011/10/05 15:43:36 ingo Exp $', sets, pls);
  ii.setModifier(false);
end

%--------------------------------------------------------------------------
% Get Default Plist
%--------------------------------------------------------------------------
function plout = getDefaultPlist(set)
  persistent pl;
  persistent lastset;
  if ~exist('pl', 'var') || isempty(pl) || ~strcmp(lastset, set)
    pl = buildplist(set);
    lastset = set;
  end
  plout = pl;
end

function pl = buildplist(set)
  
  pl = plist();
  
  switch lower(set)
    case 'default'
      
      pl = getDefaultPlist('by times');
      
    case 'by times'
      % Times
      p = param({'times',['Split the ao into time segments.<br>' ....
        'An array of start/stop times to split by. A negative stop time is ',...
        'taken from the end of the vector, e.g., [10 -10] removes 10 seconds ',...
        'from the beginning and end of the vector. An end time of 0 indicates ',...
        'the end of the vector.']}, paramValue.EMPTY_DOUBLE);
      pl.append(p);
      
    case 'by frequencies'
      
      % Frequencies
      p = param({'frequencies','An array of start/stop frequencies to split by.'}, paramValue.EMPTY_DOUBLE);
      pl.append(p);
      
    case 'by samples'
      
      % samples
      p = param({'samples','An array of start/stop samples to split by.'}, paramValue.EMPTY_DOUBLE);
      pl.append(p);
      
    case 'by chunks'
      
      % N
      p = param({'N','Split into N contiguous pieces.'}, paramValue.EMPTY_DOUBLE);
      pl.append(p);
      
      % match
      p = param({'match','Define if the last chunk should keep any remaining data samples.'}, paramValue.TRUE_FALSE);
      pl.append(p);
      
    case 'by interval start/end'
      
      % start_time
      p = param({'start_time','Start time can be either a string or a time object.'}, {1, {time(0)}, paramValue.OPTIONAL});
      pl.append(p);
      
      % end_time
      p = param({'end_time','End time can be either a string or a time object.'}, {1, {time(0)}, paramValue.OPTIONAL});
      pl.append(p);
      
    case 'by interval start/duration'
      
      % start_time
      p = param({'start_time','Start time can be either a string or a time object.'}, {1, {time(0)}, paramValue.OPTIONAL});
      pl.append(p);
      
      % duration
      p = param({'duration','Duration can be either a string or a time object.'}, {1, {time(0)}, paramValue.OPTIONAL});
      pl.append(p);
      
    case 'by interval timespan'
      
      % timespan
      p = param({'timespan','The start/end time are specified in the time span object.'}, {1, {timespan(0,0)}, paramValue.OPTIONAL});
      pl.append(p);
      
    otherwise
      error('### Unknown parameter set [%s].', set);
  end
  
end


% PARAMETERS: <key>         <value>        <description>
%             'split_type'  'times'        split the ao into time segments
%                           'frequencies'  split the ao into frequencies segments
%                           'samples'      split the ao into sample segments
%                           'chunks'       split the ao into chunks
%                           'interval'     select a duration of a tsdata
%
% Necessary for the individual split types:
%             <split type>  <key>         <description>
%             'times'       'times'       an array of start/stop times to
%                                         split by. A negative stop time is
%                                         taken from the end of the vector,
%                                         e.g., [10 -10] removes 10 seconds
%                                         from the beginning and end of the
%                                         vector. An end time of 0
%                                         indicates the end of the vector.
%             -------------------------------------------------------------
%             'frequencies' 'frequencies' an array of start/stop
%                                         frequencies to split by
%             -------------------------------------------------------------
%             'samples'     'samples'     an array of start/stop samples to
%                                         split by
%             -------------------------------------------------------------
%             'chunks'      'N'           split into N contiguous pieces
%                           'match'       define if the last chunk should
%                                         keep the remaining data samples
%                                         which couldn't split in all chunks
%                                         default [true]
%             -------------------------------------------------------------
%             'interval'    'start_time', 'end_time'
%                             start/stop time can be either a string or a
%                             time object
%             'interval'    'start_time', 'duration'
%                             start time and the duration can be either a
%                             string or a time object
%             'interval'    'timespan'
%                             the start/end time are specified in the time
%                             span object.
%             -------------------------------------------------------------
%              The UTC time format is: 'yyyy-mm-dd HH:MM:SS'
%
%              If more than one splitting method is specified, the priority
%              goes like the list above.
%
%              The time vector in the output AO retains the original
%              time values (i.e. it doesn't start from zero).
%
%              The splitting is done as  s<=t<e.
%
%              Arrays of start/stop values should be like: [s1 e1 s2 e2
%              ....]
