classdef SCDclass_mdspar < matlab.mixin.Heterogeneous
    % Superclass for MDS+ parameters
    % the superclass matlab.mixin.Heterogeneous allows
    % bilding of lists of mixed kind parameters in the
    % expcode configuration (and later in C++ code)
    % For all subclasses, by default all NaN values are converted to 0
    
    properties (Access = protected)
        mdsserver           % The MDS+ server hosting the parameter
        mdstree             % The MDS+ Tree hosting the parameter
        tdiexprmodel        % TDI expression to retrieve data when invoked with -1 shot
        tdiexprshot         % TDI expression to retrieve data when invoked with a give shotno, if empty the -1 is used
        tdiexprused         % TDI expression actually used
        %tdiexprmarte        % TDI expression actually used converted to MARTe cfg convections
        modelparam          % Full expansion of the model target parameter (modeltpstruct+modeltargetpar)
        modeltpstruct       % target tunable parameter structure, if left empty it will be filled once binding to an algorithm  
        modeltargetpar      % Model parameter w/o tunable parameters structure
        value               % value of the parameter
        datadictionary      % data dictionary hosting the parameter, if empty base workspace
        modelname           % name of the Simulink model using the parameter
        getcommand          % full command for getting the value (callable by matlab eval)
        classname           % class name for logging
        marteclassname      % class name for generating MARTe2 cfg file
        cparser             % constructor parameters parser
        skippable           % true if the parameters loading can be skipped during the actualization process and default value used, unused for now
        
        matlabseparator     % structure separator character used in MAtlab/Simulink
        marteseparator      % structure separator character used in MARTe2
        
        assignvar           % actualizedata specific
        assignstring        % actualizedata specific   
        denanstring         % actualizedata specific
        caststring          % actualizedata specific
        
        unlinked            % unlinked parameter, no actualization will be performed (this is set via an empty MDS source path)
        actualizable        % true if after MDS data retrieval phase the parameter is actualizable
    
        overrideshot        % =1 if instantiated with overridden shot number, in this case this shot number will be always used during actualization
        overrideshotn       % shot number in case of overridden shot number
        
        mdshelpstr          % MDS help string used in tree autopopulation
        mdsvalidationstr    % MDS validation string used in autopopulation
    end
    
    properties 
        verbose             % Verbosity of the class
    end
    
    methods
        
        function obj=SCDclass_mdspar()            
            obj.cparser=inputParser;
            addRequired(obj.cparser,'srctdimodel',@(x) ischar(x));
            addRequired(obj.cparser,'destparam',@(x) ischar(x));
            addParameter(obj.cparser,'srcsrv','tcvdata',@(x) ischar(x));
            addParameter(obj.cparser,'srctree','tcv_shot',@(x) ischar(x));
            addParameter(obj.cparser,'srctdishot','',@(x) ischar(x));
            addParameter(obj.cparser,'modelname','',@(x) ischar(x));
            addParameter(obj.cparser,'datadictname','',@(x) ischar(x));  
            addParameter(obj.cparser,'modeltpstruct','',@(x) ischar(x)); % desttp ?  
            addParameter(obj.cparser,'skippable','false',@(x) ischar(x));
            addParameter(obj.cparser,'shot',NaN,@(x) isnumeric(x) && isscalar(x));
            addParameter(obj.cparser,'mdshelp','',@(x) ischar(x));
            addParameter(obj.cparser,'mdsvalid','1',@(x) ischar(x));
            
            obj.verbose=1;
            obj.matlabseparator='.';
            obj.marteseparator='-';

        end
        
        function obj=parseconstructor(obj, srctdimodel, destparam, varargin)
            parse(obj.cparser,srctdimodel,destparam,varargin{:}{:});
            P = obj.cparser.Results;
            
            obj.mdsserver = P.srcsrv;
            obj.mdstree = P.srctree;
            obj.tdiexprmodel = P.srctdimodel;
            if isempty(P.srctdishot)
                obj.tdiexprshot = P.srctdimodel;
            else
                obj.tdiexprshot = P.srctdishot;
            end
            obj.modeltargetpar = P.destparam;
            obj.modelname = P.modelname;
            obj.datadictionary = P.datadictname;
            obj.modeltpstruct = P.modeltpstruct; 
            obj.skippable = P.skippable;  
            if isempty(P.srctdimodel)
                obj.unlinked = 1;
            else
                obj.unlinked = 0;
            end
            if isnan(P.shot)
                obj.overrideshot=0;
            else
                obj.overrideshot=1;
                obj.overrideshotn=P.shot;
            end    
            obj.mdshelpstr=P.mdshelp;
            obj.mdsvalidationstr=P.mdsvalid;
        end
        
        function name=getmodelname(obj)
            name=obj.modelname;
        end
        
    end
    
    % Not abstract methods common to all child classes
    methods
        function obj = mdsconnect(obj, shot)
            assert(~~exist('mdsconnect','file'),...
                'SCD:NoMDS','mdsconnect not found, are the mds matlab tools installed?')
           mdsconnect(obj.mdsserver);
           % If the class has been declared with a 'shot' parameter, then
           % the shotnumber used for param loading is overridden by this
           % parameter, otherwise it is taken from the global shot number
           % given to the actualize command
           if obj.overrideshot == 1
             localshot = obj.overrideshotn;
           else
             localshot = shot;
           end
           mdsconnect(obj.mdsserver);
           [~,s] = mdsopen(obj.mdstree, localshot);
           if ~rem(s,2)
             warning('SCDclass_mdspar:MDSerror','MDS+ tree open error for parameter %s. Actualization skipped.',obj.modelparam);
             obj.actualizable=false;
           end
        end
        
        function obj=setactualizecmds(obj)
           pointspos=strfind(obj.modelparam,'.');
           baseparam=obj.modelparam(1:pointspos(1)-1);
           structparam=obj.modelparam(pointspos(1):end); 
           obj.assignvar=sprintf('%s.Value%s',baseparam,structparam);
           obj.assignstring=sprintf('%s=%s;',obj.assignvar,obj.getcommand);
           obj.denanstring=sprintf('%s(isnan(%s))=0;',obj.assignvar,obj.assignvar);
           obj.caststring=sprintf('%s=%s;',obj.assignvar,obj.assignvar);             
        end
        
        function obj=preactualize(obj, shot)
           if(~obj.unlinked)            
               % Opening the tree
               obj=obj.mdsconnect(shot);
               if ~obj.actualizable, return; end
               % Checking if data can be retrieved, updating get command               
               [obj, obj.value]=obj.getdata(shot);
               if ~obj.actualizable, return; end
               obj=obj.setactualizecmds;
           end
        end
        
        function obj=postactualize(obj, shot)
            if(~obj.unlinked)
                if obj.verbose==1
                    if obj.overrideshot == 1
                        localshot = obj.overrideshotn;
                    else
                        localshot = shot;
                    end
                    fprintf('Actualizing parameter: ''%s'' <- ''%s'' (%s, shot %d)\n', obj.modelparam, obj.tdiexprused, obj.classname, localshot);
                end
                
                evalin('base', obj.assignstring);
                evalin('base', obj.denanstring);
                evalin('base', obj.caststring);
            else
                fprintf('Actualizing parameter: ''%s'', parameter unlinked, SKIPPED!\n', obj.modelparam);
            end         
        end
        
        function [obj,value]=getdata(obj, shot)
            % TODO: this depends upon selected MATLAB - MDSplus interface
            % mdsipmex mdsvalueraw to return uncast MDSplus data
            % python interface mdsvalue returns incast MDSplus data 
            obj=obj.actualizegetcmd('mdsvalueraw(''%s'')', shot);  
                
            value=eval(obj.getcommand);
            if(~isnumeric(value) && ~isa(obj,'SCDclass_mdsparenum')) % TODO: better handle this exception
                warning('SCDclass_mdspar:MDSerror','MDS+ error for parameter %s: %s. Actualization skipped.',obj.modelparam, value);
                obj.actualizable=false;
            else
                obj.actualizable=true;
            end
        end
        
        function obj=actualizegetcmd(obj, cmdstring, shot)
            if(shot==-1)
                obj.getcommand=sprintf(cmdstring, obj.tdiexprmodel);
                obj.tdiexprused=obj.tdiexprmodel;
            else
                obj.getcommand=sprintf(cmdstring, obj.tdiexprshot);
                obj.tdiexprused=obj.tdiexprshot;
            end
            obj.modelparam=[obj.modeltpstruct '.' obj.modeltargetpar];
        end
        
        function obj=actualizetdiexpr(obj, shot)
            if(shot==-1)
                obj.tdiexprused=obj.tdiexprmodel;
            else
                obj.tdiexprused=obj.tdiexprshot;
            end            
        end
        
        function printinfo(obj)
            fprintf('%s (class %s):\n', obj.modelparam, obj.classname);
            fprintf('  Simulink model: ''%s'', data dictionary: ''%s''\n', obj.modelname, obj.datadictionary);
            if(~obj.unlinked)
                fprintf('  MDS+ source server: ''%s'', Tree: ''%s''\n', obj.mdsserver, obj.mdstree);
                fprintf('  MDS+ TDI expressions, model: ''%s''', obj.tdiexprmodel);
                if(strcmp(obj.tdiexprmodel, obj.tdiexprshot))
                    fprintf(', shot: same');
                else
                    fprintf(', shot: ''%s''', obj.tdiexprshot);
                end
                if obj.overrideshot == 1
                    fprintf(', WARNING: shot number override to %d.\n', obj.overrideshotn);
                else
                    fprintf('.\n');
                end
            else
                fprintf('  UNLINKED!\n');
            end
            
        end
        
        function [mdsserver] = getMDSserver(obj)
          mdsserver = obj.mdsserver;
        end
        
        function [mdstree] = getMDStree(obj)
          mdstree = obj.mdstree;
        end
        
        function obj = setparamstructure(obj, structname)
            assert(nargout==1,'must set out argument for set method')

            %obj.modelparam = [structname '.' obj.modelparam];
            if(isempty(obj.modeltpstruct))
                obj.modeltpstruct = structname;
            end
            obj.modelparam=[obj.modeltpstruct '.' obj.modeltargetpar];
        end
        
        function obj = setmodelname(obj, modelname)
          assert(nargout==1,'must set out argument for set method')
          obj.modelname = modelname;
        end
        
        function obj = setdatadictionary(obj, ddname)
          assert(nargout==1,'must set out argument for set method')
          obj.datadictionary = ddname;
        end
    end
    
    methods(Sealed) % works for heterogeneous class arrays
        function out = getmodelparam(obj)
            if numel(obj) == 1
              out = obj.modelparam;
            else
              out = {obj.modelparam}';
            end
        end
        
        function out = gettargetparammarte(obj)
          % convert model param list into version with marte separators
          out = strrep(obj.modelparam, obj.matlabseparator, obj.marteseparator);         
        end
             
        function obj = bind(obj,modelname,datadictionary,exportedtp)
          for ii=1:numel(obj)
            obj(ii) = obj(ii).setmodelname(modelname);
            obj(ii) = obj(ii).setdatadictionary(datadictionary);
            obj(ii) = obj(ii).setparamstructure(exportedtp);
          end
        end
    end
    
    methods 
        
        function entrystring = genMARTe2entrycommon(obj, shot)
            obj=obj.actualizetdiexpr(shot);
            entrystring = sprintf('+%-50s = { Class=%-30s Path=%-40s',obj.gettargetparammarte,obj.marteclassname,obj.genMARTe2MDStdiexpression);
            
        end
        
        function str = genMARTe2MDSsourcestr(obj)
          %str = sprintf(' +MDSSource = {\n  Class=SPCMDSSource\n  Server=%s\n  Tree=%s',obj.mdsserver,obj.mdstree);
          str = sprintf(' +Connection_%s_%s = {\n  Class=MDSObjConnection\n  Server=%s\n  Tree=%s',obj.mdsserver,obj.mdstree,obj.mdsserver,obj.mdstree);        
        end

        function str = genMARTe2MDStdiexpression(obj)
            % Duplicate first backslash
