from tqdm import tqdm
import torch
import torch.nn.functional as F



## Model training function
def train(model, device, train_loader, optimizer, train_acc, train_losses,scheduler=None):
    model.train()
    pbar = tqdm(train_loader)
    correct = 0
    processed = 0
    scheduled_lrs = []
    for batch_idx, (data, target) in enumerate(pbar):
        data = data["image"].to(device)
        target = target.to(device)

        optimizer.zero_grad()
        y_pred = model(data)

        # loss = F.nll_loss(y_pred, target)

        loss = F.cross_entropy(y_pred, target)

        train_losses.append(loss.item())

        # Backpropagation
        loss.backward()
        optimizer.step()

        pred = y_pred.argmax(dim=1, keepdim=True)
        correct += pred.eq(target.view_as(pred)).sum().item()
        processed += len(data)

        if scheduler is not None :
            scheduler.step()
            # print(scheduler.get_last_lr())
            scheduled_lrs.append(scheduler.get_last_lr())

        pbar.set_description(
            desc=f'Loss={loss.item()} Batch_id={batch_idx} Accuracy={100*correct/processed:0.2f}')
    train_acc.append(100*correct/processed)

    return scheduled_lrs


def test(model, device, test_loader, test_acc, test_losses):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data["image"].to(device), target.to(device)
            output = model(data)
            test_loss += F.cross_entropy(output,
                                         target, reduction='sum').item()
            pred = output.argmax(dim=1, keepdim=True)
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)
    test_losses.append(test_loss)

    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.2f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))

    test_acc.append(100. * correct / len(test_loader.dataset))
