function [ options ] = forest_opt_set( varargin )
%FOREST_OPT_SET Sets the options used in the main genetic algorithm function.
%   forest_opt_set() displays the options available.
%
%   OPTIONS = forest_opt_set() creates a structure with default parameters
%
%   OPTIONS = forest_opt_set('PARAM1',VALUE1,'PARAM2',VALUE2,....) will create
%   a structure using default parameters. PARAMs specified in the input
%   will be assigned the corresponding VALUE.
%
%   OPTIONS = forest_opt_set(OLDOPTS,'PARAM',VALUE) will create a structure
%   using a prior options structure and reassigning fields corresponding to
%   the parameter value pairs.
%
%forest_opt_set PARAMETERS
%
%   Trees               - The number of trees in each forest
%                       [ positive integer | {500} ]
%   UpdatedTrees        - The number of trees updated at each iteration
%                       [ positive integer | {2} ]
%   Iterations          - The number of iterations per MCMC run
%                       [ positive integer | {20000} ]
%   Resets              - The number of MCMC resets
%                       [ positive integer | {2} ]
%   Save                - The frequency to save the model after burn-in
%                       [ positive integer | {2000} ]
%   BurnIn              - The percent of iterations to use for burn in
%                       [ positive double between 0 and 100 | {20} ]
%   Width               - The width of the prior used for the intercept
%                       [ positive double | {data dependent} ]
%   Sigma               - The width of the prior used for the variance
%                       [ positive double | {data dependent} ]
%   Group               - Optional groups for correlated observations
%                       [ positive categorical integers | {data dependent} ]
%   GroupPower          - Strength of the correlation within groups 
%                           0 = no appreciable correlation
%                           1 = very strong correlation
%                       [ positive integer between 0 and 1 | {data dependent} ]

%	$LastChangedBy: alistair $
%	$LastChangedDate: 2012-05-30 12:21:30 +0100 (Wed, 30 May 2012) $
%	$Revision: 21 $
%	Originally written on GLNXA64 by Alistair Johnson, 13-May-2012 20:32:27
%	Contact: alistairewj@gmail.com

%=== create defaults
if nargout==0
    displayForestOptions; % Call subfunction to display options
    return;
else
    options=struct( ...
        'Trees', 500, ...
        'UpdatedTrees', 2, ...
        'Iterations', 100000, ...
        'Resets', 5, ...
        'Save', 2000, ...
        'BurnIn', 20, ...
        'Width', [], ...
        'Sigma', [], ...
        'Family', [], ...
        'Group', [], ...
        'GroupPower', [] ...
        );
end
if nargin==0
    return;
end

%=== allowed parameters
okargs=fieldnames(options);
numargs=length(varargin);

%=== Loop through input arguments
k=1; % Varargin index

%=== Check if first input is a structure
if isstruct(varargin{k})
    %=== Scan through structure, use values if appropriate
    fn=fieldnames(varargin{k});
    fn_L=length(fn);
    %=== Loop through input options
    for s=1:fn_L
        %=== Commented code left for readability
        %         pname = fn{s}; pval = varargin{k}.(fn{s});
        %         param = find(strcmpi(pname, okargs)==1,1);
        [options] = updateOptionsField(options, ...
            fn{s}, varargin{k}.(fn{s}), find(strcmpi(fn{s}, okargs)==1,1));
    end
    k=k+1;
end

%=== check for incorrect number of inputs
if (k==1 && rem(nargin,2) == 1) || ... % No structure, odd number of inputs
        (k==2 && rem(nargin,2) == 0) % Structure, even number of inputs
    error(sprintf('Options:%s:IncorrectNumberOfArguments',mfilename),...
        'Incorrect number of arguments to %s.',mfilename);
end

%=== Parse remaining Parameter/Value inputs
while k<=numargs
    [options] = updateOptionsField(options, ...
        varargin{k}, varargin{k+1}, find(strcmpi(varargin{k}, okargs)==1,1));
    k=k+2;
end

%=== Validate intraconsistency of options parameters
[options] = validateConsistency(options);
end

function [options] = updateOptionsField(options, pname, pval, param)
% This function validates the input parameter name/value and updates the
% associated value in options
% This function calls the following other subfunctions:
%   validateParamType - Validates that the parameter is of the right type
%   validateParamIsSubset - Validates that the parameter has a correct value from a
%           set of acceptable values

if isempty(param) % Parameter name not found in okargs
    warning(sprintf('forest_opt_set:%s:UnknownParameterName', mfilename), ...
        'Unknown parameter %s was ignored.',pname);
    return;
