import os
import time
import sys
import copy
import numpy as np
import tensorflow as tf
import threading
import random
import argparse
import time
import logging

from logging.handlers import RotatingFileHandler
from logging import handlers
from bio_tools import *

#from tensorflow.python.client import timeline
from general_utils import minibatches, Progbar, get_cpu_loads, get_gpu_loads_and_efficiency

current_milli_time = lambda: int(round(time.time() * 1000))

parser = argparse.ArgumentParser(description='Train a morphing or tagging model.')
# Required positional argument

parser.add_argument('model_type', type=str,
                    help='morph or tag')
parser.add_argument('model_data_dir', type=str,
                    help='Data directory to load training data from (data/model_data_dir/model_type_{train,dev,test}.txt')
parser.add_argument('--model-output-dir', type=str, default=None,
                    help='Model directory to store or load checkpoints from')
parser.add_argument('--use-crf', action='store_true', default=False,
                    help='Add a CRF (Viterbi) layer after the Bi-LSTM (default off)')
parser.add_argument('--batch-size', type=int, default=-1,
                    help='Batch size (default NUM_GPUS*8 with CRF, NUM_GPUS*128 without CRF)')
parser.add_argument('--no-shuffle', action='store_true', default=False,
                    help='Don\'t shuffle before each epoch (default: off; shuffle)')
parser.add_argument('--max-epochs', type=int, default=100,
                    help='Maximum training epochs (default 100). Early stopping occurs automatically after 3 epochs anyway.')
parser.add_argument('--max-sequence-length', type=int, default=400,
                    help='Maximum sequence length for input into Bi-LSTM (default 400)')
parser.add_argument('--use-static-padding', action='store_true', default=False,
                    help='Use same max sequence length for every batch (inefficient, default off)')
parser.add_argument('--num-gpus', type=int, default=1,
                    help='Number of GPUs to use (default 1)')
parser.add_argument('--input-unit-embedding-size', type=int, default=300,
                    help='Embedding size for input unit (default 300)')
parser.add_argument('--combined-hidden-size', type=int, default=300,
                    help='Combined hidden size for input into LSTM (default 300)')
parser.add_argument('--dropout-keep-prob', type=float, default=0.7,
                    help='Dropout keep probability (default 0.7: which means 30%% dropout)')
parser.add_argument('--lstm-style', type=str, default='tf',
                    help='LSTM style: tf or cudnn (default tf). cuDNN may provide performance increases')

args = parser.parse_args()

assert args.lstm_style in ['tf', 'cudnn']

#if args.lstm_style == 'cudnn':
#    assert args.use_static_padding == True, 'must enable static padding when using cuDNN LSTM: variable sequence lengths are not supported by TensorFlow'

# important to specify soft placement parameter for EVERY instance of tf.Session
# apparently, or otherwise it doesn't really take effect
SOFT_PLACEMENT = True
NUM_GPUS = args.num_gpus

if args.batch_size == -1:
    if args.use_crf:
        args.batch_size = NUM_GPUS*8
    else:
        args.batch_size = NUM_GPUS*128

# FIXME: for now we start with 0
# rest of code may need to be fixed as well to use GPU_IDS
GPU_IDS = [i for i in range(NUM_GPUS)]

USE_BATCH_NORMALIZATION = False
USE_DYNAMIC_PADDING = not args.use_static_padding

# assume equal work
SPLIT_PROPORTIONS = []
for i in range(NUM_GPUS):
    SPLIT_PROPORTIONS.append(1)

#SPLIT_PROPORTIONS = [1] # equal work
#SPLIT_PROPORTIONS = [3, 1] # put 3x more work on GPU:0
#SPLIT_PROPORTIONS = [20, 15] # put 20/35 work on GPU:0, 15/35 work on GPU:1

assert len(SPLIT_PROPORTIONS) == NUM_GPUS

do_monitor = False

# amatteson@blp-deep-server-1:~/newunitag$ LD_PRELOAD=/home/amatteson/cudnn-8.0-6.0/cuda/lib64/libcudnn.so.6 python3 model.py
# TODO: try limiting sentence size to max size per-batch (could be much more efficient)
# TODO: add joint training of two different models (morphing and tagging)
# TODO: fix multi-gpu splitting of last remainder batch (one gpu might be left with no samples)
#       might be able to train with garbage data and ignore gradients calculated for the gpus
#       with no real batches
# TODO: check out transition params for crf and whether it's safe for this to be split across GPUs
#       like it currently is in the code (UPDATE: this is just a temporarily used parameter, but it is
#       in the trainable parameter list...a bit odd)
#       seems like it may need to be stuck on one GPU...hmmm....maybe? maybe the way we had it before was
#       ok with each GPU updating it as time went on...just the same variable??

# maximum input units (pad until this number; disallow longer input)
MAX_UNIT_COUNT = args.max_sequence_length

'''
Get a model name that might make sense given the specified parameters
'''
def get_model_name():
    arch = ''
    if args.use_crf:
        arch = 'bilstm_crf'
    else:
        arch = 'bilstm'
    if args.use_static_padding:
        seq_type = 'static'
    else:
        seq_type = 'dynamic'
    return '%s_%s_%s_%s_%s_ES%d_CH%d_DK%d_SEQ%d_B%d_EP%d' % (args.model_data_dir, args.model_type, arch, args.lstm_style, seq_type, args.input_unit_embedding_size, args.combined_hidden_size, 100.0*args.dropout_keep_prob, args.max_sequence_length, args.batch_size, args.max_epochs)

if args.model_output_dir == None:
    args.model_output_dir = get_model_name()

if not args.model_output_dir.endswith('/'):
    args.model_output_dir += '/'

# will throw error if already exists to alert user
#try:
os.mkdir(args.model_output_dir)
#except:
#    pass

class ModelConfig(object):
    def __init__(self):
        self.input_unit_embedding_sizes = None
        self.do_unit_embedding_training = None
        self.dropout_keep_prob = None
        self.combined_hidden_size = None
        self.nepochs = None
        self.nepoch_no_imprv = None
        self.learning_rate = None
        self.lr_decay = None
        self.crf = None
        self.output_path = None
        self.model_output = None
        self.log_path = None
        self.batch_size = None

config = ModelConfig()
config.output_path = args.model_output_dir
config.model_output = config.output_path + 'output/'

#try:
os.mkdir(config.model_output)
#except:
#    pass

# TODO: fix to train or eval depending on mode
config.log_path = config.model_output + 'train.log'

#log_format = logging.Formatter('%(asctime)s : %(levelname)s : [%(name)s] : %(message)s')
#logging.basicConfig(filename=config.log_path, filemode='w', level=logging.INFO)
'''logger = logging.getLogger('Model')
ch = logging.StreamHandler(sys.stdout)
ch.setFormatter(log_format)
logger.addHandler(ch)
fh = handlers.RotatingFileHandler(config.log_path, maxBytes=(1048576*500), backupCount=7)
fh.setFormatter(log_format)
logger.addHandler(fh)
'''

logging.basicConfig(
    format='%(asctime)s : %(levelname)s : [%(name)s] : %(message)s',
    level=logging.INFO,
    handlers=[
        logging.FileHandler(config.log_path, mode='w'), # overwrite log
        logging.StreamHandler(sys.stdout)
    ]
)

logger = logging.getLogger('Model')

logger.info('Model data: data/%s/%s_{train,dev,test}.txt' % (args.model_data_dir, args.model_type))
logger.info('Model output directory: %s' % args.model_output_dir)
if NUM_GPUS > 1:
    logger.info('Model batch size: %d (batch will be split amongst %d GPUs with proportions: %s)' % (args.batch_size, NUM_GPUS, str(SPLIT_PROPORTIONS)))
else:
    logger.info('Model batch size: %d' % args.batch_size)

fixed_batch_split_sizes = []
if NUM_GPUS > 1:
    for i in range(NUM_GPUS - 1):
        fixed_batch_split_sizes.append(int(args.batch_size * float(SPLIT_PROPORTIONS[i]) / float(sum(SPLIT_PROPORTIONS))))

    # add remainder
    fixed_batch_split_sizes.append(args.batch_size - sum(fixed_batch_split_sizes))
else:
    fixed_batch_split_sizes.append(args.batch_size)

if USE_DYNAMIC_PADDING:
    padding_type = 'per-batch dynamic'
else:
    padding_type = 'static'

logger.info('Model max sequence length: %d (%s padding)' % (MAX_UNIT_COUNT, padding_type))
if args.use_crf:
    logger.info('Model CRF enabled: yes')
else:
    logger.info('Model CRF enabled: no')

logger.info('Combined input unit embedding size: %d' % args.input_unit_embedding_size)
logger.info('Combined hidden layer unit size: %d' % args.combined_hidden_size)
logger.info('Dropout keep probability: %f' % args.dropout_keep_prob)
logger.info('Model max epochs: %d' % args.max_epochs)

def average_gradients(tower_grads):
    '''Calculate the average gradient for each shared variable across all towers.
    Note that this function provides a synchronization point across all towers.
    Args:
        tower_grads: List of lists of (gradient, variable) tuples. The outer list
            is over individual gradients. The inner list is over the gradient
            calculation for each tower.
    Returns:
         List of pairs of (gradient, variable) where the gradient has been averaged
         across all towers.
    '''
    average_grads = []
    for grad_and_vars in zip(*tower_grads):
        # Note that each grad_and_vars looks like the following:
        #     ((grad0_gpu0, var0_gpu0), ... , (grad0_gpuN, var0_gpuN))
        grads = []
        for g, _ in grad_and_vars:
            # Add 0 dimension to the gradients to represent the tower.
            expanded_g = tf.expand_dims(g, 0)

            # Append on a 'tower' dimension which we will average over below.
            grads.append(expanded_g)

        # Average over the 'tower' dimension.
        grad = tf.concat(axis=0, values=grads)
        grad = tf.reduce_mean(grad, 0)

        # Keep in mind that the Variables are redundant because they are shared
        # across towers. So .. we will just return the first tower's pointer to
        # the Variable.
        v = grad_and_vars[0][1]
        grad_and_var = (grad, v)
        average_grads.append(grad_and_var)
    return average_grads

# Small epsilon value for the BN transform
epsilon = 1e-3

def batch_norm_wrapper(inputs, is_training, decay = 0.999):
    scale = tf.get_variable('batch_norm_scale',
                    dtype=tf.float32, initializer=tf.ones([inputs.get_shape()[-1]]))
    beta = tf.get_variable('batch_norm_beta',
                    dtype=tf.float32, initializer=tf.zeros([inputs.get_shape()[-1]]))
    pop_mean = tf.get_variable('batch_norm_pop_mean',
                    dtype=tf.float32, initializer=tf.zeros([inputs.get_shape()[-1]]), trainable=False)
    pop_var = tf.get_variable('batch_norm_pop_var',
                    dtype=tf.float32, initializer=tf.ones([inputs.get_shape()[-1]]), trainable=False)

    if is_training:
        batch_mean, batch_var = tf.nn.moments(inputs,[0])
        train_mean = tf.assign(pop_mean,
                               pop_mean * decay + batch_mean * (1 - decay))
        train_var = tf.assign(pop_var,
                              pop_var * decay + batch_var * (1 - decay))
        with tf.control_dependencies([train_mean, train_var]):
            return tf.nn.batch_normalization(inputs,
                batch_mean, batch_var, beta, scale, epsilon)
    else:
        return tf.nn.batch_normalization(inputs,
            pop_mean, pop_var, beta, scale, epsilon)

# TODO: joint model

'''
class JointModel(object):
    def __init__(self):
        self.models = []

    def add_submodel(self, submodel):
        self.models.append(submodel)

    def add_joint_loss_op(self):
        self.joint_loss = tf.sum(self.
'''

