%
% This code is part of the supplementary material to the CVPR 2013 paper
% "From N to N+1: Multiclass Transfer Incremental Learning",
% Ilja Kuzborskij, Francesco Orabona and Barbara Caputo
%
% Copyright (C) 2012-2013, Ilja Kuzborskij
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.
%
% Contact the author: ilja.kuzborskij [at] gmail.com
%

classdef MulticlassOneVsRest < GenericClassifier

    properties
    end

    methods

        function obj = MulticlassOneVsRest(parameters)
            obj@GenericClassifier(parameters);
        end

        function train_model(obj, hyperparameters, data_obj)

            bin_classifier_set = {};

            unique_classes = unique(data_obj.train_y);

            for i=1:numel(unique_classes)
                class_id = unique_classes(i);

                bin_parameters = obj.parameters;
                bin_parameters.class_vs_rest_id = class_id;

                if isfield(data_obj, 'train_y')
                    pos = (data_obj.train_y == class_id);
                    neg = ~pos;
                    bin_data_obj = data_obj;
                    bin_data_obj.train_y_orig = bin_data_obj.train_y;
                    bin_data_obj.train_y(pos) = 1.0;
                    bin_data_obj.train_y(neg) = -1.0;
                    bin_data_obj.train_y = double(bin_data_obj.train_y);

                    obj.model.class_map = unique(data_obj.train_y);
                end

                if isfield(data_obj, 'test_y')
                    pos = (data_obj.test_y == class_id);
                    neg = ~pos;
                    bin_data_obj.test_y(pos) = 1.0;
                    bin_data_obj.test_y(neg) = -1.0;
                    bin_data_obj.test_y = double(bin_data_obj.test_y);
                end

                bin_cl = eval(sprintf('%s(bin_parameters)', ...
                                      obj.parameters.binary_classifier_class));

                %tic;
                disp(sprintf('Training model %d...\n', i));
                bin_cl.train_model(hyperparameters, bin_data_obj);
                %toc
                bin_classifier_set{end+1} = bin_cl;
            end

            obj.model.binary_classifiers = bin_classifier_set;
            obj.model.hyperparameters = hyperparameters;
        end

        function [y_pred, all_margins] = predict(obj, test_x)
            all_margins = [];
            for m=obj.model.binary_classifiers
                m = m{1};
                %disp('MulticlassOneVsRest::predict:: m.predict'); tic; % DEBUG
                [junk, margins] = m.predict(test_x);
                %toc % DEBUG
                all_margins = [all_margins; margins];
            end

            %disp('MulticlassOneVsRest::predict:: max...'); tic; % DEBUG
            [junk,pred_model_idx]=max(all_margins, [], 1);
            %toc

            %disp('MulticlassOneVsRest::predict:: arrayfun...'); tic; % DEBUG
            y_pred = arrayfun(@(x) obj.model.binary_classifiers{x}.parameters.class_vs_rest_id, pred_model_idx)';
            %toc
        end

    end

    methods (Static)
        function test
            logger = LoggerBlock;
            parameters.kernel_type = 'rbf';
            parameters.svm_C_range = 2.^(-5:2:10);
            parameters.svm_gamma_range = 2.^(-5:2:10);
            parameters.cross_validation_folds = 3;
            parameters.max_kernel_memory = 50;
            parameters.binary_classifier_class = 'WLSSVM';
            cl = MulticlassOneVsRest(parameters, logger);

            load fisheriris;
            data_obj.train_x = {meas};
            data_obj.train_y = double(ordinal(species));
            data_obj.test_x = {meas};
            data_obj.test_y = data_obj.train_y;
            data_obj.grid_search_x = {meas};
            data_obj.grid_search_y = data_obj.train_y;

            hyper = cl.find_best_model_hyperparams(data_obj);
            cl.train_model(hyper, data_obj);
            rs = cl.evaluate_model(data_obj);
            rs
        end
    end
end
