import numpy as np
import tensorflow as tf
import os

import sys
sys.path.append('..')
import platform

# if platform.system()=='Windows':
#     from importlib import reload
# else:
#     reload(sys)
#     sys.setdefaultencoding('utf8')

from utilities.utils import image_show
import random as rnd
import time
import multiprocessing as multi_thread
print_separator = "#################################################################"
from tensorflow.python.client import device_lib
import copy as cpy
import scipy.misc as misc

STANDARD_GRAYSCALE_THRESHOLD_VALUE = 240
ALTERNATE_GRAYSCALE_LOW=170
ALTERNATE_GRAYSCALE_HGH=250

# data_reading_mode = 'Full'
data_reading_mode = 'Partial'

GRAYSCALE_AVG = 127.5
class Dataset(object):
    def __init__(self,
                 data_list,
                 label0_list,label1_list,
                 sorted_by_label0,
                 print_marks,
                 info_print_interval):
        self.data_list = data_list
        self.label0_list = label0_list
        self.label1_list = label1_list

        if sorted_by_label0:
            self.sorted_data_by_label0(print_marks=print_marks,
                                       info_print_interval=info_print_interval)

    def sorted_data_by_label0(self,print_marks,info_print_interval):
        print(print_separator)
        label0_vec = np.unique(self.label0_list)
        sorted_data_list=list()
        sorted_label0_list = list()
        sorted_label1_list = list()

        sort_start = time.time()
        counter=0
        for label0 in label0_vec:
            found_indices = [ii for ii in range(len(self.label0_list)) if self.label0_list[ii] == label0]
            for ii in found_indices:
                sorted_data_list.append(self.data_list[ii])
                sorted_label0_list.append(self.label0_list[ii])
                sorted_label1_list.append(self.label1_list[ii])

            if time.time()-sort_start > info_print_interval or label0==label0_vec[0] or counter == len(label0_vec)-1:
                print(print_marks+'SortingForLabel0:%d/%d' % (counter+1,len(label0_vec)))
                sort_start=time.time()
            counter+=1
        self.data_list = sorted_data_list
        self.label0_list = sorted_label0_list
        self.label1_list = sorted_label1_list
        print(print_separator)