class MorphingTaggingModel(object):
    def __init__(self, lexicon, config):
        self.logger = logging.getLogger('MorphingTaggingModel')
        self.lexicon = lexicon
        self.config = config
        self.idx_to_unit0, self.unit0_to_idx = self.lexicon.get_input_lexicon_for_training(0)
        self.idx_to_label, self.label_to_idx = self.lexicon.get_label_lexicon_for_training()

        self.add_placeholders()
        self.add_embeddings_op()

        for g in range(NUM_GPUS):
            # this way, parameters will be shared amongst training and inference
            with tf.variable_scope('tower%d_arch' % g):
                self.add_logits_ops(gpu_num=g, is_training=True)
                tf.get_variable_scope().reuse_variables()
                self.add_logits_ops(gpu_num=g, is_training=False)

        self.add_pred_ops()
        self.add_loss_ops()
        self.add_train_op()
        self.add_init_op()

        self.logger.info('Trainable variables')

        with tf.Session(config=tf.ConfigProto(allow_soft_placement=SOFT_PLACEMENT)) as sess:
            sess.run(self.init)
            #options = tf.RunOptions(trace_level=tf.RunOptions.FULL_TRACE)
            #run_metadata = tf.RunMetadata()
            tvars = tf.trainable_variables()
            tvars_vals = sess.run(tvars)
                     #options=options,
                     #run_metadata=run_metadata)

            #fetched_timeline = timeline.Timeline(run_metadata.step_stats)
            #chrome_trace = fetched_timeline.generate_chrome_trace_format()
            #with open('timeline_debug.json', 'w') as f:
            #    f.write(chrome_trace)

            for var, val in zip(tvars, tvars_vals):
                self.logger.info('... %s' % var.name) # , val)  # Prints the name of the variable alongside its value.

    def add_placeholders(self):
        '''
        Adds placeholders to self
        '''
        # words or morphemes
        # shape = (batch size, max length of sentence in batch)
        self.unit0_ids = tf.placeholder(tf.int32, shape=[self.config.batch_size, None],
                        name='unit0_ids')

        # number of "units" (words or morphemes)
        # shape = (batch size)
        self.sequence_lengths = tf.placeholder(tf.int32, shape=[self.config.batch_size],
                        name='sequence_lengths')

        # shape = (batch size, max length of sentence in batch)
        self.labels = tf.placeholder(tf.int32, shape=[self.config.batch_size, None],
                        name='labels')

        # hyper parameters
        # dropout keep probability (1.0 means keep all data)
        self.dropout_keep_prob = tf.placeholder(dtype=tf.float32, shape=[],
                        name='dropout_keep_prob')

        # learning rate
        self.learning_rate = tf.placeholder(dtype=tf.float32, shape=[],
                        name='learning_rate')

        # split up batch for multi-gpu
        self.batch_split_sizes = tf.placeholder(tf.int32, shape=[NUM_GPUS], name='batch_split_sizes')
        self.split_unit0_ids = tf.split(self.unit0_ids, self.batch_split_sizes, axis=0)
        self.split_sequence_lengths = tf.split(self.sequence_lengths, self.batch_split_sizes, axis=0)
        self.split_labels = tf.split(self.labels, self.batch_split_sizes, axis=0)

        # used at inference time
        self.joined_labels = tf.concat(self.split_labels, axis=0)

        self.transition_params = []
        self.logits_train = []
        self.logits_infer = []
        self.loss = []
        self.labels_pred = []
        self.init_h = []
        self.init_c = []

        for _ in range(NUM_GPUS):
            self.transition_params.append(None)
            self.logits_train.append(None)
            self.logits_infer.append(None)
            self.loss.append(None)
            self.labels_pred.append(None)
            self.init_h.append(None)
            self.init_c.append(None)

        self.standard_trainable_variables = []

    def add_embeddings_op(self):
        '''
        Adds embeddings to self (for first unit)
        '''

        combined_embeddings = None

        with tf.variable_scope('unit0'):
            # initialize unit 0's vocabulary embeddings as random normal distribution
            unit0_initial_value = tf.random_normal([len(self.idx_to_unit0),
                                                   self.config.input_unit_embedding_sizes[0]],
                stddev=1.0 / (self.config.input_unit_embedding_sizes[0]**.5), \
                seed=0)

            _unit0_embeddings = tf.get_variable(initializer=unit0_initial_value,
                                           name='_unit0_embeddings', dtype=tf.float32,
                                           trainable=self.config.do_unit_embedding_training[0])

            if self.config.do_unit_embedding_training[0]:
                self.standard_trainable_variables.append(_unit0_embeddings)

            combined_embeddings = tf.nn.embedding_lookup(_unit0_embeddings,
                                                         self.unit0_ids,
                                                         name='unit0_embeddings')

            self.logger.info('... Adding unit 0 embeddings with shape: ' + \
                  str(combined_embeddings.get_shape()))

        self.logger.info('Concatenated embedding shape: ' + \
                  str(combined_embeddings.get_shape()))

        # dropout created in static graph: takes effect if necessary when
        # referring to self.combined_embeddings
        self.combined_embeddings = tf.nn.dropout(combined_embeddings,
                                       keep_prob=self.dropout_keep_prob)

        # TODO: evaluate if there's a better way to do this.
        # is it even necessary to split up unit0_ids when we have this var?
        self.split_combined_embeddings = tf.split(self.combined_embeddings,
                                       self.batch_split_sizes, axis=0)

    def add_pred_ops(self):
        '''
        Adds labels_pred to self
        '''
        for gpu_num in range(NUM_GPUS):
            with tf.device('/gpu:%d' % gpu_num):
                if not self.config.crf:
                    self.labels_pred[gpu_num] = tf.cast(tf.argmax(self.logits_infer[gpu_num], axis=-1), tf.int32)

    def add_loss_ops(self):
        '''
        Adds loss to self
        '''
        for gpu_num in range(NUM_GPUS):
            with tf.device('/gpu:%d' % gpu_num):
                if self.config.crf:
                    with tf.variable_scope('tower%d_crf' % gpu_num):
                        log_likelihood, self.transition_params[gpu_num] = tf.contrib.crf.crf_log_likelihood(
                            self.logits_train[gpu_num], self.split_labels[gpu_num], self.split_sequence_lengths[gpu_num])
                    self.loss[gpu_num] = tf.reduce_mean(-log_likelihood)
                else:
                    losses = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.logits_train[gpu_num], labels=self.split_labels[gpu_num])
                    mask = tf.sequence_mask(self.split_sequence_lengths[gpu_num])
                    losses = tf.boolean_mask(losses, mask)
                    self.loss[gpu_num] = tf.reduce_mean(losses)

                #tf.add_to_collection('losses', self.loss)

                # for tensorboard
                tf.summary.scalar('loss_%d' % gpu_num, self.loss[gpu_num])

    def add_logits_ops(self, gpu_num, is_training):
        '''
        Adds logits to self
        '''

        assert not USE_BATCH_NORMALIZATION, 'USE_BATCH_NORMALIZATION needs fix below for is_training'

        # number of output labels
        nlabels = len(self.idx_to_label)

        #if is_training:
        #    # reuse can't be false...so???
        #    do_reuse = tf.AUTO_REUSE
        #else:
        #    do_reuse = True

        with tf.device('/gpu:%d' % gpu_num):
            if args.lstm_style == 'tf':
                # self.split_combined_embeddings[gpu_num]: input shape:[64 400 300]
                lstm_cell = tf.contrib.rnn.LSTMCell(num_units=self.config.combined_hidden_size, state_is_tuple=True)
                (output_fw, output_bw), _ = tf.nn.bidirectional_dynamic_rnn(lstm_cell,
                    lstm_cell, self.split_combined_embeddings[gpu_num], sequence_length=self.split_sequence_lengths[gpu_num], dtype=tf.float32)

                output = tf.concat([output_fw, output_bw], axis=-1)

                #output = tf.Print(output, [tf.shape(output)], message='output shape:')
                # print('output.get_shape()', output.get_shape().as_list())
                # [64 400 600]
            elif args.lstm_style == 'cudnn':
                combined_embeddings_input = self.split_combined_embeddings[gpu_num]

                self.logger.debug('combined_embeddings_input shape: %s' % str(combined_embeddings_input.get_shape().as_list()))

                #combined_embeddings_input = tf.reshape(combined_embeddings_input, [-1, MAX_UNIT_COUNT, sum(self.config.input_unit_embedding_sizes)])
                combined_embeddings_input = tf.reshape(combined_embeddings_input, [fixed_batch_split_sizes[gpu_num], -1, sum(self.config.input_unit_embedding_sizes)])
                combined_embeddings_input = tf.transpose(combined_embeddings_input, [1, 0, 2])

                self.logger.debug('combined_embeddings_input transpose: %s' % str(combined_embeddings_input.get_shape().as_list()))

                num_lstm_layers = 1

                cudnn_cell = tf.contrib.cudnn_rnn.CudnnLSTM(
                                                          num_layers=num_lstm_layers,
                                                          num_units=self.config.combined_hidden_size,
                                                          direction='bidirectional',
                                                          input_mode='linear_input',
                                                          input_size=self.config.combined_hidden_size,
                                                          dtype=tf.float32
                                                          # TODO: dropout here?
                                                          )

                # bizarre: init_h,init_c require first dim to be 2 because it gets divided by dir_count(2 when bidirectional)
                # model_shapes->num_layers = (*input_h)->dim_size(0) / model_shapes->dir_count;
                # otherwise first dim ends up being 0.5 (0 as int)

                # don't change batch_split_sizes later
                self.logger.debug('fixed_batch_split_sizes[%d]: %d' % (gpu_num, fixed_batch_split_sizes[gpu_num]))

                self.init_h[gpu_num] = tf.get_variable('h', dtype=tf.float32, initializer=tf.zeros([2*num_lstm_layers, fixed_batch_split_sizes[gpu_num], self.config.combined_hidden_size]))
                self.init_c[gpu_num] = tf.get_variable('c', dtype=tf.float32, initializer=tf.zeros([2*num_lstm_layers, fixed_batch_split_sizes[gpu_num], self.config.combined_hidden_size]))

                # FIXME: tweak init_scale
                params_size_t = cudnn_cell.params_size()

                self.logger.debug('params_size_t: %s' % str(params_size_t))

                cudnn_params = tf.get_variable('lstm_params',
                         initializer=tf.random_uniform([params_size_t], -0.04, 0.04), validate_shape=False)

                outputs, h, c = cudnn_cell(
                    combined_embeddings_input,
                    input_h=self.init_h[gpu_num],
                    input_c=self.init_c[gpu_num],
                    params=cudnn_params,
                    is_training=is_training
                )

                self.logger.debug('outputs.get_shape(): %s' % str(outputs.get_shape().as_list()))
                self.logger.debug('h.get_shape(): %s' % str(h.get_shape().as_list()))
                self.logger.debug('c.get_shape(): %s' % str(c.get_shape().as_list()))

                output = tf.transpose(outputs, [1, 0, 2])

                self.logger.debug('after transpose output.get_shape(): %s' % str(output.get_shape().as_list()))

            output = tf.nn.dropout(output, self.dropout_keep_prob)

        with tf.device('/gpu:%d' % gpu_num):
            #with tf.variable_scope('tower%d_proj' % gpu_num, reuse=tf.AUTO_REUSE):
            W = tf.get_variable('tower%d_W' % gpu_num, shape=[2*self.config.combined_hidden_size, nlabels],
                dtype=tf.float32, initializer=tf.random_normal_initializer( \
                    stddev=1.0 / (2*self.config.combined_hidden_size)**.5, \
                    seed=0))

            b = tf.get_variable('tower%d_b' % gpu_num, shape=[nlabels], dtype=tf.float32,
                initializer=tf.zeros_initializer())

            ntime_steps = tf.shape(output)[1]
            output = tf.reshape(output, [-1, 2*self.config.combined_hidden_size])
            #output = tf.Print(output, [tf.shape(output)], 'after proj reshape:')
            #print('after proj reshape:', output.get_shape().as_list())
            # after proj reshape:[25600 600]
            pred = tf.matmul(output, W) + b

            #if USE_BATCH_NORMALIZATION:
            #    pred_batch_norm = batch_norm_wrapper(pred, is_training)
            #else:
            pred_batch_norm = pred

            logits = tf.reshape(pred_batch_norm, [-1, ntime_steps, nlabels])

            if is_training:
                #logits = tf.Print(logits, [logits], 'logits_train:')
                self.logits_train[gpu_num] = logits
            else:
                #logits = tf.Print(logits, [logits], 'logits_infer:')
                self.logits_infer[gpu_num] = logits

    def add_train_op(self):
        '''
        Add train_op to self
        '''
        root_scope = tf.get_default_graph().get_name_scope()
        all_trainable = tf.get_collection(tf.GraphKeys.TRAINABLE_VARIABLES, root_scope)

        self.logger.debug('Root scope: %s' % str(root_scope))

        optimizer = tf.train.AdamOptimizer(self.learning_rate)

        self.global_step = tf.get_variable('global_step', [], initializer=tf.constant_initializer(0), trainable=False)

        tower_grads = []
        for i in range(NUM_GPUS):
            with tf.device('/gpu:%d' % i):
                logits = self.logits_train[i]
                loss_op = self.loss[i]

                trainable = []

                for elem in all_trainable:
                    if elem.name.startswith('%s/tower' % root_scope):
                        if elem.name.startswith('%s/tower%d_' % (root_scope, i)):
                            trainable.append(elem)
                    else:
                        trainable.append(elem)

                self.logger.info('Trainable variables [gpu %d]' % i)

                for elem in trainable:
                    self.logger.info('... %s' % elem.name)

                #tf.get_variable_scope().reuse_variables()
                #summaries = tf.get_collection(tf.GraphKeys.SUMMARIES, scope)
                #print('\n'.join('{}: {}'.format(*k) for k in enumerate(summaries)))

                grads = optimizer.compute_gradients(loss_op, trainable)
                #print('\n'.join('{}: {}'.format(*k) for k in enumerate(grads)))
                tower_grads.append(grads)

                # Create a dummy optimization operation to create variables needed for optimization.
                with tf.variable_scope('tower%d_adam_opt' % i, reuse=tf.AUTO_REUSE):
                    _ = optimizer.minimize(loss_op)

                tf.get_variable_scope().reuse_variables()

        # We must calculate the mean of each gradient. Note that this is the
        # synchronization point across all towers.
        grads = average_gradients(tower_grads)

        # Apply the gradients to adjust the shared variables.
        apply_gradient_op = optimizer.apply_gradients(grads, global_step=self.global_step)

        self.train_op = apply_gradient_op

    def add_init_op(self):
        ## TODO: check that already initialize variables don't get
        # reinitialized (so that models don't collide with each other)
        self.init = tf.global_variables_initializer()

    def add_summary(self, sess):
        # tensorboard stuff
        self.merged = tf.summary.merge_all()
        self.file_writer = tf.summary.FileWriter(self.config.output_path, sess.graph)

    '''
    Prepare a feed dict for the specified model based on the given input batch
    Fills tf variables and placeholders with necessary values for sentence batch

    bio_data_sentence_list: list of BIODataSentence objects
    dropout_keep_prob:      inverse dropout rate: 1.0 means keep all data
    learning_rate:          optimizer learning rate
    '''
    def prepare_feed_dict(self, bio_data_sentence_batch, dropout_keep_prob=None, learning_rate=None):
        input_batch = []
        labels_batch = []
        sequence_lengths = []

        if USE_DYNAMIC_PADDING:
            max_length = max([len(s.labels) for s in bio_data_sentence_batch])
            self.logger.debug('max_length (per-batch dynamic): %d' % (max_length))
        else:
            max_length = MAX_UNIT_COUNT
            self.logger.debug('max_length (per-session static): %d' % (max_length))

        assert max_length <= MAX_UNIT_COUNT

        for sidx, sent in enumerate(bio_data_sentence_batch): # sent:BIODataSentence
            ## for now, only using first input
            input_batch.append(sent.get_inputs_padded(
                               input_idx=0,
                               max_length=max_length,
                               padding_token=BIOLexicon.PAD_TOK,
                               embed_id_dict=self.unit0_to_idx,
                               embed_oov_token=BIOLexicon.UNK_TOK))
            labels_batch.append(sent.get_labels_padded(
                               max_length=max_length,
                               padding_token=BIOLexicon.PAD_TOK,
                               embed_id_dict=self.label_to_idx,
                               embed_oov_token=BIOLexicon.UNK_TOK))
            sequence_lengths.append(sent.sentence_length)

            self.logger.debug('input_batch[%d] (len=%d): %s' % (sidx, len(input_batch[sidx]), str(input_batch[sidx])))
            self.logger.debug('labels_batch[%d] (len=%d): %s' % (sidx, len(labels_batch[sidx]), str(labels_batch[sidx])))
            self.logger.debug('sequence_length[%d]: %d' % (sidx, sequence_lengths[sidx]))

            assert len(input_batch[sidx]) == len(labels_batch[sidx])

        feed = {}
        feed[self.unit0_ids] = input_batch
        feed[self.sequence_lengths] = sequence_lengths
        feed[self.labels] = labels_batch

        #actual_batch_size = len(bio_data_sentence_batch)

        ## FIXME: during inference, we do a concat and the concat should work
        # as we expect. always fill batch to may size for safety
        #actual_batch_size = self.config.batch_size

        assert len(bio_data_sentence_batch) == self.config.batch_size

        feed[self.batch_split_sizes] = fixed_batch_split_sizes

        if self.dropout_keep_prob != None:
            feed[self.dropout_keep_prob] = dropout_keep_prob

        if self.learning_rate != None:
            feed[self.learning_rate] = learning_rate

        return feed, sequence_lengths

    def run_epoch(self, sess, train, dev, epoch):
        '''
        Performs one complete pass over the train set and evaluates on dev

        Args:
            sess: tensorflow session
            train: large BIODataSentence list (training set)
            dev: large BIODataSentence list (dev set)
            epoch: (int) number of the epoch
        '''
        nbatches = len(train) // self.config.batch_size
        if len(train) % self.config.batch_size != 0:
            nbatches += 1

        # hmm..couldn't this be slightly inefficient? seems like dynamic graph
        # almost

        prog = Progbar(target=nbatches)
        for i, sent_batch in enumerate(minibatches(train, self.config.batch_size, always_fill=True)):
            #if i % 100 == 0:

            fd, _ = self.prepare_feed_dict( \
                        bio_data_sentence_batch=sent_batch, \
                        dropout_keep_prob=self.config.dropout_keep_prob, \
                        learning_rate=self.config.learning_rate)

            #_, train_loss, summary = sess.run([self.train_op, self.loss, self.merged], feed_dict=fd)
            #_, train_loss = sess.run([self.train_op, self.loss], feed_dict=fd)

            #prog.update(i + 1, [('train loss', train_loss)])
            loss_ops_to_add = [self.loss[z] for z in range(NUM_GPUS)]
            _, losses = sess.run([self.train_op, loss_ops_to_add], feed_dict=fd)

            mean_loss = np.mean(losses)

            #prog.update(i + 1, [('train loss', train_loss)])
            prog.update(i + 1, [('train loss', mean_loss)]) # test

            # tensorboard
            #if i % 10 == 0:
                #self.file_writer.add_summary(summary, epoch*nbatches + i)

        acc, f1, mod_p = self.run_evaluate(sess, dev)
        self.logger.info('- dev acc {:04.2f} - f1 {:04.2f} - mod prec {:04.2f}'.format(100*acc, 100*f1, 100*mod_p))
        return acc, f1

    def predict_batch(self, sess, sents):
        '''
        Args:
            sess: a tensorflow session
            sents: list of BIODataSentence objects (batch)
                   (labels can be filled with PAD or other reserved value by
                    default, or may be passed in as gold value for convenience)
        Returns:
            pred_sents: list of new BIODataSentence objects filled with
                        predicted label data
        '''

        # ?? enforce?
        # assert len(sents) == self.config.batch_size

        cumulative_sequence_length = 0
        total_inference_time = 0
        total_viterbi_time = 0
        num_sentences = 0

        # dropout_keep_prob forced to 1.0 at inference time
        fd, sequence_lengths = self.prepare_feed_dict( \
                                   bio_data_sentence_batch=sents, \
                                   dropout_keep_prob=1.0)
        assert len(sequence_lengths) == len(sents)

        pred_sents = copy.deepcopy(sents)

        for sidx, s in enumerate(pred_sents):
        #    s.labels = []
            assert sequence_lengths[sidx] == len(s.inputs)
            cumulative_sequence_length += sequence_lengths[sidx]
        #    for i in range(sequence_lengths[sidx]):
        #        s.labels.append(BIOLexicon.PAD_TOK)

        if self.print_time_details:
            if args.use_static_padding:
                self.logger.info('... Fixed batch sequence length: %d' % MAX_UNIT_COUNT)
            else:
                self.logger.info('... Max batch sequence length: %d' % max(sequence_lengths))

        ## TODO: detect batch extension len(sents) % self.config.batch_size  remove this extra remainder from the results
        if self.config.crf:
            logits_exec = []
            transition_params_exec = []
            for i in range(NUM_GPUS):
                with tf.device('/gpu:%d' % i):
                    logits = self.logits_infer[i]
                    logits_exec.append(logits)
                    pred = self.labels_pred[i]
                    transition_params_exec.append(self.transition_params[i])
                    tf.get_variable_scope().reuse_variables()

            viterbi_sequences = []

            total_ff_time = current_milli_time()
            all_logits, all_transition_params = sess.run([logits_exec, transition_params_exec],
                    feed_dict=fd)
            total_ff_time = current_milli_time() - total_ff_time
            total_inference_time = total_ff_time

            if self.print_time_details:
                self.logger.info('...... Feed forward time: %.4fms/unit (batch total %.4fms)' % (total_ff_time / cumulative_sequence_length, total_ff_time))

            sidx = 0
            for gpu_idx, (this_gpu_logits, this_gpu_transition_params) in enumerate(zip(all_logits, all_transition_params)):
                # logits may be longer due to padding
                this_gpu_sequence_lengths = sequence_lengths[sidx:sidx+len(this_gpu_logits)]

                # iterate over the sentences
                for logit, sequence_length in zip(this_gpu_logits, this_gpu_sequence_lengths):
                    if sidx >= len(sents):
                        self.logger.info('... Breaking before padding during inference')
                        break # ignore extra padding

                    # keep only the valid time steps
                    logit = logit[:sequence_length]
                    viterbi_time = current_milli_time()
                    viterbi_sequence, viterbi_score = tf.contrib.crf.viterbi_decode(
                                    logit, this_gpu_transition_params)
                    viterbi_time = current_milli_time() - viterbi_time
                    total_viterbi_time += viterbi_time

                    viterbi_sequences.append(viterbi_sequence)
                    sidx += 1
                    num_sentences += 1

            total_inference_time += total_viterbi_time

            if self.print_time_details:
                self.logger.info('...... Viterbi time: %.4fms/unit (batch total %.4fms)' % (total_viterbi_time / cumulative_sequence_length, total_viterbi_time))
                self.logger.info('... Total inference time: %.4fms/unit (batch total %.4fms)' % (total_inference_time / cumulative_sequence_length, total_inference_time))

            for sidx, s in enumerate(pred_sents):
                s.labels = []
                #print('sequence_lengths[sidx]', sequence_lengths[sidx])
                #print('viterbi_sequences[sidx]', viterbi_sequences[sidx])
                for i in range(sequence_lengths[sidx]):
                    #print('i', i)
                    #print('viterbi_sequences[sidx][i]', viterbi_sequences[sidx][i])

                    ## FIXME: is it possible for the NN to return a non-sensible value here?
                    s.labels.append(self.idx_to_label[viterbi_sequences[sidx][i]])
                #print('s.labels', s.labels)

            #return viterbi_sequences, sequence_lengths
        else:
            ## TODO

            labels_pred_exec = []
            for i in range(NUM_GPUS):
                with tf.device('/gpu:%d' % i):
                    logits = self.logits_infer[i]
                    pred = self.labels_pred[i]
                    labels_pred_exec.append(pred)
                    tf.get_variable_scope().reuse_variables()

            labels_pred_concat = tf.concat(labels_pred_exec, axis=0)

            total_ff_time = current_milli_time()
            labels_pred = sess.run(labels_pred_concat, feed_dict=fd)
            total_ff_time = current_milli_time() - total_ff_time
            total_inference_time = total_ff_time

            if self.print_time_details:
                self.logger.info('... Total inference time: %.4fms/unit (batch total %.4fms)' % (total_inference_time / cumulative_sequence_length, total_inference_time))

            ## TODO: check sequence_lengths against input lengths

            for sidx, s in enumerate(pred_sents):
                #print('crf=false, sidx=', sidx)
                if sidx >= len(sents):
                    self.logger.info('... Breaking before padding during inference')
                    break # ignore extra padding

                s.labels = []
                for i in range(sequence_lengths[sidx]):
                    ## FIXME: is it possible for the NN to return a non-sensible value here?
                    s.labels.append(self.idx_to_label[labels_pred[sidx][i]])
                #print('s.labels', s.labels)

            #return labels_pred, sequence_lengths

        return pred_sents

    def run_evaluate(self, sess, test):
        '''
        Evaluates performance on specified test/dev set

        Args:
            sess: tensorflow session
            test: large BIODataSentence list (dev/test set)
        '''

        nbatches = len(test) // self.config.batch_size
        if len(test) % self.config.batch_size != 0:
            nbatches += 1

        correct_preds = 0
        total_preds = 0
        total_correct = 0
        correct_mod = 0
        total_mod = 0
        accs = []

        #prog = Progbar(target=nbatches)
        # always fill to match batch size by wrapping around if necessary
        for i, gold_sent_batch in enumerate(minibatches(test, self.config.batch_size, always_fill=True)):
            if i % 50 == 0:
                self.logger.info('Evaluate: batch %d/%d' % (i+1, nbatches))
                self.print_time_details = True
            else:
                self.print_time_details = False

            pred_sent_batch = self.predict_batch(sess, gold_sent_batch)

            assert len(gold_sent_batch) == len(pred_sent_batch)

            for sidx in range(len(gold_sent_batch)):
                gold_chunks = gold_sent_batch[sidx].get_label_chunks()
                pred_chunks = pred_sent_batch[sidx].get_label_chunks()
                correct_chunks = gold_chunks & pred_chunks

                self.logger.debug('gold_chunks: ' + str(sorted(gold_chunks)))
                self.logger.debug('pred_chunks: ' + str(sorted(pred_chunks)))

                for (chunk_idx, chunk_label) in gold_chunks:
                    if chunk_label.startswith('MOD') or chunk_label.startswith('B-MOD') or chunk_label.startswith('I-MOD'):
                        total_mod += 1

                for (chunk_idx, chunk_label) in correct_chunks:
                    if chunk_label.startswith('MOD') or chunk_label.startswith('B-MOD') or chunk_label.startswith('I-MOD'):
                        correct_mod += 1

                correct_preds += len(correct_chunks)
                total_preds += len(pred_chunks)
                total_correct += len(gold_chunks)
                accs += map(lambda items: items[0] == items[1], list(zip(gold_sent_batch[sidx].labels, pred_sent_batch[sidx].labels)))

        self.logger.info('correct_preds: ' + str(correct_preds))
        self.logger.info('total_mod: ' + str(total_mod))
        self.logger.info('total_preds: ' + str(total_preds))
        self.logger.info('total_correct: ' + str(total_correct))

        p = correct_preds / total_preds if correct_preds > 0 else 0
        r = correct_preds / total_correct if correct_preds > 0 else 0
        mod_p = correct_mod / total_mod if correct_mod > 0 else 0
        f1 = 2 * p * r / (p + r) if correct_preds > 0 else 0
        acc = np.mean(accs)
        return acc, f1, mod_p

    def train(self, sent_train, sent_dev):
        best_score = 0
        saver = tf.train.Saver()
        # for early stopping
        nepoch_no_imprv = 0

        #config = tf.ConfigProto()
        #config.gpu_options.allow_growth = True

        #with tf.Session(config=config) as sess:
        with tf.Session(config=tf.ConfigProto(allow_soft_placement=SOFT_PLACEMENT)) as sess:
            sess.run(self.init)
            # tensorboard
            self.add_summary(sess)
            for epoch in range(self.config.nepochs):
                self.logger.info('Epoch {:} out of {:}'.format(epoch + 1, self.config.nepochs))

                if not args.no_shuffle:
                    self.logger.debug('Shuffling training set...')
                    # do shuffle of sent_train
                    random.shuffle(sent_train)

                acc, f1 = self.run_epoch(sess, sent_train, sent_dev, epoch)

                # decay learning rate
                self.config.learning_rate *= self.config.lr_decay

                # early stopping and saving best parameters
                if f1 >= best_score:
                    nepoch_no_imprv = 0
                    if not os.path.exists(self.config.model_output):
                        os.makedirs(self.config.model_output)
                    saver.save(sess, self.config.model_output)
                    best_score = f1
                    self.logger.info('- new best score!')

                else:
                    nepoch_no_imprv += 1
                    if nepoch_no_imprv >= self.config.nepoch_no_imprv:
                        self.logger.info('- early stopping {} epochs without improvement'.format(
                                        nepoch_no_imprv))
                        break

    def evaluate(self, sent_test):
        saver = tf.train.Saver()
        with tf.Session(config=tf.ConfigProto(allow_soft_placement=SOFT_PLACEMENT)) as sess:
            self.logger.info('Testing model over test set')
            saver.restore(sess, self.config.model_output)
            acc, f1, mod_p = self.run_evaluate(sess, sent_test)
            self.logger.info('- test acc {:04.2f} - f1 {:04.2f} - mod prec {:04.2f}'.format(100*acc, 100*f1, 100*mod_p))

