classdef InstallerOptions < compiler.internal.package.shared.OptionsBase
%COMPILER.PACKAGE.INSTALLEROPTIONS  Create an installer options object
%
%    OPTS = COMPILER.PACKAGE.INSTALLEROPTIONS(RESULTS) creates an
%        INSTALLEROPTIONS object OPTS using the compiler.build.Results
%        object RESULTS. The RESULTS object is created by a
%        compiler.build function. The INSTALLEROPTIONS object is passed as
%        an input to the compiler.package.installer function.
%
%    OPTS = COMPILER.PACKAGE.INSTALLEROPTIONS(RESULTS,NAME,VALUE) creates
%        an INSTALLEROPTIONS object OPTS using the compiler.build.Results
%        object RESULTS and additional customizations specified by
%        name-value arguments. The RESULTS object is created by a
%        compiler.build function. The INSTALLEROPTIONS object is passed as
%        an input to the compiler.package.installer function.
%
%    OPTS = COMPILER.PACKAGE.INSTALLEROPTIONS('ApplicationName',APPNAME)
%        creates a default INSTALLEROPTIONS object OPTS with application
%        name specified by APPNAME. The INSTALLEROPTIONS object is passed
%        as an input to the compiler.package.installer function.
%
%    OPTS = COMPILER.PACKAGE.INSTALLEROPTIONS('ApplicationName',APPNAME,NAME,VALUE)
%        creates an INSTALLEROPTIONS object OPTS with application name
%        specified by APPNAME and additional customizations specified by
%        name-value arguments. The INSTALLEROPTIONS object is passed as an
%        input to the compiler.package.installer function.
%
%    INPUT ARGUMENTS:
%
%    - RESULTS (A scalar compiler.build.Results object)
%        Build results, created by a compiler.build function.
%
%    - APPNAME (A row char vector or a string scalar)
%        Name of the installed application.
%
%    OUTPUT ARGUMENTS:
%
%    - OPTS (A scalar compiler.package.InstallerOptions object)
%        Installer options, specified as an InstallerOptions object.
%
%    NAME-VALUE ARGUMENTS:
%
%    - AdditionalFiles (A row char vector, a string vector, or a cellstr vector)
%        A list of files and folders to be installed with the application.
%
%    - AddRemoveProgramsIcon (A row char vector or a string scalar)
%        Path to an image file used as the icon in the list of Windows
%        applications within settings.
%
%    - ApplicationName (A row char vector or a string scalar)
%        Name of the installed application.
%
%    - AuthorCompany (A row char vector or a string scalar)
%        Name of company that created the application.
%
%    - AuthorEmail (A row char vector or a string scalar)
%        Email address of the application author.
%
%    - AuthorName (A row char vector or a string scalar)
%        Name of application author.
%
%    - DefaultInstallationDir (A row char vector or a string scalar)
%        Default directory where you want the installer to install the
%        application.
%
%    - Description (A row char vector or a string scalar)
%        Detailed description of the application.
%
%    - InstallationNotes (A row char vector or a string scalar)
%        Notes about additional requirements for using the application.
%
%    - InstallerIcon (A row char vector or a string scalar)
%        Path to an image file used as the icon for the installed
%        application.
%
%    - InstallerLogo (A row char vector or a string scalar)
%        Path to an image file used as the installer's logo. The logo will
%        be resized to 112 pixels by 290 pixels.
%
%    - InstallerName (A row char vector or a string scalar)
%        Name of the installer file. The extension is determined by the
%        operating system in which the function is executed.
%
%    - InstallerSplash (A row char vector or a string scalar)
%        Path to an image file used as the installer's splash screen. The
%        splash screen icon will be resized to 400 pixels by 400 pixels.
%
%    - OutputDir (A row char vector or a string scalar)
%        Path to folder where the installer is saved.
%
%    - PackageType ('auto' or 'zip')
%
%        'auto' (default) - Option for installer to automatically select
%        suitable package type when creating a new installer.
%
%        'zip' - Option to generate a ZIP file as an output when creating a new
%        installer. Only supported on Windows.
%
%    - RuntimeDelivery ('web' or 'installer')
%
%        'web' (default) - Option for installer to download MATLAB Runtime
%        from MathWorks® website during application installation.
%
%        'installer' - Option to include MATLAB Runtime within the
%        installer so that it can be installed without connecting to the
%        MathWorks® website. Use this option if you think your end user may
%        not have access to the Internet.
%
%        'none' - Option to not install MATLAB Runtime. Use this option if 
%        you think your end user has installed MATLAB Runtime.
%
%    - Shortcut (A row char vector or a string scalar)
%        Path to a file or folder that the installer will create a shortcut
%        to at install time. Only supported on Windows.
%
%    - Summary (A row char vector or a string scalar)
%        Summary of the application.
%
%    - Verbose ('on'/'off', true/false, or 1/0)
%        Binary value controlling packing verbosity. It is 'off' by default.
%
%    - Version (A row char vector or a string scalar)
%        Version number of the generated application.
%
%    EXAMPLES:
%
%    % Create an INSTALLEROPTIONS object using the output of the
%    % compiler.build.standaloneApplication command.
%    buildResults = compiler.build.standaloneApplication('mymagic.m');
%    opts = COMPILER.PACKAGE.INSTALLEROPTIONS(buildResults);
%
%    % Create an INSTALLEROPTIONS object with an application name and
%    % additional options specified as name-value arguments.
%    opts = COMPILER.PACKAGE.INSTALLEROPTIONS( ...
%        'ApplicationName','InstallCoolApp','Summary','The best app ever');
%
%    % Create an INSTALLEROPTIONS object with an application name and
%    % additional options specified using dot notation.
%    opts = COMPILER.PACKAGE.INSTALLEROPTIONS( ...
%        'ApplicationName','MagicSquareGenerator');
%    opts.Summary = 'Makes a magic square for you.';
%
%    See also compiler.package.installer, mcc

