classdef mexargs_test
% MEXARGS_TEST helper class to test argument checks in C MEX-files
%
% See README.md for more details on how to use it

  properties
   S
   fun
   default
   nargout
  end
  
  methods
    function obj = mexargs_test(S)
      % MEXARGS_TEST constructor

      obj.S = S;
      obj.fun = S.mexname;
      obj.default = {S.arg.default};

      % Choose working number of output arguments
      check = S.nargout.check;
      checkname = check{1};
      checkval = check{2};
      switch checkname
        case 'le'
          obj.nargout = checkval;
        otherwise
          error('nargout check "%s" not implemented yet',checkname);
      end
    end

    function check_default(obj,testCase)
      % Check default arguments
      argout = cell(1,obj.nargout);
      try
        [argout{:}] = feval(obj.fun,obj.default{:}); %#ok<NASGU>
      catch ME
        testCase.verifyFail(sprintf('%s could not be run with the provided set of default arguments:\n id     : %s\n message: %s',upper(obj.fun),ME.identifier,ME.message));
      end
    end

    function check_nargin(obj,testCase)
      % verifyError does not support specifying number of output arguments in R2017a
      import matlab.unittest.constraints.Throws
      
      % NARGIN checks
      check = obj.S.nargin.check;
      for ii = 1:2:numel(check)
        checkname = check{ii};
        checkval  = check{ii+1};
        switch checkname
          case 'eq'
            nargs = checkval+1;
          case 'ge'
            nargs = checkval-1;
          case 'le'
            nargs = checkval+1;
          case 'one_of'
            nargs = max(checkval)+1;
          otherwise
            error('nargin check "%s" not implemented yet',checkname);
        end
        if nargs > numel(obj.default)
          args = [obj.default,cell(1,nargs-numel(obj.default))];
        else
          args = obj.default(1:nargs);
        end
        testCase.verifyThat(@() feval(obj.fun,args{:}),Throws(sprintf('%s:nargin',obj.fun),'WhenNargoutIs',obj.nargout),...
          sprintf('%s failed to throw the correct exception when given an incorrect number of input arguments',upper(obj.fun)));
      end
    end

    function check_arguments(obj,testCase)
      % verifyError does not support specifying number of output arguments in R2017a
      import matlab.unittest.constraints.Throws
      
      % Individual argument check
      for iarg = 1:numel(obj.S.arg)
        check = obj.S.arg(iarg).check;
        for ii = 1:2:numel(check)
          checkname = check{ii};
          checkval  = check{ii+1};
          args = obj.default;
          switch checkname
            case {'type','type_match'}
              if strcmp(checkname,'type_match')
                newtype = 'single'; % Assumes this only occurs for real
	      else
                newtype = 'int32';
		if strcmp(checkval,'numeric')
		  newtype = 'cell';
		elseif ~ismember(checkval,{'real','double','single'})
                  newtype = 'double';
                end
              end
              if strcmp(newtype,'cell')
		args{iarg} = {args{iarg}};
	      else
		args{iarg} = cast(args{iarg},newtype);
	      end
              errorid = sprintf('%s:inputType',obj.fun);
            case {'numel','ncols','nrows','scalar','scalar_or_empty'}
              def = args{iarg};
              [m,n] = size(def);
              switch checkname
                case 'nrows'
                  val = rand(m+1,n);
                case 'scalar_or_empty'
                  val = rand(2,1);
                otherwise
                  val = rand(m,n+1);
              end
              args{iarg} = cast(val,'like',def);
              errorid = sprintf('%s:inputSize',obj.fun);
            otherwise
              error('argument check "%s" not implemented yet',checkname)
          end
          testCase.verifyThat(@() feval(obj.fun,args{:}),Throws(errorid,'WhenNargoutIs',obj.nargout),...
            sprintf('%s failed to throw the correct exception when given input argument #%d of incorrect %s',upper(obj.fun),iarg,checkname));
        end
      end
    end

    function check_nargout(obj,testCase)
      % verifyError does not support specifying number of output arguments in R2017a
      import matlab.unittest.constraints.Throws
      
      % NARGOUT check
      check = obj.S.nargout.check;
      checkname = check{1};
      checkval = check{2};
      switch checkname
        case 'le'
          nargout_ = checkval+1;
        otherwise
          error('nargout check "%s" not implemented yet',checkname);
      end
      testCase.verifyThat(@() feval(obj.fun,obj.default{:}),Throws(sprintf('%s:nargout',obj.fun),'WhenNargoutIs',nargout_),...
        sprintf('%s failed to throw the correct exception when given an incorrect number of output arguments',upper(obj.fun)));
    end
  end
  
end
