classdef SCDclass_expcode
    %An SCD expcode object
    %   
    % Main methods:
    %  .open: Open baseline .slx model
    %     open(N): Open node N
    %     open(N,M): Open thread M of node N
    % .build, build(N), build(N,M): Generate C code for node N, thread M
    % .compile, compile(N), compile(N,M): Compile Simulink model for node N, thread M)
    % .close_all: Close all Simulink block diagrams 
    % 
    % .printinfo: Print information about components of this expcode
    % .printtasks: Print configured tasks
    % .printwavegens: Print configured wavegen signals
    %. 

    properties (Access = public)
        % Main properties
        name              % Expcode main name
        maincode          % Expcode numerical code
        status            % Expcode development status  
    end
    
    properties (Access = protected)
        loadverbose       % Verbosity level of the loading (currently 0 or 1)                
    end
    
    properties (Access = private)     
        activenodes         % numerical list of configured active nodes
        activecpus          % numerical list of configured CPUs
        %nodeddlist         % list of data dictionaries at node level
        wrapperlist         % list of configured wrappers
        initobjlist         % list of loaded algorithm objects with configured inits
        algonamelist        % List of loaded algorithm names 
        algoddlist          % list of data dictionaries at algorithm level
        mdscontainer        % container class for MDS+ interface objects
        taskcontainer       % container class for generic init and term task objects
        exportedtps         % list of tunable parameters variable to be exported
        fpinits             % list of standard inits scripts
        modeltoactualize    % name of the algorithm to actualize ('all' means all coonfigured) 
        algos               % list of algorithms objects
        nodes               % array of node properties structs
        ddname              % top level data dictionary name for the expcode
        ddpath              % top level data dicationary save path
    end
    
