
function [phaseOut,omegaOut] = fireflySimulation(varargin)
%
% Simulate decentralised pulse-coupled oscillators synchronising both phase
% and frequency to Harmonic Synchrony. 
% Developed by Kristian Nymoen, University of Oslo, 2013.
% 
% Usage:
% - fireflySimulation
% - fireflySimulation('argument',argumentValue,'argument2',argumentValue2,...)
% - [phaseOut] = fireflySimulation('argument',argumentValue)
% - [phaseOut omegaOut] = fireflySimulation('argument',argumentValue)
%
% Arguments: 
% 'alpha': Phase coupling constant (default 0.4)
% 'beta': Frequency coupling constant (default 0.4)
% 'SimulationLength': Number of samples to run the experiment (default 60000)
% 'FilterLength': Number of samples used in the running filter (default 8)
% 'FilterType': FilterType of the running filter: 'median' (default) or 'mean'
% 'Nodes': Number of oscillators (default 5)
% 'SampleRate': (default 1000)
% 'PlotLength': Number of samples to plot (default 10000)
% 'tref': refractory interval in ms (default 1)
% 'DoublingThreshold': Number of repeated negative phase jumps without firing before frequency is doubled (default 20)
% 'PlotSpectrogram': 0 (default) or 1
% 'RandomSeed': Specify a random seed in order to analyse different parameters
% 'Animate': 0 (default) or 1
% 
% Examples: 
% fireflySimulation
%   - Starts an experiment with default values (no output...)
%
% fireflySimulation('Animate',1)
%   - Starts an experiment with default values (showing animation)
%
% fireflySimulation('Nodes',6)
%   - Starts an experiment with 6 oscillators
%
% fireflySimulation('alpha',0.2,'beta',0.1,'Nodes',4,'PlotSpectrogram',1)
%   - Starts an experiment with 4 oscillators, alpha value set to 0.2 and 
%   beta value set to 0.1. Plotting spectrogram for all nodes
%
% omegaOut = fireflySimulation('RandomSeed',534);
%   - Starts an experiment with default settings using the random seed 534.
%   The experiment results will be the same every time. The output variable
%   w contains a matrix of the frequency values over time
%
% [phaseOut,omegaOut] = fireflySimulation;
%   - The output variable phaseOut contains the phi values of all involved
%   oscillators.
%




   p = inputParser;
   p.KeepUnmatched = true;
   %default settings
    defaultSimulationLength = 60000; 
    defaultFilterLength = 8;        
    defaultNodes = 5;       % number of nodes
    defaultSampleRate = 1000;  
    defaultPlotLength = 10000; 
    defaulttref = 5; %ms
    defaultDoublingThreshold = 20;
    defaultSeed = 0;
    defaultAnimSpeed = 0.2;

    
    defaultAlpha = 0.4;
    defaultBeta = 0.4;
    defaultFilterType = 'median';
  
%it is also possible to change phase coupling function further below

%initialisation
addOptional(p,'SimulationLength',defaultSimulationLength,@isscalar);
addOptional(p,'FilterLength',defaultFilterLength,@isscalar);
addOptional(p,'Nodes',defaultNodes,@isscalar);
addOptional(p,'SampleRate',defaultSampleRate,@isscalar);
addOptional(p,'PlotLength',defaultPlotLength,@isscalar);
addOptional(p,'tref',defaulttref,@isscalar);
addOptional(p,'DoublingThreshold',defaultDoublingThreshold,@isscalar);
addOptional(p,'alpha',defaultAlpha,@isscalar);
addOptional(p,'beta',defaultBeta,@isscalar);
addOptional(p,'PlotSpectrogram',0,@isscalar);
addOptional(p,'RandomSeed',defaultSeed,@isscalar);
addOptional(p,'AnimSpeed',defaultAnimSpeed,@isscalar);
addOptional(p,'FilterType',defaultFilterType);
addOptional(p,'Animate',0);