class Dataset_Iterator(object):
    def __init__(self,thread_num,
                 batch_size,style_input_num,
                 input_width, input_channel,
                 true_style,
                 style_reference_list,content_prototype_list,loss_style_reference_list,
                 info_print_interval,print_marks,content_input_number_actual,max_style_reference_loss_num,
                 augment=False,augment_flip=False,
                 label0_vec=-1,label1_vec=-1,debug_mode=False,
                 ):

        if "Validation" in print_marks:
            self.iterator_mode = "validate"
        elif "Train" in print_marks:
            self.iterator_mode = "train"

        self.debug_mode = debug_mode
        self.batch_size = batch_size
        self.input_width = input_width
        self.input_filters = input_channel
        self.true_style = true_style
        self.style_reference_list = style_reference_list
        self.loss_style_reference_list = loss_style_reference_list
        self.max_style_reference_loss_num=max_style_reference_loss_num

        self.thread_num = thread_num
        self.augment = augment
        self.augment_flip=augment_flip
        self.style_input_num = style_input_num
        self.content_input_num = len(content_prototype_list)
        self.content_input_number_actual=content_input_number_actual

        self.label0_vec = np.unique(self.true_style.label0_list)
        self.label1_vec = np.unique(self.true_style.label1_list)
        if debug_mode:
            self.label0_vec = np.concatenate([range(176161, 176191),
                                              range(0, 3725)], axis=0)
            self.label1_vec = range(1001, 1051)
            self.label0_vec = list(map(str, self.label0_vec))
            self.label1_vec = list(map(str, self.label1_vec))
        else:
            self.label0_vec = self.label0_vec.tolist()
            self.label1_vec = self.label1_vec.tolist()

        self.content_data_list_alignment_with_true_style_data(content_prototype_list=content_prototype_list,
                                                              print_marks=print_marks,
                                                              info_print_interval=info_print_interval)

    def content_data_list_alignment_with_true_style_data(self, content_prototype_list, print_marks,info_print_interval):

        find_start = time.time()
        label0_counter = 0
        content_data_list = list()
        content_label0_list = list()
        content_label1_list = list()
        for ii in range(len(content_prototype_list)):
            content_data_list.append(cpy.deepcopy(self.true_style.data_list))
            content_label0_list.append(cpy.deepcopy(self.true_style.label0_list))
            content_label1_list.append(cpy.deepcopy(self.true_style.label1_list))

        label0_vec = np.unique(self.true_style.label0_list)
        delete_label_add = 0
        delete_data_add = 0
        print(print_separator)
        for label0 in label0_vec:
            current_label0_indices_on_the_style_data = [ii for ii in range(len(self.true_style.label0_list)) if self.true_style.label0_list[ii] == label0]

            valid_label0 = True
            for kk in range(len(content_prototype_list)):
                if not label0 in content_prototype_list[kk].label0_list:
                    valid_label0 = False
                    break
            if not valid_label0:
                delete_label_add += 1
                delete_data_add += len(current_label0_indices_on_the_style_data)
                delete_counter = 0
                current_label0_indices_on_the_style_data.sort()
                for iii in current_label0_indices_on_the_style_data:
                    del self.true_style.label0_list[iii-delete_counter]
                    del self.true_style.label1_list[iii-delete_counter]
                    del self.true_style.data_list[iii-delete_counter]
                    for jjj in range(len(self.style_reference_list)):
                        del self.style_reference_list[jjj].label0_list[iii-delete_counter]
                        del self.style_reference_list[jjj].label1_list[iii-delete_counter]
                        del self.style_reference_list[jjj].data_list[iii-delete_counter]
                    for jjj in range(len(self.loss_style_reference_list)):
                        del self.loss_style_reference_list[jjj].label0_list[iii - delete_counter]
                        del self.loss_style_reference_list[jjj].label1_list[iii - delete_counter]
                        del self.loss_style_reference_list[jjj].data_list[iii - delete_counter]
                    for jjj in range(len(content_prototype_list)):
                        del content_data_list[jjj][iii - delete_counter]
                        del content_label0_list[jjj][iii - delete_counter]
                        del content_label1_list[jjj][iii - delete_counter]
                    delete_counter += 1

                continue
            else:
                current_label0_indices_on_the_style_data = [ii for ii in range(len(self.true_style.label0_list)) if self.true_style.label0_list[ii] == label0]
                for ii in range(len(content_prototype_list)):
                    current_prototype_dataset = content_prototype_list[ii]
                    current_label0_index_on_the_content_prototype_data = current_prototype_dataset.label0_list.index(label0)
                    for jj in current_label0_indices_on_the_style_data:
                        content_data_list[ii][jj] = current_prototype_dataset.data_list[current_label0_index_on_the_content_prototype_data]
                        content_label0_list[ii][jj] = current_prototype_dataset.label0_list[current_label0_index_on_the_content_prototype_data]
                        content_label1_list[ii][jj] = current_prototype_dataset.label1_list[current_label0_index_on_the_content_prototype_data]

                if time.time() - find_start > info_print_interval or label0 == label0_vec[0] or label0_counter == len(label0_vec) - 1:
                    print(print_marks + ' FindingCorrespondendingContentPrototype_BasedOnLabel0:%d/%d' %
                          (label0_counter + 1, len(label0_vec)))
                    print(print_marks + ' Deleted %d Label0s with %d samples' %
                          (delete_label_add, delete_data_add))
                    find_start = time.time()
            label0_counter += 1

        self.content_prototype_list = content_prototype_list
        for ii in range(len(content_prototype_list)):
            self.content_prototype_list[ii].data_list = content_data_list[ii]
            self.content_prototype_list[ii].label0_list = content_label0_list[ii]
            self.content_prototype_list[ii].label1_list = content_label1_list[ii]

    def reproduce_dataset_lists(self, info, shuffle,info_print_interval):
        if shuffle:
            old_content_prototype_list = cpy.deepcopy(self.content_prototype_list)
            old_true_style_data_list = self.true_style.data_list
            old_true_style_label0_list = self.true_style.label0_list
            old_true_style_label1_list = self.true_style.label1_list

            self.true_style.data_list=list()
            self.true_style.label0_list=list()
            self.true_style.label1_list=list()
            for ii in range(self.content_input_num):
                self.content_prototype_list[ii].data_list = list()
                self.content_prototype_list[ii].label0_list = list()
                self.content_prototype_list[ii].label1_list = list()

            indices_shuffled = np.random.permutation(len(old_true_style_data_list))
            for ii in indices_shuffled:
                self.true_style.data_list.append(old_true_style_data_list[ii])
                self.true_style.label0_list.append(old_true_style_label0_list[ii])
                self.true_style.label1_list.append(old_true_style_label1_list[ii])
                for jj in range(self.content_input_num):
                    self.content_prototype_list[jj].data_list.append(old_content_prototype_list[jj].data_list[ii])
                    self.content_prototype_list[jj].label0_list.append(old_content_prototype_list[jj].label0_list[ii])
                    self.content_prototype_list[jj].label1_list.append(old_content_prototype_list[jj].label1_list[ii])

        time_start = time.time()
        label1_vec = np.unique(self.true_style.label1_list)
        label1_counter = 0
        for label1 in label1_vec:
            found_indices = [ii for ii in range(len(self.true_style.label1_list)) if self.true_style.label1_list[ii] == label1]
            for jj in range(self.style_input_num):
                current_new_indices = rnd.sample(found_indices, len(found_indices))
                for kk in range(len(found_indices)):
                    self.style_reference_list[jj].data_list[found_indices[kk]] = \
                        self.true_style.data_list[current_new_indices[kk]]
                    self.style_reference_list[jj].label0_list[found_indices[kk]] = \
                        self.true_style.label0_list[current_new_indices[kk]]
                    self.style_reference_list[jj].label1_list[found_indices[kk]] = \
                        self.true_style.label1_list[current_new_indices[kk]]

            for jj in range(len(self.loss_style_reference_list)):
                current_new_indices = rnd.sample(found_indices, len(found_indices))
                for kk in range(len(found_indices)):
                    self.loss_style_reference_list[jj].data_list[found_indices[kk]] = \
                        self.true_style.data_list[current_new_indices[kk]]
                    self.loss_style_reference_list[jj].label0_list[found_indices[kk]] = \
                        self.true_style.label0_list[current_new_indices[kk]]
                    self.loss_style_reference_list[jj].label1_list[found_indices[kk]] = \
                        self.true_style.label1_list[current_new_indices[kk]]

            label1_counter += 1
            if time.time() - time_start > info_print_interval or label1_counter == len(
                    label1_vec) or label1_counter == 1:
                time_start = time.time()
                print('%s:DatasetReInitialization@CurrentLabel1:%d/%d(Label1:%s);' % (info, label1_counter, len(label1_vec), label1))

    def iterator_reset(self,sess):
        sess.run(self.true_style_iterator.initializer,
                 feed_dict={self.true_style_data_list_input_op:self.true_style.data_list,
                            self.true_style_label0_list_input_op:self.true_style.label0_list,
                            self.true_style_label1_list_input_op:self.true_style.label1_list})
        for ii in range(self.content_input_num):
            sess.run(self.prototype_iterator_list[ii].initializer,
                     feed_dict={self.prototype_data_list_input_op_list[ii]:self.content_prototype_list[ii].data_list,
                                self.prototype_label0_list_input_op_list[ii]:self.content_prototype_list[ii].label0_list,
                                self.prototype_label1_list_input_op_list[ii]:self.content_prototype_list[ii].label1_list})
        for ii in range(self.style_input_num):
            sess.run(self.reference_iterator_list[ii].initializer,
                     feed_dict={self.reference_data_list_input_op_list[ii]: self.style_reference_list[ii].data_list,
                                self.reference_label0_list_input_op_list[ii]: self.style_reference_list[ii].label0_list,
                                self.reference_label1_list_input_op_list[ii]: self.style_reference_list[ii].label1_list})
        for ii in range(len(self.loss_style_reference_list)):
            sess.run(self.loss_reference_iterator_list[ii].initializer,
                     feed_dict={self.loss_reference_data_list_input_op_list[ii]: self.loss_style_reference_list[ii].data_list,
                                self.loss_reference_label0_list_input_op_list[ii]: self.loss_style_reference_list[ii].label0_list,
                                self.loss_reference_label1_list_input_op_list[ii]: self.loss_style_reference_list[
                                    ii].label1_list})

    def create_dataset_op(self, discrminator_label1_vec):
        def _get_tensor_slice():
            data_list_placeholder = tf.placeholder(dtype=tf.string, shape=[None])
            label0_list_placeholder = tf.placeholder(dtype=tf.string, shape=[None])
            label1_list_placeholder = tf.placeholder(dtype=tf.string, shape=[None])
            dataset_op = tf.data.Dataset.from_tensor_slices((data_list_placeholder,
                                                             label0_list_placeholder,
                                                             label1_list_placeholder))
            return dataset_op, \
                   data_list_placeholder, \
                   label0_list_placeholder, label1_list_placeholder

        def _parser_func(file_list,label0_list,label1_list):
            image_string = tf.read_file(file_list)
            image_decoded = tf.image.decode_image(contents=image_string, channels=1)
            image_resized = tf.image.resize_image_with_crop_or_pad(image_decoded, self.input_width, self.input_width)
            img_output = tf.slice(image_resized,
                                  [0, 0, 0],
                                  [self.input_width, self.input_width, self.input_filters])
            return img_output, label0_list, label1_list

        def _convert_label_to_one_hot(dense_label,voc):
            table = tf.contrib.lookup.index_table_from_tensor(mapping=voc, default_value=0)
            encoded = tf.one_hot(table.lookup(dense_label),len(voc), dtype=tf.float32)
            return encoded

        def _random_thickness(input_tensor, fixed_mask):
            if fixed_mask:
                mask_tensor = STANDARD_GRAYSCALE_THRESHOLD_VALUE * \
                              tf.ones(shape=input_tensor.shape,
                                      dtype=input_tensor.dtype)
                threshold = mask_tensor
            else:
                threshold_v = tf.random_uniform(shape=[int(input_tensor.shape[0]), 1, 1, 1],
                                                minval=ALTERNATE_GRAYSCALE_LOW,
                                                maxval=ALTERNATE_GRAYSCALE_HGH,
                                                dtype=tf.float32)
                threshold = tf.tile(threshold_v, [1, int(input_tensor.shape[1]), int(input_tensor.shape[2]), 1])
                mask_tensor = tf.multiply(threshold,
                                          tf.ones(shape=input_tensor.shape,
                                                  dtype=input_tensor.dtype))

            condition = tf.greater_equal(input_tensor, mask_tensor)
            output_tensor = tf.where(condition,
                                     tf.ones_like(input_tensor),
                                     tf.zeros_like(input_tensor))
            return output_tensor, threshold

        # for true style image
        true_style_dataset, \
        true_style_data_list_input_op, \
        true_style_label0_list_input_op, true_style_label1_list_input_op = \
            _get_tensor_slice()

        if data_reading_mode == 'Full':
            reading_num_parallel_calls = self.thread_num - 1
        elif data_reading_mode == 'Partial':
            reading_num_parallel_calls = 4
        if self.debug_mode:
            reading_num_parallel_calls = 1
        if self.iterator_mode == "validate":
            reading_num_parallel_calls = 1

        true_style_dataset = \
            true_style_dataset.map(map_func=_parser_func,
                                   num_parallel_calls=reading_num_parallel_calls).apply(tf.contrib.data.batch_and_drop_remainder(self.batch_size)).repeat(-1)
        true_style_iterator = true_style_dataset.make_initializable_iterator()
        true_style_img_tensor, true_style_label0_tensor_dense, true_style_label1_tensor_dense = \
            true_style_iterator.get_next()
        true_style_img_tensor = tf.cast(true_style_img_tensor,tf.float32)
        true_style_img_tensor, true_style_threshold = \
            _random_thickness(input_tensor=true_style_img_tensor,
                              fixed_mask=True)

        self.true_style_iterator = true_style_iterator
        self.true_style_data_list_input_op = true_style_data_list_input_op
        self.true_style_label0_list_input_op = true_style_label0_list_input_op
        self.true_style_label1_list_input_op = true_style_label1_list_input_op

        # for prototype images
        prototype_iterator_list = list()
        prototype_data_list_input_op_list = list()
        prototype_label0_list_input_op_list = list()
        prototype_label1_list_input_op_list = list()
        content_prototype_threshold = list()
        for ii in range(self.content_input_num):
            prototype_dataset, \
            prototype_data_list_input_op, \
            prototype_label0_list_input_op, prototype_label1_list_input_op = \
                _get_tensor_slice()
            prototype_dataset = \
                prototype_dataset.map(map_func=_parser_func,
                                      num_parallel_calls=reading_num_parallel_calls).apply(tf.contrib.data.batch_and_drop_remainder(self.batch_size)).repeat(-1)

            prototype_iterator = prototype_dataset.make_initializable_iterator()
            prototype_img_tensor, prototype_label0_tensor, prototype_label1_tensor = prototype_iterator.get_next()

            # data augment for content prototype w.r.t. random thickness
            prototype_img_tensor = tf.cast(prototype_img_tensor, tf.float32)
            prototype_img_tensor, current_threshold = \
                _random_thickness(input_tensor=prototype_img_tensor,
                                  fixed_mask=(self.augment == False))
            content_prototype_threshold.append(current_threshold)

            prototype_iterator_list.append(prototype_iterator)
            prototype_data_list_input_op_list.append(prototype_data_list_input_op)
            prototype_label0_list_input_op_list.append(prototype_label0_list_input_op)
            prototype_label1_list_input_op_list.append(prototype_label1_list_input_op)

            if ii == 0:
                all_prototype_tensor = prototype_img_tensor
            else:
                all_prototype_tensor = tf.concat([all_prototype_tensor,prototype_img_tensor], axis=3)
        all_prototype_tensor = tf.cast(all_prototype_tensor, tf.float32)

        if not self.content_input_number_actual == 0:
            for ii in range(self.batch_size):
                current_prototype = tf.expand_dims(all_prototype_tensor[ii,:,:,:],axis=0)
                selected_indices = tf.random_uniform(shape=[self.content_input_number_actual, 1], minval=0,maxval=self.content_input_num,dtype=tf.int64)
                current_prototype_swapped = tf.transpose(current_prototype,[3,0,1,2])
                current_selected_prototype_swapped = tf.expand_dims(tf.squeeze(tf.nn.embedding_lookup(current_prototype_swapped,selected_indices)),axis=1)
                current_selected_prototype = tf.transpose(current_selected_prototype_swapped, [1, 2, 3, 0])
                if ii == 0:
                    all_prototype_tensor_new = current_selected_prototype
                else:
                    all_prototype_tensor_new = tf.concat([all_prototype_tensor_new,current_selected_prototype],axis=0)
            all_prototype_tensor = all_prototype_tensor_new
        else:
            self.content_input_number_actual = self.content_input_num

        self.prototype_iterator_list = prototype_iterator_list
        self.prototype_data_list_input_op_list = prototype_data_list_input_op_list
        self.prototype_label0_list_input_op_list = prototype_label0_list_input_op_list
        self.prototype_label1_list_input_op_list = prototype_label1_list_input_op_list

        # for reference images
        reference_iterator_list = list()
        reference_data_list_input_op_list = list()
        reference_label0_list_input_op_list = list()
        reference_label1_list_input_op_list = list()
        style_reference_threshold = list()
        for ii in range(self.style_input_num):
            reference_dataset, \
            reference_data_list_input_op, \
            reference_label0_list_input_op, reference_label1_list_input_op = \
                _get_tensor_slice()

            reference_dataset = reference_dataset.map(map_func=_parser_func,
                                                      num_parallel_calls=reading_num_parallel_calls).apply(tf.contrib.data.batch_and_drop_remainder(self.batch_size)).repeat(-1)
            reference_iterator = reference_dataset.make_initializable_iterator()
            reference_img_tensor, reference_label0_tensor, reference_label1_tensor = reference_iterator.get_next()

            # data augment for reference style w.r.t. random thickness
            reference_img_tensor = tf.cast(reference_img_tensor, tf.float32)
            reference_img_tensor, current_threshold = \
                _random_thickness(input_tensor=reference_img_tensor,
                                  fixed_mask=(self.augment == False))
            style_reference_threshold.append(current_threshold)

            reference_iterator_list.append(reference_iterator)
            reference_data_list_input_op_list.append(reference_data_list_input_op)
            reference_label0_list_input_op_list.append(reference_label0_list_input_op)
            reference_label1_list_input_op_list.append(reference_label1_list_input_op)

            if ii == 0:
                all_reference_tensor = reference_img_tensor
            else:
                all_reference_tensor = tf.concat([all_reference_tensor, reference_img_tensor], axis=3)
        all_reference_tensor = tf.cast(all_reference_tensor, tf.float32)

        self.reference_iterator_list=reference_iterator_list
        self.reference_data_list_input_op_list = reference_data_list_input_op_list
        self.reference_label0_list_input_op_list = reference_label0_list_input_op_list
        self.reference_label1_list_input_op_list = reference_label1_list_input_op_list

        true_style_label0_tensor_onehot =_convert_label_to_one_hot(dense_label=true_style_label0_tensor_dense,
                                                                   voc=self.label0_vec)
        true_style_label1_tensor_onehot =_convert_label_to_one_hot(dense_label=true_style_label1_tensor_dense,
                                                                   voc=discrminator_label1_vec)

        # for loss style references
        if len(self.loss_style_reference_list)==self.max_style_reference_loss_num:
            loss_reference_iterator_list = list()
            loss_reference_data_list_input_op_list = list()
            loss_reference_label0_list_input_op_list = list()
            loss_reference_label1_list_input_op_list = list()
            loss_style_reference_threshold = list()
            for ii in range(len(self.loss_style_reference_list)):
                loss_reference_dataset, \
                loss_reference_data_list_input_op, \
                loss_reference_label0_list_input_op, loss_reference_label1_list_input_op = \
                    _get_tensor_slice()

                loss_reference_dataset = loss_reference_dataset.map(map_func=_parser_func,
                                                                    num_parallel_calls=reading_num_parallel_calls).apply(tf.contrib.data.batch_and_drop_remainder(self.batch_size)).repeat(-1)
                loss_reference_iterator = loss_reference_dataset.make_initializable_iterator()
                loss_reference_img_tensor, loss_reference_label0_tensor, loss_reference_label1_tensor = loss_reference_iterator.get_next()

                # data augment for reference style w.r.t. random thickness
                loss_reference_img_tensor = tf.cast(loss_reference_img_tensor, tf.float32)
                loss_reference_img_tensor, loss_current_threshold = \
                    _random_thickness(input_tensor=loss_reference_img_tensor,
                                      fixed_mask=(self.augment == False))
                loss_style_reference_threshold.append(loss_current_threshold)

                loss_reference_iterator_list.append(loss_reference_iterator)
                loss_reference_data_list_input_op_list.append(loss_reference_data_list_input_op)
                loss_reference_label0_list_input_op_list.append(loss_reference_label0_list_input_op)
                loss_reference_label1_list_input_op_list.append(loss_reference_label1_list_input_op)

                if ii == 0:
                    all_loss_reference_tensor = loss_reference_img_tensor
                else:
                    all_loss_reference_tensor = tf.concat([all_loss_reference_tensor, loss_reference_img_tensor],
                                                          axis=3)
            all_loss_reference_tensor = tf.cast(all_loss_reference_tensor, tf.float32)
            self.loss_reference_iterator_list = loss_reference_iterator_list
            self.loss_reference_data_list_input_op_list = loss_reference_data_list_input_op_list
            self.loss_reference_label0_list_input_op_list = loss_reference_label0_list_input_op_list
            self.loss_reference_label1_list_input_op_list = loss_reference_label1_list_input_op_list
        else:
            all_loss_reference_tensor = -1

        # data augmentation
        if self.augment:
            # image random translation
            img_all = tf.concat([true_style_img_tensor, all_prototype_tensor, all_reference_tensor], axis=3)
            if len(self.loss_style_reference_list) == self.max_style_reference_loss_num:
                img_all = tf.concat([img_all,all_loss_reference_tensor], axis=3)
            img_all_shape = img_all.shape
            for ii in range(self.batch_size):
                current_img = img_all[ii, :, :, :]
                crop_size = tf.random_uniform(shape=[],
                                              minval=int(int(img_all.shape[1])*0.75),
                                              maxval=int(img_all.shape[1])+1, dtype=tf.int32)
                cropped_img = tf.random_crop(value=current_img,
                                             size=[crop_size, crop_size, int(img_all.shape[3])])
                cropped_img = tf.image.resize_images(cropped_img, [self.input_width, self.input_width])
                cropped_img = tf.reshape(cropped_img, [int(img_all_shape[1]), int(img_all_shape[2]),int(img_all_shape[3])])
                cropped_img = tf.expand_dims(cropped_img, axis=0)
                if ii == 0:
                    img_all_new = cropped_img
                else:
                    img_all_new = tf.concat([img_all_new, cropped_img], axis=0)
            img_all = img_all_new

            if self.augment_flip:
                for ii in range(self.batch_size):
                    flip_img = tf.expand_dims(tf.image.random_flip_up_down(tf.image.random_flip_left_right(img_all[ii,:,:,:])),axis=0)
                    threshold = tf.random_uniform(shape=[], minval=0, maxval=1,dtype=tf.float32)
                    transpose_flip = lambda: tf.transpose(flip_img,[0,2,1,3])
                    non_transpose_flip = lambda : flip_img
                    flip_img = tf.case([(tf.less(threshold, 0.5), transpose_flip)], default=non_transpose_flip)

                    if ii == 0:
                        img_all_new = flip_img
                    else:
                        img_all_new = tf.concat([img_all_new, flip_img],axis=0)
                img_all = img_all_new

            true_style_img_tensor = tf.expand_dims(img_all[:,:,:,0],axis=3)
            all_prototype_tensor = img_all[:,:,:,1:int(all_prototype_tensor.shape[3])+1]
            if len(self.loss_style_reference_list) == 0:
                all_reference_tensor = img_all[:,:,:,int(all_prototype_tensor.shape[3])+1:]
                all_loss_reference_tensor = -1
            elif len(self.loss_style_reference_list) == self.max_style_reference_loss_num:
                all_reference_tensor = img_all[:, :, :,
                                       int(all_prototype_tensor.shape[3]) + 1:
                                       int(all_prototype_tensor.shape[3]) + self.style_input_num + 1 ]
                all_loss_reference_tensor = img_all[:,:,:,
                                            int(all_prototype_tensor.shape[3]) + self.style_input_num + 1:]

        true_style_img_tensor = (true_style_img_tensor - 0.5) * 2
        all_prototype_tensor = (all_prototype_tensor - 0.5) * 2
        all_reference_tensor = (all_reference_tensor - 0.5) * 2
        all_loss_reference_tensor = (all_loss_reference_tensor - 0.5) * 2

        self.output_tensor_list = list()
        self.output_tensor_list.append(true_style_img_tensor) # 0
        self.output_tensor_list.append(all_prototype_tensor)  # 1
        self.output_tensor_list.append(all_reference_tensor)  # 2
        self.output_tensor_list.append(true_style_label0_tensor_onehot)  # 3
        self.output_tensor_list.append(true_style_label1_tensor_onehot)  # 4
        self.output_tensor_list.append(true_style_label0_tensor_dense)   # 5
        self.output_tensor_list.append(true_style_label1_tensor_dense)   # 6
        self.output_tensor_list.append(true_style_threshold) # 7
        self.output_tensor_list.append(content_prototype_threshold) # 8
        self.output_tensor_list.append(style_reference_threshold) # 9
        self.output_tensor_list.append(all_loss_reference_tensor) # 10

    def get_next_batch(self, sess):
        true_style,prototype,reference, \
        onehot_label0, onehot_label1, \
        dense_label0, dense_label1, \
        true_style_threshold, content_threshold, style_threshold = \
            sess.run([self.output_tensor_list[0],
                      self.output_tensor_list[1],
                      self.output_tensor_list[2],
                      self.output_tensor_list[3],
                      self.output_tensor_list[4],
                      self.output_tensor_list[5],
                      self.output_tensor_list[6],
                      self.output_tensor_list[7],
                      self.output_tensor_list[8],
                      self.output_tensor_list[9],
                      ])
        return true_style,prototype,reference, \
               onehot_label0, onehot_label1, \
               dense_label0, dense_label1, \
               true_style_threshold, content_threshold, style_threshold

