import torch
import torch.nn as nn
import numpy as np
from torch.autograd import Variable
import math
import time
import multiprocessing
from torch.nn.parameter import Parameter
from torch.nn.modules.module import Module
import torch.nn.functional as F
import copy

class conGraphConvolutionlayer(Module):

    def __init__(self, in_features, out_features, bias=True):
        super(conGraphConvolutionlayer, self).__init__()

        self.in_features = in_features
        self.out_features = out_features
        self.weight = Parameter(torch.FloatTensor(in_features, out_features))
        if bias:
            self.bias = Parameter(torch.FloatTensor(out_features))
        else:
            self.register_parameter('bias', None)
        self.reset_parameters()

    def reset_parameters(self):
        stdv = 1. / math.sqrt(self.weight.size(1))
        self.weight.data.uniform_(-stdv, stdv)
        if self.bias is not None:
            self.bias.data.uniform_(-stdv, stdv)

    def forward(self, input, adj):
        support = torch.mm(input, self.weight)
        output = torch.spmm(adj, support)
        if self.bias is not None:
            return output + self.bias
        else:
            return output

    def __repr__(self):
        return self.__class__.__name__ + ' (' + str(self.in_features) + ' -> ' + str(self.out_features) + ')'

class conGCN(nn.Module):
    def __init__(self, nfeat,
                 nhid,
                 common_hid_layers_num,
                 fcnn_hid_layers_num,
                 dropout,
                 nout1,
                ):
        super(conGCN, self).__init__()

        self.nfeat = nfeat
        self.nhid = nhid
        self.common_hid_layers_num = common_hid_layers_num
        self.fcnn_hid_layers_num = fcnn_hid_layers_num
        self.nout1 = nout1
        self.dropout = dropout
        self.training = True

        ## The beginning layer
        self.gc_in_exp = conGraphConvolutionlayer(nfeat, nhid)
        self.bn_node_in_exp = nn.BatchNorm1d(nhid)
        self.gc_in_sp = conGraphConvolutionlayer(nfeat, nhid)
        self.bn_node_in_sp = nn.BatchNorm1d(nhid)

        ## common_hid_layers
        if self.common_hid_layers_num > 0:
            for i in range(self.common_hid_layers_num):
                exec('self.cgc{}_exp = conGraphConvolutionlayer(nhid, nhid)'.format(i+1))
                exec('self.bn_node_chid{}_exp = nn.BatchNorm1d(nhid)'.format(i+1))
                exec('self.cgc{}_sp = conGraphConvolutionlayer(nhid, nhid)'.format(i+1))
                exec('self.bn_node_chid{}_sp = nn.BatchNorm1d(nhid)'.format(i+1))

        ## FCNN layers
        self.gc_out11 = nn.Linear(2*nhid, nhid, bias=True)
        self.bn_out1 = nn.BatchNorm1d(nhid)
        if self.fcnn_hid_layers_num > 0:
            for i in range(self.fcnn_hid_layers_num):
                exec('self.gc_out11{} = nn.Linear(nhid, nhid, bias=True)'.format(i+1))
                exec('self.bn_out11{} = nn.BatchNorm1d(nhid)'.format(i+1))
        self.gc_out12 = nn.Linear(nhid, nout1, bias=True)

    def forward(self, x, adjs):

        self.x = x

        ## input layer
        self.x_exp = self.gc_in_exp(self.x, adjs[0])
        self.x_exp = self.bn_node_in_exp(self.x_exp)
        self.x_exp = F.elu(self.x_exp)
        self.x_exp = F.dropout(self.x_exp, self.dropout, training=self.training)
        self.x_sp = self.gc_in_sp(self.x, adjs[1])
        self.x_sp = self.bn_node_in_sp(self.x_sp)
        self.x_sp = F.elu(self.x_sp)
        self.x_sp = F.dropout(self.x_sp, self.dropout, training=self.training)

        ## common layers
        if self.common_hid_layers_num > 0:
            for i in range(self.common_hid_layers_num):
                exec("self.x_exp = self.cgc{}_exp(self.x_exp, adjs[0])".format(i+1))
                exec("self.x_exp = self.bn_node_chid{}_exp(self.x_exp)".format(i+1))
                self.x_exp = F.elu(self.x_exp)
                self.x_exp = F.dropout(self.x_exp, self.dropout, training=self.training)
                exec("self.x_sp = self.cgc{}_sp(self.x_sp, adjs[1])".format(i+1))
                exec("self.x_sp = self.bn_node_chid{}_sp(self.x_sp)".format(i+1))
                self.x_sp = F.elu(self.x_sp)
                self.x_sp = F.dropout(self.x_sp, self.dropout, training=self.training)

        ## FCNN layers
        self.x1 = torch.cat([self.x_exp, self.x_sp], dim=1)
        self.x1 = self.gc_out11(self.x1)
        self.x1 = self.bn_out1(self.x1)
        self.x1 = F.elu(self.x1)
        self.x1 = F.dropout(self.x1, self.dropout, training=self.training)
        if self.fcnn_hid_layers_num > 0:
            for i in range(self.fcnn_hid_layers_num):
                exec("self.x1 = self.gc_out11{}(self.x1)".format(i+1))
                exec("self.x1 = self.bn_out11{}(self.x1)".format(i+1))
                self.x1 = F.elu(self.x1)
                self.x1 = F.dropout(self.x1, self.dropout, training=self.training)
        self.x1 = self.gc_out12(self.x1)

        gc_list = {}
        gc_list['gc_in_exp'] = self.gc_in_exp
        gc_list['gc_in_sp'] = self.gc_in_sp
        if self.common_hid_layers_num > 0:
            for i in range(self.common_hid_layers_num):
                exec("gc_list['cgc{}_exp'] = self.cgc{}_exp".format(i+1, i+1))
                exec("gc_list['cgc{}_sp'] = self.cgc{}_sp".format(i+1, i+1))
        gc_list['gc_out11'] = self.gc_out11
        if self.fcnn_hid_layers_num > 0:
            exec("gc_list['gc_out11{}'] =  self.gc_out11{}".format(i+1, i+1))
        gc_list['gc_out12'] = self.gc_out12

        return F.log_softmax(self.x1, dim=1), gc_list

