function [Spread, Settle, Maturity, RateSched, FloatReset, Basis, Principal, EOM, CapRate, FloorRate] ...
    = instargrangefloat(varargin)
%INSTARGRANGEFLOAT Subroutine for 'Type','RangeFloat' argument validation.
%   This function is called at the top of processing routines.
%
%   [Spread, Settle, Maturity, RateSched, Reset, Basis, Principal, ...
%          EndMonthRule, CapRate, FloorRate] = instargrangefloat(ArgList{:})
%
%   Inputs:
%     ArgList{:} input arguments to be dealt one-to-one to the outputs.
%
%   Outputs:
%     Outputs are conforming NINST x 1 vectors.
%
%     Spread        - Number of basis points over the reference rate.
%     Settle        - Settlement date.
%     Maturity      - Maturity date.
%     RateSched    - NINSTx1 vector of structures representing the range of rates
%                    within which cash flows are non-zero. Each element of the
%                    structure array contains two fields:
%
%                RateSched.Dates - NDatesx1 cell array of dates corresponding
%                                  to the range schedule.
%                RateSched.Rates - NDatesx2 array with the first column
%                                  containing the lower bound of the range
%                                  and the second column containing the upper bound of the range. Cash flow
%                                  for date RateSched.Dates(n) will be non-zero for rates in
%                                  the range RateSched.Rates(n,1) < Rate < RateSched.Rates(n,2)
%     Reset         - Reset frequency per year. Default is 1.
%     Basis         - Day-count basis.  Default is 0 (actual/actual).
%     Principal     - Notional principal amount. Default is 100.
%     EndMonthRule  - End of Month rule. Default is 1(in effect).
%     CapRate  - NINSTx1 decimal annual rate or NINSTx1 cell array
%                where each element is a NumDatesx2 cell array where the
%                first column is dates and the second column is associated
%                cap rates.  The date indicates the last day that the cap
%                rate is valid.
%
%     FloorRate - NINSTx1 decimal annual rate or NINSTx1 cell array
%                 where each element is a NumDatesx2 cell array where the
%                 first column is dates and the second column is associated
%                 floor rates.  The date indicates the last day that the floor
%                 rate is valid.
%
%   See also INSTRANGEFLOAT.

%   Copyright 1995-2017 The MathWorks, Inc.

%---------------------------------------------------------------------
% Content independent processing
%---------------------------------------------------------------------

% Get the argument information from a combination of instrangefloat and
% instfloat. We do this because instargrangefloat is called by the engine
% function that handles both ranges and floats
[RangeFieldList, RangeClassList, ~, RangeSizeList, RangeDefDataList] = instrangefloat;
[FloatFieldList, FloatClassList, ~, FloatSizeList, FloatDefDataList] = instfloat;
[FieldList, iRange, iFloat] = union(RangeFieldList, FloatFieldList, 'stable');
ClassList = [RangeClassList(iRange);  FloatClassList(iFloat)];
SizeList = [RangeSizeList(iRange);  FloatSizeList(iFloat)];
DefDataList = [RangeDefDataList(iRange);  FloatDefDataList(iFloat)];
NumFields = length(FieldList);

% List of output arguments through parsing
ArgCells = cell(NumFields,1);

% Check the number of arguments
if length(varargin) < 4 
    error(message('finance:instargrangefloat:notEnoughInputs'));
elseif length(varargin)>NumFields
    error(message('finance:instargrangefloat:tooManyInputs'));
else
    EndArgs = cell(1,NumFields-length(varargin));
end

% handle scheduled Principal which may contain date char vector, date string or
% datetime
if length(varargin)>=7 && iscell(varargin{7})
    for i = 1:length(varargin{7})
        if (iscell(varargin{7}{i}))            
            varargin{7}{i}(:,1) = cellfun(@(x)todatechar(x), varargin{7}{i}(:,1), 'UniformOutput', false);
        end
    end
end

% handle scheduled CapRate which may contain date char vector, date string or
% datetime
if length(varargin)>=9 && iscell(varargin{9})
    for i = 1:length(varargin{9})
        if (iscell(varargin{9}{i}))
            varargin{9}{i}(:,1) = cellfun(@(x)todatechar(x), varargin{9}{i}(:,1), 'UniformOutput', false);
        end
    end
end

% handle scheduled FloorRate which may contain date char vector, date string or
% datetime
if length(varargin)>=10 && iscell(varargin{10})
    for i = 1:length(varargin{10})
        if (iscell(varargin{10}{i}))
            varargin{10}{i}(:,1) = cellfun(@(x)todatechar(x), varargin{10}{i}(:,1), 'UniformOutput', false);
        end
    end
end

% Parse for type
[ArgCells{:}] = finargparse(ClassList, varargin{:}, EndArgs{:});

% Enforce size limits if possible by transposing
[ArgCells{:}] = finargflip(SizeList, ArgCells{:});

% Report size violations
for iArg = 1:NumFields
    NumCols = size(ArgCells{iArg},2);
    if (NumCols > SizeList{iArg}(2))
        error(message('finance:instargrangefloat:tooManyColumns', FieldList{ iArg }, SizeList{ iArg }( 2 ), NumCols));
    end
end

