function conv_nbrhood=cosmo_convert_neighborhood(nbrhood, output_type)
% Converts between cell, matrix and struct representations of neighborhoods
%
% conv_nbrhood=cosmo_convert_neighborhood(nbrhood[, output_type])
%
% Inputs:
%     nbrhood               Either a cell, struct, or matrix with
%                           neighborhood information:
%                           - cell:    Nx1 with nbrhood{k} the indices of
%                                      the k-th neighborhood
%                           - struct:  with field .neighbors, which must
%                                      be a cell
%                           - matrix:  MxN with nbrhood(:,k) the indices of
%                                      the k-th neighborhood (non-positive
%                                      values indicating no index), with M
%                                      the maximum number of features in a
%                                      single neighborhood
%     output_type           Optional, one of 'cell', 'matrix', or
%                           'struct'.
%                           If empty or omitted, then output_type is set to
%                           'matrix', unless nbrhood is a matrix, in
%                           which case it is set to 'cell'.
% Output:
%     conv_nbrhood          Neighborhood information converted to cell,
%                           struct, or matrix (see above)
%
% Example:
%     ds=cosmo_synthetic_dataset();
%     nbrhood=cosmo_spherical_neighborhood(ds,'radius',1,'progress',false);
%     % show the neighbor indices
%     cosmo_disp(nbrhood.neighbors)
%     %|| { [ 1         4         2 ]
%     %||   [ 2         1         5         3 ]
%     %||   [ 3         2         6 ]
%     %||   [ 4         1         5 ]
%     %||   [ 5         4         2         6 ]
%     %||   [ 6         5         3 ]           }
%     %
%     % convert to matrix representation
%     mx=cosmo_convert_neighborhood(nbrhood,'matrix');
%     cosmo_disp(mx)
%     %|| [ 1         2         3         4         5         6
%     %||   4         1         2         1         4         5
%     %||   2         5         6         5         2         3
%     %||   0         3         0         0         6         0 ]
%     %
%     % convert to cell representation
%     neighbors=cosmo_convert_neighborhood(nbrhood,'cell');
%     cosmo_disp(neighbors)
%     %|| { [ 1         4         2 ]
%     %||   [ 2         1         5         3 ]
%     %||   [ 3         2         6 ]
%     %||   [ 4         1         5 ]
%     %||   [ 5         4         2         6 ]
%     %||   [ 6         5         3 ]           }
%
% Notes:
%    - the rationale of this function is that cell or struct
%      representations are more intuitive and possible more space
%      efficient, but also slower to access than matrix representations.
%      This function provides conversion between different representations.
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #

    if nargin<2, output_type=''; end

    if isnumeric(nbrhood)
        converter=@convert_matrix;
    elseif iscell(nbrhood)
        converter=@convert_cell;
    elseif isstruct(nbrhood)
        converter=@convert_struct;
    else
        error('Expected matrix, cell, or struct input');
    end

    conv_nbrhood=converter(nbrhood, output_type);

function y=convert_cell(x,output_type)
    check_cell(x);
    switch output_type
        case {'','matrix'}
            y=convert_cell2matrix(x);
        case 'struct'
            y=convert_cell2struct(x);
        case {'cell'}
            y=x;
        otherwise
            throw_illegal_output_type_error();
    end


function y=convert_matrix(x,output_type)
    check_matrix(x);
    switch output_type
        case {'','cell'}
            y=convert_matrix2cell(x);
        case 'struct'
            y=convert_matrix2struct(x);
        case 'matrix'
            y=x;
        otherwise
            throw_illegal_output_type_error();
    end

function y=convert_struct(x,output_type)
    check_struct(x);
    switch output_type
        case 'cell'
            y=convert_struct2cell(x);
        case {'','matrix'}
            y=convert_struct2matrix(x);
        case 'struct'
            y=x;
        otherwise
            throw_illegal_output_type_error();
    end

function check_matrix(neighbors)
    if numel(size(neighbors))~=2
        error('input is not a matrix');
    end

    assert(isnumeric(neighbors));

    if ~isequal(round(neighbors),neighbors)
        error('input has non-integer values');
    end

function check_struct(nbrhood)
    cosmo_check_neighborhood(nbrhood,'show_warning',false);

function check_cell(neighbors)
    check_struct(convert_cell2struct(neighbors));

function nbrhood=convert_cell2struct(neighbors)
    nbrhood=struct();
    nbrhood.neighbors=neighbors;
    nbrhood.fa=struct();
    nbrhood.a=struct();

function neighbors=convert_struct2cell(nbrhood)
    neighbors=nbrhood.neighbors;

function neighbor_matrix=convert_cell2matrix(neighbors)
    nfeatures=numel(neighbors);
    nnbrs=cellfun(@numel, neighbors);
    maxn=max(nnbrs);

    neighbor_matrix=zeros(maxn, nfeatures);
    for k=1:nfeatures
        nbrs=neighbors{k};
        neighbor_matrix(1:numel(nbrs),k)=nbrs;
    end

function neighbors=convert_matrix2cell(neighbor_matrix)
    nfeatures=size(neighbor_matrix,2);
    neighbors=cell(nfeatures,1);

    for k=1:nfeatures
        nbrs=neighbor_matrix(:,k);
        neighbors{k}=nbrs(nbrs>0)';
    end

function nbrhood=convert_matrix2struct(neighbor_matrix)
    nbrhood=convert_cell2struct(convert_matrix2cell(neighbor_matrix));

function neighbor_matrix=convert_struct2matrix(nbrhood)
    neighbor_matrix=convert_cell2matrix(convert_struct2cell(nbrhood));



function throw_illegal_output_type_error()
    valid_output_types={'','matrix','struct','cell'};
    error('illegal output type, valid is one of: ''%s''.',...
                cosmo_strjoin(valid_output_types,''', '''));

