classdef LoadArgs < handle
    %LOADARGS parser for file loading arguments
    
    properties (GetAccess = private, Constant = true)
        wellVal = 1;
        electrodeVal = 2;
        timespanVal = 3;
        dimensionsVal = 4;
    end
    
    properties (GetAccess = public, Constant = true)
        ByPlateDimensions = 1;
        ByWellDimensions = 3;
        ByElectrodeDimensions = 5;
    end
    
    properties (GetAccess = public, SetAccess = private)
        Well
        Electrode
        Timespan
        Dimensions
    end
    
    methods (Static)
        
        function this = LoadArgs(argin)
            % Internal support function for AxisFile Construction
            % loadAxISFileParseArgs Parses arguments for file readers
            %
            %  Legal forms:
            %     LoadArgs();
            %     LoadArgs(well);
            %     LoadArgs(electrode);
            %     LoadArgs(well, electrode);
            %     LoadArgs(timespan);
            %     LoadArgs(well, timespan);
            %     LoadArgs(electrode, timespan);
            %     LoadArgs(well, electrode, timespan);
            %     LoadArgs(dimensions);
            %     LoadArgs(well, dimensions);
            %     LoadArgs(electrode, dimensions);
            %     LoadArgs(well, electrode, dimensions);
            %     LoadArgs(timespan, dimensions);
            %     LoadArgs(well, timespan, dimensions);
            %     LoadArgs(electrode, timespan, dimensions);
            %     LoadArgs(well, electrode, timespan, dimensions);
            %
            %  Required arguments:
            %    filename    Pathname of the file to load
            %
            %  Optional arguments:
            %    well        String listing which wells (in a multiwell file) to load.
            %                Format is a comma-delimited string with whitespace ignored, e.g.
            %                'A1, B2,C3' limits the data loaded to wells A1, B2, and C3.
            %                Also acceptable: 'all' to load all wells.
            %                If this parameter is omitted, all wells are loaded.
            %                For a single-well file, this parameter is ignored.
            %
            %    electrode   Which electrodes to load.  Format is either a comma-delimited string
            %                with whitespace ignored (e.g. '11, 22,33') or a single channel number;
            %                that is, a number, not part of a string.
            %                Also acceptable: 'all' to load all channels and 'none', '-1', or -1
            %                to load no data (returns only header information).
            %                If this parameter is omitted, all channels are loaded.
            %
            %    timespan    Span of time, in seconds, over which to load data.  Format is a two-element
            %                array, [t0 t1], where t0 is the start time and t1 is the end time and both
            %                are in seconds after the first sample in the file.  Samples returned are ones
            %                that were taken at time >= t0 and <= t1.  The beginning of the file
            %                is at 0 seconds.
            %                If this parameter is omitted, the data is not filtered based on time.
            %
            %    dimensions  Preferred number of dimensions to report the waveforms in.
            %                Value must be a whole number scalar, and only certain values are allowed:
            %
            %                dimensions = 1 -> ByPlate: returns a vector of Waveform objects, 1 Waveform
            %                        s          per signal in the plate
            %                dimensions = 3 -> ByWell: Cell Array of vectors of waveform 1 Waveform per signal
            %                                  in the electrode with size (well Rows) x (well Columns)
            %                dimensions = 5 -> ByElectrode: Cell Array of vectors of waveform 1 Waveform per .
            %                                  signal in the electrode with size (well Rows) x (well Columns) x
            %                                  (electrode Columns) x (electrode Rows)
            this.Well = [];
            this.Electrode = [];
            this.Timespan = [];
            this.Dimensions = [];
            
            fLastArg = [];
            
            fNumArgs = length(argin);
            
            if fNumArgs > 4
                error('LoadArgs:excessArgs', 'Too many arguments specified');
            end
            
            for i = 1:fNumArgs
                fCurrentArg = argin{i};
                
                if isempty(fCurrentArg) %ignore empty args
                    continue
                end
                
                if isempty(fLastArg)
                    fParseAsWell = LoadArgs.canonical_well_electrode_argument(fCurrentArg, LoadArgs.wellVal);
                    if ~isempty(fParseAsWell)
                        % Argument is a well
                        this.Well = fParseAsWell;
                        fLastArg = LoadArgs.wellVal;
                        continue;
                    end
                end
                
                if isempty(fLastArg) || fLastArg == LoadArgs.wellVal
                    fParseAsElectrode = LoadArgs.canonical_well_electrode_argument(fCurrentArg, LoadArgs.electrodeVal);
                    if ~isempty(fParseAsElectrode)
                        % Argument is an electrode
                        this.Electrode = fParseAsElectrode;
                        fLastArg = LoadArgs.electrodeVal;
                        continue;
                    end
                end
                
                if isempty(fLastArg) || fLastArg == LoadArgs.wellVal || fLastArg == LoadArgs.electrodeVal
                    fParseAsTimespan = LoadArgs.canonical_timespan_argument(fCurrentArg);
                    if ~isempty(fParseAsTimespan)
                        % Argument is a timespanVal
                        if isnumeric(fParseAsTimespan) && fParseAsTimespan(2) < fParseAsTimespan(1)
                            error('load_AxIS_file_parse_args:invalidTimespan', 'Invalid timespan argument: t1 < t0');
                        end
                        
                        this.Timespan = fParseAsTimespan;
                        fLastArg = LoadArgs.timespanVal;
                        continue;
                    end
                end
                
                if isempty(fLastArg) || i == length(argin)
                    if isscalar(fCurrentArg)
                        switch fCurrentArg
                            case { LoadArgs.ByPlateDimensions, ...
                                   LoadArgs.ByWellDimensions, ...
                                   LoadArgs.ByElectrodeDimensions} 
                                this.Dimensions = fCurrentArg;
                            otherwise
                                this.Dimensions = [];
                        end
                        fLastArg = LoadArgs.dimensionsVal;
                        continue;
                    end
                end
                
                % If we get here, the argument couldn't be parsed
                error('load_AxIS_file_parse_args:invalidArg', ['Invalid argument #' num2str(i+1) ' to load_AxIS_file']);
            end
            
            if isempty(this.Well)
                % Default: all wells
                this.Well = 'all';
            end
            
            if isempty(this.Electrode)
                % Default: all electrodes
                this.Electrode = 'all';
            end
            
            if isempty(this.Timespan)
                % Default: all time
                this.Timespan = 'all';
            end
            
        end
        
    end
    
    methods(Static, Access = private)
        
        function aParseOutput = canonical_well_electrode_argument(aArgument, type)
            
            DELIMITER = ',';
            aParseOutput = [];
            
            % Error-check argument type
            if isa(aArgument, 'LoadArgs') && ~(LoadArgs.wellVal || LoadArgs.electrodeVal)
                error('canonical_well_electrode_argument:invalidArgType', 'Internal error: Invalid argument type for parsing');
            end
            
            % Special cases
            if strcmpi(aArgument, 'all')
                aParseOutput = 'all';
            elseif type == LoadArgs.electrodeVal && isscalar(aArgument) && aArgument == -1
                aParseOutput = 'none';
            elseif type == LoadArgs.electrodeVal && isscalar(aArgument) && isnumeric(aArgument) && aArgument > 10
                aParseOutput = [floor(aArgument/10) mod(aArgument, 10) ];
            elseif ischar(aArgument)
                
                if type == LoadArgs.electrodeVal && ( strcmp(strtrim(aArgument), '-1') || strcmpi(aArgument, 'none') )
                    aParseOutput = 'none';
                    return;
                end
                
                
                % Convert well names to upper case
                fCanonicalArg = upper(aArgument);
                
                % Strip whitespace
                fCanonicalArg(isspace(fCanonicalArg)) = [];
                
                % Is it valid?
                while ~isempty(fCanonicalArg)
                    if   length(fCanonicalArg) >= 2 &&  ...
                            ( (type == LoadArgs.wellVal)      && isletter(fCanonicalArg(1))    && LoadArgs.isdigit_ax(fCanonicalArg(2))) || ...
                            ( (type == LoadArgs.electrodeVal) && LoadArgs.isdigit_ax(fCanonicalArg(1))  && LoadArgs.isdigit_ax(fCanonicalArg(2)))
                        
                        % Valid format
                        if type == LoadArgs.wellVal
                            % Format is Letter then Number, where letter is the row and number is the column
                            % Build array of column, row
                            [nextWell, fCanonicalArg] = strtok(fCanonicalArg, ',');
                            aParseOutput = [ aParseOutput ; ...
                                             str2num(nextWell(2:end)) (char(nextWell(1)) - char('A') + 1)];
                        else
                            % Format is Number then Number, where the first is the column and the second is the row
                            % Build array of column, row
                            [nextWell, fCanonicalArg] = strtok(fCanonicalArg, ',');
                            aParseOutput = [ aParseOutput ; ...
                                             str2num(nextWell(1)) str2num(nextWell(2:end))];
                        end
                        
                        % Look for the next delimiter
                        if length(fCanonicalArg) >= 1
                            if fCanonicalArg(1) == DELIMITER
                                fCanonicalArg = fCanonicalArg(2:end);
                            else
                                % Invalid next character - not a delimiter
                                aParseOutput = [];
                                break;
                            end
                        end
                    else
                        % Invalid Well ID
                        aParseOutput = [];
                        break;
                    end
                end
            end
        end
        
        function aParseOutput = canonical_timespan_argument(aArgument)
            
            if isvector(aArgument) && length(aArgument) == 2 && isnumeric(aArgument)
                aParseOutput = aArgument(:);
            else
                aParseOutput = [];
            end
            
        end
        
        function t = isdigit_ax(c)
            %ISDIGIT True for decimal digits.
            %
            %   For a string C, ISDIGIT(C) is 1 for decimal digits and 0 otherwise.
            %
            narginchk(1, 1);
            
            t = ischar(c) & ( '0' <= c ) & ( c <= '9' );
        end
        
    end
end

