function fileList = getOncArchivefileData(token,stationCode,deviceCategory,varargin)
% -------------------------------------------------------------------------
% GETONCARCHIVEFILEDATA uses a public API webservice to get archived files 
% data based on stationcode and device category.
%
% INPUTS:
%
% [required]
%   - token: found under profile > Web Services API on dmas.uvic.ca
%   - stationCode: ONC Station Code eg. IONA
%
% [optional - these have default values]
%   - deviceCategory: ONC Device Category eg. AISRECEIVER
%   - dateFrom/dateTo [default: yesterday] - dates in UTC in
%       format yyyy-mm-ddTHH:MM:SS.FFFZ
%   - rowLimit [default: []]: rowLimit on request.
%
% OUTPUTS:
%   - dataStruc is a matlab structure that contains the fields:
%       - sampleTime - date of sample in yyyy-mm-ddTHH:MM:SS.FFF format(UTC)
%       - matlabTime - date in matlab time format
%       - rawData - raw data from the selected deviceCategory
%       - metadata - metadata about the requested data
% -------------------------------------------------------------------------
% Example call:
% 
%
% -------------------------------------------------------------------------
% Additional information:
%
% To get a list of station codes with appropriate device codes, use the
% following service: (note - requires token)
%   - http://dmas.uvic.ca/api/stations?method=getTree&token='INSERT_YOUR_TOKEN_HERE'
% -------------------------------------------------------------------------
% Created October 13, 2015 by MAJ
% Ocean Networks Canada.
% -------------------------------------------------------------------------

p = inputParser;
p.addParamValue('dateFrom',[],@ischar);
p.addParamValue('dateTo',[],@ischar);
p.addParamValue('rowLimit',[],@isnumeric);
p.addParamValue('outputDirectory','',@ischar);
p.addParamValue('fileExtension','',@ischar);

p.parse(varargin{:});
dateFrom = p.Results.dateFrom;
dateTo = p.Results.dateTo;
rowLimit = char(p.Results.rowLimit);
outputDirectory = p.Results.outputDirectory;
fileExtension = p.Results.fileExtension;
disp('Input variables parsed successfully.')

input.token = token;
input.stationCode = stationCode;
input.deviceCategory = deviceCategory;
input.dateFrom = dateFrom;
input.dateTo = dateTo;
input.rowLimit = rowLimit;

endpoint = 'http://dmas.uvic.ca/api/archivefiles';
service = ArchivefileApiService(endpoint);
disp('Calling service...')
[~,fileList] = service.getFileList(input);
clear input

ind = find(cellfun('isempty',strfind(fileList,fileExtension))==1);
fileList(ind) = []; 

if isempty(fileList)
    disp('No filenames were returned. Please adjust parameters and try again.');
else
    for iFile = 1:length(fileList)
        input.token = token;
        input.filename = fileList{iFile};
        [~,file] = service.getFile(input);
        listing = dir([pwd,filesep,file]);
        if ~isempty(listing)
            disp([file,' downloaded successfully to current directory.']);
        end
        if ~isempty(outputDirectory)
            if ~isdir(outputDirectory)
                mkdir(outputDirectory)
            end
            movefile([pwd,filesep,file],outputDirectory);
            disp([file,' moved successfully to ',outputDirectory,'.']);
        end
    end

end
end
% % ------------------- Supporting functions


function methods = ArchivefileApiService(endpoint)
% ---------------------------------------------------------
% ArchivefileApiService provides an interface to the public ONC DI RawData
% webservice.
%
% Input: endpoint
%        http://dmas.uvic.ca/api/archivefiles
%
% Output: interface to methods described below.
%
% METHODS:
%       getFileListByStationCode
%       getFileByStationCode
%
%           Method inputs: [Required] (found in input structure)
%               * Token - the token value found on your profile at
%                   dmas.uvic.ca. MUST be entered.
%               * stationCode - ONC station code where data are to be taken
%                   from (there is a discovery service for this - see
%                   additional information)
%               * deviceCategory - ONC device category
%               * dateFrom/dateTo - dates in UTC: yyyy-mm-ddTHH:MM:SS.FFFZ
%               * rowLimit - row limit on returned data.
%               * filename - DMAS filename returned by getList method.
%
%
% Created October 13, 2015 MAJ
%---------------------------------------------------------------------
% Revision log:
%
%---------------------------------------------------------------------