train = BIODataInput('data/%s/%s_train.txt' % (args.model_data_dir, args.model_type))
test = BIODataInput('data/%s/%s_test.txt' % (args.model_data_dir, args.model_type))
dev = BIODataInput('data/%s/%s_dev.txt' % (args.model_data_dir, args.model_type))
lexicon = BIOLexicon()
lexicon.add_lexicon_data(train.get_lexicon())
#lexicon.add_lexicon_data(test.get_lexicon())
#lexicon.add_lexicon_data(dev.get_lexicon())

## FIXME: make sure to delete existing dir first
lexicon.save('%slexicon' % args.model_output_dir)

config.input_unit_embedding_sizes = [args.input_unit_embedding_size]
config.do_unit_embedding_training = [True]
config.dropout_keep_prob = args.dropout_keep_prob
config.combined_hidden_size = args.combined_hidden_size
config.nepochs = args.max_epochs
config.nepoch_no_imprv = 3
config.learning_rate = 0.001
config.lr_decay = 0.9

# especially for CRF...output action count is important for memory
# max sentence length can also be a factor
# if you run out of memory, check current_train_vars
# here are some reasonable values (471 morphing output actions):
#
#current_train_vars [<tf.Variable 'morphing/unit0/_unit0_embeddings:0' shape=(1902, 300) dtype=float32_ref>, <tf.Variable 'morphing/bi-lstm/bidirectional_rnn/fw/lstm_cell/kernel:0' shape=(600, 1200) dtype=float32_ref>, <tf.Variable 'morphing/bi-lstm/bidirectional_rnn/fw/lstm_cell/bias:0' shape=(1200,) dtype=float32_ref>, <tf.Variable 'morphing/proj/W:0' shape=(600, 471) dtype=float32_ref>, <tf.Variable 'morphing/proj/b:0' shape=(471,) dtype=float32_ref>, <tf.Variable 'morphing/transitions:0' shape=(471, 471) dtype=float32_ref>]
#

