function dataset = convertToDataset(h, name)
% Convert this Simulink.ModelDataLogs object to a
% Simulink.SimulationData.Dataset object.
%
% If logsout is a variable of type Simulink.ModelDataLogs, then:
%    dataset = logsout.convertToDataset(datasetName)
%
% creates a dataset whose name is specified by the argument datasetName.
%
% If the ModelDataLogs object contains data logged in a referenced model, then
% the conversion process loads all ancestors of that referenced model that are
% not already loaded.  If any of these models are not on the MATLAB path, the
% conversion fails.
%
% If the ModelDataLogs contains data logged from variant subsystems or variant
% models, then if the current active variants are not the same as when the
% logged data was collected, the conversion fails.
%
% If the ModelDataLogs contains data logged from models that have changed or
% from models whose ancestors have changed since the logged data was collected
% (e.g., blocks were added, deleted, or renamed), the conversion may fail.
%
% If the ModelDataLogs contains data logged from a Mux block, the conversion
% does not produce the same Dataset as would have been created had the data been
% logged into the Dataset format directly.
    
%   Copyright 2010-2012 The MathWorks, Inc.

    dataset = Simulink.SimulationData.Dataset();
    dataset.Name = name;
    
    modelBlockInfo = containers.Map('KeyType', 'char', 'ValueType', 'char');
    processedModels = containers.Map('KeyType', 'char', 'ValueType', 'logical');
    
    % Create an array of type Simulink.SimulationData.Signal to hold the
    % elements.
    elements = Simulink.SimulationData.Signal.empty();
    
    elements = loc_getDatasetElements(h, Simulink.SimulationData.BlockPath(),...
                                      h.BlockPath, false, ...
                                      modelBlockInfo, processedModels, elements);
    for i = 1:length(elements)
        dataset = dataset.addElement(elements(i));
    end   
    dataset = dataset.sortElements;
end



%% -----------------------------------------------------------------------
% currentNode is the node in the ModelDataLog currently being processed.
%
% blockPathToCurrentNode is the Simulink.SimulationData.BlockPath to 
% currentNode.
%
% stringPathToCurrentNode is the string version of the block path to the
% current node, but is in Model Data Logs format.  This means that it
% treats Model blocks like subsystems.
%
% isInModelRef is true if we are under at least one level of model
% reference
%
% modelBlockInfo is a Map from block paths (as strings) of Model blocks to
% the model name (as a string) of the model the block references.  We only
% consider active variants.
%
% processedModels contains a list of models that have been had their model
% block information stored in modelBlockInfo.
%
% datasetElements contains all elements of the dataset we are converting to
% that have been generated so far.  It should have new elements added to
% it and then be returned.
function datasetElements = loc_getDatasetElements(currentNode,...
                                                  blockPathToCurrentNode,...
                                                  stringPathToCurrentNode,...
                                                  isInModelRef,...
                                                  modelBlockInfo, ...
                                                  processedModels,...
                                                  datasetElements)
    switch( class(currentNode) )
        % ModelDataLogs, SubsysDataLogs, and StateflowDataLogs are all
        % containers.  We need to recurse into them.
        case {'Simulink.ModelDataLogs',...
              'Simulink.SubsysDataLogs',...
              'Simulink.StateflowDataLogs'}
          fields = fieldnames(currentNode);
          for i = 1:length(fields)
              field   = fields{i};
              
              if(loc_recurseOnField(field))
                  nextNode = currentNode.(field);
                  
                  if((isequal(class(currentNode), 'Simulink.StateflowDataLogs')) &&...
                     ((isequal(class(nextNode), 'Simulink.Timeseries')) || ...
                      (isequal(class(nextNode), 'Simulink.TsArray'))))
                      MSLDiagnostic('Simulink:Logging:ModelDataLogConvertError_StateflowNotSupported', ...
                                       nextNode.Name, stringPathToCurrentNode).reportAsWarning;
                      continue; 
                  end
                  
                  % Field names in referenced models have the slashes
                  % doubled.  Field names in top models do not.  Double up
                  % slashes if we are not in a referenced model.
                  cleanedField = field;
                  if(isInModelRef)
                      nextisInModelRef = true;
                  else
                      nextisInModelRef = isequal(class(currentNode), 'Simulink.ModelDataLogs');
                      
                      cleanedField = strrep(cleanedField, '/', '//');
                  end

                  [blockPathToNextNode, stringPathToNextNode] = ...
                      loc_getPathToNextNode(currentNode, blockPathToCurrentNode, stringPathToCurrentNode,...
                                            cleanedField, nextNode, modelBlockInfo, processedModels);

                  datasetElements = ...
                      loc_getDatasetElements(nextNode, blockPathToNextNode, ...
                          stringPathToNextNode, nextisInModelRef, modelBlockInfo, ...
                          processedModels, datasetElements);
              end
          end
          
          
          
        % Timeseries and TsArray hold actual data.  Create a
        % Simulink.SimulationData.Signal to hold the results.
        case {'Simulink.Timeseries',...
              'Simulink.TsArray'}
            if(isequal(class(currentNode), 'Simulink.Timeseries'))
                values      = currentNode.convertToMATLABTimeseries;
                values.Name = currentNode.SignalName;
            else
                values = Simulink.SimulationData.createStructOfTimeseries(currentNode);
            end
          
            signal   = Simulink.SimulationData.Signal();
            
            signal.Name      = currentNode.Name;
            signal.BlockPath = blockPathToCurrentNode;
            signal.PortType  = 'outport';
            signal.PortIndex = currentNode.PortIndex;
            signal.Values    = values;
            
            datasetElements(end+1) = signal;
            
        otherwise
            DAStudio.error('Simulink:Logging:ModelDataLogConvertError_UnexpectedObject',...
                           class(currentNode));
    end
