import argparse
import logging
import torch
import torch.nn.functional as F
import numpy as np
from torch import nn
from torch.autograd import Variable
from transformers import GPT2Config
from transformers import GPT2LMHeadModel, GPT2Tokenizer, BertTokenizer
from DataLoader import *
from Model import BERTGen
from utils import sample_sequence
import torch.optim as optim
import math
import sys
import pandas
import os
import numpy
import nltk
from torch.utils.tensorboard import SummaryWriter
import warnings
from tqdm import tqdm, trange
from torch.utils.data import RandomSampler, SequentialSampler
from torch.utils.data import DataLoader as DL
import torch
from torch.utils.data.distributed import DistributedSampler

warnings.filterwarnings("ignore", category=UserWarning)

device = torch.device('cuda')

def set_seed(args):
    np.random.seed(args.seed)
    torch.manual_seed(args.seed)
    if args.n_gpu > 0:
        torch.cuda.manual_seed_all(args.seed)


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("--model", default='gpt2', type=str)
    parser.add_argument("--top_k", type=int, default=0)
    parser.add_argument("--top_p", type=float, default=0.9)
    parser.add_argument('--seed', type=int, default=42, help="random seed for initialization")
    parser.add_argument('--do_train', default=False, action="store_true", help="whether to train or test the model")
    parser.add_argument('--do_rl', default=False, action="store_true", help="whether to train or test the model")
    parser.add_argument('--do_val', default=False, action="store_true", help="whether to train or test the model")
    parser.add_argument('--do_test', default=False, action="store_true", help="whether to compute the BLEU scores on test split")
    parser.add_argument('--do_test_challenge', default=False, action="store_true", help="whether to compute the BLEU scores on challenge split")
    parser.add_argument('--do_ppl', default=False, action="store_true", help="whether to compute perplexity of the model")
    parser.add_argument('--do_verify', default=False, action="store_true", help="whether compute the adv-acc score on test split")
    parser.add_argument('--do_verify_challenge', default=False, action="store_true", help="whether compute the adv-acc score on challenge split")
    parser.add_argument('--epoch', default=10, type=int, help="whether to train or test the model")
    parser.add_argument('--batch_size', default=6, type=int, help="whether to train or test the model")
    parser.add_argument('--local_rank', default=-1, type=int, help="whether to train or test the model")
    parser.add_argument('--learning_rate', default=2e-6, type=float, help="whether to train or test the model")
    parser.add_argument('--dataset', default='table', type=str, help="whether to train or test the model")
    parser.add_argument('--every', default=50, type=int, help="whether to train or test the model")
    parser.add_argument('--load_from', default='', type=str, help="whether to train or test the model")
    parser.add_argument('--id', default='models', type=str, help="specify the id of the experiment")
    parser.add_argument('--max_len', default=800, type=int, help="whether to train or test the model")
    parser.add_argument('--dim', default=768, type=int, help="whether to train or test the model")
    parser.add_argument('--layers', default=3, type=int, help="whether to train or test the model")
    parser.add_argument('--head', default=4, type=int, help="whether to train or test the model")
    parser.add_argument("--modelpath", type=str, default="bert-base-uncased",
                        help="For distributed training: local_rank")
    parser.add_argument('--gradient_accumulation_steps', type=int, default=5, help="accumulation steps for gradient")
    parser.add_argument('--decode_first_K', type=int, default=10000, help="For debugging purpose")
    args = parser.parse_args()

    if args.local_rank == -1:
        device = torch.device("cuda")
        args.n_gpu = 1
    else:
        torch.cuda.set_device(args.local_rank)
        device = torch.device('cuda', args.local_rank)
        torch.distributed.init_process_group(backend='nccl')
        args.n_gpu = 1
    args.device = device

    if args.local_rank not in [-1, 0]:
        torch.distributed.barrier()

    tokenizer = GPT2Tokenizer.from_pretrained(args.model)
    model = GPT2LMHeadModel.from_pretrained(args.model)
    #model = nn.DataParallel(model)
    model.to(args.device)

    if args.local_rank == 0:
        torch.distributed.barrier()

    criterion = nn.CrossEntropyLoss(reduction='none', ignore_index=-1)
    if args.do_train:
        if args.local_rank in [-1, 0]:
            if not os.path.exists(args.id):
                os.mkdir(args.id)
            tb_writer = SummaryWriter(log_dir='tensorboard/GPT2-{}'.format(args.model))
        
        dataset = GPTTableDataset2('data/train_lm_preprocessed.json', tokenizer, args.max_len)
        
        if args.local_rank == -1:
            sampler = RandomSampler(dataset)
        else:
            sampler = DistributedSampler(dataset)
        
        train_dataloader = DL(dataset, sampler=sampler, batch_size=args.batch_size, num_workers=0)

        model.train()
        optimizer = optim.Adam(model.parameters(), args.learning_rate)

        avg_loss = 0
        global_step = 0

        if args.local_rank != -1:
            model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.local_rank], 
                                        output_device=args.local_rank, find_unused_parameters=True)
        else:
            model = torch.nn.DataParallel(model)

        for epoch_idx in trange(0, args.epoch, desc='Epoch', disable=args.local_rank not in [-1, 0]):
            #for idx in range(0, dataset.train_len()):
            for idx, batch in enumerate(tqdm(train_dataloader, desc="Iteration", disable=args.local_rank not in [-1, 0])):
                batch = tuple(Variable(t).to(device) for t in batch)
                trg_inp, trg_out, mask, caption = batch
                inputs = torch.cat([caption, trg_inp], 1)

                model.zero_grad()
                optimizer.zero_grad()

                logits = model(inputs)[0]
                logits = logits[:, -trg_out.shape[1]:, :].contiguous()

                loss = criterion(logits.view(-1, logits.shape[-1]), trg_out.view(-1))

                loss = loss * mask.view(-1)
                loss = loss.sum() / mask.sum()

                avg_loss += loss.item()

                loss.backward()
                optimizer.step()
                global_step += 1

                if args.local_rank in [-1, 0] and idx % args.every == 0 and idx > 0:
                    tb_writer.add_scalar("perplexity", math.exp(avg_loss / args.every), global_step)

                    fake_inputs = caption
                    gt_inputs = trg_out.cpu().data.numpy()

                    #samples = model.sample(fake_inputs, tabfeat, caption, highlight_idx, bert)
                    samples = sample_sequence(model, 30, fake_inputs, [])
                    samples = samples[:, caption.shape[1]:]
                    samples = samples.cpu().data.numpy()

                    for s, gt in zip(samples, gt_inputs):
                        text = tokenizer.decode(s, clean_up_tokenization_spaces=True)
                        text = text[: text.find(tokenizer.eos_token)]
                        print("PREDICTION |||||| ", text)
                        text = tokenizer.decode(gt, clean_up_tokenization_spaces=True)
                        text = text[: text.find(tokenizer.eos_token)]
                        print("GROUNDTRUH |||||| ",text)
                        break

                    avg_loss = 0

            if args.local_rank in [-1, 0]:
                if args.model == 'gpt2':
                    torch.save(model.state_dict(), '{}/GPT_ep{}.pt'.format(args.id, epoch_idx))
                else:
                    torch.save(model.state_dict(), '{}/GPT_medium_ep{}.pt'.format(args.id, epoch_idx))
        
        if args.local_rank in [-1, 0]:
            tb_writer.close()
