function [ tomlini ] = tomlini(pFilename, pReservedCharacters)
%TOMLINI Reads a file that consist of a specific combination of a ini and toml file.
%   Creates a struct variable that has properties name as the sections in
%   the file. The values of these properties either have the type struct,
%   cell or double, depending on how the data can be parsed.
%
%   Parameters:
%       pFilename (string) The filename that should be read.
%       pReservedCharacters (struct) Optional. It is possible to customize
%       	the characters that are used for parsing.
%           Default values:
%               section            [
%               sectionTitleEnd    ]
%               comment            %
%               variableIndicator  -
%
%
%   Example file:
%
%   [SectionName]
%   -var value
%   -var2 value
%
%   [SecondSectionName]
%   10 20 30 40
%   50 60 70 80
%
%   [ThirdSection]
%   sometext 10 20
%   someOtherText 30 40


%% Initialize return variables

tomlini = false;

%% Initialize variables required for processing

tomlini = {};

% File specific characters required for processing
reservedCharacters = struct( ...
    'section', '[', ... % character indicating new section
    'sectionTitleEnd', ']', ... % immediately follows the section title
    'comment', '%', ... % character to indicate comments
    'variableIndicator', '-' ... % indicates a variable in the current line
);

%% Parameter check

parameterError = false;

if (~ischar(pFilename))
    warning([mfilename ': Given parameter is not a string!']);
    parameterError = true;
end

if (exist('pReservedCharacters', 'var') && isstruct(pReservedCharacters))
    % merge the two structs
    names = fieldnames(pReservedCharacters);
    for i = 1:length(names)
        reservedCharacters.(names{i}) = pReservedCharacters.(names{i});
    end
end

if (parameterError)
    return;
else
    clear parameterError;
end

%% Read the file

fileContent = fileread(pFilename);

%% Parse the file

allSections = splitIntoSections(fileContent);

for i = 1:length(allSections)
    if (isempty(allSections{i}) || isComment(allSections{i})) % ignore the line
        continue;
    end
    [title, content] = extractTitleAndContent(allSections(i));

    switch determineParseType(content)
        case 'matrix'
            tomlini.(title) = parseAsMatrix(content, title);
        case 'struct'
            tomlini.(title) = parseAsStruct(content);
        case 'cell'
            tomlini.(title) = parseAsCell(content);
        otherwise
            warning([mfilename ': Something went wrong during type estimation for parsing!']);
    end
    
end

    function [ type ] = determineParseType(pContent)
        lines = splitlines(pContent);
        % check if everything can be parsed as number
        tmpRow = strsplit(lines{1}, {'\t', ' '}, 'CollapseDelimiter', true);
        tmpRow = tmpRow(~cellfun('isempty', tmpRow));
        nanInRow = contains(tmpRow, 'NaN');
        rowAsNumber = str2double(tmpRow);
        if (nanInRow == isnan(rowAsNumber))
            type = 'matrix';
            return;
        end
        
        % it cannot be parsed as matrix, so check if it can be parsed as
        % struct
        hasVariableIndicator = ...
            (lines{1}(1) == reservedCharacters.variableIndicator);
        alphabeticLetters = isletter(lines{1});
        
        if hasVariableIndicator && alphabeticLetters(2)
            type = 'struct';
        else
            type = 'cell';
        end
    end

    function [ sections ] = splitIntoSections(pFileContent)
        sections = split(pFileContent, reservedCharacters.section);
        % remove all empty cells
        sections = sections(~cellfun('isempty', sections));
    end

    function [ title, content ] = extractTitleAndContent(pSection)
        parts = split(pSection, reservedCharacters.sectionTitleEnd);
        title = strrep(strtrim(parts{1}), ' ', '_'); % replace space
        title = strrep(title, '\t', '_'); % replace tabs
        content = strtrim(parts{2});
    end

    function [ isComment ] = isComment(pLine)
        trimmedLine = strtrim(pLine);
        if (isempty(trimmedLine))
            isComment = true;
            return;
        end
        if (trimmedLine(1) == reservedCharacters.comment)
            isComment = true;
        else
            isComment = false;
        end
    end
    
    function [ returnContent ] = parseAsStruct(pContent)
        lines = splitlines(pContent);
        returnContent = struct();
        for i = 1:length(lines)
            if (isempty(lines{i}) || isComment(lines{i}))
                continue
            end
            [line, comment] = extractValueAndComment(lines{i});
            [key, value] = strtok(line(2:end));
            
            if ~strcmp(line(1), reservedCharacters.variableIndicator)
                continue;
            end
            valueAsNumber = str2num(value);
            if isempty(valueAsNumber)
                parsedValue = strtrim(value);
            else
                parsedValue = valueAsNumber;
            end

            % check if key already exists
            if (isfield(returnContent, key)) % add it to the existing key as new line
                parsedValue = [ returnContent.(key); parsedValue];
            end
            returnContent.(key) = parsedValue;
            
        end
    end

    function [ matrix ] = parseAsMatrix(pContent, pTitle)
        lines = splitlines(pContent);
        %matrix = [];
        columnCount = 0;
        for i = 1:length(lines)
            if (isempty(lines{i}) || isComment(lines{i}))
                continue
            end
            % cut rest of line if a comment character is available
            [line, comment] = extractValueAndComment(lines{i});
            lines{i} = line;
            clear line;
            % split by whitespaces
            lineAsCell = strsplit(lines{i}, {'\t', ' '}, 'CollapseDelimiters', true);
            % remove empty cells
            lineAsCell = lineAsCell(~cellfun('isempty', lineAsCell));
            lineAsDouble = str2double(lineAsCell);
            if i == 1
                matrix = lineAsDouble;
                columnCount = length(lineAsDouble);
            else
                if (length(lineAsDouble) ~= columnCount)
                    warning([ mfilename ...
                        'Expecting ' num2str(columnCount) ' columns in line ' ...
                        num2str(i) ' of section ' pTitle ', but found ' ...
                        num2str(length(lineAsDouble)) ' columns. Line is being skipped!' ...
                    ]);
                    continue;
                end
                matrix = [matrix; lineAsDouble];
            end
        end
    end

    function [ parsedCell ] = parseAsCell(pContent)
        lines = splitlines(pContent);
        parsedCell = {};
        for i = 1:length(lines)
            if (isempty(lines{i}) || isComment(lines{i}))
                continue
            end
            [line, comment] = extractValueAndComment(lines{i});
            parsedCell{i} = strsplit( ...
                strtrim(line), {' ', '\t'}, 'CollapseDelimiters', true ...
            );
        end
        parsedCell = vertcat(parsedCell{:}); % combine all cells to one
    end

    function [ value, comment ] = extractValueAndComment(pString)
        tmp = strsplit(pString, reservedCharacters.comment);
        value = tmp{1};
        if length(tmp) == 2
            comment = tmp{2};
        else
            comment = '';
        end
    end

end