% perform row expansion along the instruments
ArgCells(cellfun(@isempty, ArgCells)) = {NaN};
[ArgCells{:}] = finargsz(1,ArgCells{:});

% Fill in defaults for NaNs
for iArg = 1:NumFields
    if(isnumeric(ArgCells{iArg}))
        Ind = isnan(ArgCells{iArg});
        ArgCells{iArg}(Ind) = DefDataList{iArg};
    end
end

%---------------------------------------------------------------------
% Content independent processing
%---------------------------------------------------------------------

[Spread, Settle, Maturity, RateSched, FloatReset, Basis, Principal, EOM, CapRate, FloorRate] = ...
    deal(ArgCells{:});

%---------------------------------------------------------------------
% Check validity of parameters
%---------------------------------------------------------------------

% Spread, Settle, Maturity and RateSched are required

if (any(isnan(Spread)) || any(isempty(Spread)))
    Spread = 0;
end

if any(isnan(Settle))
    error(message('finance:instargrangefloat:missingSettle'));
end

if any(isnan(Maturity))
    error(message('finance:instargrangefloat:missingMaturity'));
end

% Settle < Maturity
if any(Settle >= Maturity)
    error(message('finance:instargrangefloat:invalidSettle'));
end

if( any(FloatReset ~= 1 & FloatReset ~= 2 & FloatReset ~= 3 & ...
        FloatReset ~= 4 & FloatReset ~= 6 & FloatReset ~= 12))
    error(message('finance:instargrangefloat:invalidReset'));
end

if any(~isvalidbasis(Basis))
    error(message('finance:instargrangefloat:invalidBasis'));
end


if any(EOM ~= 0 & EOM ~= 1)
    error(message('finance:instargrangefloat:invalidEOM'));
end

if isnumeric(Principal) && any(Principal < 0)
    error(message('finance:instargrangefloat:invalidPrincipal'))
end
Principal = tonumeric(Principal, [], 'finance:instargrangefloat:invalidPrincipal');

if isstruct(RateSched)
    for sIdx = 1:length(RateSched)
        if(length(RateSched(sIdx).Dates) ~= size(RateSched(sIdx).Rates,1) || ...
                size(RateSched(sIdx).Rates,2) ~= 2)
            error(message('finance:instargrangefloat:invalidRateSchedSize'));
        end
    end
end

if ~isnumeric(CapRate) && ~iscell(CapRate)
    error(message('finance:instargrangefloat:invalidCapRate'));
end
CapRate = tonumeric(CapRate, Maturity, 'finance:instargrangefloat:invalidCapRate');

if ~isnumeric(FloorRate) && ~iscell(FloorRate)
    error(message('finance:instargrangefloat:invalidFloorRate'));
end
FloorRate = tonumeric(FloorRate, Maturity, 'finance:instargrangefloat:invalidFloorRate');

function DatesCell = tonumeric(DatesCell, Maturity, msgIdent)
if iscell(DatesCell)        
    
    % If dates were passen in as string, turn them into serial dates, and
    % turn the inner cell arrays into numeric arrays:
    for iDateIdx = 1:length(DatesCell)                                
        if iscell(DatesCell{iDateIdx})
            % It must be then a NDates x 2 cell array            
            if(size(DatesCell{iDateIdx},2) ~= 2)
                error(message(msgIdent));
            end
                        
            %  Turn into NDates x 2 numeric array
            if(iscell( DatesCell{iDateIdx}(:,1)) && all(cellfun(@ischar,DatesCell{iDateIdx}(:,1))))
                DatesCell{iDateIdx} = [datenum(DatesCell{iDateIdx}(:,1)) cell2mat(DatesCell{iDateIdx}(:,2))];
            elseif(iscell( DatesCell{iDateIdx}(:,1)) && all(cellfun(@isnumeric,DatesCell{iDateIdx}(:,1))))
                DatesCell{iDateIdx} = cell2mat(DatesCell{iDateIdx});
            else
                NDates = size(DatesCell{iDateIdx},1);
                FMat = zeros(NDates,2);
                for iDate = 1:NDates
                    if(iscell(DatesCell{iDateIdx}(iDate, 1)))
                        FMat(iDate,:) = [datenum(DatesCell{iDateIdx}{iDate,1} ) DatesCell{iDateIdx}{iDate,2}];
                    else
                        FMat(iDate,:) = DatesCell{iDateIdx};
                    end
                end
                DatesCell{iDateIdx} = FMat;
            end
        else
            if ~isnumeric(DatesCell{iDateIdx}) || (length(DatesCell{iDateIdx})~=1 && size(DatesCell{iDateIdx},2) ~=2)
                % If not a cell array then it needs to be a single number
                % or a Nx2 numeric array
                error(message(msgIdent));            
            end
            if ~isempty(Maturity)
                % If a maturity was passed in (FloorRate and CapRate), then
                % mark this as the value all the way to the instrument's
                % maturity.
                DatesCell{iDateIdx} = [Maturity(iDateIdx), DatesCell{iDateIdx}];
            end
        end
    end
end

function DateCell = todatechar(DateCell)

if isdatetime(DateCell) && isnat(DateCell)
    DateCell = NaN;
end

if isstring(DateCell) || isdatetime(DateCell)
    DateCell = char(DateCell);
end


% [EOF]