def conGCN_train(model,
                 train_valid_len,
                 test_len,
                 feature,
                 adjs,
                 label,
                 epoch_n,
                 loss_fn,
                 optimizer,
                 train_valid_ratio = 0.9,
                 scheduler = None,
                 early_stopping_patience = 5,
                 clip_grad_max_norm = 1,
                 load_test_groundtruth = False,
                 print_epoch_step = 1,
                 cpu_num = -1,
                 GCN_device = 'CPU'
                ):

    if GCN_device == 'CPU':
        device = torch.device("cpu")
        print('Use CPU as device.')
    else:
        if torch.cuda.is_available():
            device = torch.device("cuda")
            print('Use GPU as device.')
        else:
            device = torch.device("cpu")
            print('Use CPU as device.')

    if cpu_num == -1:
        cores = multiprocessing.cpu_count()
        torch.set_num_threads(cores)
    else:
        torch.set_num_threads(cpu_num)

    model = model.to(device)
    adjs = [adj.to(device) for adj in adjs]
    feature = feature.to(device)
    label = label.to(device)

    time_open = time.time()

    train_idx = range(int(train_valid_len*train_valid_ratio))
    valid_idx = range(len(train_idx), train_valid_len)

    best_val = np.inf
    clip = 0
    loss = []
    para_list = []
    for epoch in range(epoch_n):
        try:
            torch.cuda.empty_cache()
        except:
            pass

        optimizer.zero_grad()
        output1, paras = model(feature.float(), adjs)

        loss_train1 = loss_fn(output1[list(np.array(train_idx)+test_len)], label[list(np.array(train_idx)+test_len)].float())
        loss_val1 = loss_fn(output1[list(np.array(valid_idx)+test_len)], label[list(np.array(valid_idx)+test_len)].float())
        if load_test_groundtruth == True:
            loss_test1 = loss_fn(output1[:test_len], label[:test_len].float())
            loss.append([loss_train1.item(), loss_val1.item(), loss_test1.item()])
        else:
            loss.append([loss_train1.item(), loss_val1.item(), None])

        if epoch % print_epoch_step == 0:
            print("******************************************")
            print("Epoch {}/{}".format(epoch+1, epoch_n),
                  'loss_train: {:.4f}'.format(loss_train1.item()),
                  'loss_val: {:.4f}'.format(loss_val1.item()),
                  end = '\t'
                 )
            if load_test_groundtruth == True:
                print("Test loss= {:.4f}".format(loss_test1.item()), end = '\t')
            print('time: {:.4f}s'.format(time.time() - time_open))
        para_list.append(paras.copy())
        for i in paras.keys():
            para_list[-1][i] = copy.deepcopy(para_list[-1][i])

        if early_stopping_patience > 0:
            if torch.round(loss_val1, decimals=4) < best_val:
                best_val = torch.round(loss_val1, decimals=4)
                best_paras = paras.copy()
                best_loss = loss.copy()
                clip = 1
                for i in paras.keys():
                    best_paras[i] = copy.deepcopy(best_paras[i])
            else:
                clip += 1
                if clip == early_stopping_patience:
                    break
        else:
            best_loss = loss.copy()
            best_paras = None

        loss_train1.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=clip_grad_max_norm)
        optimizer.step()
        if scheduler != None:
            try:
                scheduler.step()
            except:
                scheduler.step(metrics = loss_val1)

    print("***********************Final Loss***********************")
    print("Epoch {}/{}".format(epoch+1, epoch_n),
          'loss_train: {:.4f}'.format(loss_train1.item()),
          'loss_val: {:.4f}'.format(loss_val1.item()),
          end = '\t'
         )
    if load_test_groundtruth == True:
        print("Test loss= {:.4f}".format(loss_test1.item()), end = '\t')
    print('time: {:.4f}s'.format(time.time() - time_open))

    torch.cuda.empty_cache()

    return output1.cpu(), loss, model.cpu()


from setuptools import setup, find_packages
from os import path

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, 'README.md'), encoding='utf-8') as f:
    long_description = f.read()

setup(
    name='pysnippets',
    version='0.1.0',
    description='Scattered python snippets',
    long_description=long_description,
    long_description_content_type='text/markdown',
    url='https://github.com/gongzhitaao/snippets/pysnippets',
    author='gongzhitaao',
    author_email='zhitaao.gong@gmail.com',
    classifiers=[
        'Development Status :: 3 - Alpha',
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Build Tools',
        'License :: OSI Approved :: MIT License',
        'Programming Language :: Python :: 3',
    ],
    keywords='snippets',
    packages=find_packages(exclude=['contrib', 'docs', 'tests']),
    install_requires=[],
    extras_require={
        'test': ['pytest'],
    },
)


import torch
import torch.nn as nn
import torch.utils.data as Data
from torch.autograd import Variable
import torch.nn.functional as F
import time
import numpy as np
assert F
import csv,random
from imblearn.over_sampling import SMOTE

