classdef ModelDataBrowser < ... 
        matlab.ui.internal.databrowser.TableDataBrowser & ...
        matlab.ui.internal.databrowser.PreviewPanelInterface
    
    %MODELDATABROWSER Implementation of Econometric Modeler's model
    %data browser
    
    %Copyright 2020 The MathWorks, Inc.
    
    properties(Access = private)
        Session
        Listeners
        OldLabels = {};
        OpenMenuItem
        ModifyMenuItem
        DeleteMenuItem
        ExportMenuItem
    end
    
    events
        DataBrowserSelectionChanged
    end
    
    methods
        function this = ModelDataBrowser(session)
            % super
            this = this@matlab.ui.internal.databrowser.TableDataBrowser('modelDataBrowser', getString(message('econ:econModeler:ModelWorkspaceName')));
            this.Session = session;
            % customize widget and layout
            buildUI(this);
            % customize listeners and callbacks
            connectUI(this);
            
            this.Listeners{end+1} = event.listener(this.Session,'ModelRegistered',@(s,e)assignNewModel(this));
            this.Listeners{end+1} = event.listener(this.Session,'ModelClearRegistered',@(s,e)updateUI(this));
            this.Listeners{end+1} = event.listener(this.Session,'TimeSeriesRegistered',@(s,e)clearSelections(this));
            this.Listeners{end+1} = event.listener(this.Session,'SessionClear',@(s,e)clearOldLabels(this));
        end
        
        function updateUI(this)
            this.Table.Data = this.getName()';
        end
        
        function label = getName(this,idx)
            % Get the Time Series names for a given index, or all Time
            % Series names
            if nargin == 2
               ts = this.getData(idx);
            else
                ts = this.getData();
            end
            
            if isempty(ts)
                label = {};
            else
                label = {ts.Name};
            end
        end
        
        function data = getData(this,idx)
            % Get the model object for a given index, or all model
            % objects
            if nargin == 1
                data = this.Session.getAllModels();
            elseif ~isempty(idx)
                data = this.Session.getModel(idx);
            else
                data = [];
            end
        end
        
        function clearSelections(this)
            this.Table.Selection = [];
        end
    end
    
    methods(Access = protected)
        function buildUI(this)
            
            this.SingleRowSelection = false;
            
            % add dynamic contextmenu
            this.Table.ContextMenu = uicontextmenu('parent',this.Figure);
            this.Table.ContextMenu.Tag = 'modelContextMenu';
            this.OpenMenuItem = uimenu(this.Table.ContextMenu,'label',getString(message('econ:econModeler:OpenMenuItem')),'callback',@(src,data) cbOpen(this));
            this.ModifyMenuItem = uimenu(this.Table.ContextMenu,'label',getString(message('econ:econModeler:ModifyMenuItem')),'callback',@(src,data) cbModify(this));
            this.DeleteMenuItem = uimenu(this.Table.ContextMenu,'label',getString(message('econ:econModeler:DeleteMenuItem')),'callback',@(src,data) cbDelete(this));
            this.ExportMenuItem = uimenu(this.Table.ContextMenu,'label',getString(message('econ:econModeler:ExportMenuItem')),'callback',@(src,data) cbExport(this));
        end
        
        function connectUI(this)
            lis = addlistener(this.Table.ContextMenu,'ContextMenuOpening',@(src, data) updateContextMenu(this, src, data));
            registerUIListeners(this,lis,'ContextMenuOpeningListener');
        end
        
        function cbOpen(this)
            %Plot the time series when a user double-clicks on it
            import internal.EconModeler.EventData.NewPlotEventData
            import internal.EconModeler.Enumerations.PlotTypes
            notify(this.Session,'NewPlotRequest',NewPlotEventData(PlotTypes.FIT));
        end
        
        function cbDelete(this)
            %Send out deletion ontification, and update view
            import internal.EconModeler.EventData.VarListEventData
            selection = this.Table.Selection;
            models = this.Session.getModel(selection);
            names = {models.Name};
            notify(this.Session,'ModelClearDeleteRequest',VarListEventData(names));
            this.updateUI();
            this.OldLabels = this.getName();
            this.Table.Selection = [];
        end
        
        function cbModify(this)
            %Open dialog to adjust model parameters
            selection = this.Table.Selection;
            model = this.Session.getModel(selection);
            model.estimateAgain();
        end
        
        function cbExport(this)
            %Export selected model(s)
            selection = this.Table.Selection;
            models = this.Session.getModel(selection);
            modelNames = {models.Name};
            
            existingIdx = logical((cellfun(@(x)evalin('base',['exist(''',x,''',''var'')']),modelNames)));
            if any(existingIdx)
                existingNames = ['(',char(join(modelNames(existingIdx),',')),')'];
                button = questdlg(getString(message('econ:econModeler:RepeatVariable',existingNames)),...
                    getString(message('econ:econModeler:AppTitle')),...
                    getString(message('econ:econModeler:Yes')),...
                    getString(message('econ:econModeler:No')),...
                    getString(message('econ:econModeler:Yes')));
                switch button
                    %Cancel import if user selects No or Cancel, or lcoses
                    %dialog
                    case {getString(message('econ:econModeler:No')),''}
                        return;
                end
            end
            for n = 1:length(models)
                models(n).export();
            end
        end
        
        function DoubleClickCallback(this,~)
            cbOpen(this)
        end
        
        function RenameCallback(this,~,oldName,newName) 
            %Send out time series rename notification
            import internal.EconModeler.EventData.RenameEventData
            if ~strcmp(oldName,newName)
                %Skip this logic if the new name and old name are the same (this can happen if the user
                % gives non variable names multiple times) 
                newName = this.Session.makeUniqueWSName(newName);
                notify(this.Session,'ModelRenameRequest',RenameEventData(oldName,newName));
                this.updateUI();
            end
        end
        
        function SelectionCallback(this,rows)
            import internal.EconModeler.EventData.ModelEventData
            
            % refresh preview panel
            eventdata = matlab.ui.internal.databrowser.PreviewEventData(rows);
            this.notify('PreviewRequested',eventdata);
            
            data = this.getData(rows);
            this.notify('DataBrowserSelectionChanged',ModelEventData(data));
        end
        
        function assignNewModel(this)
            %Update view and change selection when new time series are
            %created
            
            import internal.EconModeler.EventData.ModelEventData
            import matlab.ui.internal.databrowser.PreviewEventData
            
            this.updateUI();
            [~,idx] = setdiff(string(this.getName()),string(this.OldLabels));
            if ~isempty(idx)
                this.Table.Selection = idx';
                data = this.getData(idx);
                this.notify('DataBrowserSelectionChanged',ModelEventData(data));
                this.notify('PreviewRequested',PreviewEventData(idx));
            end
            this.OldLabels = this.getName();
        end
         
        function updateContextMenu(this, ~, data)
            interactionInformation = data.InteractionInformation;
            if data.ContextObject == this.Table ...
                && ~(interactionInformation.RowHeader || interactionInformation.ColumnHeader)
                row = interactionInformation.DisplayRow;
                col = interactionInformation.DisplayColumn;
                % React when a cell or the white space is clicked.
                if isempty([row col])
                    % remove current row selections
                    this.Table.Selection = [];
                    % only show "Add" menu
                    this.OpenMenuItem.Visible = false;
                    this.ModifyMenuItem.Visible = false;
                    this.DeleteMenuItem.Visible = false;
                    this.ExportMenuItem.Visible = false;
                else
                    % select the right-clicked row if it is not selected
                    if ~any(this.Table.Selection==row)
                        this.Table.Selection = row;
                    end
                    if length(this.Table.Selection) > 1
                        this.OpenMenuItem.Visible = false;
                        this.ModifyMenuItem.Visible = false;
                        this.DeleteMenuItem.Visible = true;
                        this.ExportMenuItem.Visible = true;
                    else
                        this.OpenMenuItem.Visible = true;
                        this.ModifyMenuItem.Visible = true;
                        this.DeleteMenuItem.Visible = true;
                        this.ExportMenuItem.Visible = true;
                    end
                end
            else
                % remove current row selections
                this.Table.Selection = [];
                % only show "Add" menu
                this.OpenMenuItem.Visible = false;
                this.ModifyMenuItem.Visible = false;
                this.DeleteMenuItem.Visible = false;
                this.ExportMenuItem.Visible = false;
            end
        end
        
        function clearOldLabels(this)
            this.OldLabels = {};
        end
    end
end

