import os
import time
import math
import random
import numpy as np
import pandas as pd
from pathlib import Path
import glob

import matplotlib.pyplot as plt
from PIL import Image, ImageEnhance, ImageOps

from tqdm import tqdm, tqdm_notebook

import torch
from torch import nn, cuda
from torch.autograd import Variable 
import torch.nn.functional as F
import torchvision as vision
import torchvision.models as models
from torch.utils.data import Dataset, DataLoader
from torch.optim import Adam, SGD, Optimizer
from torch.optim.lr_scheduler import _LRScheduler, CosineAnnealingLR, ReduceLROnPlateau

from sklearn.metrics import f1_score

class CIFAR10Policy(object):
    """ Randomly choose one of the best 25 Sub-policies on CIFAR10.
        Example:
        >>> policy = CIFAR10Policy()
        >>> transformed = policy(image)
        Example as a PyTorch Transform:
        >>> transform=transforms.Compose([
        >>>     transforms.Resize(256),
        >>>     CIFAR10Policy(),
        >>>     transforms.ToTensor()])
    """
    def __init__(self, fillcolor=(128, 128, 128)):
        self.policies = [
            SubPolicy(0.1, "invert", 7, 0.2, "contrast", 6, fillcolor),
            SubPolicy(0.7, "rotate", 2, 0.3, "translateX", 9, fillcolor),
            SubPolicy(0.8, "sharpness", 1, 0.9, "sharpness", 3, fillcolor),
            SubPolicy(0.5, "shearY", 8, 0.7, "translateY", 9, fillcolor),
            SubPolicy(0.5, "autocontrast", 8, 0.9, "equalize", 2, fillcolor),

            SubPolicy(0.2, "shearY", 7, 0.3, "posterize", 7, fillcolor),
            SubPolicy(0.4, "color", 3, 0.6, "brightness", 7, fillcolor),
            SubPolicy(0.3, "sharpness", 9, 0.7, "brightness", 9, fillcolor),
            SubPolicy(0.6, "equalize", 5, 0.5, "equalize", 1, fillcolor),
            SubPolicy(0.6, "contrast", 7, 0.6, "sharpness", 5, fillcolor),

            SubPolicy(0.7, "color", 7, 0.5, "translateX", 8, fillcolor),
            SubPolicy(0.3, "equalize", 7, 0.4, "autocontrast", 8, fillcolor),
            SubPolicy(0.4, "translateY", 3, 0.2, "sharpness", 6, fillcolor),
            SubPolicy(0.9, "brightness", 6, 0.2, "color", 8, fillcolor),
            SubPolicy(0.5, "solarize", 2, 0.0, "invert", 3, fillcolor),

            SubPolicy(0.2, "equalize", 0, 0.6, "autocontrast", 0, fillcolor),
            SubPolicy(0.2, "equalize", 8, 0.8, "equalize", 4, fillcolor),
            SubPolicy(0.9, "color", 9, 0.6, "equalize", 6, fillcolor),
            SubPolicy(0.8, "autocontrast", 4, 0.2, "solarize", 8, fillcolor),
            SubPolicy(0.1, "brightness", 3, 0.7, "color", 0, fillcolor),

            SubPolicy(0.4, "solarize", 5, 0.9, "autocontrast", 3, fillcolor),
            SubPolicy(0.9, "translateY", 9, 0.7, "translateY", 9, fillcolor),
            SubPolicy(0.9, "autocontrast", 2, 0.8, "solarize", 3, fillcolor),
            SubPolicy(0.8, "equalize", 8, 0.1, "invert", 3, fillcolor),
            SubPolicy(0.7, "translateY", 9, 0.9, "autocontrast", 1, fillcolor)
        ]


    def __call__(self, img):
        policy_idx = random.randint(0, len(self.policies) - 1)
        return self.policies[policy_idx](img)

    def __repr__(self):
        return "AutoAugment CIFAR10 Policy"