class Datamanager():
    def __init__(self):
        self.dataset = {}
        self.threshold = 1e-10              ## The threshold for classifying the label of the sample after simoid function
        self.normalize = {}                 ## Record the mean and standard deviation for testing set normalization

        self.over_sample = False            ## Determine whether copy the positive (attribute #4 = 2) samples
        self.over_sample_rate = 150         ## Number of copies

        self.down_sample = False            ## Determine whether delete the negative (attribute #4 = 1) samples
        self.down_sample_rate = 2           ## Make number of negative samples = down_sample_rate * Number of positive samples

        self.smote = True                   ## Determine whether use SMOTE to generate minor class samples    source: https://imbalanced-learn.org/en/stable/generated/imblearn.over_sampling.SMOTE.html

        self.weighted_loss = True           ## Determine whether adjust the weight of BCE loss function
        self.weighted_loss_rate = 0.1       ## Weight of negative samples in loss function ( 1 - weighted_loss_rate for positive samples)

    def get_data(self,name,file_name,b_size,args,shuf=True):
        with open(file_name,newline='') as csvfile:
            rows = csv.reader(csvfile)                                                          ## Read file
            data = []                                                                           ## Store the data from file
            for row in rows:
                data.append(row)
            data = data[2:]

            data = np.array(data)
            if name == 'train' :
                data = np.delete(data,2,0)                                                      ## Missing data => remove
                data = np.delete(data,0,0)                                                      ## Positive(attribute #4 = 2) outlier => remove
                data = np.delete(data,4,0)
                data = np.delete(data,5,0)
                data = np.delete(data,6,0)
                data = np.delete(data,11,0)
                data = np.delete(data,2542,0)
            if name == 'test' :
                data = np.delete(data,1103,0)
                data = np.delete(data,1102,0)
                data = np.delete(data,699,0)
                data = np.delete(data,5,0)

            data = np.delete(data,176,1)                                                        ## These columns have std = 0 => remove
            data = np.delete(data,167,1)
            data = np.delete(data,166,1)
            data = np.delete(data,165,1)
            data = np.delete(data,5,1)
            data = np.delete(data,4,1)
            data = np.delete(data,166,1)
            data = np.delete(data,165,1)
            data = np.delete(data,164,1)

            for i in range(data.shape[1]):
                if i == 3 :
                    for j in range(data.shape[0]):                                              ## Transform label of attribute #4 '2' to 1(positive), '1' to 0(negative)
                        if data[j][i] == '1':
                            data[j][i] = 0
                        elif data[j][i] == '2':
                            data[j][i] = 1
                        else:
                            print('error target')
                elif data[0][i] == 'TRUE' or data[0][i] == 'FALSE': #                           ## Transform label 'TRUE' to 1, 'Negative' to 0
                    for j in range(data.shape[0]):
                        if data[j][i] == 'TRUE':
                            data[j][i] = 1.0
                        elif data[j][i] == 'FALSE':
                            data[j][i] = 0.0
                        else:
                            print(j,i,data[j][i])
                            print('other type')
                    mean = data[:,i].astype(np.double).mean()                                   ## Normalization. Record mean and standard deviation
                    std = data[:,i].astype(np.double).std()
                    if(std == 0):
                        print(i)
                    data[:,i] = (data[:,i].astype(np.double) - mean) / std
                    self.normalize[i] = [mean,std]

                else:
                    if name == 'train':                                                         ## Normalization. Record mean and standard deviation
                        mean = data[:,i].astype(np.double).mean()
                        std = data[:,i].astype(np.double).std()
                        if(std == 0):
                            print(i)
                        data[:,i] = (data[:,i].astype(np.double) - mean) / std
                        self.normalize[i] = [mean,std]
                    else:
                        data[:,i] = (data[:,i].astype(np.double) - self.normalize[i][0]) / self.normalize[i][1]

            if name == 'train' :
                np.random.shuffle(data)
                Y = data[:int(data.shape[0]*0.9),3].reshape(-1,1).astype(np.double)             ## Split training and validation set, and extract attribute #4 as targets
                Y_val = data[int(data.shape[0]*0.9):,3].reshape(-1,1).astype(np.double)
                X = np.delete(data,3,1).astype(np.double)[:int(data.shape[0]*0.9),:]
                X_val = np.delete(data,3,1).astype(np.double)[int(data.shape[0]*0.9):,:]

                if self.over_sample or self.down_sample or self.smote:
                    count_0 = 0
                    count_1 = 0
                    count_1_list = []
                    for i in range(Y.shape[0]):
                        if Y[i][0] == 0:
                            count_0 = count_0 + 1
                        else:
                            count_1 = count_1 + 1
                            count_1_list.append(i)
                    print('count_0:',count_0)
                    print('count_1:',count_1)
                    if self.over_sample:                                                        ## Copy the positive (attribute #4 = 2) samples
                        ori_one_X , ori_one_Y = X[count_1_list] , Y[count_1_list]
                        for i in range(self.over_sample_rate):
                            noise = np.random.normal(0, 0.3, ori_one_X.shape)
                            add_one_X = ori_one_X + noise
                            X = np.concatenate((X,add_one_X),axis = 0)
                            Y = np.concatenate((Y,ori_one_Y),axis = 0)

                    if self.down_sample:                                                        ## Delete the negative (attribute #4 = 1) samples
                        number = int(count_0 - count_1 * (self.over_sample_rate + 1) * self.down_sample_rate)
                        while(number > 0):
                            for i in range(Y.shape[0]):
                                if Y[i][0] == 0:
                                    X = np.delete(X,i,0)
                                    Y = np.delete(Y,i,0)
                                    number = number - 1
                                    break
                    if self.smote:                                                              ## Use SMOTE to generate minor class(positive) samples
                        sm = SMOTE(sampling_strategy = 1)
                        X, Y = sm.fit_resample(X, Y)
                        Y = Y.reshape(-1,1)
                        count_0 = 0
                        count_1 = 0
                        for i in range(Y.shape[0]):
                            if Y[i][0] == 0:
                                count_0 = count_0 + 1
                            else:
                                count_1 = count_1 + 1
                        #print('count_0:',count_0)
                        #print('count_1:',count_1)

                #print(X.shape)
                #print(Y.shape)

                X,Y = torch.from_numpy(X).cuda(),torch.from_numpy(Y).cuda()                                         ## Convert numpy array to tensor for Pytorch
                train_dataset = Data.TensorDataset(data_tensor=X[:], target_tensor=Y[:])                            ## Wrap up the input/target tensor into TensorDataset   source: https://pytorch.org/docs/stable/data.html
                self.dataset['train'] = Data.DataLoader(dataset=train_dataset, batch_size=b_size, shuffle=shuf)     ## Put the TensorDataset in Dataloader (stored in a dictionary), shuffling the samples    source: https://pytorch.org/docs/stable/data.html

                X_val,Y_val = torch.from_numpy(X_val).cuda(),torch.from_numpy(Y_val).cuda()
                val_dataset = Data.TensorDataset(data_tensor=X_val[:], target_tensor=Y_val[:])
                self.dataset['val'] = Data.DataLoader(dataset=val_dataset, batch_size=b_size, shuffle=shuf)

            elif name == 'test':                                                                                ## Process the testing set
                Y = data[:,3].reshape(-1,1).astype(np.double)
                X = np.delete(data,3,1).astype(np.double)

                X,Y = torch.from_numpy(X).cuda(),torch.from_numpy(Y).cuda()
                train_dataset = Data.TensorDataset(data_tensor=X[:], target_tensor=Y[:])
                self.dataset['test'] = Data.DataLoader(dataset=train_dataset, batch_size=b_size, shuffle=shuf)      ## Put the TensorDataset in Dataloader (stored in a dictionary), not shuffling the samples    source: https://pytorch.org/docs/stable/data.html

    def train(self,model,trainloader,epoch):                                            ## Train the model
        model.train()                                                                   ## Set to training mode
        optimizer = torch.optim.Adam(model.parameters())                                ## Use Adam optimizer to optimize all DNN parameters    source: https://pytorch.org/docs/stable/optim.html
        loss_func = nn.BCELoss()                                                        ## Use binary cross entropoy for model evaluation       source: https://pytorch.org/docs/stable/nn.html
        total_loss = 0                                                                  ## Calculate total loss in a epoch
        t1_p1 = 0                                                                       ## Confusion matrix initialization
        t1_p0 = 0
        t0_p1 = 0
        t0_p0 = 0

        for batch_index, (x, y) in enumerate(trainloader):                              ## Process a batch of data in each timestep
            x, y= Variable(x).cuda(), Variable(y).cuda()
            output = model(x)                                                           ## Use present model to forecast the the result
            if self.weighted_loss:                                                      ## Adjust the weight of BCE loss functional             source: https://pytorch.org/docs/stable/nn.html
                weight = np.empty([len(x)])
                for i in range(len(x)):
                    weight[i] = self.weighted_loss_rate
                arr = np.where(y.data == 1)
                weight[arr[0].tolist()] = 1 - self.weighted_loss_rate
                weight = torch.from_numpy(weight).cuda().double().view(len(x),1)
                loss_func = nn.BCELoss(weight = weight)

            loss = loss_func(output,y)
            optimizer.zero_grad()                                                       ## Set the gradient in the previous time step to zero
            loss.backward()                                                             ## Back propagate    source: https://pytorch.org/docs/stable/optim.html
            optimizer.step()                                                            ## Gradient descent    source: https://pytorch.org/docs/stable/autograd.html
            if batch_index % 4 == 0:                                                    ## Print model status    source: https://pytorch.org/docs/stable/optim.html
                print('\rTrain Epoch: {} | [{}/{} ({:.0f}%)]\t '.format(
                        epoch, batch_index * len(x), len(trainloader.dataset),
                        100. * batch_index / len(trainloader)),end='')

            total_loss+= loss.data[0]*len(x)                                            ## Sum up batch loss

            pred = np.empty([len(x),1])                                                 ## Calculate confusion matrix
            output = output.cpu().data.numpy()

            for i in range(len(x)):
                if(output[i] > self.threshold):
                    pred[i,0] = 1
                else:
                    pred[i,0] = 0

            y = y.cpu().data.numpy()

            for i in range(pred.shape[0]):
                if y[i] == 1 and pred[i] == 1:
                    t1_p1 = t1_p1 + 1
                elif y[i] == 1 and pred[i] == 0:
                    t1_p0 = t1_p0 + 1
                elif y[i] == 0 and pred[i] == 1:
                    t0_p1 = t0_p1 + 1
                elif y[i] == 0 and pred[i] == 0:
                    t0_p0 = t0_p0 + 1

        total_loss/= len(trainloader.dataset)
        print('Total loss: {:.4f}'.format(total_loss))                                  ## Print model status
        print('t1_p1: ',t1_p1 , 't0_p1: ',t0_p1 )
        print('t1_p0: ',t1_p0 , 't0_p0: ',t0_p0 )
        return total_loss

    def val(self,model,name,valloader):                                                 ## Test the model
        model.eval()                                                                    ## Set to evaluation mode
        val_loss = 0                                                                    ## Calculate total loss
        t1_p1 = 0                                                                       ## Confusion matrix initialization
        t1_p0 = 0
        t0_p1 = 0
        t0_p0 = 0
        for x, y in valloader:
            x, y = Variable(x, volatile=True).cuda(), Variable(y,volatile=True).cuda()
            output = model(x)                                                           ## Use present model to forecast the the result
            val_loss += F.binary_cross_entropy(output, y, size_average=False).data[0]   ## Sum up batch loss

            pred = np.empty([len(x),1])                                                 ## Calculate confusion matrix
            output = output.cpu().data.numpy()
            for i in range(len(x)):
                if(output[i] > self.threshold):
                    pred[i,0] = 1
                else:
                    pred[i,0] = 0
            y = y.cpu().data.numpy()

            for i in range(pred.shape[0]):
                if y[i] == 1 and pred[i] == 1:
                    t1_p1 = t1_p1 + 1
                elif y[i] == 1 and pred[i] == 0:
                    t1_p0 = t1_p0 + 1
                elif y[i] == 0 and pred[i] == 1:
                    t0_p1 = t0_p1 + 1
                elif y[i] == 0 and pred[i] == 0:
                    t0_p0 = t0_p0 + 1

        val_loss /= len(valloader.dataset)
        print(name , ' set: Average loss: {:.4f}'.format(val_loss))                     ## Print model status
        print('t1_p1: ',t1_p1 , 't0_p1: ',t0_p1 )
        print('t1_p0: ',t1_p0 , 't0_p0: ',t0_p0 )
        return val_loss

