% Collection of classes with simple interface for common functions to the
% patterns in the classes
%
classdef janis_classCollection < handle

    properties (SetAccess = protected)
        % cell of classes, which should be janis_class or a subclass
        classes = {};
        sourceDimension = {};
    end

    properties
        % human readable name or description
        description = 'untitled';
    end


    methods
        % adds a Class, throws exception if not a janis_class
        function this = addClass(this, newClass)
            if isa(newClass,'janis_class')
                this.classes{size(this.classes,1)+1,1} = newClass;
                if isempty(this.sourceDimension)
                    this.sourceDimension = newClass.sourceDimension;
                elseif ~isequal(newClass.sourceDimension,this.sourceDimension)
                    warning(['Image dimension of Member "' newMember.description '" does not match dimension of other members!']);
                end
            else
                throw(MException('janis:WrongClassClass','This is not a janis class type'));
            end
        end

        % returns class with index "idx"
        function jclass = get(this,idx)
            jclass = this.classes{idx};
        end

        % just inits the patterns
        function this = init(this)
            for count=1:size(this.classes,1)
                this.classes{count} = this.classes{count}.loadAll;
            end
        end

        % returns amount of Classes
        function s = size(this)
            s = size(this.classes,1);
        end

        % returns amount of all subjects over all classes.
        function p = patternCount(this)
            p = 0;
            for count=1:size(this.classes,1)
                p = p + this.classes{count}.size;
            end
        end

        % returns  true, if:
        %    * feature reduction methods should be set
        %    * images should be loaded
        %    * there should be at least ONE subject in every class
        %    * tehre should be at least two classes
        function valid = isValid(this)
            if this.size > 1
                valid = this.classes{1}.isValid;
                voxels = this.classes{1}.voxelsPerSub;
                for count=2:size(this.classes,1)
                    valid = valid && this.classes{count}.isValid && voxels == this.classes{count}.voxelsPerSub;
                end
            else
                valid = false;
            end
        end

        % returns  true, if:
        %    * all sizes of classes are equal
        % false, if
        %    * one class has a different amount of subjects
        function balanced = isBalanced(this)
            balanced = numel(unique(cellfun(@(x) x.size,this.classes))) == 1;
        end

        % Function, which switches the class labels of all members by random
        %     This is a nice helper method for a permutation test
        %     This returns a copy and does not modify the original the
        %     oringinal class collection!
        function thisCopy = shuffledCopy(this)
            thisCopy = janis_classCollection;
            thisCopy.description = this.description;
            allMembers = {};
            for c=1:size(this.classes,1)
                allMembers = {allMembers{:} this.classes{c}.members{:}};
            end
            indices = crossvalind('Kfold', size(allMembers,2), size(allMembers,2));
            counter = 0;
            for c=1:size(this.classes,1)
                permClass = janis_class([this.classes{c}.name ' (permuted)']);
                for m=1:size(this.classes{c}.members)
                    tmp = allMembers(1,indices((m + counter)));
                    permClass.addMember(tmp{:});
                end
                thisCopy = thisCopy.addClass(permClass);
                counter = counter + this.classes{c}.size;
            end

        end

        function thisCopy = copy(this)
            thisCopy = janis_classCollection;
            thisCopy.description = this.description;
            for c=1:size(this.classes,1)
                newClass = janis_class(this.classes{c}.name);
                for m=1:size(this.classes{c}.members)
                    tmp = this.classes{c}.getMember(m).copy;
                    newClass.addMember(tmp);
                end
                thisCopy = thisCopy.addClass(newClass);
            end
        end

        function this = setTestGroups(this,testIdx)
            for k=1:this.size
                this.classes{k}.testGroup = testIdx(k);
            end
        end

        % A human readable representation of whats goin' on.
        function show(this)
            fprintf('\nClass Collection:  ::: "%s" :::\n',this.description);
            if this.isValid; l = 'Yes'; else l = 'No';
            end;
            fprintf('   Valid structure:   %s\n', l);
            fprintf('   Classes:           %d\n', this.size);
            fprintf('   Subjects overall:  %d\n', this.patternCount);
            fprintf('\n');
            fprintf('   Index        Description     Subjects     Voxels per subject\n')
            fprintf('   ----------------------------------------------------------------\n');
            for cl=1:size(this.classes,1)
                fprintf('    %3d.:       %12s      %3d             %d\n', cl, this.classes{cl}.name, this.classes{cl}.size, this.classes{cl}.voxelsPerSub);
            end
            fprintf('   ----------------------------------------------------------------\n');
        end

        function clearData(this)
            for k=1:this.size
                for m=1:size(this.classes{k}.members)
                    this.classes{k}.getMember(m).freePattern;
                end
            end
        end

        function duplicates = checkDuplicates(this)
            vals = struct;
            for k=1:this.size
                markers = this.classes{k}.getUniqueMarkers;
                for mem=1:length(markers)
                    fields=fieldnames(markers{mem});
                    for f=1:length(fields)
                        singleMarker = getfield(markers{mem},fields{f});
                        if ~isfield(vals,fields{f})
                            vals = setfield(vals,fields{f},[]);
                        end
                        vals = setfield(vals,fields{f}, [getfield(vals,fields{f}); {singleMarker}]);
                    end
                end
            end
            fields=fieldnames(vals);
            duplicates = {};
            for k = 1:length(fields)
                s = getfield(vals,fields{k});
                if iscell(s{1})
                    b = horzcat(s{:});
                    c = b(:);
                    t = strvcat(c{:});
                else
                    t = strvcat(s{:});
                end
                [U I] = unique(t,'first','rows');
                dups = t;
                dups(I,:) = [];
                if ~isempty(dups)
                    duplicates =  [duplicates; {dups}];
                end
            end
        end

    end

end