% SSM2DOT converts a statespace model object a DOT file.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DESCRIPTION: SSM2DOT converts a statespace model object a DOT file.
%
% CALL:        ssm2dot(ssm, options);
%
% INPUTS:      ssm     - ssm object
%              options - plist of options
%
% <a href="matlab:utils.helper.displayMethodInfo('ssm', 'ssm2dot')">Parameters Description</a>
%
% VERSION: $Id: ssm2dot.m,v 1.15 2011/04/08 08:56:24 hewitson Exp $
% 
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function varargout = ssm2dot(varargin)
  
  % Check if this is a call for parameters
  if utils.helper.isinfocall(varargin{:})
    varargout{1} = getInfo(varargin{3});
    return
  end
  
  % starting initial checks
  utils.helper.msg(utils.const.msg.MNAME, ['running ', mfilename]);
  
  in_names = cell(size(varargin));
  for ii = 1:nargin,in_names{ii} = inputname(ii);end
  
  [inputSSMs, ssm_invars, rest] = utils.helper.collect_objects(varargin(:), 'ssm', in_names);
  [pl, pl_invars, rest] = utils.helper.collect_objects(rest, 'plist');
  if ~isempty(rest)
    pl = combine(pl, plist(rest{:}));
  end
  options = combine(pl, getDefaultPlist());
  
  
  filename = find(options, 'filename');
  if isempty(filename)
    error('### Please specify an output filename in the plist');
  end
  
  statesOn = find(options, 'States');
  if strcmpi(statesOn, 'yes')
    statesOn = true;
  else
    statesOn = false;
  end
  
  if numel(inputSSMs) ~= 1
    error('### please input (only) one SSM object.');
  end
  
  % Find the last assemble in the history
  [n,a, nodes] = getNodes(inputSSMs.hist, '');
  ssmNodes = findAssembleInputs(nodes, 1);
  % if we only get one node there must not be any assemble blocks so we can
  % just use the first node.
  if length(ssmNodes)==1 || isempty(ssmNodes)
    ssmNodes = 1;
  end
  
  colors = {'blueviolet', 'chartreuse4', 'firebrick3', 'darkorange', 'navyblue', 'aquamarine3', 'deepskyblue'};
  
  % What info do we need?
  sss(length(ssmNodes),1) = ssm;
  for j=1:length(ssmNodes)
    node = nodes(ssmNodes(j));
    % Collect the nodes to execute
    % and convert to commands
    cmds = hist2m(node.h);
    % execute each command
    for kk=numel(cmds):-1:1
      eval(cmds{kk});
    end
    % add to outputs
    sss(j) = a_out;
  end
  % Write .dot file
  fd = fopen(filename, 'w+');
  % write header
  fprintf(fd, 'digraph G \n{\n');
  fprintf(fd, '\trankdir="LR";\n');
  fprintf(fd, '\tnode [style=filled, fillcolor=white fixedsize=false width=0.5 fontsize=16 shape=rectangle];\n');
  fprintf(fd, '\tedge [penwidth=5];\n');
  fprintf(fd, '\n\n');
  % Write block set
  for j=1:length(ssmNodes)
    % get the object
    ss = sss(j);
    inputblocks  = ss.inputnames;
    inputvars    = ss.inputvarnames;
    outputblocks = ss.outputnames;
    outputvars   = ss.outputvarnames;
    ssnames      = ss.ssnames;
    ssvarnames   = ss.ssvarnames;
    % Create sub graphs
    fprintf(fd, '\tsubgraph cluster%d {\n', j);
    fprintf(fd, '\t\tlabel="%s";\n', ss.name);
    fprintf(fd, '\t\tfontcolor=black;\n');
    fprintf(fd, '\t\tcolor=gray60;\n');
    fprintf(fd, '\t\tstyle=filled;\n');
    for k=1:numel(inputblocks)
      fprintf(fd, '\t\tsubgraph cluster%d%d_in {\n', j, k);
      fprintf(fd, '\t\t\tlabel="%s";\n', inputblocks{k});
      fprintf(fd, '\t\t\tstyle=filled;\n');
      fprintf(fd, '\t\t\tcolor=yellow1;\n');
      fprintf(fd, '\t\t\tfontcolor=black;\n');
      for l=1:numel(inputvars{k})
        fprintf(fd, '\t\t\tssm_in_%d_%d_%d [label="%s"];\n', j,k,l,inputvars{k}{l});
      end
      fprintf(fd, '\t\t}\n');
    end
    for k=1:numel(outputblocks)
      fprintf(fd, '\t\tsubgraph cluster%d%d_out {\n', j, k);
      fprintf(fd, '\t\t\tlabel="%s";\n', outputblocks{k});
      fprintf(fd, '\t\t\tstyle=filled;\n');
      fprintf(fd, '\t\t\tcolor=lightblue1;\n');
      for l=1:numel(outputvars{k})
        fprintf(fd, '\t\t\tssm_out_%d_%d_%d [label="%s"];\n', j,k,l,outputvars{k}{l});
      end
      fprintf(fd, '\t\t}\n');
    end
    if statesOn
      for k=1:numel(ssnames)
        fprintf(fd, '\t\tsubgraph cluster%d%d_state {\n', j, k);
        fprintf(fd, '\t\t\tlabel="%s";\n', ssnames{k});
        fprintf(fd, '\t\t\tstyle=filled;\n');
        fprintf(fd, '\t\t\tcolor=wheat1;\n');
        for l=1:numel(ssvarnames{k})
          fprintf(fd, '\t\t\tssm_state_%d_%d_%d [label="%s"];\n', j,k,l,ssvarnames{k}{l});
        end
        fprintf(fd, '\t\t}\n');
      end
    end
    fprintf(fd, '\t}\n');
    fprintf(fd, '\n\n');
  end
  % Write node list
  fprintf(fd, '\n');
  fprintf(fd, '\n');
  nl = 1;
  for j=1:length(ssmNodes)
    % get the object
    ss = sss(j);
    inputblocks  = ss.inputnames;
    inputvars    = ss.inputvarnames;
    % Now join outputs to inputs
    for k=1:numel(inputblocks)
      iblock = inputblocks{k};
      % look for a matching output block
      for oj = 1:length(ssmNodes)
        oss = sss(oj);
        outputblocks = oss.outputnames;
        outputvars   = oss.outputvarnames;
        for ok=1:numel(outputblocks)
          if strcmp(outputblocks{ok}, iblock)
            % make a connection from each output to each input
            for ol = 1:numel(outputvars{ok})
              col = colors{mod(nl, numel(colors))+1};
              % draw a line
              fprintf(fd, 'ssm_out_%d_%d_%d -> ssm_in_%d_%d_%d [color="%s"];\n', oj, ok, ol, j, k, ol, col);
              nl = nl + 1;
            end
          end
        end
      end
    end
  end
  fprintf(fd, '\n');
  fprintf(fd, '\n');
  % close graph
  fprintf(fd, '}\n');
  % Close
  fclose(fd);
  