class DNN(nn.Module):                                                                   ## Set up DNN
    def __init__(self,args):
        super(DNN, self).__init__()
        print(args.unit)
        self.den=nn.ModuleList()
        for i in range(1,len(args.unit)-1):                                             ## Set up hidden layers
            self.den.append( nn.Sequential(
                nn.Linear(args.unit[i-1], args.unit[i]),                                ## Source: https://pytorch.org/docs/stable/nn.html
                nn.ReLU(),
                nn.Dropout(0.2)
            ))
        self.den.append( nn.Sequential(
            nn.Linear(args.unit[-2], args.unit[-1]),
            nn.Dropout(0.2),
            nn.Sigmoid(),
        ))

    def forward(self, x):                                                               ## Connect layers and activation function
        for i in self.den:
            x = i(x)
        return x


# ported from uniborg thanks to @s_n_a_p_s , @r4v4n4 ,  @spechide and @PhycoNinja13b
#:::::Credit Time::::::
# 1) Coded By: @s_n_a_p_s
# 2) Ported By: @r4v4n4 (Noodz Lober)
# 3) End Game Help By: @spechide
# 4) Better Colour Profile Pic By @PhycoNinja13b

import asyncio
import base64
import os
import random
import shutil
import time
from datetime import datetime

from PIL import Image, ImageDraw, ImageFont
from pySmartDL import SmartDL
from telethon.errors import FloodWaitError
from telethon.tl import functions

