function installer(varargin)
%COMPILER.PACKAGE.INSTALLER  Create an installer for files generated by the MATLAB Compiler product
%
%    COMPILER.PACKAGE.INSTALLER(RESULTS) creates an installer for files
%        generated by the MATLAB Compiler using the compiler.build.Results
%        object RESULTS. The RESULTS object is created by a
%        compiler.build function.
%
%    COMPILER.PACKAGE.INSTALLER(RESULTS,NAME,VALUE) creates an installer
%        for files generated by the MATLAB Compiler using the
%        compiler.build.Results object RESULTS. The installer can be
%        customized using optional name-value arguments.
%
%    COMPILER.PACKAGE.INSTALLER(RESULTS,'Options',OPTS) creates an
%        installer for files generated by the MATLAB Compiler using the
%        compiler.build.Results object RESULTS. Installer options are
%        specified by an InstallerOptions object OPTS. If you use an
%        InstallerOptions object, you cannot specify any other options
%        using name-value arguments.
%
%    COMPILER.PACKAGE.INSTALLER(FILES,FILEPATH,'ApplicationName',APPNAME)
%        creates an installer for files generated by the MATLAB Compiler.
%        The installed application's name is specified by APPNAME. The
%        extension is determined by the operating system in which the
%        function is executed.
%
%    COMPILER.PACKAGE.INSTALLER(FILES,FILEPATH,'ApplicationName',APPNAME,NAME,VALUE)
%        creates an installer for files generated by the MATLAB Compiler.
%        The installed application's name is specified by APPNAME. The
%        installer can be customized using optional name-value arguments.
%
%    COMPILER.PACKAGE.INSTALLER(FILES,FILEPATH,'Options',OPTS) creates an
%        installer for files generated by the MATLAB Compiler with
%        installer options specified by an InstallerOptions object OPTS. If
%        you use an InstallerOptions object, you cannot specify any other
%        options using name-value arguments.
%
%    INPUT ARGUMENTS:
%
%    - RESULTS (A scalar compiler.build.Results object)
%        Build results, created by a compiler.build function.
%
%    - FILES (A row char vector, a cellstr vector, or a string vector)
%        List of files and folders for installation. These files are
%        typically generated by the MATLAB Compiler product and can also
%        include any additional files and folders required by the installed
%        application to run. Files generated by the MATLAB Compiler product
%        in a particular release can be packaged using the
%        COMPILER.PACKAGE.INSTALLER function of the same release.
%
%    - FILEPATH (A row char vector or a string scalar)
%        Path to the requiredMCRProducts.txt file generated by the MATLAB
%        Compiler product.
%
%    - APPNAME (A row char vector or a string scalar)
%        Name of the installed application.
%
%    - 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', 'installer', or 'none')
%
%        '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 during application installation.
%        Use this option if you think your end user has already 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 installer from an existing file named 'mymagic.m'.
%    % Build a standalone application using the compiler.build.standaloneApplication command.
%    buildResults = compiler.build.standaloneApplication('mymagic.m');
%    % Create an installer for the standalone application using the compiler.package.installer function.
%    COMPILER.PACKAGE.INSTALLER(buildResults)
%
%    % Customize an installer using name-value arguments.
%    COMPILER.PACKAGE.INSTALLER(buildResults, ...
%        'AuthorName','Frog','InstallerName','MagicSquare_Installer')
%
%    % Customize an installer using an InstallerOptions object.
%    opts = compiler.package.InstallerOptions(buildResults);
%    opts.AuthorCompany = 'Boston Common';
%    opts.AuthorName = 'Frog';
%    COMPILER.PACKAGE.INSTALLER(buildResults,'Options',opts)
%
%    % Create an installer from the outputs of the mcc command.
%    % Build a standalone application using the mcc command.
%    mcc -m mymagic.m
%    % Create an installer
%    COMPILER.PACKAGE.INSTALLER('mymagic.exe','requiredMCRProducts.txt', ...
%        'ApplicationName','MagicSquare_Generator')
%
%    See also compiler.package.InstallerOptions, compiler.build.standaloneApplication, compiler.build.standaloneWindowsApplication, mcc

%   Copyright 2020-2023 The MathWorks, Inc.
packageStart = datetime;
packageID = matlab.lang.internal.uuid;

% Verify Compiler license
if ~license('checkout','Compiler')
    error(message('Compiler:deploytool:license_checkout_error'))
end

narginchk(1,2+2*numel(properties(compiler.package.InstallerOptions.empty)))

[items,prodnums,io, buildID] = compiler.internal.package.shared.inputParser( ...
    @compiler.package.InstallerOptions,varargin{:});
items = unique([items(:);io.AdditionalFiles(:)]);

% Confirm Shortcut value is still valid
if isempty(io.Shortcut)
    shortcut = '';
else
    shortcut = validateShortcut(io.Shortcut,items);
end