%%    
%% STATIC METHODS    
%%

    methods(Static)
      function help
        help(mfilename)
      end
      
      function node = defaultnode(nodenr)
        
        % [general part]
        node.hasrfm = 1; % all nodes have rfm
        node.datadict=sprintf('SCD_rtc_%02d.sldd',nodenr);
        node.algos = {}; % node-specific algos
        node.algodatadicts = {}; % data dictionaries for these algos
        
        switch nodenr
          case 1
            node.ncpu = 1;
            node.type = 'std1cpu2015a';
            node.timing.t_start = -4.5;
            node.timing.t_stop = 3;
            node.timing.dt = 1e-4;
            node.thperiod = 1.0000e-03;
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.initscdbeforecomp = 0;
            node.haswavegen = 1;
            node.hasethercat = 0;
            node.hasadc = 1;
          case 2
            node.ncpu = 6;
            node.type = 'std1cpu2015a';
            node.timing.t_start = -4.5;
            node.timing.t_stop = 3;
            node.timing.dt = 1e-4;
            node.thperiod = [1.0000e-03 1.0000e-03 1.0000e-03 1.0000e-03 1.0e-3 1.0e-3];
            node.buildcfg.conffile = cell(node.ncpu,1);
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.conffile{2} = 'standard';
            node.buildcfg.conffile{3} = 'standard';
            node.buildcfg.conffile{4} = 'standard';
            node.buildcfg.conffile{5} = 'standard';
            node.buildcfg.conffile{6} = 'standard';
            node.buildcfg.initscdbeforecomp = 0;
            node.haswavegen = 1;
            node.hasadc = 1;
            node.hasethercat = 1;
            node.algos = {'SCDcalib02'}; % direct node algorithms
          case 3
            node.ncpu = 4;
            node.type = '4cpus2015a';
            node.timing.t_start = -2.0;
            node.timing.t_stop = 2.75;
            node.timing.dt = 1e-3;
            node.thperiod = [1.0000e-03 1.0000e-03 1.0000e-03 1.0000e-03];
            node.buildcfg.conffile = cell(node.ncpu,1);
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.conffile{2} = 'standard';
            node.buildcfg.conffile{3} = 'standard';
            node.buildcfg.conffile{4} = 'standard';
            node.buildcfg.initscdbeforecomp = [0 0 0 0];
            node.haswavegen = 1;
            node.hasadc = 0;
            node.hasethercat = 0;
          case 4
            node.ncpu = 4;
            node.type = 'std1cpu2015a';
            node.timing.t_start = -4.5;
            node.timing.t_stop = 3;
            node.timing.dt = 1e-4;
            node.thperiod = 1.0000e-04;
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.initscdbeforecomp = 0;
            node.haswavegen = 1;
            node.hasadc = 0;
            node.hasethercat = 0;
          case 5
            node.ncpu = 1;
            node.type = 'std1cpu2015a';
            node.timing.t_start = -4.5;
            node.timing.t_stop = 3;
            node.timing.dt = 1e-4;
            node.thperiod = 1.0000e-04;
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.initscdbeforecomp = 0;
            node.haswavegen = 1;
            node.hasadc = 0;
            node.hasethercat = 0;
          case 6
            node.ncpu = 4;
            node.type = '4cpus2015a';
            node.timing.t_start = 0.0;
            node.timing.t_stop = 2.75;
            node.timing.dt = 1e-3;
            node.thperiod = [1.0000e-03 1.0000e-03 1.0000e-03 1.0000e-03];
            node.buildcfg.conffile = cell(node.ncpu,1);
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.conffile{2} = 'standard';
            node.buildcfg.conffile{3} = 'standard';
            node.buildcfg.conffile{4} = 'standard';
            node.buildcfg.initscdbeforecomp = [0 0 0 0];
            node.haswavegen = 1;
            node.hasadc = 0;
            node.hasethercat = 0;
          case 7
            node.ncpu = 4;
            node.type = '4cpus2015a';
            node.timing.t_start = -0.5;
            node.timing.t_stop = 2.5;
            node.timing.dt = 1e-3;
            node.thperiod = [1.0000e-03 1.0000e-03 1.0000e-03 1.0000e-03];
            node.buildcfg.conffile = cell(node.ncpu,1);
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.conffile{2} = 'standard';
            node.buildcfg.conffile{3} = 'standard';
            node.buildcfg.conffile{4} = 'standard';
            node.buildcfg.initscdbeforecomp = [0 0 0 0];
            node.haswavegen = 1;
            node.hasadc = 1;
            node.hasethercat = 0;
          case 8
            node.ncpu = 1;
            node.type = '4cpus2015a';
            node.timing.t_start = -0.5;
            node.timing.t_stop = 2.5;
            node.timing.dt = 1e-3;
            node.thperiod = [1.0000e-03 1.0000e-03 1.0000e-03 1.0000e-03];
            node.buildcfg.conffile = cell(node.ncpu,1);
            node.buildcfg.conffile{1} = 'standard';
            node.buildcfg.conffile{2} = 'standard';
            node.buildcfg.conffile{3} = 'standard';
            node.buildcfg.conffile{4} = 'standard';
            node.buildcfg.initscdbeforecomp = [0 0 0 0];
            node.haswavegen = 1;
            node.hasadc = 1;
            node.hasethercat = 0;
        end
        
        % CPU-dependent quantities
        node.wrapper = cell(node.ncpu,1);
        node.wrapdatadicts=cell(node.ncpu,1);
        node.varalgo = ones(node.ncpu,1);
        node.cpuactive = zeros(node.ncpu,1);
        
        % Wrapper data dictionaries
        [node.wrapdatadicts,node.wrapper] = deal(cell(node.ncpu,1)); % init
        for iwrap=1:node.ncpu
          node.wrapper{iwrap} = sprintf('SCDwrap_template%02d%02d.slx',nodenr,iwrap);
          node.wrapdatadicts{iwrap} = sprintf('SCDwrap_template%02d%02d.sldd',nodenr,iwrap);
        end
        
        % algorithm data dictionaries
        for ialgo = 1:numel(node.algos)
          node.algodatadicts{ialgo} = [node.algos{ialgo},'.sldd'];
        end
      end
      
      function build_nodes(compileslx_list)
        for mynodeslx = compileslx_list
             fprintf(' *** Buiding Node %s ***\n',mynodeslx{1});
             rtwbuild(mynodeslx{1});
        end
      end
      
      function myslx = getslxname(inode,icpu)
        if nargin==0
          myslx = 'tcv';
        elseif nargin==1
            assert(isnumeric(inode))
            myslx = sprintf('SCD_rtc_%02d',inode);
          elseif nargin==2
            myslx = sprintf('SCD_rtccode_%02d_%02d',inode,icpu);
          else
            error('invalid number of arguments');
        end
      end
      
      function mydd = getddname(inode)
        if nargin==0
          mydd = 'tcv.sldd';
        elseif nargin==1
          assert(isnumeric(inode))
          mydd = sprintf('SCD_rtc_%02d.sldd',inode);
        else
          error('invalid number of arguments');
        end
      end
      
      function close_all(saveflag)
        if nargin==0
          saveflag = 1;
        end
        assert(saveflag == 1 || saveflag == 0,'saveflag must be 1 or 0')
        if saveflag==1
          closemsg = 'Saving and Closing %s\n';
        else
          closemsg = 'Dicarding changes and closing %s\n';
        end
        
        while ~isempty(bdroot)
          fprintf(closemsg,bdroot);
          close_system(bdroot,saveflag)
        end
      end
    end

%%    
%% PUBLIC METHODS    
%%

    methods
%% Constructor
        function obj=SCDclass_expcode()
           % All templates constructor
       
            obj.name = 'template';
            obj.maincode = 1;
            obj.status = 'debug';
            obj.loadverbose = 1;
            obj.ddname = obj.getddname;
            
            hardwarepath = fileparts(which('tcv.slx')); 
            assert(~isempty(hardwarepath),'tcv.slx not found?')
            obj.ddpath = hardwarepath;

            obj.activenodes=[1 2 6 7 8];
            obj.activecpus=[ {1}; ...
                             {1:6}; ...
                             {1:6}; ...
                             {1}; ...
                             {1}; ...                             
                             {1:4}; ...
                             {1:4}; ...
                             {1}]; 
                 
            obj.algonamelist={};
            obj.algoddlist={};
            obj.wrapperlist={};
            obj.exportedtps = [];
            obj.algos={};

            obj.mdscontainer = SCDclass_mdsobjcontainer;
            obj.taskcontainer = SCDclass_taskcontainer;

            nnodes = max(obj.activenodes);
            for inode = 1:nnodes
              tmp(inode) = SCDclass_expcode.defaultnode(inode);
            end
            obj.nodes = tmp; clear tmp;
        end

