function predicted=cosmo_classify_svm(samples_train, targets_train, samples_test, opt)
% classifier wrapper that uses either matlab's or libsvm's SVM.
%
% predicted=cosmo_classify_svm(samples_train, targets_train, samples_test, opt)
%
% Inputs:
%   samples_train      PxR training data for P samples and R features
%   targets_train      Px1 training data classes
%   samples_test       QxR test data
%   opt                (optional) struct with options for classification
%                      If a field 'svm' is present it should be either
%                      'libsvm' or 'matlabsvm' to use that SVM. If this
%                      field is absent it will be selected automatically.
%
% Output
%   predicted          Qx1 predicted data classes for samples_test
%
% Notes:
%  - cosmo_classify_svm can use either libsvm or matlab's svm, whichever is
%    present
%  - if both are present, then there is a conflict because 'svmtrain' is
%    implemented differently by libsvm or matlab's svm. The path setting
%    determines which svm implementation is used.
%  - when using libsvm it requires version 3.18 or later:
%    https://github.com/cjlin1/libsvm
%  - for a guide on svm classification, see
%      http://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf
%  - Matlab's SVM classifier is rather slow, especially for multi-class
%    data (more than two classes). When classification takes a long time,
%    consider using libsvm.
%  - In both implemenations, by default the data is scaled.
%    Note that cosmo_crossvalidate and cosmo_crossvalidation_measure
%    provide an option 'normalization' to perform data scaling.
%
%
% See also: svmtrain, svmclassify, cosmo_classify_svm,
%           cosmo_classify_libsvm, cosmo_crossvalidate,
%           cosmo_crossvalidation_measure
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

persistent cached_classifier_func;
persistent cached_classifier_name;

auto_select=true;

if nargin>=4
    if isfield(opt,'svm')
        svm_name=opt.svm;
        auto_select=false;
        opt=rmfield(opt,'svm');
    end
else
    opt=struct();
end

if ~isnumeric(cached_classifier_func) && ...
                        (auto_select || strcmp(svm_name, ...
                                        cached_classifier_name))
    classifier_func=cached_classifier_func;
else
    if auto_select
        svm_name='libsvm';

        if ~cosmo_check_external(svm_name, false) && ...
                        cosmo_check_external('matlabsvm',false)
            svm_name='matlabsvm';
        end
    end

    % let it throw an error if there is a conflict (e.g. matlabsvm with
    % neuroelf)
    cosmo_check_external(svm_name);

    switch svm_name
        case 'libsvm'
            classifier_func=@cosmo_classify_libsvm;
        case 'matlabsvm';
            classifier_func=@cosmo_classify_matlabsvm;
        otherwise
            error(['unsupported svm ''%s'': must be one of: '...
                        'matlabsvm, libsvm'], svm_name);
    end

    cached_classifier_name=svm_name;
end

% ensure that the classifer func is not stored in this function if an error
% occurs. After sucessful classifcation the classifier_func is restored.
cached_classifier_func=[];

predicted=classifier_func(samples_train, targets_train, samples_test, opt);

cached_classifier_func=classifier_func;


