test check neighborhood

function test_suite = test_check_neighborhood
    % tests for cosmo_check_neighborhood
    %
    % #   For CoSMoMVPA's copyright information and license terms,   #
    % #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions = localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_check_neighborhood_exceptions
    aet = @(varargin)assertExceptionThrown(@() ...
                                           cosmo_check_neighborhood(varargin{:}), '');
    is_ok = @cosmo_check_neighborhood;

    % test four possible scenario's:
    % 1) two arguments (nh and ds), nh has .origin
    % 2) two arguments (nh and ds), nh does not have .origin
    % 3) one arguments nh, nh has .origin
    % 4) one arguments nh, nh does not have .origin

    ds = cosmo_synthetic_dataset();
    nh = [];
    aet(nh);
    nh = struct();
    aet(nh);
    aet(nh, ds);

    nh.fa = struct();
    nh.neighbors = 'foo';

    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);

    nh.neighbors = {[1 2]; 3};
    nh.fa.i = [2 1];
    nh.a.vol.dim = [1 2 1];
    nh.origin.a = ds.a;
    nh.origin.fa = ds.fa;
    is_ok(nh, ds);
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh);

    nh.neighbors = {[1; 2]; 3};
    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);

    nh.neighbors = {[1 2], 3};
    aet(nh, ds);
    aet_wo(nh, ds);
    aet(nh);

    nh.neighbors = {[1 2]; 3; 4};
    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);

    nh.neighbors = {[1 2]; 3};
    nh.foo = struct();
    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);
    nh = rmfield(nh, 'foo');

    nh.neighbors = {[.5 1]; 3};
    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);

    nh.neighbors = {[.5 1]; struct()};
    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);

    nh.neighbors = {[1 2]; 7};
    aet(nh, ds);
    aet_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh); % should pass

    nh.neighbors = {[1 2]; 3};
    is_ok(nh, ds); % should pass
    ok_wo(nh, ds);
    is_ok(nh); % should pass

    nh.origin.fa = ds.fa;
    nh.origin.a = ds.a;
    nh.fa = ds.fa;
    nh.a = ds.a;
    nh.neighbors = num2cell(1:6)';
    is_ok(nh, ds); % should pass
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh);

    nh.origin.fa = rmfield(nh.fa, 'i');
    aet(nh, ds);
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh); % should pass

    nh.origin.a.fdim.labels = nh.a.fdim.labels(2:end);
    aet(nh, ds);
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh); % should pass

    nh.origin.a.fdim.values = nh.a.fdim.values(2:end);
    aet(nh, ds);
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh); % should pass
    assertFalse(is_ok(nh, false, ds)); % should pass
    assertFalse(is_ok(nh, ds, false)); % should pass

    ds.a.fdim.values = ds.a.fdim.values(2:end);
    ds.a.fdim.labels = ds.a.fdim.labels(2:end);
    is_ok(nh, ds); % should pass
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh); % should pass

    ds2 = ds;
    ds2.a.vol.dim = [2 2 2];
    aet(nh, ds2);
    ok_wo(nh, ds2);
    ok_wo(nh);
    is_ok(nh);

    ds = cosmo_slice(ds, 3:6);
    nh = struct();
    nh.sa = ds.sa;
    nh.neighbors = {1; 2};
    nh.origin.a = ds.a;
    aet(nh, ds);
    aet_wo(nh, ds);
    aet_wo(nh);
    aet(nh);

    % should pass
    nh.neighbors = {1; 2; 4; 3};
    is_ok(nh, ds);
    ok_wo(nh, ds);
    ok_wo(nh);
    is_ok(nh);

    % cannot have both .sa and .fa
    nh2 = nh;
    nh2.fa = struct();
    aet(nh2, ds);

    % different .a.vol.dim
    nh2 = nh;
    nh2.origin.a.vol.dim = [4 0 0];
    aet(nh2, ds);
    ok_wo(nh2, ds);
    ok_wo(nh2);
    is_ok(nh2);

    % some exceptions for show_warning
    aet(ds, nh, 'show_warning');
    aet(ds, nh, 'foo');

function nh = remove_origin(nh)
    if isfield(nh, 'origin')
        nh = rmfield(nh, 'origin');
    end

function aet_wo(nh, varargin)
    % assert exception thrown without origin
    nh = remove_origin(nh);

    warning_state = cosmo_warning();
    cleaner = onCleanup(@()cosmo_warning(warning_state));
    cosmo_warning('off');

    assertExceptionThrown(@() ...
                          cosmo_check_neighborhood(nh, varargin{:}), '');

function ok_wo(nh, varargin)
    % is ok without origin
    nh = remove_origin(nh);

    warning_state = cosmo_warning();
    cleaner = onCleanup(@()cosmo_warning(warning_state));
    cosmo_warning('off');
    cosmo_check_neighborhood(nh, varargin{:});