% EQ overloads the == operator for ltpda plist objects.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% DESCRIPTION: EQ overloads the == operator for ltpda plist objects.
%
%              Two plists are considered equal if each has the same name,
%              created time, version, creator and parameter objects. The
%              order of the param-objects doesn't matter.
%
% CALL:        result = eq(u1,u2)
%
%              With a PLIST
%
%              r = eq(obj1, obj2, plist('Exceptions', {'prop1', 'prop2'}))
%              r = eq(obj1, obj2, plist('Tol', eps(1)))
%              r = eq(obj1, obj2, plist('Exceptions', 'prop', 'Tol', 1e-14))
%
% INPUTS:      pl1, pl2 - Input objects
%
% OUTPUTS:     If the two objects are considered equal, result == true,
%              otherwise, result == false.
%
% <a href="matlab:utils.helper.displayMethodInfo('plist', 'eq')">Parameters Description</a>
%
% VERSION:     $Id: eq.m,v 1.27 2011/04/08 08:56:21 hewitson Exp $
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function result = eq(obj1, obj2, varargin)
  
  % Check if this is a call for parameters
  hh = [{obj1}, {obj2}, varargin];
  if utils.helper.isinfocall(hh{:})
    result = getInfo(varargin{1});
    return
  end
  
  import utils.const.*
  
  %%%%% Check class
  if ~strcmp(class(obj1), class(obj2))
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The objects are not from the same class. [%s] <-> [%s]', class(obj1), class(obj2));
    result = false;
    return
  end
  
  %%%%% Check length of obj1 and obj2
  if numel(obj1) ~= numel(obj2)
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The size of the %s-object''s. [%d] <-> [%d]', class(obj1), numel(obj1), numel(obj2));
    result = false;
    return
  end
  
  plin = [];
  exception_list = varargin;
  if ~isempty(varargin) && isa(varargin{1}, 'plist')
    plin = varargin{1};
  end
  
  %%%%% Get the tolerance from a potential existing plist
  if ~isempty(plin) && plin.isparam('tol')
    tol = plin.find('tol');
  else
    dpl = getDefaultPlist();
    tol = dpl.find('tol');
  end
  
  %%%%% Convert a potential existing plist into a exception
  if ~isempty(plin) && plin.isparam('exceptions')
    exception_list = find(plin, 'Exceptions');
    if isempty(exception_list)
      exception_list = cell(0);
    elseif ~iscell(exception_list)
      exception_list = cellstr(exception_list);
    end
  end
  
  result = true;
  
  %%%%% for each element in obj1 and obj2
  for jj = 1:numel(obj1)
    
    if isa(obj1, 'ltpda_uoh')
      utils.helper.msg(msg.PROC1, 'testing %s against %s', obj1.name, obj2.name);
    end
    
    fields = fieldnames(obj1(jj));
    
    for ii = 1:length(fields)
      field   = fields{ii};
      
      %%%%% Creates the exception list for the current field.
      %%%%% For example: {'name', 'ao/name'}
      ck_field = {field, sprintf('%s/%s', class(obj1), field)};
      
      % Special case (for the ao- and history-class):
      % Is the field = 'hist', 'inhists' then add 'history' to the exception list.
      %%%%% For example: {'history', 'ao/history'}
      if utils.helper.ismember(field, {'hist', 'inhists'})
        ck_field{end+1} = 'history';
        ck_field{end+1} = sprintf('%s/history', class(obj1));
      elseif strcmp(field, 'val')
        ck_field{end+1} = 'value';
        ck_field{end+1} = sprintf('%s/value', class(obj1));
      end
      
      %%%%% Check field if it is not in the exception list
      if ~(any(utils.helper.ismember(ck_field, exception_list)))
        
        if isa(obj1(jj).(field), 'sym')
          %%%%%%%%%%   The property is a sym-object   %%%%%%%%%%
          if ~eq(obj1(jj).(field), obj2(jj).(field))
            result = false;
            disaply_msg(obj1, jj, field)
            return
          end
          
        elseif isa(obj1(jj).(field), 'ltpda_obj')
          %%%%%%%%%%   The property is a ltpda-object   %%%%%%%%%%
          
          %%%%% Check the length of the property
          if length(obj1(jj).(field)) ~= length(obj2(jj).(field))
            utils.helper.msg(msg.PROC1, 'NOT EQUAL: The property [%s] of the object [%s] have not the same size %d <-> %d.', field, class(obj1), length(obj1(jj).(field)), length(obj2(jj).(field)));
            result = false;
            return
          end
          
          %%%%% Special case for param-objects because the order shouldn't matter
          if isa(obj1(jj).(field), 'param')
            
            if ~eqparam(obj1(jj).(field), obj2(jj).(field), exception_list, tol)
              result = false;
              disaply_msg(obj1, jj, field)
              return
            end
            
          else
            %%%%% For each element of the property
            for kk = 1:numel(obj1(jj).(field))
              if isempty(plin)
                % Command with a list of exceptions
                res = eq(obj1(jj).(field)(kk), obj2(jj).(field)(kk), exception_list{:});
              else
                % Command with a PLIST
                res = eq(obj1(jj).(field)(kk), obj2(jj).(field)(kk), plin);
              end
              if ~res
                result = false;
                disaply_msg(obj1, jj, field)
                return
              end
            end
          end
          
        elseif isstruct(obj1(jj).(field))
          %%%%%%%%%%   The property is a structure   %%%%%%%%%%
          
          if ~eqstruct(obj1(jj).(field), obj2(jj).(field), exception_list, tol)
            result = false;
            disaply_msg(obj1, jj, field)
            return
          end
          
        elseif iscell(obj1(jj).(field))
          %%%%%%%%%%   The property is a cell array   %%%%%%%%%%
          
          if ~eqcell(obj1(jj).(field), obj2(jj).(field), exception_list, tol)
            result = false;
            disaply_msg(obj1, jj, field)
            return
          end
          
        elseif isnumeric(obj1(jj).(field))
          %%%%%%%%%%   The property is an elemental MATLAB datatype   %%%%%%%%%%
          if ~utils.math.isequal(obj1(jj).(field), obj2(jj).(field), tol)
            result = false;
            disaply_msg(obj1, jj, field)
            return
          end
        else
          %%%%%%%%%%   The property is an elemental MATLAB datatype   %%%%%%%%%%
          if ~isequal(obj1(jj).(field), obj2(jj).(field))
            result = false;
            disaply_msg(obj1, jj, field)
            return
          end
        end
        
      end
    end
    
  end