parse(p,varargin{:});

SimulationLength = p.Results.SimulationLength;
FilterLength = p.Results.FilterLength;
n = p.Results.Nodes;
sr = p.Results.SampleRate;
PlotLength = p.Results.PlotLength;
tref = p.Results.tref;
DoublingThreshold = p.Results.DoublingThreshold;
Alpha = p.Results.alpha;
Beta = p.Results.beta;
plotSpectrogram = p.Results.PlotSpectrogram;
randomSeed = p.Results.RandomSeed;
filterType = str2func(p.Results.FilterType);
animate = p.Results.Animate;
animSpeed = p.Results.AnimSpeed*200;

if randomSeed ~= 0
	rng(randomSeed);
end

omegas = zeros(SimulationLength,n);
 

phi = zeros(SimulationLength,n);           
phi(1,1:n) = rand(n,1);    %randomised initial phase
%phi(1,1:n) = [0 0.4 0.7]; 

omega = 2*2.^((rand(1,n)-0.5)*2);   %randomised initial frequency
%omega = [1.0635 2.6719 1.7153];

E = ones(SimulationLength,n);
f = zeros(1,n);

domega = zeros(n,2); %domega variable for RFA

in = 0;


for i = 2:SimulationLength
    
    fire = 0; %clear flag for fire event at time i
    
    %increase the phase of each oscillator, else reset to zero (i.e. leave phi(i,j) at initial value = 0)
    for j=1:n
        if phi(i-1,j) < 1
            phi(i,j) = phi(i-1,j)+omega(j)/sr;
        else
            %fprintf('%d %d %f %d \n',i,j,phi(i,j),f(j))
            in=in+1;interactions(in,[1:3,11]) = [2 i j f(j)];
            f(j)=mod(f(j)+1,2); % fire only on every other peak
        end
    end
    
    phiref = tref*omega*0.0005;
    
    for j=randperm(n) % go through all oscillators in random order.
       % fprintf('time %d - %f %f %f node %d\n',i, phi(i,:),j)

        if phi(i,j) >= 1        % if node j is at maximum
           % fprintf('phis %d %d %d \n',i,j,f(j))
           %f(j)=1;             % fire on every peak
            if f(j) == 0
                in=in+1;interactions(in,1:11) = [-1 i j 0 0 0 0 0 0 0 0];
            end
            if f(j) == 1 && fire == 1;       % if node j fires and firing has already occured at this time. 
                in=in+1;interactions(in,1:3) = [0 i j];
            end
           
            if f(j) == 1 && fire == 0;       % if node j fires and firing has not already occured at this time.
                fire = 1; %set flag for fire event at time i
                phi(i,j) = 1;   % j restrict self to max 1
                
                for k = setdiff(randperm(n),j,'stable') %k = all nodes but j

                    if phi(i,k) > phiref(k) %if the phase of node k is above phiref

                        %CHOOSE A PHASE COUPLING FUNCTION
                        pf = 5;

                        switch pf
                            case 1 %standard excitatory: phi + alpha * phi
                               phi(i,k) = phi(i,k)+Alpha*phi(i,k);                                  
                            case 2 %standard, bidirectional
                               phi(i,k) = phi(i,k)+Alpha*phi(i,k)*(phi(i,k)-0.5)/abs(phi(i,k)-0.5); 
                            case 3 %sinewave * phase
                               phi(i,k) = phi(i,k)+Alpha*phi(i,k)*-sin(phi(i,k)*2*pi);              
                            case 4 %sinewave 
                               phi(i,k) = phi(i,k)+Alpha*-sin(phi(i,k)*2*pi);              
                            case 5 %bidirectional sinewave^2 
                               phi(i,k) = phi(i,k)+Alpha*-sin(phi(i,k)*2*pi)*abs(-sin(phi(i,k)*2*pi)); 
                            case 6 %bidirectional sinewave^2 * phase
                               phi(i,k) = phi(i,k)+Alpha*phi(i,k)*-sin(phi(i,k)*2*pi)*abs(-sin(phi(i,k)*2*pi)); 
                            case 7 %bidirectional coswave^2
                               phi(i,k) = phi(i,k)+Alpha*-cos(phi(i,k)*pi)*abs(-cos(phi(i,k)*pi));
                            case 8
                               phi(i,k) = phi(i,k)+Alpha*(phi(i,k)-0.5)^3;
                        end
                        
                        if phi(i,k)<0%trim phase to [0 1] range
                            phi(i,k)=0;  
                        elseif phi(i,k)>=1
                            phi(i,k)=1;
                        end
                        
                    if phi(i,k) < phiref(k) || phi(i,k) > 1-phiref(k)
                        E(i:SimulationLength,k) = 0; %Error is 0 if firing happens within phiref
                    else
                        E(i:SimulationLength,k) = (1-cos(2*pi*phi(i,k)))/2; %else, error is calculated like this
                    end

                        
                        %this line makes oscillator frequencies converge tighter at the risk of instability. if disabled, phase coupling will ensure sync anyway
                        %if phi(i,k)==1 && phi(i-1,k) < 0.95,domega(k,1)=domega(k,1)*1.01;end

                        % FREQUENCY COUPLING FUNCTION
                        domega(k,1) = ((omega(k) * 2^(Beta*-sin(2*pi*phi(i,k))*(filterType(E(length(E(:,k))-FilterLength+1:length(E(:,k)),k)))))+prod(domega(k,1:2)))/(domega(k,2)+1);

                        domega(k,2) = domega(k,2)+1;
                        if domega(k,2) > DoublingThreshold
                            omega(k) = 2*omega(k);
                            domega(k,:) = [0 0];
                            disp(['Someone''s stuck. Doubling frequency of fly ' num2str(k) ' Time ' num2str(i/sr) 's'])
                        end
                        in=in+1;interactions(in,1:9) = [1 i j k 1 filterType(E(length(E(:,k))-FilterLength+1:length(E(:,k)),k)) phi(i,k)-phi(i-1,k) Beta*sin(2*pi*phi(i,k)) domega(k,1)]; 
                    else
                        in=in+1;interactions(in,1:6) = [1 i j k 0 filterType(E(length(E(:,k))-FilterLength+1:length(E(:,k)),k))]; %col 5 == 0 means fire within tref
                    end
                end
            end
            
            if domega(j,2) > 0 %if there has been freq adjustments
                omega(j) = domega(j,1);    %Reachback firefly algorithm
                domega(j,:) = [0 0];
                if omega(j) == 0
                        disp('a node has died... omega = zero')
                end
                in=in+1;interactions(in,1:10) = [-2 i j j 1 filterType(E(length(E(:,j))-FilterLength+1:length(E(:,j)),j)) -1 0 0 omega(j)]; 
            end