config.crf = args.use_crf
config.batch_size = args.batch_size

SHOW_EFFICIENCY_EVERY_N = 1000

def efficiency_show(gpu_efficiency_rating_over_time, cpu_efficiency_rating_over_time):
    assert len(gpu_efficiency_rating_over_time) >= SHOW_EFFICIENCY_EVERY_N
    assert len(cpu_efficiency_rating_over_time) >= SHOW_EFFICIENCY_EVERY_N

    # show last N results
    #last_N_times, last_N_ratings = zip(*gpu_efficiency_rating_over_time[-SHOW_EFFICIENCY_EVERY_N:])

    # show avg of all time
    gpu_last_N_times, gpu_last_N_ratings = zip(*gpu_efficiency_rating_over_time)
    gpu_avg_eff = sum(gpu_last_N_ratings) / float(len(gpu_last_N_ratings))
    logger.info('Average GPU efficiency(0.0~1.0): %.2f' % gpu_avg_eff)

    cpu_last_N_times, cpu_last_N_ratings = zip(*cpu_efficiency_rating_over_time)
    cpu_avg_eff = sum(cpu_last_N_ratings) / float(len(cpu_last_N_ratings))
    logger.info('Average CPU efficiency(0.0~1.0): %.2f' % cpu_avg_eff)

def efficiency_monitor_start(data):
    gpu_efficiency_rating_over_time = []
    cpu_efficiency_rating_over_time = []
    while do_monitor:
        # monitor efficiency
        # TODO: also add to tensorboard
        cpu_loads = get_cpu_loads()
        gpu_loads, gpu_efficency_rating = get_gpu_loads_and_efficiency(GPU_IDS)

        # if rating is 0, gpu is probably just not in use (could be invalid data?? or bottlenecked on other area of app...might be important to know anyway)

        curtime = current_milli_time()

        cpu_efficiency = sum(cpu_loads) / float(len(cpu_loads))
        cpu_efficiency_rating_over_time.append((curtime, cpu_efficiency))

        #if efficency_rating != 0.0 and 0.0 not in gpu_loads:
        gpu_efficiency_rating_over_time.append((curtime, gpu_efficency_rating))
        if len(gpu_efficiency_rating_over_time) > 0 and len(gpu_efficiency_rating_over_time) % SHOW_EFFICIENCY_EVERY_N == 0:
            efficiency_show(gpu_efficiency_rating_over_time, cpu_efficiency_rating_over_time)

        # we don't want the measurement to get stuck on a certain point in the epoch
        # so sleep every random amount of seconds
        time.sleep(0.5+random.random())

g1 = tf.Graph()
with g1.as_default() as g:
    with tf.variable_scope(args.model_type):
        model = MorphingTaggingModel(lexicon=lexicon, config=config)
        # begin efficiency monitoring

        do_monitor = True
        t = threading.Thread(target=efficiency_monitor_start, args=('',))
        t.start()

        model.train(sent_train=train.sentences, sent_dev=dev.sentences)

        do_monitor = False # stop monitoring

        model.evaluate(sent_test=test.sentences)


