function [output] = EdulogRun(port, dur, sps, loggers)
% Run specified Eduloggers for a specified duration at a specified temporal
% resolution.
%
% "port" is the port Eduloggers are connected to, this is visible on the
% Neulog API window. "dur" is the duration (s) of the clap test, it must be
% at least 15s for any response to be visible. "sps" is the number of
% samples the edulogger should take per second, up to a maximum of 5.
% "loggers" is a one dimensional cell array, with each string specifying
% the name of a different Edulogger as described in the Neulog API
% literature:
% https://neulog.com/wp-content/uploads/2014/06/NeuLog-API-version-7.pdf
%
% "output" is a structure generated by running an Edulogger experiment,
% consisting of the following fields: Time: The time (s) since the start of
% the experiment of each sample. (double) Concern: Whether or not each
% sample took more than twice the specified sample rate to retrieve
% (logical) An additional field for each kind of Edulogger used, containing
% the measurements taken at each point in data.Time. Fieldnames should line
% up with the names specified in "loggers".

% History:
% ??-??-????    Todd Parsons    Written.

%% Essential checks. Not sure where the folder on Linux or macOS would be?
if IsWin && ~exist('C:\neulog_api', 'dir') % If the Neulog API is not installed...
    error('Neulog API not found, please <a href="https://neulog.com/software/">install</a>.') % Link user to the installation page
end

if ~exist('webread')
    error('Required webread() function unavailable. You need Matlab R2014b+ or GNU/Octave 6.0+ for this to work.');
end

if ~isnumeric(port) % If the port given is not a number...
    error('Port number (port) must be numeric') % Deliver an error
end

switch isnumeric(sps) % Is the given SPS a number?
    case true % If so...
        if sps > 5 % If it is greater than 5...
            error('SPS exceeds max temporal resolution, please choose a value lower than 5') % Deliver an error
        elseif sps < 0 % If it is less than 0...
            error('Cannot take fewer than 0 samples per second') % Deliver an error
        end
    case false % If not...
        error('Samples per second (sps) must be numeric') % Deliver an error
end

if ~isnumeric(dur) % If the given duration is not a number...
    error('Duration (dur) must be a numeric value') % Deliver an error
end

%% Run edulogger
preface = ['http://localhost:' num2str(port) '/NeuLogAPI?']; % Construct the string to preface any argument passed to the Eduloggers

for n = 1:dur*sps % For each sample...
    tic % Start a timer
    while toc < 1/sps % Until the timer reaches sps^-1
        for l = 1:length(loggers) % For each logger...
            data.(loggers{l}){n} = ... % Set the appropriate cell in data to equal...
                webread( ... % ...the response received from the Edulogger when you send it...
                [preface, 'GetSensorValue:[', loggers{l}, '],[1]'] ... % ...this command: The preface, a request for values and the logger type
                );
        end
        data.Time(n) = toc; % Record the time taken
        data.Concern(n) = round(toc, 1) > 2/sps; % Did this sample take more than twice the desired time to retrieve?
        data.Event(n) = false;
    end
end

%% Transform data
output=struct(); % Create a blank structure
data.Time = cumsum(data.Time); % Convert time to a cumulative sum
for n = 1:dur*sps % For each sample...
    for l = 1:length(loggers) % For each logger...
        output(n).(loggers{l}) = ... % Set the appropriate cell in output to equal...
            str2num( ... % ...as a number, not a string...
            data.(loggers{l}){n}( ... % ...the appropriate value from data...
            findnum(data.(loggers{l}){n}) ... % ...put through a function which removes anything which isn't a number
            )); %#ok<ST2NM>
    end
    output(n).Time = data.Time(n); % Save timestamps to the output
    output(n).Concern = data.Concern(n); % Save concern matrix to the output
end
end

function i = findnum(str)
% Find values in a string which can be converted to numeric without
% returning an error.
%
% "str" is the string in which to find numbers
%
% "i" is a logical matrix with the indices of numbers in the string as
% true.

i = find(... % Find indices at which str is equal to...
    str == '0' | ... % ...0
    str == '1' | ... % ...1
    str == '2' | ... % ...2
    str == '3' | ... % ...3
    str == '4' | ... % ...4
    str == '5' | ... % ...5
    str == '6' | ... % ...6
    str == '7' | ... % ...7
    str == '8' | ... % ...8
    str == '9' | ... % ...9
    str == '.' | ... % ...a decimal point
    str == '-'   ... % ...a minus sign
    );
end
