% CMDS2PIPELINE builds new pipelines in the given workbench from the
% set of LTPDA commands.
%
% CALL: cmds2pipeline(wb, name, cmds)
%       cmds2pipeline(wb, name, cmds, blockpositions)
%
% M Hewitson 12-11-10
%
% $Id: cmds2pipeline.m,v 1.2 2011/04/08 08:56:32 hewitson Exp $
%
function cmds2pipeline(wb, docname, cmds, varargin)
  
  if numel(varargin) > 0
    blockPositions = varargin{1};
  else
    blockPositions = {};
  end
  
  % create new diagram
  awtinvoke(wb.mp, 'createNewBlockDiagram', docname);
  % logical array for checking that we only place
  % each block once
  blockplaced = zeros(size(cmds));
  
  % Go through each command and place its block
  cn = 1;
  dX = 100;
  dY = 150;
  sX = 100;
  X = sX; % starting X coord
  Y = 50; % starting Y coord
  % loop until all blocks are placed
  blockNames = {};
  while ~all(blockplaced) && cn <=numel(cmds)
    if ~blockplaced(cn)
      % parse this command
      srcblock = LTPDAworkbench.parseCmd(cmds{cn});
      %--------- build a block
      % to do this we need an minfo object; we have the
      % the method name but we need the class. Either this
      % is a constructor or it must have an input object
      ecmd = sprintf('%s.getInfo(''%s'');', srcblock.class, srcblock.method);
      ii = eval(ecmd);
      % override auto-generated positions if the user supplies them
      if ~isempty(blockPositions)
        pos = blockPositions{cn};
        X = pos(1);
        Y = pos(2);
      end
      name = wb.addBlock(srcblock.outname, ii, X,Y);
      blockNames = [blockNames {name}];
      blockplaced(cn) = 1;
      % look for commands which take this output as an input
      [blockplaced,blockNames] = placeBlock(wb, blockplaced, ...
        blockPositions, srcblock.outname, cmds, X, dX, Y, cn, blockNames);
      % reset X
      X = sX+floor(utils.math.rand(10,20));
      % increment in Y
      Y = Y + dY;
    end
    % check next block
    cn = cn + 1;
  end
  
  % Build a connection list
  connections = struct('src', {}, 'srcport', {}, 'dst',{}, 'dstport', {});
  cc = 1;
  for cn =1:numel(cmds)
    conn = cmd2conn(cmds{cn});    
    % deal with block-to-block connections
    if numel(conn.srcports)==numel(conn.dstports)
      for kk=1:numel(conn.srcblocks)
        if numel(conn.srcblocks) == 1
          % one-to-many
          srcb = conn.srcblocks{1};
        else
          % one-to-one
          srcb = conn.srcblocks{kk};
        end
        if numel(conn.dstblocks) == 1
          % many-to-one
          dstb = conn.dstblocks{1};
        else
          % one-to-one
          dstb = conn.dstblocks{kk};
        end
        if ~isempty(srcb) && ~isempty(dstb)
          % make connection
          if ~isempty(dstb)
            connections(cc) = struct('src', srcb, ...
              'srcport', conn.srcports(kk), ...
              'dst', dstb, ...
              'dstport', conn.dstports(kk));
            connections(end)
            wb.connectBlocks(connections(cc).src, connections(cc).srcport+1, ...
              connections(cc).dst, connections(cc).dstport+1);
            cc = cc + 1;
          end
        end
      end % End loop over src blocks
    end
  end % End first loop over commands
  
  % also deal with connections to the plist
  for kk=1:numel(cmds)
    cconn = cmd2conn(cmds{kk});
    conn = connFromPlist(cconn, blockNames);
    if ~isempty(conn)
      for ll=1:numel(conn)
        % we need to know what destination port to use
        % - look for max in connections for this dst block
        b = wb.getBlockByName(conn(ll).dst);
        p = b.getNextFreeInput();
        connections(cc) = struct('src', conn(ll).src, ...
          'srcport', conn(ll).srcport, ...
          'dst', conn(ll).dst, ...
          'dstport', p);
        wb.connectBlocks(conn(ll).src, conn(ll).srcport+1, conn(ll).dst, p+1)
        cc = cc + 1;
      end
    end    
  end
  
  % upload plists
  for cn =1:numel(cmds)
    block = LTPDAworkbench.parseCmd(cmds{cn});
    dstblock = block.outname;
    % get src block name(s) and ports from each plist
    for pp=1:numel(block.plists)
      pl = block.plists{pp};
      mtchs = regexp(pl, '(\w*)_PORT(\d*)', 'tokens');
      % for each match, we look for a corresponding connection
      % so that we can determine the destination port
      if ~isempty(mtchs)
        for kk=1:numel(mtchs)
          srcblock = mtchs{kk}{1};
          srcport  = str2double(mtchs{kk}{2});
          % look for srcblock/srcport/dstblock in connection list
          % and get the dstport
          dstport = -1;
          for cc=1:numel(connections)
            % if this matches...
            if strcmp(connections(cc).src, srcblock) && ...
                strcmp(connections(cc).dst, dstblock) && ...
                connections(cc).srcport == srcport
              % ... then we set the dstport
              dstport = connections(cc).dstport;
            end
          end
          % now we can fix up the plist
          if dstport >= 0
            pl = regexprep(pl, [srcblock '_PORT\d*'], ['''PORT_' num2str(dstport) '''']);
          end
        end
      else
        % Try also for any block name in the plist
        for kk=1:numel(blockNames)
          srcblock = blockNames{kk};
          % look for this srcblock in the plist but we need a clever
          % regular expression to check this is an argument of the plist and
          % not part of a key, for example.
          tks = regexp(pl, ['[\s\[,]+(' srcblock ')[\s,\]]+'], 'tokens');
          dstport = -1;
          for ll=1:numel(tks)
            % To get the destination port, we need to find this
            % connection in the connections list
            for cc=1:numel(connections)
              cnc = connections(cc);
              if strcmp(cnc.src, srcblock) && ...
                  strcmp(cnc.dst, dstblock) && ...
                  cnc.srcport == 0
                dstport = cnc.dstport;
              end
            end
          end
          if dstport >= 0
            pl = regexprep(pl, srcblock, ['''PORT_' num2str(dstport) '''']);
          end
        end
      end
      % now we can upload the plist
      wb.uploadPlist(dstblock, eval(pl));
    end
  end
  
  
end

%--------------------------------------------------------------------------
% Create a connection structure from a plist.
% 
% This deals with those plists that have BLOCK_PORT# type entries.
% 
function conn = connFromPlist(iconn, blocks)
  
  conn = struct('src', {}, 'srcport', {}, 'dst',{}, 'dstport', {});
  cc = 1;
  
  for jj=1:numel(iconn.plist)
    pl = iconn.plist{jj};
    dsts = regexp(pl, '\w*_PORT\d*','match');
    if ~isempty(dsts)
      for kk=1:numel(dsts)
        % parse dst string
        tks = regexp(dsts{kk}, '(\w*)_PORT(\d*)', 'tokens');
        for ll=1:numel(tks)
          % make a connection
          src = tks{ll}{1};
          srcp = tks{ll}{2};
          conn(cc).src = src;
          conn(cc).srcport  = str2double(srcp);
          conn(cc).dst = iconn.dstblocks{1};
          conn(cc).dstport  = -1; % next free port
          cc = cc + 1;
        end
      end
    else
      % also look for any input variables which are block names but have
      % no _PORT# suffix
      for kk=1:numel(blocks)
        srcblock = blocks{kk};
        % look for this srcblock in the plist but we need a clever
        % regular expression to check this is an argument of the plist and
        % not part of a key, for example.
        tks = regexp(pl, ['[\s\[,]+(' srcblock ')[\s,\]]+'], 'tokens');
        for ll=1:numel(tks)
          conn(cc).src = tks{ll}{1};
          conn(cc).srcport  = 0;
          conn(cc).dst = iconn.dstblocks{1};
          conn(cc).dstport  = -1; % next free port
          cc = cc + 1;
        end
      end
    end
  end
end

%--------------------------------------------------------------------------
% Convert a connection to a command.
% 
function conn = cmd2conn(cmd)
  
  block = LTPDAworkbench.parse(cmd);
  
  % first get block name from end
  [s,r] = strtok(block.comment, '|');
  if ~isempty(r)
    blockname = {strrep(strtrim(r(2:end)), ' ', '_')};
  else
    blockname = '';
  end
    
  [outname,outports] = fixBlockdiagramName(block.outvars{1});
  
  if isempty(blockname)
    blockname = block.outvars(1);
  end
  
  innames = {};
  srcports = [];
  for kk=1:numel(block.invars)
    [s,p] = fixBlockdiagramName(block.invars{kk});
    if isempty(p)
      p = 0;
    end
    innames = [innames s];
    srcports  = [srcports p];
  end
  
  % trim all strings and build output struct
  conn.srcblocks = innames;
  conn.srcports  = srcports;
  conn.dstblocks = blockname;
  conn.dstports  = [1:numel(innames)]-1;
  conn.plist     = block.pls;
  
end

%--------------------------------------------------------------------------
% Do some parsing of block diagram names to make sensible variable names.
% 
function [name, ports] = fixBlockdiagramName(name)
  
  name = strtrim(name);
  if name(1) == '[' && name(end) == ']'
    name = name(2:end-1);
  end
  % match the last occurance of '_PORT' and check there is a
  % number behind it  
  pstrs = regexp(name, '_PORT(\d*)', 'tokens');
  ports = [];
  for kk=1:numel(pstrs)
    ports(kk) = str2double(pstrs{kk}{1});
  end
  
  name = regexprep(name, '_PORT\d*', '');
end

%--------------------------------------------------------------------------
% Place a block on the canvas
% 
function [blockplaced,blockNames] = placeBlock(wb, blockplaced, blockPositions, ...
                                               outname, cmds, X, dX, Y, cn, blockNames)
  
  % look for commands which take this output as an input
  for kk=1:numel(cmds)
    dstblock = LTPDAworkbench.parseCmd(cmds{kk});
    if any(strcmp(outname, dstblock.innames)) %|| ~isempty(idx)
      if ~blockplaced(kk)
        blockplaced(kk) = 1;
        %--------- build a block
        % to do this we need an minfo object; we have the
        % the method name but we need the class. Either this
        % is a constructor or it must have an input object
        ecmd = sprintf('%s.getInfo(''%s'');', dstblock.class, dstblock.method);
        ii = eval(ecmd);
        X = X + dX;
        Y = Y + 30;
        if ~isempty(blockPositions)
          pos = blockPositions{kk};
          X = pos(1);
          Y = pos(2);
        end
        name = wb.addBlock(dstblock.outname, ii, X,Y);
        blockNames = [blockNames {name}];
        %         name = wb.addBlock(doutname, ii, X,Y+ (mod(cn+kk-1,2))*50);
        % recursively call for this block
        [blockplaced,blockNames] = placeBlock(wb, blockplaced, blockPositions, ...
                                             dstblock.outname, cmds, X, dX, Y, cn, blockNames);
      end
    end
  end
  
end

%--------------------------------------------------------------------------
% Replace other block names with PORT_#
% 
% #### OLD CODE #####
% 
function [pl, newconnections] = fixpl(pl,connections)
  
  % first we need to replace all BLOCK_PORT# with PORT_#
  pl = regexprep(pl, '(\w*)_PORT(\d*)', '''PORT_$2''');
  
  cn = 1;
  % go through all connections
  for kk=1:numel(connections)
    conn = connections(kk);
    % check if the plist has a token matching the src
    rstr = ['[' conn.src ']'];
    nstr = sprintf('''PORT_%d''', conn.dstport-1);
    idx = strfind(pl, rstr);
    pl = strrep(pl, rstr, nstr);
    if ~isempty(idx)
      newconnections(cn) = struct('src', conn.src, 'dst', conn.dst, ...
        'srcport', conn.srcport, 'dstport', conn.dstport-1);
    end
  end
  
end

% END