#Solution:
#step1:calcualte the degree of the array--#hashtable: key:element, value:number
#step2:calcualte the shortest array
#using counter to store the numbers, than counter.values() = degree
#an important problem, can teach how to use enumerate, hashtable, counters,

def finddegree(nums):
    left, right = dict(), dict()
    counter = Counter(nums)
    for i, num in enumerate(nums):
        if num not in left:
            left[num] = i
        right[num] = i
        counter[num] += 1
    degree = max(counter.values())
    res = len(nums)
    for k, v in counter.items():
        if v == degree:
            res = min(res,right[k] - left[k] +1)
    return res

#use two dict to record the first time and last time

def findShortestSubArray(self, nums: List[int]) -> int:
        cnt = Counter(nums)
        degree = max(cnt.values())
        dici, dicj = {}, {}
        for idx, num in enumerate(nums):
            if num not in dici:
                dici[num] = idx
            dicj[num] = idx
        res = len(nums)
        for key in cnt:
            if cnt[key] == degree:
                res = min(res, dicj[key] - dici[key] + 1)
        return res


import html
from typing import TYPE_CHECKING, Union

from prompt_toolkit import Application, HTML
from prompt_toolkit.data_structures import Point
from prompt_toolkit.layout.controls import FormattedTextControl

from ..data.table import Table
from ..data.type import TablePromptDict, TablePromptList

if TYPE_CHECKING:  # pragma: no cover
    from pandas import DataFrame

class TablePrompt(Application):

    def __init__(
        self,
        question: str,
        data: Union["DataFrame", TablePromptDict, TablePromptList],
        *args,
        **kwargs,
    ):
        super().__init__(*args, **kwargs)

        # get prompt content box
        self.prompt_content: FormattedTextControl = self.layout.container.get_children()[0].content  # type: ignore

        # save question
        self.question = question

        # process data
        self.table = Table(data)

        # set cursor
        self.prompt_content.get_cursor_position = lambda: Point(
            self.table.get_current_cursor_position()[0],
            self.table.get_current_cursor_position()[1] + 1,  # add 1 offset for height of question
        )

    def update(self):
        """update prompt content"""
        content = f'[<question_mark>?</question_mark>] <question>{self.question}</question>:\n'

        # append table
        content += html.escape(
            self.table.get_table_as_str()
        )  # escaping is needed so formatting from PromptToolkit won't destroy the whole table

        self.prompt_content.text = HTML(content)

    def prompt(self) -> Union["DataFrame", TablePromptDict, None]:
        """start the application, returns the return value"""
        self.update()
        out: Union["DataFrame", TablePromptDict, None] = self.run()

        return out

    def on_up(self):
        """when up is pressed, the cell one above will be selected"""
        self.table.on_up()

        self.update()

    def on_down(self):
        """when down is pressed, the cell one below will be selected"""
        self.table.on_down()

        self.update()

    def on_left(self):
        """when left is pressed, the cell one to the left will be selected"""
        self.table.on_left()

        self.update()

    def on_right(self):
        """when left is pressed, the cell one to the right will be selected"""
        self.table.on_right()

        self.update()

    def on_key(self, key_sequence: list[str]):
        """when a key is pressed, the key will be added to the current cells content"""
        key = key_sequence[0]
        self.table.add_key(key)

        self.update()

    def on_backspace(self):
        """when backspace is pressed, the last char will be removed from the current cells content"""
        self.table.del_key()

        self.update()

    def on_enter(self):
        # return the modified table
        self.exit(result=self.table.data.get_data())


# -*- coding:utf8 -*-
import os
from PIL import Image
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow.examples.tutorials.mnist import input_data

import numpy as np

source_image_dir = 'image'
source_image_size = 16
target_image_size = 28

source_image = []
source_file_list = os.listdir(source_image_dir)
source_file_list.sort()

for file in source_file_list:
    if not file.find('png') == -1:
        im = np.array(Image.open(os.path.join(source_image_dir, file)).convert('L'), dtype=np.int16)
        source_image.append(im)
mnist = input_data.read_data_sets("image")
target_image = mnist.train.images[:2000]
target_image = np.reshape(target_image, [target_image.shape[0], 28, 28])
target_image_label = mnist.train.labels[:2000]

# 卷积自编码器
learn_rate = 5
loop_num = 100

inputs = tf.placeholder(tf.float32, (1, source_image_size, source_image_size, 1), name='image_input')
targets = tf.placeholder(tf.float32, (target_image_size, target_image_size), name='image_output')
# 解码

