function [net, classNames] = doImagePretrainedNetwork(name,opts)
% Non-codegen compatible implementation of imagePretrainedNetwork

%   Copyright 2023 The MathWorks, Inc.

% Determine if the architecture needs editing.
if ~isempty(opts.NumClasses) || ~isempty(opts.NumResponses)
    if ~isempty(opts.NumClasses) && ~isempty(opts.NumResponses)
        error(message('nnet_cnn:imagePretrainedNetwork:AmbiguousEdit'));
    elseif ~isempty(opts.NumClasses)
        task = "classification";
    else
        task = "regression";
    end
else
    task = "none";
end

persistent definitions;
if isempty(definitions)
    definitions = iGetNetworkDefinitions();
end
if ~isKey(definitions, name)
    error(message('nnet_cnn:imagePretrainedNetwork:UnknownName',name));
end

def = definitions(name);
isPretrainedWeights = opts.Weights == "pretrained";

% Restrict when the classes output is allowed.
if (nargout == 2) && (~isPretrainedWeights || (task ~= "none"))
    error(message('nnet_cnn:imagePretrainedNetwork:ExtraClassesOutput'));
end

% Load the network.
try
    if isPretrainedWeights || ~def.HasWeightsSyntax
        dagnet = def.Constructor();
    else
        dagnet = def.Constructor('Weights',"none");
    end
catch
    body = string(message('nnet_cnn:imagePretrainedNetwork:AddOnExplorer'));
    cmd = sprintf('matlab.addons.supportpackage.internal.explorer.showSupportPackages(''%s'', ''tripwire'')',def.Basecode);
    url = nnet.internal.cnn.util.formHyperlink(cmd,body);
    if def.HasWeightsSyntax
        cmd = sprintf('imagePretrainedNetwork("%s",Weights="none")',name);
        error(message('nnet_cnn:imagePretrainedNetwork:NotInstalled',name,def.Description,url,cmd));
    else
        error(message('nnet_cnn:imagePretrainedNetwork:InstallRequired',name,def.Description,url));
    end
end
if ~isa(dagnet,'nnet.cnn.LayerGraph')
    dagnet = layerGraph(dagnet);
end
net = deep.internal.sdk.dag2dlnetwork(dagnet);
if def.NeedsFlattenLayer && ~isa(net.Layers(end),'nnet.cnn.layer.FlattenLayer')
    net = iAppendFlattenLayer(net);
end

classNames = string(dagnet.Layers(end).Classes);
if opts.ClassNamesType == "cell"
    classNames = cellstr(classNames);
end

% Perform any network surgery needed.
if task == "classification"
    net = deep.internal.util.replaceNetworkHead(net,def.Layer,task,opts.NumClasses);
elseif task == "regression"
    net = deep.internal.util.replaceNetworkHead(net,def.Layer,task,opts.NumResponses);
end

if isPretrainedWeights
    if ~net.Initialized
        % Initialize the new layers introduced for transfer learning.
        net = initialize(net);
    end
else
    net = deep.internal.util.uninitializeNetwork(net);
end

end

function dict = iGetNetworkDefinitions()
dict = dictionary;
% Each entry has the following information:
%   1) function handle to construct the DAG or Series network
%   2) layer name of the start of the head of the network
%   3) support package description (if any)
%   4) support package id (if any)
%   5) supports the Weights="none" syntax in the constructor
%   6) if the network should have a trailing flattenLayer
dict("squeezenet") = iCreateDefinition(@squeezenet,...
    'conv10',...
    '',...
    '',...
    true, true);
dict("googlenet") = iCreateDefinition(@googlenet,...
    'loss3-classifier',...
    'Deep Learning Toolbox Model for GoogLeNet Network',...
    'GOOGLENET',...
    true, false);
dict("googlenet-places365") = iCreateDefinition(@(varargin)googlenet('Weights','places365',varargin{:}),...
    'loss3-classifier',...
    'Deep Learning Toolbox Model for GoogLeNet Network',...
    'GOOGLENET',...
    true, false);