%% Main expcode handling methods

        function open(~,varargin)
          openslx = SCDclass_expcode.getslxname(varargin{:});
          fprintf('Opening %s.slx\n',openslx)
          open(openslx);
        end

        function setup(obj)
           % This function sets up the TCV Simulink  model
           % to simulate this experimental code.
           % NOTE THAT it doesn't actualize tunable parameters
           % and wavegens, call actualizedata for that
           fprintf('Setting up expcode %d, ''%s'', configuring wrappers ...\n',obj.maincode,obj.name);
           obj=obj.setupwrappers;

           fprintf('Setting up main expcode data dictionary')
           obj = obj.setupmaindd;
 
           fprintf('Setting up expcode %d, ''%s'', configuring data dictionaries ...\n',obj.maincode,obj.name);
           obj.setupwrapdd;
           obj.setupalgodd;

           fprintf('Setting up variant model configuration')
           obj.setupvaralgo;

           fprintf('Setting up expcode %d, ''%s'', configuring default tunable parameters ...\n',obj.maincode,obj.name);           
           obj.updatedefaulttp;
           fprintf('Setting up expcode %d, ''%s'', configuring global data ...\n',obj.maincode,obj.name);
           obj.setupmain;
           fprintf('Setting up expcode %d, ''%s'', configuring main workspace variables ...\n',obj.maincode,obj.name);           
           obj.buildworkspacesimstruct;
           obj.buildworkspacetpstruct;
           
           % Set cache and codegen folders
           obj.setcachefolder;
           obj.setcodegenfolder;

        end

        function obj = setupmaindd(obj)
          % prepare main top-level SLDD
          
          ddfullpath = which(obj.ddname);
          if isempty(ddfullpath)
            dd = Simulink.data.dictionary.create(fullfile(obj.ddpath,obj.ddname));
          else 
            dd = Simulink.data.dictionary.open(obj.ddname);
          end
          
          % link data dictionaries for active nodes
          for ii=obj.activenodes
            mydatasource = obj.nodes(ii).datadict;
            fprintf('adding data source %s to %s\n',mydatasource,obj.ddname)
            dd.addDataSource(mydatasource);
          end
          % Set up RFM bus
          addRFMbus(obj,dd)
        end
        
        function actualize(obj, shot, varargin)
            p=inputParser;
            % if a model name is given, operations will be performed
            % only on it, otherwise they will be performed on all
            % configured models
            addParameter(p,'model','',@(x) ischar(x));
            parse(p,varargin{:});
            
            if(isempty(p.Results.model))
                obj.modeltoactualize='all';
            else
                obj.modeltoactualize=p.Results.model;
            end
            
            obj.actualizedata(shot);
            fprintf('Actualizing expcode %d, ''%s'', performing tasks ...\n',obj.maincode,obj.name);
            obj.taskcontainer.modeltoexecute=obj.modeltoactualize;
            obj.taskcontainer.exectasksoninit(shot);
        end

        function init(obj)
          % close all data dictionaries
          Simulink.data.dictionary.closeAll('-discard')
          
          if ~isempty(obj.initobjlist)
              for ii=1:numel(obj.initobjlist)
                 if nargin==1
                  obj.initobjlist{ii}.init();
                 elseif nargin==2
                   error('this use is deprecated, add tasks instead')
                 else
                   error('invalid number of arguments for callinits');
                 end
                 opendds = Simulink.data.dictionary.getOpenDictionaryPaths;
                 assert(~any(contains(opendds,obj.ddname)),...
                   'init for %s leaves %s open - this should be avoided',...
                   obj.initobjlist{ii}.getname,obj.ddname)
              end
          end
        end
        
        function updatemdsmodel(obj, algo)
           obj.updatemds(algo, -1); 
        end
                
        function compile(~,varargin)
          myslx = SCDclass_expcode.getslxname(varargin{:});
          fprintf('Compiling %s.slx\n',myslx)
          try
            eval(sprintf('%s([],[],[],''compile'')',myslx));
            eval(sprintf('%s([],[],[],''term'')',myslx));
          catch ME
            rethrow(ME)
          end
        end
        
        function sim(~,varargin)
          SCDconf_setConf('SIM')
          myslx = SCDclass_expcode.getslxname(varargin{:});
          fprintf('Simulating %s.slx\n',myslx)
          sim(myslx)
        end  

        function build(obj,varargin)
                    
          if numel(varargin)==0
            fprintf('\n=== Building all threads for %s ===\n',obj.name);
            % make list of nodes to build
            compileslx_list = cell(0);
            for inode=1:8
              nodeinfo=obj.nodes(inode);
              for icpu = 1:nodeinfo.ncpu
                if nodeinfo.cpuactive(icpu)
                  compileslx_list = [compileslx_list,...
                    SCDclass_expcode.getslxname(inode,icpu)]; %#ok<AGROW>
                end
              end
            end
            
            if isempty(compileslx_list)
              fprintf('No active nodes found, no .slx to compile. Done\n');
              return
            end
          else
            % user passed nodes,threads to compile
            myslx = SCDclass_expcode.getslxname(varargin{:});
            fprintf('building %s.slx\n',myslx)
            compileslx_list{1} = myslx;
          end
         
           % set CodeGen folder for this expcode (allows fast rebuilding)
           CodeGenFolder = obj.setcodegenfolder;
           
           % check env variable
           assert(~isempty(getenv('RTCCODE_LIBPATH')),'RTCCODE_LIBPATH environment variable needs to be defined to compile');
    
           % set configuration settings for compilation
           SCDconf_setConf('CODE');

           % Build
           try
             SCDclass_expcode.build_nodes(compileslx_list);
           catch ME
             % if fails on first attempt, clear build folder and retry
             fprintf(' **** FIRST ATTEMPT BUILDING %s FAILED **** \n',obj.name)
             fprintf('  Error message:\n %s\n',getReport(ME));
             fprintf('  Try again clearning build folder\n')
             
             system(sprintf('rm -rf %s/*',CodeGenFolder));
             SCDclass_expcode.build_nodes(compileslx_list);
           end           
        end
        %% Property access methods
        
        function nodes = getnodes(obj)
          nodes = obj.nodes;
        end
        
        %% Printing methods
        
        function printinfo(obj)
          fprintf('*****************************************************\n');
          fprintf('* SCD expcode: ''%s'', main code: %d\n',obj.name,obj.maincode);
          fprintf('*****************************************************\n');
          fprintf('* Configured wrappers:\n')
          if(numel(obj.wrapperlist)>0)
            for ii=1:numel(obj.wrapperlist)
              fprintf('  Node: %d, CPU: %d, varalgo: %d, wrapper: ''%s'', datadict: ''%s''\n', ...
                obj.wrapperlist{ii}{1}, ...
                obj.wrapperlist{ii}{2}, ...
                obj.wrapperlist{ii}{3}, ...
                obj.wrapperlist{ii}{4}, ...
                obj.wrapperlist{ii}{5} ...
                );
            end
          end
          fprintf('* Configured algorithms:\n');
          if(numel(obj.algonamelist)>0)
            for ii=1:numel(obj.algonamelist), fprintf('  ''%s''\n', char(obj.algonamelist{ii})); end
          end
          fprintf('* Configured algorithm data dictionaries:\n')
          if(numel(obj.algoddlist)>0)
            for ii=1:numel(obj.algoddlist), fprintf('  ''%s''\n', char(obj.algoddlist{ii})); end
          end
          fprintf('* Configured exported tunparams structures:\n')
          if(numel(obj.exportedtps)>0)
            for ii=1:numel(obj.exportedtps), fprintf('  ''%s''\n', char(obj.exportedtps{ii})); end
          end
          fprintf('* Configured MDS tunparams objects: %d (use printparameters method for details)\n', obj.mdscontainer.numparams);
          fprintf('* Configured MDS wavegens objects: %d (use printwavegens method for details)\n', obj.mdscontainer.numwavegens);
          fprintf('* Configured general purpose tasks: %d (use printtasks method for details)\n', obj.taskcontainer.numtasks);
          fprintf('* Configured init scripts: %d (use printinits method for details)\n', numel(obj.initobjlist));
        end
        
        function printtasks(obj)
            obj.taskcontainer.printtasks;
        end

        function printinits(obj)
           % TODO: uniform the approach between stdinits and fpinits
           % but only if fpinits will stay ...
            
           fprintf('* Configured std init scripts:\n');
           if(~isempty(obj.initobjlist))
               for ii=1:numel(obj.initobjlist)
                   [stdinitstmp,~]=obj.initobjlist{ii}.getinits;
                   if ~isempty(stdinitstmp)
                    for jj=1:numel(stdinitstmp)
                     fprintf('  %s\n',char(stdinitstmp{jj}));
                    end
                   end
               end
           end      
           
           fprintf('* Configured fp init scripts:\n');
           if(~isempty(obj.fpinits))
               for ii=1:numel(obj.fpinits)
                   fprintf('  %s -> %s\n',char(obj.fpinits{ii}{1}),char(obj.fpinits{ii}{2}));
               end
           end
           
        end

        function printMARTe2taskconfig(obj, shot, varargin)   
            p=inputParser;
            % if a model name is given, operations will be performed
            % only on it, otherwise they will be performed on all
            % configured models
            addParameter(p,'model','',@(x) ischar(x));
            parse(p,varargin{:});
            
            if(isempty(p.Results.model))
                obj.taskcontainer.modeltogenerate='all';
            else
                obj.taskcontainer.modeltogenerate=p.Results.model;
            end
            obj.taskcontainer.printMARTe2taskconfig(shot);
        end
        
        function printMARTe2parconfig(obj, shot, varargin)   
            p=inputParser;
            % if a model name is given, operations will be performed
            % only on it, otherwise they will be performed on all
            % configured models
            addParameter(p,'model','',@(x) ischar(x));
            parse(p,varargin{:});
            
            if(isempty(p.Results.model))
                obj.mdscontainer.modeltogenerate='all';
            else
                obj.mdscontainer.modeltogenerate=p.Results.model;
            end
            obj.mdscontainer.printMARTe2parconfig(shot);
        end

        function 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.ddname,@(x) ischar(x));
            parse(p,varargin{:});
            myddname = p.Results.ddname;
            
            dim=obj.mdscontainer.printMARTe2wgbusconfig(shot, myddname, busname, frequency);               
        end

        function printparameters(obj)
            obj.mdscontainer.printparameters;
        end

        function printwavegens(obj)
            obj.mdscontainer.printwavegens;
        end

