% ---
% Mother of allMTT Audio Features
% ---

classdef MTTAudioFeature < handle
    
    % common properties
    properties (Dependent)
        
        data;
    end
    
    properties (Hidden)
        
        % ---
        % TODO: get clip features by id and not by pos
        % ---
        
        % position in db
        db_pos;
    end
    
    % do not save whole db into mat file
    properties (Hidden, Transient)
        
        % database
        my_db;
    end
    
    
    % common methods
    methods 
        
        % ---
        % constructor: pointer to feature in database
        % ---
        function feature = MTTAudioFeature(db, varargin)
        % feature = MTTAudioFeatureBasicSm(db, varargin)
     
            if nargin >= 1
                % save database handle;
                feature.my_db = db;
                
                if nargin >= 2 
                    if isnumeric(varargin{1})
                        
                        feature.db_pos = varargin{1};

                        % ---
                        % read parameters from data, which should not be
                        % overwritten by the manual imputs ?
                        % ---
                        feature.set_params(feature.data.info.params);
                        
                        if numel(varargin) > 1
                            warning('Specified parameters cant be applied with preexisting data');
                        end

                    else
                        
                        % set parameters for feature extraction
                        feature.set_params(varargin{:});
                    end
                end
            
            else
                % ---
                % TODO: empty constructor
                % ---
            end
        end
        
        % ---
        % get and set methods, as the lazy matlab referencing
        % is of no use when the feature has to be adjusted 
        % during the progess of finalising
        % ---
        function data = get.data(feature)
            
            data = feature.my_db.featuredb(feature.db_pos);
        end
        
        function set.data(feature, data)
            
             feature.my_db.featuredb(feature.db_pos) = data;
        end
        
        function id = owner_id(feature)
        % returns associated clip(s)
        
            for i = 1:numel(feature)
                id(i) = feature(i).data.info.owner_id;
            end
        end
        
        function out = dim(feature)
        % returns dimension of single feature vector
        
            out = feature(1).data.final.dim;
        end
        
        function label = labels(features)
           label = [];
            if isfield(features(1).data.final, 'vector_info')
                
                % ---
                % TODO: these labels should be stored at a central point,
                % as this is a big waste of memory
                % ---
                label = features(1).data.final.vector_info.labels;
            end 
        end
        
        function out = vector(features)
        % returns the feature vector(s) 
        % if multiple features are input, the vectors are 
        % concatenated to a single matrix of size 
        %       (feature.dim x numel(features)
        
        % finalise features if not done yet
        if ~isfield(features(1).data, 'final') || features(1).data.final.dim == 0
            
            features.finalise();
        end
        
        %shortcut for empty features
        if features(1).data.final.dim < 1
            out =[];
            return
        end

        out = zeros(features(1).data.final.dim, numel(features));
        for i = 1:numel(features)

            % finalise single feature vectors
            if ~isfield(features(i).data, 'final') || features(i).data.final.dim == 0

                features(i).finalise();
            end

            out(:,i) = features(i).data.final.vector;
        end
        end
        
        function out = common(feature)
        % returns common feature values and params
        
           out = feature(1).my_db.commondb;
        end
        
        % visualises all the finalised feature vectors at once
        function a1 = visualise_vectors(features)
            
            % Collect source description data
            ids = zeros( numel( features, 1));
            
            for i = 1: numel(features)
                
                ids(i) = features(i).owner_id();
            end
%             clips = MTTClip(ids);
            
            % get collective feature data
            vec = features.vector();
            
            % feature description data
            labels = features(1).labels;
            
            % plot data
            h = figure;
            imagesc(vec);
            a1 = gca();
            axis xy;
            
            set(a1,'XTick',1:numel(features), 'XTickLabel', ids);
            
            if ~isempty(labels)
                set(a1,'YTick', [1:features(1).data.final.dim], 'YTickLabel', labels);
            end
            
            xlabel('Clip ID');
        end
        
        
        % ---
        % compares the params of the feature with the 
        % provided param struct or feature
        % ---
        function out = eq_params(this, in)
        % function out = eq_params(this, in)
        % 
        % resolves if a feature with params(in) would be
        % the same as the  given feature
            
            type = this.my_db.featuredb_type;
            
            % ---
            % construct dummy feature to get the params parsing right
            % ---
            if isstruct(in)
                
                % call constructor for this feature 
                % type without valid db
                in = feval(type, 0, in);
            end
            
            % return whether the strings are equal or not 
            out = strcmp(this.param_hash(), in.param_hash());
        end
        
        function unused = set_params(this, varargin)
        % copies the parameters within the struct to the featrue instance,
        % or
        % uses the process_options function to set the parameters.
        % this is used for initialisation / constructor of features

        unused = {};
        if numel(this) > 1
            
            for i = 1:numel(this)
                set_params(this(i), varargin{:});
            end
        else
            
            if isstruct(varargin{1})

                % ok, just copy the information
                % this.my_params = varargin{1};

                % ---
                % NOTE: this is a compability script
                % set fields succesively to keep new / nonset field names
                % we do not add parameters!!
                % ---
                fields = fieldnames(varargin{1});
                for i = 1:numel(fields)
                    
                    % test whether the parameter is supported
                    if isfield(this.my_params, fields{i})
                        
                        this.my_params.(fields{i}) = varargin{1}.(fields{i});
                    end
                end
            else

                % get all parameter lines
                fields = fieldnames(this.my_params);

                outputstring = '';
                inputstring = '';

                % we collect the string for all the fields
                for i = 1:numel(fields)

                    % generate left- hand side of param collection
                    outputstring = sprintf('%s this.my_params.%s',...
                        outputstring, fields{i});

                    % right-hand-side
                    inputstring = sprintf('%s ''%s'', this.my_params.%s',...
                        inputstring, fields{i}, fields{i});

                    if i < numel(fields)

                        % put comma behind last argument
                        inputstring = sprintf('%s,',inputstring);
                        outputstring = sprintf('%s,',outputstring);
                    end

                end

                % evaluate set
                eval(sprintf('[%s, unused] = process_options(varargin, %s);', ...
                    outputstring, inputstring));
            end
        end
        end %fun
        
        % ---
        % saveto function 
        % ---
        function saveto(features, matfile)
        % saves (multiple features to a .mat file)
        
            % get clip ids
            c = zeros(numel(features), 1);
            for i = 1:numel(features)
                c(i) = features(i).owner_id();
            end
        
            % build clips
            clips = MTTClip(c);
            
            % let db export the corresponding clips
            features(1).my_db.export(matfile, clips);
        end
        
        % ---
        % destructor: do we really want to remove this 
        % from the database? No, but 
        % TODO: create marker for unused objects in db, and a cleanup
        %  function
        % ---
        function delete(feature)
            
        end
    end
    
    methods (Hidden = true)
        
        function assign(feature, db_pos)
        % sets the feature data link
        
            feature.db_pos = db_pos;
        end
    end
    
    methods (Static)
        
        function ph = param_hash(type, varargin)
        % loads the params for a feature type and adds the 
        % given parameter values to it.
        
            % this function can be static or dynamic
            if nargin > 1
                % static case
                dummy = feval(type,[], varargin{:});
            else
                % dynamic case
                dummy = type;
            end
         ph = hash(xml_format(dummy.my_params),'MD5');
        end
        
        function params = inherited_params(type, varargin)
        % loads the params for a feature type and adds the 
        % given parameter values to it.
            
            dummy = feval(type);
            
            params = dummy.my_params;
            
            % basic check if the input is correct
            if mod(numel(varargin), 2) ~= 0
                error('number of params does not match number of values');
            end
            
            % add the fields to the struct
            for i = 1:2:(numel(varargin)-1);
                
                params.(varargin{i}) = varargin{i+1};  
            end
        end
    end
end
    
    
    
    
    
    
    