end

%--------- Get the input SSMs to all assemble blocks
function idx = findAssembleInputs(nodes, an)
  idx = [];
  % get children
  children = findChildNodes(nodes, an, '');
  % find all sub-assembles or ssm end points
  for j=1:numel(children)
    ch = children(j);
    subasmbl = findChildNodes(nodes, ch, 'assemble');
    if isempty(subasmbl)
      idx = [idx ch];
    else
      idx = [idx findAssembleInputs(nodes, ch)];
    end
  end
end

%---- Find particular child nodes
function idx = findChildNodes(nodes, pn, name)
  
  idx = [];
  if strcmp(nodes(pn).names, name)
    idx = [idx pn];
  end
  Nnodes = numel(nodes);
  for j=pn:Nnodes
    if nodes(j).pn == pn
      if isempty(name)
        idx = [idx j];
      else
        if strcmp(nodes(j).names, name)
          idx = [idx j]; % we have what we want
        else
          if j<Nnodes
            idx = [idx findChildNodes(nodes, nodes(j).n, name)]; % check below
          end
        end
      end
    end
  end
end



%--------------------------------------------------------------------------
% Get Info Object
%--------------------------------------------------------------------------
function ii = getInfo(varargin)
  
  if nargin == 1 && strcmpi(varargin{1}, 'None')
    sets = {};
    pl   = [];
  else
    sets = {'Default'};
    pl   = getDefaultPlist;
  end
  % Build info object
  ii = minfo(mfilename, 'ssm', 'ltpda', utils.const.categories.internal, '$Id: ssm2dot.m,v 1.15 2011/04/08 08:56:24 hewitson Exp $', sets, pl);
end

%--------------------------------------------------------------------------
% Get Default Plist
%--------------------------------------------------------------------------
function pl = getDefaultPlist()
  pl = plist();
  
  p = param({'filename', 'The output filename to save the graphic to.'}, paramValue.EMPTY_STRING);
  pl.append(p);
  
  p = param({'states', 'Draw the states of each model.'}, paramValue.TRUE_FALSE);
  p.val.setValIndex(2);
  pl.append(p);
end

