import torch
import time
import os
import sys

from utils import AverageMeter, calculate_accuracy


def train_epoch(epoch, data_loader, model, criterion, optimizer, opt,
                epoch_logger, batch_logger, yfcc_loader=None):
    print('train at epoch {}'.format(epoch))

    model.train()

    batch_time = AverageMeter()
    data_time = AverageMeter()
    losses = AverageMeter()
    accuracies = AverageMeter()

    end_time = time.time()
    if yfcc_loader is not None:
        yfcc_loader_iter = iter(yfcc_loader)

    for i, (inputs, targets) in enumerate(data_loader):
        data_time.update(time.time() - end_time)

        # if not opt.no_cuda:
        #     targets = targets.cuda(async=True)
        # inputs = Variable(inputs)
        # targets = Variable(targets)
        if yfcc_loader is not None:
            try:
                yfcc_inputs, yfcc_targets = next(yfcc_loader_iter)
            except StopIteration:
                yfcc_loader_iter = iter(yfcc_loader)
                yfcc_inputs, yfcc_targets = next(yfcc_loader_iter)

            inputs = torch.cat((inputs, yfcc_inputs), 0)
            targets = torch.cat((targets, yfcc_targets), 0)

        inputs = inputs.cuda()
        targets = targets.cuda()

        outputs = model(inputs)
        #print(outputs.size())
        #if opt.model == 'avts':

        loss = criterion(outputs, targets)
        acc = calculate_accuracy(outputs, targets)

        losses.update(loss.data.item(), inputs.size(0))
        accuracies.update(acc, inputs.size(0))

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        batch_time.update(time.time() - end_time)
        end_time = time.time()

        batch_logger.log({
            'epoch': epoch,
            'batch': i + 1,
            'iter': (epoch - 1) * len(data_loader) + (i + 1),
            'loss': losses.val,
            'acc': accuracies.val,
            'lr': optimizer.param_groups[0]['lr']
        })


        print('Epoch: [{0}][{1}/{2}]\t'
              'Time {batch_time.val:.3f} ({batch_time.avg:.3f})\t'
              'Data {data_time.val:.3f} ({data_time.avg:.3f})\t'
              'Loss {loss.val:.4f} ({loss.avg:.4f})\t'
              'Acc {acc.val:.3f} ({acc.avg:.3f})'.format(
                  epoch,
                  i + 1,
                  len(data_loader),
                  batch_time=batch_time,
                  data_time=data_time,
                  loss=losses,
                  acc=accuracies))

    epoch_logger.log({
        'epoch': epoch,
        'loss': losses.avg,
        'acc': accuracies.avg,
        'lr': optimizer.param_groups[0]['lr']
    })

    if epoch % opt.checkpoint == 0:
        save_file_path = os.path.join(opt.result_path,
                                      'save_{}.pth'.format(epoch))
        states = {
            'epoch': epoch + 1,
            'arch': opt.arch,
            'state_dict': model.state_dict(),
            'optimizer': optimizer.state_dict(),
        }
        torch.save(states, save_file_path)