%   Copyright 2020-2023 The MathWorks, Inc.
    
    properties
        RuntimeDelivery {mustBeNonempty} = 'web'
        InstallerSplash {mustBeNonempty} = fullfile(compiler.build.StandaloneApplicationOptions.DefaultImageDir,'default_splash.png')
        InstallerIcon {mustBeNonempty} = fullfile(compiler.build.StandaloneApplicationOptions.DefaultImageDir,'default_icon_48.png')
        InstallerLogo {mustBeNonempty} = fullfile(compiler.build.StandaloneApplicationOptions.DefaultImageDir,'default_logo.png')
        PackageType {mustBeNonempty} = 'auto'
        AdditionalFiles = {}
        AddRemoveProgramsIcon = ''
        AuthorName = ''
        AuthorEmail = ''
        AuthorCompany = ''
        Summary = ''
        Description = ''
        InstallationNotes = ''
        Shortcut = ''
        Verbose matlab.lang.OnOffSwitchState = 'off'
        Version {mustBeNonempty} = '1.0'
        InstallerName = ''
        ApplicationName = ''
    end
    
    properties(Dependent)
        OutputDir
        DefaultInstallationDir {mustBeNonempty}
    end
    
    % private
    properties(Access = private)
        OutputDirManager = compiler.internal.build.OutputDirManager('installer')
        UserProvidedDefaultInstallationDir = ''
    end
    properties(Constant,Hidden)
        DefaultInstallerName = 'MyAppInstaller'
    end
    
    % define abstract
    properties(Constant,Access = protected)
        RequiredProperties = {'ApplicationName'}
    end
    
    % constructor
    methods
        function obj = InstallerOptions(varargin)
            obj = obj@compiler.internal.package.shared.OptionsBase(varargin{:});
        end
    end
    
    % get
    methods
        function name = get.InstallerName(obj)
            name = obj.InstallerName;
            if isempty(name)
                name = compiler.package.InstallerOptions.DefaultInstallerName;
            end
        end
        
        function outdir = get.OutputDir(obj)
            outdir = obj.OutputDirManager.getOutputDir(obj.ApplicationName);
        end
        
        function installdir = get.DefaultInstallationDir(obj)
            installdir = obj.UserProvidedDefaultInstallationDir;
            if isempty(installdir)
                switch upper(computer)
                    case 'PCWIN64'
                        rootdir = getenv('ProgramFiles');
                    case 'GLNXA64'
                        rootdir = '/usr';
                    case 'MACI64'
                        rootdir = '/Applications';
                    case 'MACA64'
                        rootdir = '/Applications';
                end
                
                name = compiler.internal.utils.scrubName(obj.ApplicationName);
                installdir = fullfile(rootdir,name);
            end
        end
    end
    
    % set
    methods
        function obj = set.AdditionalFiles(obj,newFileList)
            if isempty(newFileList)
                obj.AdditionalFiles = {};
                return
            end
            
            obj.AdditionalFiles = compiler.internal.validate.itemList( ...
                newFileList,'PropertyName','AdditionalFiles', ...
                'AllowFoldersInList',true);
        end
        
        function obj = set.OutputDir(obj,newDir)
            obj.OutputDirManager = obj.OutputDirManager.setOutputDir(newDir);
        end
        
        function obj = set.DefaultInstallationDir(obj,newDir)
            compiler.internal.validate.propertyIsScalarText(newDir,'DefaultInstallationDir')
            obj.UserProvidedDefaultInstallationDir = char(newDir);
        end
        
        function obj = set.InstallerSplash(obj,newSplash)
            compiler.internal.validate.propertyIsScalarText(newSplash,'InstallerSplash')
            compiler.internal.validate.fileMustExist(newSplash)
            newSplash = compiler.internal.validate.makePathAbsolute(newSplash);
            compiler.internal.image.rejectAnimatedGIFs(newSplash)
            if ispc || ismac
                obj.InstallerSplash = newSplash;
            else
                % Linux
                warning(message('Compiler:installer:noSplashScreenOnLinux'))
            end
        end
        
        function obj = set.InstallerIcon(obj,newIcon)
            compiler.internal.validate.propertyIsScalarText(newIcon,'InstallerIcon')
            compiler.internal.validate.fileMustExist(newIcon)
            newIcon = compiler.internal.validate.makePathAbsolute(newIcon);
            compiler.internal.image.rejectAnimatedGIFs(newIcon)
            obj.InstallerIcon = newIcon;
        end
        
        function obj = set.InstallerLogo(obj,newLogo)
            compiler.internal.validate.propertyIsScalarText(newLogo,'InstallerLogo')
            compiler.internal.validate.fileMustExist(newLogo)
            newLogo = compiler.internal.validate.makePathAbsolute(newLogo);
            compiler.internal.image.rejectAnimatedGIFs(newLogo)
            obj.InstallerLogo = newLogo;
        end

        function obj = set.AddRemoveProgramsIcon(obj,newIcon)
            if isempty(newIcon)
                obj.AddRemoveProgramsIcon = '';
                return
            end
            compiler.internal.validate.propertyIsScalarText(newIcon,'InstallerIcon')
            compiler.internal.validate.fileMustExist(newIcon)
            newIcon = compiler.internal.validate.makePathAbsolute(newIcon);
            compiler.internal.image.rejectAnimatedGIFs(newIcon)
            if ispc
                obj.AddRemoveProgramsIcon = newIcon;
            else
                % Linux or Mac
                warning(message('Compiler:installer:uninstallIconWindowsOnly'))
            end
        end
        
        function obj = set.Shortcut(obj,newShortcut)
            compiler.internal.validate.propertyIsScalarText(newShortcut,'Shortcut')
            if isempty(newShortcut) || strlength(newShortcut) == 0
                obj.Shortcut = '';
            else
                if exist(newShortcut) == 0 %#ok<EXIST>
                    error(message('Compiler:package:shared:cannotFindFileOrFolder',newShortcut))
                end
                if ispc
                    obj.Shortcut = compiler.internal.validate.makePathAbsolute(newShortcut);
                else
                    warning(message('Compiler:installer:shortcutWindowsOnly'))
                end
            end
        end
        
        function obj = set.RuntimeDelivery(obj,newFlag)
            compiler.internal.validate.propertyIsScalarText(newFlag,'RuntimeDelivery')
            mustBeMember(newFlag,{'web','installer','none'})
            obj.RuntimeDelivery = char(newFlag);
        end

        function obj = set.PackageType(obj, newFlag)
            compiler.internal.validate.propertyIsScalarText(newFlag,'PackageType')
            mustBeMember(newFlag, {'auto', 'zip'})
            if ispc || newFlag == "auto"
                obj.PackageType = char(newFlag);
            else
                warning(message('Compiler:installer:packageTypeWindowsOnly'))
            end
            
        end
        
        function obj = set.AuthorName(obj,newName)
            compiler.internal.validate.propertyIsScalarText(newName,'AuthorName')
            obj.AuthorName = char(newName);
        end
        
        function obj = set.AuthorEmail(obj,newEmail)
            compiler.internal.validate.propertyIsScalarText(newEmail,'AuthorEmail')
            obj.AuthorEmail = char(newEmail);
        end
        
        function obj = set.AuthorCompany(obj,newCompany)
            compiler.internal.validate.propertyIsScalarText(newCompany,'AuthorCompany')
            obj.AuthorCompany = char(newCompany);
        end
        
        function obj = set.Summary(obj,newSummary)
            compiler.internal.validate.propertyIsScalarText(newSummary,'Summary')
            obj.Summary = char(newSummary);
        end
        
        function obj = set.Description(obj,newDesc)
            compiler.internal.validate.propertyIsScalarText(newDesc,'Description')
            obj.Description = char(newDesc);
        end
        
        function obj = set.InstallationNotes(obj,newNotes)
            compiler.internal.validate.propertyIsScalarText(newNotes,'InstallationNotes')
            obj.InstallationNotes = char(newNotes);
        end
        
        function obj = set.Version(obj,newVersion)
            compiler.internal.validate.propertyIsScalarText(newVersion,'Version')
            newVersion = char(newVersion);
            
            verdata = textscan(newVersion,'%s','Delimiter','.');
            verdata = verdata{1};
            for n = 1:numel(verdata)
                if isnan(str2double(verdata{n}))
                    error(message('Compiler:clishared:badProperty', ...
                        'Version',newVersion))
                end
            end
            
            obj.Version = newVersion;
        end
        
        function obj = set.ApplicationName(obj,newName)
            compiler.internal.validate.cliName(newName,'ApplicationName')
            obj.ApplicationName = char(newName);
        end
        
        function obj = set.InstallerName(obj,newName)
            compiler.internal.validate.cliName(newName,'InstallerName')
            obj.InstallerName = char(newName);
        end
    end
    
    methods(Hidden)
        function s = saveobj(obj)
            p = properties(obj);
            s = struct;
            for n = 1:numel(p)
                s.(p{n}) = obj.(p{n});
            end
        end
    end
    
    methods(Static,Hidden)
        function nvpairs = updateInputsFromResultsObject(resultsobj,varargin)

            nvpairs = varargin;

            if ~compiler.internal.validate.nameValuePairsHaveProperty('ApplicationName',varargin{:})
                nvpairs = ['ApplicationName',resultsobj.Options.getName,nvpairs];
            end

            standaloneTypes = {char(compiler.internal.build.BuildTypes.standaloneApplication); ...
                char(compiler.internal.build.BuildTypes.standaloneWindowsApplication)};
            if ispc
                switch resultsobj.BuildType
                    case standaloneTypes
                        if ~compiler.internal.validate.nameValuePairsHaveProperty('AddRemoveProgramsIcon',varargin{:}) && ...
                                exist(resultsobj.Options.ExecutableIcon,'file') == 2

                            nvpairs = ['AddRemoveProgramsIcon',resultsobj.Options.ExecutableIcon,nvpairs];
                        end

                        if ~compiler.internal.validate.nameValuePairsHaveProperty('Shortcut',varargin{:})

                            exename = compiler.internal.package.installer.addInstallerExtension(resultsobj.Options.ExecutableName);
                            idx = endsWith(resultsobj.Files,sprintf('%s%s',filesep,exename));

                            if sum(idx) == 1 && exist(resultsobj.Files{idx},'file') == 2
                                nvpairs = ['Shortcut',resultsobj.Files{idx},nvpairs];
                            end
                        end
                    case char(compiler.internal.build.BuildTypes.excelClientForProductionServer)
                        error(message('Compiler:installer:excelForMPSNotSupported'))
                end
            end
        end
        
        function obj = loadobj(s)
            obj = compiler.package.InstallerOptions('ApplicationName','default');
            if exist(s.InstallerIcon,'file') ~= 2
                warning(message('Compiler:installer:propDoesNotExist',s.InstallerIcon,'InstallerIcon'))
                s.InstallerIcon = obj.InstallerIcon;
            end
            
            if exist(s.InstallerLogo,'file') ~= 2
                warning(message('Compiler:installer:propDoesNotExist',s.InstallerLogo,'InstallerLogo'))
                s.InstallerLogo = obj.InstallerLogo;
            end
            
            if exist(s.InstallerSplash,'file') ~= 2
                warning(message('Compiler:installer:propDoesNotExist',s.InstallerSplash,'InstallerSplash'))
                s.InstallerSplash = obj.InstallerSplash;
            end
            
            p = properties(obj);
            islinux = isunix && ~ismac;
            for n = 1:numel(p)
                if ~(islinux && any(strcmpi(p{n},{'InstallerIcon','InstallerSplash'})))
                    obj.(p{n}) = s.(p{n});
                end
            end
        end
    end
end
