% STD_PAC - Compute or read PAC data (Phase Amplitude Coupling).
%
% Usage:  
%   >> [X times logfreqs ] = std_pac(EEG, 'key', 'val', ...);
% Inputs:
%   EEG          - an EEG dataset structure. 
%
% Optional inputs:
%   'components1'- [numeric vector] components in the EEG structure used 
%                  for spectral amplitude in PAC {default|[]: all }
%   'components2'- [numeric vector] components in the EEG structure used
%                  for phase in PAC {default|[]: all }
%   'channels1'  - [numeric vector or cell array of channel labels] channels 
%                  in the EEG structure for spectral amplitude in PAC 
%                  {default|[]: no channels}
%   'channels2'  - [numeric vector or cell array of channel labels] channels 
%                  in the EEG structure for phase in PAC 
%                  {default|[]: no channels}
%   'freqs'      - [minHz maxHz] the PAC frequency range to compute power.
%                  {default: 12 to EEG sampling rate divided by 2}
%   'cycles'     - [wavecycles (factor)]. If 0 -> DFT (constant window length 
%                  across frequencies).
%                  If >0 -> the number of cycles in each analysis wavelet. 
%                  If [wavecycles factor], wavelet cycles increase with 
%                  frequency, beginning at wavecyles. (0 < factor < 1) 
%                  factor = 0 -> fixed epoch length (DFT, as in FFT). 
%                  factor = 1 -> no increase (standard wavelets)
%                  {default: [0]}
%   'freqphase'  - [valHz] single number for computing the phase at a given
%                  frequency.
%   'cyclephase' - [valcycle] single cycle number.
%   'timewindow' - [minms maxms] time window (in ms) to plot.
%                  {default: all output latencies}
%   'padratio'   - (power of 2). Multiply the number of output frequencies 
%                  by dividing their frequency spacing through 0-padding.
%                  Output frequency spacing is (low_freq/padratio).
%   'recompute'  - ['on'|'off'] 'on' forces recomputation of PAC. 
%                  {default: 'off'}
%
% Other optional inputs:
%   This function will take any of the NEWTIMEF optional inputs (for instance
%   to compute log-space frequencies)...
%
% Outputs:
%   X         - the PAC of the requested ICA components/channels 
%               in the selected frequency and time range. 
%   times     - vector of time points for which the PAC were computed. 
%   freqs     - vector of frequencies (in Hz) at which the 
%               PAC was evaluated. 
%
% Files written or modified:     
%              [dataset_filename].icapac   <-- saved component PAC
% OR for channels
%              [dataset_filename].datpac   <-- saved channel PAC
%
% See also: TIMEF, STD_ITC, STD_ERP, STD_SPEC, STD_TOPO, STD_PRECLUST
%
% Authors: Arnaud Delorme, SCCN, INC, UCSD, July, 2009-

% Copyright (C) Arnaud Delorme, SCCN, INC, UCSD, arno@sccn.ucsd.edu
%
% This file is part of EEGLAB, see http://www.eeglab.org
% for the documentation and details.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are met:
%
% 1. Redistributions of source code must retain the above copyright notice,
% this list of conditions and the following disclaimer.
%
% 2. Redistributions in binary form must reproduce the above copyright notice,
% this list of conditions and the following disclaimer in the documentation
% and/or other materials provided with the distribution.
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
% THE POSSIBILITY OF SUCH DAMAGE.

function [X, times, freqs, parameters] = std_pac(EEG, varargin)

if nargin < 1
    help std_pac;
    return;
end

options = {};
[g timefargs] = finputcheck(varargin, { ...
                        'components1'    'integer'     []      [];
                        'channels1'      { 'cell','integer' }  { [],[] }     {};
                        'components2'    'integer'     []      [];
                        'channels2'      { 'cell','integer' }  { [],[] }     {};
                        'outputfile'    'string'      []      '';
                        'powbase'       'real'        []      [];
                        'plot'          'string'      { 'on','off' }      'off';
                        'recompute'     'string'      { 'on','off' }      'off';
                        'getparams'     'string'      { 'on','off' }      'off';
                        'timerange'     'real'        []      []; 
                        'freqrange'     'real'        []      [];
                        'padratio'      'real'        []      1;
                        'freqs'         'real'        []      [12 EEG.srate/2];
                        'cycles'        'real'        []      [8];
                        'freqphase'     'real'        []      [5];
                        'cyclephase'    'real'        []      [3];
                        'interp'        'struct'      { }     struct([]);
                        'rmcomps'       'integer'     []      [];
                        'freqscale'     'string'      []      'log' }, 'std_pac', 'ignore');