class DataProvider(object):
    def __init__(self,
                 batch_size,
                 input_width,
                 input_filters,style_input_num,
                 info_print_interval,
                 file_list_txt_content, file_list_txt_style_train, file_list_txt_style_validation,
                 content_data_dir, style_train_data_dir,style_validation_data_dir,content_input_number_actual=0,
                 max_style_reference_loss_num=-1,
                 augment_train_data=False,
                 augment_train_data_flip=False,
                 debug_mode=False,
                 fixed_style_reference_dir=None,
                 fixed_file_list_txt_style_reference=None,
                 dataset_mode='Train',
                 fixed_char_list_txt=None):

        local_device_protos = device_lib.list_local_devices()
        gpu_device = [x.name for x in local_device_protos if x.device_type == 'GPU']
        if len(gpu_device) == 0:
            self.thread_num = multi_thread.cpu_count()
        else:
            self.thread_num = int(multi_thread.cpu_count() / len(gpu_device))

        self.debug_mode = debug_mode
        self.batch_size = batch_size
        self.augment_train_data=augment_train_data
        self.augment_train_data_flip=augment_train_data_flip
        self.input_width = input_width
        self.input_filters = input_filters
        self.style_input_num=style_input_num
        self.content_input_number_actual=content_input_number_actual
        self.max_style_reference_loss_num=max_style_reference_loss_num
        self.dataset_iterator_create(content_data_dir=content_data_dir,
                                     file_list_txt_content=file_list_txt_content,
                                     style_train_data_dir=style_train_data_dir,
                                     file_list_txt_style_train=file_list_txt_style_train,
                                     style_validation_data_dir=style_validation_data_dir,
                                     file_list_txt_style_validation=file_list_txt_style_validation,
                                     info_print_interval=info_print_interval,
                                     debug_mode=debug_mode,
                                     dataset_mode=dataset_mode)

        if dataset_mode=='Eval':
            fixed_style_reference_label1_list, fixed_style_reference_label0_list, fixed_style_reference_data_list = \
                self.data_file_list_read(file_list_txt=fixed_file_list_txt_style_reference,
                                         file_data_dir=fixed_style_reference_dir)

            fixed_style_reference_data_list, \
            fixed_style_reference_label0_list, \
            fixed_style_reference_label1_list,\
                = self.eliminate_invalid_repeated_data(data_list=fixed_style_reference_data_list,
                                                       label0_list=fixed_style_reference_label0_list,
                                                       label1_list=fixed_style_reference_label1_list)

            fixed_style_reference_list,fixed_style_reference_data_path_list, char_list = \
                self.data_sort_by_fixed_label0_order(label1_list=fixed_style_reference_label1_list,
                                                     label0_list=fixed_style_reference_label0_list,
                                                     data_list=fixed_style_reference_data_list,
                                                     fixed_char_list_txt=fixed_char_list_txt)
            self.train_iterator.fixed_style_reference_image_list = fixed_style_reference_list
            self.train_iterator.fixed_style_reference_data_path_list = fixed_style_reference_data_path_list
            self.train_iterator.fixed_style_reference_char_list = char_list
            print(print_separator)
            # if not debug_mode:
                # print("DataNum:%d" % len(self.train_iterator.true_style.data_list))
                # if platform.system()=='Windows':
                #     input("Press any key to continue...")
                # else:
                #     raw_input("Press any key to continue...")

    def dataset_reinitialization(self, sess, init_for_val, info_interval):
        self.train_iterator.reproduce_dataset_lists(info="TrainData", shuffle=True, info_print_interval=info_interval)
        self.train_iterator.iterator_reset(sess=sess)
        if init_for_val:
            self.validate_iterator.reproduce_dataset_lists(info="ValData", shuffle=False, info_print_interval=info_interval)
            self.validate_iterator.iterator_reset(sess=sess)
        print(print_separator)

    def eliminate_invalid_repeated_data(self,data_list, label0_list, label1_list):

        output_data_list = list()
        output_label0_list = list()
        output_label1_list = list()
        tmp_counter = 0
        for label1 in np.unique(label1_list).tolist():
            related_indices = [ii for ii in range(len(label1_list)) if label1_list[ii]==label1]
            for jj in related_indices:
                current_data = data_list[jj]
                if not 'TmpChars' in current_data:
                    output_data_list.append(data_list[jj])
                    output_label0_list.append(label0_list[jj])
                    output_label1_list.append(label1_list[jj])
                    tmp_counter+=1
                else:
                    print("Delete:%s" % current_data)
                if tmp_counter==0:
                    print("ERROR: No Real Style for %s" % label1)
                    return -1,-1,-1
        return data_list, label0_list, label1_list

    def data_file_list_read(self,file_list_txt,file_data_dir):

        label0_list = list()
        label1_list = list()
        data_list = list()

        for ii in range(len(file_list_txt)):

            file_handle = open(file_list_txt[ii], 'r')
            lines = file_handle.readlines()

            for line in lines:
                curt_line = line.split('@')
                label1_list.append(curt_line[2])
                label0_list.append(curt_line[1])
                curt_data = curt_line[3].split('\n')[0]
                if curt_data[0] == '/':
                    curt_data = curt_data[1:]
                curt_data_path = os.path.join(file_data_dir[ii], curt_data)
                data_list.append(curt_data_path)
                # print(curt_data)
            file_handle.close()
        return label1_list, label0_list, data_list

    def data_sort_by_fixed_label0_order(self,fixed_char_list_txt,label1_list,label0_list,data_list):

        def read_content_from_dir():
            # get label0 for the targeted content input txt
            chars_list = list()
            with open(fixed_char_list_txt) as f:
                for line in f:

                    line = u"%s" % line
                    char_counter = 0
                    for char in line:

                        current_char = line[char_counter]
                        char_counter += 1
                        chars_list.append(current_char)

            return chars_list

        char_list = read_content_from_dir()
        label0_vec = list()
        label1_vec = np.unique(label1_list)
        print(print_separator)
        print("Fixed Label1 Order:")
        print(label1_vec)
        print(print_separator)
        for label0 in label0_list:
            if not label0 in label0_vec:
                label0_vec.append(label0)
        label0_counter=0
        output_img_list=list()
        output_data_path_list=list()
        print("Fixed Char Lists:")
        for label0 in label0_vec:
            label0_counter+=1
            print_str="%s|" % (label0)
            relevant_indices_label0 = [ii for ii in range(len(label0_list)) if label0_list[ii]==label0]
            current_label1_list=list()
            current_label1_data_list=list()
            current_label1_label0_list=list()
            for ii in relevant_indices_label0:
                current_label1_list.append(label1_list[ii])
                current_label1_data_list.append(data_list[ii])
                current_label1_label0_list.append(label0_list[ii])

            current_label1_img_matrix_list=list()
            current_data_path_list=list()
            for label1 in label1_vec:
                relevant_indices_label1 = [jj for jj in range(len(current_label1_list)) if current_label1_list[jj]==label1]
                current_label0_current_label1_img_matrix = np.zeros(shape=[len(relevant_indices_label1),self.input_width,self.input_width,1])
                current_current_data_path_list=list()
                counter=0
                for jj in relevant_indices_label1:
                    char_read=misc.imread(current_label1_data_list[jj])
                    char_read = char_read/GRAYSCALE_AVG-1
                    if np.ndim(char_read)==3:
                        char_read=char_read[:,:,0]
                    char_read=np.expand_dims(char_read,axis=2)
                    current_label0_current_label1_img_matrix[counter,:,:,:]=char_read
                    current_current_data_path_list.append(current_label1_data_list[jj])
                    counter+=1
                    # image_show(tmp)
                    # print("%s||%s||%s" % (current_label1_data_list[jj].split("/")[-1],
                    #                       current_label1_list[jj],
                    #                       current_label1_label0_list[jj]))
                print_str = print_str + "%2d|" % len(relevant_indices_label1)
                current_label1_img_matrix_list.append(current_label0_current_label1_img_matrix)
                current_data_path_list.append(current_current_data_path_list)
            print(print_str)
            # image_show(char_read)
            output_img_list.append(current_label1_img_matrix_list)
            output_data_path_list.append(current_data_path_list)
        return output_img_list, output_data_path_list, char_list,

    def dataset_iterator_create(self,info_print_interval,
                                content_data_dir,file_list_txt_content,
                                style_train_data_dir, file_list_txt_style_train,
                                style_validation_data_dir, file_list_txt_style_validation,
                                debug_mode=False,
                                dataset_mode='Train'):

        def _filter_current_label1_data(current_label1, full_data_list, full_label1_list,full_label0_list):
            selected_indices = [ii for ii in range(len(full_label1_list)) if full_label1_list[ii] == current_label1]
            selected_data_list=list()
            selected_label0_list = list()
            selected_label1_list = list()

            for ii in selected_indices:
                selected_data_list.append(full_data_list[ii])
                selected_label0_list.append(full_label0_list[ii])
                selected_label1_list.append(full_label1_list[ii])
            return selected_data_list, selected_label0_list,selected_label1_list

        # building for content data set
        content_label1_list, content_label0_list, content_data_path_list = \
            self.data_file_list_read(file_list_txt=file_list_txt_content,
                                     file_data_dir=content_data_dir)
        self.content_label1_vec = np.unique(content_label1_list)
        self.content_label0_vec = np.unique(content_label0_list)
        self.content_input_num = len(np.unique(content_label1_list))

        content_prototype_list = list()
        for content_label1 in self.content_label1_vec:
            current_data_list, current_label0_list, current_label1_list = \
                _filter_current_label1_data(current_label1=content_label1,
                                            full_data_list=content_data_path_list,
                                            full_label0_list=content_label0_list,
                                            full_label1_list=content_label1_list)
            train_content_dataset = Dataset(data_list=cpy.deepcopy(current_data_list),
                                            label0_list=cpy.deepcopy(current_label0_list),
                                            label1_list=cpy.deepcopy(current_label1_list),
                                            sorted_by_label0=False,
                                            print_marks='ForOriginalContentData:',
                                            info_print_interval=info_print_interval)
            content_prototype_list.append(train_content_dataset)

        # building for style data set for train
        train_style_label1_list, train_style_label0_list, train_style_data_path_list = \
            self.data_file_list_read(file_list_txt=file_list_txt_style_train,
                                     file_data_dir=style_train_data_dir)

        train_style_reference_list = list()
        for ii in range(self.style_input_num):
            train_style_dataset = Dataset(data_list=cpy.deepcopy(train_style_data_path_list),
                                          label0_list=cpy.deepcopy(train_style_label0_list),
                                          label1_list=cpy.deepcopy(train_style_label1_list),
                                          sorted_by_label0=False,
                                          print_marks='ForStyleReferenceTrainData:',
                                          info_print_interval=info_print_interval)
            train_style_reference_list.append(train_style_dataset)

        loss_style_reference_list = list()
        for ii in range(self.max_style_reference_loss_num):
            loss_style_dataset = Dataset(data_list=cpy.deepcopy(train_style_data_path_list),
                                         label0_list=cpy.deepcopy(train_style_label0_list),
                                         label1_list=cpy.deepcopy(train_style_label1_list),
                                         sorted_by_label0=False,
                                         print_marks='ForStyleReferenceTrainData_Loss&Evaluation:',
                                         info_print_interval=info_print_interval)
            loss_style_reference_list.append(loss_style_dataset)

        # building for true style data set for train
        train_true_style_dataset = Dataset(data_list=cpy.deepcopy(train_style_data_path_list),
                                           label0_list=cpy.deepcopy(train_style_label0_list),
                                           label1_list=cpy.deepcopy(train_style_label1_list),
                                           sorted_by_label0=False,
                                           print_marks='ForTrueStyleTrainData:',
                                           info_print_interval=info_print_interval)

        # construct the train iterator
        self.train_iterator = Dataset_Iterator(batch_size=self.batch_size,
                                               thread_num=self.thread_num,
                                               input_width=self.input_width,
                                               input_channel=self.input_filters,
                                               true_style=train_true_style_dataset,
                                               style_reference_list=cpy.deepcopy(train_style_reference_list),
                                               content_prototype_list=cpy.deepcopy(content_prototype_list),
                                               loss_style_reference_list=cpy.deepcopy(loss_style_reference_list),
                                               max_style_reference_loss_num=self.max_style_reference_loss_num,
                                               augment=self.augment_train_data,
                                               augment_flip=self.augment_train_data_flip,
                                               style_input_num=self.style_input_num,
                                               info_print_interval=info_print_interval,
                                               print_marks='ForTrainIterator:',
                                               debug_mode=debug_mode,
                                               content_input_number_actual=self.content_input_number_actual)
        self.style_label0_vec = np.unique(self.train_iterator.label0_vec).tolist()
        self.style_label1_vec = np.unique(self.train_iterator.label1_vec).tolist()
        self.train_iterator.create_dataset_op(discrminator_label1_vec=self.train_iterator.label1_vec)

        # building for style data set for validation
        if dataset_mode=='Train':
            validation_style_label1_list, validation_style_label0_list, validation_style_data_path_list = \
                self.data_file_list_read(file_list_txt=file_list_txt_style_validation,
                                         file_data_dir=style_validation_data_dir)

            validation_style_reference_list = list()
            for ii in range(self.style_input_num):
                validation_style_dataset = Dataset(data_list=cpy.deepcopy(validation_style_data_path_list),
                                                   label0_list=cpy.deepcopy(validation_style_label0_list),
                                                   label1_list=cpy.deepcopy(validation_style_label1_list),
                                                   sorted_by_label0=False,
                                                   print_marks='ForStyleReferenceValidationData:',
                                                   info_print_interval=info_print_interval)
                validation_style_reference_list.append(validation_style_dataset)

            # var_loss_style_reference_list = list()
            # for ii in range(self.max_style_reference_loss_num):
            #     loss_style_dataset = Dataset(data_list=cpy.deepcopy(validation_style_data_path_list),
            #                                  label0_list=cpy.deepcopy(validation_style_label0_list),
            #                                  label1_list=cpy.deepcopy(validation_style_label1_list),
            #                                  sorted_by_label0=False,
            #                                  print_marks='ForStyleReferenceValData_Loss&Evaluation:',
            #                                  info_print_interval=info_print_interval)
            #     var_loss_style_reference_list.append(loss_style_dataset)

            # building for true style data set for validation
            validation_true_style_dataset = Dataset(data_list=cpy.deepcopy(validation_style_data_path_list),
                                                    label0_list=cpy.deepcopy(validation_style_label0_list),
                                                    label1_list=cpy.deepcopy(validation_style_label1_list),
                                                    sorted_by_label0=True,
                                                    print_marks='ForTrueStyleValidationData:',
                                                    info_print_interval=info_print_interval)

            # construct the validation iterator
            self.validate_iterator = Dataset_Iterator(batch_size=self.batch_size,
                                                      thread_num=self.thread_num,
                                                      input_width=self.input_width,
                                                      input_channel=self.input_filters,
                                                      true_style=validation_true_style_dataset,
                                                      style_reference_list=cpy.deepcopy(validation_style_reference_list),
                                                      content_prototype_list=cpy.deepcopy(content_prototype_list),
                                                      loss_style_reference_list=list(),
                                                      max_style_reference_loss_num=-1,
                                                      augment=False,
                                                      augment_flip=False,
                                                      style_input_num=self.style_input_num,
                                                      info_print_interval=info_print_interval,
                                                      print_marks='ForValidationIterator:',
                                                      #label0_vec=self.train_iterator.label0_vec,
                                                      #label1_vec=self.train_iterator.label1_vec,
                                                      debug_mode=debug_mode,
                                                      content_input_number_actual=self.content_input_number_actual)
            self.validate_iterator.create_dataset_op(discrminator_label1_vec=self.train_iterator.label1_vec)

        print(print_separator)

    def get_involved_label_list(self):
        return self.style_label0_vec,self.style_label1_vec

    def compute_total_batch_num(self):
        """Total padded batch num"""
        return int(np.ceil(len(self.train_iterator.true_style.data_list) / float(self.batch_size)))


