import torch


def validate(model, criterion, validation_dataloader, device):
    """ Runs through validation data and calculates and returns loss and accuracy
        :param model: model to validate
        :param criterion: used to calculate model validation loss
        :param validation_dataloader: dataloader for validation data
        :param device: 'cpu' or 'gpu'

        :return: (accuracy, validation_loss) calculated accuracy for all validation data, and calculated loss for all
        validation data
    """
    # Put model in 'evaluation' mode
    model.eval()

    with torch.no_grad():
        validation_loss = 0
        accuracy = 0
        for inputs, labels in validation_dataloader:
            # Move input and label tensors to the default device
            inputs, labels = inputs.to(device), labels.to(device)
            # Forward pass
            validation_logps = model.forward(inputs)
            # Calculate loss
            loss = criterion(validation_logps, labels)
            validation_loss += loss.item()
            # Calculate accuracy
            validation_ps = torch.exp(validation_logps)
            top_p, top_class = validation_ps.topk(1, dim=1)
            equals = top_class == labels.view(*top_class.shape)
            accuracy += torch.mean(equals.type(torch.FloatTensor))

    # Put model back in 'training' mode
    model.train()

    return accuracy, validation_loss