classdef UIVECImpactParameterDialog < handle
    %UIVECCOMPONENTPARAMETERDIALOG Dialog for setting VEC Model Parameters
    %using the Impact Form
    
    %Copyright 2021 The MathWorks Inc.
    
    properties(Access = protected)
        Session internal.EconModeler.econModelerSession
        ModelType = internal.EconModeler.Enumerations.ModelTypes.VECImpact;
        Figure matlab.ui.Figure
        TabGroup matlab.ui.container.TabGroup
        OrderTab matlab.ui.container.Tab
        VectorTab matlab.ui.container.Tab
        FormOrderLabel matlab.ui.control.Label
        FormOrderDropDown matlab.ui.control.DropDown
        LagOrderLabel  matlab.ui.control.Label
        LagOrderSpinner matlab.ui.control.Spinner
        ImpactOrderLabel matlab.ui.control.Label
        ImpactOrderTable matlab.ui.control.Table
        ShortRunOrderLabel matlab.ui.control.Label
        ShortRunOrderDropDown matlab.ui.control.DropDown
        ShortRunOrderTable matlab.ui.control.Table
        FormVectorLabel matlab.ui.control.Label
        FormVectorDropDown matlab.ui.control.DropDown
        LagVectorLabel  matlab.ui.control.Label
        LagVectorEdit matlab.ui.control.EditField
        ImpactVectorLabel matlab.ui.control.Label
        ImpactVectorTable matlab.ui.control.Table
        ShortRunVectorLabel matlab.ui.control.Label
        ShortRunVectorDropDown matlab.ui.control.DropDown
        ShortRunVectorTable matlab.ui.control.Table
        ModelEqAxes matlab.ui.control.UIAxes
        ModelEqText matlab.graphics.primitive.Text
        EstimateButton matlab.ui.control.Button
        CancelButton matlab.ui.control.Button
        DetailsButton matlab.ui.control.Button
        TimeSeries internal.EconModeler.EconComponents.AbstractTimeSeries
        Listeners cell

        ShortRunOrderValues cell
        ShortRunVectorValues cell

    end
    
    methods(Access = ?internal.EconModeler.DialogFactory)
        function this = UIVECImpactParameterDialog(session,source)
            %UIVECCOMPONENTPARAMETERDIALOG Construct an instance of this class
            
            this.Session = session;

            if isa(source,'internal.EconModeler.EconComponents.AbstractModel')
                this.TimeSeries = source.Parent;
            else
                this.TimeSeries = source;
            end
            
            %Figure
            this.Figure = uifigure('Name',getString(message('econ:econModeler:VECImpactModelTitle')),...
                'CloseRequestFcn',@this.closeCallback,...
                'Visible','off',...
                'Tag','parameterFigure',...
                'WindowStyle','modal');
            this.Figure.Position = [680,558,900,400];

            matlab.graphics.internal.themes.figureUseDesktopTheme(this.Figure);

            this.ShortRunOrderValues = {nan(length(this.TimeSeries))};
            this.ShortRunVectorValues = {nan(length(this.TimeSeries))};

            %Outer Grid contains all controls
            outergrid = uigridlayout(this.Figure,[3 1],"RowHeight",{'3x','1x','fit'});

            this.TabGroup = uitabgroup(outergrid,'Tag','tagGroup',...
                'SelectionChangedFcn',@this.setModelEquation);
            this.TabGroup.Layout.Row = 1;
            this.OrderTab = uitab(this.TabGroup,'Title',getString(message('econ:econModeler:LagOrder')));
            this.VectorTab = uitab(this.TabGroup,'Title',getString(message('econ:econModeler:LagVector')));

            orderGrid = uigridlayout(this.OrderTab,[1 3],'ColumnWidth',{'fit','1x','1x'});
            
            leftOrderGrid = uigridlayout(orderGrid,[4 2],'RowHeight',{'1x','fit','fit','1x'},...
                'ColumnWidth',{'fit','fit'});
            leftOrderGrid.Layout.Column = 1;
            this.FormOrderLabel = uilabel(leftOrderGrid,'Text',getString(message('econ:econModeler:JohansenForm')),...
                'Tag','formOrderLabel');
            this.FormOrderLabel.Layout.Row = 2;
            this.FormOrderLabel.Layout.Column = 1;
            this.FormOrderDropDown = uidropdown(leftOrderGrid,'Items',{'H2','H1*','H1','H*','H'},...
                'Value','H1','ValueChangedFcn',@this.setForm,...
                'Tag','formOrderDropDown');
            this.FormOrderDropDown.Layout.Row = 2;
            this.FormOrderDropDown.Layout.Column = 2;
            this.LagOrderLabel = uilabel(leftOrderGrid,'Text',getString(message('econ:econModeler:NumberOfLags')),...
                'Tag','LagOrderLabel');
            this.LagOrderLabel.Layout.Row = 3;
            this.LagOrderLabel.Layout.Column = 1;
            this.LagOrderSpinner = uispinner(leftOrderGrid,'Limits',[1,inf],...
                'Value',1,'Step',1,'RoundFractionalValues','on','ValueChangedFcn',@this.setLags,...
                'Tag','lagOrderSpinner');
            this.LagOrderSpinner.Layout.Row = 3;
            this.LagOrderSpinner.Layout.Column = 2;

            middleOrderGrid = uigridlayout(orderGrid,[2 3],'RowHeight',{'fit','1x'},...
                'ColumnWidth',{'fit','fit','1x'});
            middleOrderGrid.Layout.Column = 2;
            this.ShortRunOrderLabel = uilabel(middleOrderGrid,'Text',getString(message('econ:econModeler:ShortRun')),...
                'Tag','shortRunOrderLabel');
            this.ShortRunOrderLabel.Layout.Row = 1;
            this.ShortRunOrderLabel.Layout.Column = 1;
            this.ShortRunOrderDropDown = uidropdown(middleOrderGrid,...
                "Value",1,"Items",{getString(message('econ:econModeler:LagSelection',1))}, "ItemsData",1,...
                "ValueChangedFcn",@this.updateShortRunTable,...
                "Tag",'shortRunOrderDropDown');
            this.ShortRunOrderDropDown.Layout.Row = 1;
            this.ShortRunOrderDropDown.Layout.Column = 2;
            this.ShortRunOrderTable = uitable(middleOrderGrid,'Data',this.ShortRunOrderValues{1},...
                'RowName',{this.TimeSeries.Name},'ColumnName',{this.TimeSeries.Name},...
                'ColumnEditable',true,...
                'CellEditCallback',@this.setShortRun,'Tag','shortRunOrderTable');
            this.ShortRunOrderTable.Layout.Row = 2;
            this.ShortRunOrderTable.Layout.Column = [1 3];

            rightOrderGrid = uigridlayout(orderGrid,[2 1],'RowHeight',{'fit','1x'});
            rightOrderGrid.Layout.Column = 3;
            this.ImpactOrderLabel = uilabel(rightOrderGrid,'Text',getString(message('econ:econModeler:ImpactMatrix')),...
                'Tag','cointegrationOrderLabel');
            this.ImpactOrderLabel.Layout.Row = 1;
            this.ImpactOrderTable = uitable(rightOrderGrid,'Data',nan(length(this.TimeSeries)),...
                'RowName',{this.TimeSeries.Name},...
                'ColumnName',{this.TimeSeries.Name},...
                'ColumnEditable',true,'CellEditCallback',@this.setImpact,...
                'Tag','impactOrderTable');
            this.ImpactOrderTable.Layout.Row = 2;

            vectorGrid = uigridlayout(this.VectorTab,[1 3],'ColumnWidth',{'fit','1x','1x'});
            
            leftVectorGrid = uigridlayout(vectorGrid,[4 2],'RowHeight',{'1x','fit','fit','1x'},...
                'ColumnWidth',{'fit','fit'});
            leftVectorGrid.Layout.Column = 1;
            this.FormVectorLabel = uilabel(leftVectorGrid,'Text',getString(message('econ:econModeler:JohansenForm')),...
                'Tag','formVectorLabel');
            this.FormVectorLabel.Layout.Row = 2;
            this.FormVectorLabel.Layout.Column = 1;
            this.FormVectorDropDown = uidropdown(leftVectorGrid,'Items',{'H2','H1*','H1','H*','H'},...
                'Value','H1','ValueChangedFcn',@this.setForm,...
                'Tag','formVectorDropDown');
            this.FormVectorDropDown.Layout.Row = 2;
            this.FormVectorDropDown.Layout.Column = 2;
            this.LagVectorLabel = uilabel(leftVectorGrid,'Text',getString(message('econ:econModeler:Lags')),...
                'Tag','LagVectorLabel');
            this.LagVectorLabel.Layout.Row = 3;
            this.LagVectorLabel.Layout.Column = 1;
            this.LagVectorEdit = uieditfield(leftVectorGrid,...
                    'HorizontalAlignment','right',...
                    'Value','1',...
                    'ValueChangingFcn',@this.checkVectorText,...
                    'Tag','lagEdit');
            this.LagVectorEdit.Layout.Row = 3;
            this.LagVectorEdit.Layout.Column = 2;

            middleVectorGrid = uigridlayout(vectorGrid,[2 3],'RowHeight',{'fit','1x'},...
                'ColumnWidth',{'fit','fit','1x'});
            middleVectorGrid.Layout.Column = 2;
            this.ShortRunVectorLabel = uilabel(middleVectorGrid,'Text',getString(message('econ:econModeler:ShortRun')),...
                'Tag','shortRunVectorLabel');
            this.ShortRunVectorLabel.Layout.Row = 1;
            this.ShortRunVectorLabel.Layout.Column = 1;
            this.ShortRunVectorDropDown = uidropdown(middleVectorGrid,...
                "Value",1,"Items",{getString(message('econ:econModeler:LagSelection',1))}, "ItemsData",1,...
                "ValueChangedFcn",@this.updateShortRunTable,...
                "Tag",'shortRunVectorDropDown');
            this.ShortRunVectorDropDown.Layout.Row = 1;
            this.ShortRunVectorDropDown.Layout.Column = 2;
            this.ShortRunVectorTable = uitable(middleVectorGrid,'Data',this.ShortRunVectorValues{1},...
                'RowName',{this.TimeSeries.Name},'ColumnName',{this.TimeSeries.Name},...
                'ColumnEditable',true,...
                'CellEditCallback',@this.setShortRun,'Tag','shortRunOrderTable');
            this.ShortRunVectorTable.Layout.Row = 2;
            this.ShortRunVectorTable.Layout.Column = [1 3];

            rightVectorGrid = uigridlayout(vectorGrid,[2 1],'RowHeight',{'fit','1x'});
            rightVectorGrid.Layout.Column = 3;
            this.ImpactVectorLabel = uilabel(rightVectorGrid,'Text',getString(message('econ:econModeler:ImpactMatrix')),...
                'Tag','cointegrationVectorLabel');
            this.ImpactVectorLabel.Layout.Row = 1;
            this.ImpactVectorTable = uitable(rightVectorGrid,'Data',nan(length(this.TimeSeries)),...
                'RowName',{this.TimeSeries.Name},...
                'ColumnName',{this.TimeSeries.Name},...
                'ColumnEditable',true,'CellEditCallback',@this.setImpact,...
                'Tag','ImpactVectorTable');
            this.ImpactVectorTable.Layout.Row = 2;

            %Create the Equation Axes
            this.ModelEqAxes = uiaxes(outergrid,...
                'XTick',[],...
                'XTickLabel',[],...
                'YTick',[],...
                'YTickLabel',[],...
                'XColor','white',...
                'YColor','white',...
                'Tag','modelEquationAxes');
            this.ModelEqAxes.Title.String = getString(message('econ:econModeler:ModelEquation'));
            this.ModelEqAxes.Layout.Row = 2;
            %Set this to avoid the axes toolbar from appearing
            this.ModelEqAxes.Toolbar.Visible = 'off';
            
            %Equation text
            this.ModelEqText = text(this.ModelEqAxes,0.5,0.5,'',...
                'Units','Normalized',...
                'FontSize',16,...
                'HorizontalAlignment','center',...
                'Interpreter','latex',...
                'Tag','modelEquationText');
            
            bottomgrid = uigridlayout(outergrid,[1 6],'ColumnWidth',{'1x','fit','fit','fit'});
            bottomgrid.Layout.Row = 3;
            
            %Details Button
            this.DetailsButton = uibutton(bottomgrid,...
                'Text',getString(message('econ:econModeler:Details')),...
                'ButtonPushedFcn',@this.launchHelp,...
                'Tag','detailsButton');
            this.DetailsButton.Layout.Column = 2;
            
            %Estimate Button
            this.EstimateButton = uibutton(bottomgrid,...
                'Text',getString(message('econ:econModeler:Estimate')),...
                'ButtonPushedFcn',@this.requestEstimate,...
                'Tag','estimateButton');
            this.EstimateButton.Layout.Column = 3;
            
            %Cancel Button
            this.CancelButton = uibutton(bottomgrid,...
                'Text',getString(message('econ:econModeler:Cancel')),...
                'ButtonPushedFcn',@this.closeFig,...
                'Tag','cancelButton');
            this.CancelButton.Layout.Column = 4;


            %Get the time series to estimate
            if isa(source,'internal.EconModeler.EconComponents.AbstractModel')
                this.FormOrderDropDown.Value = source.Form;
                this.FormVectorDropDown.Value = source.Form;
                
                this.ShortRunOrderValues = source.ShortRunOrder;
                this.ShortRunVectorValues = source.ShortRunVector;
                this.LagOrderSpinner.Value = length(source.ShortRunOrder);
                srIdx = find(cellfun(@(x)~all(all(x==0)),source.ShortRunVector));
                this.LagVectorEdit.Value = num2str(srIdx);

                this.ImpactOrderTable.Data = source.Impact;
                this.ImpactVectorTable.Data = source.Impact;

                if source.Tab == "Order"
                    %Set the dialog to match the short run Order
                    this.TabGroup.SelectedTab = this.OrderTab;
                    this.setLags(this.LagOrderSpinner,struct(...
                        'Value',this.LagOrderSpinner.Value,'PreviousValue',1));
                else
                    %Set the dialog to match the custom short run Vector
                    this.TabGroup.SelectedTab = this.VectorTab;
                    this.checkVectorText(this.LagVectorEdit,struct('Value',this.LagVectorEdit.Value));
                end
            else
                this.setModelEquation();
            end

            movegui(this.Figure,'center');
            this.Figure.Visible = 'on';
            
            this.Listeners{end+1} = event.listener(this.Session,'AppClosed',@(s,e)closeCallback(this));
        end
    end

    methods(Access = protected)
        function closeCallback(this,~,~)
            this.Session.notify('DialogClosed');
            %Delete object when figure closes
            delete(this.Figure);
            delete(this);
        end
        
        function closeFig(this,~,~)
            %Cancel button and Estimate closes figure
            close(this.Figure);
        end

        function setForm(this,~,e)
            this.FormOrderDropDown.Value = e.Value;
            this.FormVectorDropDown.Value = e.Value;
            this.setModelEquation();
        end

        function setLags(this,~,e)
            setModelEquation(this);

            order = e.Value;
            if order == 1
                this.LagVectorEdit.Value = "1";
            else
                this.LagVectorEdit.Value = "1:" + order;
            end
            this.checkVectorText(this.LagVectorEdit,struct('Value',this.LagVectorEdit.Value));

            oldOrder = e.PreviousValue;
            if oldOrder > order
                %Number of Lags has been reduced
                this.ShortRunOrderValues(order+1:end) = [];
                %Remove the higher orders from the parameters dropdown (order)
                if this.ShortRunOrderDropDown.Value > order
                    this.ShortRunOrderDropDown.Value = order;
                    this.updateTableDisplay();
                end
                this.ShortRunOrderDropDown.Items(order+1:end) = [];
                this.ShortRunOrderDropDown.ItemsData(order+1:end) = [];
            elseif order > oldOrder
                % Order is now larger, add items to the parameter dropdown 
                for n = oldOrder+1:order
                    this.ShortRunOrderValues(n) = {nan(length(this.TimeSeries))};
                    this.ShortRunOrderDropDown.Items{n} = getString(message('econ:econModeler:LagSelection',n));
                    this.ShortRunOrderDropDown.ItemsData(n) = n;
                end
            end

            %Set the values for the parameters dropdown vector
            this.ShortRunVectorDropDown.Items = {};
            this.ShortRunVectorDropDown.ItemsData = [];
            for n = 1:order
                this.ShortRunVectorDropDown.Items{n} = getString(message('econ:econModeler:LagSelection',n));
                this.ShortRunVectorDropDown.ItemsData(n) = n;
            end
            this.ShortRunVectorDropDown.Value = 1;
        end

        function checkVectorText(this,s,e)
        %Check edited text to confirm that it represents a MATLAB row
        %vector

            if isempty(e.Value)
                %Empty is valid
                s.FontColor = 'black';
                setModelEquation(this,[],[],s.Tag,e.Value);
                return;
            end

            numberExpression = '^[\d,:\[\]\s]*$';
            if isempty(regexp(e.Value,numberExpression,'once'))
                %Check to see only expected symbols in string
                s.FontColor = 'red';
                setModelEquation(this,[],[],s.Tag,e.Value);
                return;
            end

            try
                %Make sure expression evaluates to increasing integer row vector
                val = sort(str2num(e.Value)); %#ok<ST2NM>
                validateattributes(val,{'numeric'},{'integer','positive','row'});
                s.FontColor = 'black';
            catch
                s.FontColor = 'red';
            end 
            setModelEquation(this,[],[],s.Tag,e.Value);
            
            %Set the Dropdown items
            this.ShortRunVectorDropDown.Items = {};
            this.ShortRunVectorDropDown.ItemsData = [];
            for n = 1:length(val)
                this.ShortRunVectorDropDown.Items{n} = getString(message('econ:econModeler:LagSelection',val(n)));
                this.ShortRunVectorDropDown.ItemsData(n) = val(n);
            end

            for n = 1:max(val)
                if any(val == n)
                    if n > length(this.ShortRunVectorValues)
                        this.ShortRunVectorValues{n} = nan(length(this.TimeSeries));
                    end
                else
                    this.ShortRunVectorValues{n} = zeros(length(this.TimeSeries));
                end
            end
            if length(this.ShortRunVectorValues) > max(val)
                this.ShortRunVectorValues(max(val)+1:end) = [];
            end
        end

        function updateShortRunTable(this,~,~)
            this.ShortRunOrderTable.Data = this.ShortRunOrderValues{this.ShortRunOrderDropDown.Value};
            this.ShortRunVectorTable.Data = this.ShortRunVectorValues{this.ShortRunVectorDropDown.Value};
        end

        function setShortRun(this,~,~)
            this.ShortRunOrderValues{this.ShortRunOrderDropDown.Value} = this.ShortRunOrderTable.Data;
            this.ShortRunVectorValues{this.ShortRunVectorDropDown.Value} = this.ShortRunVectorTable.Data;
        end

        function setImpact(this,~,e)
            this.ImpactOrderTable.Data(e.Indices(1),e.Indices(2)) = e.NewData;
            this.ImpactVectorTable.Data(e.Indices(1),e.Indices(2)) = e.NewData;
        end

        function setModelEquation(this,~,~,varargin)

            p = inputParser;
            p.addParameter('lagEdit',[]);
            p.parse(varargin{:});

            equationString = this.getEquation(p.Results);
            if strcmp(equationString,getString(message('econ:econModeler:InvalidEquation')))
                %Do not use tex if displaying the invalid lag specification
                %message, as translation can produce warnings with tex
                this.ModelEqText.Interpreter = 'none';
            else
                this.ModelEqText.Interpreter = 'latex';
            end
            this.ModelEqText.String = equationString;
            drawnow;
        end

        function str = getEquation(this,p)
            try
                str = "$$ \Delta y_{t} = \Pi y_{t-1} ";
                str = str + this.getFormString() + this.getShortRunString(p) + "+ \varepsilon_{t}$$";
            catch
                %Display message if this fails
                str = getString(message('econ:econModeler:InvalidEquation'));
            end
        end

        function str = getFormString(this)
            form = this.FormOrderDropDown.Value;
            switch form
                case 'H2'
                    str = "";
                case 'H1*'
                    str = "+ Ac_{0}";
                case 'H1'
                    str = "+ Ac_{0} + c_{1}";
                case 'H*'
                    str = "+ A(c_{0}+d_{0}t) + c_{1}";
                case 'H'
                    str = "+ A(c_{0}+d_{0}t) + c_{1} + d_{1}t";
            end
        end

        function str = getShortRunString(this,p)

            if this.TabGroup.SelectedTab == this.OrderTab
                order = this.LagOrderSpinner.Value;
                vector = 1:order;
            else
                if isempty(p.lagEdit)
                    vector = this.lagStr2Num(this.LagVectorEdit.Value);
                else
                    vector = this.lagStr2Num(p.lagEdit);
                end
            end

            if isempty(vector)
                str = "";
                return;
            end

            str = "+ ";
            if length(vector) >= 1 && length(vector) <= 3
                for n = vector
                    str = str + "\Phi_{" + n + "}\Delta y_{t-" + n + "}";
                    if n ~= vector(end)
                        str = str+"+";
                    end
                end
            elseif length(vector) > 3
                for n = 1:2
                    str = str + "\Phi_{" + vector(n) + "}\Delta y_{t-" + vector(n) + "}";
                    str = str+"+";
                end
                str = str + "...+\Phi_{" + vector(end) + "}\Delta y_{t-" + vector(end) + "}";
            end
        end

        function requestEstimate(this,~,~)
            import internal.EconModeler.EventData.NewModelEventData
            %Get all parameters, and notify app of estimate request

            params.TimeSeries = this.TimeSeries;
            if this.TabGroup.SelectedTab == this.OrderTab
                params.ShortRun = this.ShortRunOrderValues;
                params.Tab = "Order";
            else
                params.ShortRun = this.ShortRunVectorValues;
                params.Tab = "Vector";
            end

            params.Form = this.FormOrderDropDown.Value;
            params.Impact = this.ImpactOrderTable.Data;

            params.ShortRunOrder = this.ShortRunOrderValues;
            params.ShortRunVector = this.ShortRunVectorValues;

            this.FormOrderDropDown.Enable = 'off';
            this.LagOrderSpinner.Enable = 'off';
            this.ShortRunOrderDropDown.Enable = 'off';
            this.ShortRunOrderTable.Enable = 'off';
            this.ImpactOrderTable.Enable = 'off';
            this.FormVectorDropDown.Enable = 'off';
            this.LagVectorEdit.Enable = 'off';
            this.ShortRunVectorDropDown.Enable = 'off';
            this.ShortRunVectorTable.Enable = 'off';
            this.ImpactVectorTable.Enable = 'off';
            this.DetailsButton.Enable = "off";
            this.EstimateButton.Enable = "off";
            this.CancelButton.Enable = "off";
            try
                this.Session.requestEstimate(NewModelEventData(this.ModelType,params));
            catch ME
                this.FormOrderDropDown.Enable = 'on';
                this.LagOrderSpinner.Enable = 'on';
                this.ShortRunOrderDropDown.Enable = 'on';
                this.ShortRunOrderTable.Enable = 'on';
                this.ImpactOrderTable.Enable = 'on';
                this.FormVectorDropDown.Enable = 'on';
                this.LagVectorEdit.Enable = 'on';
                this.ShortRunVectorDropDown.Enable = 'on';
                this.ShortRunVectorTable.Enable = 'on';
                this.ImpactVectorTable.Enable = 'on';
                this.DetailsButton.Enable = "on";
                this.EstimateButton.Enable = "on";
                this.CancelButton.Enable = "on";
                uialert(this.Figure,ME.message,...
                    getString(message('econ:econModeler:AppTitle')),...
                    'Icon','error','Modal',true);
                return;
            end
            this.closeFig();
        end
    end

    methods(Static)
        function num = lagStr2Num(str)
            %Convert a lag vector string to number equivalent
            
            if isempty(str)
                num = [];
                return;
            end
            
             numberExpression = '^[\d,:\[\]\s]*$';
             if isempty(regexp(str,numberExpression,'once'))
                 error(message('econ:econModeler:InvalidLagSpecification'));
             end
           
             num = str2num(str); %#ok<ST2NM>
             validateattributes(num,{'numeric'},{'integer','positive','row'});
        end
    end
end

