classdef MTTAudioFeatureDBgen < handle
   
    % ---
    % the database is stored as a global variable
    % ---
    
    properties (Hidden)
               
        % feature databases
        featuredb;
        
        commondb;
        
        feature_type;
        feature_params;
        
        % unfortunately we just can have one clip type at a time
        clip_type;
    end
    
    
    properties (Hidden)
        
        % ---
        % We use a "db_magnaaudiofeat_xxx" class pointer for all the loaded features,
        % which is supposed to work like a cache.
        %  my_dbpos links to the position of this feature in the db. 
        % ---

        % database hash
        featuredb_hash;
    end
    
    % ---
    % member functions
    % ---
    methods
        
        % constructor
        function db = MTTAudioFeatureDBgen(type, matfile)
            
            if nargin >= 1
                
                % ---
                % we test for correct type by
                % using an call to the empty arg constructor function
                % ---
                try
                    feval(type);
                    
                catch err
                    fprintf('%s\n', err.message);
                    error('The specified class does not provide an constructor')
                end
                db.feature_type = type;
                
                if nargin >=2 && ~isempty(dir(matfile))

                    
                    % import database if filename given
                    db.import(matfile);
                end
                
            end

            % try to load a standard db from mat file?
        end

        % ---
        % database retrieval.
        % this should return a handle to a feature class pointing in this
        % global feature database.
        %
        % parameters can be passed via the varargin parameter
        % ---
        function features = get_features(db, clip, varargin)
            
            % ---
            % TODO: error checking and processing
            % ---
            
            % ---
            % prepare for multiple clips
            % ---
            if numel(clip) > 1
                
                % iterate for all the found clips
                features = feval(db.feature_type);
                for i = 1:numel(clip)
                    features(i) = get_features(db, clip(i), varargin{:});
                end
            else
                % ---
                % single feature extraction
                % ---
            
                pos = db.get_clip_pos(clip);
                if ~pos

                    % assign new database position
                    pos = db.get_next_pos();
                    
                    % call feature constructor
                    features = feval(db.feature_type, db, varargin{:});
                    
                    % extract / load feature data
                    
                    cprint(2 ,'Extracting %s for clip %d \n', db.feature_type, clip.id());
                    
                    if isempty( db.featuredb)

                        db.featuredb = features.extract(clip);
                    else

                        db.featuredb(pos) = features.extract(clip);
                    end
                    % ---
                    % NOTE:
                    % IF WE ARE SURE THAT EVERYTHING HAS WORKED OUT FINE:
                    % assign new database position in cache
                    % ---
                    db.set_pos(pos, clip);
                    
                    % ---
                    % NOTE: feature objects are directly linked to DB
                    % positions
                    % ---
                    features.assign(pos);
                else
                    
                    % just return the cached link
                    features = feval(db.feature_type, db, pos);
                end
                
                % ---
                % finalise features if possible (commondb not empty)
                % ---
                if ~isempty(db.commondb) && isempty(features.data.final) && ...
                        ismethod(features,'finalise')
                    
                    % call finalise
                    features.finalise();
                end
                
                % set clip type of the db
                db.clip_type = class(clip(1));
                
                % get last params
                db.feature_params = features(1).my_params;
            end
        end
        
        function set_common(db, common)
        % sets the common database to input
            
            if isempty(db.commondb)
                
                cprint(2, 'Setting common feature values\n');
            else
                
                cprint(1, 'Common feature values changed');
            end
            
             db.commondb = common;
        end
        
        function export(db, matfile, clips)
        % saves featuredb to matlab data file
            
            global globalvars;
            % save revision for later version and compability control
            info.creatorrev = globalvars.camir.revision;
        
            cprint(2, 'Exporting %s database to %s ...\n', db.feature_type, matfile);
            if nargin == 3
                % ---
                % TODO: create new hash
                % ---
                for i = 1:numel(clips)
                    pos(i) = db.get_clip_pos(clips(i));
                    
                    if pos(i) == 0
                        error('Corrupted database');
                    end
                end
                
                % get specific data set
                featuredb = db.featuredb(pos);
                
                featuredb_hash = db.featuredb_hash(pos);
                
            else
                featuredb = db.featuredb;

                featuredb_hash = db.featuredb_hash;
            end
            
            commondb = db.commondb;

            feature_type = db.feature_type;
            
            % ---
            % save clip type as well
            % NOTE: this only works if all clips have the same type (e.g.
            % CASIMIRClip or MTTclip
            % ---
            clip_type = class(clips(1));
            
            save(matfile, 'featuredb', 'commondb', 'feature_type', 'featuredb_hash', 'clip_type');
        end
        
        function [features, clips] = import(db, matfile, type)
        % gets featuredb from matlab data file
            
            cprint(2, 'importing features from %s', matfile)
            load(matfile,'-MAT');
            
            if ~strcmp(feature_type, db.feature_type) 
                
                error('feature type of db to import does not match');
            end
            
            % ---
            % TODO / FIXME: check parameter hash before importing
            % ---
            
%             if db.size() > 0
% 
%                 % get a feature param from the db;
%                 last_pos = db.get_last_pos;
%                 dummyparams = db.featuredb(last_pos).info.params;
%                 
%                 % ---
%                 % construct a dummy feature and compare parameters to
%                 % the params in the database
%                 % ---
%                 dummyclip = MTTClip(db.featuredb_hash(last_pos));
%                 dummyfeat = db.get_features(dummyclip, dummyparams);
%                 
%                 if ~dummybsm.eq_params(fparams(i))
%                     
%                     db_magnaaudiofeat_basicsm.reset;
%                 end
%                 
%             end

            if exist('clip_type','var')
                clips = feval(clip_type,featuredb_hash);
                db.clip_type = clip_type;
            else
                clips = MTTClip(featuredb_hash);
                db.clip_type = 'MTTClip';
            end
            
            % ---
            % import features individually into db
            % ---
           
            for i = 1:numel(clips)
                
                % test if database already contains clip
                if ~db.get_clip_pos(clips(i));
                    
                    % get position for this database
                    pos = db.get_next_pos();
                    
                    % copy values
                    if ~isempty(db.featuredb)
                        
                        db.featuredb(pos) = featuredb(i);
                    elseif pos == 1;
                        
                        db.featuredb = featuredb(i);
                    else
                        
                        error ('Corrupted database');
                    end
                    % update hash
                    db.set_pos(pos, clips(i));
                end
            end
            
%             Set common features
            db.set_common(commondb);
            
            if nargout > 0
                % retrieve features;
                features = get_features(db, clips);
            end
            
        end
        
        function [matfile] = load(db, featuretype, fparams, clips)
            % this is the new implementation of MTTAudiofeature_load
            
            % get filename
            matfile = MTTAudioFeatureDBgen.get_db_filename(featuretype, fparams,clips);
            
            % does it exist?
            if exist(matfile,'file') == 2
                import(db, matfile);
            else
                matfile= [];
            end
        end
        
        function [matfile] = save(db)
            % this is the new implementation of MTTAudiofeature_load
            
            % get clips 
            clips = feval(db.clip_type,db.featuredb_hash);

             % get filename
            matfile = MTTAudioFeatureDBgen.get_db_filename(db.feature_type, db.feature_params ,clips);
            
            % does it exist?
            if exist(matfile,'file') == 2
                warning 'overwriting feature file for this config'
            end
            
            % save params in xml
            xml_save(sprintf('%s.xml', substr(matfile,  0, -4)),db.feature_params);
            
            %export features
            export(db, matfile, clips);
        end

        function remove_features(db, clip)
        % weakly deletes clip features from db
        
            clear_pos(clip);
        end
        
        function delete(db)
            % ---
            % probably not anything to do here, as we want to 
            % keep the db!
            % see static method destroy
            % ---
        end

        function reset(db)
        % ---
        % deletes all the cached data and destroys the 
        % global feature database
        % --- 
            db.commondb = [];
            db.featuredb = [];
            db.featuredb_hash = [];
        end 
        
        function out = size(db)
        % returns the number of features saved in this db
        
            out = sum(db.featuredb_hash > 0);
        end

        function memory(db)
        % returns size of whole db in bytes
        
        % ---
        % TODO: Make this work 
        % ---
            
            fprintf(' \n This db contains feature sets for %d clips\n ',numel(db.featuredb_hash))
            % get local copies of data
            featuredb = db.featuredb;
            featuredb_hash = db.featuredb_hash;
            commondb = db.commondb;
            
            whos('featuredb', 'featuredb_hash', 'commondb')
        end
    end
   
    % ---
    % private functions
    % ---
    methods (Hidden)

        % ---
        % Hash functions 
        % ---
        function out = get_clip_pos(db, clip)
        % should become database hashing function

            out = find(db.featuredb_hash == clip.id);
            if isempty(out)
                out = 0;
            end
        end
        
        function out = get_next_pos(db)
        % return index for the new clip features
        
            out = numel(db.featuredb_hash) + 1;
        end
        
        function last_pos = get_last_pos(db)
        % return index the last valid db entry
        
            last_pos = find(db.featuredb_hash > 0, 1, 'last');
        end
        
        
        function set_pos(db, pos, clip)
        % set index for the new clip features
        
            db.featuredb_hash(pos) = clip.id;
        end
        
        function clear_pos(db, clip)
        % remove index of the clip features
        
            db.featuredb_hash(get_clip_pos(db, clip)) = 0;
        end
    end
    
    methods (Static)
        
        % ---
        % this resets all feature dbs except the one exluded in the 
        % 'exclude', {''} cell 
        % ---
        function reset_feature_dbs(varargin)
            
            [exclude] = process_options(varargin,'exclude',{});
            % ---
            % resets all feature dbs except raw features
            % ---
            vars = whos ('*','global','-regexp', '^db_*');
            
            % ---
            % check if each is class of DBgen.
            % if not in exclude variable, reset
            % ---
            for i = 1:numel(vars)
                
                % import global variable
                eval(sprintf('global %s',vars(i).name));
                
                if strcmp(eval(sprintf('class(%s)',vars(i).name)), 'MTTAudioFeatureDBgen') ...
                        && isempty(strcellfind(exclude, vars(i).name))
                    
                    eval(sprintf('%s.reset',vars(i).name));
                end 
            end
        end

        function feature_type = import_type(matfile)
        % function feature_type = import_type(matfile)
        % 
        % returns the type of the saved feature db.
            
            load(matfile, 'feature_type', '-MAT');
        end
        
        function db_nameo = db_name(type)
        % returns the standard global var name for a db of given type    
            
            switch type
                case 'MTTAudioFeatureRAW'
                    db_nameo = 'db_magnaaudiofeat';
                    
                case 'MTTAudioFeatureBasicSm'
                    db_nameo = 'db_magnaaudiofeat_basicsm';
                    
                case 'MTTTagFeatureGenreBasic'
                    db_nameo = 'db_magnatagfeat_genrebasic';
                    
                case 'MTTMixedFeatureGenreBasicSm'
                    db_nameo = 'db_magnamixedfeat_genrebasicsm';
                    
                otherwise
                    db_nameo = sprintf('db_%s', type);
            end 
        end
        
        function matfile = get_db_filename(featuretype, fparams,clips)             
            % get the paramhash
            paramhash = MTTAudioFeatureDBgen.param_hash(featuretype, fparams);

            % add the clip hash bit
            cliphash = MTTAudioFeatureDBgen.clip_hash(clips);
            
            % determine the filename
            matfile = sprintf('f%s.%s.mat', paramhash,cliphash);
        end
            
        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 ch = clip_hash(clips)
            
            % returns the hash of a number of clips
            ch = hash([class(clips(1)) mat2str(sort(clips.id))],'MD5');
        end
    end
end 