class SubPolicy(object):
    def __init__(self, p1, operation1, magnitude_idx1, p2, operation2, magnitude_idx2, fillcolor=(128, 128, 128)):
        ranges = {
            "shearX": np.linspace(0, 0.3, 10),
            "shearY": np.linspace(0, 0.3, 10),
            "translateX": np.linspace(0, 150 / 331, 10),
            "translateY": np.linspace(0, 150 / 331, 10),
            "rotate": np.linspace(0, 30, 10),
            "color": np.linspace(0.0, 0.9, 10),
            "posterize": np.round(np.linspace(8, 4, 10), 0).astype(np.int),
            "solarize": np.linspace(256, 0, 10),
            "contrast": np.linspace(0.0, 0.9, 10),
            "sharpness": np.linspace(0.0, 0.9, 10),
            "brightness": np.linspace(0.0, 0.9, 10),
            "autocontrast": [0] * 10,
            "equalize": [0] * 10,
            "invert": [0] * 10
        }

        # from https://stackoverflow.com/questions/5252170/specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand
        def rotate_with_fill(img, magnitude):
            rot = img.convert("RGBA").rotate(magnitude)
            return Image.composite(rot, Image.new("RGBA", rot.size, (128,) * 4), rot).convert(img.mode)

        func = {
            "shearX": lambda img, magnitude: img.transform(
                img.size, Image.AFFINE, (1, magnitude * random.choice([-1, 1]), 0, 0, 1, 0),
                Image.BICUBIC, fillcolor=fillcolor),
            "shearY": lambda img, magnitude: img.transform(
                img.size, Image.AFFINE, (1, 0, 0, magnitude * random.choice([-1, 1]), 1, 0),
                Image.BICUBIC, fillcolor=fillcolor),
            "translateX": lambda img, magnitude: img.transform(
                img.size, Image.AFFINE, (1, 0, magnitude * img.size[0] * random.choice([-1, 1]), 0, 1, 0),
                fillcolor=fillcolor),
            "translateY": lambda img, magnitude: img.transform(
                img.size, Image.AFFINE, (1, 0, 0, 0, 1, magnitude * img.size[1] * random.choice([-1, 1])),
                fillcolor=fillcolor),
            "rotate": lambda img, magnitude: rotate_with_fill(img, magnitude),
            # "rotate": lambda img, magnitude: img.rotate(magnitude * random.choice([-1, 1])),
            "color": lambda img, magnitude: ImageEnhance.Color(img).enhance(1 + magnitude * random.choice([-1, 1])),
            "posterize": lambda img, magnitude: ImageOps.posterize(img, magnitude),
            "solarize": lambda img, magnitude: ImageOps.solarize(img, magnitude),
            "contrast": lambda img, magnitude: ImageEnhance.Contrast(img).enhance(
                1 + magnitude * random.choice([-1, 1])),
            "sharpness": lambda img, magnitude: ImageEnhance.Sharpness(img).enhance(
                1 + magnitude * random.choice([-1, 1])),
            "brightness": lambda img, magnitude: ImageEnhance.Brightness(img).enhance(
                1 + magnitude * random.choice([-1, 1])),
            "autocontrast": lambda img, magnitude: ImageOps.autocontrast(img),
            "equalize": lambda img, magnitude: ImageOps.equalize(img),
            "invert": lambda img, magnitude: ImageOps.invert(img)
        }

        # self.name = "{}_{:.2f}_and_{}_{:.2f}".format(
        #     operation1, ranges[operation1][magnitude_idx1],
        #     operation2, ranges[operation2][magnitude_idx2])
        self.p1 = p1
        self.operation1 = func[operation1]
        self.magnitude1 = ranges[operation1][magnitude_idx1]
        self.p2 = p2
        self.operation2 = func[operation2]
        self.magnitude2 = ranges[operation2][magnitude_idx2]


    def __call__(self, img):
        if random.random() < self.p1: img = self.operation1(img, self.magnitude1)
        if random.random() < self.p2: img = self.operation2(img, self.magnitude2)
        return img
  

class TestDataset(Dataset):
    def __init__(self, df, mode='test', transforms=None):
        self.df = df
        self.mode = mode
        self.transform = transforms[self.mode]
        
    def __len__(self):
        return len(self.df)
    
    def __getitem__(self, idx):
        
        image = Image.open(TEST_IMAGE_PATH / self.df[idx]).convert("RGB")
        
        if self.transform:
            image = self.transform(image)
            
        return image
    

target_size = (224, 224)

data_transforms = {
    'test': vision.transforms.Compose([
        vision.transforms.Resize((224,224)),
        #vision.transforms.RandomResizedCrop(target_size, scale=(0.8,1.0)),
        #vision.transforms.RandomHorizontalFlip(),
        #vision.transforms.RandomRotation(20),
        #CIFAR10Policy(),
        vision.transforms.ToTensor(),
        vision.transforms.Normalize(
            [0.485, 0.456, 0.406], 
            [0.229, 0.224, 0.225])
    ]),
}
def seed_everything(seed):
    random.seed(seed)
    os.environ['PYTHONHASHSEED'] = str(seed)
    np.random.seed(seed)
    torch.manual_seed(seed)
    torch.cuda.manual_seed(seed)
    torch.backends.cudnn.deterministic = True

SEED = 2019
seed_everything(SEED)