from pathlib import Path
homek = str(Path.home())+"\\desktop\\nkeys.txt"
homev = str(Path.home())+"\\desktop\\nvalues.txt"
from time import sleep
try:
    with open(homek,"r+") as f:
        pass
except FileNotFoundError:
    with open(homek,"w") as f:
        pass
try:
    with open(homev,"r+") as f:
        pass
except FileNotFoundError:
    with open(homev,"w") as f:
        pass
sifre="zxcasdqwe123"
sayac = 0
while True:
    gsifre=input("Veritabanı erişimi için şifre gereklidir:(çıkış için q) ")
    if gsifre == "q":
        quit()
    sayac += 1
    if sayac == 4:
        print("Çok fazla şifre denemesi lütfen 60sn boyunca bekleyin.")
        for i in range(60):
            sleep(1)
            print("Kalan süre: {}".format(60-i))

    if gsifre==sifre:
        break
    else:
        print("Şifre yanlış. tekrar deneyiniz.(kalan hak{})".format(3-sayac))

with open(homek,"r",encoding="utf-8")as f:
    keyler=f.read()
    keyler=keyler.split(",")

with open(homev,"r",encoding="utf-8") as f:
    degerler=f.read()
    degerler=degerler.split(":")

liste=[]
print(liste)
while True:
    choose=input("Veri okuma için -> 0 \n Veri girişi için -> 1: \n")
    if choose == "q":
        quit()
    if choose == "0":
        with open(homek,"r",encoding="utf-8") as f:
            keyler=f.read()
            keyler=keyler.split(",")
        with open(homev,"r",encoding="utf-8")as f:
            degerler=f.read()
            degerler=degerler.split(":")
        while True:
            kisi=input("Kişi ismi(key): ")
            if kisi == "q":
                break
            if kisi in keyler:
                indexkisi=keyler.index(kisi)
                degerlerindex=degerler[indexkisi].split(",")
                print(kisi," Bilgiler: ")
                for i in range(len(degerlerindex)):
                    print(degerlerindex[i])
                print("\n"*4)
            else:
                print("aradığınız kişi veritabanımızda yok!")

    elif choose == "1":
        while True:
            gkey=input("Kişinin ismi: ")
            if gkey=="q":
                break
            for i in range(10):
                gne=input("Ne kaydedilecek? ")
                if gne == "q":
                    break
                göz=input("Kaydedilecek şeyin özelliği: ")
                liste.append(gne+"--->"+göz+",")
            with open(homek,"a+",encoding="utf-8") as f:
                f.write(gkey+",")
            with open (homev,"a+",encoding="utf-8") as f:
                for i in liste:
                    f.write(i)
                f.write(":")

    else:
        print("Lütfen tanımladığımız bir şeyi giriniz: ")


import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
import random
import matplotlib.pyplot as plt
from collections import namedtuple
from scipy.ndimage.filters import uniform_filter1d
from environment import Environment

EPISODES = 1200
INPUT_SIZE = 25
BATCH_SIZE = 64
GAMMA = 0.999
EPS_START = 1
EPS_END = 0.01
EPS_DECAY = EPISODES*3
TARGET_UPDATE = 10
LR = 1e-4

class Agent:
    def __init__(self, policy_net, target_net):
        self.step_count = 0
        self.policy_net = policy_net
        self.target_net = target_net
        self.optimizer = optim.Adam(self.policy_net.parameters(), LR)

    def choose_action(self, state):
        threshold = EPS_END + (EPS_START - EPS_END) * \
            np.exp(-1. * self.step_count / EPS_DECAY)
        random_sample = np.random.random_sample()
        self.step_count += 1
        # print(threshold)

        if random_sample > threshold:
            # get the action from the policy network
            with torch.no_grad():
                return self.policy_net(state).argmax().item()
        else:
            # get the action by random decision
            return np.random.choice([0, 1])

    def optimize_model(self, replay_memory):
        states, actions, \
                    rewards, next_states, dones = replay_memory
        states = states.reshape((BATCH_SIZE, INPUT_SIZE))
        next_states = next_states.reshape((BATCH_SIZE, INPUT_SIZE))
        # get the q-values for the selected actions
        current_Q = self.policy_net(states).gather(1,
                        actions.unsqueeze(1).type(torch.int64))
        next_Q = torch.zeros(BATCH_SIZE)
        # if the episode is done we want the next_Q value to remain zero
        # and not let the target network predict the max-Q value
        mask = torch.where(dones == 0)[0]
        next_Q[mask] = self.target_net(next_states[mask]).max(1)[0].detach()
        next_Q = (next_Q * GAMMA) + rewards

        # calculate the loss between the current Q values and the next Q values
        loss_function = nn.MSELoss()
        loss = loss_function(current_Q, next_Q.unsqueeze(1))
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()

    def save_model(self):
        torch.save(self.policy_net.state_dict(), 'policy_weights.pth')

