classdef SCDclass_algo
    %SCD algorithm handling object
    %   The class holds all information and 
    %   methods for handling a Simulink
    %   algorithm
     
    properties (SetAccess = private, Hidden=false)
       modelname           % Name of the model
       modelslx            % slx model file name
       folder              % folder containing algorithm
       datadictionary      % Name of the used data dictionary
    end
    
    properties (SetAccess = private,Hidden=true)
       mdscontainer        % Container class for MDS+ interface objects
       taskcontainer       % Container class for init and term tasks
       exportedtps         % fList of tunable parameters variable to be exported
       refdatadictionaries % Cell array of referenced data dictionaries
       refddparentalgo     % Parent algorithm of referenced data dictionaries
       stdinits            % General initialization scripts
       fpinits             % inits scripts for fixed parameters
       timing              % Timing info structure
       exportedtpsdefaults % default tunable parameters defining functions
       exportedtpspermiss  % tunable parameters loading error permissivity rule
       buslist             % list of buses and their sources
       defaulttunparampermissivity % default tunparams loading permissivity
       wavegenbasestruct   % base struct name of the next added wavegen
       exportedwgstructs   % list of exported waveforms structs
    end
    
    properties 
      SCDBusdd_path       % location of folder for datadictionary containing SCDsignal buses
    end
    
    properties(Hidden=true, Constant)
      SCDBusdd_name = 'SCDBuses.sldd'; % name of datadictionary contining SCD signal buses
    end
      
    methods
        function obj=SCDclass_algo(name)
            %% Constructor
            
            % Empty algorithm constructor
            obj.modelname=name;
            obj.mdscontainer = SCDclass_mdsobjcontainer;
            obj.taskcontainer = SCDclass_taskcontainer;
            obj.exportedtps = {};
            obj.exportedtpsdefaults = {};
            obj.exportedtpspermiss = {};
            obj.stdinits  = {};
            obj.fpinits   = {};
            obj.datadictionary = '';           
            obj.refdatadictionaries = {};
            obj.timing.t_start = 0   ;
            obj.timing.t_stop  = 1   ;
            obj.timing.dt      = 1e-3;
            obj.buslist = [];
            obj.defaulttunparampermissivity = true;
            obj.wavegenbasestruct = '';
            obj.exportedwgstructs = {};
            
            % Buses
            obj.modelslx =  [obj.modelname,'.slx'];
            
            % files
            % algo path depending on .slx location
            assert(~isempty(which(obj.modelslx)),'can''t find slx model %s',obj.modelslx);
            obj.folder = fileparts(which(obj.modelslx));
            
            % bus where to store SCDbus data dictionary same as algo by
            % default
            obj.SCDBusdd_path = obj.folder; % default
            
            % Standard data dictionary equal to algorithm name, 
            % overriddable by the setter method
            obj=obj.setdatadictionary([name '.sldd']);
        end

        %% Print infos
        function printinfo(obj)
           fprintf('*****************************************************\n');
           fprintf('* SCD algorithm: ''%s''\n',obj.modelname); 
           fprintf('*****************************************************\n');           
           if(~isempty(obj.datadictionary))
               fprintf('* Data dictionary:\n  %s\n',obj.datadictionary);
           else
               fprintf('* Data dictionary:\n  base workspace\n');
           end
           if numel(obj.exportedtps)>0
               fprintf('* Tunable params structs:\n');
               for ii=1:numel(obj.exportedtps)
                   fprintf('  %s\n',obj.exportedtps{ii});
               end
           end
           fprintf('* Tunable params objects:\n')
           obj.printparameters;
           fprintf('* Wavegen objects:\n');
           obj.printwavegens;
           fprintf('* Tasks objects:\n');
           obj.printtasks;
           fprintf('* Fixed parameters inits:\n');
           obj.printinits;
           fprintf('* Buses: \n');
           obj.printbuslist;
           fprintf('*****************************************************\n');                      
        end
        
        function printinits(obj)
          fprintf('****** Registered initialization functions ******* \n')
          if(~isempty(obj.fpinits))
            for ii=1:numel(obj.fpinits)
              if isempty(obj.fpinits{ii}{2})
                fprintf('%s : init function with no outputs\n',char(obj.fpinits{ii}{1}));
              else
                fprintf('%s -> %s in workspace %s \n',char(obj.fpinits{ii}{1}),char(obj.fpinits{ii}{2}{1}),char(obj.fpinits{ii}{3}));
              end
            end
          end
        end
        
        function printMARTe2parconfig(obj, shot)   
            %obj.mdscontainer.modeltogenerate=obj.modelname;
            obj.mdscontainer.modeltogenerate='all';            
            obj.mdscontainer.printMARTe2parconfig(shot);
        end

        function overalldim = printMARTe2wgbusconfig(obj, shot, busname, frequency, varargin)
            % printMARTe2wgbusconfig(obj, shot, busname, frequency, varargin)
            %
            % prints cfg file for loading busname Simulink.Bus
            % as a wavegen (or a set of them) in MARTe2
            % shot can be -1 or a fixed shot (usually -1), but currently
            % the Shot= entry is populated by a fixed macro
            % frequency is the frequency of signal generation in MARTe2
            % the optional parameter 'ddname' can be given to specify
            % the data dictionary where the bus definition is, if omitted
            % the default expcode level data dicationary is used
            
            p=inputParser;
            addParameter(p,'ddname',obj.datadictionary,@(x) ischar(x));
            parse(p,varargin{:});
            myddname = p.Results.ddname;
            
            overalldim = obj.mdscontainer.printMARTe2wgbusconfig(shot, myddname, busname, frequency);               
        end
        

        %% General purpose getters
        
        function out = getname(obj)
            out=obj.modelname;
        end

        function out = getmdscontainer(obj)
            out=obj.mdscontainer;
        end
            
        function out = gettaskcontainer(obj)
            out=obj.taskcontainer;
        end
        
        function out = gettiming(obj)
            out=obj.timing;
        end
        
        function [stdinits, fpinits] = getinits(obj)
            stdinits = obj.stdinits;
            fpinits  = obj.fpinits;
        end
        
        function refdd = getrefdd(obj)
          refdd = obj.refdatadictionaries;
        end
        
        function refddparent = getrefddparentalgo(obj)
          refddparent = obj.refddparentalgo;
        end
                
        %% Setup functions setter and getter
        function obj = addstdinitfcn(obj,fcnhandle)
         if ischar(fcnhandle)
           fcnhandle = str2func(fcnhandle);
         end
         assert(isa(fcnhandle,'function_handle'),'stdinit function must be a function handle');
         assert(nargout(fcnhandle)<=0,'stdinit functions may not have output arguments')
         obj.stdinits{end+1} = fcnhandle;
        end
       
        function obj = addfpinitfcn(obj, fcnname, targetstruct,targetworkspace)
         if nargin < 4
           targetworkspace = 'datadictionary';
         end
         if ~iscell(targetstruct) && ~isempty(targetstruct)
           targetstruct = {targetstruct};
         end
         
         for ii=1:numel(obj.fpinits)
           for istruct = 1:numel(targetstruct)
             mytarget = targetstruct{istruct};
             if ~isempty(mytarget) && contains(obj.fpinits{ii}{2},mytarget)
               warning('SCDclass_algo:addfpinitfcn',...
                 'A function defining the structure %s has already been added, ignoring.\n',mytarget)
               return
             end
           end
         end
         
         % FF question to CG: why is this a cell and not a struct?
         temp=cell(10,1);
         temp{1}=fcnname;
         temp{2}=targetstruct;
         temp{3}=targetworkspace;
         obj.fpinits{end+1}=temp;
        end
       
       %% Bus setter and getter
       function obj = addbus(obj,name,source)
         assert(~isequal(name,source),'bus source file and name may not be the same')
         % Register a bus from a file to be added to dd
         ii = numel(obj.buslist)+1;
         obj.buslist(ii).name   = name;
         obj.buslist(ii).source = source;
       end
       
       function list = getbuslist(obj)
          list = obj.buslist;
       end
       
       function printbuslist(obj)
         list = getbuslist(obj);
         fprintf('******* Registered bus definitions files for %s: *****\n',obj.modelname)
         fprintf('%-25s %-15s %-50s\n','Name','Type','Source')
         for ii=1:numel(list)
           if isa(list(ii).source,'function_handle')
             type = 'function handle';
             fcs = functions(list(ii).source);
             source = fcs.file;
             name = 'DEFINED IN FILE';
           else
             type = 'file';
             name = list(ii).name;
             source = which([list(ii).source,'.m']);
           end
           if isempty(source), source='NOT FOUND'; end
           fprintf('%-25s %-15s %-50s\n',name,type,source); 
         end
         fprintf('\n');
       end
           
        %% Data dictionary setter and getter
        function obj = setdatadictionary(obj, datadict)
            obj.datadictionary=datadict;
        end
        
        function out = getdatadictionary(obj)
            out = obj.datadictionary;
        end

        function obj = addrefdd(obj,refdd,parentalgo)
          % refdd: name of referenced data dictionary
          % parentalgo: (optional), SCDclass_algo object of algorithm
          % generating this data dicationary.
          if nargin<3, parentalgo=''; end
          
          assert(isequal(refdd(end-4:end),'.sldd'),'refdd must end in .sldd')
          obj.refddparentalgo{end+1} = parentalgo;
          obj.refdatadictionaries{end+1} = refdd;
        end
        
        %% Wavegens setter and getters
        function obj = addwavegenbasetruct(obj, basestruct)
            obj.exportedwgstructs{end+1}=basestruct;
        end
        
        %% Data dictionary manipulation manipulations
        function addbusestodd(obj)
          % Add buses to data dictionary from .m file descriptions
          dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
          designDataObj = getSection(dictionaryObj, 'Design Data');

          SCDBusdd_populated = false; % flag to signal whether SCDBus data dictionary was populated
          busList = obj.getbuslist;
          for ii=1:numel(busList)
            mybusSource = busList(ii).source; 
            busName = busList(ii).name;

            if ischar(mybusSource)
              % User specified a file that contains a bus definition
              % (typically exported from bus editor)
              fprintf('Adding bus %s from %s to %s\n',busName,mybusSource,obj.getdatadictionary);
              
              assert(logical(exist(mybusSource,'file')),'%s does not exist',mybusSource)
              
              eval(mybusSource); % eval bus script here
              
              assert(exist(busName,'var')~=0,...
                'no variable %s found despite running script %s',busName,which(mybusSource))
              names{1} = busName; buses{1} = eval(busName);
              sourcename = mybusSource;
            elseif isa(mybusSource,'function_handle')
              % user specified a function that returns cell arrays of
              % bus names and bus objects. busNames is ignored in this case
              [names,buses] = mybusSource();
              sourcename = func2str(mybusSource);
              
            else
              error('don''t know how to handle %s',class(mybusSource))
            end
            
            for jj=1:numel(buses) % loop over buses to be added
              if startsWith(names{jj},'SCDBus_')
                if ~SCDBusdd_populated % first call
                  obj.createdatadictionary(obj.SCDBusdd_name,obj.SCDBusdd_path);
                  dictionaryObj_SCDbus = Simulink.data.dictionary.open(obj.SCDBusdd_name);
                  designDataObj_SCDbus = getSection(dictionaryObj_SCDbus, 'Design Data');
                  
                  % add link to algo dictionary if necessary
                  if ~ismember(obj.SCDBusdd_name,dictionaryObj.DataSources) % does not exist, try to add
                     fprintf('adding referenced data dictionary %s to %s \n',obj.SCDBusdd_name,obj.getdatadictionary)
                     dictionaryObj.addDataSource(obj.SCDBusdd_name);
                  end
                  SCDBusdd_populated = true;
                end
                % If SCDBus, add to general SCDbus data dictionary
                dd_target            = obj.SCDBusdd_name;
                designDataObj_target = designDataObj_SCDbus;
                dictionaryObj_target = dictionaryObj_SCDbus;
              else % add to algo dd
                dd_target            = obj.getdatadictionary;
                designDataObj_target = designDataObj;
                dictionaryObj_target = dictionaryObj;
              end
              
              fprintf('adding bus %25s from function %s to %s\n',...
                names{jj},sourcename,dd_target)
              obj.replaceorcreateddentry(designDataObj_target,names{jj},buses{jj});
            end
          end
          
          if dictionaryObj.HasUnsavedChanges
            dictionaryObj.saveChanges;
          end % Save if necessary
          
          if SCDBusdd_populated && dictionaryObj_SCDbus.HasUnsavedChanges
            dictionaryObj_target.saveChanges;
          end % Save if necessary
        end
        
        function addrefddtodd(obj)
          
          % other optional data dictionaries (e.g. used by lower-level components of an algorithm)
          % specified in refdatadictionaries
          
          dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
          %refddlist = [obj.refdatadictionaries,'configurations.sldd']; % list of referenced dds
          refddlist = obj.refdatadictionaries; % list of referenced dds
          
          for ii=1:numel(refddlist)
            refdd = refddlist{ii};
          
            if ~ismember(refdd,dictionaryObj.DataSources) % does not exist, try to add
              fprintf('adding referenced data dictionary %s to %s \n',refdd,obj.getdatadictionary)
              refddpath = which(refdd);
              if isempty(refddpath) % data dictionary to link does not exist, try to generate it
                parentalgo = obj.refddparentalgo{ii};
                if isempty(parentalgo)
                  error('Data dictionary %s was not found but no parent algorithm is specified',refdd{ii})
                else
                  fprintf('Data dictionary %s was not found, running init of parent algorithm %s to create it\n',parentalgo.getname)
                  parentalgo.SCDBusdd_path = obj.SCDBusdd_path; % use same SCDbusdd path as parent
                  % run parent algo init
                  parentalgo.init;
                  % check expected dd now exists
                  refddpath = which(refdd);
                  assert(~isempty(refddpath),...
                    'Data dictionary still not found despite running %s init, aborting',parentalgo.getname)
                end
              end
              fprintf('Adding referenced data dictionary %s to %s\n',refdd,obj.getdatadictionary)
              dictionaryObj.addDataSource(refdd);
            else % already exists
              fprintf('Data dictionary %s is already referenced in algo data dictionary, not adding\n',refdd);
            end
          end
          if dictionaryObj.HasUnsavedChanges, dictionaryObj.saveChanges; end
        end
        
        function replaceorcreateddentry(obj,designDataObj,entry,value)
          if designDataObj.exist(entry)
            oldEntry = designDataObj.getEntry(entry);
            assert(numel(oldEntry)==1,'multiple entries found for %s',entry)
            if isequal(oldEntry.getValue,value)
              fprintf('%s: keep old value of %s since not changed\n',obj.getname,entry);
            else
              oldEntry.setValue(value); % replace
              fprintf('%s: replaced value of %s since it changed\n',obj.getname,entry);
            end
          else
            fprintf('   %s: added new %s\n',obj.getname, entry);
            designDataObj.addEntry(entry,value);
          end
        end
        
        %% Tunable parameters structures handling functions
        
        function out = getexportedtps(obj)
            out = obj.exportedtps;
        end
        
        function obj = addtunparamstruct(obj, structname, default_function, load_permissive)
            % obj = addtunparamstruct(structname, varargin)
            %
            % adds a tunable parameter to the algo object
            % inputs:
            %  structname:  name of the tunable param (string)
            %  default_function: a function handle to get 
            %   its default value, this function takes no arguments
            %   and must return either a struct or a Simulink.Parameter
            %  load_permissive: a boolean value, if true any failing
            %   loading attemp to this structure (via a loading function)
            %   will return a warning, otherwise an error. Default: true.
            % 
            % example: obj=obj.addtunparamstruct('SCDalgo_doublet_tp',
            % @()SCDalgo_doublet_loadtp());
            % 
            if(~ismember(structname, obj.exportedtps))
                obj.exportedtps{end+1}=structname;
            else
                error('SCDclass_algo:addtunparamsstruct','Tunable parameter struct already present, cannot add!');
            end
            if nargin>=3
                assert(isa(default_function, 'function_handle'),'SCDclass_algo:addtunparamstruct', 'third argument, if present, must be a function handle');
                obj.exportedtpsdefaults{end+1} = default_function; % add defaults to list
            else
                obj.exportedtpsdefaults{end+1} = []; % empty, no default assigned for this tp
            end
            if nargin==4
                assert(isa(load_permissive,'logical'),'SCDclass_algo:addtunparamstruct','fourth argument, if present, must be boolean');
                obj.exportedtpspermiss{end+1} = load_permissive;
            else
                obj.exportedtpspermiss{end+1} = true;
            end
        end
               
        function obj = updatetemplatetp(obj)
            % obj = updatetemplatetp()
            %
            % For every configured tunparams structure
            % if a default value function is present it is
            % updated in the template version of the tun params
            % in the algo data dictionary.
            % The default value function of a tunparams can be given
            % as an optional third argument of addtunparamstruct
            % method
            dict = Simulink.data.dictionary.open(obj.getdatadictionary);
            designDataobj = getSection(dict, 'Design Data');
            for ii=1:numel(obj.exportedtps)
                if ~isempty(obj.exportedtpsdefaults{ii})
                    % get current value of parameters from the function
                    % handle
                    fprintf('   %s: calling %s\n', obj.getname, char(obj.exportedtpsdefaults{ii}));
                    TP=obj.exportedtpsdefaults{ii}();
                    if ~isa(TP,'Simulink.Parameter')
                        assert(isstruct(TP),'default function must return either a Simulink.Parameter or a structure');
                        P = Simulink.Parameter;
                        P.Value = TP;
                    else
                        P = TP;
                    end
                    P.CoderInfo.StorageClass='ExportedGlobal';
                    % updates default tunable parameters structure in data dictionary
                    tmplname = sprintf('%s_tmpl',obj.exportedtps{ii});
                    if designDataobj.exist(tmplname)
                      oldEntry = designDataobj.getEntry(tmplname);
                      % isequal does not detect type changes
                      %if isequal(oldEntry.getValue.Value,P.Value)
                      %  fprintf('%s: keep old template %s since not changed\n',obj.getname,tmplname);
                      %  continue;
                      %else
                      %  % replace
                      %  oldEntry.setValue(P);
                      %  fprintf('%s: replaced value of template %s since it changed\n',obj.getname,tmplname)
                      %end
                      oldEntry.setValue(P);
                      fprintf('%s: replaced value of template %s\n',obj.getname,tmplname)   
                    else
                        fprintf('   %s: added new %s\n',obj.getname, tmplname);
                        designDataobj.addEntry(tmplname,P);
                    end
                end
            end
        end
        
        function buildworkspacetpstruct(obj)
            % buildworkspacetpstruct(obj)
            % 
            % This funtion builds workspace structures containing
            % replicas of all tunable parameters structures in the data
            % dictionaries, this structure is the one actually used 
            % for loading simulation data from MDS.
            %
            % It is better not to use directly data dictionaries structures
            % to avoid flooding dds with big sim data sets (and
            % consequently the SCD GIT itself
           
            if(isempty(obj.datadictionary))
                warning('SCDclass_algo:buildworkspacetpstruct','Methods ignored for this object, data dictionary isn''t configured');
                return;
            end
                
            dd=SCDconf_getdatadict(obj.datadictionary);
            
            for ii=1:numel(obj.exportedtps)
              tp_name = obj.exportedtps{ii};
              hasdefault = ~isempty(obj.exportedtpsdefaults);
              if hasdefault
                tp_tmpl_name=sprintf('%s_tmpl',tp_name);
                try
                  tp_value = dd.getEntry(tp_tmpl_name).getValue;
                catch ME
                  fprintf('error getting %s from dd %s\n',tp_tmpl_name,obj.datadictionary)
                  rethrow(ME)
                end
                assignstr=sprintf('%s=temp;',tp_name);
                assignin('base','temp',tp_value);
                evalin('base',assignstr);
              else % no default template defined, define from generating function handle
                fprintf('no default defined for %s\n',tp_name)
              end
            end
            evalin('base','clear temp;'); %cleanup
        end
        
        function buildworkspacesimdata(obj)
            % buildworkspacesimdata(obj)
            % 
            % This function builds a SCDsimdata structure
            % containing the necessary timeseries to support
            % a standalone simulation of the algorithm
            %
            % Currently it prepares a timeseries structure
            % to be filled with the wavegens configured within
            % the algorithm
            
            simdata = struct;
            numwavegens=obj.mdscontainer.getnumwavegens();
            if numwavegens>0
               %obj.mdscontainer=obj.mdscontainer.setwavegenbasestruct([obj.getname '_simdata']);   
               for wgidx=1:numwavegens
                  wg=obj.mdscontainer.mdswavegens(wgidx);                  
                  eval(['simdata.' wg.gettarget '=timeseries(wg.castdata([0;0]),[0;1]);']);
                  assignin('base',wg.getbasestruct,simdata);
               end
               %assignin('base',wg.getbasestruct,simdata);
            else
                warning('SCDclass_algo:buildworkspacesimdata','No wavegens configured for this algo, nothing  to do.');
                return;
            end 
        end
        
        %% MDS container methods forwarders
        
        function obj = addparameter(obj, params)
          % obj = addparameter(obj, params)
          % 
          % this method offers the way to add tunable
          % parameters description to the object.
          % params must resolve to a SCDDS loader class
          % object
          %
          % assume this parameter is bound to last exported tp on the
          %
          % example:
          % 
          % obj=obj.addtunparamstruct('algo_template_tp', @()algo_template_loadtp());
          % obj=obj.addparameter(SCDclass_mdsparnumeric('ks1','gain'               );
          %
          % the couples of lines before first add a tunable parameter
          % structure and the a numeric type loader class.
          % The final result is that this logical link between
          % the source param and dest Simulink.Parameter object
          % will be put in place:
          %
          % algo_template_tp.gain <-> ks1
          
          if ~isempty(obj.exportedtps), tpname = obj.exportedtps{end};
          else, tpname = '';
          end
          
          if ~isempty(obj.exportedtpspermiss), permiss = obj.exportedtpspermiss{end};
          else, permiss = obj.defaulttunparampermissivity;
          end
          
          % bind parameter(s) to algoobj's name, dictionary and exportedtp
          params = params.bind(obj.modelname, obj.datadictionary, tpname, permiss);
          
          % add params to mdscontainer
          obj.mdscontainer=obj.mdscontainer.addparameter(params);
        end
        
        function obj = printparameters(obj)
            obj.mdscontainer=obj.mdscontainer.printparameters;
        end
        
        function obj = actualizeparameters(obj, shot)
            obj.mdscontainer=obj.mdscontainer.actualizeparameters(shot);
        end
        
        function obj = addwavegen(obj, wavegen)
          if ~isempty(obj.exportedwgstructs), wgbasename = obj.exportedwgstructs{end};
          else, wgbasename = '';
          end
          % bind wavegen to modelname, dd, algo timing and base struicture
          % name
          for ii=1:numel(wavegen)
            wavegen(ii) = wavegen(ii).bind(obj.modelname,obj.datadictionary,obj.timing,wgbasename);
          end
          % add to algo's mds object container
          obj.mdscontainer=obj.mdscontainer.addwavegen(wavegen);
          % set default wavegen base structure name
%           if isempty(obj.wavegenbasestruct)
%             obj.mdscontainer=obj.mdscontainer.setwavegenbasestruct([obj.getname '_simdata']);
%           else
%             obj.mdscontainer=obj.mdscontainer.setwavegenbasestruct([obj.wavegenbasestruct '_simdata']);
%           end
        end
        
        function obj = printwavegens(obj)
            obj.mdscontainer=obj.mdscontainer.printwavegens;
        end
        
        function obj = actualizewavegens(obj, shot)
            obj.mdscontainer=obj.mdscontainer.actualizewavegens(shot);
        end
        
        function obj = cleanwavegens(obj)
            obj.mdscontainer=obj.mdscontainer.cleanwavegens;
        end
        
        function actualize(obj, shot)
            % actualize(shot)
            %
            % this method actualizes (fetches from source
            % and updates values) all defined
            % tunable parameteres (previously added
            % with the addparameter method) and
            % waveforms (previously added via the addwavegen
            % method)
           
            actualizeparameters(obj, shot);
            actualizewavegens(obj, shot);
        end 
        
       
        %% Task container methods forwarders
        function obj = addtask(obj, task)
            obj.taskcontainer=obj.taskcontainer.addtask(task);
            obj.taskcontainer=obj.taskcontainer.bindlasttask(obj.modelname, obj.datadictionary);
        end
        
        function obj = printtasks(obj)
            obj.taskcontainer=obj.taskcontainer.printtasks;
        end
         
       %% Timing information
       function obj = settiming(obj, t_start, dt, t_stop)
           obj.timing.t_start=t_start;
           obj.timing.dt=dt;
           obj.timing.t_stop=t_stop;
       end
       
       %% Initializations
       function initdd(obj)
         % setup data dictionary
         
         % generate data dictionary if it does not exist yet
         obj.createdatadictionary(obj.getdatadictionary,obj.folder);
         
         % link data dictionary to model and enable access to base
         obj.load;
         
         % call fixed parameter setup functions
         obj.callfpinits;
         
         % populate with template tunable parameters
         obj.updatetemplatetp;

         % add referenced data dictionaries to obj data dictionary
         obj.addrefddtodd
         
         % add buses
         obj.addbusestodd;
       end
       
       function callinits(obj)
         for ii=1:numel(obj.stdinits)
           fprintf('calling standard init function %s\n',func2str(obj.stdinits{ii}));
           initfunction = obj.stdinits{ii};
           initfunction(); % call it
         end
       end
       
       function callfpinits(obj)
         % call initialization functions that set fixed parameters
         if ~isempty(obj.fpinits)
           for ii=1:numel(obj.fpinits)
             
             initfunction = obj.fpinits{ii}{1};
             targetnames  = obj.fpinits{ii}{2};
             workspace    = obj.fpinits{ii}{3};
             
             nout = numel(targetnames);
             
             if ~isempty(targetnames)
               fprintf('Calling fixed parameter init function ''%s'', assigning its output to ''%s'' in %s...\n',...
                 char(initfunction),cell2mat(targetnames),workspace);
             end
             if ischar(initfunction)
               initcmd=sprintf('%s(obj);', initfunction);
               [value{1:nout}] = eval(initcmd);
             elseif isa(initfunction,'function_handle')
               if nargin(initfunction)==1
                 argins={obj};
               elseif nargin(initfunction)==0
                 argins = {};
               else
                 error('unexpected number of input arguments for function %s',func2str(initfunction));
               end
               [value{1:nout}] = initfunction(argins{:}); % function has an input argument
             else
               error('initfunction must be a string or a function handle')
             end
             
             % assigns in target datadictionary depending
             % on target workspace
             fprintf('opening %s\n',obj.getdatadictionary);
             dictionaryObj = Simulink.data.dictionary.open(obj.getdatadictionary);
             dd = getSection(dictionaryObj, 'Design Data');
             for iout = 1:nout
               % cycle over number of output arguments of the function
               val = value{iout};
               target = targetnames{iout};
               if strcmp(workspace,'datadictionary')
                 % assign in associated data dictionary
                 obj.replaceorcreateddentry(dd,target,val);
               elseif strcmp(workspace,'base')
                 % assign in base workspace
                 assignin('base',target,val)
               end
             end
             if dictionaryObj.HasUnsavedChanges, dictionaryObj.saveChanges; end
           end
         end
       end
       
       function callrefmodelinits(obj,modelname)
         % call initialization functions of referenced models in hierarchy
         refmodels = find_mdlrefs(modelname);
         for model = refmodels
           mymodel = model{:};
           load_system(mymodel);
           if isequal(mymodel,modelname) % only init if model itself
             hws = get_param(mymodel,'modelworkspace');
             if isequal(hws.DataSource,'MATLAB File')
               fprintf('\n**Initializing model workspace for %s from file %s **\n',mymodel,hws.FileName)
               hws.reload; % reload workspace data from file
             end
           else
             obj.callrefmodelinits(mymodel); % recursive call to go down the hierarchy
           end
         end
       end
       
       %% generic operation methods
       function init(obj)
           SCDconf_setConf('SIM');
           obj.callinits; % call generic algorithm init functions 
           obj.initdd; % setup data dictionary
           obj.callrefmodelinits(obj.getname); % setup workspaces for referenced models
       end
       
       function setup(obj)
          % setup()
          %
          % calls updatetemplatetp  
          % buildworkspacetpstruct
          % buildworkspacesimdata
          %
          % an algorithm block diagram
          % should pass ctrl-d (instandalone opening)
          % after this call
          obj.updatetemplatetp;
          obj.buildworkspacetpstruct;
          obj.buildworkspacesimdata;
          SCDconf_setConf('sim');
       end
 
       function compile(obj)
         try
           eval(sprintf('%s([],[],[],''compile'')',obj.modelname));
         catch ME
             try
                eval(sprintf('%s([],[],[],''term'')',obj.modelname)); % terminate if necessary
             catch
                rethrow(ME); % rethrow error so we can see it
             end
         end
         eval(sprintf('%s([],[],[],''term'')',obj.modelname)); % terminate is successful
       end
       
       function sim(obj)
         sim(obj.modelname);
       end
       
       function test_harness(obj)
         harnessname = sprintf('%s_harness_run',obj.getname);
         if ~exist(harnessname,'file')
           warning('no harness %s found, skipping test',harnessname);
           return
         else
           fprintf('running test harness %s\n',harnessname);
           run(sprintf('%s(obj)',harnessname)); % input_init + sim + check_result
           % add tests to check output result somehow (TBD)
         end
                  
       end
       
       function open(obj)
         open(obj.modelname)
       end
       
       function load(obj)
         load_system(obj.modelname);
       end
       
       function updatemds(obj, shot)
            %if shot~=-1
            %    error('SCDclass_expcode:updatemds','update permitted only on the model shot');
            %end
            
%             % first update algorithm tunparams default locally
%             for ii=1:numel(obj.algonamelist)
%                if strcmp(algo, obj.algonamelist{ii})
%                    obj.algos{ii}.updatetemplatetp();
%                end
%             end
%             % then update mds
            obj.mdscontainer.modeltogenerate=obj.modelname;
            obj.mdscontainer.autopopulateMDSparams(shot);         
            obj.mdscontainer.autopopulateMDSwavegens(shot);
        end
       
    end
    
    methods(Static,Hidden=true)
      function createdatadictionary(ddname,folder)
        % Create data dictionary in path folder/ddname
        dd_fullpath = fullfile(folder,ddname);

        % Close this model's data dictionary
        if contains(Simulink.data.dictionary.getOpenDictionaryPaths,ddname)
          Simulink.data.dictionary.closeAll(ddname);
        end
        
        % Create if not existing already
        if isempty(which(dd_fullpath))
          fprintf('generating data dictionary %s\n',dd_fullpath);
          Simulink.data.dictionary.create(dd_fullpath);
          ddname=Simulink.data.dictionary.open(dd_fullpath);
          ddname.EnableAccessToBaseWorkspace=1;
        end
      end

    end
end