from . import AUTONAME, BOTLOG, BOTLOG_CHATID, DEFAULT_BIO
from .sql_helper.globals import addgvar, delgvar, gvarstatus

DEFAULTUSERBIO = DEFAULT_BIO or " рЌ»рЌЕрЈєрјбрЈєрЉјрЈђ рЈърЈєрЈдрЌ┤ рјбрЈєрЌ░рЌ┤  "
CHANGE_TIME = Config.CHANGE_TIME
DEFAULTUSER = AUTONAME or Config.ALIVE_NAME

FONT_FILE_TO_USE = "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf"

autopic_path = os.path.join(os.getcwd(), "userbot", "original_pic.png")
digitalpic_path = os.path.join(os.getcwd(), "userbot", "digital_pic.png")
autophoto_path = os.path.join(os.getcwd(), "userbot", "photo_pfp.png")

digitalpfp = Config.DIGITAL_PIC or "https://telegra.ph/file/aeaebe33b1f3988a0b690.jpg"

@bot.on(admin_cmd(pattern="autopic ?(.*)"))
async def autopic(event):
    if event.fwd_from:
        return
    if Config.DEFAULT_PIC is None:
        return await edit_delete(
            event,
            "**Error**\nFor functing of autopic you need to set DEFAULT_PIC var in Heroku vars",
            parse_mode=parse_pre,
        )
    downloader = SmartDL(Config.DEFAULT_PIC, autopic_path, progress_bar=False)
    downloader.start(blocking=False)
    while not downloader.isFinished():
        pass
    input_str = event.pattern_match.group(1)
    if input_str:
        try:
            input_str = int(input_str)
        except ValueError:
            input_str = 60
    else:
        if gvarstatus("autopic_counter") is None:
            addgvar("autopic_counter", 30)
    if gvarstatus("autopic") is not None and gvarstatus("autopic") == "true":
        return await edit_delete(event, f"`Autopic is already enabled`")
    addgvar("autopic", True)
    if input_str:
        addgvar("autopic_counter", input_str)
    await edit_delete(event, f"`Autopic has been started by my Master`")
    await autopicloop()

@bot.on(admin_cmd(pattern="digitalpfp$"))
async def main(event):
    if event.fwd_from:
        return
    downloader = SmartDL(digitalpfp, digitalpic_path, progress_bar=False)
    downloader.start(blocking=False)
    while not downloader.isFinished():
        pass
    if gvarstatus("digitalpic") is not None and gvarstatus("digitalpic") == "true":
        return await edit_delete(event, f"`Digitalpic is already enabled`")
    addgvar("digitalpic", True)
    await edit_delete(event, f"`digitalpfp has been started by my Master`")
    await digitalpicloop()

@bot.on(admin_cmd(pattern="bloom$"))
async def autopic(event):
    if event.fwd_from:
        return
    if Config.DEFAULT_PIC is None:
        return await edit_delete(
            event,
            "**Error**\nFor functing of bloom you need to set DEFAULT_PIC var in Heroku vars",
            parse_mode=parse_pre,
        )
    downloader = SmartDL(Config.DEFAULT_PIC, autopic_path, progress_bar=True)
    downloader.start(blocking=False)
    while not downloader.isFinished():
        pass
    if gvarstatus("bloom") is not None and gvarstatus("bloom") == "true":
        return await edit_delete(event, f"`Bloom is already enabled`")
    addgvar("bloom", True)
    await edit_delete(event, f"`Bloom has been started by my Master`")
    await bloom_pfploop()

@bot.on(admin_cmd(pattern="autoname$"))
async def _(event):
    if event.fwd_from:
        return
    if gvarstatus("autoname") is not None and gvarstatus("autoname") == "true":
        return await edit_delete(event, f"`Autoname is already enabled`")
    addgvar("autoname", True)
    await edit_delete(event, "`AutoName has been started by my Master `")
    await autoname_loop()

@bot.on(admin_cmd(pattern="autobio$"))
async def _(event):
    if event.fwd_from:
        return
    if gvarstatus("autobio") is not None and gvarstatus("autobio") == "true":
        return await edit_delete(event, f"`Autobio is already enabled`")
    addgvar("autobio", True)
    await edit_delete(event, "`Autobio has been started by my Master `")
    await autobio_loop()