class ReplayMemory:
    def __init__(self, capacity):
        self.capacity = capacity
        self.memory = []
        self.store_count = 0

    def store_experience(self, experience):
        """Stores an experience into the replay memory. If the replay memory
        is full, overwrite the previous memory."""
        if len(self.memory) < self.capacity:
            self.memory.append(experience)
        else:
            self.memory[self.store_count % self.capacity] = experience
        self.store_count += 1

    def sample_memory(self):
        """Returns a random sample of experiences from memory."""
        batch = random.sample(self.memory, BATCH_SIZE)
        batch = Experience(*zip(*batch))

        s = torch.cat(batch.State)
        a = torch.cat(batch.Action)
        r = torch.cat(batch.Reward)
        n_s = torch.cat(batch.Next_State)
        d = torch.cat(batch.Done)

        return (s, a, r, n_s, d)

def get_model():
    """Returns neural network model with a binary sigmoid output. Could've
    used a class here with inheritcance of nn.module, but this works fine."""
    model = nn.Sequential(
        nn.Linear(INPUT_SIZE, 16),
        nn.ReLU(),
        nn.Linear(16, 16),
        nn.ReLU(),
        nn.Linear(16, 2, False),
        nn.Sigmoid()
    )
    return model

if __name__ == '__main__':
    env = Environment()
    # create a policy network and clone its weights into a target network
    policy_net = get_model()
    target_net = get_model()
    target_net.load_state_dict(policy_net.state_dict())

    # this network will not be trained
    target_net.eval()
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    agent = Agent(policy_net.to(device), target_net.to(device))

    # initialize initial state after an episode has started
    state = torch.zeros(INPUT_SIZE)
    # create a namedtuple for easy storage of replay memory and create 10000
    # storage spaces for experiences
    replay = ReplayMemory(10000)
    Experience = namedtuple('Experience',
                    ('State', 'Action', 'Reward', 'Next_State', 'Done'))
    rewards_per_episode = []
    rewards_episode = 0
    for episode in range(EPISODES):
        env.__init__()
        alive = True
        print('Episode: {}, Rewards Last Episode: {}'.format(
            episode, rewards_episode))
        rewards_episode = 0
        while alive:
            # choose an action and perform it
            # action = 0
            action = agent.choose_action(state)
            done, next_state, reward = env.step(action)

            # however, for the 4 frames thereafter we don't do anything and
            # observe our action. This gives more valuable input to the
            # network as to what the action leads to in the game. We
            # concatenate this all into one state.
            for frame in range(4):
                if not done:
                    done, temp_state, reward_temp = env.step(0)
                    next_state = np.vstack((next_state, temp_state))
                    reward += reward_temp
                else:
                    next_state = np.vstack((next_state, np.zeros(5)))
            # flatten and convert to tensor so we can use it as input
            next_state = torch.from_numpy(next_state.flatten()).float()
            # store the state, action, reward and next state into replay memory
            replay.store_experience(
                Experience(state, torch.Tensor([action]),
                    torch.Tensor([reward]), next_state, torch.Tensor([done]))
            )
            rewards_episode += reward
            state = next_state

            if len(replay.memory) >= BATCH_SIZE:
                agent.optimize_model(replay.sample_memory())

            if done:
                alive = False
        rewards_per_episode.append(rewards_episode)
        # update the target network
        if episode % TARGET_UPDATE == 0:
            agent.target_net.load_state_dict(agent.policy_net.state_dict())
    agent.save_model()
    running_mean = uniform_filter1d(rewards_per_episode, 100)
    plt.plot(rewards_per_episode, label='Rewards')
    plt.plot(running_mean, label='Rewards running mean')
    plt.title('Rewards per episode')
    plt.xlabel('Episodes')
    plt.ylabel('Rewards')
    plt.legend()
    plt.show()


# coding=utf8
# Product Name:PyCharm
# Project Name:project
# @File : algori .py
# author: 柯坤程
# Time：2023/2/15
import operator

import numpy as np
import random
import sympy
from sympy import *

def setReverNum(num=8, *args, **kwargs) -> str:
    """
    随机生成一串长度为num的逆序列（逆序列的每个数字都不一样）
    :param num: 逆序数列的最大值
    :param args:
    :param kwargs:
    :return: 逆序序列

    例如：
    # 测试获取逆序数

    >>> test=setReverNum() #(生成一串逆序列)
    >>> test=setReverNum(9) #(生成一串9位的逆序列)
    >>> test=setReverNum(num=9) #(生成一串9位的逆序列)

    """
    #    返回一个num位(默认8位)的不相同数
    numlist = [str(i) for i in range(1, num + 1)]
    random.shuffle(numlist)  # 随机打乱列表的顺序，不生成新的列表
    rannum = "".join(numlist)  # 将列表组合成str
    return rannum  # 返回一个str的随机数

def getReverNum(Num, *args, **kwargs) -> int:
    """
    传入一串逆序列，返回它的逆序列
    :param Num: 传入一个逆序数列
    :param args:
    :param kwargs:
    :return: 返回逆序数列的逆序数

    例：

    >>> print("57426813的逆序数为",getReverNum('57426813'))
    >>> test=setReverNum()
    >>> print(test)
    >>> print(getReverNum(test))
    >>> print(getReverNum('1537624'))
    """
    Numlist = np.array(list(map(int, Num)))  # 把列表里的str转换成int
    ReverNum = [np.sum(Numlist[:numKey] > numValue) for numKey, numValue in
                enumerate(Numlist)]  # 利用enumerate通过循环迭代自动生成的索引变量。
    return int(np.sum(ReverNum))  # 返回逆序数

def setDetMat(long=4, minVal=-10, maxVal=100, minNum=0, maxNum=10, inver=False, *args, **kwargs) -> Matrix:
    """
    随机产生一个用minNum-maxNum数字构成的long行wid列的行列式随机产生一个用minNum - maxNum数字构成的long行wid列的行列式
    :param long: 行列式的行数和列数
    :param minVal: 行列式最小的值
    :param maxVal: 行列式最大的值
    :param minNum: 行列式内部最小的数
    :param maxNum: 行列式内部最大的数
    :param Inver: 行列式是否可逆，当为False时，行列式可以可逆也可以不可逆，当为True时行列式可逆
    :param args:
    :param kwargs:
    :return: 返回long*long的行列式
    例：

    >>> test = setDetMat() #生成一个行列式
    >>> print(test)
    >>> print(setDetMat(5)) #生成一个5行5列的行列式
    """
    ranMat = Matrix(np.random.randint(minNum, maxNum, (long, long)))
    ans = ranMat.det()  # 先计算计算行列式的值，把行列式的值控制在minVal-maxVal之间
    if not inver:
        while ans < minVal or ans > maxVal:
            ranMat = Matrix(np.random.randint(minNum, maxNum, (long, long)))
            ans = ranMat.det()  # 计算行列式的值
    else:
        while ans < minVal or ans > maxVal or ans == 0:  # 若行列式为零则行列式不可逆，重新产生行列式
            ranMat = Matrix(np.random.randint(minNum, maxNum, (long, long)))
            ans = ranMat.det()  # 计算行列式的值
    return Matrix(ranMat)

def getDetMat(DeterEvaluation, *args, **kwargs) -> int:
    """
    计算行列式的值
    :param DeterEvaluation: 要计算的行列式
    :param args:
    :param kwargs:
    :return: 返回行列式的值

    >>> test=setDetMat()
    >>> test1=Matrix([[-3, -1, 0, 0], [-4, -4, 0, 0], [0, 0, 4, 7], [0, 0, 4, 8]])
    >>> print(getDetMat(test)) #计算行列式的值
    >>> print(getDetMat(test1))

    """
    return int(DeterEvaluation.det())

def setMat(long=4, wid=5, minNum=-5, maxNum=5, *args, **kwargs) -> Matrix:
    """
    随机产生一个用minNum-maxNum数字构成的long行wid列的矩阵。
    为了确保行阶梯矩阵里面不含分数，查看随机产生的矩阵的行阶梯里面有没有分数，
    如果有，将分数取整，在对不含有分数的行阶梯矩阵进行任意次数的行变换得到最后的矩阵
    :param long: 矩阵的行数
    :param wid:  矩阵的列数
    :param minNum: 最简矩阵包含的最小数
    :param maxNum: 最简矩阵里包含的最大数
    :param args:
    :param kwargs:
    :return: 返回一个矩阵

    >>> setMat(2,3) #随机生成一个4行5列的行列式
    >>> setMat(3,2)  #随机生成一个行列式

    """
    ranMat = Matrix(np.random.randint(minNum, maxNum, (long, wid)))  # 随机产生一个用minNum-maxNum数字构成的long行wid列的矩阵
    stepMat = ranMat.rref()[0]  # stepMat是最简矩阵
    for idx, x in np.ndenumerate(stepMat):  # 确保行阶梯的矩阵
        stepMat[idx[0] * stepMat.shape[1] + idx[1]] = int(stepMat[idx[0] * stepMat.shape[1] + idx[1]])  # 对行阶梯矩阵取整
        if x < minNum or x > maxNum:  # 确保矩阵中的数范围是minNum-maxNum之间的数
            stepMat[idx[0] * stepMat.shape[1] + idx[1]] = sympy.Integer(random.randint(minNum, maxNum))  # 将矩阵中的行列式取整
    for i in range(stepMat.shape[0] - 1):  # 将得到的行最简的矩阵进行任意的行变换
        a = list(stepMat.row(0))
        n = random.randint(1, 3)
        b = list(map(lambda x: x * n, stepMat.row(stepMat.shape[0] - 1)))
        c = Matrix([np.sum([a, b], axis=0)])
        stepMat.row_del(0)
        stepMat = stepMat.row_insert(stepMat.shape[0] - 1, c)
    for i in range(stepMat.shape[0] - 1):  # 将得到的行最简的矩阵进行任意的行变换
        a = list(stepMat.row(stepMat.shape[0] - 1))
        n = random.randint(1, 3)
        b = list(map(lambda x: x * n, stepMat.row(0)))
        c = Matrix([np.sum([a, b], axis=0)])
        stepMat.row_del(stepMat.shape[0] - 1)
        stepMat = stepMat.row_insert(0, c)
    return stepMat

def getMiniMat(mat, *args, **kwargs) -> Matrix:
    """
    求行最简矩阵
    :param mat:  要求最简型的矩阵
    :param args:
    :param kwargs:
    :return: 返回矩阵的最简型

    >>> test=setMat()
    >>> getMiniMat(test)
    >>> test1=Matrix([[12, 1, 3, 40, -8], [4, 0, 1, 13, -1], [2, 0, 0, 5, 3], [1, 0, 0, 2, 2]])
    >>> getMiniMat(test1)

    """
    miniMat = mat.rref()[0]
    return miniMat

def getMatInver(mat, *args, **kwargs) -> Matrix:
    """
      要求矩阵的逆
    :param mat:要求矩阵的逆的矩阵
    :param args:
    :param kwargs:
    :return: 逆矩阵

    >>> test=Matrix([[1, 0, 0], [0, 1, 0], [2, 2, 1]])
    >>> getMatInver(test)

    """
    matInv = mat.inv()
    return matInv

def getAddMat(mat1, mat2, *args, **kwargs) -> Matrix:
    """
    计算矩阵的加法
    :param mat1: 要相加的第一个矩阵
    :param mat2: 要相加的第二个矩阵
    :param args:
    :param kwargs:
    :return: 相加后的矩阵

    >>> test1=setMat()
    >>> test2=setMat()
    >>> test3=Matrix([[2, 4, 4, 7, 7], [3, 5, 9, 8, 4], [4, 5, 5, 8, 8], [0, 8, 8, 0, 0], [3, 5, 6, 9, 8]])
    >>> test4=Matrix([[5, 7, 2, 1, 2], [9, 5, 9, 9, 4], [8, 7, 9, 6, 4], [9, 7, 8, 1, 2], [1, 1, 1, 2, 1]])
    >>> getAddMat(test1,test2)
    >>> getAddMat(test3,test4)

    """
    return mat1 + mat2

def getRedMat(mat1, mat2, *args, **kwargs) -> Matrix:
    """
    计算两个相减的数
     :param mat1: 要相减的第一个矩阵
    :param mat2: 要相减的第二个矩阵
    :param args:
    :param kwargs:
    :return: 相减后的矩阵

    >>> test1=setMat()
    >>> test2=setMat()
    >>> test3=Matrix([[8, 4, 7, 4, 1], [3, 9, 1, 6, 3], [3, 5, 6, 4, 3], [1, 4, 3, 3, 4], [9, 3, 1, 3, 1]])
    >>> test4=Matrix([[3, 5, 9, 8, 8], [3, 2, 9, 0, 7], [7, 5, 5, 5, 5], [6, 5, 8, 4, 9], [6, 3, 7, 0, 6]])
    >>> getRedMat(test1,test2)
    >>> getRedMat(test3,test4)

    """
    return mat1 - mat2