end


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                               Local Functions                               %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION:    disaply_msg
%
% DESCRIPTION: Diesplay a message if the objects are not equal
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function disaply_msg(obj, obj_no, field)
  import utils.const.*
  if numel(obj) > 1
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: %s.%s (%d. object)', class(obj(obj_no)), field, obj_no);
  else
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: %s.%s', class(obj(obj_no)), field);
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION:    eqstruct
%
% DESCRIPTION: Equal method to compare structures
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function result = eqstruct(obj1, obj2, exception_list, tol)
  
  import utils.const.*
  
  %%%%% Check class
  if ~strcmp(class(obj1), class(obj2))
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The objects are not from the same class. [%s] <-> [%s]', class(obj1), class(obj2));
    result = false;
    return
  end
  
  %%%%% Check length of obj1 and obj2
  fieldsA = fieldnames(obj1);
  fieldsB = fieldnames(obj2);
  if numel(fieldsA) ~= numel(fieldsB)
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The size of the %s-object''s. [%d] <-> [%d]', class(obj1), numel(obj1), numel(obj2));
    result = false;
    return
  end
  
  result = true;
  
  for oo = 1:numel(obj1)
    for ii = 1:numel(fieldsA)
      
      if isa(obj1(oo).(fieldsA{ii}), 'ltpda_obj')
        %%%%%%%%%%%%%   LTPDA objects
        if ~eq(obj1(oo).(fieldsA{ii}), obj2(oo).(fieldsA{ii}), plist('exceptions', exception_list, 'tol', tol))
          result = false;
          disaply_msg(obj1, oo, fieldsA{ii})
          return
        end
        
      elseif isstruct(obj1(oo).(fieldsA{ii}))
        %%%%%%%%%%%%%   STRUCTURE
        if ~eqstruct(obj1(oo).(fieldsA{ii}), obj2(oo).(fieldsA{ii}), exception_list, tol)
          result = false;
          disaply_msg(obj1, oo, fieldsA{ii})
          return
        end
        
      elseif iscell(obj1(oo).(fieldsA{ii}))
        %%%%%%%%%%%%%   STRUCTURE
        if ~eqcell(obj1(oo).(fieldsA{ii}), obj2(oo).(fieldsA{ii}), exception_list, tol)
          result = false;
          disaply_msg(obj1, oo, fieldsA{ii})
          return
        end
      elseif isnumeric(obj1(oo).(fieldsA{ii}))
        if ~utils.math.isequal(obj1(oo).(fieldsA{ii}), obj2(oo).(fieldsA{ii}), tol)
          result = false;
          disaply_msg(obj1, oo, fieldsA{ii})
          return
        end
        
      else
        if ~isequal(obj1(oo).(fieldsA{ii}), obj2(oo).(fieldsA{ii}))
          result = false;
          disaply_msg(obj1, oo, fieldsA{ii})
          return
        end
      end
      
    end % over all fields
  end % over all objects
  
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION:    eqcell
%
% DESCRIPTION: Equal method to compare structures
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function result = eqcell(obj1, obj2, exception_list, tol)
  
  import utils.const.*
  
  %%%%% Check class
  if ~strcmp(class(obj1), class(obj2))
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The objects are not from the same class. [%s] <-> [%s]', class(obj1), class(obj2));
    result = false;
    return
  end
  
  %%%%% Check length of obj1 and obj2
  if numel(obj1) ~= numel(obj2)
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The size of the %s-object''s. [%d] <-> [%d]', class(obj1), numel(obj1), numel(obj2));
    result = false;
    return
  end
  
  result = true;
  
  for ii = 1:numel(obj1)
    
    if isa(obj1{ii}, 'ltpda_obj')
      %%%%%%%%%%%%%   LTPDA objects
      if ~eq(obj1{ii}, obj2{ii}, plist('exceptions', exception_list, 'tol', tol))
        result = false;
        disaply_msg(obj1, ii, 'cell-object')
        return
      end
      
    elseif isstruct(obj1{ii})
      %%%%%%%%%%%%%   STRUCTURE
      if ~eqstruct(obj1{ii}, obj2{ii}, exception_list, tol)
        result = false;
        disaply_msg(obj1, ii, 'cell-object')
        return
      end
      
    elseif iscell(obj1{ii})
      %%%%%%%%%%%%%   CELL
      if ~eqcell(obj1{ii}, obj2{ii}, exception_list, tol)
        result = false;
        disaply_msg(obj1, ii, 'cell-object')
        return
      end
      
    elseif isnumeric(obj1{ii})
      %%%%%%%%%%%%%   Numberic
      if ~utils.math.isequal(obj1{ii}, obj2{ii}, tol)
        result = false;
        disaply_msg(obj1, ii, 'cell-object')
        return
      end
      
    else
      if ~isequal(obj1{ii}, obj2{ii})
        result = false;
        disaply_msg(obj1, ii, 'cell-object')
        return
      end
    end
  end
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION:    eqparam
%
% DESCRIPTION: Equal method to compare param-objects. The order of the
%              param-objects doesn't matter.
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function result = eqparam(obj1, obj2, exception_list, tol)
  
  import utils.const.*
  
  %%%%% Check class
  if ~strcmp(class(obj1), class(obj2))
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The objects are not from the same class. [%s] <-> [%s]', class(obj1), class(obj2));
    result = false;
    return
  end
  
  %%%%% Check length of obj1 and obj2
  if numel(obj1) ~= numel(obj2)
    utils.helper.msg(msg.PROC1, 'NOT EQUAL: The size of the %s-object''s. [%d] <-> [%d]', class(obj1), numel(obj1), numel(obj2));
    result = false;
    return
  end
  
  result = true;
  
  for oo = 1:numel(obj1)
    
    found = false;
    for ii = 1:numel(obj2)
      
      if strcmp(obj1(oo).key, obj2(ii).key)
        allparts = true;
        fields = fieldnames(obj1(oo));
        for ff = 1:numel(fields)
          field = fields{ff};
          
          ck_field = {field, sprintf('%s/%s', class(obj1), field)};
          if ~(any(utils.helper.ismember(ck_field, exception_list)))
            
            if strcmp(field, 'val')
              %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
              %%% Compare only the current value
              val1 = obj1(oo).getVal();
              val2 = obj2(ii).getVal();
              if isa(val1, 'ltpda_obj')
                %%%%%%%%%%%%%   LTPDA objects
                allparts = allparts && eq(val1, val2, plist('exceptions', exception_list, 'tol', tol));
                
              elseif isstruct(val1)
                %%%%%%%%%%%%%   STRUCTURE
                allparts = allparts && eqstruct(val1, val2, exception_list, tol);
                
              elseif iscell(val1)
                %%%%%%%%%%%%%   CELL
                allparts = allparts && eqcell(val1, val2, exception_list, tol);
              elseif isnumeric(val1)
                %%%%%%%%%%%%%   Numeric
                allparts = allparts && utils.math.isequal(val1, val2, tol);
              else
                allparts = allparts && isequal(val1, val2);
              end
              
              %%%%% Check the properties of the paramValue Object
              if isa(obj1(oo).val, 'paramValue')
                allparts = allparts && eqstruct(obj1(oo).val.property, obj2(ii).val.property, exception_list, tol);
              end
              
              continue;
            end
            
            if isa(obj1(oo).(field), 'ltpda_obj')
              %%%%%%%%%%%%%   LTPDA objects
              allparts = allparts && eq(obj1(oo).(field), obj2(ii).(field), plist('exceptions', exception_list, 'tol', tol));
              
            elseif isstruct(obj1(oo).(field))
              %%%%%%%%%%%%%   STRUCTURE
              allparts = allparts && eqstruct(obj1(oo).(field), obj2(ii).(field), exception_list, tol);
              
            elseif iscell(obj1(oo).(field))
              %%%%%%%%%%%%%   CELL
              allparts = allparts && eqcell(obj1(oo).(field), obj2(ii).(field), exception_list, tol);
              
            elseif isnumeric(obj1(oo).(field))
              %%%%%%%%%%%%%   Numeric
              allparts = allparts && utils.math.isequal(obj1(oo).(field), obj2(ii).(field), tol);
            else
              allparts = allparts && isequal(obj1(oo).(field), obj2(ii).(field));
            end
            
          end
          
        end
        if allparts
          found = true;
        end
      end
      
    end % inner-loop
    
    if ~found
      result = false;
      disaply_msg(obj1, oo, ['key: ''' obj1(oo).key ''''])
      return
    end
    
  end % outer-loop
  
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION:    getInfo
%
% DESCRIPTION: 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, 'plist', 'ltpda', utils.const.categories.relop, '$Id: eq.m,v 1.27 2011/04/08 08:56:21 hewitson Exp $', sets, pl);
  ii.setModifier(false);
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% FUNCTION:    getDefaultPlist
%
% DESCRIPTION: Get Default Plist
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function plo = getDefaultPlist()
  plo = plist();
  
  % Exceptions
  p = param({'Exceptions', 'Test the objects without the given property names'}, paramValue.EMPTY_CELL);
  plo.append(p);
  
  % Tolerance
  p = param({'Tol', 'Test double values with the given tolerance'}, paramValue.DOUBLE_VALUE(eps(1)));
  plo.append(p);
  
end