@bot.on(admin_cmd(pattern="end (.*)"))
async def _(event):  # sourcery no-metrics
    if event.fwd_from:
        return
    input_str = event.pattern_match.group(1)
    if input_str == "autopic":
        if gvarstatus("autopic") is not None and gvarstatus("autopic") == "true":
            delgvar("autopic")
            if os.path.exists(autopic_path):
                file = await event.client.upload_file(autopic_path)
                try:
                    await event.client(functions.photos.UploadProfilePhotoRequest(file))
                    os.remove(autopic_path)
                except BaseException:
                    return
            return await edit_delete(event, "`Autopic has been stopped now`")
        return await edit_delete(event, "`Autopic haven't enabled`")
    if input_str == "digitalpfp":
        if gvarstatus("digitalpic") is not None and gvarstatus("digitalpic") == "true":
            delgvar("digitalpic")
            await event.client(
                functions.photos.DeletePhotosRequest(
                    await bot.get_profile_photos("me", limit=1)
                )
            )
            return await edit_delete(event, "`Digitalpfp has been stopped now`")
        return await edit_delete(event, "`Digitalpfp haven't enabled`")
    if input_str == "bloom":
        if gvarstatus("bloom") is not None and gvarstatus("bloom") == "true":
            delgvar("bloom")
            if os.path.exists(autopic_path):
                file = await event.client.upload_file(autopic_path)
                try:
                    await event.client(functions.photos.UploadProfilePhotoRequest(file))
                    os.remove(autopic_path)
                except BaseException:
                    return
            return await edit_delete(event, "`Bloom has been stopped now`")
        return await edit_delete(event, "`Bloom haven't enabled`")
    if input_str == "autoname":
        if gvarstatus("autoname") is not None and gvarstatus("autoname") == "true":
            delgvar("autoname")
            await event.client(
                functions.account.UpdateProfileRequest(first_name=DEFAULTUSER)
            )
            return await edit_delete(event, "`Autoname has been stopped now`")
        return await edit_delete(event, "`Autoname haven't enabled`")
    if input_str == "autobio":
        if gvarstatus("autobio") is not None and gvarstatus("autobio") == "true":
            delgvar("autobio")
            await event.client(
                functions.account.UpdateProfileRequest(about=DEFAULTUSERBIO)
            )
            return await edit_delete(event, "`Autobio has been stopped now`")
        return await edit_delete(event, "`Autobio haven't enabled`")

async def autopicloop():
    AUTOPICSTART = gvarstatus("autopic") == "true"
    if AUTOPICSTART and Config.DEFAULT_PIC is None:
        if BOTLOG:
            return await bot.send_message(
                BOTLOG_CHATID,
                "**Error**\n`For functing of autopic you need to set DEFAULT_PIC var in Heroku vars`",
            )
        return
    if gvarstatus("autopic") is not None:
        try:
            counter = int(gvarstatus("autopic_counter"))
        except Exception as e:
            LOGS.warn(str(e))
    while AUTOPICSTART:
        if not os.path.exists(autopic_path):
            downloader = SmartDL(Config.DEFAULT_PIC, autopic_path, progress_bar=False)
            downloader.start(blocking=False)
            while not downloader.isFinished():
                pass
        shutil.copy(autopic_path, autophoto_path)
        im = Image.open(autophoto_path)
        file_test = im.rotate(counter, expand=False).save(autophoto_path, "PNG")
        current_time = datetime.now().strftime("  Time: %H:%M \n  Date: %d.%m.%y ")
        img = Image.open(autophoto_path)
        drawn_text = ImageDraw.Draw(img)
        fnt = ImageFont.truetype(FONT_FILE_TO_USE, 30)
        drawn_text.text((150, 250), current_time, font=fnt, fill=(124, 252, 0))
        img.save(autophoto_path)
        file = await bot.upload_file(autophoto_path)
        try:
            await bot(functions.photos.UploadProfilePhotoRequest(file))
            os.remove(autophoto_path)
            counter += counter
            await asyncio.sleep(CHANGE_TIME)
        except BaseException:
            return
        AUTOPICSTART = gvarstatus("autopic") == "true"

async def digitalpicloop():
    DIGITALPICSTART = gvarstatus("digitalpic") == "true"
    i = 0
    while DIGITALPICSTART:
        if not os.path.exists(digitalpic_path):
            downloader = SmartDL(digitalpfp, digitalpic_path, progress_bar=False)
            downloader.start(blocking=False)
            while not downloader.isFinished():
                pass
        shutil.copy(digitalpic_path, autophoto_path)
        Image.open(autophoto_path)
        current_time = datetime.now().strftime("%H:%M")
        img = Image.open(autophoto_path)
        drawn_text = ImageDraw.Draw(img)
        cat = str(base64.b64decode("dXNlcmJvdC9oZWxwZXJzL3N0eWxlcy9kaWdpdGFsLnR0Zg=="))[
            2:36
        ]
        fnt = ImageFont.truetype(cat, 200)
        drawn_text.text((350, 100), current_time, font=fnt, fill=(124, 252, 0))
        img.save(autophoto_path)
        file = await bot.upload_file(autophoto_path)
        try:
            if i > 0:
                await bot(
                    functions.photos.DeletePhotosRequest(
                        await bot.get_profile_photos("me", limit=1)
                    )
                )
            i += 1
            await bot(functions.photos.UploadProfilePhotoRequest(file))
            os.remove(autophoto_path)
            await asyncio.sleep(CHANGE_TIME)
        except BaseException:
            return
        DIGITALPICSTART = gvarstatus("digitalpic") == "true"