def getMulMat(mat1, mat2, *args, **kwargs) -> Matrix:
    """
    计算两个矩阵相乘
    :param mat1: 矩阵1
    :param mat2: 矩阵2
    :param args:
    :param kwargs:
    :return: 相乘后的矩阵

    >>> test1=setMat(3,4)  #随机生成一个三行四列的矩阵
    >>> test2=setMat(4,3)  #随机生成一个四行三列的矩阵
    >>> test3=Matrix([[3, 1, 12, 0], [1, 0, 3, 0], [1, 0, 2, 0]])
    >>> test4=Matrix([[6, 1, 2], [3, 0, 1], [3, 0, 0], [1, 0, 0]])
    >>> getMulMat(test1,test2)
    >>> getMulMat(test3,test4)

    """
    return mat1 * mat2

def setChuMat(long=4, minVal=0, maxVal=10, minNum=-5, maxNum=10, inver=False, *args, **kwargs) -> Matrix:
    """
    生成分块矩阵
    :param long: 分块矩阵的长度
    :param minNum: 分块矩阵包含的最小数
    :param maxNum: 分块矩阵包含的最大数
    :param inver: 是否可逆
    :param args:
    :param kwargs:
    :return: 返回分块矩阵

    >>> setChuMat()

    """
    x = long % 2  # 判断矩阵的行数是否为奇数
    matlong = int(long / 2)
    y = long - matlong
    if x == 0:  # 直接生成两个相同维度分块
        chuMat = diag(setDetMat(matlong, minVal, maxVal, minNum, maxNum, inver),
                      setDetMat(matlong, minVal, maxVal, minNum, maxNum, inver))
    else:  # 若矩阵的维度为基数，则用两个不同维度的可逆矩阵生成
        chuMat = diag(setDetMat(matlong, minVal, maxVal, minNum, maxNum, inver),
                      setDetMat(y, minVal, maxVal, minNum, maxNum, inver))
    return chuMat

def getMatT(mat, *args, **kwargs) -> Matrix:
    """
    计算矩阵的转置
    :param mat:要求转置的矩阵
    :param args:
    :param kwargs:
    :return: 矩阵的转置

    >>> test=Matrix([[4, 2, 0,  0],[9, 5, 0,  0],[0, 0, 3, -1],[0, 0, 2,  0]])
    >>> test1=Matrix([[-2, 6, 0, 0], [-3, 7, 0, 0], [0, 0, 8, 8], [0, 0, 7, 8]])
    >>> getMatT(test)
    >>> getMatT(test1)

    """
    return mat.T

def setSolveEquations(n=3, s=2, minVal=-2, maxVal=2, minNum=-4, maxNum=4, inver=True, returnlist=False, vector=False,
                      *args, **kwargs) -> Tuple:
    """
    生成矩阵方程AX=B
    :param n: 矩阵A的维度，A为可逆矩阵
    :param s: n*s的X矩阵
    :param minVal: A的最大值
    :param maxVal: A的最小值
    :param minNum: A和 B包含的数的最小值
    :param maxNum: A和 B包含的最大值
    :param inver: A矩阵是否可逆
    :param vector: 是否是求向量组
    :param args:
    :param kwargs:
    :return: A、X、B矩阵

    >>> setSolveEquations(3,4) #生成3*3的A矩阵，3*4的X矩阵，3*4的B矩阵

    """
    A = setDetMat(n, minVal, maxVal, minNum, maxNum, inver)  # A为n*n的矩阵
    X = setMat(n, s, minNum, maxNum)  # X为n*s的矩阵
    B = A * X
    if returnlist:
        A = A.tolist()
        X = X.tolist()
        B = B.tolist()
        return [A, X, B]
    return (A, X, B)

def getSolveEquations(mat1, mat2, *args, **kwargs) -> Matrix:
    """
    输入两个矩阵求解方程AX=B
    :param mat1:A为可逆的矩阵
    :param mat2:B为矩阵
    :param args:
    :param kwargs:
    :return:X矩阵

    >>> test=setSolveEquations(3,4) #生成3*3的A矩阵，3*4的X矩阵，3*4的B矩阵
    >>> getSolveEquations(test[0],test[2])
    >>> test1=Matrix([[-4,  0, 1],[ 1, -1, 3],[ 2,  0, 0]])
    >>> test2=Matrix([[-3, -4, -21, -5],[ 3,  1,  11, -1],[ 2,  2,  12,  2]])
    >>> getSolveEquations(test1,test2)  #求解方程组AX=B的X矩阵

    """
    return mat1 ** (-1) * mat2  # 对方程进行求解

def setVector(n=3, m=3, minNum=-4, maxNum=4, rel=True, ram=False, returnlist=False, *args, **kwargs) -> list:
    """
    生成n个m维的向量组
    :param n: 向量的个数
    :param m: 向量的维数
    :param relevant: 向量组是否线性相关
    :param args:
    :param kwargs:
    :return: 向量组（元组）

    >>> setVector(4,4) #生成4个4维的向量
    >>> setVector(3,4)#生成3个4维的向量
    >>> setVector(4,4,rel=True)#生成3个4维的线性相关的向量
    >>> setVector(4,4,ram=True)#生成3个4维的线性相关性随机的向量

    """
    Vectors = []
    if ram == True:
        x = (random.randint(1, 3)) % 2  # 产生0到2的随机数
        if x == 1:
            rel = False
    for i in range(n):  # 依次生成一个向量，依次添加进向量组里
        if returnlist:
            s = setMat(m, 1, minNum, maxNum).tolist()
        else:
            s = setMat(m, 1, minNum, maxNum)
        Vectors.append(s)
    if rel == True:
        if not getRelevant(Vectors):
            Vectors = setVector(n, m, minNum, maxNum, rel, ram, returnlist)
    else:
        if getRelevant(Vectors):
            Vectors = setVector(n, m, minNum, maxNum, rel, ram, returnlist)

    return Vectors

def getRelevant(vector, *args, **kwargs) -> bool:
    """
    判断向量组里的元素是否相关
    :param Vector: 向量组
    :param args:
    :param kwargs:
    :return: 线性相关返回True,线性无关返回False

    >>> test=setVector(3,3,rel=True)#产生3个3维的向量，相关性随机
    >>> test
    >>> getRelevant(test[0])
    >>> test1=setVector(3,3,ram=True)#产生3个3维的向量，相关性随机
    >>> test1
    >>> getRelevant(test1[0])
    >>> test1=setVector(3,3,ram=True)#产生3个3维的向量，相关性随机
    >>> test1
    >>> test3=[Matrix([[3],[1],[1],[1]]), Matrix([[3],[3],[1],[1]]), Matrix([[3],[3],[3],[1]]), Matrix([[6],[3],[1],[1]])]
    >>> getRelevant(test3)

    """
    vec1 = []
    for i in range(len(vector)):  # 将向量组转换成一个矩阵
        vec2 = np.array(vector)[i].reshape(1, -1)
        vec1.append(list(vec2[0]))
    vec1 = Matrix(vec1).T
    r = getmatRank(vec1)  # 计算矩阵的秩
    n = np.array(vec1).shape[0]  # 查看向量的个数
    if r < n:  # 线性相关
        return True
    else:  # 线性无关
        return False

def getmatRank(mat, *args, **kwargs) -> int:  # 求矩阵的秩
    """

    :param mat: 要求秩的矩阵
    :param args:
    :param kwargs:
    :return: 矩阵的秩

    >>> test=setMat()
    >>> getmatRank(test)
    >>> test1=setMat(3,4)
    >>> getmatRank(test1)
    >>> test2=Matrix([[5, 4, 9, 9], [4, 4, 9, 5], [9, 9, 2, 8], [9, 5, 8, 4]])
    >>> getmatRank(test2)

    """
    return int(mat.rank())

def getMatDot(mat1, mat2, *args, **kwargs):
    """
    求两向量的内积
    :param mat1: 向量组1
    :param mat2: 向量组2
    :param args:
    :param kwargs:
    :return: 返回内积

    >>> mat1=setMat(1, 3,5,20)
    >>> mat2=setMat(1, 3,5,20)
    >>> getMatDot(mat1,mat2)
    >>> mat3=Matrix([[5, 5, 17]])
    >>> mat4=Matrix([[5, 8, 6]])
    >>> getMatDot(mat3,mat4)

    """
    mat1 = np.array(mat1)
    mat2 = np.array(mat2)
    mat2 = np.transpose(mat2)
    return int(mat1.dot(mat2)[0])

def getStepMat(mat, *args, **kwargs) -> Matrix:  # 求矩阵的阶梯型
    """
    注意，这个算法只能求出矩阵的一个阶梯型，矩阵的阶梯型并不是唯一的
    :param mat: 要求阶梯型的矩阵
    :param args:
    :param kwargs:
    :return: 返回阶梯型矩阵

    >>> mat1=Matrix([[18, 1, 3, 71, -70], [6, 0, 1, 23, -23], [3, 0, 0, 10, -10], [1, 0, 0, 3, -3]])
    >>> getStepMat(mat1)

    """
    return mat.rref()[0]

def setSymMat(n=3, minVal=-10, maxVal=100, minNum=0, maxNum=10, inver=True) -> Matrix:
    """
     生成对称矩阵(二次型矩阵)
    :param n:二次型矩阵的维度
    :return:

    >>> setSymMat(4)

    """
    mat = np.array(setDetMat(n, minVal, maxVal, minNum, maxNum))  # 创建一个方阵
    mat = np.triu(mat)  # 保留其上三角部分
    mat += mat.T - np.diag(mat.diagonal())  # 将上三角”拷贝”到下三角部分
    mat = Matrix(mat)
    if inver:
        if mat.det() == 0:
            mat = setSymMat(n, minVal, maxVal, minNum, maxNum, inver)
    return mat

def setlinear_combination(n=4, minVal=0, maxVal=10, minNum=1, maxNum=6, returnlist=False) -> Tuple:
    """
    :param n:求一个向量a被n个向量表示的线性组合
    :return:（A,X,B）向量B能被向量组A表示，向量系数为X

    >>> setlinear_combination(4)

    """
    mat1 = setMat(1, n, minNum, maxNum)  # 生成一个一行四列的矩阵
    mat2 = setDetMat(n, minVal, maxVal, minNum, maxNum)
    mat = []
    for i in range(sqrt(len(mat2))):
        sum = np.array(mat2.row(i)).sum()
        mat.append(sum * mat1[i])
    if returnlist:
        mat1 = mat1.tolist()
        mat2 = mat2.tolist()
        return [mat2, mat1, mat]
    else:
        mat = Matrix(mat)
    return (mat2, mat1, mat)

def setDiagMat(n=3, minNum=-2, maxNun=6) -> Matrix:
    """
    生成对角矩阵
    :param n: 对角矩阵的维度
    :param minNum: 对角矩阵中最小的数
    :param maxNun: 对角矩阵中最大的数
    :return:随机的对角矩阵
    """
    diaList = np.array(setMat(1, n))  # 先生成对角的元素列表
    mat = np.diag(diaList[0])
    return Matrix(mat)

def setQuadratic_Matrix(n=3, inverMinNum=-1, inverMaxNum=3, minNum1=1, maxNum1=3, returnlist=False) -> Tuple:
    """
    根据对角矩阵生成对称矩阵
    :param n: 对称矩阵的维度
    :param inverMinNum:对角矩阵中最小的数
    :param inverMaxNum: 对角矩阵中最大的数
    :param minNum1: 正定矩阵中最小的数
    :param maxNum1: 正定矩阵中最大的数
    :return:返回对称矩阵,可逆矩阵，对角矩阵（可逆矩阵乘以对称矩阵乘以可逆矩阵等于对角矩阵）

    >>> test=setQuadratic_Matrix()
    >>> test[1]*test[0]*test[1]
    >>> test[2]

    """
    diaMat = setDiagMat(n, inverMinNum, inverMaxNum)  # 生成一个对角矩阵
    mat = setSymMat(n, minNum=minNum1, maxNum=maxNum1, inver=True)  # 生成一个正交矩阵(可逆矩阵)
    mat2 = getMatInver(mat).T  # 生成一个正交矩阵
    quaMat = mat2 * diaMat * getMatInver(mat)  # 对称矩阵
    if returnlist:
        diaMat = diaMat.tolist()
        mat = mat.tolist()
        quaMat = quaMat.tolist()
        return [quaMat, mat, diaMat]
    return (quaMat, mat, diaMat)  # 返回对称矩阵,可逆矩阵，对角矩阵（P可逆矩阵乘以A对称矩阵乘以P可逆矩阵等于对角矩阵）