if ischar(g), error(g); end

% checking input parameters
% -------------------------
if isempty(g.components1) && isempty(g.channels1)
    if isempty(EEG(1).icaweights)
        error('EEG.icaweights not found');
    end
    g.components1 = 1:size(EEG(1).icaweights,1);
    g.components2 = 1:size(EEG(1).icaweights,1);
    disp('Computing PAC with default values for all components of the dataset');
end

% select ICA components or data channels
% --------------------------------------
if ~isempty(g.outputfile)
    filenamepac   = fullfile('', [ g.outputfile '.datpac' ]);
    g.indices1 = std_chaninds(EEG, g.channels1);
    g.indices2 = std_chaninds(EEG, g.channels2);
    prefix = 'chan';
elseif ~isempty(g.components1)
    g.indices1 = g.components1;
    g.indices2 = g.components2;
    prefix = 'comp';
    filenamepac   = fullfile(EEG.filepath, [ EEG.filename(1:end-3) 'icapac' ]);
    if ~isempty(g.channels1)
        error('Cannot compute PAC for components and channels at the same time');
    end
elseif ~isempty(g.channels1)
    g.indices1 = std_chaninds(EEG, g.channels1);
    g.indices2 = std_chaninds(EEG, g.channels2);
    prefix = 'chan';
    filenamepac   = fullfile(EEG.filepath, [ EEG.filename(1:end-3) 'datpac' ]);
end

% Compute PAC parameters
% -----------------------
parameters = { 'wavelet', g.cycles, 'padratio', g.padratio, ...
               'freqs2', g.freqphase, 'wavelet2', g.cyclephase, 'freqscale', g.freqscale, timefargs{:} };
if length(g.freqs)>0, parameters = { parameters{:} 'freqs' g.freqs }; end

% Check if PAC information found in datasets and if fits requested parameters 
% ----------------------------------------------------------------------------
if exist( filenamepac ) && strcmpi(g.recompute, 'off')
    fprintf('Use existing file for PAC: %s\n', filenamepac);
    if ~isempty(g.components1)
         [X, times, freqs, parameters] = std_readpac(EEG, 1,  g.indices1,  g.indices2, g.timerange, g.freqrange);
    else [X, times, freqs, parameters] = std_readpac(EEG, 1, -g.indices1, -g.indices2, g.timerange, g.freqrange);
    end
    return;
end

% return parameters
% -----------------
if strcmpi(g.getparams, 'on')
    X = []; times = []; freqs = [];
    return;
end

options = {};
if ~isempty(g.components1)
    tmpdata = eeg_getdatact(EEG, 'component', [1:size(EEG(1).icaweights,1)]);
else
    EEG.data = eeg_getdatact(EEG, 'channel', [1:EEG.nbchan], 'rmcomps', g.rmcomps);
    if ~isempty(g.rmcomps), options = { options{:} 'rmcomps' g.rmcomps }; end
    if ~isempty(g.interp), 
        EEG = eeg_interp(EEG, g.interp, 'spherical'); 
        options = { options{:} 'interp' g.interp };
    end
    tmpdata = EEG.data;
end;        

% Compute PAC
% -----------
all_pac = [];
for k = 1:length(g.indices1)  % for each (specified) component
    for l = 1:length(g.indices2)  % for each (specified) component
        tmpparams = parameters;

        % Run pac() to get PAC
        % --------------------
        timefdata1  = tmpdata(g.indices1(k),:,:);
        timefdata2  = tmpdata(g.indices2(l),:,:);
        if strcmpi(g.plot, 'on'), figure; end
        %[logersp,logitc,logbase,times,logfreqs,logeboot,logiboot,alltfX] ...
        [pacvals, times, freqs1, freqs2] = pac( timefdata1, timefdata2, EEG(1).srate, 'tlimits', [EEG.xmin EEG.xmax]*1000, tmpparams{1:end});
        all_pac = setfield( all_pac, [ prefix int2str(g.indices1(k)) '_' int2str(g.indices2(l)) '_pac' ], squeeze(single(pacvals )));
    end
end

% Save PAC into file
% ------------------
all_pac.freqs      = freqs1;
all_pac.times      = times;
all_pac.datatype   = 'PAC';
all_pac.parameters = tmpparams;

