classdef(Hidden,Sealed) TimeSeriesFactory < handle
    %TIMESERIESFACTORY Factory class for producing time series objects in
    %the econModeler app
    
    %Copyright 2017-2019 The MathWorks Inc.
    
    properties
        Session
        Listeners
    end
    
    methods(Access = ?internal.EconModeler.econModelerSession)
        function this = TimeSeriesFactory(session)
            this.Session = session;
            this.Listeners{end+1} = event.listener(this.Session,'DataImported',@(s,e)createImportedTimeSeries(this,s,e));
            this.Listeners{end+1} = event.listener(this.Session,'NewTimeSeriesRequest',@(s,e)determineTimeSeriesType(this,s,e));
        end
    end
    
    methods(Access = private)
        function createImportedTimeSeries(this,~,e)
            import internal.EconModeler.EconComponents.TimeSeries.ImportedTimeSeries
            import internal.EconModeler.EventData.TimeSeriesEventData
            
            newTimeSeries = ImportedTimeSeries.empty;
            importedData = e.ImportedData;
            
            [~,numSeries] = size(importedData{2});
            if istable(importedData{2}) || istimetable(importedData{2})
                % If imported data is a table/timetable, import each
                % variable as a time series
                
                allNames = importedData{2}.Properties.VariableNames;
                allVarNames = matlab.lang.makeUniqueStrings(matlab.lang.makeValidName(allNames));
                
                for m = 1:numSeries
                    %Original names must be kept for code gen. Modified
                    %name is displayed in the app
                    originalName = allNames{m};
                    varName = allVarNames{m};
                    value = importedData{2}{:,m};
                    if ~isa(value,'double') || ~isreal(value)
                        %Don't import if a variable is complex or not numeric
                        continue;
                    end
                    newTimeSeries = [newTimeSeries; ImportedTimeSeries(this.Session,varName,value,originalName)]; %#ok<AGROW>
                end
            else
                %If imported data is double, import each column as a time
                %series
                for m = 1:numSeries
                    name = importedData{1};
                    value = importedData{2}(:,m);
                    if ~isreal(value)
                        %If column is complex, don't import it
                        continue;
                    end
                    newTimeSeries = [newTimeSeries; ImportedTimeSeries(this.Session,[name num2str(m)],value,m)]; %#ok<AGROW>
                end
            end
            if isempty(newTimeSeries)
                %If no valid time series are found, error out
                uialert(this.Session.App,getString(message('econ:econModeler:BadImportData')),...
                    getString(message('econ:econModeler:AppTitle')),...
                    'Icon','error','Modal',true);
                return;
            end
            this.Session.ImportedSeries = newTimeSeries;
            notify(this.Session,'NewTimeSeriesCreated',TimeSeriesEventData(newTimeSeries));
        end
        
        function createDetrendTimeSeries(this,selection)
            import internal.EconModeler.EconComponents.TimeSeries.DetrendTimeSeries
            import internal.EconModeler.EventData.TimeSeriesEventData
            
            newTimeSeries = DetrendTimeSeries.empty();
            for n = 1:length(selection)
                newTimeSeries = [newTimeSeries;DetrendTimeSeries(this.Session,selection(n))]; %#ok<AGROW>
            end
            notify(this.Session,'NewTimeSeriesCreated',TimeSeriesEventData(newTimeSeries));
        end
        
        function createDiffTimeSeries(this,selections)
            import internal.EconModeler.EconComponents.TimeSeries.DiffTimeSeries
            import internal.EconModeler.EventData.TimeSeriesEventData
            
            newTimeSeries = DiffTimeSeries.empty();
            for n = 1:length(selections)
                newTimeSeries = [newTimeSeries;DiffTimeSeries(this.Session,selections(n))]; %#ok<AGROW>
            end
            notify(this.Session,'NewTimeSeriesCreated',TimeSeriesEventData(newTimeSeries));
        end
        
        function createLogTimeSeries(this,selections)
            import internal.EconModeler.EconComponents.TimeSeries.LogTimeSeries
            import internal.EconModeler.EventData.TimeSeriesEventData
            
            newTimeSeries = LogTimeSeries.empty();
            for n = 1:length(selections)
                newTimeSeries = [newTimeSeries;LogTimeSeries(this.Session,selections(n))]; %#ok<AGROW>      
            end
            notify(this.Session,'NewTimeSeriesCreated',TimeSeriesEventData(newTimeSeries));
        end
        
        
        function createSeasonalTimeSeries(this,selections,period)
            import internal.EconModeler.EconComponents.TimeSeries.SeasonalDiffTimeSeries
            import internal.EconModeler.EventData.TimeSeriesEventData
            
            newTimeSeries = SeasonalDiffTimeSeries.empty();
            for n = 1:length(selections)
                newTimeSeries = [newTimeSeries;SeasonalDiffTimeSeries(this.Session,selections(n),period)]; %#ok<AGROW>
            end
            notify(this.Session,'NewTimeSeriesCreated',TimeSeriesEventData(newTimeSeries)); 
        end
        
        function determineTimeSeriesType(this,~,e)
            import internal.EconModeler.Enumerations.TimeSeriesTypes
            
            selections = this.Session.getSelections();
            try
                if e.RequestedTimeSeries == TimeSeriesTypes.DETREND
                    createDetrendTimeSeries(this,selections);
                elseif e.RequestedTimeSeries == TimeSeriesTypes.LOG
                    createLogTimeSeries(this,selections);
                elseif e.RequestedTimeSeries == TimeSeriesTypes.DIFF
                    createDiffTimeSeries(this,selections)
                elseif e.RequestedTimeSeries == TimeSeriesTypes.SEASONAL
                    createSeasonalTimeSeries(this,selections,e.Period);
                end
            catch ME
                uialert(this.Session.App,ME.message,...
                    getString(message('econ:econModeler:AppTitle')),...
                    'Icon','error','Modal',true);
            end
            
        end
    end
end