% Warn if items has exe but shortcut is empty
if ispc && strlength(shortcut) == 0
    exeitems = items(endsWith(items,'.exe'));
    numexe = numel(exeitems);
    if numexe > 0
        exeString = strjoin(exeitems,'\n');
        warning(message('Compiler:installer:exeButNoShortcut',exeString))
    end
end

%Confirm the required runtime products is not empty
if ~strcmp(io.RuntimeDelivery,'none')
    if isempty(prodnums) 
        error(message('Compiler:installer:emptyRequiredMCRProducts'))
    end
else
    prodnums = {};
end

if exist(io.InstallerIcon,'file') ~= 2
    error(message('Compiler:installer:propDoesNotExist',io.InstallerIcon,'InstallerIcon'))
end

if exist(io.InstallerLogo,'file') ~= 2
    error(message('Compiler:installer:propDoesNotExist',io.InstallerLogo,'InstallerLogo'))
end

if exist(io.InstallerSplash,'file') ~= 2
    error(message('Compiler:installer:propDoesNotExist',io.InstallerSplash,'InstallerSplash'))
end

if ~isempty(io.AddRemoveProgramsIcon) && exist(io.AddRemoveProgramsIcon,'file') ~= 2
    error(message('Compiler:installer:propDoesNotExist',io.AddRemoveProgramsIcon,'AddRemoveProgramsIcon'))
end

% Create and canonicalize OutputDir
if exist(io.OutputDir,'dir') == 0
    mkdir(io.OutputDir)
end
io.OutputDir = builtin('_canonicalizepath',io.OutputDir);
[~,att] = fileattrib(io.OutputDir);
if ~(att.UserWrite && att.UserRead)
    error(message('Compiler:package:shared:badPermissions',io.OutputDir))
end

[ocIcon,icon_ico,icon_48] = compiler.internal.build.processIcon( ...
    io.InstallerIcon,io.OutputDir,io.ApplicationName); %#ok<ASGLU>

% Try to delete existing zipfile
bundleFileName = fullfile(io.OutputDir,'bundle.zip');
if exist(bundleFileName,'file') == 2
    delete(bundleFileName)
end

if(io.Verbose)
    fprintf("%s\n", message("Compiler:installer:packagingFiles").getString());
end

% Resize Logo
[~,logoname,logoext] = fileparts(io.InstallerLogo);
tempLogo = compiler.internal.utils.getUniquePath('file', ...
    'temp',sprintf('%s%s',logoname,logoext),io.OutputDir);
compiler.internal.image.copy(io.InstallerLogo,tempLogo, ...
    'OutputImageSize',[290,112])
ocLogo = onCleanup(@()delete(tempLogo));

% Resize splash screen
[~,splashname,splashext] = fileparts(io.InstallerSplash);
tempSplash = compiler.internal.utils.getUniquePath('file', ...
    'temp',sprintf('%s%s',splashname,splashext),io.OutputDir);
compiler.internal.image.copy(io.InstallerSplash,tempSplash, ...
    'OutputImageSize',[400,400])
ocSplash = onCleanup(@()delete(tempSplash));

% Create install bundle builder
ibb = compiler.internal.InstallBundleBuilder( struct(...
    'userApplicationName', io.ApplicationName, ...
    'shortcut', shortcut, ...
    'guid', matlab.lang.internal.uuid, ...
    'defaultInstallDirectory', io.DefaultInstallationDir, ...
    'applicationIcon', icon_48, ...
    'applicationLogo', tempLogo, ...
    'version', io.Version, ...
    'mcrExternalProductIdentifiers', cell2mat(prodnums), ...
    'bundleFile', bundleFileName));

if ~isempty(io.AddRemoveProgramsIcon) && ispc
    [ocAddRemoveProgramsIcon,addrem_ico] = compiler.internal.build.processIcon( ...
        io.AddRemoveProgramsIcon,io.OutputDir,'uninstall_icon'); %#ok<ASGLU>
    ibb.setAddRemoveProgramIcon(addrem_ico)
    items = [items(:);addrem_ico];
end

ibb.setApplicationFiles(items);

if ~isempty(io.AuthorCompany)
    ibb.setAuthorOrganization(io.AuthorCompany);
end

% We're losing knowledge of "Target" installer notes
osnotes = compiler.internal.getDefaultInstallNotes;
fullnotes = io.InstallationNotes;
if ~isempty(osnotes)
    fullnotes = sprintf('%s\n\n%s',osnotes,fullnotes);
end
ibb.setInstallationNotes(fullnotes);

welcomePanelText = sprintf('%s %s',io.ApplicationName,io.Version);
if ~isempty(io.Summary)
    ibb.setApplicationSummary(io.Summary);
    welcomePanelText = sprintf('%s\n%s',welcomePanelText,io.Summary);
end
if ~isempty(io.Description)
    welcomePanelText = sprintf('%s\n\n%s',welcomePanelText,io.Description);
