function parms = CheckOptions(options, options_schema, strict)
%% options = CheckOptions(keyvals, options_schema, [strict])
% Purpose: organize key/value pairs in structure with default or
% user-supplied values according to a schema.
% Inputs:
% keyvals: list of key/value pairs ('option1',value1,'option2',value2,...)
% options_schema: cell array containing 3 values per known 'option':
% - option name
% - default value
% - allowed values
% vector of true/false values
% vector of min/max values
% vector of allowed values (more than 2 elements)
% cell array of allowed values
% empty to specify no limitations.
% strict: whether to fail if options not specified in the options_schema
% are found. (default: true)
% Outputs:
% options: structure with options (using default values if not supplied)
%
% See also: Options2Keyval, CheckSpecification, CheckModel, CheckData
% note: this function was adapted from one developed "in-house" years ago...
if (0 ~= mod(length(options),2)) % Validate that the # of args is even
error('List of arguments must be even (must have name/value pair)');
end;
if (0 ~= mod(length(options_schema),3)) % Validate the options_schema info is right
error('Programming error: list of default arguments must be even (must have name/value pair)');
end;
if (~exist('strict', 'var'))
strict = true;
end;
parms = [];
% Rip all cell arguments into non-cell arguments.
for index = 1:length(options)
if iscell(options{index})
if isempty(options{index})
options{index}=[];
elseif ~iscell(options{index}{1})
%options{index} = { options{index} };
end
end;
end;
valid_fields = options_schema(1:3:end);
input_fields = options(1:2:end);
unknown_fields = setdiff(input_fields, valid_fields);
% Validate that there are no extraneous params sent in
if (strict && ~isempty(unknown_fields))
error('The following unrecogized options were passed in: %s', sprintf('%s ',unknown_fields{:}));
end;
if (~isempty( options ))
for f=1:length(options)/2
parms.(options{2*f-1})=options{2*f};
end
%parms=struct(options{:});
end;
% This allows 'pass-through' of parameters;
% remove any fields that are unknown
% unless no schema is defined.
if (~strict && ~isempty(parms) && ~isempty(options_schema))
parms = rmfield(parms,unknown_fields);
end;
% Check arg values and set defaults
for f=1:3:length(options_schema)
% The value has been set explicitly by the caller;
% Validate the input parameters by the 'range' field
if (isfield(parms,options_schema{f}))
param_name = options_schema{f};
param_value = getfield(parms,param_name);
param_range = options_schema{f+2};
% no value was specified,
if isempty(param_value)
parms = setfield(parms, options_schema{f}, options_schema{f+1});
% no range was specified,
elseif isempty(param_range)
continue;
% param range is a cell array of strings; make sure the current value is within that range.
elseif iscell(param_range)
num_flag = 0;
char_flag = 0;
for i=1:length(param_range)
if isnumeric(param_range{i}), num_flag=1; end;
if ischar(param_range{i}), char_flag=1; end;
end;
if num_flag && char_flag
error('type of parameter range (cell array of numbers and strings) specified for parameter ''%s'' is currently unsupported', options_schema{f});
elseif char_flag
if iscell(param_value)
for i=1:length(param_value)
if ~ischar(param_value{i})
error('parameter ''%s'' must be string or cell array of strings', options_schema{f});
end;
end;
elseif ~ischar(param_value)
error('parameter ''%s'' must be string or cell array of strings', options_schema{f});
else
param_value = {param_value};
end;
if length(find(ismember(param_value,param_range))) ~= length(param_value)
error('parameter ''%s'' value must be one of the following: { %s}', ...
param_name, sprintf('''%s'' ',param_range{:}));
end;
elseif num_flag
param_range = cell2mat(param_range);
if ~isnumeric(param_value)
error('parameter ''%s'' must be numeric', options_schema{f});
end;
if length(find(ismember(param_value,param_range))) ~= length(param_value)
error('parameter ''%s'' value must be one of the following: { %s}', ...
param_name,sprintf('%d ',param_range));
end;
else
error('type of parameter range specified for parameter ''%s'' is currently unsupported', options_schema{f});
end;
% param range is logical and has two elements (i.e. true/false)
elseif islogical(param_range) && length(param_range)==2
if ~ismember(param_value,param_range)
error('parameter %s value must be true (1) or false (0)', options_schema{f});
end;
% param range is numeric and has two elements (i.e. min and max)
elseif isnumeric(param_range) && length(param_range)==2
% param range is numeric or logical, and within a specified range,
if ~isempty(find(param_value < param_range(1))) || ...
~isempty(find(param_value > param_range(2)))
if int64(param_range(1))==param_range(1) && int64(param_range(2))==param_range(2)
error('parameter %s value must be between %d and %d',...
options_schema{f},param_range(1),param_range(2));
else
error('parameter %s value must be between %0.4f and %0.4f',...
options_schema{f},param_range(1),param_range(2));
end;
end;
% param range is numeric and has more than two elements (allowed values)
elseif isnumeric(param_range)
if ~isnumeric(param_value)
error('parameter ''%s'' must be numeric', options_schema{f});
end;
if length(find(ismember(param_value,param_range))) ~= length(param_value)
error('parameter ''%s'' value must be one of the following: [ %s]', ...
param_name,sprintf('%d ',param_range));
end;
% param range is of a type we currently don't support.
else
error('type of parameter range specified for parameter ''%s'' is currently unsupported', options_schema{f});
end;
% field not found, so set the default value.
else
%parms = setfield(parms, options_schema{f}, options_schema{f+1});
parms.(options_schema{f})=options_schema{f+1};
end;
end;