function [ lbl ] = read_pds_lbl( filename, input_args )
%READ_PDS_LBL Reads a PDS LBL file
%   READ_PDS_LBL(filename) Stuffs a PDS LBL-file into a structure
%
%   PDS LBL-files are structured as LABEL = VALUE pairs.
%   OBJECTs of TABLE and COLUMN type are stuffed in a cell-array
%
%   Some renaming of labels occurs due to invalid characters in labels:
%       invalid char    replaced with
%       =============================
%           ^               PTR_
%           :               _COLON_
%
%   Peje Nilsson 2016-02-23 peje.nilsson@irf.se
%       Initial release

f = fopen(filename, 'r');
if f < 0 
    error(['Can''t open file: ' filename ])
end

debug = false;
remain = fread(f,inf,'*char')';
fclose(f);

cmntchar = '#';
lbl = struct();
while true
    [token, remain] = next_line(remain);
    if isempty(token)
        break
    end
    if token(1) ~= cmntchar
        % We have the first data row, use it to count columns
        [label, value, remain] = parse_string(token, remain);
        if ~isempty(label) && strcmp(label, 'END')
            break;
        end
        if ~isempty(label) && ~isempty(value) && strcmp(label, 'OBJECT') && strcmp(value, 'TABLE')
            [table, remain] = create_table(remain);
            if ~isfield(lbl,value)
                lbl.(value) = cell(1);
                lbl.(value){end} = table;
            else
                lbl.(value){end+1} = table;       
            end
            continue
        end
        if ~isempty(label) && ~isempty(value)
            if label(1) == '^'
                label = strrep(label, '^', 'PTR_');
            end
            if contains(label, ':')
                label = strrep(label, ':', '_COLON_');
            end
            if strcmp(label,'NOTE')
                continue % no need to store NOTEs in structure
            end
            if ~isfield(lbl,label)
                lbl.(label) = dequoute_string(value);
                if debug
                    fprintf('%s = ',label)
                    disp(value)
                end
            else
                error(['Field: ' label ' exists already and contains: ' lbl.(label)])
            end
        end
    end
end

%% Create a TABLE object
    function [a, remain] = create_table(remain)
        a = struct();

        while true
            [token, remain] = next_line(remain);
            [label, value, remain] = parse_string(token, remain);
            
            if isempty(label) && isempty(value)
                continue
            end
            if ~isempty(label) && ~isempty(value) && strcmp(label, 'END_OBJECT') && strcmp(value, 'TABLE')
                break
            end
            if ~isempty(label) && ~isempty(value) && strcmp(label, 'OBJECT') && strcmp(value, 'COLUMN')
                [column, remain] = create_column(remain);
                if ~isfield(a,value)
                    a.(value) = cell(1);
                    a.(value){end} = column;
                else
                    a.(value){end+1} = column;       
                end
                continue
            end
            if ~isfield(a,label)
                a.(label) = dequoute_string(value);
                if debug
                    fprintf('%s = ',label)
                    disp(value)
                end
            else
                error(['Field: ' label ' exists already and contains: ' a.(label)])
            end
        end
    end

%% Create a COLUMN object
    function [a, remain] = create_column(remain)
        a = struct();

        while true
            [token, remain] = next_line(remain);
            [label, value, remain] = parse_string(token, remain);
            
            if isempty(label) && isempty(value)
                continue
            end
            if ~isempty(label) && ~isempty(value) && strcmp(label, 'END_OBJECT') && strcmp(value, 'COLUMN')
                break
            end
            if ~isfield(a,value)
                a.(label) = dequoute_string(value);
                if debug
                    fprintf('%s = ',label)
                    disp(value)
                end
            else
                error(['Field: ' label ' exists already and contains: ' a.(label)])
            end
        end
    end
%% Parse a line into LABEL/VALUE pair 
% reads additional lines if needed
%
    function [label, value, remain] = parse_string(line, remain)
        [label, b] = strtok(line, '=');
        value = strtok(b, '=');
        label = strtrim(label);
        value = strtrim(value);
        if ~isempty(value) && value(1) == '"' && sum(value=='"') ~= 2
            end_of_string = false;
            while ~end_of_string
                [v1, remain] = next_line(remain);
                v1 = strtrim(v1);
                value = [value ' ' v1];
                if v1(end) == '"'
                    end_of_string = true;
                end
            end
        end
        if ~isempty(value) && contains('([', value(1)) && ~contains(value, '])')
            end_of_array = false;
            while ~end_of_array
                [v1, remain] = next_line(remain);
                v1 = strtrim(v1);
                value = [value ' ' v1];
                if contains(')]', v1(end))
                    end_of_array = true;
                end
            end
        end
        if ~isempty(value) && contains('{', value(1)) && ~contains(value, '}')
            end_of_array = false;
            while ~end_of_array
                [v1, remain] = next_line(remain);
                v1 = strtrim(v1);
                value = [value ' ' v1];
                if contains('}', v1(end))
                    end_of_array = true;
                end
            end
        end
        if isempty(label) || isempty(value)
            return
        end
        if contains('{(', value(1))
            value = strrep(strrep(value,'(','['),')',']');
            try
                val = eval(value);
            catch exception
                if debug
                    disp(exception)
                end
                val = value;
            end
            value = val;
        elseif contains('0123456789-',value(1)) && ...
            ~contains(value, 'T') && ...    %% exclude dates & times from eval
            ~contains(value, ':') && ...
            ~ (sum(value == '-') > 1)
            value = strrep(strrep(value,'(','['),')',']');
            try
                val = eval(value);
            catch exception
                if debug
                    disp(exception)
                end
                val = value;
            end
            value = val;
        end
    end
%%
    function s = dequoute_string(s)
        if iscell(s)
            s = cellfun(@dequoute_string, s, 'UniformOutput', false);
        elseif s(1) == '"' && s(end) == '"'
            s = s(2:end-1);
        end
    end
%% Retrive next line from remainder
    function [line, remain] = next_line(remainder)
        delimiter=char([10 13]);
        [line, remain] = strtok(remainder, delimiter);
    end
end