def getOrMat(mat, orthonormal=False, *args, **kwargs) -> list:
    """
    返回正交后的矩阵(斯密斯正交化)
    :param mat:要正交化的向量组
    :param orthonormal:是否执行单位化
    :param args:
    :param kwargs:
    :return: 正交化后的向量组

    >>> test=[Matrix([[1],[1],[1]]), Matrix([[3],[3],[1]]), Matrix([[6],[2],[1]])]
    >>> getOrMat(test)

    """
    return GramSchmidt(mat, orthonormal)

def setHomEquation(long=4, wid=5, minNum=-5, maxNum=5, returnlist=False):
    """
    生成一组有无穷多解的齐次方程组
    :param long: 矩阵的行数
    :param wid:  矩阵的列数
    :param minNum: 最简矩阵包含的最小数
    :param maxNum: 最简矩阵里包含的最大数
    :param returnlist: 是否输出列表形式
    :return:

    >>> setHomEquation(3,4)

    """
    mat = setMat(long, wid, minNum, maxNum)  # 生成一个long*wid的矩阵
    mat2 = Matrix(np.zeros((1, long))).T  # 生成1*wid的零矩阵
    if (mat.rank() < mat.shape[1]):  # 判断齐次方程是否有无穷多解，如果没有则重新生成
        if returnlist:
            mat = mat.tolist()
            mat2 = mat2.tolist()
            return [mat, mat2]
        else:
            return (mat, mat2)
    else:
        setHomEquation(long, wid, minNum, maxNum)

def setNotHomEquation(long=4, wid=6, minNum=-2, maxNum=2, returnlist=False)->Tuple:
    """
    生成随机的无穷多解的增广矩阵增广矩阵
    :param long:增广矩阵的行数
    :param wid: 增光矩阵的列数
    :param minNum: 增广矩阵中最小的数
    :param maxNum: 增广矩阵中最大的数
    :param returnlist: 是否返回列表
    :return: (增广矩阵的系数，增广矩阵的值)

    >>> setNotHomEquation()

    """
    mat = setMat(long, wid, minNum, maxNum)  # 生成一个long*wid的增广矩阵
    mat2 = Matrix(np.array(mat)[:, :mat.shape[1] - 1])  # 截取增广矩阵的系数矩阵
    mat3 = mat.col(mat.shape[1] - 1)  # 截取增广矩阵的结果 and np.sum(np.array(mat3)==0)<long
    if (mat.rank() < mat.shape[1] - 1 and mat.rank() == mat2.rank() and np.sum(
            np.array(mat3) == 0) < long):  # 判断非齐次方程是否有无穷多解，如果没有则重新生成
        if returnlist:
            mat = mat.tolist()
            mat2 = mat2.tolist()
            mat3 = mat3.tolist()
            return [mat2, mat3]
        else:
            return (mat2, mat3)
    else:
        (mat2, mat3) = setNotHomEquation(long, wid, minNum, maxNum, returnlist)
        return (mat2, mat3)

def listStrToMat(listStrmat):
    """
     将str类型的array转换成int类型的Matrix(只限三维以内)
    :param listStrmat:要转换的数组
    :return:int类型的Matrix

    >>> test=[['7','7','7'],['7','7','7'],['7','7','7']]
    >>> listStrToMat(test)
    >>> test2=[[['1','2'],['1/2','1']],[['1','1/989'],['1','2']]]
    >>> listStrToMat(test2)
    >>> test3=[[['1','2/7'],['1','1']],[['1','1'],['1','2']],[['1','2'],['1','1']]]
    >>> listStrToMat(test3)
    >>> test4=['1','2/9']
    >>> listStrToMat(test4)

    """
    metrics = len(np.array(listStrmat).shape)  # 判断要转换数组的维度

    listNum = []
    if metrics == 1:  # 如果数组是一维的话直接转化
        listNum = Matrix([x for x in listStrmat]).T

    elif metrics == 2:  # 如果数组是二维的话，遍历循环两次
        for i in listStrmat:
            number = [x for x in i]
            listNum.append(number)
        listNum = Matrix(listNum)
    elif metrics == 3:  # 如果数组是三维的话，遍历循环三次
        for i in listStrmat:
            liNum = []
            for j in i:
                number = [x for x in j]
                liNum.append(number)
            listNum.append(Matrix(liNum))
    return listNum

def judge_ReverNum(topic, answer) -> bool:
    """
     判断逆序数是否正确1
    :param topic:传入一个逆序列
    :param answer: 传入要判断的答案
    :param inputStr:输入的是字符串数组
    :return:判断结果

    >>> test1=setReverNum()#生成逆序列
    >>> test1
    >>> answer=getReverNum(test1)#得test1的逆序数
    >>> answer
    >>> judge_ReverNum(test1,15)#判断逆序数是否正确
    >>> judge_ReverNum('51736248',11)

    """
    if (answer == getReverNum(topic)):
        return True
    else:
        return False
    return

def judge_DetMat(topic, answer) -> bool:
    """

    判断answer是否为行列式topic的值2
    :param topic:输入一个行列式
    :param answer: 要判断的答案
    :return: 判断结果

    >>> test1=setDetMat(3)#生成随机2维矩阵
    >>> test1
    >>> getDetMat(test1)#获取行列式的值
    >>> judge_DetMat(test1,1)#判断传入的答案是否正确
    >>> test2=Matrix([[8, 6, 3],[7, 3, 7],[6, 3, 3]])
    >>> getDetMat(test2)
    >>> judge_DetMat(test2,39)

    """
    if (answer == getDetMat(topic)):
        return True
    else:
        return False
    return

def judge_MiniMat(topic, answer) -> bool:
    """
    判断answer是否为矩阵topic的最简型3
    :param topic:输入一个矩阵
    :param answer: 要判断的答案
    :return: 判断结果

    >>> test1=setMat(3,4)
    >>> test1
    >>> getMiniMat(test1)
    >>> answer1=Matrix([[1, 0, 0,  3],[0, 1, 0, -1],[0, 0, 1, -1]])
    >>> judge_MiniMat(test1,answer1)
    >>> test2=Matrix([[9, 1, 14, 12],[3, 0,  4,  5],[1, 0,  1,  2]])
    >>> answer2=Matrix([[1, 0, 0,  3],[0, 1, 0, -1],[0, 0, 1, -1]])
    >>> getMiniMat(test2)
    >>> judge_MiniMat(test2,answer2)
    """
    if (answer == getMiniMat(topic)):
        return True
    else:
        return False
    return

def judge_inverMat(topic, answer) -> bool:
    """
    判断输入的answer是否为topic的可逆矩阵4
    :param topic:输入一个矩阵
    :param answer: 要判断的答案
    :return: 判断结果

    >>> test1=setDetMat(3, 1, 10, 0, 3, inver=True)  # 生成一个可逆矩阵4
    >>> test1
    >>> answer1=Matrix([[ 1,  0, 0],[ 0,  1, 0],[-2, -1, 1]])
    >>> judge_inverMat(test1,answer1)
    >>> test2=Matrix([[1, 0, 0],[0, 1, 0],[2, 1, 1]])
    >>> getMatInver(test2)
    >>> answer2=Matrix([[ 1,  0, 0],[ 0,  1, 0],[-2, -1, 1]])
    >>> judge_inverMat(test2,answer2)
    """
    if (answer == getMatInver(topic)):
        return True
    else:
        return False

def judge_MatAdd(topic, answer) -> bool:
    """
     判断输入的answer是否为topic相加后的矩阵5
    :param topic:输入输入两个要相加的矩阵，例如(矩阵1,矩阵2)
    :param answer: 要判断的答案
    :return: 判断结果

    >>> test1=(setDetMat(3), setDetMat(3))
    >>> test1
    >>> getAddMat(test1[0],test1[1])
    >>> answer1=Matrix([[ 5,  2,  4],[12,  3,  5],[12, 10, 13]])
    >>> judge_MatAdd(test1,answer1)
    >>> test2=(Matrix([[5, 5, 6],[8, 4, 3],[2, 4, 2]]), Matrix([[8, 3, 1],[1, 4, 0],[6, 3, 4]]))
    >>> answer2=Matrix([[13, 8, 7],[ 9, 8, 3],[ 8, 7, 6]])
    >>> judge_MatAdd(test2,answer2)
    """
    if (answer == getAddMat(topic[0], topic[1])):
        return True
    else:
        return False

def judge_MatRed(topic, answer) -> bool:
    """
    判断answer是否为topic相减后的矩阵6
    :param topic: 两个相减的矩阵，例如(矩阵1，矩阵2)
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=(setDetMat(3), setDetMat(3))
    >>> test1
    >>> answer1=Matrix([[ 2,  0,  3],[ 3, -1, -2],[-3,  1, -6]])
    >>> judge_MatRed(test1,answer1)
    >>> test2=(Matrix([[4, 7, 4],[4, 5, 2],[1, 6, 3]]), Matrix([[2, 7, 1],[1, 6, 4],[4, 5, 9]]))
    >>> answer2=Matrix([[ 2,  0,  3],[ 3, -1, -2],[-3,  1, -6]])
    >>> judge_MatRed(test2,answer2)
    """
    if (answer == getRedMat(topic[0], topic[1])):
        return True
    else:
        return False

def judge_MatMul(topic, answer) -> bool:
    """
    判断answer是否为topic乘后的矩阵7
    :param topic: 两个相乘的矩阵，例如(矩阵1，矩阵2)
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=(setMat(3, 4), setMat(4, 3))
    >>> test1
    >>> getMulMat(test1[0],test1[1])
    >>> answer1=Matrix([[84, 3, 10],[24, 1,  3],[22, 1,  3]])
    >>> judge_MatMul(test1,answer1)
    >>> test2=(Matrix([[3, 1, 12, 0],[1, 0,  3, 0],[1, 0,  2, 0]]), Matrix([[18, 1, 3],[ 6, 0, 1],[ 2, 0, 0],[ 1, 0, 0]]))
    >>> answer2=Matrix([[84, 3, 10],[24, 1,  3],[22, 1,  3]])
    >>> judge_MatMul(test2,answer2)
    """
    if (answer == getMulMat(topic[0], topic[1])):
        return True
    else:
        return False

def judge_ChuMat_num(topic, answer) -> bool:
    """
    判断answer是否为分块矩阵topic的值8
    :param topic: 分块矩阵
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=setChuMat(inver=True)  # 生成一个分块矩阵8
    >>> test1
    >>> getDetMat(test1)
    >>> judge_ChuMat_num(test1,1)
    >>> test2=Matrix([[ 0, 2,  0,  0],[-3, 4,  0,  0],[ 0, 0, -3, -4],[ 0, 0,  6,  7]])
    >>> answer2=18
    >>> judge_ChuMat_num(test2,18)
    """
    if (answer == getDetMat(topic)):
        return True
    else:
        return False

def judge_ChuMat_T(topic, answer) -> bool:
    """

    判断answer是否为分块矩阵topic的转置9
    :param topic: 一个分块矩阵
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=setChuMat(inver=True)  # 生成一个分块矩阵8
    >>> test1
    >>> answer1=Matrix([[ 1,  6, 0,  0],[-2, -5, 0,  0],[ 0,  0, 7, -5],[ 0,  0, 2, -1]])
    >>> getMatT(test1)
    >>> judge_ChuMat_T(test1,answer1)
    >>> test2=Matrix([[1, -2,  0,  0],[6, -5,  0,  0],[0,  0,  7,  2],[0,  0, -5, -1]])
    >>> answer2=Matrix([[ 1,  6, 0,  0],[-2, -5, 0,  0],[ 0,  0, 7, -5],[ 0,  0, 2, -1]])
    >>> judge_ChuMat_T(test2,answer2)
    """
    if (answer == getMatT(topic)):
        return True
    else:
        return False