def get_base_model(base_model, num_classes):
    if(base_model == 'ResNet-50' or base_model == 'fResNet-50' or base_model == 'gResNet-50'):
        model_ft =  models.resnet50()
        model_ft.fc = nn.Linear(2048, num_classes)
        return model_ft, (224, 224)
    elif(base_model == 'ResNext50' or base_model == 'aResNext50' or base_model == 'bResNext50' or base_model == 'hResNext50'):
        model_ft = models.resnext50_32x4d()
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs, num_classes)
        return model_ft, (224, 224)
    elif(base_model == 'ResNext101' or base_model == 'fResNext101' or base_model == 'aResNext101' or base_model == 'cResNext101' or base_model == 'dResNext101'):
        model_ft = models.resnext101_32x8d()
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs, num_classes)
        return model_ft, (224, 224)
    elif(base_model == 'ResNet-101'):
        model_ft = models.resnet101()
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs, num_classes)
        return model_ft, (224, 224)
    elif(base_model == 'VGG19-bn'):
        model_ft = models.vgg19_bn()
        num_ftrs = model_ft.classifier[6].in_features
        model_ft.classifier[6] = nn.Linear(num_ftrs, num_classes)
        return model_ft, (224, 224)   
    elif(base_model == 'aResNext101_32x8d_wsl' or base_model == 'bResNext101_32x8d_wsl' or base_model == 'cResNext101_32x8d_wsl'):
        model_ft = models.resnext101_32x8d()
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs, num_classes)
        return model_ft, (224, 224)
    elif(base_model == 'aResNext101_32x16d_wsl'):
        model_ft = torch.hub.load('facebookresearch/WSL-Images', 'resnext101_32x16d_wsl')
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs, num_classes)
        return model_ft, (224, 224)   
    
TEST_IMAGE_PATH = Path('../input/3rd-ml-month-car-image-cropping-dataset/test_crop/')
test_csv = pd.read_csv('../input/2019-3rd-ml-month-with-kakr/test.csv')
x_test = test_csv['img_file']

#target_models = ['bResNext50', 'aResNext50', 'cResNext101','fResNet-50', 'gResNet-50']
#target_folders = ['../input/bresnext50/', '../input/aresnext50/', '../input/cresnext101/', '../input/fresnet50/', '../input/gresnet50/']
#num_folder = [8, 4, 4, 4, 8]

#target_models = ['bResNext50', 'cResNext101', 'fResNet-50', 'gResNet-50']
#target_folders = ['../input/bresnext50/', '../input/cresnext101/', '../input/fresnet50/', '../input/gresnet50/']
#num_folder = [8, 4, 4, 8]

#target_models = ['bResNext50', 'aResNext50']
#target_folders = ['../input/bresnext50/', '../input/aresnext50/']



#target_models = ['bResNext50', 'aResNext50']
#target_folders = ['../input/bresnext50/', '../input/aresnext50/']


target_models = ['aResNext50', 'cResNext101_32x8d_wsl', 'bResNext101_32x8d_wsl', 'aResNext101_32x8d_wsl']
target_folders = ['../input/aresnext50/', '../input/cresnext101-32x8d-wsl/', '../input/bresnext101-32x8d-wsl/', '../input/aresnext101-32x8d-wsl/']
num_folder = [4, 12, 8, 4]



print(os.listdir('../input'))

#weights_paths = '../input/aresnext101/'
#weights_path = '../input/4f_weights/'

num_classes = 196

batch_size = 1 # 배치 1로 주면 순서대로 나온다
test_dataset = TestDataset(x_test, mode='test', transforms=data_transforms)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)

# n_fold = 4
tta = 1
all_prediction = np.zeros((len(test_dataset), num_classes))

for k, target_model in enumerate(target_models):
    
    print(target_model)
    model, _ = get_base_model(target_model, num_classes)
    model.cuda()
    
    n_fold = num_folder[k] 
    weights_path = target_folders[k]
    
    for i in range(n_fold):
        
        # print('{}th fold'.format(i))
        path = weights_path + target_model + '_weight_best_{}.pt'.format(i)
        print(path)
        model.load_state_dict(torch.load(path))
        model.eval()
        
        for _ in range(tta):
            print("tta {}".format(_+1))
            
            prediction = np.zeros((len(test_dataset), num_classes)) # num_classes=196
            with torch.no_grad():
                for i, images in enumerate(test_loader):
                    images = images.cuda()

                    preds = model(images).detach()
                    prediction[i * batch_size: (i+1) * batch_size] = preds.cpu().numpy()
            all_prediction = all_prediction + prediction

total_fold = np.sum(num_folder) * tta
#all_prediction /= (len(target_models) * n_fold * tta) 
all_prediction /= total_fold

result = np.argmax(all_prediction, axis=1)

submission = pd.read_csv('../input/2019-3rd-ml-month-with-kakr/sample_submission.csv')
submission["class"] = result
submission["class"].replace(0, 196, inplace=True)

submission.to_csv("submission.csv", index=False)
submission.head()