function obj = train(obj,displayMode)
% TRAIN trains a local model network with the LOLIMOT algorithm.
%
% This training algorithm calls all the other for the construction of the
% LOLIMOT object required functions and administrates the LOLIMOT object.
% It is also responsible for stopping the training with respect to the
% no-go criteria an delivering the finished net object.
%
%
% obj = train(obj,displayMode)
%
%
% INPUT/OUTPUT:
%   obj:          object    LOLIMOT object containing all relevant net
%                           and data set information and variables.
%
%
% LoLiMoT - Nonlinear System Identification Toolbox
% Torsten Fischer, 20-December-2011
% Institute of Mechanics & Automatic Control, University of Siegen, Germany
% Copyright (c) 2012 by Prof. Dr.-Ing. Oliver Nelles


% Get the starting time for abording algorithm
time = tic;

if exist('displayMode','var')
    obj.history.displayMode = displayMode;
end

% Handle the display mode
if ~islogical(obj.history.displayMode)
    warning('lolimot:train','"displayMode" is not given correctly! No display outputs will be delivered during the training!')
    obj.history.displayMode = false;
end

% Calculate the regressors, if they are not given
if isempty(obj.xRegressor) || isempty(obj.zRegressor)
    obj.xRegressor = data2xRegressor(obj,obj.input,obj.output);
    obj.zRegressor = data2zRegressor(obj,obj.input,obj.output);
end

% Calculate termination conditions, based on user definitions or defaults
N  = size(obj.output,1); % Number of samples

% Max number of local models is not given
if isempty(obj.maxNumberOfLM)
    obj.maxNumberOfLM = inf;
end

% Ensure that at least 1 LM is given
if obj.maxNumberOfLM == 0
    obj.maxNumberOfLM = 1;
    if obj.history.displayMode
        fprintf('\nData is too sparse for given setup! No splitting with LOLIMOT considered.')
    end
end

% Max number of parameters is not given
if isempty(obj.maxNumberOfParameters)
    obj.maxNumberOfParameters = N;
end

% Min Error is not given
if isempty(obj.minError)
    obj.minError = 0;
end

% Estimate first net
if isempty(obj.localModels) % Estimate a global linear model
    obj = estimateFirstLM(obj); % One linear model
else % Estimate a nonlinear model based on the given structure
    obj = estimateGivenModel(obj); % Model based on structure
end

% Make history entries
obj.history = writeHistory(obj.history,obj,time);

% Display loss function value for the first net
if obj.history.displayMode
    fprintf('\nInitial net has %d local linear model(s): J = %f.\n', ...
        numel(obj.localModels), obj.history.globalLossFunction(obj.history.iteration));
end

% Tree construction algorithm
while obj.checkTerminationCriterions % Go on training until one termination criterion is reached
    
    while any(obj.idxAllowedLM & obj.leafModels) % While-loop over all allowed local models
        
        % Initialize the best global loss function value
        globalLossFunctionBest = obj.history.globalLossFunction(end);
        
        % Initialize the flag, if a permitted split is found
        flagPermittedSplitFound = false;
        
        % Find worst performing LM
        worstLM = findWorstLM(obj);
        
        % Check for split of worst performing LLM
        if obj.history.displayMode
            fprintf('\n\n%d. Iteration. Number of local linear models = %d. Checking for split of model %d ...', ...
                obj.history.iteration, sum(obj.leafModels), worstLM);
        end
        
        for splitDimension = 1:size(obj.zRegressor,2) % Over all dimensions of the z-regressor
            for split = 1:obj.splits % Over all splitting ratios
                splitRatio = split/(obj.splits+1);
                [objSplit, forbiddenSplit] = ...
                    LMSplitEstimate(obj, worstLM, splitDimension, splitRatio);
                
                % If the split is forbidden, try the next dimension or split ratio
                if forbiddenSplit
                    if obj.history.displayMode
                        fprintf('\n   Split in dimension %d with ratio %4.2f is forbidden!', ...
                            splitDimension, splitRatio);
                    end
                    continue % Continue with the next split
                end
                
                if obj.history.displayMode
                    fprintf('\n   Testing split in dimension %d with ratio %4.2f: J = %f.', ...
                        splitDimension, splitRatio, objSplit.history.globalLossFunction(end));
                end
                
                % If better than the currently best alternative
                if globalLossFunctionBest - objSplit.history.globalLossFunction(end) > eps
                    objBest = objSplit;
                    globalLossFunctionBest = objSplit.history.globalLossFunction(end);
                    flagPermittedSplitFound = true;
                end
            end
        end
        
        % Check if a permitted split of the current worstLM can be done
        if flagPermittedSplitFound
            % Break while-loop; no other local models need to be tested
            break
        else
            % No permitted split found; go one investigating another local model and lock the worstLM
            obj.idxAllowedLM(worstLM) = false;
            if obj.history.displayMode
                fprintf('\n\n%d. Iteration. Model %d can not be splitted. Try another model...', ...
                    obj.history.iteration, worstLM);
            end
        end
    end
    
    % Update model object, if a permitted split is found
    if flagPermittedSplitFound
        
        % Perform best split
        obj = objBest;
        
        % Make history entries
        obj.history = writeHistory(obj.history,obj,time);
        
        if obj.history.displayMode
            fprintf('\n-> Splitting in dimension %d with ratio %4.2f: J = %f.', ...
                obj.history.splitDimension(obj.history.iteration-1), obj.history.splitRatio(obj.history.iteration-1), ...
                obj.history.globalLossFunction(obj.history.iteration));
        end
    end
end

% Final display
if obj.history.displayMode
    fprintf('\n\nFinal net has %d local linear models: J = %f.', ...
        sum(obj.leafModels), obj.history.globalLossFunction(obj.history.iteration));
    fprintf('\n\nNet %d with %d LLMs is suggested as the model with the best complexity trade-off.\n\n', ...
        obj.suggestedNet, sum(obj.history.leafModelIter{obj.suggestedNet}));
end