end 




%% -----------------------------------------------------------------------
% Fills in the modelBlockInfo map with information about the given model.
% Since modelBlockInfo and processedModels are both handle classes,
% we do not have to return them.
function loc_getModelBlockInfo(modelname, modelBlockInfo, processedModels)
    if(~ processedModels.isKey(modelname))
        loaded_models = find_system('type', 'block_diagram');
        isLoaded = ismember(modelname, loaded_models);
        if(~isLoaded)
            load_system(modelname);
            c1 = onCleanup(@() close_system(modelname, 0));
        end
        
        
        % @todo update the usage of edit-time filter filterOutCodeInactiveVariantSubsystemChoices()
        % instead use the post-compile filter codeCompileVariants() -g2614641
        [~, modelBlocks] = find_mdlrefs(modelname,'MatchFilter',@Simulink.match.internal.filterOutCodeInactiveVariantSubsystemChoices, 'AllLevels', false ); % look only inside code active choice of VSS
        for i = 1:length(modelBlocks)
            modelBlock = modelBlocks{i};
            if(isequal(get_param(modelBlock, 'ProtectedModel'), 'off'))
                refModelName = get_param(modelBlock, 'ModelName');
                modelBlock = Simulink.SimulationData.BlockPath.manglePath(modelBlock);
                
                modelBlockInfo(modelBlock) = refModelName;
            end
        end
        
        processedModels(modelname) = true; %#ok<NASGU>
    end
end






%% -----------------------------------------------------------------------
% Given a Simulink.SimulationData.BlockPath and a string, create a new
% BlockPath with the string appended to the end of the BlockPath 
function newBlockPath = loc_appendToBlockPath(blockPath, next)  
    blockPathAsCell        = blockPath.convertToCell();
    blockPathAsCell{end+1} = next;
    newBlockPath           = Simulink.SimulationData.BlockPath(blockPathAsCell);
end




