from __future__ import print_function
from tensorflow import keras
from tensorflow.keras.datasets import cifar10
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.callbacks import CSVLogger
import os
from tensorflow.keras.callbacks import ReduceLROnPlateau
from tensorflow_model_optimization.python.core.sparsity.keras import prune
from tensorflow_model_optimization.python.core.sparsity.keras import pruning_callbacks
from tensorflow_model_optimization.python.core.sparsity.keras import pruning_schedule

ConstantSparsity = pruning_schedule.ConstantSparsity
import sys
import math
sys.path.append(os.path.dirname(__file__))
from resnet18 import resnet_v1
from vgg import vgg, vgg_3x3, vgg_dep_wise_3x3

config = {
    'batch_size': 32,
    'epochs': 1000,
    'backbone': 'vgg',
    'num_classes': 10,
    'num_predictions': 20,
    'data_augmentation': True,
    'use_downsampling': True,
    'pruning': True,
    'pruning_sparsity': 0.25,
    'pruning_freq': 20,
    'pruning_begin_epoch': 200,
}

save_dir = os.path.join(os.getcwd(), 'saved_models')
model_name = 'keras_cifar10_{0}_trained_model'.format(config['backbone'])

# The data, split between train and test sets:
(x_train, y_train), (x_test, y_test) = cifar10.load_data()
# Convert class vectors to binary class matrices.
y_train = keras.utils.to_categorical(y_train, config['num_classes'])
y_test = keras.utils.to_categorical(y_test, config['num_classes'])
x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255

# model = resnet_v1(x_train.shape[1:], num_classes=config['num_classes'], use_downsampling=config['use_downsampling'])
model = vgg_3x3(x_train.shape[1:], num_classes=config['num_classes'])
model.summary()
model.save(model_name + '.h5')
# initiate Adam optimizer
opt = keras.optimizers.Adam(learning_rate=0.001, decay=1e-5)
pruning_params = {
    'pruning_schedule':
    ConstantSparsity(config['pruning_sparsity'],
                     begin_step=math.ceil(
                         float(x_train.shape[0]) * config['pruning_begin_epoch'] / config['batch_size']),
                     frequency=config['pruning_freq'])
}

if config['pruning']:
    model = prune.prune_low_magnitude(model, **pruning_params)
model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])

csv_logger = CSVLogger('training.csv')
callbacks = [csv_logger]
if config['pruning']:
    callbacks.append(pruning_callbacks.UpdatePruningStep())
    callbacks.append(pruning_callbacks.PruningSummaries(log_dir='log'))

if not config['data_augmentation']:
    print('Not using data augmentation.')
    model.fit(x_train,
              y_train,
              batch_size=config['batch_size'],
              epochs=config['epochs'],
              validation_data=(x_test, y_test),
              shuffle=True)
else:
    print('Using real-time data augmentation.')
    # This will do preprocessing and realtime data augmentation:
    datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        zca_epsilon=1e-06,  # epsilon for ZCA whitening
        rotation_range=0,  # randomly rotate images in the range (degrees, 0 to 180)
        # randomly shift images horizontally (fraction of total width)
        width_shift_range=0.1,
        # randomly shift images vertically (fraction of total height)
        height_shift_range=0.1,
        shear_range=0.,  # set range for random shear
        zoom_range=0.,  # set range for random zoom
        channel_shift_range=0.,  # set range for random channel shifts
        # set mode for filling points outside the input boundaries
        fill_mode='nearest',
        cval=0.,  # value used for fill_mode = "constant"
        horizontal_flip=True,  # randomly flip images
        vertical_flip=False,  # randomly flip images
        # set rescaling factor (applied before any other transformation)
        rescale=None,
        # set function that will be applied on each input
        preprocessing_function=None,
        # image data format, either "channels_first" or "channels_last"
        data_format=None,
        # fraction of images reserved for validation (strictly between 0 and 1)
        validation_split=0.0)

    # Compute quantities required for feature-wise normalization
    # (std, mean, and principal components if ZCA whitening is applied).
    datagen.fit(x_train)

    # Fit the model on the batches generated by datagen.flow().
    model.fit_generator(datagen.flow(x_train, y_train, batch_size=config['batch_size']),
                        epochs=config['epochs'],
                        steps_per_epoch=1000,
                        callbacks=callbacks,
                        validation_data=(x_test, y_test))

# Save model and weights
# if not os.path.isdir(save_dir):
#     os.makedirs(save_dir)
# model_path = os.path.join(save_dir, model_name)
# model.save(model_path)
# print('Saved trained model at %s ' % model_path)

# Score trained model.
# scores = model.evaluate(x_test, y_test, verbose=1)
# print('Test loss:', scores[0])
# print('Test accuracy:', scores[1])