end
if ~isempty(io.AuthorName)
    welcomePanelText = sprintf('%s\n%s',welcomePanelText,io.AuthorName);
end
if ~isempty(io.AuthorEmail)
    welcomePanelText = sprintf('%s\n%s',welcomePanelText,io.AuthorEmail);
end
ibb.setWelcomePanelText(welcomePanelText);

% This writes the bundle.zip
ibb.createBundle();
if exist(bundleFileName,'file') == 2
    delbndl_oc = onCleanup(@()delete(bundleFileName));
else
    error(message('Compiler:installer:fileCreateFailed',bundleFileName))
end

switch upper(computer)
    case 'PCWIN64'
        embedPayloadObj = compiler.internal.EmbedPayloadwin64;
    case 'GLNXA64'
        embedPayloadObj = compiler.internal.EmbedPayloadglnxa64;
    case 'MACI64'
        embedPayloadObj = compiler.internal.EmbedPayloadmac64;
    case 'MACA64'
        embedPayloadObj = compiler.internal.EmbedPayloadmac64;
end

installerFile = fullfile(io.OutputDir, ...
    compiler.internal.package.installer.addInstallerExtension(io.InstallerName));
% The empies are interpreted as nulls when passed to java
switch io.RuntimeDelivery
case 'web'
    packageTarget = 'WebInstaller';
    urltxtfile = fullfile(io.OutputDir,'installAgentURL.txt');
    endpoint = compiler.internal.runtime.utils.getInstallagentDowndloadURL();
    fid = fopen(urltxtfile, 'w');
	if fid ~= -1
		fprintf(fid, '%s', endpoint);
		fclose(fid);
	end
    if exist(urltxtfile,'file') == 2
        urltxt_oc = onCleanup(@()delete(urltxtfile));
    else
        error(message('Compiler:installer:fileCreateFailed',urltxtfile))
    end

    installerFile = embedPayloadObj.embed(installerFile, bundleFileName, urltxtfile, ...
	    icon_ico, tempSplash, true, io.PackageType);
case 'installer'
    if(io.Verbose)
        fprintf("%s\n", message("Compiler:installer:packagingMATLABRuntime").getString());
    end
    packageTarget = 'OfflineInstaller';
    loc = compiler.internal.runtime.utils.getExistingMCRInstallerWithValidation;
    if isempty(loc)
        error(message('Compiler:installer:couldNotFindRuntimeInstaller'))
    end
    agent = compiler.internal.buildinstallagent(prodnums{:});
    installerFile = embedPayloadObj.embed(installerFile, bundleFileName, agent, ...
	    icon_ico, tempSplash, false, io.PackageType);
case 'none'
    if(io.Verbose)
        fprintf("%s\n", message("Compiler:installer:noMATLABRuntime").getString());
    end
    packageTarget = 'ApplicationOnlyInstaller';
    if isempty(compiler.internal.runtime.utils.getExistingMCRInstallerWithValidation())
        error(message('Compiler:installer:needInstallAgent'))
    end
    agent = compiler.internal.buildinstallagent();
    installerFile = embedPayloadObj.embed(installerFile, bundleFileName, agent, ...
	    icon_ico, tempSplash, false, io.PackageType);
otherwise
    error('Internal error: compiler.package.InstallerOptions failed to flag unsupported option for RuntimeDelivery at an earlier step.');
end

% On mac, a .app "folder" is created, so we expect 7
% Other OS's we expect a file, therefore 2
if exist(installerFile) ~= (2 + 5*ismac) %#ok<EXIST>
    error(message('Compiler:installer:fileCreateFailed',installerFile))
end

packagesize = dir(installerFile).bytes;  % this will be 0 on mac since it's a folder. 
packageTime = seconds(datetime - packageStart);

compiler.internal.package.shared.processResults(packageID, buildID, packageTarget, string(prodnums), io, packageTime, packagesize)

if(io.Verbose)
    fprintf("%s\n", message("Compiler:installer:packagingComplete", installerFile ).getString());
end

function shortcut = validateShortcut(shortcut,items)

% Note that "items" have been canonicalized by now
allpossible = cell(numel(items),2);
allpossible(:,1) = items(:);
for idx = 1:numel(items)
    [pth,nm,ext] = fileparts(items{idx});
    allpossible{idx,2} = [nm,ext];
    if exist(items{idx},'dir') == 7
        d = dir(fullfile(items{idx},'**'));
        for mm = 1:numel(d)
            if ~any(strcmp(d(mm).name,{'.','..'}))
                allpossible{end+1,1} = fullfile(d(mm).folder,d(mm).name); %#ok<AGROW>
                allpossible{end,2} = allpossible{end,1}(numel(pth)+2:end);
            end
        end
    end
end

scidx = find(strcmp(shortcut,allpossible(:,1)),1);
if isempty(scidx)
    error(message('Compiler:installer:shortcutNotPackaged'))
else
    shortcut = allpossible{scidx,2};
end
