classdef MatMust < handle
    %MATMUST A library to get deal with ESA webmust REST API
    %   MATMUST embed the first 
    
    properties
       POSToptions
       GEToptions
       loginstatus
       url
       urlsub
       userProjects
    end
    
    properties (SetAccess = private)
        tockenfilename
        tockenstruct
    end
   
   properties (Transient)
   end
    
    methods
        function obj = MatMust(varargin)
            % Initialize the values
            obj.POSToptions = weboptions('RequestMethod', 'POST', 'ArrayFormat', 'json', 'Timeout',30, 'MediaType','application/json');
            obj.GEToptions = weboptions('RequestMethod', 'GET', 'ArrayFormat', 'repeating', 'Timeout',30, 'MediaType','application/x-www-form-urlencoded');
            obj.url = 'https://bepicolombo.esac.esa.int/webclient-must/';
            obj.urlsub = 'mustlink/';
            obj.tockenfilename = 'auth.json';
            obj.loginstatus = false;
            
            %check the url
            if(nargin > 0)
                if isfield(varargin{1},'url')
                    obj.url = varargin{1}.url;
                end
            end
            
            %check url
            [~,status] = urlread(obj.url);
            if ~status
               error('invalid URL')
            end
            
            if(MatMust_logincheck(obj))
                disp('++++       MATMUST initialization       ++++');
                disp('++++ Access token valid. User logged in ++++');
                disp('++++++++++++++++++++++++++++++++++++++++++++');
                return
            end
            
        end
        function e = MatMust_logincheck(obj)
            f = fopen(obj.tockenfilename);
            e = false;
            % check if user is already logged in
            if f > 0
                raw = fread(f,inf);
                tockenfilejson = jsondecode(char(raw)');
                if isfield(tockenfilejson,'token') &  isfield(tockenfilejson,'expiresAt')                 
                    if length(split(tockenfilejson.token,'.')) == 3 & datetime(datenum(tockenfilejson.expiresAt, 'yyyy-mm-dd HH:MM:SS'), 'ConvertFrom','datenum', 'TimeZone','utc') > datetime(datetime(now, 'ConvertFrom','datenum', 'TimeZone','local'), 'TimeZone','utc')
                        obj.loginstatus = true;
                        obj.tockenstruct = tockenfilejson;
                        MatMust_getUserLinkedProjects(obj);
                        e=true; 
                    else
                        disp('++++               MATMUST initialization                    ++++');
                        disp('++++ invalid or exiperd access token. User not logged in yet ++++');
                        disp('++++     User login needed - use MatMust_login to access     ++++');
                        disp('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++');
                    end
                end
            else
                disp('++++               MATMUST initialization                    ++++');
                disp('++++      cannot find access token. User not logged          ++++');
                disp('++++     User login needed - use MatMust_login to access     ++++');
                disp('+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++');
            end
        end
        function t = MatMust_getTockenStatus(obj)
            t = obj.loginresponse;
        end
        function t = MatMust_getUserProjects(obj)
            if obj.loginstatus
                query = 'usermanagement/projects';
                obj.GEToptions.HeaderFields = {'Authorization', obj.tockenstruct.token};
                try
                    response = webread([obj.url,obj.urlsub, query], obj.GEToptions);
                    t = response ;
                catch ME
                    fprintf('+++ WARNING\n%s - %s\n', ME.identifier, ME.message)
                end

            end
        end
        function t = MatMust_getUserLinkedProjects(obj, varargin)
            % Get the project to witch the logged in User belongs and have access to. 
            % if 'dump' option is set a screenslash is done
            userInfo = MatMust_getUserInfo(obj);
            obj.userProjects = userInfo.projects;
            t = obj.userProjects;
            if nargin > 1
                if strcmp(varargin{1},'dump')
                    fprintf('+++++++++++++ PROJECTS LIST +++++++++++++\n');
                    for i = 1:numel(t)
                        fprintf('\tname: %s\n\tdescription: %s\n\tid: %i\n', t(i).name, t(i).description, t(i).id);
                        fprintf('+++++++++++++++++\n');
                    end
                end
            end
        end
        function t = MatMust_getUserInfo(obj)
            if obj.loginstatus
                query = 'usermanagement/userinfo';
                obj.GEToptions.HeaderFields = {'Authorization', obj.tockenstruct.token};
                try
                    response = webread([obj.url,obj.urlsub, query], obj.GEToptions);
                    t = response ;
                catch ME
                    fprintf('+++ WARNING\n%s - %s\n', ME.identifier, ME.message)
                end
            end
        end 
        function data = MatMust_getDataFromName(obj, ds, parname, dateStart, dateStop, varargin   )
            % data = MatMust_getDataFromName(obj, ds, parname, dateStart, dateStop, options  )
            % MatMust_getDataFromName get the data name parameter from the project name ds
            % in the time interval dateStart, dateStop.
            %
            % inputs
            % ------
            % ds = 'string' -> name of the project i.e.'BEPICRUISE'
            % parname = 'string' -> a parameter name i.e. 'NCAD7EB7'
            % dateStart = 'yyyy-mm-dd HH:MM:SS' format date UTC time
            % dateStop = 'yyyy-mm-dd HH:MM:SS' format date UTC time
            %   'dump','plot' optional parameter to dump and plot the data
            %   'calibrated' optional parameter that specify that you want
            %   calibrated data
          
            % intialize data output
            data = [];
            if ~(MatMust_logincheck(obj))
                return
            end
            
            
            %validate ds
            if isfield(obj.userProjects,'name')
                if ~any(strcmp({obj.userProjects.name},ds))
                    disp('++++ Project not in Available User List, check ds name or check you access rigth ++++');
                    return
                end
            end
            
            if any(ismember(varargin,'calibrated')) calibrate = 'true'; else calibrate = 'false'; end

            query = ['dataproviders/',ds,'/parameters/data'];
            obj.GEToptions.HeaderFields = {'Authorization', obj.tockenstruct.token};

            if isa(parname,'cell')
                parname = char(join(parname,','));
            end

            args = {'key', 'name',...
                    'values', (parname),...
                    'from', (dateStart),...
                    'to', (dateStop),...
                    'calibrate', calibrate,...
                    'mode', 'SIMPLE'};

            try
                response = webread([obj.url,obj.urlsub, query], args{:} ,obj.GEToptions);
            catch ME
                fprintf('+++ WARNING\n%s - %s\n', ME.identifier, ME.message)
                return
            end
            
            if isa(response,'struct')
                data{1} = response;
            else
                data = response;
            end
            
            %refine date string to adapt MATLAB date format
            for i=1:numel(data{1})
                if isempty(data{1}(i).data)
                    fprintf('+++ WARNING\n%s for %s\n', 'No Data existing in time span', data{1}.name)
                    continue
                end
                data_par = data{1}(i).data;
                t = num2cell(datenum(datetime('1970-01-01', 'TimeZone','utc'))+(str2num(str2mat(data_par.date))/1000/86400));
                [data{1}(i).data.dateMAT] = deal(t{:});
                clear t
            end

            if nargin > 5
                if any(ismember(varargin,'dump'))
                    fprintf('+++++++++++++ DATA RESULT LIST +++++++++++++\n');
                    for i=1:numel(data{1})
                         if isempty(data{i}(i).data)
                             continue
                         end
                        data_par = data{i}.data;
                        fprintf('%s-%s: %s -> %s\n', data{1}(i).subsystem, data{1}(i).type, data{1}(i).name, data{1}(i).description );
                        %TODO filter for several kind of dataType 'DOUBLE',
                        %'SIGNED_SMALL_INT', 'STRING'
                        switch data{1}(i).dataType
                            case 'DOUBLE'
                                format = '%f';
                            case {'SIGNED_SMALL_INT', 'UNSIGNED_INT'}
                                format = '%i';
                            case 'STRING'
                                format = '%s';
                            otherwise
                                format = '%s';
                            
                        end
                        format = ['%s - ',format,'\n'];
                        for i = 1:numel(data_par)
                             fprintf(format,datestr(data_par(i).dateMAT,'yyyy-mm-dd HH:MM:SS.FFF'), data_par(i).value ); 
                        end 

                        fprintf('\n++++++++++++++\n\n');
                    end
                    fprintf('+++++++++++++++++++++++++++++++++++++++++\n');
                end
                if any(ismember(varargin,'plot'))
                     for i=1:numel(data{1}) 
                        if isempty(data{1}(i).data)
                             continue
                        end
                        data_par = data{1}(i).data;
                        figure
                        title(sprintf('%s-%s: %s -> %s\n', data{1}(i).subsystem, data{1}(i).type, data{1}(i).name, data{1}(i).description ))
                        if strcmp(calibrate,'true')
                            switch class(data_par(1).calibratedValue) 
                                case 'char'
                                    indx = [1,find(diff([data_par.value])~=0)]+1;
                                    ylabels = string;
                                    ylabels([unique([data_par.value],'stable')]) = replace({data_par(indx).calibratedValue},'_',' ');
                                    plot([data_par.dateMAT], [data_par.value], '-x' );
                                    set(gca,'ytick',unique([data_par.value],'sorted'))
                                    set(gca,'yticklabel',ylabels)
                                    if exist('dataFigShowUpdatefcn')==2
                                        set(datacursormode(gcf),'UpdateFcn',@dataFigShowUpdatefcn)
                                    end
                                otherwise 
                                    plot([data_par.dateMAT], [data_par.calibratedValue], '-x' );
                            end
                        else
                            plot([data_par.dateMAT], [data_par.value], '-x' );
                        end
                        xlabel('time');
                        if isfield(data{1}(i), 'unit')
                            ylabel(data{1}(i).unit);
                        end
                        title([data{1}(i).description,' - ',data{1}(i).name]);
                        datetick('x');
                        grid on
                    end
                end
            end

        end
        function t = MatMust_searchTMparFromName(obj, namequery, dataproviders, varargin)
            % +++ 
            % MatMust_searchTMparFromName
            % Search a parameter into the requested dataprovider (es. BEPICRUISE)
            % MatMust_searchTMparFromName(obj, namequery, dataproviders, varargin)
            % 
            % dataproviders = dataprovider IdName. 
            % namequery = The query to perform (ex. '*HGA' )
            % varargin =  You can specify several options in varagin:
            %   ex: 'dump' -> print to screen the parameters found
            %   'Description' -> Extend search in Parameters Descriptions
            %   'Id' -> Extend Search in Parameter Id
            %   'Unit' -> Extend Search in Unit name space
            %
            
            
            if obj.loginstatus
                query = 'metadata/treesearch';
                obj.GEToptions.HeaderFields = {'Authorization', obj.tockenstruct.token};
                if isa(dataproviders,'cell')
                    dataproviders = char(join(dataproviders,','));
                end
                try
                    optindx = strcmp(varargin,'Description')+strcmp(varargin,'Unit')+strcmp(varargin,'Id');
                    if any(find(optindx))
                        field = join(varargin(find(optindx)),',');
                        response = webread([obj.url,obj.urlsub, query], 'field',['name,',char(field)],'text',namequery,'dataproviders', dataproviders ,obj.GEToptions);
                    else
                        
                        response = webread([obj.url,obj.urlsub, query], 'field','name','text',namequery,'dataproviders', dataproviders ,obj.GEToptions);
                    end
                    t = response ;
                catch ME
                    fprintf('+++ WARNING\n%s - %s\n', ME.identifier, ME.message)
                end
                if nargin > 3
                    if any(find(strcmp(varargin,'dump')))
                        fprintf('+++++++++++++ PARAMETERS QUERY RESULT LIST +++++++++++++\n');
                        found = false;
                        for i = 1:numel(t)
                            if numel(t(i).children) > 0
                                found = true;
                                fprintf('\n\n\t+++++++++++++ %s LIST +++++++++++++\n', t(i).type);
                                for j = 1:numel(t(i).children)
                                    par = t(i).children(j); 
                                    fprintf('\ttitle: %s\n\tname: %s\n\tdescription: %s\n\tid: %s\n', par.title, par.sortingField, par.label, par.id);
                                    fprintf('  +++++++++++++++++\n');
                                end
                            end
                        end 
                        if ~found,  fprintf('++++                  Nothing found                 ++++\n'); end
                        fprintf('++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n');
                    end
                end
            end
        end
        function MatMust_login(obj, varargin )
            % login 
            %   https://bepicolombo.esac.esa.int/webclient-must/mustlink/api-docs/index.html#/Authentication/login
            % input:
            %   "username": "string",
            %   "password": "string",
            %   "maxDuration": false
            % output: 
            %   "token": "string",
            %   "issuer": "string",
            %   "subject": "string",
            %   "issuedAt": "string",
            %   "expiresAt": "string",
            %   "expirationDate": "yyyy-mm-ddTHH:MM:SS.FFFZ"
           
            if ~obj.loginstatus
                % validation input
                input_validation = struct('username', 'char', 'password', 'char', 'maxDuration', 'logical' );

                exit = false;
                if(nargin > 0)
                    inputs = varargin{1};
                    e = obj.MatMust_inputvalidator(inputs, input_validation);
                    if ~e
                        exit = false;
                        return
                    else
                        exit = true;
                    end
                else
                    exit = false;
                    return
                end

                json_POST = inputs;
                query = 'auth/login/';
                obj.tockenstruct = webwrite([obj.url,obj.urlsub, query],json_POST,obj.POSToptions); 
                if isfield(obj.tockenstruct,'token')
                    disp('+++ Succesfully logged in +++')
                    obj.loginstatus = true;
                else
                    disp('+++ Error in loggin +++')
                    obj.loginstatus = false;
                end


                %store into a file
                if obj.loginstatus
                    f = fopen(obj.tockenfilename, 'w');
                    text = jsonencode(obj.tockenstruct);
                    fprintf(f,'%s',text);
                    fclose(f);
                end
                
                %store the UserAvailable Projects in class Property
                MatMust_getUserLinkedProjects(obj);
            
            else
               disp('user already loggen in')
            end
            
        end       
        function exit = MatMust_inputvalidator(obj, input, validation)
            % For now, every validation_input field is treated as required
            % TODO: set add a subfield required at validataion json
            exit = false;
            val_fields = fields(validation);
            for i = 1:numel(val_fields)
                val_field = validation.(val_fields{i});
                if isfield(input, val_fields{i})
                    if isa(input.(val_fields{i}), val_field)
                        exit = true;
                    else
                        exit = false;
                    end
                else
                    exit = false;
                end
            end
  
        end
    end
    
end