%% Configuration methods       
        
        function obj = addwrapper(obj, inode, icpu, varalgo, wrappername, varargin) 
            
            p=inputParser;
            checknodes = @(x) ismember(x,obj.activenodes );
            checkcpu = @(x) ismember(x,obj.activecpus{inode});
          
            addRequired(p, 'node', checknodes); 
            addRequired(p, 'cpu', checkcpu); 
            addRequired(p, 'varalgo');
            addRequired(p, 'modelname', @(x) ischar(x));
            addOptional(p, 'datadictionary', '', @(x) ischar(x));
            
            parse(p, inode, icpu, varalgo, wrappername, varargin{:});
            
            % In data dictionary is not specified it is assumed having
            % the same name as the model
            if(isempty(p.Results.datadictionary))
                datadictionary=[p.Results.modelname '.sldd'];
            else
                datadictionary=[p.Results.datadictionary '.sldd'];
            end
            
            % First checking for duplicates
            if(numel(obj.wrapperlist)>0)
                wrappernamelist={};
                wrapperddnamelist={};
                for ii=1:numel(obj.wrapperlist)
                    wrappernamelist{end+1}=obj.wrapperlist{ii}{4};
                    wrapperddnamelist{end+1}=obj.wrapperlist{ii}{5};
                end
                if(ismember(p.Results.modelname,wrappernamelist) || ismember(p.Results.datadictionary,wrapperddnamelist))
                  warning('SCDclass_expcode:addwrapper','wrapper ''%s'' already present, ignoring.',wrappername);
                  return
                end
            end
            
            % update node info
            obj.nodes(inode).varalgo(icpu) = varalgo;
            obj.nodes(inode).wrapper{icpu} = wrappername;
            obj.nodes(inode).cpuactive(icpu) = true;
            obj.nodes(inode).active = true;
            % save in wrapper list
                
            tempwrapper=cell(10,1);
            tempwrapper{1}=p.Results.node;
            tempwrapper{2}=p.Results.cpu;
            tempwrapper{3}=p.Results.varalgo;
            tempwrapper{4}=p.Results.modelname;
            tempwrapper{5}=datadictionary;
            obj.wrapperlist{end+1}=tempwrapper;
            
        end     
            
        function obj = addalgorithm(obj, in_node, in_cpu, in_algo)
            
            % TODO: import the entrire object into a container class ??
            
            p=inputParser;
            checknodes = @(x) ismember(x,obj.activenodes );
            checkcpu = @(x) ismember(x,obj.activecpus{in_node});
          
            addRequired(p, 'node', checknodes); 
            addRequired(p, 'cpu', checkcpu); 
            addRequired(p, 'algo', @(x) isobject(x));
            
            parse(p, in_node, in_cpu, in_algo);

            node = p.Results.node;
            cpu = p.Results.cpu;
            algo = p.Results.algo;
            
            % Checking and importing algorithm name
            if(~ismember(algo.getname,obj.algonamelist))
                obj.algonamelist{end+1}=algo.getname;
                obj.algos{end+1}=algo;
                algoispresent=false;
            else
                fprintf('algorithm ''%s'' already present in the expcode, importing only wavegens',algo.getname);
                algoispresent=true;
            end
                
            % Importing algorithm MDS objects into the main mdscontainer,
            % filling base structure name
            algomdscontainer=algo.getmdscontainer;
            basewgstruct=sprintf('SCDsimdata.SCDnode%02d%02d_simdata.wavegen', node, cpu);
            algomdscontainer=algomdscontainer.setwavegenbasestruct(basewgstruct);
            obj.mdscontainer=obj.mdscontainer.importmdswavegens(algomdscontainer);
            
            if algoispresent, return; end % return if algo is already present
                
            % Importing exported tunable parameters
            obj.mdscontainer=obj.mdscontainer.importmdsparams  (algomdscontainer);
            algoexptps=algo.getexportedtps;
            if numel(algoexptps)>0
                for ii=1:numel(algoexptps)
                    if ~ismember(algoexptps{ii},obj.exportedtps)
                        obj.exportedtps{end+1}=algoexptps{ii};
                    else
                        fprintf('exported tunparams sctruct ''%s'' already present, ignoring',algoexptps{ii})
                    end
                end
            end
            
            % Importing algorithms data dictionary, only those with proper
            % name
            algodd=algo.getdatadictionary;
            if(strcmp(algodd(1:7),'SCDalgo'))
                if(~ismember(algodd,obj.algoddlist))
                    obj.algoddlist{end+1}=algodd;
                else
                    warning('SCDclass_expcode:addalgorithm','algorithm data dictionary ''%s'' already present, ignoring', algodd);
                end
            else
                % warning here ?
            end
           
            % Importing tasks 
            algotaskcontainer=algo.gettaskcontainer;
            obj.taskcontainer=obj.taskcontainer.importtaskobjects(algotaskcontainer);
            
            % Importing inits
            [stdinitstmp,fpinits]=algo.getinits;
            if(numel(fpinits)>0)
              toadd = ones(numel(fpinits),1);
              for ii=1:numel(fpinits)
                if(~isempty(obj.fpinits))
                  for jj=1:numel(obj.fpinits)
                    for kk=1:numel(obj.fpinits{jj}{2})
                      if(strcmp(char(obj.fpinits{jj}{2}{kk}),fpinits{ii}{2}))
                        warning('SCDclass_expcode:addalgorithm','An init driving the structure %s has already been added, ignoring this init',char(fpinits{ii}{2}))
                        toadd(ii)=0;
                      end
                    end
                  end
                end
                if toadd(ii)
                  temp=cell(10,1);
                  temp{1}=fpinits{ii}{1};
                  temp{2}=fpinits{ii}{2};
                  obj.fpinits{end+1}=temp;
                end
              end
              if any(toadd) % if any inits from this algoobj were taken
                obj.initobjlist{end+1}=algo; %% Add the full algorithm object here, to see if it is fine
              end
            elseif(numel(stdinitstmp)>0)
                obj.initobjlist{end+1}=algo;
            end
            
        end     
            
        function out = getexportedtps(obj)
            out = obj.exportedtps;
        end
        
        function obj = importexpcode(obj, expcode)
           % This function imports settings of an existing expcode into another
           
           warning('Not yet supported!');
           return;
           
           % tunparamstruct import
           exportedtpstoadd = expcode.getexportedtps;
           for ii=1:numel(exportedtpstoadd)
               if ~sum(ismember(exportedtpstoadd{ii},obj.exportedtps))
                   obj.addtunparamstruct(exportedtpstoadd{ii});
               end
           end
            
           % parameters import
           obj.mdscontainer=obj.mdscontainer.importmdsobjects(expcode.mdscontainer);
        end


        %% Default values setup methods 
       
        function updatedefaulttp(obj)
        % updatedefaulttp()
        %
        % updates _tmpl tunparams structures
        % for every defined updating function
        % To define an update function see SCDclass_algo.addtunparamstruct
            
            for ii=1:numel(obj.algonamelist)
