function classificationLearner(varargin)
% classificationLearner   Opens the Classification Learner app
%  
%    classificationLearner opens the Classification Learner app or brings
%    focus to the app if it is already open.
%
%    classificationLearner(filename) opens the Classification Learner app
%    with the previously saved session in filename. The filename argument,
%    specified as a character vector or string scalar, must include the
%    name of a Classification Learner session file and the path to the
%    file, if it is not in the current folder. The file must have a .mat
%    extension.
%
%    classificationLearner(TBL,Y) opens the Classification Learner app and
%    populates the New Session dialog box with the predictor variables in
%    the table TBL and the response variable Y.
%  
%    Y can be one of the following:
%        1. An array of class labels. Y can be a categorical array, logical
%           vector, numeric vector, string array, or cell array of
%           character vectors.
%        2. The name of a variable in TBL, specified as a character vector
%           or string scalar. This variable is used as the response Y, and 
%           the remaining variables in TBL are used as predictors.
%  
%     classificationLearner(X,Y) is an alternative syntax that accepts an
%     N-by-P matrix of predictors X with one row per observation and one
%     column per predictor. The response Y is an array of N class labels.
%  
%     classificationLearner(...,Name,Value) specifies options using one or
%     more name-value arguments in addition to the input data arguments in
%     previous syntaxes. For example, you can specify the validation and
%     test set options: 'CrossVal', 'KFold', 'Holdout', and
%     'TestDataFraction'.
% 
%         'CrossVal'     - Specified as 'on' or 'off'. If 'on', the app
%                          uses 5-fold cross-validation. You can use the 
%                          'KFold' and 'Holdout' name-value arguments to 
%                          override this cross-validation setting. You can 
%                          use only one of these two name-value arguments 
%                          at a time. Default: 'on'
%         'Holdout'      - Holdout validation uses the specified fraction
%                          of the data for validation, and uses the rest of
%                          the data for training. Specify a numeric scalar
%                          between 0.05 and 0.5.
%         'KFold'        - Number of folds to use in cross-validation,
%                          specified as a positive integer.
%   'TestDataFraction'   - Fraction of the data to reserve for testing. 
%                          Specify a numeric scalar between 0 and 0.5.

%   Copyright 2013-2021 The MathWorks, Inc.

[licenseStatus, errMsg] = builtin('license','checkout','Statistics_Toolbox');

if licenseStatus == 0
    error(errMsg);
end

appArguments = [];
sessionFilePath = [];
if nargin > 0
    [varargin{:}] = convertStringsToChars(varargin{:});

    if nargin == 1
        if ~ischar(varargin{1})
            error('stats:mlearnapp:AppArgumentsNumberError', iMessageString('stats:mlearnapp:AppArgumentsNumberErrorMessage'));
        elseif iFileExists(varargin{1})
            error('stats:mlearnapp:InputFileNotFound', iMessageString('stats:mlearnapp:MLearnAppSessionFileNotFoundErrorMessage', varargin{1}));
        end
        sessionFilePath = varargin{1};
    else
        if mod(nargin,2) ~= 0
            error('stats:mlearnapp:AppArgumentsNumberError', iMessageString('stats:mlearnapp:AppArgumentsNumberErrorMessage'));
        end
        
        varargin = [varargin(:)', {inputname(1)}, {inputname(2)}, {'classification'}];
        
        % Parse the arguments passed
        appArguments = mlearnapp.internal.tools.AppArguments(varargin);
    end
end

appReadyListener = [];

% Try to get the Classification Learner handle from groot
hApp = getappdata( groot, iMLearnAppDataPropertyName() );

% If the Classification Learner handle is not in groot, then we need to create it.
isStartUp = isempty( hApp ) || ~isa( hApp, 'handle' ) || ~isvalid( hApp );
if isStartUp
    % Create the main app
    appTag = 'ClassificationLearner';
    appTitleMessage = mlearnapp.internal.adapterlayer.Message('stats:mlearnapp:AppTitleClassificationLearner');
    metricFactory = mlearnapp.internal.model.metric.ClassificationMetricFactory();
    validationPartitionFactory = mlearnapp.internal.adapterlayer.ClassificationValidationPartitionFactory();
    dialogFactory = mlearnapp.internal.ui.DialogFactory();
    mainThreadBusyTracker = dialogFactory.createMainThreadBusyTracker();
    sessionFactory = mlearnapp.internal.model.SessionFactory();
    problemDependentToolstripFactory = mlearnapp.internal.ui.toolstrip.ClassificationLearnerToolstripFactory();
    session = sessionFactory.createSession(metricFactory, validationPartitionFactory, problemDependentToolstripFactory, mainThreadBusyTracker);
    newSessionCreatorFactory = mlearnapp.internal.ui.manager.NewSessionCreatorFactory();
    importTestDataCreatorFactory = mlearnapp.internal.ui.manager.ImportTestDataCreatorFactory();
    
    hApp = dialogFactory.createMLearnAppWindow(session, newSessionCreatorFactory, importTestDataCreatorFactory, appTag, appTitleMessage, problemDependentToolstripFactory, metricFactory, mainThreadBusyTracker);
    
    % Store the handle in groot to preserve it
    setappdata( groot, iMLearnAppDataPropertyName(), hApp );
    
    % Listen to the "close" event so that we can remove the handle from the app-data.
    addlistener( hApp, 'ObjectBeingDestroyed', @iRemoveMLearnAppFromAppData );
    addlistener( hApp, 'WindowClosed', @iRemoveMLearnAppFromAppData );
    appReadyListener = addlistener(hApp, 'WindowReady', @(~,~) appReadyCallback(hApp, appArguments, sessionFilePath));
end

% Make Classification Learner visible
hApp.makeVisible();

% create new app session from command line arguments
if ~isempty(appArguments) && hApp.isReady()
    delete(appReadyListener);
    iOpenNewSessionDialogWithArgs(hApp, appArguments);
end

if ~isempty(sessionFilePath) && hApp.isReady()
    delete(appReadyListener);
    iOpenNewSessionFromFile(hApp, sessionFilePath);
end
end

function appReadyCallback(hApp, appArguments, sessionFilePath)
    if ~isempty(appArguments)
        iOpenNewSessionDialogWithArgs(hApp, appArguments)
    end

    if ~isempty(sessionFilePath)
        iOpenNewSessionFromFile(hApp, sessionFilePath);
    end
end

function iOpenNewSessionDialogWithArgs(hApp, appArguments)
    % create new app session from command line arguments
    hApp.NewSessionCreator.newSessionFromArgs(appArguments);
end

function iOpenNewSessionFromFile(hApp, sessionFilePath)
    hApp.Session.SessionSaveLoadController.openSessionFromFilePathArgs(sessionFilePath);
end

function iRemoveMLearnAppFromAppData( ~, ~ )
% iRemoveMLearnAppFromAppData   Remove Classification Learner App Object from app-data of groot
% iRemoveMLearnAppFromAppData( src, evt )
if isappdata( groot, iMLearnAppDataPropertyName() )
    rmappdata( groot, iMLearnAppDataPropertyName );
end
end

function name = iMLearnAppDataPropertyName()
% iMLearnAppDataPropertyName   Name of the app-data property used to store
% the Classification Learner Object.
name = 'ClassificationLearnerAppHandle';
end

function string = iMessageString(ID, varargin)
string = mlearnapp.internal.adapterlayer.Message(ID, varargin{:}).getString();
end

function tf = iFileExists(filePath)
    tf = exist(filePath, 'file') ~= 2 ;
end
