function T = sldlda(X, nums, varargin)
%SLDLDA Performs Direct Linear Discriminant Analysis
%
% $ Syntax $
%   - T = sldlda(X, nums)
%   - T = sldlda(X, nums, ...)
%
% $ Arguments $
%   - X:        the training sample matrix
%   - nums:     the numbers of samples in all classes
%   - T:        the solved transform matrix
%
% $ Description $
%   - T = sldlda(X, nums) performs direct LDA on the samples X using 
%     default settings.
%
%   - T = sldlda(X, nums, ...) performs direct LDA on the samples X
%     with the specified properties.
%     \*
%     \t   Table 1.  The properties of Fisher Discriminant Analysis   \\
%     \h     name     &     description                                \\
%           'pdimset' &  The cell containing the arguments for determining
%                        the range space of Sb. They will be input to
%                        slrangespace for dimension determination.
%           'whiten'  &  The cell containing the arguments for computing 
%                        the whitening transform in 2nd stage. They will
%                        input to slwhiten_from_cov.
%                        default = {}.       \\
%           'Sb'      &  The pre-computed between-class scattering matrix
%                        or the cell containing the arguments for 
%                        computing the scatter matrix in the form
%                        {type, ...}, which is input to slscatter.     \\
%           'Sw'      &  The pre-computed within-class scattering matrix
%                        or the cell containing the arguments for 
%                        computing the scatter matrix in the form
%                        {type, ...}, which is input to slscatter.     \\
%         'weights'   &  The sample weights. default = [].             \\
%     \*  
%
% $ Remarks $
%   -# The function solves the transform in mainly following stages: 
%      First solves the range space of between-class scattering, 
%      projecting all samples onto it. Then, solve the whitening transform
%      of the projected within-class scattering.
%      
%   -# If Sb or its computing rule is given, the range space is  directly 
%      solved from Sb, otherwise the null space is solved from class 
%      centers. If both Sb and Sw are given, then the samples are not 
%      used in the function. In this cases, you can simply input an empty X. 
%
%   -# If both Sb and Sw are given, the pre-pca step will not be conducted.
%      no matter whether prepca is true or false.
%
% $ History $
%   - Created by Dahua Lin on May 1st, 2006
%

%% parse and verify input arguments 

if nargin < 2
    raise_lackinput('slfld', 2);
end

% check size

if ~isempty(X)    
    if ndims(X) ~= 2
        error('sltoolbox:invaliddims', ...
            'The sample matrix X should be a 2D matrix');
    end
    [d, n] = size(X);
    
    k = length(nums);
    if ~isequal(size(nums), [1, k]);
        error('sltoolbox:invaliddims', ...
            'The nums vector should be a row vector');
    end
    if sum(nums) ~= n
        error('sltoolbox:sizmismatch', ...
            'The total number in nums is not consistent with that in X');
    end
end

% check options
opts.pdimset = {};
opts.whiten = {};
opts.Sb = {'Sb'};
opts.Sw = {'Sw'};
opts.weights = [];
opts = slparseprops(opts, varargin{:});

has_Sb = ~isempty(opts.Sb) && isnumeric(opts.Sb);
has_Sw = ~isempty(opts.Sw) && isnumeric(opts.Sw);
if has_Sb && has_Sw
    d = size(opts.Sw, 1);
    
    if ~isequal(size(opts.Sb), [d, d]) || ~isequal(size(opts.Sw), [d, d])
        error('sltoolbox:sizmismatch', ...
            'Size consistency in Sb and Sw');
    end
        
else
    if isempty(X)
        error('sltoolbox:invalidargs', ...
            'The samples cannot be empty when Sb or Sw is not pre-computed');
    end
    if (has_Sb && ~isequal(size(opts.Sb), [d, d])) || (has_Sw && ~isequal(size(opts.Sw), [d, d]))
        error('sltoolbox:sizmismatch', ...
            'Size consistency in Sb and Sw');
    end
    
end
w = opts.weights;

%% Step 1: Compute range space of Sb

if has_Sb
    T1 = slrangespace({'cov', opts.Sb}, opts.pdimset{:});
elseif ~isempty(opts.Sb) && ~isequal(opts.Sb, {'Sb'})
    Sb = slscatter({'cov', X}, opts.Sb{:}, 'sweights', w, 'nums', nums);
    T1 = slrangespace(Sb, opts.pdimset{:});
    clear Sb;
else
    Xc = get_weighted_centers(X, w, nums);
    T1 = slrangespace(Xc, opts.pdimset{:});
    clear Xc wc;
end


%% Step 2: Compute the whiten transform for Sw on range space

if has_Sw
    PSw = T1' * opts.Sw * T1;
else
    X = T1' * X;
    PSw = slscatter(X, opts.Sw{:}, 'sweights', w, 'nums', nums);
end
T2 = slwhiten_from_cov(PSw, opts.whiten{:});
T2 = flipdim(T2, 2);

%% Integrate the transforms

T = T1 * T2;


%% The function for computing weighted centers
function [Xc, wc] = get_weighted_centers(X, w, nums)

Xc = slmeans(X, w, nums);
if isempty(w)
    wc = nums;
else
    k = length(nums);
    [sp, ep] = slnums2bounds(nums);
    wc = zeros(1, k);
    for i = 1 : k
        wc(i) = sum(w(sp(i):ep(i)));
    end
end
wc = sqrt(max(wc, 0));
Xc = slmulvec(Xc, wc, 2);