else % Parameter name found
    %=== Check if parameter is the right type
    [valid1,errmsg1]=validateParamType(pname,pval);
    
    if valid1==0
        % Parameter is not of the right type
        error(sprintf('forest_opt_set:%s:invalidParamVal',mfilename),errmsg1);
    else
        %=== Check if parameter is one of a set of acceptable values
        [valid2,errmsg2]=validateParamIsSubset(pname,pval);
    end
        
    if valid2==0 
        error(sprintf('forest_opt_set:%s:invalidParamVal',mfilename),errmsg2);
    else % Everything is OK, don't panic.
        options.(pname)=pval;
    end
end

end

function [valid, errmsg] = validateParamType(param, val)
valid=1; errmsg='';
if isempty(val) % Default used
    return;
end

switch param
    case {'Iterations','Resets','Save','Trees','UpdatedTrees'}
        % Positive definite integers 
        if ~isnumeric(val) || val<0
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric',param);
        end
        if abs(val-floor(val))>1e-15 % Not an integer
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric',param);
        end
    case 'Group'
        % Positive definite integer vectors
        if ~isnumeric(val)
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be numeric',param);
        end
        if any(val<0)
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive number',param);
        end
        if any(abs(val-floor(val))>1e-15) % Not an integer
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric',param);
        end
    case 'GroupPower'% Doubles between 0 and 1
        if ~isnumeric(val) || val<0
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric',param);
        end
        if val>1
            valid=0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be between 0 and 1',param);
        end
    case {'BurnIn'}
        % Positive definite doubles between 0 and 100
        if ~isnumeric(val) || val<0 || val>100
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric between 0 and 100',param);
        end
    case {'Width','Sigma'}
        % Positive definite doubles
        if ~isempty(val) && (~isnumeric(val) || val<0)
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric above 0',param);
        end
    case {'Family'}
        % String
        if ~isempty(val) && ~ischar(val)
            valid = 0;
            errmsg = sprintf('Invalid value for OPTIONS parameter %s: must be a positive numeric above 0',param);
        end
    otherwise
        valid=0;
        errmsg = sprintf('Unknown parameter field %s', param);
end

end

function [valid,errmsg] = validateParamIsSubset(pname,pval)
% This function ensures that the given functions are members of a
% pre-defined set.
% Ensure pval is a valid member of the costFcn set - i.e. that pval exists
% in the directory as a function
valid=1;
errmsg='';

%=== Get pname
if strcmp(pname,'Family')
    if ~isempty(pval)
        switch pval
            case 'binomial'
                % ok
            case 'normal'
                % ok
            otherwise
                % not ok
                valid=0;
                errmsg='Family must be ''binomial'' or ''normal''.';
        end
    end
    
end

end

function [options] = validateConsistency(options)

paramsChecked = {'Save'};

for k=1:length(paramsChecked)
    param = paramsChecked{k};
    pval = options.(param);
    switch param
        case 'Save'
            if mod(options.Iterations,pval)>0
                warning(sprintf('forest_opt_set:%s:NotIntegerMultiple', mfilename), ...
                    'Value of ''Iterations'', %g, is not an integer multiple of''Save'', %g..',...
                    options.Iterations,pval);
            end
        case 'Family'
            pval = lower(pval);
            options.(param) = pval;
        otherwise
            % Recite Acadian Poetry ... or do nothing, your choice!
            % there is beauttiful Acadian Poem at http://www.gov.ns.ca/legislature/library/digitalcollection/bookpart1.stm
    end
end
end


function [] = displayForestOptions

fprintf('forest_opt_set PARAMETERS\n');
fprintf('\n');
fprintf('   Trees               - The number of trees in each forest\n');
fprintf('                       [ positive integer | {200} (1,Inf) ]\n');
fprintf('   Iterations          - The number of iterations per MCMC run\n');
fprintf('                       [ positive integer | {20000} (1,Inf) ]\n');
fprintf('   Resets              - The number of MCMC resets\n');
fprintf('                       [ positive integer | {2} (1,Inf) ]\n');
fprintf('   Save                - The frequency to save the model after burn-in\n');
fprintf('                       [ positive integer | {2000} (1,Inf) ]\n');
fprintf('   BurnIn              - The percent of iterations to use for burn in\n');
fprintf('                       [ positive integer | {20} (0,1) ]\n');
fprintf('   Groups              - Optional groups for correlated observations\n');
fprintf('                       [ positive categorical integers | {data dependent} ]\n');

end