%               if strcmp(algo, obj.algonamelist{ii})
                   obj.algos{ii}.updatetemplatetp();
%               end
            end
        end
       
    end

%% PRIVATE METHODS    
    
    methods (Access = private)

%% Matlab environmental setup methods       
        
        function CacheFolder = setcachefolder(obj)
          % Set Cache Folder for experimental code (avoid conflicts with other tmp files)
          CacheFolder = fullfile(fileparts(mfilename('fullpath')),'..','..',...
            'gencodes',sprintf('CacheFolder-%d',obj.maincode));
          fprintf('Setting Simulink Cache folder to %s\n',CacheFolder)
          Simulink.fileGenControl('set',...
            'CacheFolder',CacheFolder,...
            'createdir',true);
        end
        
        function CodeGenFolder = setcodegenfolder(obj)
          CodeGenFolder = fullfile(fileparts(mfilename('fullpath')),'..','..',...
            'gencodes',sprintf('CodeGenFolder-%d',obj.maincode));
          
          fprintf('Setting code generation folder to %s\n',CodeGenFolder)
          Simulink.fileGenControl('set',...
            'CodeGenFolder',CodeGenFolder,...
            'createdir',true);
        end
        
        function obj=printlog(obj,str,varargin)
          % printlog, allows sprintf()-like expressions
            if obj.loadverbose==1
               fprintf('Expcode %d, ', obj.maincode); 
               fprintf(str,varargin{:}); fprintf('\n');
            end
        end
                 
        function obj = buildworkspacetpstruct(obj)
            % this funtion builds a workspace structures containing
            % replicas of all tunable parameters structures in the data
            % dictionaries, this structure is the one actually used 
            % for loading simulation wavegen data
            % It is better not to use directly data dictionaries structures
            % to avoid flooding dds with big sim data sets (and
            % conseguently the rtccode GIT repo itself
           
            dict=Simulink.data.dictionary.open(obj.ddname);
            dd=getSection(dict, 'Design Data');
            
            for ii=1:numel(obj.exportedtps)
                simstructnamedd=sprintf('%s_tmpl',char(obj.exportedtps(ii)));
                simstructnamews=char(obj.exportedtps(ii));
                if ~dd.exist(simstructnamedd)
                  warning('tunable params structure %s not found', simstructnamedd);
                  continue
                end
                Entry = dd.getEntry(simstructnamedd);
                assert(numel(Entry)<2,'multiple entries found for %s',structnamedd);
                simstruct = dd.getEntry(simstructnamedd).getValue;
                assignstr = sprintf('%s=temp;',simstructnamews);
                assignin('base','temp',simstruct);
                evalin('base',assignstr);
            end
            evalin('base','clear temp;');            
        end
                 
        function obj = setupwrapdd(obj)
            % Sets up the data dictionaries of the wrappers to be loaded
            % on the nodes inside the main data dictionary
            
            % For every node, given the current status finds its dictionary
            % and the requred data sources, find actions to do and setup
            % wrappers data sources accordingly
            
            % It is assumed that there is only 1 wrapper and 1 wrapper data
            % dictionary active at a given time for every CPU model
            
            % Looping through every active node
            for idx_nodedds=1:numel(obj.activenodes)
               inode = obj.activenodes(idx_nodedds);
               
               % Getting node data dictionary and required data sources 
               datadictname = obj.nodes(inode).datadict;
               fprintf('opening %s\n',datadictname)
               
               nodedd = Simulink.data.dictionary.open(datadictname);
               
               % Clear all existing data sources
               nodeddsources=nodedd.DataSources;
               for ii=1:numel(nodeddsources)
                 nodedd.removeDataSource(nodeddsources{ii});
               end

               % Add all wrapper dictionaries as datasources for node dd
               reqsources_to_add = [...
                 obj.nodes(inode).wrapdatadicts;
                 obj.nodes(inode).algodatadicts];
               
               for ii=1:numel(reqsources_to_add)
                 myresource = reqsources_to_add{ii};
                 obj.printlog('Adding wrapper data source %s to %s', myresource, datadictname);
                 nodedd.addDataSource(myresource);
               end
            end  
        end
        
        function obj = setupalgodd(obj)
          
          % Getting data dictionary and required data sources
          ddObj=Simulink.data.dictionary.open(obj.ddname);
          reqsources   = obj.algoddlist;
          reqsources_toadd = reqsources(startsWith(reqsources,'SCDalgo'));
          for ii=1:numel(reqsources_toadd)
            mysource = reqsources_toadd{ii};
            obj.printlog('Adding algorithm data source %s to %s',mysource,obj.ddname);
            ddObj.addDataSource(mysource);
          end
        end
        
        function buildworkspacesimstruct(obj)
%           obj.mdscontainer.buildworkspacesimstructnode(obj,node)
%           obj.mdscontainer.buildworkspacesimstruct; % old
          
          dd=SCDconf_getdatadict(obj.ddname);
            for ii = 1:numel(obj.activenodes)
              inode = obj.activenodes(ii);
              buildworkspacesimstructnode(obj,inode,dd);
            end
            
          evalin('base', 'SCDrefdata = SCDsimdata;');  
            
        end    

        function obj = setupmain(obj)
            % sets up global configs for the expcode
            scd.expcode = obj.maincode;
            scd.timing.dt = 25e-6; % hard code for now, determine later from slowest thread
            Simulink.data.assigninGlobal('tcv','scd',scd);
        end
        
        function obj = setupwrappers(obj)
          % map obj.wrapperlist to wrapper list for each node
          for ii=1:numel(obj.wrapperlist)
            inode      = obj.wrapperlist{ii}{1};
            icpu       = obj.wrapperlist{ii}{2};
            varalgo    = obj.wrapperlist{ii}{3};
            modelname  = obj.wrapperlist{ii}{4}; %#ok<NASGU>
            wrapper_dd = obj.wrapperlist{ii}{5};
            switch inode
              case {1,8}
                assert(icpu==1,'SCDclass_expcode:setupwrappers',...
                    'not supported node%02d cpu',inode)
                obj.nodes(inode).varalgo(icpu)       = varalgo;
                obj.nodes(inode).wrapdatadicts{icpu} = wrapper_dd;
              case {2,3,4,6,7}
                assert((icpu>=1 &&  icpu <=max(obj.activecpus{inode})),...
                  'SCDclass_expcode:setupwrappers','not supported node%02d cpu',inode);
            otherwise
              warning('SCDclass_expcode:setupwrappers','not supported node');
            end
           obj.nodes(inode).varalgo(icpu) = varalgo;
           obj.nodes(inode).wrapdatadicts{icpu} = wrapper_dd;
          end
        end
    
        function addRFMbus(obj,dd)
          ddSection = dd.getSection('Design Data');
          zeroElem = Simulink.BusElement;
          zeroElem.Name = 'zero';
          
          Elems = repmat(Simulink.BusElement,numel(obj.activenodes),1);
          
          for ii = 1:numel(obj.activenodes)
            inode = obj.activenodes(ii);
            leafName    = sprintf('Node%02d_RFM' ,inode);
            subBusName  = sprintf('Bus: RFMOUT%02dbus',inode);
            
            % corresponding leaf element in main RFMIn bus
            Elems(ii) = Simulink.BusElement;
            Elems(ii).Name        = leafName;
            Elems(ii).Description = sprintf('Node %02d RFM output',inode);
            Elems(ii).DataType    = subBusName;
          end
          ii=ii+1;

          % Extra bus and leaf for Externals_RFM (non-node entities writing to RFM)
          [extBusObj,extBusName] = SCDconf_createRFMOUTextbus;
          assignin(ddSection,extBusName,extBusObj);
          
          Elems(ii) = Simulink.BusElement;
          Elems(ii).Name        = 'Externals_RFM';
          Elems(ii).DataType    = ['Bus: ',extBusName];
          Elems(ii).Description = 'Bus for data from external nodes writing to RFM';
         
          % Assemble RFM input bus
          RFMINbus = Simulink.Bus;
          RFMINbus.HeaderFile  = '';
          RFMINbus.Description = 'RFM input bus for all nodes';
          RFMINbus.DataScope   = 'Auto';
          RFMINbus.Alignment   = -1;
          RFMINbus.Elements    = Elems;
          
          assignin(ddSection,'RFMINbus',RFMINbus);

        end
        
        function obj = setupvaralgo(obj)
            % Sets up varalgo structure in main tcv data dictionary
            % according to varalgo info of the nodes

            % Getting data section of main tcv data dictionary
            fprintf('opening %s to setup variant model configurations\n',obj.ddname);
            
            d=Simulink.data.dictionary.open(obj.ddname);
            dd=getSection(d, 'Design Data');
            
            s=struct();
            % Looping through every active node
            for ii=1:numel(obj.activenodes)
              inode = obj.activenodes(ii);
              varalgo=obj.nodes(inode).varalgo';
              
              for jj=1:numel(varalgo)
                fieldname = sprintf('algo%02d%02d',inode,jj);
                s.(fieldname)=varalgo(jj);
              end
            end
            % save in data dictionary
            if dd.exist('SCDvaralgo')
              ee = dd.getEntry('SCDvaralgo'); ee.setValue(s);
            else
              dd.addEntry('SCDvaralgo',s);
            end
        end
        
        function obj = buildworkspacesimstructnode(obj,inode,dd)
          % build simulation data structure
          node = obj.nodes(inode);

          if evalin('base','exist(''SCDsimdata'',''var'')')
            SCDsimdata = evalin('base','SCDsimdata');
          else
            SCDsimdata = struct(); % assign here if empty
          end
          
          if node.haswavegen
            for ithread = 1:node.ncpu
              % get whatever is in data dictionary template in wrappers
              simstructname = sprintf('SCDnode%02d%02d_simdata',inode,ithread);
              wgbusname = sprintf('WG%02d%02dbus',inode,ithread);

              fprintf('   setting up %s',simstructname);
              if dd.exist(simstructname)
                simstruct = dd.getEntry(simstructname).getValue;
                mybus=dd.getEntry(wgbusname).getValue; % get from data dictionary
                if ~isstruct(simstruct) || ~isfield(simstruct,'wavegen')
                  fprintf('.. loaded simstruct wavegen is not compatible');
                  regenerate = true;
                elseif SCDconf_structbuscmp(simstruct.wavegen,mybus)
                  fprintf('... loaded WG from data dictionary\n');
                  regenerate=false;
                else
                  fprintf('... loaded WG structure from dd does not match bus definition...');
                  regenerate=true;
                end
              else
                regenerate=true;
                fprintf('... could not find %s in data dictionary',simstructname);
              end
              
              if regenerate
                simstruct.wavegen = SCDconf_createstructfrombus(dd,wgbusname); % structure to match
                %ddsource = dd.find('Name',wgbusname).DataSource; % dd containing bus
                %wrapdd=Simulink.data.dictionary.open(ddsource).getSection('Design Data');
                %wrapdd.addEntry(simstructname,simstruct)
                fprintf('... re-generated from bus %s\n',wgbusname')
              end
              SCDsimdata.(simstructname) = simstruct;
            end
          end
          
          % Node-specific
          simstructname = sprintf('SCDnode%02dsimdata',inode);
          if node.hasadc
            adcbusname = sprintf('ADC%02dbus',inode);
            fprintf('   setting up %s.adc\n',simstructname);
            SCDsimdata.(simstructname).adc = SCDconf_createstructfrombus(dd,adcbusname);
          end
          
          if node.hasethercat
            assert(inode==2,'Ethercat not yet implemented for other node than 2');
            ethercatbusname = 'ETHCAT1IN';
            fprintf('   setting up %s.ethercat\n',simstructname);
            SCDsimdata.(simstructname).ethercat = ...
            SCDconf_createstructfrombus(dd,ethercatbusname);
          end
          
          % add also RFM
          RFMbusname = 'RFMINbus';
          SCDsimdata.rfm = SCDconf_createstructfrombus(dd,RFMbusname);

          % assign result in base workspace         
          assignin('base','SCDsimdata',SCDsimdata);
        end
  
        
%% Actualization methods        
        
        function actualizedata(obj, shot)
           % This function actualizes configured
           % tunable parameters and wavegens timeseries
           % according to the given shot
           
           currentexpcode=Simulink.data.evalinGlobal('tcv','scd.expcode');
           if(currentexpcode~=obj.maincode)
               error('SCDclass_expcode:wrongexpcodeloaded','Cannot perform data actualization, the loaded expcode is not matching, try to setup the expcode first');
           end
           fprintf('Actualizing expcode %d, ''%s'', configuring tunable parameters ...\n',obj.maincode,obj.name);
           obj.actualizeparameters(shot)
           fprintf('Actualizing expcode %d, ''%s'', configuring wavegens ...\n',obj.maincode,obj.name);
           obj.actualizewavegens(shot);
        end

        function actualizeparameters(obj,shot)
            obj.mdscontainer.modeltoactualize=obj.modeltoactualize;
            obj.mdscontainer.actualizeparameters(shot);
        end
        
        function actualizewavegens(obj,shot)
            obj.mdscontainer.modeltoactualize=obj.modeltoactualize;
            obj.mdscontainer.actualizewavegens(shot);
        end

%% MDS update methods        
        function updatemds(obj, algo, 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=algo;
            obj.mdscontainer.autopopulateMDSparams(shot);         
            obj.mdscontainer.autopopulateMDSwavegens(shot);
        end

        
    end

    
end