methods.getFile = @getFile;
methods.getFileList = @getFileList;

% Get an array of Data using dateFrom - dateTo
    function [err, result] = getFileList(input)
        params = [
            {'method','getList'}, ...
            {'token',input.token}, ...
            {'station',input.stationCode}, ... % comma separated list - must be char string
            {'deviceCategory',input.deviceCategory}];
        
        if ~isempty(input.dateTo)
            params = [params,{'dateTo',input.dateTo}]; % ISO8601 extended format ("yyyy-MM-dd'T'HH:mm:ss'.'SSS'Z'").
        end
        if ~isempty(input.dateFrom)
            params = [params,{'dateFrom',input.dateFrom}]; % ISO8601 extended format ("yyyy-MM-dd'T'HH:mm:ss'.'SSS'Z'").
        end

        func = 'getList';
        [err, result] = doRequest('GET', func, params);
    end

    function [err, result] = getFile(input)
        params = [
            {'method','getFile'}, ...
            {'token',input.token}, ...
            {'filename',input.filename}
            ];
        func = 'getFile';
        [err, result] = doRequest('GET',func, params);
    end


% Makes a request to the server and handles parsing the JSON response
% The err is also set appropriately based on a valid DmasAPIResponse
    function [err, result] = doRequest(method,func, params)
        err = []; result = [];
        try
            if strcmpi(func,'getlist')
                [json_value, status] = urlread(endpoint, method, params);
                if status == 1
                    result = parse_json(json_value);
                else
                    % We shouldn't actually ever get here if the service unless
                    % there is a bug in the service, or a communication problem
                    err.code = 500;
                    err.message = 'Internal server error';
                end
            else
                url = [endpoint,'?'];
                for iParam = 1:2:length(params)
                   url = [url,params{iParam},'=',params{iParam+1}];
                   if iParam~=length(params)-1
                       url = [url,'&'];
                   end
                end
                ind = find(cellfun('isempty',strfind(params,'filename'))==0);
                urlwrite(url,params{ind+1});
                result = params{ind+1};
            end
        catch e
            % 400 is assuming the error didn't happen on the server
            err.code = 400;
            err.message = e.message;
        end
    end
end


function data = parse_json(string)
% DATA = PARSE_JSON(string)
% This function parses a JSON string and returns a cell array with the
% parsed data. JSON objects are converted to structures and JSON arrays are
% converted to cell arrays.