# 扩张成28*28
unpool = tf.image.resize_images(inputs, size=(target_image_size, target_image_size),
                                method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
# 反卷积，得到５个反卷积图像
conv = tf.layers.conv2d_transpose(inputs=unpool, filters=10, kernel_size=(3, 3), strides=1, padding='same',
                                  activation=tf.nn.sigmoid)
conv = conv[0]
# conv1 1x28x28x5
W_image = tf.Variable(initial_value=tf.random_normal(conv.shape), dtype=tf.float32)
W_merge = tf.ones(shape=[conv.shape[0], conv.shape[2], 1])
V_merge = tf.random_normal(shape=[int(conv.shape[0]), int(conv.shape[1]), ])
conv = tf.multiply(W_image, conv)
conv = tf.matmul(conv, W_merge)
conv = tf.reshape(conv, [conv.shape[0], conv.shape[1]])

out = tf.layers.dense(inputs=conv, units=conv.shape[0])
out = tf.sigmoid(out) * 255

loss = tf.losses.mean_squared_error(predictions=out, labels=targets)
tran_op = tf.train.AdadeltaOptimizer(learn_rate).minimize(loss)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    # tf.train.Saver().restore(sess, "./model_save")

    print('working...')
    for i in range(loop_num):
        for t_i, target_im in zip(target_image_label, target_image):
            feed = {inputs: np.reshape(source_image[t_i], [1, source_image_size, source_image_size, 1]),
                    targets: target_im}
            sess.run(tran_op, feed_dict=feed)
        loss_ = sess.run(loss, feed_dict=feed)
        tf.train.Saver().save(sess, "./model_save")
        print('step %d, loss=%f' % (i, loss_))


import tkinter as tk
import os
from time import*
from tkinter import Toplevel, messagebox
window = tk.Tk()
window.title("run")
window.geometry("500x500")
window.configure(background="Red")

label = tk.Label(window,height=500,width=500,background="Red",text="You.Can't.Run.From.Me")
label.pack()

font = ("Comic Sans MS", 20, "bold")
label.configure(font=font)

def onclosewindow():
   tk.messagebox.showerror('run',"TOLD YOU.YOU CANT ESCAPE.")
   top = Toplevel()

   top.geometry("500x500")
   anotherlabel = tk.Label(top,height=500,width=500,background="Red",text="WANNA PLAY WITH ME?")
   anotherlabel.pack()
   anotherlabel.configure(font=font)
   top.configure(background="Red")
   def anotheronclosewindow():
      tk.messagebox.showerror('run',"TOLD YOU.YOU CANT ESCAPE.")
      anothertop = Toplevel()

      anothertop.geometry("500x500")
      anotherlabel = tk.Label(anothertop,height=500,width=500,background="Red",text="STOP IGNORING ME.THATS NOT NICE OF YOU.")
      anotherlabel.pack()
      anotherlabel.configure(font=font)
      anothertop.configure(background="Red")
      def anotheranotheronclosewindow():
          tk.messagebox.showerror('run',"TOLD YOU.YOU CANT ESCAPE.")
          anotheranothertop = Toplevel()

          anotheranothertop.geometry("750x750")
          anotheranotherlabel = tk.Label(anotheranothertop,height=500,width=500,background="Red",text="JUST STOP CLOSING WINDOWS!! YOU'LL BE FINE.")
          anotheranotherlabel.pack()
          anotheranotherlabel.configure(font=font)
          anotheranothertop.configure(background="Red")
          def otherwindow():
             tk.messagebox.showerror('run',"FINE.")
             othertop = Toplevel()
             othertop.geometry("1000x1000")
             otherlabel = tk.Label(othertop,height=500,width=500,background="Red",text="IF YOU WON't DO IT,ILL DO IT.")
             otherlabel.pack()
             otherlabel.configure(font=font)
             othertop.configure(background="Red")
             def lastwindow():
                 tk.messagebox.showerror('run',"YOU DONT HAVE A CHANCE LEFT.")
                 lasttop = Toplevel()
                 lasttop.geometry("10000x10000")
                 lastlabel = tk.Label(lasttop,height=500,width=500,background="Red",text="HAHAHA.")
                 lastlabel.pack()
                 lastlabel.configure(font=font)
                 lasttop.configure(background="Red")
                 lasttop.protocol("WM_DELETE_WINDOW",tk.messagebox.showerror('run',"CANT LEAVE(ERROR HTTP REQUEST 449)"))
                 sleep(3)
                 for i in range(100000000):
                    print("YOU ARE STUCK NOW.")
                 quit(print("NEVER COME AGAIN."))
             othertop.protocol("WM_DELETE_WINDOW",lastwindow)
          anotheranothertop.protocol("WM_DELETE_WINDOW",otherwindow)
      anothertop.protocol("WM_DELETE_WINDOW",anotheranotheronclosewindow)
   top.protocol("WM_DELETE_WINDOW",anotheronclosewindow)
window.protocol("WM_DELETE_WINDOW",onclosewindow)

print(open)
window.mainloop()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from enum import Enum
from hpc_ds_types import *
from hpc_ds_desc import HPCDatastoreDescription
from hpc_ds_dsclient import DatasetServerClient
from hpc_ds_reg_client import RegisterServiceClient
from time import time
import requests

class HPCDatastoreRepository(object):
	"""HPC Datastore Repository representation"""

	data_headers = {
		"json": { "Content-Type": "application/json" },
		"string": { "Content-Type": "text/plain; charset=utf-8" }
	}

	def __init__(self, server_url, dataset_path=None, credentials=None):
		"""Set up base URL of the repository to use
		:type dataset_path: str
		:param dataset_path: Identity of the datastore (UUID)

		:type server_url: str
		:param server_url: DataStore server URL, defaults to localhost:9080

		:type credentials: object
		:param credentials: Future access credentials, set to None for now
		"""
		self.server_url = server_url
		self.dataset_path = dataset_path
		self.credentials = credentials

	def assert_dataset_ready(self, dataset_path):
		if dataset_path is None:
			raise InvalidDatasetException(
			"No dataset path has been specified, repository cannot be accessed")

	def get_base_url(self, use_dataset=True):
		"""Returns full datastore URL"""
		retval = self.server_url + DS_PATH
		if use_dataset:
			self.assert_dataset_ready(self.dataset_path)
			retval += '/' + self.dataset_path
		return retval

	def create(self, datastore_description):
		"""Create repository and record the server URL"""
#		print("Request URL: %s" % self.get_base_url(False))
		desc = requests.post(self.get_base_url(False),
								data=datastore_description.to_json(),
								headers=self.data_headers["json"])

		if desc is not None and int(desc.status_code / 100) == 2:
			self.dataset_path = desc.text
		else:
			raise HPCRepositoryAccessException(
			"Dataset has not been created, HTTP error %i" % (desc.status_code))
		return None

	def retrieve(self):
		"""Load repository data"""
		desc = requests.get(self.get_base_url())
		if desc is not None and int(desc.status_code / 100) == 2:
			return desc.json()
		else:
			raise HPCRepositoryAccessException(
			"Dataset %s has not been found, HTTP error %i"
				% (self.dataset_path, desc.status_code))
		return None

	def delete(self, deleted_dataset_path): # =self.dataset_path):
		# Argument forced to rather not to prevent deleting used datasets
		self.assert_dataset_ready(deleted_dataset_path)
		desc = requests.delete(self.get_base_url(False) + '/' + deleted_dataset_path)
		if desc is not None and int(desc.status_code / 100) == 2:
			if deleted_dataset_path == self.dataset_path: self.dataset_path = None
			return True
		elif desc.status_code == 404:
			if deleted_dataset_path == self.dataset_path: self.dataset_path = None
#			return False
#		else:
#			raise HPCRepositoryAccessException(
#				"Dataset %s has not been deleted, HTTP error %i"
#				% (deleted_dataset_path, desc.status_code))
		return False

	def get_common_metadata(self):
		"""Get N5 metadata from repository"""
		self.assert_dataset_ready(self.dataset_path)
		result = requests.get(self.get_base_url() + "/common-metadata")
		if result is not None and int(result.status_code / 100) == 2:
			return result.text

	def set_common_metadata(self, metadata):
		"""Set N5 metadata to repository"""
		self.assert_dataset_ready(self.dataset_path)
		result = requests.post(self.get_base_url() + "/common-metadata",
								data=str(metadata),
								headers=self.data_headers["string"])
		return result is not None and int(result.status_code / 100) == 2

	def add_channels(self, count):
		"""Add extra channels"""
		self.assert_dataset_ready(self.dataset_path)
		result = requests.post(self.get_base_url() + "/channels",
								data=str(count),
								headers=self.data_headers["json"])
		return result is not None and int(result.status_code / 100) == 2

	def rebuild(self, resolutions=[Point3D(1,1,1)]):
		"""Rebuilds data for specified resolutions from base data"""
		self.assert_dataset_ready(self.dataset_path)
		res_url = Point3D(1,1,1).to_ds_url_part() # From doc, otherwise ""
		for resolution in resolutions:
			res_url += Point3D.to_ds_url_part(resolution)
		result = requests.get(self.get_base_url() + res_url + "/rebuild")
		return result is not None and int(result.status_code / 100) == 2

class HPCDatastoreClient(object):
	"""HPC Datastore client implementation in Python 3"""

	def __init__(self, server_url="http://localhost:9080", dataset_path=None,
					access_regime = DatastoreAccess.READ, credentials=None):
		"""Initializes the Data Store connection

		:type dataset_path: str
		:param dataset_path: Identity of the datastore (UUID)

		:type access_regime: DatasetAccess
		:param access_regime: Access regime to the datastore - R/W/RW

		:type server_url: str
		:param server_url: DataStore server URL, defaults to localhost and port 9080

		:type credentials: object
		:param credentials: Future access credentials, set to None for now
		"""

		self.access_regime = access_regime
		self.credentials = credentials
		self.ds_description = None
		#self.server_url = server_url
		self.repository = HPCDatastoreRepository(server_url, dataset_path,
							credentials)

		#Running Data Store servers
		self.ds_servers = dict()

	def load_description(self):
		json_objects=self.repository.retrieve()
		#print(json_objects)
		self.ds_description = HPCDatastoreDescription(json_objects=json_objects)

	def start_dataset_server(self, resolution, access_regime=None,
									version="latest", timeout=15000):
		"""Open dataset server for limited time to work with slices and
		returns object which is representing it

		:type resolutions: ``Point3D``
		:param server_url: (X,Y,Z) tuple representing the resolution levels
		       from HPCDatastoreDescription on individual axes

		:type access_regime: DatastoreAccess
		:param access_regime: Access regime to the datastore - R/W/RW.
				None will result in the default access regime being used.

		:type version: str
		:param version: Name of the version (may be number, latest, ...)

		:type timeout: int
		:param timeout: Timeout of Service Client instance in ms

		:rtype: ``DatasetServerClient``
		:return: A new or already running instance of ``DatasetServerClient``
		"""
		if access_regime is None:
			access_regime = self.access_regime

		if self.ds_description is None:
			self.load_description()

		ds_regserv = RegisterServiceClient(self.repository.get_base_url(),
					access_regime, resolution, version, timeout,
					self.credentials)

		ds_id=ds_regserv.to_url()
		if ds_id in self.ds_servers \
		  and (self.ds_servers[ds_id].expires is None \
				or self.ds_servers[ds_id].expires > time()):
			ds_regserv = self.ds_servers[ds_id]
		else:
			ds_regserv.start()
			self.ds_servers[ds_id] = ds_regserv
			ds_regserv.client.voxel_type = self.ds_description.voxelType

		return ds_regserv.client

	def __str__(self):
		out_s = ""
		for k in self.__dict__:
			out_s += "%s: %s\n" % (k,self.__dict__[k])
		return out_s


# -*- coding: utf-8 -*-

import pandas as pd
import statsmodels.api as sm
import pylab as pl
import numpy as np
from sklearn.metrics import roc_curve, auc

df = pd.read_csv("titanic_all_numeric.csv")

df.columns
df.head()
df.dtypes
#df = df.drop(['age_was_missing'], axis=1)

y = df['survived']

y

X = df.drop(['survived'], axis =1)

df.dtypes

X.columns
y

from sklearn.model_selection import train_test_split
X_train,X_test,y_train,y_test=train_test_split(X, y, test_size=0.2, random_state=0)

from sklearn.linear_model import LogisticRegression

logreg = LogisticRegression()

# fit the model with data
logreg.fit(X_train,y_train)

#
y_pred=logreg.predict(X_test)
y_pred
y_test

from sklearn import metrics
cnf_matrix = metrics.confusion_matrix(y_test, y_pred)
cnf_matrix

TP = cnf_matrix[0,0]
TN = cnf_matrix[1,1]

FP = cnf_matrix[1,0]
FN=cnf_matrix[0,1]

(TP+TN)/ (TP+TN+FP+FN)

'''
print("Accuracy:",metrics.accuracy_score(y_test, y_pred))

y_test = [1,1,1,1,1,1,1,1,0,0]
y_pred = [1,1,1,1,1,1,1,1,1,1]
y_test = [0,0,0,0,0,0,0,0,0,0]
'''

print("Accuracy:",metrics.accuracy_score(y_test, y_pred))

print("Precision:",metrics.precision_score(y_test, y_pred))
print("Recall:",metrics.recall_score(y_test, y_pred))

y_pred_proba = logreg.predict_proba(X_test)[::,1]
y_pred_proba
y_pred_proba.shape

fpr, tpr, _ = metrics.roc_curve(y_test, y_pred_proba)

fpr
tpr

auc = metrics.roc_auc_score(y_test, y_pred_proba)
auc

#together

plt.plot(fpr,tpr,label="data 1, auc="+str(auc))
plt.legend(loc=4)
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver operating characteristic')
plt.legend(loc="lower right")
plt.show();

import math

ls= list(range(-16,16))
ls

y=[]
for x in ls:
    y.append((1)/(1+math.exp(-x)))

y

import matplotlib.pyplot as plt
plt.scatter(ls,y)


#Determinar cuántos hombres y cuantas mujeres se encuentran en un grupo de n personas,
#suponiendo que los datos son extraídos alumno por alumno.

cantAlumnos = int(input("Cuantos alumnos hay en la clase? "))
alumnos = []
hombres = 0
mujeres = 0
for i in range (1, cantAlumnos + 1):
	alumnos.append(input("El alumno # " + str(i) + " es hombre(h) o mujer(m) "))
for i in alumnos:
	if i == "h":
		hombres = hombres + 1
	else:
		mujeres = mujeres + 1
print("En el curso hay " + str(hombres) + " hombres y " + str(mujeres) + " mujeres")


# -*- coding=utf-8 -*-
"""
Created by hhx on 17/11/30.
"""

import time
import random

from freetime.util import log as ftlog
from freetime.core.timer import FTLoopTimer
from freetime.entity.msg import MsgPack
from poker.protocol import router
from poker.entity.biz import bireport
from newfish.entity import util, config
from newfish.entity import drop_system
from newfish.entity.config import FISH_GAMEID

Task_Delay_Ready_Time = 2       # 任务延时后的准备时间
Task_Red_Ready_Time = 12        # 限时任务在新手场的准备时间
Task_Normal_Ready_Time = 9      # 限时任务在其他场的准备时间

class TaskState:
    NotOpen = 0                 # 未开始
    Start = 1                   # 开始
    Update = 2                  # 进行中
    Fail = 3                    # 失败
    Success = 4                 # 成功

# 任务模型
class TaskModel:
    Red = 0                     # 新手任务
    Main = 1                    # 主线任务
    Branch = 2                  # 支线任务
    Random = 3                  # 随机任务

class TaskType:
    CatchFishCoin = 1           # 捕获xx金币
    CatchFishNum = 2            # 捕获xx只xx鱼
    CatchBossNum = 3            # 捕获xx只BOSS
    FishNumCoinHigh = 4         # 捕获xx只xx金币以上的鱼
    BetFishNum = 5              # 捕获xx只xx倍率鱼
    ComboNum = 6                # 连击数达到xx
    UseSkillNum = 7             # 使用xx次xx技能
    UseSkillCatchFishNum = 8    # 使用技能捕获xx只xx鱼
    UseSkillCatchFishCoin = 9   # 使用技能捕获xx金币的xx鱼
    CatchFishNumByOneFire = 10  # xx网捕获xx鱼
    HoldCoin = 11               # 持有xx金币
    CatchPearlNum = 12          # 捕获xx颗珍珠
    GunLevelUp = 13             # 升级xx次火炮
    CatchRainbowFishNum = 14    # 捕获xx只彩虹鱼
    CatchRedPacketFishNum = 15  # 捕获多少只红包券鱼
    UserSkillItem = 16          # 使用技能道具

class TaskBase(object):

    def __init__(self, player, taskConf, taskSystem, taskData=None):
        self.player = player
        self.userId = player.userId
        self.taskId = taskConf["taskId"]
        self.taskConfig = taskConf

        self.taskSystem = taskSystem
        self.taskData = taskData or self._getInitMainData()
        self.taskInterval = taskConf["timeLong"]
        self.helpProgress = taskData.get("helpProgress", 0) if taskData else 0
        self.waitTime = taskData.get("waitTime", 0) if taskData else taskConf["waitTime"]
        self.taskActivateTimer = None       # 任务激活
        self.sendTaskInfoTimer = None       # 发送任务信息
        self.updateTaskInfoTimer = None     # 刷新任务进度
        self.userEndTaskTimer = None        # 任务结束倒计时
        self.isSendMsg = False
        self.receiveRewards = None
        self.catchBetNum = taskData.get("catchBetNum", 0) if taskData else 0
        self._reload()

    def clear(self):
        """清理定时器"""
        if self.taskActivateTimer:
            self.taskActivateTimer.cancel()
        if self.sendTaskInfoTimer:
            self.sendTaskInfoTimer.cancel()
        if self.updateTaskInfoTimer:
            self.updateTaskInfoTimer.cancel()
        if self.userEndTaskTimer:
            self.userEndTaskTimer.cancel()

    def _reload(self):
        self.recordStartTime = 0
        if not self.isTaskOver():
            if self.taskData.get("type", 0) and self.taskData.get("type", 0) != self.taskConfig["type"]:
                self.taskData = self._getInitMainData()
                return
            if self.taskConfig["target"] != self.taskData["target"]:        # 未完成的任务任务目标发生变化后重置存档
                self.taskData = self._getInitMainData()
                return
            if self.taskConfig["targetNum"] != self.taskData["targetNum"] and self.taskData.get("type", 0) not in [0, TaskType.HoldCoin]:
                self.taskData = self._getInitMainData()
                return

    def _getInitMainData(self):
        """获取初始化主线数据"""
        targetNum = self.taskConfig["targetNum"]
        if util.isNewbieRoom(self.player.table.typeName) and self.taskConfig["type"] == TaskType.HoldCoin:
            targetNum += self.player.allChip // 100 * 100
        return {
            "type": self.taskConfig["type"],
            "progress": 0,
            "state": TaskState.NotOpen,
            "failTime": 0,
            "targetNum": targetNum,
            "target": self.taskConfig["target"]
        }

    def _addProgress(self, value, isMe, progress=0, isTargetAddition=False):
        """增加任务进度"""
        assert (self.taskData)
        if value == 0 or self.taskData["state"] != TaskState.Update:                            # value
            return
        if ftlog.is_debug():
            ftlog.debug("_addProgress1", "value =", value, "isMe =", isMe, "progress =", progress, "isTargetAddition =", isTargetAddition, "helpProgress =", self.helpProgress)
        if isMe:
            value *= self.taskSystem.taskExpedite
            progress_ = min(self.taskData["progress"] + value, 999999999999)
            self._updateProgress(progress_, isMe)
        else:
            if progress > 0 or isTargetAddition:
                if progress:
                    self.helpProgress += progress
                else:
                    self.helpProgress += value
                progress_ = min(self.taskData["progress"] + value, 999999999999)
                self._updateProgress(progress_, isMe)
                if ftlog.is_debug():
                    ftlog.debug("_addProgress2", "helpProgress =", self.helpProgress, "totalProgress =", progress_)

    def _updateProgress(self, value, isMe):
        """
        更新任务进度
        """
        if self.taskData["progress"] != value:
            self.taskData["progress"] = value
            self.taskData["time"] = int(time.time())
            if self._isComplete():
                self.taskEnd()
            else:
                if isMe:
                    self.sendUserTaskInfo()
                else:                               # 好友助力任务进度延迟刷新
                    self.isSendMsg = True

    def _receiveTaskReward(self):
        """领取奖励"""
        assert (self.taskData)
        if self.isTaskOver():  # 已领取
            return 1, None
        targetCount = self.taskData["targetNum"]
        if self.taskData["progress"] < targetCount:  # 未达成
            return 1, None
        _rewards = self._getDropItems()
        if not _rewards:
            _rewards = self.taskConfig["rewards"]     # 普通奖励
        if _rewards and util.isChestRewardId(_rewards[0]["name"]):      # 宝箱奖励(新手宝箱奖励固定)
            if util.isNewbieRoom(self.player.table.typeName):
                _rewards = [
                    {"name": 101, "count": 500},
                    {"name": 1145, "count": 1},
                    {"name": 1149, "count": 1}
                    # {"name": 1137, "count": 3}
                ]
            else:
                from newfish.entity.chest import chest_system
                _rewards = chest_system.getChestRewards(self.userId, _rewards[0]["name"])

        rewards = []
        for _, reward in enumerate(_rewards):
            if reward["name"] <= 0:
                continue
            rwDict = {}
            rwDict["name"] = reward["name"]
            rwDict["count"] = reward["count"]
            rewards.append(rwDict)

        code = 0
        self.taskData["state"] = TaskState.Success  # 改变状态
        if rewards:
            code = util.addRewards(self.userId, rewards, "BI_NFISH_TABLE_TASK_REWARDS", int(self.taskId))
        return code, rewards

    def _getDropItems(self):
        """获取掉落的道具"""
        realRewards = []
        rewards = self.taskConfig["rewards"]
        for reward in rewards:
            dropId = reward["name"]
            dropConf = config.getDropConf(dropId)
            if dropConf:
                _, rds = drop_system.getDropItem(dropId)
                realRewards.append(rds)
        return realRewards

    def _getCatchFishCoin(self, event, target=0):
        """获取捕鱼的金币"""
        totalCoin = 0  # 总金币数不包括招财珠
        gunSkinMul = event.gunSkinMul
        fishCoins = {}
        outCoinsNum = 0
        for gainMap in event.gain:
            fishType = int(gainMap["fishType"])
            itemId = gainMap["itemId"]
            itemCount = gainMap["count"] / gunSkinMul
            if itemId == config.CHIP_KINDID:  # 金币
                totalCoin += itemCount
                if fishType // 1000 == 14:
                    fishType = fishType % 14000 + 11000
                fishCoins[str(fishType)] = fishCoins.setdefault(str(fishType), 0) + itemCount
                if target and itemCount >= target:
                    outCoinsNum += 1
        return totalCoin, fishCoins, outCoinsNum

    def dealCatchEvent(self, event, tableMultiple, coinAddition, playersNum):
        """
        处理捕鱼事件
        :param event: 捕获事件详情
        :param tableMultiple: 房间倍率
        :param coinAddition: 当处于累计捕获金币任务时的进度加成
        :param playersNum: 当前桌内玩家数量
        :return:
        """
        if self.taskConfig["type"] in [TaskType.UseSkillNum, TaskType.ComboNum]:
           return
        if self.taskData["state"] != TaskState.Update:
            return
        isMe = event.userId == self.userId
        fishTypes = event.fishTypes  # 鱼种类
        wpId = event.wpId
        gunSkinMul = event.gunSkinMul
        target = self.taskConfig["target"]
        progress = 0
        isTargetAddition = False
        if self.taskConfig["type"] in [TaskType.CatchFishCoin, TaskType.UseSkillCatchFishCoin]:
            progress = coinAddition
        elif self.taskConfig["type"] in [TaskType.CatchFishNum, TaskType.CatchBossNum,
                                         TaskType.FishNumCoinHigh, TaskType.BetFishNum,
                                         TaskType.CatchRainbowFishNum, TaskType.CatchRedPacketFishNum]:
            friendHelpPlayerMultiple = config.getCommonValueByKey("friendHelpPlayerMultiple", {}).get(str(playersNum - 1), 0)
            probb = (friendHelpPlayerMultiple * (self.taskData["targetNum"] + 5) / self.taskData["targetNum"]) * 10000
            randInt = random.randint(1, 10000)
            if randInt <= probb:
                isTargetAddition = True
        if self.taskConfig["type"] == TaskType.CatchFishCoin:  # 1捕获xx鱼达金币数
            totalCoin, fishCoins, _ = self._getCatchFishCoin(event)
            if target:
                self._addProgress(fishCoins.get(str(target), 0), isMe, progress=progress, isTargetAddition=isTargetAddition)
            else:
                self._addProgress(totalCoin, isMe, progress=progress, isTargetAddition=isTargetAddition)

        elif self.taskConfig["type"] == TaskType.CatchFishNum:  # 2捕获鱼个数
            if target:
                betTarget = 14000 + int(target) % 11000
                fishNum = fishTypes.count(target) + fishTypes.count(betTarget)
                self._addProgress(fishNum, isMe, progress=progress, isTargetAddition=isTargetAddition)
            else:
                self._addProgress(len(fishTypes), isMe, progress=progress, isTargetAddition=isTargetAddition)
            return
        elif self.taskConfig["type"] == TaskType.CatchBossNum:  # 3捕获boss个数
            bossNum = 0  # boss个数
            for fishType in fishTypes:
                fishConf = config.getFishConf(fishType, self.taskSystem.table.typeName, tableMultiple)
                if fishConf["type"] in config.BOSS_FISH_TYPE:
                    bossNum += 1
            self._addProgress(bossNum, isMe, progress=progress, isTargetAddition=isTargetAddition)
            return

        elif self.taskConfig["type"] == TaskType.FishNumCoinHigh:  # 4多少金币以上的鱼
            _, _, num = self._getCatchFishCoin(event, target)
            self._addProgress(num, isMe, progress=progress, isTargetAddition=isTargetAddition)
            return
        elif self.taskConfig["type"] == TaskType.BetFishNum: # 5--多少只倍率鱼
            betFishMap = {}
            for gainMap in event.gain:
                fishConf = config.getFishConf(gainMap["fishType"], self.taskSystem.table.typeName, tableMultiple)
                itemId = gainMap["itemId"]
                itemCount = gainMap["count"]

                if fishConf["type"] in config.MULTIPLE_FISH_TYPE:
                    if itemId == config.CHIP_KINDID:  # 金币
                        bet = itemCount / fishConf["score"] / tableMultiple / gunSkinMul
                        betFishMap[str(bet)] = betFishMap.get(str(bet), 0) + 1
            betNum = 0
            for bet in betFishMap:
                if int(bet) >= target:
                    betNum += betFishMap[bet]
            self._addProgress(betNum, isMe, progress=progress, isTargetAddition=isTargetAddition)

        elif self.taskConfig["type"] == TaskType.UseSkillCatchFishNum:  # 8 使用技能捕获鱼数
            wpType = util.getWeaponType(wpId)
            if wpType in [config.SKILL_WEAPON_TYPE,
                          config.RB_FIRE_WEAPON_TYPE,
                          config.RB_BOMB_WEAPON_TYPE]:
                if target:
                    self._addProgress(fishTypes.count(target), isMe, progress=progress, isTargetAddition=isTargetAddition)
                else:
                    self._addProgress(len(fishTypes), isMe, progress=progress, isTargetAddition=isTargetAddition)

        elif self.taskConfig["type"] == TaskType.UseSkillCatchFishCoin:  # 9 使用技能捕获XX金币类型的鱼数
            totalCoin, fishCoins, _ = self._getCatchFishCoin(event)  # 总金币数不包括招财珠
            wpType = util.getWeaponType(wpId)
            if wpType in [config.SKILL_WEAPON_TYPE,
                          config.RB_FIRE_WEAPON_TYPE,
                          config.RB_BOMB_WEAPON_TYPE]:
                if target:
                    self._addProgress(fishCoins.get(str(target), 0), isMe, progress=progress, isTargetAddition=isTargetAddition)
                else:
                    self._addProgress(totalCoin, isMe, progress=progress, isTargetAddition=isTargetAddition)
        elif self.taskConfig["type"] == TaskType.CatchFishNumByOneFire:  # 10 1网捕获多少鱼
            if len(fishTypes) >= target:
                value = 1
                if not isMe:
                    return
                self._addProgress(value, isMe, progress=progress, isTargetAddition=isTargetAddition)
        elif self.taskConfig["type"] == TaskType.CatchRainbowFishNum:   # 捕获彩虹鱼
            rainbowNum = 0
            for fishType in fishTypes:
                fishConf = config.getFishConf(fishType, self.taskSystem.table.typeName, tableMultiple)
                if fishConf["type"] in config.RAINBOW_FISH_TYPE:
                    rainbowNum += 1
            self._addProgress(rainbowNum, isMe, progress=progress, isTargetAddition=isTargetAddition)
        elif self.taskConfig["type"] == TaskType.CatchRedPacketFishNum: # 捕获红包券鱼
            redPacketNum = 0
            for fishType in fishTypes:
                fishConf = config.getFishConf(fishType, self.taskSystem.table.typeName, tableMultiple)
                if fishConf["type"] == 4:
                    redPacketNum += 1
            self._addProgress(redPacketNum, isMe, progress=progress, isTargetAddition=isTargetAddition)

    def dealUseSkillEvent(self, event):
        """使用技能事件"""
        if self.taskData["state"] != TaskState.Update:
            return
        if self.taskConfig["type"] == TaskType.UseSkillNum:
            target = self.taskConfig["target"]
            if target == 0:
                self._addProgress(1, event.userId == self.userId)
            else:
                if event.skillId == target:
                    self._addProgress(1, event.userId == self.userId)

    def dealUseSkillItem(self, event):
        """使用技能道具"""
        if self.taskData["state"] != TaskState.Update:
            return
        if self.taskConfig["type"] == TaskType.UserSkillItem:
            self._addProgress(1, event.userId == self.userId)

    def dealGetPearl(self, pearl):
        """获得珍珠"""
        if self.taskData["state"] != TaskState.Update:
            return
        if self.taskConfig["type"] == TaskType.CatchPearlNum:
            self._addProgress(pearl, True)

    def dealCommboEvent(self, event):
        """处理连击事件"""
        if self.taskData["state"] != TaskState.Update:
            return
        if self.taskConfig["type"] == TaskType.ComboNum:
            if event.comboNum >= self.taskConfig["target"]:
                self._addProgress(1, event.userId == self.userId)

    def refreshHoldCoin(self, coin):
        """刷新持有金币数量"""
        if self.taskData["state"] != TaskState.Update:
            return
        if ftlog.is_debug():
            ftlog.debug("refreshHoldCoin", self.userId, coin)
        if self.taskConfig["type"] == TaskType.HoldCoin:
            self._updateProgress(coin, True)

    def refreshLevel(self, level):
        """刷新等级"""
        if self.taskData["state"] != TaskState.Update:
            return
        ftlog.debug("refreshLevel", self.userId, level)
        if self.taskConfig["type"] == TaskType.UpgradeLevel:
            self._updateProgress(level, True)

    def getTaskId(self):
        """
        获取任务Id
        """
        return self.taskId

    def taskStart(self, interval=0, delay=0):
        """
        任务开始
        """
        if ftlog.is_debug():
            ftlog.debug("taskBase_______taskStart", self.taskId)

        if self.taskActivateTimer:
            self.taskActivateTimer.cancel()
        if delay > 0:
            taskActivateInterval = max(interval, Task_Delay_Ready_Time)
            self.taskActivateTimer = FTLoopTimer(taskActivateInterval, 0, self._taskActivate, delay)
            self.taskActivateTimer.start()
        elif self.taskConfig["timeLong"] > 0:
            if util.isNewbieRoom(self.player.table.typeName) and self.taskSystem.taskModel == TaskModel.Main:
                time_ = Task_Red_Ready_Time
            else:
                time_ = Task_Normal_Ready_Time
            taskActivateInterval = max(interval, time_)
            taskInfoInterval = max(interval - time_, 0)
            self.delaySendUserTaskInfo(taskInfoInterval)
            self.taskActivateTimer = FTLoopTimer(taskActivateInterval, 0, self._taskActivate)
            self.taskActivateTimer.start()
        else:
            self.taskActivateTimer = FTLoopTimer(interval, 0, self._taskActivate)
            self.taskActivateTimer.start()
        from newfish.game import TGFish
        from newfish.entity.event import TableTaskStartEvent
        event = TableTaskStartEvent(self.userId, FISH_GAMEID, self.taskSystem.table.tableId, self.getTaskId())
        TGFish.getEventBus().publishEvent(event)

    def _taskActivate(self, delay=0):
        """任务激活"""
        # 未完成前3个中期目标时不触发渔场任务.
        if self.taskSystem.needCheckMidTermTargetStates:
            if not self.taskSystem.top3MidTermTargetFinished:
                self.taskSystem.top3MidTermTargetFinished = True
                for taskClass in self.player.achieveSystem.holdAssetTasks:
                    if taskClass.taskId < 1003 or (taskClass.taskId == 1003 and not taskClass.isComplete()):
                        self.taskSystem.top3MidTermTargetFinished = False
                        break
            if not self.taskSystem.top3MidTermTargetFinished:
                if self.taskActivateTimer:
                    self.taskActivateTimer.cancel()
                self.taskActivateTimer = FTLoopTimer(60, 0, self._taskActivate)
                self.taskActivateTimer.start()
                if ftlog.is_debug():
                    ftlog.debug("_taskActivate, need delay !", self.player.userId, self.taskId)
                return
        if ftlog.is_debug():
            ftlog.debug("_taskActivate->", delay, self.waitTime)
        self.taskData["state"] = TaskState.Start
        if delay > 0:
            self.taskData["state"] = TaskState.Update
            self.taskInterval = delay
        self.recordStartTime = int(time.time())
        self.clear()
        if self.taskInterval != 0:
            self.userEndTaskTimer = FTLoopTimer(self.taskInterval, 0, self.taskEnd)
            self.userEndTaskTimer.start()
        if self.taskSystem.isFriendRoom():
            self.updateTaskInfoTimer = FTLoopTimer(1, -1, self.sendTimerUpdate)
            self.updateTaskInfoTimer.start()
        self.sendUserTaskInfo()
        self.taskData["state"] = TaskState.Update

    def getTaskFailTime(self):
        """
        当前任务已经失败多少次
        """
        return self.taskData["failTime"]

    def getTaskInfo(self):
        """
        获取返回给客户端的任务数据
        """
        progress = min(self.taskData["progress"], self.taskData["targetNum"])
        progress = int(progress) if progress - int(progress) == 0 else progress
        meProgress = progress - self.helpProgress
        meProgress = int(meProgress) if meProgress - int(meProgress) == 0 else meProgress
        taskInfo = {}
        taskInfo["taskId"] = self.taskConfig["taskId"]
        taskInfo["isLimitTime"] = self.isLimitTime()
        taskInfo["shareMode"] = 1   # 1 if not self.isLimitTime() or util.isLocationLimit(self.userId) else 0
        taskInfo["progress"] = [int(progress), int(meProgress), self.taskData["targetNum"]]
        taskInfo["state"] = self.taskData["state"]
        taskInfodescId = self.taskConfig["desc"]
        taskInfo["desc"] = config.getMultiLangTextConf(str(taskInfodescId), lang=util.getLanguage(self.userId))
        taskInfo["reward"] = self.taskConfig["rewards"]
        if self.receiveRewards:
            taskInfo["realReward"] = self.receiveRewards
        taskInfo["target"] = self.taskConfig["target"]
        taskInfo["suggestTarget"] = self.taskConfig["suggestTarget"]
        timeLeft = self.recordStartTime + self.taskInterval - int(time.time())
        taskInfo["timeLeft"] = timeLeft if timeLeft >= 0 else self.taskInterval
        taskInfo["timeLong"] = self.taskInterval
        taskInfo["failTimeConf"] = self.taskConfig["failTime"]
        taskInfo["failTime"] = self.taskData["failTime"]        # 失败次数
        taskInfo["isNextLimitTime"] = self.taskSystem.isNextLimitTime(self.getTaskId())
        taskInfo["model"] = TaskModel.Red if util.isNewbieRoom(self.player.table.typeName) and self.taskSystem.taskModel == TaskModel.Main else self.taskSystem.taskModel
        taskInfo["type"] = self.taskConfig["type"]
        if self.taskSystem.taskModel in [TaskModel.Red, TaskModel.Main]:
            taskInfo["index"] = self.taskSystem.allMainTaskIds.index(self.taskId)
        return taskInfo

    def getTaskData(self):
        """
        获取存储的任务数据
        """
        saveData = {}
        saveData["type"] = self.taskConfig["type"]
        saveData["taskId"] = self.taskId
        saveData["progress"] = self.taskData["progress"]
        saveData["helpProgress"] = self.helpProgress
        saveData["state"] = self.taskData["state"]
        saveData["target"] = self.taskConfig["target"]
        saveData["targetNum"] = self.taskData["targetNum"]
        saveData["failTime"] = self.taskData["failTime"]
        saveData["waitTime"] = int(self.taskActivateTimer.getTimeOut()) if self.taskActivateTimer else 0
        if self.catchBetNum:
            saveData["catchBetNum"] = self.catchBetNum
        return saveData

    def taskEnd(self):
        """
        任务结束
        """
        if ftlog.is_debug():
            ftlog.debug("taskBase_______taskEnd", self.taskId, self.userId, self.taskData["state"])
        beforeState = self.taskData["state"]
        self.clear()
        if beforeState == TaskState.Update:
            isComplete = self._isComplete()
            if isComplete:
                _, rewards = self._receiveTaskReward()
                self.receiveRewards = rewards
                self.taskSystem.saveRedState()  # 保存红包
            else:
                self.taskSystem.taskExpedite = 1
                self.taskData["state"] = TaskState.Fail
                if self.isLimitTime():
                    self.taskData["failTime"] += 1
            from newfish.game import TGFish
            from newfish.entity.event import TableTaskEndEvent
            event = TableTaskEndEvent(self.userId, FISH_GAMEID, self.taskSystem.table.tableId, self.getTaskId(),
                                      isComplete,  self.isLimitTime(), self.receiveRewards)
            TGFish.getEventBus().publishEvent(event)
            # 上传当前完成任务进度
            if self.taskSystem.taskModel == TaskModel.Main and self.taskSystem.isRedRoom():
                ftlog.debug("BI_NFISH_THOUSAND_RED_TASK_PROGRESS--->000", self.getTaskId())
                bireport.reportGameEvent("BI_NFISH_GE_THOUSAND_RED_TASK", self.userId,FISH_GAMEID,
                                self.taskSystem.table.roomId, self.taskSystem.table.tableId,
                                self.getTaskId(), int(isComplete), 0,  0, [], config.CLIENTID_ROBOT)
                # 新手引导步骤完成
                if self.taskConfig["type"] == TaskType.UpgradeLevel:
                    util.addGuideStep(self.player.userId, config.NEWBIE_GUIDE_GUN_UP, self.player.clientId)
        self.sendUserTaskInfo()

    def sendTimerUpdate(self):
        """
        任务进度刷新
        """
        if self.taskConfig["type"] == TaskType.HoldCoin:
            self.taskSystem.refreshHoldCoin(self.player.holdCoin)
        elif self.taskConfig["type"] == TaskType.UpgradeLevel:
            gunLevel = self.player.gunLevel - 2100
            self.taskSystem.refreshLevel(gunLevel)
        if self.isSendMsg:
            self.sendUserTaskInfo()

    def delaySendUserTaskInfo(self, interval=0):
        """
        延时发送任务信息
        """
        if self.sendTaskInfoTimer:
            self.sendTaskInfoTimer.cancel()
        self.sendTaskInfoTimer = FTLoopTimer(interval, 0, self.sendUserTaskInfo)
        self.sendTaskInfoTimer.start()

    def sendUserTaskInfo(self):
        """
        发送任务信息
        """
        self.isSendMsg = False
        message = MsgPack()
        message.setCmd("table_task_info")
        message.setResult("gameId", FISH_GAMEID)
        message.setResult("userId", self.userId)
        taskInfo = self.getTaskInfo()
        message.setResult("taskInfo", taskInfo)
        router.sendToUser(message, self.userId)

    def _isComplete(self):
        """
        任务是否完成
        """
        assert (self.taskData)
        targetCount = self.taskData["targetNum"]
        return self.taskData["progress"] >= targetCount

    def isTaskSuccess(self):
        """
        任务成功
        """
        return self.taskData["state"] == TaskState.Success

    def getTaskConf(self):
        assert (self.taskConfig)
        return self.taskConfig

    def isTaskOver(self):
        """任务结束"""
        return self.taskData["state"] in [TaskState.Success, TaskState.Fail]

    def getTaskState(self):
        return self.taskData["state"]

    def getTaskTargets(self):
        if self.taskData["state"] != TaskState.Update:
            return []
        if self.taskConfig["type"] in [TaskType.CatchFishNum, TaskType.CatchFishCoin,
                                       TaskType.UseSkillCatchFishNum, TaskType.UseSkillCatchFishCoin]:
            if self.taskConfig["target"] != 0:
                return [self.taskConfig["target"]]
        return []

    def isLimitTime(self):
        """是否限制了时间"""
        if not self.taskConfig:
            return 0
        return 1 if self.taskConfig.get("timeLong", 0) != 0 else 0


# Definition for a binary tree node.
from typing import Optional

class TreeNode:
    def __init__(self, val=0, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right

class Solution:
    def upsideDownBinaryTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]:
        def dfs(node):
            res = node
            l = node.left
            if l:
                res = dfs(l)
                l.left = node.right
                l.right = node
            return res

        if not root:
            return None
        res = dfs(root)
        root.left = root.right = None
        return res