function out = CO_FirstMin(y,minWhat,extraParam,minNotMax)
% CO_FirstMin  Time of first minimum in a given self-correlation function
%
%---INPUTS:
% y, the input time series
% minWhat, the type of correlation to minimize: either 'ac' for autocorrelation,
%           or 'mi' for automutual information. By default, 'mi' specifies the
%           'gaussian' method from the Information Dynamics Toolkit. Other
%           options can also be implemented as 'mi-kernel', 'mi-kraskov1',
%           'mi-kraskov2' (all from Information Dynamics Toolkit implementations),
%           or 'mi-hist' (histogram-based method).
% extraParam, an additional parameter required from the minWhat (e.g., Kraskov)
% minNotMax, return the max instead of min.
%
% Note that selecting 'ac' is unusual operation: standard operations are the
% first zero-crossing of the autocorrelation (as in CO_FirstCrossing), or the first
% minimum of the mutual information function ('mi').

% ------------------------------------------------------------------------------
% Copyright (C) 2020, Ben D. Fulcher <ben.d.fulcher@gmail.com>,
% <http://www.benfulcher.com>
%
% If you use this code for your research, please cite the following two papers:
%
% (1) B.D. Fulcher and N.S. Jones, "hctsa: A Computational Framework for Automated
% Time-Series Phenotyping Using Massive Feature Extraction, Cell Systems 5: 527 (2017).
% DOI: 10.1016/j.cels.2017.10.001
%
% (2) B.D. Fulcher, M.A. Little, N.S. Jones, "Highly comparative time-series
% analysis: the empirical structure of time series and their methods",
% J. Roy. Soc. Interface 10(83) 20130048 (2013).
% DOI: 10.1098/rsif.2013.0048
%
% This function is free software: you can redistribute it and/or modify it under
% the terms of the GNU General Public License as published by the Free Software
% Foundation, either version 3 of the License, or (at your option) any later
% version.
%
% This program is distributed in the hope that it will be useful, but WITHOUT
% ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
% FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
% details.
%
% You should have received a copy of the GNU General Public License along with
% this program. If not, see <http://www.gnu.org/licenses/>.
% ------------------------------------------------------------------------------

% ------------------------------------------------------------------------------
% Check inputs:
% ------------------------------------------------------------------------------
if nargin < 2 || isempty(minWhat)
    % Mutual information using gaussian method from Information Dynamics Toolkit:
    minWhat = 'mi-gaussian';
end
if nargin < 3
    extraParam = [];
end
if nargin < 4
    minNotMax = true;
end

N = length(y); % Time-series length

% ------------------------------------------------------------------------------
% Define the autocorrelation function
% ------------------------------------------------------------------------------
switch minWhat
case {'mi','mi-gaussian'} % default method (using Information Dynamics Toolkit)
    corrfn = @(x) IN_AutoMutualInfo(y,x,'gaussian');
case 'mi-kernel' % (using Information Dynamics Toolkit)
    corrfn = @(x) IN_AutoMutualInfo(y,x,'kernel');
case 'mi-kraskov1' % (using Information Dynamics Toolkit)
    corrfn = @(x) IN_AutoMutualInfo(y,x,'kraskov1');
case 'mi-kraskov2' % (using Information Dynamics Toolkit)
    % extraParam is the number of nearest neighbors:
    corrfn = @(x) IN_AutoMutualInfo(y,x,'kraskov2',extraParam);
case 'mi-hist'
    % Automutual information implemented in super-naive box counting as in BF_MutualInformation
    corrfn = @(x) BF_MutualInformation(y(1:end-x), y(1+x:end), 'range', 'range',extraParam);
case {'ac','corr'}
    % Autocorrelation implemented as CO_AutoCorr
    corrfn = @(x) CO_AutoCorr(y,x,'Fourier');
otherwise
    error('Unknown correlation type specified: ''%s''',minWhat);
end

% ------------------------------------------------------------------------------
% Search for a minimum
% ------------------------------------------------------------------------------
% (Incrementally through time lags until a minimum is found)

autoCorr = zeros(N-1,1); % pre-allocate maximum length autocorrelation vector
if minNotMax
    %---------------------------------------------------------------------------
    % FIRST LOCAL MINUMUM
    %---------------------------------------------------------------------------
    for i = 1:N-1
        % Calculate the auto-correlation at this lag:
        autoCorr(i) = corrfn(i);

        % Hit a NaN before got to a minimum -- there is no minimum
        if isnan(autoCorr(i))
            warning('No minimum in %s [[time series too short to find it?]]',minWhat)
            out = NaN;
            return
        end

        % We're at a minimum:
        if i==2 && (autoCorr(2) > autoCorr(1))
            % already increases at lag of 2 from lag of 1: a minimum (since ac(0) is maximal)
            out = 1;
            return
        elseif (i > 2) && (autoCorr(i-2) > autoCorr(i-1)) && (autoCorr(i-1) < autoCorr(i)); % minimum at previous i
            out = i-1; % I found the first minimum!
            return
        end
    end
else
    %---------------------------------------------------------------------------
    % FIRST LOCAL MAXIMUM
    %---------------------------------------------------------------------------
    for i = 1:N-1
        % Calculate the auto-correlation at this lag:
        autoCorr(i) = corrfn(i);

        % Hit a NaN before got to a max -- there is no max
        if isnan(autoCorr(i))
            warning('No maximum in %s [[time series too short to find it?]]',minWhat)
            out = NaN;
            return
        end

        % We're at a local maximum:
        if (i > 2) && (autoCorr(i-2) < autoCorr(i-1)) && (autoCorr(i-1) > autoCorr(i)); % minimum at previous i
            out = i-1; % I found the first maximum!
            return
        end
    end
end

% Still decreasing -- no minimum was found after searching all across the time series:
out = N;

end