async def bloom_pfploop():
    BLOOMSTART = gvarstatus("bloom") == "true"
    if BLOOMSTART and Config.DEFAULT_PIC is None:
        if BOTLOG:
            return await bot.send_message(
                BOTLOG_CHATID,
                "**Error**\n`For functing of bloom you need to set DEFAULT_PIC var in Heroku vars`",
            )
        return
    while BLOOMSTART:
        if not os.path.exists(autopic_path):
            downloader = SmartDL(Config.DEFAULT_PIC, autopic_path, progress_bar=False)
            downloader.start(blocking=False)
            while not downloader.isFinished():
                pass
        # RIP Danger zone Here no editing here plox
        R = random.randint(0, 256)
        B = random.randint(0, 256)
        G = random.randint(0, 256)
        FR = 256 - R
        FB = 256 - B
        FG = 256 - G
        shutil.copy(autopic_path, autophoto_path)
        image = Image.open(autophoto_path)
        image.paste((R, G, B), [0, 0, image.size[0], image.size[1]])
        image.save(autophoto_path)
        current_time = datetime.now().strftime("\n Time: %H:%M:%S \n \n Date: %d/%m/%y")
        img = Image.open(autophoto_path)
        drawn_text = ImageDraw.Draw(img)
        fnt = ImageFont.truetype(FONT_FILE_TO_USE, 60)
        ofnt = ImageFont.truetype(FONT_FILE_TO_USE, 250)
        drawn_text.text((95, 250), current_time, font=fnt, fill=(FR, FG, FB))
        drawn_text.text((95, 250), "      Ъўѕ", font=ofnt, fill=(FR, FG, FB))
        img.save(autophoto_path)
        file = await bot.upload_file(autophoto_path)
        try:
            await bot(functions.photos.UploadProfilePhotoRequest(file))
            os.remove(autophoto_path)
            await asyncio.sleep(CHANGE_TIME)
        except BaseException:
            return
        BLOOMSTART = gvarstatus("bloom") == "true"

async def autoname_loop():
    AUTONAMESTART = gvarstatus("autoname") == "true"
    while AUTONAMESTART:
        DM = time.strftime("%d-%m-%y")
        HM = time.strftime("%H:%M")
        name = f"Рїџ№ИЈ {HM}||Рђ║  {DEFAULTUSER} Рђ╣||ЪЊЁ {DM}"
        LOGS.info(name)
        try:
            await bot(functions.account.UpdateProfileRequest(first_name=name))
        except FloodWaitError as ex:
            LOGS.warning(str(ex))
            await asyncio.sleep(ex.seconds)
        await asyncio.sleep(CHANGE_TIME)
        AUTONAMESTART = gvarstatus("autoname") == "true"

async def autobio_loop():
    AUTOBIOSTART = gvarstatus("autobio") == "true"
    while AUTOBIOSTART:
        DMY = time.strftime("%d.%m.%Y")
        HM = time.strftime("%H:%M:%S")
        bio = f"ЪЊЁ {DMY} | {DEFAULTUSERBIO} | Рїџ№ИЈ {HM}"
        LOGS.info(bio)
        try:
            await bot(functions.account.UpdateProfileRequest(about=bio))
        except FloodWaitError as ex:
            LOGS.warning(str(ex))
            await asyncio.sleep(ex.seconds)
        await asyncio.sleep(CHANGE_TIME)
        AUTOBIOSTART = gvarstatus("autobio") == "true"

bot.loop.create_task(autopicloop())
bot.loop.create_task(digitalpicloop())
bot.loop.create_task(bloom_pfploop())
bot.loop.create_task(autoname_loop())
bot.loop.create_task(autobio_loop())

CMD_HELP.update(
    {
        "autoprofile": """**Plugin : **`autoprofile`

Рђб  **Syntax : **`.autopic angle`
Рђб  **Function : **__Rotating image along with the time on it with given angle if no angle is given then doesnt rotate. You need to set __`DEFAULT_PIC`__ in heroku__

Рђб  **Syntax : **`.digitalpfp`
Рђб  **Function : **__Your profile pic changes to digitaltime profile picutre__

Рђб  **Syntax : **`.bloom`
Рђб  **Function : **__Random colour profile pics will be set along with time on it. You need to set__ `DEFAULT_PIC`__ in heroku__

Рђб  **Syntax : **`.autoname`
Рђб  **Function : **__for time along with name, you must set __`AUTONAME`__ in the heroku vars first for this to work__

Рђб  **Syntax : **`.autobio`
Рђб  **Function : **__for time along with your bio, Set __`DEFAULT_BIO`__ in the heroku vars first__

Рђб  **Syntax : **`.end function`
Рђб  **Function : **__To stop the given functions like autopic ,difitalpfp , bloom , autoname and autobio__

**Рџа№ИЈDISCLAIMERРџа№ИЈ**
__USING THIS PLUGIN CAN RESULT IN ACCOUNT BAN. WE ARE NOT RESPONSIBLE FOR YOUR BAN.__
"""
    }
)


'''
This module provides management methods for the pygame screen
'''

import sys
import pygame

class MetaGame(type):
    '''
    the metaclass for the game class - this implements classproperties on Game
    '''
    @property
    def clock(cls):
        '''
        produce the game clock
        '''
        return cls._clock

    @property
    def screen(cls):
        '''
        get the game screen
        '''
        return cls._screen

    @screen.setter
    def screen(cls, value):
        '''
        set the game screen
        '''
        cls._screen = value

    @property
    def scenes(cls):
        '''
        get the game scene stack
        '''
        return cls._scenes

    @scenes.setter
    def scenes(cls, value):
        '''
        set the game scene stack
        '''
        cls._scenes = value