%             if(obj.tdiexprused(1)=='\' && not(obj.tdiexprused(2)=='\'))
%                 martetdi=['\' obj.tdiexprused];
%             else
%                 martetdi=obj.tdiexprused;
%             end
            % Duplicate backslashes
            martetdi=strrep(obj.tdiexprused, '\', '\\');
            %substitute every " with '
            martetdi=strrep(martetdi, '"', '''');
            %put double string quota
            martetdi=['"' martetdi '"'];
            str=martetdi;
        end
        
        function str = genTDIpopulatecmd(obj)
            obj=obj.actualizegetcmd('%s', 1);
            tdistr = 'rtcalgo_putparam(';
            tdistr = [tdistr '''' obj.tdiexprused ''','];
            str=tdistr;
            
        end
         
        function autopopulatemds(obj, shot)
            
            % original TDI:
            %   _nodeval = _node//'.value';
            %   _nodecom = _node//'.comment';
            %   _nodevalid = _validity;
            %   _expr = 'build_param('//_nodeval//','//_nodecom//','//_nodevalid//')';
            %   TreePut(_node,_expr);
            %   TreePut(_nodeval,'$', _value);
            %   TreePut(_nodecom,'$', _comment);
            
            
            obj=obj.actualizegetcmd('%s', shot);
            node = obj.tdiexprused;
            nodeval = [node '.value'];
            nodecom = [node '.comment'];
            
            % workspace version of tunparams
            valsrc = [obj.modeltpstruct '.Value.' obj.modeltargetpar];
            val = evalin('base', valsrc);
              
            % template version of tunparams
            %valsrc = [obj.modeltpstruct '_tmpl.Value.' obj.modeltargetpar]
            %val = Simulink.data.evalinGlobal(obj.modelname, valsrc);
            
            fprintf('Changing MDS node: ''%s'' <- %s\n',node, valsrc);
            obj.mdsconnect(shot);
            
            writeval = ['build_param(' nodeval ',' nodecom ',' obj.mdsvalidationstr ')'];
            retval=mdsput(node, writeval, 'x');
            if isnumeric(retval)
                if ~rem(retval,2)
                    warning('SCDclass_mdspar:MDSerror','Error writing node build_param.');
                end
            else
                warning('SCDclass_mdspar:MDSerror','Error writing node build_param.');
            end
            
            retval=mdsput(nodecom, obj.mdshelpstr);
            if isnumeric(retval)
                if ~rem(retval,2)
                    warning('SCDclass_mdspar:MDSerror','Error writing node comment.');
                end
            else
                warning('SCDclass_mdspar:MDSerror','Error writing node comment.');
            end
            
            writeval=obj.casttomds(val);
            mdsput(nodeval, writeval);
            checkval=mdsvalueraw(nodeval);
            if ~any(writeval==checkval)
                warning('SCDclass_mdspar:MDSerror','Error writing node value.');
            end
            
        end
        
    end
    
    
      
    % Abstract method actually implemented by child classes
    methods (Abstract)
        
        % Gets data from MDS+ and actualizes it on the model,
        % Connects to MDS+ and opens the tree, to be
        % improved via container class of this class
        actualizedata(obj, shot)
        
        % Gets data from mds, mds connection is assumed already estabilished
        % and tree opened
        %[obj, value] = getdata(obj, shot)
        
        % Generate C++ code 
        %gencode(obj)
        
        % Generate MARTe2 configuration entry
        entrystring = genMARTe2entry(obj, shot)
        
        % Prints the parameter info summary
        %printinfo(obj)
        
        % cast for mdsput
        out = casttomds(obj, in)
        
    end
end