dict("inceptionv3") = iCreateDefinition(@inceptionv3,...
    'predictions',...
    'Deep Learning Toolbox Model for Inception-v3 Network',...
    'INCEPTIONV3',...
    true, false);
dict("densenet201") = iCreateDefinition(@densenet201,...
    'fc1000',...
    'Deep Learning Toolbox Model for DenseNet-201 Network',...
    'DENSENET201',...
    true, false);
dict("mobilenetv2") = iCreateDefinition(@mobilenetv2,...
    'Logits',...
    'Deep Learning Toolbox Model for MobileNet-v2 Network',...
    'MOBILENET_V2',...
    true, false);
dict("resnet18") = iCreateDefinition(@resnet18,...
    'fc1000',...
    'Deep Learning Toolbox Model for ResNet-18 Network',...
    'RESNET18',...
    true, false);
dict("resnet50") = iCreateDefinition(@resnet50,...
    'fc1000',...
    'Deep Learning Toolbox Model for ResNet-50 Network',...
    'RESNET50',...
    true, false);
dict("resnet101") = iCreateDefinition(@resnet101,...
    'fc1000',...
    'Deep Learning Toolbox Model for ResNet-101 Network',...
    'RESNET101',...
    true, false);
dict("xception") = iCreateDefinition(@xception,...
    'predictions',...
    'Deep Learning Toolbox Model for Xception Network',...
    'XCEPTION',...
    true, false);
dict("inceptionresnetv2") = iCreateDefinition(@inceptionresnetv2,...
    'predictions',...
    'Deep Learning Toolbox Model for Inception-ResNet-v2 Network',...
    'INCEPTIONRESNETV2',...
    false, false);
dict("shufflenet") = iCreateDefinition(@shufflenet,...
    'node_202',...
    'Deep Learning Toolbox Model for ShuffleNet Network',...
    'SHUFFLENET',...
    false, false);
dict("nasnetmobile") = iCreateDefinition(@nasnetmobile,...
    'predictions',...
    'Deep Learning Toolbox Model for NASNet-Mobile Network',...
    'NASNET_MOBILE',...
    false, false);
dict("nasnetlarge") = iCreateDefinition(@nasnetlarge,...
    'predictions',...
    'Deep Learning Toolbox Model for NASNet-Large Network',...
    'NASNET_LARGE',...
    false, false);
dict("darknet19") = iCreateDefinition(@darknet19,...
    'conv19',...
    'Deep Learning Toolbox Model for DarkNet-19 Network',...
    'DLTDARKNET19',...
    true, true);
dict("darknet53") = iCreateDefinition(@darknet53,...
    'conv53',...
    'Deep Learning Toolbox Model for DarkNet-53 Network',...
    'DLTDARKNET53',...
    true, true);
dict("efficientnetb0") = iCreateDefinition(@efficientnetb0,...
    'efficientnet-b0|model|head|dense|MatMul',...
    'Deep Learning Toolbox Model for EfficientNet-b0 Network',...
    'EFFICIENTNETB0',...
    true, false);
dict("alexnet") = iCreateDefinition(@alexnet,...
    'fc8',...
    'Deep Learning Toolbox Model for AlexNet Network',...
    'ALEXNET',...
    true, false);
dict("vgg16") = iCreateDefinition(@vgg16,...
    'fc8',...
    'Deep Learning Toolbox Model for VGG-16 Network',...
    'VGG16',...
    true, false);
dict("vgg19") = iCreateDefinition(@vgg19,...
    'fc8',...
    'Deep Learning Toolbox Model for VGG-19 Network',...
    'VGG19',...
    true, false);
end

function def = iCreateDefinition(ctor,layer,description,basecode,hasWeightsSyntax,needsFlattenLayer)
def.Constructor = ctor;
def.Layer = layer;
def.Description = description;
def.Basecode = basecode;
def.HasWeightsSyntax = hasWeightsSyntax;
def.NeedsFlattenLayer = needsFlattenLayer;
end

function net = iAppendFlattenLayer(net)
previousName = net.OutputNames{1};
flattenName = [previousName '_flatten'];
net = addLayers(net,flattenLayer(Name=flattenName));
net = connectLayers(net,previousName,flattenName);
end