def judge_SolveEquations(topic, answer) -> bool:
    """
    判断answer是否为topic相减后的矩阵9(矩阵方程 AX=B求解)10
    :param topic: (矩阵A,矩阵X,矩阵B)
    :param answer: 需要判断的答案
    :return:判断的结果
    >>> test1=setSolveEquations()
    >>> test1
    >>> answer1= Matrix([[2, 1],[2, 0],[1, 0]])
    >>> judge_SolveEquations(test1,answer1)
    >>> test2=(Matrix([[ 2, -2,  2],[-2,  1, -3],[-1,  1, -2]]), Matrix([[4, 1],[2, 0],[1, 0]]), Matrix([[ 6,  2],[-9, -2],[-4, -1]]))
    >>> answer2= Matrix([[4, 1],[2, 0],[1, 0]])
    >>> judge_SolveEquations(test2,answer2)
    """
    if (answer == topic[1]):
        return True
    else:
        return False

def judge_matRel(topic, answer) -> bool:
    """
    判断向量组topic是否线性相关，answer是否判断正确11
    :param topic: 一个向量组
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=setVector(4, 4, ram=True)  # 生成4个4维的线性相关性随机的向量组11
    >>> test1
    >>> getRelevant(test1)
    >>> judge_matRel(test1,True)
    >>> test2=[Matrix([[6],[3],[3],[1]]), Matrix([[3],[1],[1],[1]]), Matrix([[6],[6],[3],[1]]), Matrix([[6],[6],[2],[1]])]
    >>> judge_matRel(test2,False)
    """
    if answer == '是':
        answer = True
    if answer == '否':
        answer = False
    if (answer == getRelevant(topic)):
        return True
    else:
        return False

def judge_RankMat(topic, answer) -> bool:  # 判断秩是否正确
    """
    判断answer是否为矩阵topic的秩12
    :param topic: 一个矩阵
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=setMat(3, 4)  # 生成一个4行5列的矩阵12
    >>> test1
    >>> getmatRank(test1)
    >>> judge_RankMat(test1,2)
    >>> test2=Matrix([[6, 1, 16, 60],[3, 0,  7, 27],[1, 0,  2,  8]])
    >>> getmatRank(test2)
    >>> judge_RankMat(test2,3)
    """
    if (answer == getmatRank(topic)):
        return True
    else:
        return False

def judge_MatDot(topic, answer) -> bool:  # 判断内积是否正确
    """
    判断answer是否为向量组topic的内积13
    :param topic: 两个一维向量，例(一维向量1,一维向量2)
    :param answer: 需要判断的答案
    :return:判断的结果
    >>> test1=(setMat(1, 3, 5, 20), setMat(1, 3, 5, 20))  # 生成两个向量13
    >>> test1
    >>> getMatDot(test1[0], test1[1])
    >>> judge_MatDot(test1,9)
    >>> test2=(Matrix([[5, 20, 12]]), Matrix([[18, 15, 17]]))
    >>> judge_MatDot(test2,594)

    """
    if (answer == getMatDot(topic[0], topic[1])):
        return True
    else:
        return False

def judge_StepMat(topic, answer) -> bool:  # 判断矩阵的阶梯型是否正确，矩阵的阶梯型并不是唯一的，所以通过阶梯型的最简型来判断，矩阵的最简型是唯一的
    """
    判断answer是否为矩阵topic的阶梯型14
    :param topic:一个矩阵
    :param answer: 需要判断的答案
    :return:判断的结果
    >>> test1=setMat(3, 4)  # 生成一个矩阵14
    >>> test1
    >>> getStepMat(test1)
    >>> answer1=Matrix([[1, 0, 0, 1],[0, 1, 0, 0],[0, 0, 1, 0]])
    >>> judge_StepMat(test1,answer1)
    >>> test2= Matrix([[6, 1, 12, 6],[2, 0,  3, 2],[1, 0,  1, 1]])
    >>> answer2=Matrix([[1, 0, 0, 1],[0, 1, 0, 0],[0, 0, 1, 0]])
    >>> judge_StepMat(test2,answer2)
    """
    if (answer == getMiniMat(topic)):
        return True
    else:
        return False

def judge_SymMat(topic, answer) -> bool:
    """
    判断answer是否为二次型矩阵topic的秩15
    :param topic:一个二次型矩阵
    :param answer: 需要判断的答案
    :return:判断的结果
   >>> test1= setSymMat(4)  # 生成一个二次矩阵15
   >>> test1
   >>> getmatRank(test1)
   >>> judge_SymMat(test1,1)
   >>> test2= Matrix([[7, 7, 2, 7],[7, 4, 7, 0],[2, 7, 4, 7],[7, 0, 7, 1]])
   >>> judge_SymMat(test2,4)
    """
    if (answer == getmatRank(topic)):
        return True
    else:
        return False

def judge_linear_combination(topic, answer) -> bool:  # 判断表示的系数是否正确
    """
    判断answer是否为向量组topic[A]的线性组合16
    :param topic:（A,X,B）向量B能被向量组A表示，向量系数为X
    :param answer: 需要判断的答案
    :return:判断的结果

    >>> test1=setlinear_combination()  # 生成（A,X,B）向量B能被向量组A表示，向量系数为X---------16
    >>> test1
    >>> answer1=Matrix([[1, 1, 5, 1]])
    >>> judge_linear_combination(test1,test1)
    >>> test2=(Matrix([[4, 3, 3, 5],[3, 2, 2, 2],[5, 3, 5, 3],[5, 3, 5, 3]]), Matrix([[1, 1, 5, 1]]), Matrix([[15],[ 9],[80],[16]]))
    >>> answer2=Matrix([[1, 1, 5, 1]])
    >>> judge_linear_combination(test2,answer2)
    """
    if (answer == topic[1]):
        return True
    else:
        return False

def judge_diaMat(topic, answer) -> bool:  # 判断PAP是否为对角阵,对称矩阵对角化后的对角元素不按顺序的话是唯一的
    """
    判断可逆矩阵answer是否为topic的答案17
    :param topic:(对称矩阵，可逆矩阵，对角矩阵)
    :param answer: 需要判断的答案
    :return:判断的结果
    >>> test1=setQuadratic_Matrix()  # 已知对称矩阵A，求一可逆矩阵P，使得PAP为对角阵17
    >>> test1
    >>> answer1=Matrix([[2, 1, 1],[1, 1, 2],[1, 2, 2]])
    >>> judge_diaMat(test1,answer1)
    >>> test2= (Matrix([[0,  0,  0],[0,  1, -1],[0, -1,  1]]), Matrix([[2, 1, 2],[1, 1, 1],[2, 1, 1]]), Matrix([[1, 0, 0],[0, 0, 0],[0, 0, 0]]))
    >>> answer2=Matrix([[2, 1, 2],[1, 1, 1],[2, 1, 1]])
    >>> judge_diaMat(test2,answer2)

    """
    answerDia = answer * topic[0] * answer  # 先计算可逆矩阵与对称矩阵与可逆矩阵相乘的结果

    for i in range(len(np.array(answerDia))):  # 先判断answerDia是不是对角矩阵
        for j in range(len(np.array(answerDia)[i])):
            if i != j and np.array(answerDia)[i][j] != 0:
                return False
    diaList = []  # 取出答案的生成的对角矩阵的元素列表
    answerList = []  # 取出题目的答案的对角矩阵的元素
    for i in range(len(np.array(answerDia))):  # 取出答案的生成的对角矩阵的元素
        diaList.append(np.array(answerDia)[i][i])
    for i in range(len(np.array(topic[2]))):  # 取出题目的答案的对角矩阵的元素
        answerList.append(np.array(topic[2])[i][i])
    for i in answerDia:  # 如果题目答案的对角矩阵不在计算后的对角矩阵中，则错误
        if i not in diaList:
            return False
    return True  # 如果符合以上条件则正确

def judge_orVector(topic, answer) -> bool:  # 判断正交后的矩阵是否正确（的进行单位化）,必须按照顺序
    """
    判断向量组topic正交化后的向量组是否为answer18
    :param topic:向量组
    :param answer: 需要判断的答案
    :return:判断的结果
    >>> test1=setVector(3, 3, rel=False)
    >>> test1
    >>> getOrMat(test1)
    >>> test2=[Matrix([[4],[2],[1]]), Matrix([[2],[2],[1]]), Matrix([[6],[3],[1]])]
    >>> answer2=[Matrix([[4],[2],['1']]), Matrix([['-10/21'],[ '16/21'],[ '8/21']]), Matrix([[0],[ '1/5'],['-2/5']])]
    >>> judge_orVector(test2,answer2)
    """
    # list = []
    # for i in range(np.shape(np.array(answer))[1]):  # 把输入的矩阵化成向量组
    #     list.append(Matrix(np.array(answer).col(i)))
    # answer = list
    print(answer)
    print(getOrMat(topic))
    if (answer == getOrMat(topic)):
        return True
    else:
        return False

def judge_HomEquationUntie(topic, answer) -> bool:
    """

    判断answer是否为齐次方程组topic的解19
    :param topic:齐次方程组,例(系数矩阵，系数矩阵的值)
    :param answer: 需要判断的答案
    :return:判断的结果
    >>> test1=(Matrix([[-2], [1], [-1], [0], [1]]),Matrix([[0], [0], [0]]))
    >>> test1
    >>> answer1=[Matrix([-1,1,0,0,0]),Matrix([-1,0,-1,0,1])]
    >>> judge_HomEquationUntie(test1,answer1)
    """

    sum = answer[0]
    for i in range(len(answer) - 1):  # 将所有的基础解系加起来,验算解
        sum = getAddMat(sum, answer[i + 1])
    if (topic[0] * sum == topic[1]):  # 相乘判断是否为解
        return True
    else:
        return False

def judge_HomEquationUntie(topic, answer) -> bool:
    """
    判断answer是否为非齐次方程组topic的值20
    :param topic:非齐次方程组,例(系数矩阵，系数矩阵的值)
    :param answer: 需要判断的答案
    :return:判断的结果

    """

    sum = answer[0]
    for i in range(len(answer) - 1):  # 将所有的基础解系加起来,验算解
        sum = getAddMat(sum, answer[i + 1])
    if (topic[0] * sum == topic[1]):  # 相乘判断是否为解
        return True
    else:
        return False

if __name__ == '__main__':  # 主函数测试
    pass


import discord
from discord.ext import commands

class Admin(commands.Cog):

    def __init__(self, bot):
        self.bot = bot

    @commands.command(name="kick")
    @commands.has_permissions(kick_members=True)
    async def kick(self, ctx, member : discord.Member, *, reason=None):
        try:
            await member.kick(reason=reason)
            embed = discord.Embed(color=discord.Colour.dark_orange(), title="", description="")
            embed.add_field(name="Kicked:",

                value=f"""
                **{member}** has been kicked.
                Reason: **{reason}**
                """, inline=True)

            await ctx.reply(embed=embed)

        except discord.Forbidden:
            await ctx.send(f'You do not have the proper permissions to kick.')
        except discord.HTTPException:
            await ctx.send(f'Kicking failed.')

    @commands.command(name="ban")
    @commands.has_permissions(administrator=True)
    async def ban(self, ctx, member : discord.Member, *, reason=None):
        try:
            await member.ban(reason=reason)
            embed = discord.Embed(color=discord.Colour.dark_red(), title="", description="")
            embed.add_field(name="Banned:",

                value=f"""
                **{member}** has been **banned** from the server.
                Reason: **{reason}**
                """, inline=True)

            await ctx.reply(embed=embed)

        except discord.Forbidden:
            await ctx.send(f'You do not have the proper permissions to ban.')
        except discord.NotFound:
            await ctx.send(f'{member} not found!')
        except discord.HTTPException:
            await ctx.send('Banning Failed.')

    @commands.command(name="unban")
    @commands.has_permissions(administrator=True)
    async def unban(self, ctx, *, member: discord.User, reason=None):
        try:
            await ctx.guild.unban(member)
            embed = discord.Embed(color=discord.Colour.dark_red(), title="", description="")
            embed.add_field(name="Unbanned:",

                value=f"""
                **{member}** has been **unbanned** from the server.
                Reason: **{reason}**
                """, inline=True)

            await ctx.reply(embed=embed)

        except discord.NotFound:
            await ctx.send(f'{member} not found!')
        except discord.Forbidden:
            await ctx.send(f'Do not have proper permission to unban!')
        except discord.HTTPException:
            await ctx.send(f'Unbanning failed.')

    @commands.command(name="clear")
    @commands.has_permissions(administrator=True)
    async def clear(self, ctx, amount=1):
        if amount > 100:
            user = ctx.author.name
            await ctx.send(f"{user}, you cannot clear that amount!")
        else:
            await ctx.channel.purge(limit=amount + 1)

async def setup(bot):
    await bot.add_cog(Admin(bot))