%            [j;E(:,j)]
            if sum(E(length(E(:,j))-FilterLength+1:length(E(:,j)),j)) == 0 && length(interactions(interactions(:,3)==j & interactions(:,11)==1,1)) > 8
                %length(interactions(interactions(:,3)==j,1))
                %disp('selfmean adjust')
                sortedOmegas = sort(interactions(interactions(:,3)==j & interactions(:,11)==1,2),'descend')';
                omega(j) = -2000/mean(diff(sortedOmegas(1:FilterLength)));
                in=in+1;interactions(in,1:10) = [-3 i j j 1 filterType(E(length(E(:,j))-FilterLength+1:length(E(:,j)),j)) -1 0 0 omega(j)]; 
            end
        end
    end

    for j=1:n
        if phi(i,j) >= 1
            phi(i,j) = 1;
        end
            omegas(i,j) = omega(j);
    end
end

if plotSpectrogram == 1
figure(1),clf(1)
    subplot(5,4,1:2)
        plot((1:PlotLength)/sr,phi(1:PlotLength,:)),title('phase: beginning')

    subplot(5,4,3:4)
        plot((SimulationLength-PlotLength:SimulationLength)/sr,phi(SimulationLength-PlotLength:SimulationLength,:)),title('phase end')

    subplot(5,4,[5 10])
        plot((1:SimulationLength)/sr,phi(1:SimulationLength,:)+1.05*cumsum(ones(n,SimulationLength))'-2),title('phase')
        xlim([0 PlotLength]/sr)
        hold on
        scatter(interactions(interactions(:,11)==1,2)/1000,(interactions(interactions(:,11)==1,3)-1)*1.05,'k.')

    subplot(5,4,[7 12])
        plot((1:SimulationLength)/sr,filter([1 -1],1,phi(1:SimulationLength,:))+1.15*cumsum(ones(n,SimulationLength))'-2),title('delta phase')
        xlim([0 PlotLength]/sr)

    subplot(5,4,[13 18])
        plot((1:SimulationLength)/1000,omegas),title('freq')

    subplot(5,4,[15 20])
        tmp = filter([1 -1],1,omegas);tmp(1:n,:)=[];
        plot((n+1:SimulationLength)/1000,tmp),title('delta freq')


    figure(2)
	for i = 1:n
	subplot(n,1,i)
	spectrogram(sin(2*pi*phi(:,i)),4096,2048,10000,sr,'yaxis');ylim([0 8])
	end
end



if nargout > 0
    phaseOut = phi;
    if nargout > 1 || animate == 1
        omegaOut = omegas;
    end 
end

if animate == 1
    subplot(6,2,1:4);
    plot((1:length(phaseOut(:,1)))/sr,phaseOut),xlabel('time (s)'),ylabel('phase')
    l1 = line([0 0],[0 1],'LineWidth',2,'color',[0 0 0]);
    k3 = subplot(6,2,[6 8]);
    plot((1:length(phaseOut(:,1)))/sr,phaseOut),xlabel('time (s)'),ylabel('phase'),hold on
    l3 = line([0 0],[0 1],'LineWidth',2,'color',[0 0 0]);
    
    k4 = subplot(6,2,[10 12]);
    plot((1:length(omegaOut(:,1)))/sr,omegaOut),xlabel('time (s)'),ylabel('frequency'),hold on
    l4 = line([0 0],[0 4],'LineWidth',2,'color',[0 0 0]);
    
    k2 = subplot(6,2,5:2:11);
    for i = 1+5*animSpeed:animSpeed:length(phaseOut(:,1))
        tic
        xlim(k3,[i-sr,i+sr]/sr)
        xlim(k4,[i-sr,i+sr]/sr)
        polar(pi, 1);hold on
    p1 = polar(k2,phaseOut(i-5*animSpeed:i,:)*2*pi,1./omegaOut(i-5*animSpeed:i,:),'-');
    line([0 3],[0 0],'LineWidth',3,'color',[0 0 0]);
    p2 = polar(k2,phaseOut(i:i+1,:)*2*pi,1./omegaOut(i:i+1,:),'.');
    set(l1,'xdata',[i/sr i/sr])
%    set(l5,'xdata',[i/sr i/sr])
    set(l3,'xdata',[i/sr i/sr])
    set(l4,'xdata',[i/sr i/sr])
    set(p1,'LineWidth',1)
    for q = 1:n
        r = interactions(interactions(:,1)==2 & interactions(:,2)>i & interactions(:,3)==q,11);
        if ~isempty(r)
            if r(1) == 1 
                set(p2(q),'MarkerSize',5,'Marker','o')
                %p2 = polar(k2,phaseOut(i,q)*2*pi,1./omegaOut(i,q),'o');
            else
                set(p2(q),'MarkerSize',18)
            end
        end
    end
    hold off
    drawnow
    pause(0.2-toc)
    end
end

end