function [net, info] = cnn_gender(varargin)
%CNN_gender   Demonstrates training a CNN on CelebA

run(fullfile(fileparts(mfilename('fullpath')), ...
  '..', '..', 'matlab', 'vl_setupnn.m')) ;

opts.dataDir = fullfile(vl_rootnn, 'data','CelebA') ;
opts.modelType = 'orig' ; %You will need to change this when using your mini 
                          %network for problem 4
opts.network = [] ;
opts.networkType = 'simplenn' ;
opts.batchNormalization = true ;
opts.weightInitMethod = 'gaussian' ;
[opts, varargin] = vl_argparse(opts, varargin) ;

%This is just naming your network and making a directory to save the
%trained network in
sfx = opts.modelType ;
if opts.batchNormalization, sfx = [sfx '-bnorm'] ; end
sfx = [sfx '-' opts.networkType] ;
opts.expDir = fullfile(vl_rootnn, 'data', ['gender-' sfx]) ;
[opts, varargin] = vl_argparse(opts, varargin) ;


opts.numFetchThreads = 12 ;
opts.lite = false ;
opts.imdbPath = fullfile(opts.expDir, 'imdb.mat');
opts.train = struct() ;
opts = vl_argparse(opts, varargin) ;
if ~isfield(opts.train, 'gpus'), opts.train.gpus = []; end;

% -------------------------------------------------------------------------
%                                                              Prepare data
% -------------------------------------------------------------------------

if exist(opts.imdbPath)
  imdb = load(opts.imdbPath) ;
  imdb.imageDir = fullfile(opts.dataDir, 'images');
else
  imdb = cnn_gender_setup_data('dataDir', opts.dataDir, 'lite', opts.lite) ;
  mkdir(opts.expDir) ;
  save(opts.imdbPath, '-struct', 'imdb') ;
end

% This Computes image statistics (mean, RGB covariances, etc.)
imageStatsPath = fullfile(opts.expDir, 'imageStats.mat') ;
train = find(imdb.images.set == 1) ;
images = fullfile(imdb.imageDir, imdb.images.name(train(1:100:end))) ;
[averageImage, rgbMean, rgbCovariance] = getImageStats(images, ...
                                                    'imageSize', [64 64], ...
                                                    'numThreads', opts.numFetchThreads, ...
                                                    'gpus', opts.train.gpus) ;
save(imageStatsPath, 'averageImage', 'rgbMean', 'rgbCovariance') ;

[v,d] = eig(rgbCovariance) ;
rgbDeviation = v*sqrt(d) ;
clear v d ;

% -------------------------------------------------------------------------
%                                                             Prepare model
% -------------------------------------------------------------------------
%This initializes the network with the architecture you specify in
%cnn_gender_init.m, For problems 3 and 4, you will need to edit
%cnn_gender_init.m
if isempty(opts.network)
  net = cnn_gender_init('model', opts.modelType, ...
                     'batchNormalization', opts.batchNormalization, ...
                     'weightInitMethod', opts.weightInitMethod, ...
                     'networkType', opts.networkType, ...
                     'averageImage', rgbMean, ...
                     'colorDeviation', rgbDeviation, ...
                     'classNames', imdb.classes.name, ...
                     'classDescriptions', imdb.classes.description) ;
else
  net = opts.network ;
  opts.network = [] ;
end

% -------------------------------------------------------------------------
%                                                                     Learn
% -------------------------------------------------------------------------
%You should not need to edit this for problems 3 or 4
[net, info] = cnn_train(net, imdb, getBatchFn(opts, net.meta), ...
                      'expDir', opts.expDir, ...
                      net.meta.trainOpts, ...
                      opts.train) ;

% -------------------------------------------------------------------------
%                                                                      Test
% -------------------------------------------------------------------------
%You should not need to edit this for problems 3 or 4
[net, info] = cnn_test(net, imdb, getBatchFn(opts, net.meta), ...
                       'expDir', opts.expDir, ...
                       net.meta.trainOpts, ...
                       opts.train);

% -------------------------------------------------------------------------
function fn = getBatchFn(opts, meta)
% -------------------------------------------------------------------------

if numel(meta.normalization.averageImage) == 3
  mu = double(meta.normalization.averageImage(:)) ;
else
  mu = imresize(single(meta.normalization.averageImage), ...
                meta.normalization.imageSize(1:2)) ;
end

useGpu = numel(opts.train.gpus) > 0 ;

bopts.test = struct(...
  'useGpu', useGpu, ...
  'numThreads', opts.numFetchThreads, ...
  'imageSize',  meta.normalization.imageSize(1:2), ...
  'cropSize', meta.normalization.cropSize, ...
  'subtractAverage', mu) ;

% Copy the parameters for data augmentation
bopts.train = bopts.test ;
for f = fieldnames(meta.augmentation)'
  f = char(f) ;
  bopts.train.(f) = meta.augmentation.(f) ;
end

fn = @(x,y) getBatch(bopts,useGpu,lower(opts.networkType),x,y) ;

% -------------------------------------------------------------------------
function varargout = getBatch(opts, useGpu, networkType, imdb, batch)
% -------------------------------------------------------------------------
images = strcat([imdb.imageDir filesep], imdb.images.name(batch)) ;
if ~isempty(batch) && imdb.images.set(batch(1)) == 1
  phase = 'train' ;
else
  phase = 'test' ;
end
data = getImageBatch(images, opts.(phase), 'prefetch', nargout == 0) ;
if nargout > 0
  labels = imdb.images.label(batch) ;
  varargout = {data, labels} ;
end

