function [split_info, fold_info] = make_splits(config, file_info)

split_info = [];
fold_info = [];

if isempty(file_info.subdir)
    error('No subdirectories to split');
end

% Subdirectories of the dataset, should be of the form object_00
subdir_names = {file_info.subdir(:).name};

% Load the categories
if ~isempty(config.category_path) && exist(config.category_path, 'file')
    [category_ids object_name] = textread(config.category_path, '%d %s\n');
else
    fprintf('No object categories defined');
    category_ids = ones(length(file_info.subdir), 1);
    object_name = subdir_names';
end

%% Get Splits
n_splits = config.n_splits;
split_format = '%d %s\n';
if exist(config.split_path, 'file')
    % Load the splits
    [split_ids object_name] = textread(config.split_path, split_format);
else
    split_ids = create_new_splits(category_ids, n_splits);

    % Write to file
    fileID = fopen(config.split_path, 'w');

    if fileID < 0
        error('Could not find split file');
    end

    cellfun(@(x,y) fprintf(fileID, split_format, x, y), ...
            num2cell(split_ids), object_name);
    fclose(fileID);
end

%% Get Folds
folds = [];
if isfield(config, 'fold_path') && isfield(config, 'n_folds')
    fold_format = ['split: %d    folds: ' ...
                   repmat('%d ', [1 config.n_folds]) ...
                   '   object: %s\n'];

    if exist(config.fold_path, 'file')
        % Load the folds
        fileID = fopen(config.fold_path, 'r');
        [data] = textscan(fileID, fold_format, 'CollectOutput', 1);
        fclose(fileID);

        split_ids = data{1}(:,1);
        object_name = data{2};
        folds = logical(data{1}(:,2:end));
    else
        folds = create_new_folds(category_ids, split_ids, ...
                                 n_splits, config.n_folds);

        % Write to file
        fileID = fopen(config.fold_path, 'w');
        cellfun(@(x,y,z) fprintf(fileID, fold_format, x, y, z), ...
                mat2cell(split_ids, ones(1, size(split_ids, 1)), 1), ...
                mat2cell(folds, ones(1, size(folds, 1)), size(folds, 2)), ...
                object_name);
        fclose(fileID);
    end
end

obj_perm = cellfun(@(x) sum(find(strcmp(x, object_name), 1)), subdir_names);
obj_perm(obj_perm == 0) = size(split_ids, 1) + 1;
split_ids = [split_ids; 0];
split_info = split_ids(obj_perm);

if ~isempty(folds)
    train_split = fold_training_split(folds, split_ids);
    folds = logical([folds; ones(1, size(folds,2))]);
    fold_info.hold_out = folds(obj_perm, :);
    fold_info.train_split = train_split;
end

end % make_splits function

%% Create_new_splits
% Within each of the defined catefories
% we assign each the objects in the category to a split
% by randomly permuting the objects in the category
% and dividing them among the possible splits.
function [split_ids] = create_new_splits(categories, n_splits)
n_categories = length(unique(categories));

split_ids = cell(n_categories, 1);
for i = 1:n_categories
    len = sum(categories == i);
    split_ids{i} = ceil((randperm(len)-1+eps)/(len-1+eps)*n_splits)';
end
split_ids = cat(1, split_ids{:});
end % create_new_splits

function left_out = create_new_folds(category_ids, split_ids, ...
                                     n_splits, n_folds)
% Generate n_potential possible folds,
% we keep n_folds that are dissimilar
% to ensure a variety of object combinations.
n_objs = size(category_ids, 1);
n_potential = 1000 * n_folds;
n_categories = length(unique(category_ids));

% The split used for training features
potential_split = zeros(1, n_potential);
% The logical matrix indicating the selected objects
potential_folds = logical(zeros(n_objs, n_potential));

% Determine which objects to leave out for each fold
for fold_i = 1:n_potential
    % Randomly choose a split, which determines the training objects.
    % Consequently, this determines the dictionary used to compute features.
    potential_split(fold_i) = randi(n_splits);

    % We can only leave out objects not used in learning the dictionary
    possible_out = (split_ids ~= potential_split(fold_i));

    % We leave out one object for each of the categories
    for cat_ind = 1:n_categories
        % Select one of the possible objects in this category
        possible_inds = find((category_ids == cat_ind) & possible_out);
        left_out_ind = possible_inds(randi(length(possible_inds)));

        % We keep track of left out objects in a logical array
        potential_folds(left_out_ind, fold_i) = 1;
    end
end

% Select n_folds
train_split = zeros(1, n_folds);
left_out = logical(zeros(n_objs, n_folds));
count_left_out = zeros(n_objs, 1);
for fold_i = 1:n_folds
    % Find a fold that is different from the ones we've already chosen.
    similarity = sum(bsxfun(@times, count_left_out, potential_folds));
    [~, min_ind] = min(similarity);

    % Keep this fold
    train_split(fold_i) = potential_split(min_ind);
    left_out(:, fold_i) = potential_folds(:, min_ind);

    % Keep a count of how often each object was left out.
    count_left_out = count_left_out + left_out(:, fold_i);

    % Set this potential fold to all 1s so it's not picked again.
    potential_folds(:, min_ind) = 1;
end

end % create_new_folds

function train_splits = fold_training_split(folds, split_ids)
test_splits = cellfun(@(x) unique(split_ids(x)), ...
                      mat2cell(folds, size(folds,1), ones(1,size(folds,2))), ...
                      'UniformOutput', false);
train_splits = ...
    cellfun(@(x) split_ids(find(sum(...
        bsxfun(@ne, x', repmat(split_ids, [1 numel(x)])),2) == numel(x),1)), ...
            test_splits);
end
