%% Options class
% This is a generic options class.
%
% It has been designed to read config files for test setups. It should be
% formatted by lines of desired options with option name first, then
% listing the value[s] to be used:
% > example.cfg:
%      Option1_Name Option1_Value
%      Option2_Name Option2_Value
%
% Current value support includes:
%      Integers,
%      Doubles,
%      Strings,
%      Logicals,
%      Vector of integers,
%      Vector of doubles.
%
% Option names interpreted as comments and ignored: %, #, //, !

classdef Options

    properties (GetAccess = 'private', SetAccess = 'private') % Options list
        options
    end

    methods (Access = 'public')

        function this = Options(opt_file)
            % Constructor

            this.options = containers.Map('KeyType', 'char', 'ValueType', 'any');

            if (nargin < 1) return; end

            this = read_file(this, opt_file);

        end

        function out = get(this, option, fall_back)
            % Specify which option to get, along with the desired fallback.

            out = '';

            % If fallback value have been specified use it
            if (~exist('fall_back', 'var') || isempty(fall_back))
                out = fall_back;
            end

            % If the option have been specified use it
            if isKey(this.options, option)
                out = this.options(option);
            end

            % Convert from the cell structure if needed
            if (iscell(out))

                switch class(fall_back)
                    case 'double'
                        out = double([out{1:length(fall_back)}]);
                    case 'char'
                        out = out{1};
                    case 'logical'
                        out = (strcmp(out{1}, 'True') || strcmp(out{1}, 'true'));
                end

            end

            this.options(option) = out;

        end

        function set(this, option, value)
            % Data assignment functions
            this.options(option) = value;
        end

    end

    methods (Access = 'private')

        % Function to read option files
        function this = read_file(this, filename)

            function options = importfile(filename)
                %IMPORTFILE Import data from a text file
                %  OPTIONS = IMPORTFILE(FILENAME) reads data from text file FILENAME for
                %  the default selection.  Returns the data as a cell array.
                %
                %  OPTIONS = IMPORTFILE(FILE, DATALINES) reads data for the specified
                %  row interval(s) of text file FILENAME. Specify DATALINES as a
                %  positive scalar integer or a N-by-2 array of positive scalar integers
                %  for dis-contiguous row intervals.
                %
                %  See also READTABLE.
                %
                % Auto-generated by MATLAB on 09-Jun-2021 10:42:38

                %% Input handling

                % If dataLines is not specified, define defaults
                if nargin < 2
                    dataLines = [1, Inf];
                end

                %% Set up the Import Options and import the data
                opts = delimitedTextImportOptions("NumVariables", 7);

                % Specify range and delimiter
                opts.DataLines = dataLines;
                opts.Delimiter = " ";

                % Specify column names and types
                opts.VariableNames = ["data_name", "fieldcsv", "VarName3", "VarName4", "VarName5", "VarName6", "VarName7"];
                opts.VariableTypes = ["char", "char", "char", "char", "char", "char", "char"];

                % Specify file level properties
                opts.ExtraColumnsRule = "ignore";
                opts.EmptyLineRule = "read";
                opts.ConsecutiveDelimitersRule = "join";
                opts.LeadingDelimitersRule = "ignore";

                % Specify variable properties
                opts = setvaropts(opts, ["data_name", "fieldcsv", "VarName3", "VarName4", "VarName5", "VarName6", "VarName7"], "WhitespaceRule", "preserve");
                opts = setvaropts(opts, ["data_name", "fieldcsv", "VarName3", "VarName4", "VarName5", "VarName6", "VarName7"], "EmptyFieldRule", "auto");

                % Import the data
                options = readtable(filename, opts);

                %% Convert to output type
                options = table2cell(options);
                numIdx = cellfun(@(x)~isnan(str2double(x)), options);
                options(numIdx) = cellfun(@(x) {str2double(x)}, options(numIdx));
            end

            % Surfialise the streams and variables needed.
            if (~exist(filename, 'file')); return; end
            file = importfile(filename);

            % Index of current option
            idx = 0;

            % Loop through all lines in the file
            for (i = 1:size(file, 1))

                % Read the option name
                option = file{i, 1};

                % Ignore know comments:
                if (isempty(option))
                    continue;
                elseif (strcmp(option, "#"))
                    continue;
                elseif (strcmp(option, "//"))
                    continue;
                elseif (strcmp(option, "!"))
                    continue;
                elseif (strcmp(option, "%"))
                    continue;
                elseif (this.options.isKey(option))
                    error(["Option defined already: ", option]);
                end

                % Setup the values
                value = {};

                for (j = 2:size(file, 2))

                    if ~isempty(file{i, j})
                        value{j - 1} = file{i, j};
                    end

                end

                % Add option to map
                this.options(option) = {value{1:end}};

            end

        end

    end

end