if ~isempty(g.channels1)
    if ~isempty(EEG(1).chanlocs)
        tmpchanlocs = EEG(1).chanlocs;
        all_pac.chanlabels1   = { tmpchanlocs(g.indices1).labels };
        all_pac.chanlabels2   = { tmpchanlocs(g.indices2).labels };
    end
end

std_savedat( filenamepac , all_pac );
if ~isempty(g.components1)
     [X, times, freqs, parameters] = std_readpac(EEG, 1,  g.indices1,  g.indices2, g.timerange, g.freqrange);
else [X, times, freqs, parameters] = std_readpac(EEG, 1, -g.indices1, -g.indices2, g.timerange, g.freqrange);
end

% --------------------------------------------------------
% -------------------- READ PAC DATA ---------------------
% --------------------------------------------------------
function [pacvals, freqs, timevals, params] = std_readpac(ALLEEG, abset, comp1, comp2, timewindow, freqrange);

if nargin < 5
    timewindow = [];
end
if nargin < 6
    freqrange = [];
end

% multiple entry
% --------------
if length(comp1) > 1 || length(comp2) > 1
    for index1 = 1:length(comp1)
        for index2 = 1:length(comp2)
            [tmppac, freqs, timevals, params] = std_readpac(ALLEEG, abset, comp1(index1), comp2(index2), timewindow, freqrange);
            pacvals(index1,index2,:,:,:) = tmppac;
        end
    end
    return;
end

for k = 1: length(abset)    
    
    if comp1 < 0
        filename = fullfile( ALLEEG(abset(k)).filepath,[ ALLEEG(abset(k)).filename(1:end-3) 'datpac']);
        comp1   = -comp1;
        comp2   = -comp2;
        prefix = 'chan';
    else    
        filename = fullfile( ALLEEG(abset(k)).filepath,[ ALLEEG(abset(k)).filename(1:end-3) 'icapac']);
        prefix = 'comp';
    end
    try
        tmppac = load( '-mat', filename, 'parameters', 'times', 'freqs');
    catch
        error( [ 'Cannot read file ''' filename '''' ]);
    end
    
    tmppac.parameters = removedup(tmppac.parameters);
    params    = struct(tmppac.parameters{:});
    params.times = tmppac.times;
    params.freqs = tmppac.freqs;
    if isempty(comp1)
        pacvals    = [];
        freqs  = [];
        timevals  = [];
        return;
    end
    tmppac   = load( '-mat', filename, 'parameters', 'times', 'freqs', ...
                     [ prefix int2str(comp1) '_' int2str(comp2) '_pac']);
    
    pacall{k} = double(getfield(tmppac, [ prefix int2str(comp1) '_' int2str(comp2) '_pac']));
    tlen      = length(tmppac.times);
    flen      = length(tmppac.freqs);

end

% select plotting or clustering time/freq range
% ---------------------------------------------
if ~isempty(timewindow)
    if timewindow(1) > tmppac.times(1) || timewindow(end) < tmppac.times(end)
        maxind = max(find(tmppac.times <= timewindow(end)));
        minind = min(find(tmppac.times >= timewindow(1)));
    else
        minind = 1;
        maxind = tlen;
    end
else
    minind = 1;
    maxind = tlen;
end
if ~isempty(freqrange)
    if freqrange(1) > exp(1)^tmppac.freqs(1) || freqrange(end) < exp(1)^tmppac.freqs(end)
        fmaxind = max(find(tmppac.freqs <= freqrange(end)));
        fminind = min(find(tmppac.freqs >= freqrange(1)));
    else
        fminind = 1;
        fmaxind = flen;
    end
else
    fminind = 1;
    fmaxind = flen;
end

% return parameters
% ----------------
for cond  = 1:length(abset)
    try
        pac = pacall{cond}(fminind:fmaxind,minind:maxind);
    catch
        pac = pacall{cond}; % for 'method', 'latphase'
    end
    pacvals(:,:,cond) = pac;
end
freqs    = tmppac.freqs(fminind:fmaxind);
timevals = tmppac.times(minind:maxind);

% remove duplicates in the list of parameters
% -------------------------------------------
function cella = removedup(cella)
    [tmp indices] = unique_bc(cella(1:2:end));
    if length(tmp) ~= length(cella)/2
        %fprintf('Warning: duplicate ''key'', ''val'' parameter(s), keeping the last one(s)\n');
    end
    cella = cella(sort(union(indices*2-1, indices*2)));

