% ELEMENTOP applies the given operator to the input smodels.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DESCRIPTION: ELEMENTOP applies the given operator to the input smodels.
%
% CALL:        a = elementOp(callerIsMethod, op, opname, opsym, infoObj, pl, fcnArgIn, varNames)
%
% PARAMETERS:  op       - MATLAB operation name
%              opname   - Name for displaying
%              opsym    - Operation symbol
%              infoObj  - minfo object
%              pl       - default plist
%              fcnArgIn - Input argument list of the calling fcn.
%              varNames - Variable names of the input
%
% VERSION:     $Id: elementOp.m,v 1.14 2011/05/10 20:43:09 mauro Exp $
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function varargout = elementOp(varargin)
  
  import utils.const.*
  
  % Settings
  callerIsMethod = varargin{1};
  op     = varargin{2};
  opname = varargin{3};
  opsym  = varargin{4};
  
  infoObj = varargin{5};
  pl      = varargin{6};
  
  fcnArgIn = varargin{7};
  varNames = varargin{8};
  
  if numel(fcnArgIn) ~= 2 && numel(fcnArgIn) ~= 3
    error('### ');
  end
  
  mdl1 = fcnArgIn{1};
  mdl2 = fcnArgIn{2};
  
  %%%%%%%%%%   Convert numbers into a symbolic model object   %%%%%%%%%%
  if isnumeric(mdl1) || ischar(mdl1)
    expr = mdl1;
    varNames{1} = num2str(expr);
    % Create an array with the same size of the second input
    mdl1 = smodel.newarray(size(mdl2));
    % Copy the object and replace the expression
    for ii = 1:numel(mdl2)
      mdl1(ii) = copy(mdl2(ii), true);
      mdl1(ii).yunits = '';
      mdl1(ii).expr = msym(expr);
      % Do not duplicate yunits in case of product/division
      if ~strcmpi(op, 'times') && ~strcmpi(op, 'rdivide')
        mdl1(ii).yunits = mdl2(ii).yunits;
      end
    end
  end
  if isnumeric(mdl2) || ischar(mdl2)
    expr = mdl2;
    varNames{2} = num2str(expr);
    % Create an array with the same size of the first input
    mdl2 = smodel.newarray(size(mdl1));
    % Copy the object and replace the expression
    for ii = 1:numel(mdl1)
      mdl2(ii) = copy(mdl1(ii), true);
      mdl2(ii).yunits = '';
      mdl2(ii).expr = msym(expr);

      % Do not duplicate yunits in case of product/division
      if ~strcmpi(op, 'times') && ~strcmpi(op, 'rdivide')
        mdl2(ii).yunits = mdl1(ii).yunits;
      end
    end
  end
  
  % Convert cdata aos into a smodel object
  if isa(mdl2, 'ao')
    if isa(mdl2.data, 'cdata') && numel(mdl2.data.y) == 1
      expr = mdl2.y;
      % Create an array with the same size of the first input
      mdl2 = smodel.newarray(size(mdl1));
      % Copy the object and replace the expression
      for ii = 1:numel(mdl1)
        mdl2(ii) = copy(mdl1(ii), true);
        mdl2(ii).expr = msym(expr);

      end
    else
      error('### It is not possible to apply %s to the two objects!', opname);
    end
  end
  
  %%%%%%%%%%   If the first or second input is only one object then   %%%%%%%%%%
  %%%%%%%%%%   resize the input to the size of the other object.      %%%%%%%%%%
  if numel(mdl1) == 1 && numel(mdl1) ~= numel(mdl2)
    mdl1(1:numel(mdl2)) = mdl1;
    mdl1 = reshape(mdl1, size(mdl2));
  end
  if numel(mdl2) == 1 && numel(mdl2) ~= numel(mdl1)
    mdl2(1:numel(mdl1)) = mdl2;
    mdl2 = reshape(mdl2, size(mdl1));
  end
  
  if ~all(size(mdl1) == size(mdl2))
    error('### The Input objects must have the same size. Size of model1 [%d %d], model2 [%d %d]', size(mdl1,1),  size(mdl1,2), size(mdl2,1), size(mdl2,2));
  end
  
  %%%%%%%%%%   Add each element inside the array   %%%%%%%%%%
  mdl = smodel.newarray(size(mdl1));
  for kk = 1:numel(mdl1)
    switch opsym
      case {'.*', '*'} 
        if any(strcmp(mdl1(kk).expr.s, {'0','(0)'})) || any(strcmp(mdl2(kk).expr.s, {'0','(0)'}))
          % 0*something = 0
          mdl(kk).expr = msym(['0']);
        else
          mdl(kk).expr = msym(['(' mdl1(kk).expr.s ')' opsym '(' mdl2(kk).expr.s ')']);
        end
      case {'./', '/'}
        if any(strcmp(mdl1(kk).expr.s, {'0','(0)'}))
          % 0/something = 0
          mdl(kk).expr = msym(['0']);
        else
          mdl(kk).expr = msym(['(' mdl1(kk).expr.s ')' opsym '(' mdl2(kk).expr.s ')']);
        end
      case '+'
        if any(strcmp(mdl1(kk).expr.s, {'0','(0)'}))
          % 0 + something = something
          mdl(kk).expr = msym([mdl2(kk).expr.s]);
        elseif any(strcmp(mdl2(kk).expr.s, {'0','(0)'}))
          % something + 0 = something
          mdl(kk).expr = msym([mdl1(kk).expr.s]);
        else
          mdl(kk).expr = msym(['(' mdl1(kk).expr.s ')' opsym '(' mdl2(kk).expr.s ')']);
        end        
      otherwise
        mdl(kk).expr = msym(['(' mdl1(kk).expr.s ')' opsym '(' mdl2(kk).expr.s ')']);
    end
 
    mdl(kk).name = ['(' mdl1(kk).name ')' opsym '(' mdl2(kk).name ')'];
    
    % Merge parameters, alias, xvar, tran fields
    smodel.mergeFields(mdl1(kk), mdl2(kk), mdl(kk), 'params', 'values');
    smodel.mergeFields(mdl1(kk), mdl2(kk), mdl(kk), 'aliasNames', 'aliasValues');
    smodel.mergeFields(mdl1(kk), mdl2(kk), mdl(kk), 'xvar', 'xvals');
    smodel.mergeFields(mdl1(kk), mdl2(kk), mdl(kk), 'xvar', 'xunits');
    smodel.mergeFields(mdl1(kk), mdl2(kk), mdl(kk), 'xvar', 'trans');
    
    % Take care of units
    if strcmpi(op,'times')
      mdl(kk).yunits = simplify(unit(unit(mdl1(kk).yunits).*unit(mdl2(kk).yunits)));
    elseif strcmpi(op,'rdivide')
      mdl(kk).yunits = simplify(unit(unit(mdl1(kk).yunits)./unit(mdl2(kk).yunits)));
    else
      mdl(kk).yunits = mdl1(kk).yunits;
      if mdl1(kk).yunits ~= mdl2(kk).yunits
        error('### Y units should be equal for %s', opname);
      end
    end
    
    % Add history
    if ~callerIsMethod
      inname1 = varNames{1};
      inname2 = varNames{2};
      if numel(fcnArgIn{1}) > 1
        [ii, jj] = ind2sub(size(fcnArgIn{1}), kk);
        inname1 = sprintf('%s(%d,%d)', inname1, ii, jj);
      end
      if numel(fcnArgIn{2}) > 1
        [ii, jj] = ind2sub(size(fcnArgIn{2}), kk);
        inname2 = sprintf('%s(%d,%d)', inname2, ii, jj);
      end
      mdl(kk).addHistory(infoObj, pl, {inname1, inname2}, [mdl1(kk).hist mdl2(kk).hist]);
    end
  end
  
  varargout{1} = mdl;
  
end