[((28, 0, 28, 55), 'warnings.filterwarnings', 'warnings.filterwarnings', (), '', False, 'import warnings\n'), ((30, 9, 30, 29), 'torch.device', 'torch.device', ({(30, 22, 30, 28): '"""cuda"""'}, {}), "('cuda')", False, 'import torch\n'), ((33, 4, 33, 29), 'numpy.random.seed', 'np.random.seed', ({(33, 19, 33, 28): 'args.seed'}, {}), '(args.seed)', True, 'import numpy as np\n'), ((34, 4, 34, 32), 'torch.manual_seed', 'torch.manual_seed', ({(34, 22, 34, 31): 'args.seed'}, {}), '(args.seed)', False, 'import torch\n'), ((40, 13, 40, 38), 'argparse.ArgumentParser', 'argparse.ArgumentParser', ({}, {}), '()', False, 'import argparse\n'), ((84, 16, 84, 57), 'transformers.GPT2Tokenizer.from_pretrained', 'GPT2Tokenizer.from_pretrained', ({(84, 46, 84, 56): 'args.model'}, {}), '(args.model)', False, 'from transformers import GPT2LMHeadModel, GPT2Tokenizer, BertTokenizer\n'), ((85, 12, 85, 55), 'transformers.GPT2LMHeadModel.from_pretrained', 'GPT2LMHeadModel.from_pretrained', ({(85, 44, 85, 54): 'args.model'}, {}), '(args.model)', False, 'from transformers import GPT2LMHeadModel, GPT2Tokenizer, BertTokenizer\n'), ((92, 16, 92, 70), 'torch.nn.CrossEntropyLoss', 'nn.CrossEntropyLoss', (), '', False, 'from torch import nn\n'), ((36, 8, 36, 45), 'torch.cuda.manual_seed_all', 'torch.cuda.manual_seed_all', ({(36, 35, 36, 44): 'args.seed'}, {}), '(args.seed)', False, 'import torch\n'), ((72, 17, 72, 37), 'torch.device', 'torch.device', ({(72, 30, 72, 36): '"""cuda"""'}, {}), "('cuda')", False, 'import torch\n'), ((75, 8, 75, 46), 'torch.cuda.set_device', 'torch.cuda.set_device', ({(75, 30, 75, 45): 'args.local_rank'}, {}), '(args.local_rank)', False, 'import torch\n'), ((76, 17, 76, 54), 'torch.device', 'torch.device', ({(76, 30, 76, 36): '"""cuda"""', (76, 38, 76, 53): 'args.local_rank'}, {}), "('cuda', args.local_rank)", False, 'import torch\n'), ((77, 8, 77, 60), 'torch.distributed.init_process_group', 'torch.distributed.init_process_group', (), '', False, 'import torch\n'), ((82, 8, 82, 35), 'torch.distributed.barrier', 'torch.distributed.barrier', ({}, {}), '()', False, 'import torch\n'), ((90, 8, 90, 35), 'torch.distributed.barrier', 'torch.distributed.barrier', ({}, {}), '()', False, 'import torch\n'), ((106, 27, 106, 98), 'torch.utils.data.DataLoader', 'DL', (), '', True, 'from torch.utils.data import DataLoader as DL\n'), ((120, 25, 120, 100), 'tqdm.trange', 'trange', (), '', False, 'from tqdm import tqdm, trange\n'), ((102, 22, 102, 44), 'torch.utils.data.RandomSampler', 'RandomSampler', ({(102, 36, 102, 43): 'dataset'}, {}), '(dataset)', False, 'from torch.utils.data import RandomSampler, SequentialSampler\n'), ((104, 22, 104, 49), 'torch.utils.data.distributed.DistributedSampler', 'DistributedSampler', ({(104, 41, 104, 48): 'dataset'}, {}), '(dataset)', False, 'from torch.utils.data.distributed import DistributedSampler\n'), ((115, 20, 116, 99), 'torch.nn.parallel.DistributedDataParallel', 'torch.nn.parallel.DistributedDataParallel', (), '', False, 'import torch\n'), ((118, 20, 118, 48), 'torch.nn.DataParallel', 'torch.nn.DataParallel', ({(118, 42, 118, 47): 'model'}, {}), '(model)', False, 'import torch\n'), ((95, 19, 95, 42), 'os.path.exists', 'os.path.exists', ({(95, 34, 95, 41): 'args.id'}, {}), '(args.id)', False, 'import os\n'), ((96, 16, 96, 33), 'os.mkdir', 'os.mkdir', ({(96, 25, 96, 32): 'args.id'}, {}), '(args.id)', False, 'import os\n'), ((122, 40, 122, 120), 'tqdm.tqdm', 'tqdm', (), '', False, 'from tqdm import tqdm, trange\n'), ((125, 25, 125, 57), 'torch.cat', 'torch.cat', ({(125, 35, 125, 53): '[caption, trg_inp]', (125, 55, 125, 56): '1'}, {}), '([caption, trg_inp], 1)', False, 'import torch\n'), ((151, 30, 151, 73), 'utils.sample_sequence', 'sample_sequence', ({(151, 46, 151, 51): 'model', (151, 53, 151, 55): '30', (151, 57, 151, 68): 'fake_inputs', (151, 70, 151, 72): '[]'}, {}), '(model, 30, fake_inputs, [])', False, 'from utils import sample_sequence\n'), ((145, 55, 145, 86), 'math.exp', 'math.exp', ({(145, 64, 145, 85): '(avg_loss / args.every)'}, {}), '(avg_loss / args.every)', False, 'import math\n'), ((123, 30, 123, 41), 'torch.autograd.Variable', 'Variable', ({(123, 39, 123, 40): 't'}, {}), '(t)', False, 'from torch.autograd import Variable\n')]