% F. Glineur, 2009
% (inspired by the JSON parser by Joel Feenstra on MATLAB File Exchange
% (http://www.mathworks.com/matlabcentral/fileexchange/20565) but with
% faster handling of strings)

pos = 1;
len = length(string);
% String delimiters and escape characters are identified beforehand to improve speed
esc = regexp(string, '["\\]'); index_esc = 1; len_esc = length(esc);

if pos <= len
    switch(next_char)
        case '{'
            data = parse_object;
        case '['
            data = parse_array;
        otherwise
            error_pos('Outer level structure must be an object or an array');
    end
end

    function object = parse_object
        parse_char('{');
        object = [];
        if next_char ~= '}'
            while 1
                str = parse_string;
                if isempty(str)
                    error_pos('Name of value at position %d cannot be empty');
                end
                parse_char(':');
                val = parse_value;
                object.(valid_field(str)) = val;
                if next_char == '}'
                    break;
                end
                parse_char(',');
            end
        end
        parse_char('}');
    end

    function object = parse_array
        parse_char('[');
        object = cell(0, 1);
        if next_char ~= ']'
            while 1
                val = parse_value;
                object{end+1} = val;
                if next_char == ']'
                    break;
                end
                parse_char(',');
            end
        end
        parse_char(']');
    end

    function parse_char(c)
        skip_whitespace;
        if pos > len || string(pos) ~= c
            error_pos(sprintf('Expected %c at position %%d', c));
        else
            pos = pos + 1;
            skip_whitespace;
        end
    end

    function c = next_char
        skip_whitespace;
        if pos > len
            c = [];
        else
            c = string(pos);
        end
    end

    function skip_whitespace
        while pos <= len && isspace(string(pos))
            pos = pos + 1;
        end
    end

    function str = parse_string
        if string(pos) ~= '"'
            error_pos('String starting with " expected at position %d');
        else
            pos = pos + 1;
        end
        str = '';
        while pos <= len
            while index_esc <= len_esc && esc(index_esc) < pos
                index_esc = index_esc + 1;
            end
            if index_esc > len_esc
                str = [str string(pos:end)];
                pos = len + 1;
                break;
            else
                str = [str string(pos:esc(index_esc)-1)];
                pos = esc(index_esc);
            end
            switch string(pos)
                case '"'
                    pos = pos + 1;
                    return;
                case '\'
                    if pos+1 > len
                        error_pos('End of file reached right after escape character');
                    end
                    pos = pos + 1;
                    switch string(pos)
                        case {'"' '\' '/'}
                            str(end+1) = string(pos);
                            pos = pos + 1;
                        case {'b' 'f' 'n' 'r' 't'}
                            str(end+1) = sprintf(['\' string(pos)]);
                            pos = pos + 1;
                        case 'u'
                            if pos+4 > len
                                error_pos('End of file reached in escaped unicode character');
                            end
                            str(end+1:end+6) = string(pos-1:pos+4);
                            pos = pos + 5;
                    end
                otherwise % should never happen
                    str(end+1) = string(pos);
                    pos = pos + 1;
            end
        end
        error_pos('End of file while expecting end of string');
    end

    function num = parse_number
        [num, one, err, delta] = sscanf(string(pos:min(len,pos+20)), '%f', 1); % TODO : compare with json(pos:end)
        if ~isempty(err)
            error_pos('Error reading number at position %d');
        end
        pos = pos + delta-1;
    end

    function val = parse_value
        switch(string(pos))
            case '"'
                val = parse_string;
                return;
            case '['
                val = parse_array;
                return;
            case '{'
                val = parse_object;
                return;
            case {'-','0','1','2','3','4','5','6','7','8','9'}
                val = parse_number;
                return;
            case 't'
                if pos+3 <= len && strcmpi(string(pos:pos+3), 'true')
                    val = true;
                    pos = pos + 4;
                    return;
                end
            case 'f'
                if pos+4 <= len && strcmpi(string(pos:pos+4), 'false')
                    val = false;
                    pos = pos + 5;
                    return;
                end
            case 'n'
                if pos+3 <= len && strcmpi(string(pos:pos+3), 'null')
                    val = [];
                    pos = pos + 4;
                    return;
                end
        end
        error_pos('Value expected at position %d');
    end

    function error_pos(msg)
        poss = max(min([pos-15 pos-1 pos pos+20],len),1);
        if poss(3) == poss(2)
            poss(3:4) = poss(2)+[0 -1];         % display nothing after
        end
        msg = [sprintf(msg, pos) ' : ... ' string(poss(1):poss(2)) '<error>' string(poss(3):poss(4)) ' ... '];
        ME = MException('JSONparser:invalidFormat', msg);
        throw(ME);
    end

    function str = valid_field(str)
        % From MATLAB doc: field names must begin with a letter, which may be
        % followed by any combination of letters, digits, and underscores.
        % Invalid characters will be converted to underscores, and the prefix
        % "alpha_" will be added if first character is not a letter.
        if ~isletter(str(1))
            str = ['alpha_' str];
        end
        str(~isletter(str) & ~('0' <= str & str <= '9')) = '_';
    end

end