%% -----------------------------------------------------------------------
% Given:
% 
% currentNode, a node in the Model Data Logs hierarchy 
%
% blockPathToCurrentNode, a Simulink.SimulationData.BlockPath to the
% current node in the model data logs object
%
% stringPathToCurrentNode, the string version of the block path to the
% current node, but is in Model Data Logs format.  This means that it
% treats Model blocks like subsystems.
%
% nextNode, the node we are moving to next
% 
% fieldToNextNode, the field of the current node we followed to get to
% the next node
%
% modelBlockInfo is a Map from block paths (as strings) of Model blocks
% to the model name (as a string) of the model the block references.  We
% only consider active variants.
%
% processedModels contains a list of models that have been had their model
% block information stored in modelBlockInfo.
%
%
% Compute:
%
% blockPathToNextNode, a Simulink.SimulationData.Block path to the nextNode
%
% stringPathToNextNode, a string version of the block path in Model Data
% Logs format
function [blockPathToNextNode, stringPathToNextNode] = ...
        loc_getPathToNextNode(currentNode, blockPathToCurrentNode, stringPathToCurrentNode,...
                fieldToNextNode, nextNode, modelBlockInfo, processedModels)
    if(isequal(class(currentNode), 'Simulink.SubsysDataLogs'))
        % If the parent is a subsystem, we want to adjust
        % blockPathToCurrentNode and remove the last element, otherwise we
        % will add an extra level of Model hierarchy when creating the
        % block path to the next node
        blockPathToCurrentNode = loc_removeLastElementOfBlockPath(blockPathToCurrentNode);
    elseif(isequal(class(currentNode), 'Simulink.StateflowDataLogs'))
        if((isequal(class(nextNode), 'Simulink.Timeseries')) || ...
           (isequal(class(nextNode), 'Simulink.TsArray')))
            blockPathToNextNode  = blockPathToCurrentNode;
            stringPathToNextNode = stringPathToCurrentNode;
            return;
        else
            blockPathToCurrentNode = loc_removeLastElementOfBlockPath(blockPathToCurrentNode);
        end
    end
    
    if(isequal(class(nextNode), 'Simulink.Timeseries') || ...
       isequal(class(nextNode), 'Simulink.TsArray'))
        stringPathToNextNode = nextNode.BlockPath;
    else
        stringPathToNextNode = [stringPathToCurrentNode, '/', fieldToNextNode];
    end

    stringPathToNextNode = Simulink.SimulationData.BlockPath.manglePath(stringPathToNextNode);
    
    % Getting the string path to the next node is now a matter of
    % concatenating the field to the next node onto the end of the string
    % path to the current node

    lastPart = loc_getLastPartOfBlockPath(blockPathToCurrentNode, stringPathToNextNode, modelBlockInfo, processedModels);
    blockPathToNextNode = loc_appendToBlockPath(blockPathToCurrentNode, lastPart);
end
    


%% -----------------------------------------------------------------------
% Remove the last element from a Simulink.SimulationData.BlockPath and
% return the resulting Simulink.SimulationData.BlockPath object
function blockPath = loc_removeLastElementOfBlockPath(blockPath)
    blockPathAsCell    = blockPath.convertToCell();
    newBlockPathAsCell = blockPathAsCell(1:(end-1));
    blockPath          = Simulink.SimulationData.BlockPath(newBlockPathAsCell);
end


%% -----------------------------------------------------------------------
% Given a BlockPath that points to the model the current node is in, and a
% string path to the current node, get the lastPart (i.e., the part that
% would need to be appended to block path to make it point to string path);
%
% Getting the block path to the next node is more complicated.  We have a
% string path to the next node and need to get just the last part of the
% path to append to block path.  To do this, we will loop through block path,
% replacing paths to model blocks in lastPart with the name of the
% referenced model.  When done, we will be left with a string that can be
% appended onto block path.
function lastPart = loc_getLastPartOfBlockPath(blockpath, stringpath, modelBlockInfo, processedModels)
    lastPart = stringpath;
    for i = 1:blockpath.getLength()
        pathPart       = blockpath.getBlock(i);
        pathPartLength = length(pathPart);

        modelname = Simulink.SimulationData.BlockPath.getModelNameForPath(pathPart);
        if(~processedModels.isKey(modelname))
            loc_getModelBlockInfo(modelname, modelBlockInfo, processedModels)
        end
        
        % This means we don't have a mapping from model block to model
        % name, meaning the model block no longer exists in the most recent
        % version of the model
        if(~modelBlockInfo.isKey(pathPart))
            DAStudio.error('Simulink:Logging:ModelDataLogConvertError_ModelBlockNotFound', ...
                           pathPart)
        end
        
        refModelName = modelBlockInfo(pathPart);
        lastPart = [refModelName, lastPart((pathPartLength+1):end)];
    end  
end



%% -----------------------------------------------------------------------
% Given a field on a Model Data Log node, determine if we need to 
% recursively examine that field
function needToRecurse = loc_recurseOnField(fieldName)
    needToRecurse = ~ ( isequal(fieldName, 'Name') ||...
                        isequal(fieldName, 'BlockPath') );
end