class Game(object, metaclass=MetaGame):
    '''
    manage the pygame screen
    '''
    _clock = None
    _screen = None
    _scenes = None

    _fps_unlocked = False
    _max_fps = 0

    @classmethod
    def init(cls, title='pygame', max_fps=0):
        '''
        initialize pygame and some other important things
        '''
        # initialize pygame
        pygame.mixer.pre_init(channels=1)
        pygame.init()

        # set window caption
        pygame.display.set_caption(title)

        # initialize game clock
        cls._clock = pygame.time.Clock()
        cls._max_fps = max_fps

    @classmethod
    def main(cls):
        '''
        start the main loop of the game
        '''
        while cls._scenes:
            # get the scene on top of the scene stack
            scene = cls._scenes.peek()

            try:
                # process events
                for event in pygame.event.get():
                    if event.type == pygame.QUIT:
                        return
                    elif event.type == pygame.VIDEORESIZE:
                        cls.screen.resize(event.dict['size'])
                    elif event.type == pygame.KEYUP and event.key == pygame.K_HASH:
                        cls.screen.debug.enabled = not cls.screen.debug.enabled
                    elif event.type == pygame.KEYUP and event.key == pygame.K_EXCLAIM:
                        cls._fps_unlocked = not cls._fps_unlocked
                    else:
                        scene.on_event(event)

                # update the scenegraph objects and redraw
                scene.update()

                # flip the buffers at the given maximum refresh rate
                cls.screen.flip()
                cls._clock.tick(0 if cls._fps_unlocked else cls._max_fps)
            except StopIteration:
                Game.scenes.pop()

    @classmethod
    def quit(cls):
        '''
        quit the game
        '''
        sys.exit(0)


from torch.utils.data import *
from imutils import paths
import numpy as np
import random
import cv2
import os

CHARS = ['京', '沪', '津', '渝', '冀', '晋', '蒙', '辽', '吉', '黑',
         '苏', '浙', '皖', '闽', '赣', '鲁', '豫', '鄂', '湘', '粤',
         '桂', '琼', '川', '贵', '云', '藏', '陕', '甘', '青', '宁',
         '新',
         '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
         'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K',
         'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V',
         'W', 'X', 'Y', 'Z', 'I', 'O', '-'
         ]

CHARS_DICT = {char:i for i, char in enumerate(CHARS)}

class LPRDataLoader(Dataset):
    def __init__(self, img_dir, imgSize, lpr_max_len, PreprocFun=None):
        self.img_dir = img_dir
        self.img_paths = []
        for i in range(len(img_dir)):
            self.img_paths += [el for el in paths.list_images(img_dir[i])]
        random.shuffle(self.img_paths)
        self.img_size = imgSize
        self.lpr_max_len = lpr_max_len
        if PreprocFun is not None:
            self.PreprocFun = PreprocFun
        else:
            self.PreprocFun = self.transform

    def __len__(self):
        return len(self.img_paths)

    def __getitem__(self, index):
        filename = self.img_paths[index]
        Image = cv2.imread(filename)
        height, width, _ = Image.shape
        if height != self.img_size[1] or width != self.img_size[0]:
            Image = cv2.resize(Image, self.img_size)
        Image = self.PreprocFun(Image)

        basename = os.path.basename(filename)
        imgname, suffix = os.path.splitext(basename)
        imgname = imgname.split("-")[0].split("_")[0]
        label = list()
        for c in imgname:
            # one_hot_base = np.zeros(len(CHARS))
            # one_hot_base[CHARS_DICT[c]] = 1
            label.append(CHARS_DICT[c])

        if len(label) == 8:
            if self.check(label) == False:
                print(imgname)
                assert 0, "Error label ^~^!!!"

        return Image, label, len(label)

    def transform(self, img):
        img = img.astype('float32')
        img -= 127.5
        img *= 0.0078125
        img = np.transpose(img, (2, 0, 1))

        return img

    def check(self, label):
        if label[2] != CHARS_DICT['D'] and label[2] != CHARS_DICT['F'] \
                and label[-1] != CHARS_DICT['D'] and label[-1] != CHARS_DICT['F']:
            print("Error label, Please check!")
            return False
        else:
            return True


import json

def json_read(path):
    '''
    Parses file of json type.
    '''
    with open(path, 'r', encoding='utf-8') as f:
        text = json.load(f)
    return text

def conti_with_count():
    names = json_read('names.json')
    continents = json_read('continent.json')
    result = {}
    for country in continents:
        if continents[country] not in result:
            result[continents[country]] = [names[country]]
        else:
            result[continents[country]].append(names[country])

    return result


from __future__ import absolute_import, division, print_function

import os
from setuptools import setup

HERE = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(HERE, 'README.rst')) as f:
    README = f.read()

setup(name='marv',
      version='3.2.0',
      description='MARV framework',
      long_description=README,
      classifiers=[
          'Development Status :: 5 - Production/Stable',
          "Framework :: Flask",
          'License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)',
          'Operating System :: POSIX :: Linux',  # for now
          'Programming Language :: Python',
          'Programming Language :: Python :: 2.7',
          'Programming Language :: Python :: 2 :: Only',  # for now
          'Programming Language :: Python :: Implementation :: CPython',  # for now
          "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
          'Topic :: Scientific/Engineering',
      ],
      author='Ternaris',
      author_email='team@ternaris.com',
      url='https://github.com/ternaris/marv',
      license='AGPLv3+',
      packages=[
          'marv',
          'marv.app',
          'marv.tests',
          'marv_detail',
          'marv_node',
          'marv_node.testing',
          'marv_node.tests',
          'marv_nodes',
          'marv_pycapnp',
          'marv_pycapnp.tests',
          'marv_store',
          'marv_webapi',
      ],
      include_package_data=True,
      zip_safe=False,
      test_suite='nose.collector',
      tests_require=['nose'],
      install_requires=['Flask-Cors',
                        'Flask-SQLAlchemy',
                        'PyJWT',
                        'bcrypt',
                        'configparser',
                        'cython',
                        'Jinja2>=2.7.3',
                        'requests-oauthlib',
                        'pycapnp-for-marv',
                        'marv-cli'],
      extras_require={
          'testing': ['coverage',
                      'ipdb',
                      'ipdbplugin',
                      'ipython',
                      'mock',
                      'nose',
                      'testfixtures'],
      },
      entry_points={'marv_cli': ['marv = marv.cli']})