classdef (Hidden = true) Optimize < handle
    
    % The Optimize class is the root Optimize LET class
    
    % Copyright 2020-2021 The MathWorks, Inc.
    
    properties (Access = public)
        
        % Set LET auto-run to off
        AutoRun = false;
        
        % Required visible public property for embedding into a live script
        UIFigure matlab.ui.Figure
    end
    
    properties (Hidden, Access = public)
        
        % Principle app container components
        GridLayout (1, 1) matlab.ui.container.GridLayout
        UIAccordion (1, 1) matlab.ui.container.internal.Accordion
        ProblemTypeAccordionPanel (1, 1) matlab.ui.container.internal.AccordionPanel
        ProblemTypeGrid (1, 1) matlab.ui.container.GridLayout
        ProblemDataAccordionPanel (1, 1) matlab.ui.container.internal.AccordionPanel
        ProblemDataGrid (1, 1) matlab.ui.container.GridLayout
        SolverOptionsAccordionPanel (1, 1) matlab.ui.container.internal.AccordionPanel
        DisplayAccordionPanel (1, 1) matlab.ui.container.internal.AccordionPanel
        DisplayGrid (1, 1) matlab.ui.container.GridLayout
        
        % Sub-view objects manage the widgets and user inputs for certain
        % sections of the uifigure
        ObjectiveType matlab.internal.optimgui.views.solverbased.ObjectiveType
        ConstraintType matlab.internal.optimgui.views.solverbased.ConstraintType
        SelectSolver matlab.internal.optimgui.views.solverbased.SelectSolver
        ObjectiveFunction matlab.internal.optimgui.views.solverbased.ObjectiveFunction
        Solver matlab.internal.optimgui.views.solverbased.Solver
        Constraints matlab.internal.optimgui.views.solverbased.Constraints
        Options matlab.internal.optimgui.views.Options
        TextDisplay matlab.internal.optimgui.views.TextDisplay
        PlotFcn matlab.internal.optimgui.views.PlotFcn
        
        % Model to update and view
        Model matlab.internal.optimgui.models.solverbased.OptimizeModel
    end
    
    properties (Hidden, SetAccess = private, GetAccess = public)
        
        % Listeners
        lhSolverList event.proplistener
        lhSolverName event.proplistener
        lhProblemTypeChanged event.proplistener
        lhConstraintTypeChanged event.proplistener
        lhTaskChanged event.listener
        lhRemoveConstraints event.listener
        lhUpdateConstraintsGrid event.listener
        lhAlgorithmChanged event.listener
        lhCshClicked event.listener
        
        % This property stores whether solver model related views are enabled
        % These views get disabled when user does not have a license for the
        % specified problem type
        areSolverModelViewsEnabled (1, 1) logical = true;
    end
    
    events
        
        % Notify the Live Editor that the task changed
        Changed
    end
    
    methods (Access = public)
        
        function app = Optimize()
        
        % Create default model object
        app.Model = matlab.internal.optimgui.models.solverbased.OptimizeModel();
        
        % Create app container components
        app.createComponents();
        
        % Create sub-views
        app.createSubViews();
        
        % Listen for changes to the SolverList and SolverName Model properties
        app.lhSolverList = addlistener(app.Model, {'ObjectiveType', 'ConstraintType'}, 'PostSet', @app.SolverListChanged);
        app.lhSolverName = addlistener(app.Model, 'SolverName', 'PostSet', @app.SolverNameChanged);
        
        % Listen for changes to the problem type buttons
        app.lhProblemTypeChanged = addlistener(app.Model, {'ObjectiveType', 'ConstraintType'}, 'PostSet', @app.ProblemTypeChanged);
        
        % Listen for changes to the constraint type. It may impact valid Algorithms
        app.lhConstraintTypeChanged = addlistener(app.Model, 'ConstraintType', 'PostSet', @app.ConstraintTypeChanged);
        
        % Listen to sub-views for user inputs
        fun = @(x) addlistener(x, 'ValueChangedEvent', @app.TaskChanged);
        app.lhTaskChanged = arrayfun(fun, [app.ObjectiveType, app.ConstraintType, app.SelectSolver, ...
            app.ObjectiveFunction, app.Solver, app.Constraints, app.Options, ...
            app.TextDisplay, app.PlotFcn]);
        
        % Listen to the ConstraintType class for removed constraints
        app.lhRemoveConstraints = addlistener(app.ConstraintType, 'RemoveConstraintsEvent', @app.removeConstraints);
        
        % Listen to the ConstraintType class to update the Constraints class grid visibility
        app.lhUpdateConstraintsGrid = addlistener(app.ConstraintType, 'UpdateConstraintsGridEvent', ...
            @app.updateConstraintsGrid);
        
        % Listen to the Options class for the Algorithm changing
        app.lhUpdateConstraintsGrid = addlistener(app.Options, 'AlgorithmChangedEvent', @app.AlgorithmChanged);
        
        % Listen to sub-views for users clicking the context sensitive help (Csh) image
        fun = @(x) addlistener(x, 'CshImageClickedEvent', @app.goToDocLink);
        app.lhCshClicked = arrayfun(fun, [app.SelectSolver, app.ObjectiveFunction, ...
            app.Constraints, app.Options]);
        
        % Suppress command window output
        if nargout == 0
            clear app
        end
        end
        
        function delete(app)
        
        % Code that executes before app deletion
        delete(app.UIFigure)
        delete([app.lhSolverList, app.lhSolverName, app.lhProblemTypeChanged, ...
            app.lhConstraintTypeChanged, app.lhTaskChanged, ...
            app.lhRemoveConstraints, app.lhUpdateConstraintsGrid, ...
            app.lhUpdateConstraintsGrid, app.lhAlgorithmChanged, ...
            app.lhCshClicked]);
        end
    end
    
    methods (Access = private)
        
        function createComponents(app)
        
        % UIFigure
        app.UIFigure = uifigure;
        app.UIFigure.Position = [100, 100, 710, 400];
        app.UIFigure.Name = matlab.internal.optimgui.utils.getMessage('Labels', 'Optimize');
        
        % GridLayout
        app.GridLayout = uigridlayout(app.UIFigure);
        app.GridLayout.ColumnWidth = {'1x'};
        app.GridLayout.RowHeight = {'fit'};
        
        % Accordion
        app.UIAccordion = matlab.ui.container.internal.Accordion('Parent', app.GridLayout);
        
        % ProblemType accordion panel
        app.ProblemTypeAccordionPanel = matlab.ui.container.internal.AccordionPanel('Parent', app.UIAccordion);
        app.ProblemTypeAccordionPanel.Title = matlab.internal.optimgui.utils.getMessage('Labels', 'ProblemType');
        
        % ProblemTypeGrid
        app.ProblemTypeGrid = uigridlayout(app.ProblemTypeAccordionPanel);
        app.ProblemTypeGrid.RowHeight = {'fit', 'fit', 'fit'};
        app.ProblemTypeGrid.ColumnWidth = {'fit', 'fit'};
        
        % Problem data accordion panel
        app.ProblemDataAccordionPanel = matlab.ui.container.internal.AccordionPanel('Parent', app.UIAccordion);
        app.ProblemDataAccordionPanel.Title = matlab.internal.optimgui.utils.getMessage('Labels', 'ProblemData');
        
        % ProblemDataGrid
        app.ProblemDataGrid = uigridlayout(app.ProblemDataAccordionPanel);
        app.ProblemDataGrid.RowHeight = {'fit', 'fit', 'fit'};
        app.ProblemDataGrid.ColumnWidth = {'fit', 'fit'};
        
        % SolverOptions Accordion Panel
        app.SolverOptionsAccordionPanel = matlab.ui.container.internal.AccordionPanel('Parent', app.UIAccordion);
        app.SolverOptionsAccordionPanel.Title = matlab.internal.optimgui.utils.getMessage('Labels', 'SolverOptions');
        app.SolverOptionsAccordionPanel.Collapsed = true;
        
        % Display Accordion Panel
        app.DisplayAccordionPanel = matlab.ui.container.internal.AccordionPanel('Parent', app.UIAccordion);
        app.DisplayAccordionPanel.Title = matlab.internal.optimgui.utils.getMessage('Labels', 'DisplayProgress');
        app.DisplayAccordionPanel.Collapsed = false;
        
        % DisplayGrid
        app.DisplayGrid = uigridlayout(app.DisplayAccordionPanel);
        app.DisplayGrid.ColumnWidth = {'fit', 'fit'};
        app.DisplayGrid.RowHeight = {'fit', 'fit'};
        end
        
        function createSubViews(app)
        
        % Create sub-view objects. Pass in the view's parent container and Model
        app.ObjectiveType = matlab.internal.optimgui.views.solverbased.ObjectiveType(...
            app.ProblemTypeGrid, app.Model);
        app.ConstraintType = matlab.internal.optimgui.views.solverbased.ConstraintType(...
            app.ProblemTypeGrid, app.Model);
        app.SelectSolver = matlab.internal.optimgui.views.solverbased.SelectSolver(...
            app.ProblemTypeGrid, app.Model);
        app.ObjectiveFunction = matlab.internal.optimgui.views.solverbased.ObjectiveFunction(...
            app.ProblemDataGrid, app.Model.SolverModel);
        app.Solver = matlab.internal.optimgui.views.solverbased.Solver(...
            app.ProblemDataGrid, app.Model.SolverModel);
        app.TextDisplay = matlab.internal.optimgui.views.TextDisplay(...
            app.DisplayGrid, app.Model.SolverModel.Options);
        app.PlotFcn = matlab.internal.optimgui.views.PlotFcn(...
            app.DisplayGrid, app.Model.SolverModel.Options);
        
        % Update figure before creating "hidden" views
        drawnow nocallbacks
        app.Constraints = matlab.internal.optimgui.views.solverbased.Constraints(...
            app.ProblemDataGrid, app.Model.SolverModel);
        app.Options = matlab.internal.optimgui.views.Options(...
            app.SolverOptionsAccordionPanel, app.Model.SolverModel.Options);
        end
        
        function updateView(app)
        
        % Called by the setState method on undo/redo and when loading a saved
        % script with a task.
        
        % Update all sub-views
        app.ObjectiveType.updateView(app.Model);
        app.ConstraintType.updateView(app.Model);
        app.SelectSolver.updateView(app.Model);
        app.updateSolverModelViews();
        
        % Update the Enable property of solver model related views as necessary
        app.updateSolverModelViewsEnabled();
        
        % Listener callbacks are not triggered by setState. Explicitly call
        % ProblemTypeChanged to sync the problem type icons with the model
        app.ProblemTypeChanged();
        end
        
        function updateSolverModelViews(app)
        
        % Called by updateView and SolverNameChanged methods.
        % Updates all solver model related views
        
        app.ObjectiveFunction.updateView(app.Model.SolverModel);
        app.Solver.updateView(app.Model.SolverModel);
        app.Constraints.updateView(app.Model.SolverModel);
        app.Options.updateView(app.Model.SolverModel.Options);
        app.TextDisplay.updateView(app.Model.SolverModel.Options);
        app.PlotFcn.updateView(app.Model.SolverModel.Options);
        end
        
        function SolverListChanged(app, ~, ~)
        
        % Listener callback for changes to Model.SolverList property
        
        % Update SelectSolver.DropDown ItemsData and Items properties
        app.SelectSolver.DropDown.ItemsData = app.Model.SolverList;
        app.SelectSolver.DropDown.Items = app.Model.SolverListMessage;
        
        % If the current solver is not part of the new SolverList,
        % update Model.SolverName
        if ~any(strcmp(app.Model.SolverList, app.Model.SolverName))
            app.Model.SolverName = app.Model.SolverList{1};
        end
        
        % Update SelectSolver.DropDown.Value property
        app.SelectSolver.DropDown.Value = app.Model.SolverName;
        end
        
        function SolverNameChanged(app, ~, ~)
        
        % Listener callback for changes to Model.SolverName property
        
        % Set new Model.SolverModel property
        app.Model.updateSolverModel();
        
        % Update the Enable property of solver model related views as necessary
        app.updateSolverModelViewsEnabled();
        
        % If the user has a license for the specified problem type,
        % Update solver model related views
        if app.Model.hasLicense
            app.updateSolverModelViews();
        end
        end
        
        function ProblemTypeChanged(app, ~, ~)
        
        % Listener callback for changes to problem type buttons and called by updateView method.
        
        % Start with all objective and constraint types enabled
        objectiveEnable = repmat({'on'}, size(app.ObjectiveType.StateButtons));
        constraintEnable = repmat({'on'}, size(app.ConstraintType.StateButtons));
        
        % Linear objective and Unconstrained cannot co-exist
        if strcmp(app.Model.ObjectiveType, 'Linear')
            constraintEnable(strcmp({app.ConstraintType.StateButtons.Tag}, 'None')) = {'off'};
        elseif strcmp(app.Model.ConstraintType, 'None')
            objectiveEnable(strcmp({app.ObjectiveType.StateButtons.Tag}, 'Linear')) = {'off'};
        end
        
        % Set Enable property of buttons
        [app.ObjectiveType.StateButtons.Enable] = deal(objectiveEnable{:});
        [app.ConstraintType.StateButtons.Enable] = deal(constraintEnable{:});
        
        % Update figure
        drawnow nocallbacks
        end
        
        function ConstraintTypeChanged(app, ~, ~)
        
        % Listener callback for changes to ConstraintType. Selected constraints
        % may limit valid Algorithms
        
        % If this solver is a MultiAlgorithmSolver, update the valid algorithms.
        if app.Model.SolverModel.Options.isMultiAlgorithm
            app.Model.SolverModel.Options.updateValidAlgorithms();
        end
        end
        
        function TaskChanged(app, ~, ~)
        
        % Listener callback for the ValueChangedEvent of sub-view classes
        
        % Notify Live Editor to save state
        notify(app, 'Changed');
        end
        
        function removeConstraints(app, ~, event)
        
        % Listener callback for the RemoveConstraintsEvent of the
        % ConstraintType class.
        
        % Reset any removed constraints back to their default value
        app.Constraints.resetConstraints(event.Data);
        end
        
        function updateConstraintsGrid(app, ~, event)
        
        % Listener callback for the UpdateConstraintsGridEvent of the
        % ConstraintType class.

        % If necessary, create the selected constraint widget
        thisConstraint = event.Data;
        if any(strcmp(thisConstraint, app.Model.SolverModel.Constraints)) && ...
                isempty(app.Constraints.(thisConstraint))
            app.Constraints.makeConstraintWidget(thisConstraint);
        end

        % Update the constraints view grid visibility
        app.Constraints.updateGridVisibility();
        end
        
        function AlgorithmChanged(app, ~, ~)
        
        % Listener callback for the AlgorithmChangedEvent of the Options class
        
        % Update the options model and related views that change with Algorithm
        app.updateOptionsModelAndViews(false);
        end
        
        function updateOptionsModelAndViews(app, updateTextAndPlot)
        
        % Called by AlgorithmChanged and reset methods.
        
        % Set OptionsModel
        app.Model.SolverModel.Options.setOptionsModel();
        
        % Update options related views
        app.Options.updateView(app.Model.SolverModel.Options);
        if updateTextAndPlot
            app.TextDisplay.updateView(app.Model.SolverModel.Options);
            app.PlotFcn.updateView(app.Model.SolverModel.Options);
        end
        end
        
        function goToDocLink(~, ~, event)
        
        % Listener callback for the CshImageClickedEvent of some sub-view classes
        
        % Pull doc link from catalog and go to page
        link = matlab.internal.optimgui.utils.getMessage('DocLinks', event.Data);
        web(fullfile(docroot, link));
        end
        
        function updateSolverModelViewsEnabled(app)
        
        % Called by updateView and SolverNameChanged methods. Aligns the Enable property
        % of solver model view components with the Model.hasLicense property
        
        % If no solver is available due to the license, disable solver model views.
        % Else, enable views, but only if already disabled
        if ~app.Model.hasLicense
            app.setSolverModelViewsEnabled('off');
        elseif ~app.areSolverModelViewsEnabled
            app.setSolverModelViewsEnabled('on');
        end
        end
        
        function setSolverModelViewsEnabled(app, value)
        
        % Called by updateSolverModelViewsEnabled
        
        % Set Enable property of all components of the solver model related views accordingly
        set(findall([app.ProblemDataAccordionPanel, app.SolverOptionsAccordionPanel, ...
            app.DisplayAccordionPanel], '-property', 'Enable'), 'Enable', value);
        app.areSolverModelViewsEnabled = strcmp(value, 'on');
        end
    end
    
    % Required methods for embedding in a Live Script. getState, generateScript,
    % and generateSummary are called everytime the Changed event is notified
    % in the TaskChanged method
    methods (Access = public)
        
        function state = getState(app)
        
        % Convert OptimizeState object to a structure
        % Use built-in function, but need to turn warning off/on
        origWarnState = warning('off', 'MATLAB:structOnObject');
        state = struct(app.Model.State);
        warning(origWarnState);
        end
        
        function setState(app, state)
        
        % If the SolverName contains unlicensed, re-check the SolverName based on the
        % objective and constraint types. The user may have saved the task in some
        % license state, but it could be different when they re-open it.
        % Elseif the SolverName is not valid for the current license throw an error
        % and don't load the task
        if contains(state.SolverName, 'unlicensed')
            solverList = app.Model.SolverTypeMap(state.ObjectiveType, ...
                state.ConstraintType);
            state.SolverName = solverList{1};
        elseif ~any(strcmp(state.SolverName, app.Model.SolverTypeMap.masterList))
            globalSolvers = app.Model.SolverTypeMap.globalSolvers;
            if any(strcmp(state.SolverName, globalSolvers(:, 1)))
                license_status = app.Model.SolverTypeMap.UnlicensedGlobal;
            else
                license_status = app.Model.SolverTypeMap.UnlicensedOptim;
            end
            error(['matlab:internal:optimfun:optimgui:', license_status{:}], ...
                matlab.internal.optimgui.utils.getMessage('Labels', license_status{:}))
        end
        
        % Create OptimizeState object from state structure
        objState = app.Model.State.updateState(state);
        
        % Update model based on state object
        app.Model.updateModel(objState);
        
        % Update app view
        app.updateView();
        end
        
        function [code, outputs] = generateScript(app)
        
        % If the user has license for the specified problem type proceed with
        % code generation for the SolverModel.
        % Else, there is no valid SolverModel. Show the toolbox required message as code.
        if app.Model.hasLicense
            % If sufficient inputs are specified to solve the problem, generate the code
            % Else, generate code listing missing required inputs
            [isSet, whatsMissing] = app.Model.SolverModel.areInputsSet();
            if isSet
                [outputs, code] = app.Model.SolverModel.generateCode();
            else
                [outputs, code] = app.Model.SolverModel.generateWhatsMissingCode(whatsMissing);
            end
        else
            outputs = {};
            code = ['disp(''', matlab.internal.optimgui.utils.getMessage('Labels', ...
                app.Model.SolverName), ''')'];
        end
        end
        
        function summary = generateSummary(app)
        
        % If the user has license for the specified problem type AND sufficient
        % inputs are specified to solve the problem, generate summary
        % Else, show the unset summary
        if app.Model.hasLicense && app.Model.SolverModel.areInputsSet()
            summary = app.Model.SolverModel.generateSummary();
        else
            summary = matlab.internal.optimgui.utils.getMessage('CodeGeneration', 'unsetSummary');
        end
        end
        
        function reset(app)
        
        % Reset all options
        app.Model.State.Options = matlab.internal.optimgui.OptimizeConstants.DefaultOptionsStruct;
        app.updateOptionsModelAndViews(true);
        end
    end
end
