import functions.Policy as pc
import tensorflow as tf
import core
import Utils
from operator import mul
from functools import reduce
import numpy as np

class RecurrentStochasticPolicy(pc.Policy):
    def __init__(self, dtype, gs):
        # shortcuts
        action_dim = int(gs.output.shape[-1])
        state_dim = int(gs.input.shape[-1])

        action = tf.identity(gs.output, name=self.output_names[0])

        # standard deviation layer
        with tf.name_scope('stdevconcatOutput'):
            wo = tf.Variable(tf.zeros(shape=[1, action_dim], dtype=dtype), name='W',
                             trainable=True)  # Log standard deviation
            action_stdev = tf.identity(tf.exp(wo), name='stdev')

        gs.l_param_list.append(wo)
        gs.a_param_list.append(wo)

        super(RecurrentStochasticPolicy, self).__init__(dtype, gs)

        stdev_assign_placeholder = tf.placeholder(dtype, shape=[1, action_dim], name='Stdev_placeholder')
        Stdev_assign = tf.assign(wo, tf.log(stdev_assign_placeholder), name='assignStdev')
        Stdev_get = tf.exp(wo, name='getStdev')

        tangent_in = tf.placeholder(dtype, shape=[1, None], name='tangent')
        old_stdv = tf.placeholder(dtype, shape=[1, action_dim], name='stdv_o')
        old_action_sampled = tf.placeholder(dtype, shape=[None, None, action_dim], name='sampledAction')
        old_action_noise = tf.placeholder(dtype, shape=[None, None, action_dim], name='actionNoise')
        advantage_in = tf.placeholder(dtype, shape=[None, None], name='advantage')

        # Algorithm params
        kl_coeff = tf.Variable(tf.constant(1.0, dtype=dtype), name='kl_coeff', trainable=False)
        ent_coeff = tf.Variable(tf.constant(0.01, dtype=dtype), name='ent_coeff', trainable=False)
        clip_param = tf.Variable(tf.constant(0.2, dtype=dtype), name='clip_param', trainable=False)

        PPO_params_placeholder = tf.placeholder(dtype, shape=[1, 3], name='PPO_params_placeholder')

        param_assign_op_list = []
        param_assign_op_list += [
            tf.assign(kl_coeff, tf.reshape(tf.slice(PPO_params_placeholder, [0, 0], [1, 1]), []), name='kl_coeff_assign')]
        param_assign_op_list += [
            tf.assign(ent_coeff, tf.reshape(tf.slice(PPO_params_placeholder, [0, 1], [1, 1]), []), name='ent_coeff_assign')]
        param_assign_op_list += [
            tf.assign(clip_param, tf.reshape(tf.slice(PPO_params_placeholder, [0, 2], [1, 1]), []), name='clip_param_assign')]

        PPO_param_assign_ops = tf.group(*param_assign_op_list, name='PPO_param_assign_ops')

        mask = tf.sequence_mask(gs.seq_length, maxlen=tf.shape(gs.input)[1], name='mask')
        action_target = tf.placeholder(dtype, shape=[None, None, None], name='targetAction')

        action_masked = tf.boolean_mask(action, mask)
        action_target_masked = tf.boolean_mask(action_target, mask)

        with tf.name_scope('trainUsingTarget'):
            core.square_loss_opt(dtype, action_target_masked, action_masked, tf.train.AdamOptimizer(learning_rate=self.learningRate), maxnorm=self.max_grad_norm)

        with tf.name_scope('trainUsingGrad'):
            gradient_from_critic = tf.placeholder(dtype, shape=[1, None], name='Inputgradient')
            train_using_grad_optimizer = tf.train.AdamOptimizer(learning_rate=self.learningRate)

            split_parameter_gradients = tf.split(gradient_from_critic,
                                                 [reduce(mul, param.get_shape().as_list(), 1) for param in
                                                  gs.a_param_list], 1)
            manipulated_parameter_gradients = []
            for grad, param in zip(split_parameter_gradients, gs.l_param_list):
                manipulated_parameter_gradients += [tf.reshape(grad, tf.shape(param))]

            manipulated_parameter_gradients_and_parameters = zip(manipulated_parameter_gradients, gs.l_param_list)
            train_using_gradients = train_using_grad_optimizer.apply_gradients(
                manipulated_parameter_gradients_and_parameters, name='applyGradients')

        util = Utils.Utils(dtype)

        with tf.name_scope('Algo'):
            logp_n = tf.boolean_mask(util.log_likelihood(action, action_stdev, old_action_sampled), mask)
            logp_old = tf.boolean_mask(util.log_likelihood(old_action_noise, old_stdv), mask)
            advantage = tf.boolean_mask(advantage_in, mask)
            ratio = tf.exp(logp_n - logp_old)
            ent = tf.reduce_sum(wo + .5 * tf.cast(tf.log(2.0 * np.pi * np.e), dtype=dtype), axis=-1)
            meanent = tf.reduce_mean(ent)

            with tf.name_scope('PPO'):
                surr1 = tf.multiply(ratio, advantage)
                surr2 = tf.multiply(tf.clip_by_value(ratio, 1.0 - clip_param, 1.0 + clip_param), advantage)
                PPO_loss = tf.reduce_mean(tf.maximum(surr1, surr2))  # PPO's pessimistic surrogate (L^CLIP)

                kl_ = tf.boolean_mask(
                    util.kl_divergence((old_action_sampled - old_action_noise), old_stdv, action, action_stdev), mask)
                kl_mean = tf.reshape(tf.reduce_mean(kl_), shape=[1, 1, 1], name='kl_mean')

                Total_loss = PPO_loss - tf.multiply(ent_coeff, meanent)
                Total_loss2 = PPO_loss - tf.multiply(ent_coeff, meanent) + tf.multiply(kl_coeff, tf.reduce_mean(kl_))

                policy_gradient = tf.identity(tf.expand_dims(util.flatgrad(Total_loss, gs.l_param_list, self.max_grad_norm), axis=0), name='Pg')  # flatgrad
                policy_gradient2 = tf.identity(tf.expand_dims(util.flatgrad(Total_loss2, gs.l_param_list, self.max_grad_norm), axis=0), name='Pg2')  # flatgrad


from __future__ import print_function

import glob
import warnings

import numpy as np
import pandas as pd
import soundfile
import os
import os.path as osp
import librosa
import torch
from desed.utils import create_folder
from torch import nn

import config as cfg

def read_audio(path, target_fs=None, **kwargs):
    """ Read a wav file
    Args:
        path: str, path of the audio file
        target_fs: int, (Default value = None) sampling rate of the returned audio file, if not specified, the sampling
            rate of the audio file is taken

    Returns:
        tuple
        (numpy.array, sampling rate), array containing the audio at the sampling rate given

    """
    (audio, fs) = soundfile.read(path, **kwargs)
    if audio.ndim > 1:
        audio = np.mean(audio, axis=1)
    if target_fs is not None and fs != target_fs:
        audio = librosa.resample(audio, orig_sr=fs, target_sr=target_fs)
        fs = target_fs
    return audio, fs

def weights_init(m):
    """ Initialize the weights of some layers of neural networks, here Conv2D, BatchNorm, GRU, Linear
        Based on the work of Xavier Glorot
    Args:
        m: the model to initialize
    """
    classname = m.__class__.__name__
    if classname.find('Conv2d') != -1:
        nn.init.xavier_uniform_(m.weight, gain=np.sqrt(2))
        m.bias.data.fill_(0)
    elif classname.find('BatchNorm') != -1:
        m.weight.data.normal_(1.0, 0.02)
        m.bias.data.fill_(0)
    elif classname.find('GRU') != -1:
        for weight in m.parameters():
            if len(weight.size()) > 1:
                nn.init.orthogonal_(weight.data)
    elif classname.find('Linear') != -1:
        m.weight.data.normal_(0, 0.01)
        m.bias.data.zero_()

def to_cuda_if_available(*args):
    """ Transfer object (Module, Tensor) to GPU if GPU available
    Args:
        args: torch object to put on cuda if available (needs to have object.cuda() defined)

    Returns:
        Objects on GPU if GPUs available
    """
    res = list(args)
    if torch.cuda.is_available():
        for i, torch_obj in enumerate(args):
            res[i] = torch_obj.cuda()
    if len(res) == 1:
        return res[0]
    return res

class SaveBest:
    """ Callback to get the best value and epoch
    Args:
        val_comp: str, (Default value = "inf") "inf" or "sup", inf when we store the lowest model, sup when we
            store the highest model
    Attributes:
        val_comp: str, "inf" or "sup", inf when we store the lowest model, sup when we
            store the highest model
        best_val: float, the best values of the model based on the criterion chosen
        best_epoch: int, the epoch when the model was the best
        current_epoch: int, the current epoch of the model
    """
    def __init__(self, val_comp="inf"):
        self.comp = val_comp
        if val_comp in ["inf", "lt", "desc"]:
            self.best_val = np.inf
        elif val_comp in ["sup", "gt", "asc"]:
            self.best_val = 0
        else:
            raise NotImplementedError("value comparison is only 'inf' or 'sup'")
        self.best_epoch = 0
        self.current_epoch = 0

    def apply(self, value):
        """ Apply the callback
        Args:
            value: float, the value of the metric followed
        """
        decision = False
        if self.current_epoch == 0:
            decision = True
        if (self.comp == "inf" and value < self.best_val) or (self.comp == "sup" and value > self.best_val):
            self.best_epoch = self.current_epoch
            self.best_val = value
            decision = True
        self.current_epoch += 1
        return decision

class EarlyStopping:
    """ Callback to stop training if the metric have not improved during multiple epochs.
    Args:
        patience: int, number of epochs with no improvement before stopping the model
        val_comp: str, (Default value = "inf") "inf" or "sup", inf when we store the lowest model, sup when we
            store the highest model
    Attributes:
        patience: int, number of epochs with no improvement before stopping the model
        val_comp: str, "inf" or "sup", inf when we store the lowest model, sup when we
            store the highest model
        best_val: float, the best values of the model based on the criterion chosen
        best_epoch: int, the epoch when the model was the best
        current_epoch: int, the current epoch of the model
    """
    def __init__(self, patience, val_comp="inf", init_patience=0):
        self.patience = patience
        self.first_early_wait = init_patience
        self.val_comp = val_comp
        if val_comp == "inf":
            self.best_val = np.inf
        elif val_comp == "sup":
            self.best_val = 0
        else:
            raise NotImplementedError("value comparison is only 'inf' or 'sup'")
        self.current_epoch = 0
        self.best_epoch = 0

    def apply(self, value):
        """ Apply the callback

        Args:
            value: the value of the metric followed
        """
        current = False
        if self.val_comp == "inf":
            if value < self.best_val:
                current = True
        if self.val_comp == "sup":
            if value > self.best_val:
                current = True
        if current:
            self.best_val = value
            self.best_epoch = self.current_epoch
        elif self.current_epoch - self.best_epoch > self.patience and self.current_epoch > self.first_early_wait:
            self.current_epoch = 0
            return True
        self.current_epoch += 1
        return False

class AverageMeterSet:
    def __init__(self):
        self.meters = {}

    def __getitem__(self, key):
        return self.meters[key]

    def update(self, name, value, n=1):
        if name not in self.meters:
            self.meters[name] = AverageMeter()
        self.meters[name].update(value, n)

    def reset(self):
        for meter in self.meters.values():
            meter.reset()

    def values(self, postfix=''):
        return {name + postfix: meter.val for name, meter in self.meters.items()}

    def averages(self, postfix='/avg'):
        return {name + postfix: meter.avg for name, meter in self.meters.items()}

    def sums(self, postfix='/sum'):
        return {name + postfix: meter.sum for name, meter in self.meters.items()}

    def counts(self, postfix='/count'):
        return {name + postfix: meter.count for name, meter in self.meters.items()}

    def __str__(self):
        string = ""
        for name, meter in self.meters.items():
            fmat = ".4f"
            if meter.val < 0.01:
                fmat = ".2E"
            string += "{} {:{format}} \t".format(name, meter.val, format=fmat)
        return string

class AverageMeter:
    """Computes and stores the average and current value"""

    def __init__(self):
        self.reset()

    def reset(self):
        self.val = 0
        self.avg = 0
        self.sum = 0
        self.count = 0

    def update(self, val, n=1):
        self.val = val
        self.sum += val * n
        self.count += n
        self.avg = self.sum / self.count

    def __format__(self, format):
        return "{self.avg:{format}}".format(self=self, format=format)

def generate_tsv_wav_durations(audio_dir, out_tsv):
    """ Generate a dataframe with filename and duration of the file
    Args:
        audio_dir: str, the path of the folder where audio files are (used by glob.glob)
        out_tsv: str, the path of the output tsv file

    Returns:
        pd.DataFrame, the dataframe containing filenames and durations
    """
    meta_list = []
    for file in glob.glob(os.path.join(audio_dir, "*.wav")):
        d = soundfile.info(file).duration
        meta_list.append([os.path.basename(file), d])
    meta_df = pd.DataFrame(meta_list, columns=["filename", "duration"])
    if out_tsv is not None:
        meta_df.to_csv(out_tsv, sep="\t", index=False, float_format="%.1f")
    return meta_df

def generate_tsv_from_isolated_events(wav_folder, out_tsv=None):
    """ Generate list of separated wav files in a folder and export them in a tsv file
    Separated audio files considered are all wav files in 'subdirectories' of the 'wav_folder'
    Args:
        wav_folder: str, path of the folder containing subdirectories (one for each mixture separated)
        out_tsv: str, path of the csv in which to save the list of files
    Returns:
        pd.DataFrame, having only one column with the filename considered
    """
    if out_tsv is not None and os.path.exists(out_tsv):
        source_sep_df = pd.read_csv(out_tsv, sep="\t")
    else:
        source_sep_df = pd.DataFrame()
        list_dirs = [d for d in os.listdir(wav_folder) if osp.isdir(osp.join(wav_folder, d))]
        for dirname in list_dirs:
            list_isolated_files = []
            for directory, subdir, fnames in os.walk(osp.join(wav_folder, dirname)):
                for fname in fnames:
                    if osp.splitext(fname)[1] in [".wav"]:
                        # Get the level folders and keep it in the tsv
                        subfolder = directory.split(dirname + os.sep)[1:]
                        if len(subfolder) > 0:
                            subdirs = osp.join(*subfolder)
                        else:
                            subdirs = ""
                        # Append the subfolders and name in the list of files
                        list_isolated_files.append(osp.join(dirname, subdirs, fname))
                    else:
                        warnings.warn(f"Not only wav audio files in the separated source folder,"
                                      f"{fname} not added to the .tsv file")
            source_sep_df = source_sep_df.append(pd.DataFrame(list_isolated_files, columns=["filename"]))
        if out_tsv is not None:
            create_folder(os.path.dirname(out_tsv))
            source_sep_df.to_csv(out_tsv, sep="\t", index=False, float_format="%.3f")
    return source_sep_df

def meta_path_to_audio_dir(tsv_path):
    return os.path.splitext(tsv_path.replace("metadata", "audio"))[0]

def audio_dir_to_meta_path(audio_dir):
    return audio_dir.replace("audio", "metadata") + ".tsv"

def get_durations_df(gtruth_path, audio_dir=None):
    if audio_dir is None:
        audio_dir = meta_path_to_audio_dir(gtruth_path)
    path, ext = os.path.splitext(gtruth_path)
    path_durations_synth = path + "_durations" + ext
    if not os.path.exists(path_durations_synth):
        durations_df = generate_tsv_wav_durations(audio_dir, path_durations_synth)
    else:
        durations_df = pd.read_csv(path_durations_synth, sep="\t")
    return durations_df


import itertools
import logging
import math
import random
import cv2
import numpy as np
import torch
from typing import Any, Dict, List, Optional, Tuple
from albumentations import Rotate, DualTransform, ImageOnlyTransform, Resize, RandomScale
from albumentations.augmentations.crops import functional as F_crops
from albumentations.augmentations.geometric import functional as F_geometric
from albumentations.augmentations import functional as F
from torchvision.transforms import functional as F_torchvision
from timm.data.constants import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD

logger = logging.getLogger(__name__)

class RotateZ(Rotate):
    """Rotate around X axis on randomly selected angle."""
    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, Any]:
        # (H, W, D) -> (D, H, W)
        kwargs['image'] = np.transpose(kwargs['image'], (2, 0, 1))

        # Rotate as usual image
        kwargs = super().__call__(*args, force_apply=force_apply, **kwargs)

        # (D, H, W) -> (H, W, D)
        kwargs['image'] = np.transpose(kwargs['image'], (1, 2, 0))

        return kwargs

    def apply_to_mask(self, img, angle=0, x_min=None, x_max=None, y_min=None, y_max=None, **params):
        # Project mask to XZ plane.

        # cv2's y corresponds to H dimention which is shrinked
        # by cos(angle): mask is projected to XZ plane.
        H, W = img.shape[:2]
        img_out = cv2.resize(
            img,
            (0, 0),
            fx=1,
            fy=np.cos(np.deg2rad(angle)),
            interpolation=cv2.INTER_NEAREST,
        )

        # Pad mask to the same size as image by H dimention.
        top_pad = (H - img_out.shape[0]) // 2
        bottom_pad = H - img_out.shape[0] - top_pad
        img_out = cv2.copyMakeBorder(
            img_out,
            top=top_pad,
            bottom=bottom_pad,
            left=0,
            right=0,
            borderType=self.border_mode,
        )

        # Crop mask: if border is cropped, then
        # mask should not only be projected to XZ plane,
        # but also cropped to the same size as image by H dimention.

        # Here only H changes and H corresponds
        # to y here and x in image case because of transpose
        # (see __call__ method).
        if self.crop_border:
            img_out = F_crops.crop(img_out, x_min=0, y_min=x_min, x_max=W, y_max=x_max)

        return img_out

class RandomCropVolumeInside2dMask:
    """Crop a random part of the input.
    """
    def __init__(
        self,
        base_size: Optional[int] = None,
        base_depth: Optional[int] = None,
        scale: Tuple[float, float] = (1.0, 1.0),
        scale_z: Tuple[float, float] = (1.0, 1.0),
        ratio: Tuple[float, float] = (1.0, 1.0),
        value: int = 0,
        mask_value: int = 0,
        crop_mask_index=0,
        always_apply=True,
        p=1.0,
    ):
        if not (base_size is not None or base_depth is not None):
            logger.warning(
                f"Eigher base_size or base_depth should be not None. "
                f"Got base_size={base_size}, base_depth={base_depth}. "
                f"No transform will be performed."
            )

        assert scale[0] > 0 and scale[1] > 0, f"scale should be positive. Got {scale}"
        assert scale_z[0] > 0 and scale_z[1] > 0, f"scale_z should be positive. Got {scale_z}"
        assert ratio[0] > 0 and ratio[1] > 0, f"ratio should be positive. Got {ratio}"
        assert scale[0] <= scale[1], f"scale[0] should be less or equal than scale[1]. Got {scale}"
        assert scale_z[0] <= scale_z[1], f"scale_z[0] should be less or equal than scale_z[1]. Got {scale_z}"
        assert ratio[0] <= ratio[1], f"ratio[0] should be less or equal than ratio[1]. Got {ratio}"

        self.base_size = base_size
        self.base_depth = base_depth
        self.scale = scale
        self.scale_z = scale_z
        self.ratio = ratio
        self.value = value
        self.mask_value = mask_value
        self.crop_mask_index = crop_mask_index

        self.always_apply = always_apply
        self.p = p

    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        # Toss a coin
        if not force_apply and not self.always_apply and random.random() > self.p:
            return kwargs

        # Get random scale and ratio
        scale = random.uniform(self.scale[0], self.scale[1])
        ratio = random.uniform(self.ratio[0], self.ratio[1])

        h_start, h_end = 0, kwargs['image'].shape[0]
        w_start, w_end = 0, kwargs['image'].shape[1]
        if self.base_size is not None:
            # Get height and width
            height = int(self.base_size * scale)
            width = int(self.base_size * scale * ratio)

            # Get crop mask
            crop_mask = kwargs['masks'][self.crop_mask_index]  # (H, W)

            # Crop the mask to ensure that the crop is inside the mask
            h_shift, w_shift = height // 2 + 1, width // 2 + 1
            crop_mask = crop_mask[
                h_shift:-h_shift,
                w_shift:-w_shift,
            ]

            # Get indices of non-zero elements
            nonzero_indices = np.nonzero(crop_mask)

            # Get random index
            random_index = random.randint(0, nonzero_indices[0].shape[0] - 1)
            h_center, w_center = nonzero_indices[0][random_index], nonzero_indices[1][random_index]

            # Shift indices back to compensate crop above
            h_center += h_shift
            w_center += w_shift

            # Get crop indices
            h_start = h_center - height // 2
            h_end = h_start + height
            w_start = w_center - width // 2
            w_end = w_start + width

            # Ensure that crop is inside the image
            assert h_start >= 0 and h_end <= kwargs['image'].shape[0], \
                f"h_start={h_start} and h_end={h_end} should be in [0, {kwargs['image'].shape[0]}]"
            assert w_start >= 0 and w_end <= kwargs['image'].shape[1], \
                f"w_start={w_start} and w_end={w_end} should be in [0, {kwargs['image'].shape[1]}]"

        scale_z = random.uniform(self.scale_z[0], self.scale_z[1])
        z_start, z_end = 0, kwargs['image'].shape[2]
        if self.base_depth is not None:
            # Get depth
            depth = int(self.base_depth * scale_z)

            # Get random z_start
            z_start_max = max(kwargs['image'].shape[2] - depth, 1)
            z_start = np.random.randint(0, z_start_max)

            # Get z_end
            z_end = min(z_start + depth, kwargs['image'].shape[2])

        # Crop data
        kwargs['image'] = kwargs['image'][h_start:h_end, w_start:w_end, z_start:z_end].copy()
        for i in range(len(kwargs['masks'])):
            kwargs['masks'][i] = kwargs['masks'][i][h_start:h_end, w_start:w_end].copy()

        return kwargs

class RandomCropVolume(DualTransform):
    """Crop a random part of the input.
    If image, crop is 3D, if mask, crop is 2D.

    Args:
        height (int): height of the crop.
        width (int): width of the crop.
        depth (int): depth of the crop.
        p (float): probability of applying the transform. Default: 1.

    Targets:
        image, mask

    Image types:
        uint8, float32
    """

    def __init__(self, height, width, depth, always_apply=False, p=1.0):
        super().__init__(always_apply, p)
        self.height = height
        self.width = width
        self.depth = depth

    def apply(self, img, h_start=0, w_start=0, d_start=0, is_mask=False, **params):
        img = F_crops.random_crop(img, self.height, self.width, h_start, w_start)
        if not is_mask:
            z = np.random.randint(d_start, max(img.shape[2] - self.depth, d_start + 1))
            img = img[:, :, z:min(z + self.depth, img.shape[2])]
        return img

    def apply_to_mask(self, img: np.ndarray, **params) -> np.ndarray:
        return self.apply(img, is_mask=True, **{k: cv2.INTER_NEAREST if k == "interpolation" else v for k, v in params.items()})

    def apply_to_bbox(self, bbox, **params):
        return F_crops.bbox_random_crop(bbox, self.height, self.width, **params)

    def apply_to_keypoint(self, keypoint, **params):
        return F_crops.keypoint_random_crop(keypoint, self.height, self.width, **params)

    def get_params(self):
        return {"h_start": random.random(), "w_start": random.random(), "d_start": random.random()}

    def get_transform_init_args_names(self):
        return ("height", "width", "depth")

class CenterCropVolume(DualTransform):
    """Crop the central part of the input.

    Args:
        height (int): height of the crop.
        width (int): width of the crop.
        depth (int): depth of the crop.
        strict (bool): if True, will fail if image size is less than crop size by any dimention,
            otherwise will crop as much as possible. Default: True.
        p (float): probability of applying the transform. Default: 1.

    Targets:
        image, mask

    Image types:
        uint8, float32

    Note:
        It is recommended to use uint8 images as input.
        Otherwise the operation will require internal conversion
        float32 -> uint8 -> float32 that causes worse performance.
    """

    def __init__(self, height, width, depth, strict=True, always_apply=False, p=1.0):
        super().__init__(always_apply, p)
        if not ((height is not None and width is not None) or depth is not None):
            logger.warning(
                f"Eigher height and width or depth should be not None. "
                f"Got height={height}, width={width}, depth={depth}. "
                f"No transform will be performed."
            )
        self.height = height
        self.width = width
        self.depth = depth
        self.strict = strict

    def apply(self, img, is_mask=False, **params):
        if self.height is not None and self.width is not None:
            height, width = img.shape[0], img.shape[1]
            if not self.strict:
                if height < self.height:
                    logger.warning(
                        f"Height of the image is {height} but height of the crop is {self.height}. "
                        f"All the height of the image will be used."
                    )
                if width < self.width:
                    logger.warning(
                        f"Width of the image is {width} but width of the crop is {self.width}. "
                        f"All the width of the image will be used."
                    )
                height, width = \
                    min(img.shape[0], self.height), \
                    min(img.shape[1], self.width)
            img = F_crops.center_crop(img, height, width)
        if not is_mask and self.depth is not None:
            if img.shape[2] < self.depth:
                logger.warning(
                    f"Depth of the image is {img.shape[2]} but depth of the crop is {self.depth}. "
                    f"All the depth of the image will be used."
                )

            if self.strict:
                assert img.shape[2] >= self.depth, \
                    f"Depth of the image is {img.shape[2]} but depth of the crop is {self.depth}. " \

            z_start = max((img.shape[2] - self.depth) // 2, 0)
            z_end = min(z_start + self.depth, img.shape[2])
            img = img[:, :, z_start:z_end]
        return img

    def apply_to_mask(self, img: np.ndarray, **params) -> np.ndarray:
        return self.apply(img, is_mask=True, **{k: cv2.INTER_NEAREST if k == "interpolation" else v for k, v in params.items()})

    def apply_to_bbox(self, bbox, **params):
        return F_crops.bbox_center_crop(bbox, self.height, self.width, **params)

    def apply_to_keypoint(self, keypoint, **params):
        return F_crops.keypoint_center_crop(keypoint, self.height, self.width, **params)

    def get_transform_init_args_names(self):
        return ("height", "width", "depth")

class RandomZSlices(ImageOnlyTransform):
    """Randomly select N (permuted) slices from the input.

    Args:
        n_slices (int): number of slices to select.
        p (float): probability of applying the transform. Default: 1.

    Targets:
        image, mask

    Image types:
        uint8, float32
    """

    def __init__(self, n_slices, permuted=True, always_apply=False, p=1.0):
        super().__init__(always_apply, p)
        self.n_slices = n_slices
        self.permuted = permuted

    def apply(self, img, **params):
        indices = np.random.permutation(img.shape[2])[:self.n_slices]
        if not self.permuted:
            indices = np.sort(indices)
        return img[:, :, indices]

    def get_transform_init_args_names(self):
        return ("n_slices", )

class UniformZSlices(ImageOnlyTransform):
    """Select N (uniformly) slices from the input.

    Args:
        n_slices (int): number of slices to select.
        p (float): probability of applying the transform. Default: 1.

    Targets:
        image, mask

    Image types:
        uint8, float32
    """

    def __init__(self, n_slices, always_apply=False, p=1.0):
        super().__init__(always_apply, p)
        self.n_slices = n_slices

    def apply(self, img, **params):
        indices = np.linspace(0, img.shape[2] - 1, self.n_slices).astype(int)
        return img[:, :, indices]

    def get_transform_init_args_names(self):
        return ("n_slices", )

class ResizeVolume(Resize):
    def __init__(self, height, width, depth=None, interpolation=cv2.INTER_LINEAR, always_apply=False, p=1):
        super().__init__(height, width, interpolation, always_apply, p)
        self.depth = depth

    def apply(self, img, interpolation=cv2.INTER_LINEAR, is_mask=False, **params):
        # Interpolate 2D
        img = super().apply(img, interpolation=interpolation, **params)
        if is_mask:
            return img

        if self.depth is not None:
            # Interpolate depth
            # (H, W, D) -> (D, H, W)
            img = np.transpose(img, (2, 0, 1))

            # Resize as usual image
            img = F_geometric.resize(img, height=self.depth, width=img.shape[1], interpolation=interpolation)

            # (D, H, W) -> (H, W, D)
            img = np.transpose(img, (1, 2, 0))

        return img

    def apply_to_mask(self, img: np.ndarray, **params) -> np.ndarray:
        return self.apply(img, is_mask=True, **{k: cv2.INTER_NEAREST if k == "interpolation" else v for k, v in params.items()})

class ToCHWD(ImageOnlyTransform):
    def apply(self, img: torch.Tensor, **params) -> torch.Tensor:
        # (D, H, W) (grayscale) ->
        # (1, H, W, D)
        return img.unsqueeze(0).permute(0, 2, 3, 1)

class ToWritable(DualTransform):
    def apply(self, img, **params):
        img = img.copy()
        img.setflags(write=True)
        return img

class RandomScaleResize(RandomScale):
    """Same as RandomScale but resize to original size after scaling."""
    def apply(self, img, scale=0, interpolation=cv2.INTER_LINEAR, **params):
        original_h, original_w = img.shape[:2]
        img = super().apply(img, scale, interpolation, **params)
        return F_geometric.resize(img, height=original_h, width=original_w, interpolation=interpolation)

class SubtractDivide:
    """Subtracttion and division is applied by the formula:
    `img = (img - subtract) / divide`

    Targets:
        image

    Image types:
        uint8, float32
    """

    def __init__(
        self,
        subtract: float = 0.0,
        divide: float = 1.0,
        always_apply=True,
        p=1.0,
    ):
        self.subtract = subtract
        self.divide = divide
        self.always_apply = always_apply
        self.p = p

    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        # Toss a coin
        if not force_apply and not self.always_apply and random.random() > self.p:
            return kwargs

        subtract, divide = self.subtract, self.divide

        if (
            "subtract" in kwargs and kwargs["subtract"] is not None and
            "divide" in kwargs and kwargs["divide"] is not None
        ):
            subtract = kwargs["subtract"]
            divide = kwargs["divide"]
        else:
            logger.warning(
                f"Either subtract or divide values are not provided. "
                f"Subtract is set to {subtract} and divide is set to {divide}."
            )

        kwargs['image'] = F.normalize(kwargs['image'], subtract, divide, 1)

        return kwargs

class CopyPastePositive:
    """Copy masked area from one image to another.

    Targets:
        image, mask

    Image types:
        uint8, float32
    """

    def __init__(
        self,
        mask_index: int = 2,
        always_apply=True,
        p=1.0,
    ):
        self.mask_index = mask_index
        self.always_apply = always_apply
        self.p = p

    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        # Toss a coin
        if not force_apply and not self.always_apply and random.random() > self.p:
            return kwargs

        mask = (kwargs['masks'][self.mask_index] > 0) & (kwargs['masks1'][self.mask_index] <= 0)

        kwargs['image'][mask] = kwargs['image1'][mask]
        for i in range(len(kwargs['masks'])):
            kwargs['masks'][i][mask] = kwargs['masks1'][i][mask]

        return kwargs

# https://github.com/albumentations-team/albumentations/pull/1409/files
class MixUp:
    def __init__(
        self,
        alpha = 32.,
        beta = 32.,
        always_apply: bool = False,
        p: float = 0.5,
    ):
        self.alpha = alpha
        self.beta = beta
        self.always_apply = always_apply
        self.p = p

    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        # Toss a coin
        if not force_apply and not self.always_apply and random.random() > self.p:
            return kwargs

        h1, w1, _ = kwargs['image'].shape
        h2, w2, _ = kwargs['image1'].shape
        if h1 != h2 or w1 != w2:
            raise ValueError("MixUp transformation expects both images to have identical shape.")

        r = np.random.beta(self.alpha, self.beta)

        kwargs['image'] = (kwargs['image'] * r + kwargs['image1'] * (1 - r))
        for i in range(len(kwargs['masks'])):
            kwargs['masks'][i] = (kwargs['masks'][i] * r + kwargs['masks1'][i] * (1 - r))

        return kwargs

class CutMix:
    def __init__(
        self,
        width: int = 64,
        height: int = 64,
        always_apply: bool = False,
        p: float = 0.5,
    ):
        self.width = width
        self.height = height
        self.always_apply = always_apply
        self.p = p

    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        # Toss a coin
        if not force_apply and not self.always_apply and random.random() > self.p:
            return kwargs

        h, w, _ = kwargs['image'].shape
        h1, w1, _ = kwargs['image1'].shape
        if (
            h < self.height or
            w < self.width or
            h1 < self.height or
            w1 < self.width
        ):
            raise ValueError("CutMix transformation expects both images to be at least {}x{} pixels.".format(self.max_height, self.max_width))

        # Get random bbox
        h_start = random.randint(0, h - self.height)
        w_start = random.randint(0, w - self.width)
        h_end = h_start + self.height
        w_end = w_start + self.width

        # Copy image and masks region
        kwargs['image'][h_start:h_end, w_start:w_end] = kwargs['image1'][h_start:h_end, w_start:w_end]
        for i in range(len(kwargs['masks'])):
            kwargs['masks'][i][h_start:h_end, w_start:w_end] = kwargs['masks1'][i][h_start:h_end, w_start:w_end]

        return kwargs

class ToFloatMasks:
    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        for i in range(len(kwargs['masks'])):
            kwargs['masks'][i] = kwargs['masks'][i].astype(np.float32)
        return kwargs

class EnlargePositive:
    """Scale up masked area."""
    def __init__(
        self,
        mask_index: int = 2,
        scale_limit: float = 0.1,
        interpolation=cv2.INTER_LINEAR,
        always_apply=False,
        p=1.0,
    ):
        self.mask_index = mask_index
        self.scale_limit = scale_limit
        self.interpolation = interpolation
        self.always_apply = always_apply
        self.p = p

    def __call__(self, *args, force_apply: bool = False, **kwargs) -> Dict[str, np.ndarray]:
        # Toss a coin
        if not force_apply and not self.always_apply and random.random() > self.p:
            return kwargs

        # Get random scale
        scale = 1 + random.uniform(0, self.scale_limit)

        # Get crop bbox around center of mass
        mask = kwargs['masks'][self.mask_index]
        center_h = math.floor(mask.mean(axis=1) * scale)
        center_w = math.floor(mask.mean(axis=0) * scale)
        h_start = max(0, center_h - mask.shape[0] // 2)
        w_start = max(0, center_w - mask.shape[1] // 2)
        h_end = min(kwargs['image'].shape[0], h_start + mask.shape[0])
        w_end = min(kwargs['image'].shape[1], w_start + mask.shape[1])

        # Copy image and masks
        image_larger = kwargs['image'].copy()
        masks_larger = [mask.copy() for mask in kwargs['masks']]

        # Scale up and crop around center of mass
        image_larger = cv2.resize(
            image_larger,
            (0, 0),
            fx=scale,
            fy=scale,
            interpolation=self.interpolation,
        )
        image_larger = F_crops.crop(
            image_larger,
            xmin=w_start,
            ymin=h_start,
            xmax=w_end,
            ymax=h_end,
        )

        for i in range(len(masks_larger)):
            masks_larger[i] = cv2.resize(
                masks_larger[i],
                (0, 0),
                fx=scale,
                fy=scale,
                interpolation=cv2.INTER_NEAREST,
            )
            masks_larger[i] = F_crops.crop(
                masks_larger[i],
                xmin=w_start,
                ymin=h_start,
                xmax=w_end,
                ymax=h_end,
            )

        # Get enlarged mask
        mask = masks_larger[self.mask_index] > 0

        # Copy masked area
        kwargs['image'][h_start:h_end, w_start:w_end][mask] = image_larger[mask]
        for i in range(len(kwargs['masks'])):
            kwargs['masks'][i][h_start:h_end, w_start:w_end][mask] = masks_larger[i][mask]

        return kwargs

class TtaHorizontalFlip:
    def apply(self, batch: torch.Tensor) -> torch.Tensor:
        # (N, C, H, W, D)
        return batch.flip(3)

    def apply_inverse_to_pred(self, batch_pred: torch.Tensor) -> torch.Tensor:
        # (N, C = 1, H, W)
        return batch_pred.flip(3)

class TtaVerticalFlip:
    def apply(self, batch: torch.Tensor) -> torch.Tensor:
        # (N, C, H, W, D)
        return batch.flip(2)

    def apply_inverse_to_pred(self, batch_pred: torch.Tensor) -> torch.Tensor:
        # (N, C = 1, H, W)
        return batch_pred.flip(2)

class TtaRotate90:
    def __init__(self, n_rot) -> None:
        assert n_rot % 4 != 0, f"n_rot should not be divisible by 4. Got {n_rot}"
        self.n_rot = n_rot % 4

    def apply(self, batch: torch.Tensor) -> torch.Tensor:
        # (N, C, H, W, D)
        return batch.rot90(self.n_rot, (2, 3))

    def apply_inverse_to_pred(self, batch_pred: torch.Tensor) -> torch.Tensor:
        # (N, C = 1, H, W)
        return batch_pred.rot90(-self.n_rot, (2, 3))

class TtaRotate:
    def __init__(self, limit_degrees: int = 90, fill_value=0.0) -> None:
        self.limit_degrees = limit_degrees
        self.fill_value = fill_value
        self.angle = None

    @staticmethod
    def _rotate(batch: torch.Tensor, angle: int, fill: float=0) -> torch.Tensor:
        N, C, H, W, D = batch.shape
        # (N, C, H, W, D) -> (N, C, D, H, W)
        batch = batch.permute(0, 1, 4, 2, 3)
        # (N, C, D, H, W) -> (N, C * D, H, W)
        batch = batch.reshape(N, C * D, H, W)
        batch = F_torchvision.rotate(
            batch,
            angle,
            interpolation=F_torchvision.InterpolationMode.BILINEAR,
            expand=False,
            fill=fill,
        )
        # (N, C * D, H, W) -> (N, C, D, H, W)
        batch = batch.reshape(N, C, D, H, W)
        # (N, C, D, H, W) -> (N, C, H, W, D)
        batch = batch.permute(0, 1, 3, 4, 2)
        return batch

    def apply(self, batch: torch.Tensor) -> torch.Tensor:
        assert self.angle is None, "TtaRotate should be applied only once."
        # (N, C, H, W, D)
        self.angle = random.randint(-self.limit_degrees, self.limit_degrees)
        return TtaRotate._rotate(batch, self.angle, fill=self.fill_value)

    def apply_inverse_to_pred(self, batch_pred: torch.Tensor) -> torch.Tensor:
        assert self.angle is not None, "TtaRotate should be applied before TtaRotate.apply_inverse_to_pred."
        # (N, C = 1, H, W) -> (N, C = 1, H, W, D = 1)
        batch_pred = batch_pred.unsqueeze(-1)
        # Fill with NaNs to ignore them in averaging
        batch_pred = TtaRotate._rotate(batch_pred, -self.angle, fill=torch.nan)
        # (N, C = 1, H, W, D = 1) -> (N, C = 1, H, W)
        batch_pred = batch_pred.squeeze(-1)
        self.angle = None
        return batch_pred

class Tta:
    def __init__(self, model, n_random_replays=1, use_hflip=True, use_vflip=True, rotate90_indices=None):
        assert n_random_replays > 0 or use_hflip or use_vflip or rotate90_indices is not None, \
            "At least one of n_random_replays > 0, "\
            "use_hflip or use_vflip or rotate90_indices is not None should be True."
        assert all([i > 0 and i <= 3 for i in rotate90_indices]), \
            f"rotate90_indices should be in [0, 3]. Got {rotate90_indices}"
        assert len(rotate90_indices) == len(set(rotate90_indices)), \
            f"rotate90_indices should not contain duplicates. Got {rotate90_indices}"
        self.model = model

        # Imagenet normalization during training is assumed
        fill_value = (0.0 - sum(IMAGENET_DEFAULT_MEAN) / 3) / (sum(IMAGENET_DEFAULT_STD) / 3)

        # All possible combinations of
        # - flips
        # - rotations on 90 degrees
        # - n_replays rotations on random angle
        rotates90 = [None]
        if rotate90_indices is not None:
            rotates90 = [None] + [
                TtaRotate90(i) for i in rotate90_indices
            ]
        flips = [None]
        if use_hflip:
            flips += [
                TtaHorizontalFlip(),
            ]
        if use_vflip:
            flips += [
                TtaVerticalFlip(),
            ]
        rotates = [None]
        if n_random_replays > 0:
            rotates = [None] + [
                TtaRotate(limit_degrees=45, fill_value=fill_value)
                for _ in range(n_random_replays)
            ]
        self.transforms = [
            rotates90,
            flips,
            rotates,
        ]

    def predict(self, batch: torch.Tensor) -> List[torch.Tensor]:
        preds = []

        # Apply TTA
        for transform_chain in itertools.product(*self.transforms):
            # Direct transform
            batch_aug = batch.clone()
            for transform in transform_chain:
                if transform is not None:
                    batch_aug = transform.apply(batch_aug)

            # Predict
            pred_aug = self.model(batch_aug)
            if pred_aug.ndim == 3:
                pred_aug = pred_aug.unsqueeze(1)

            # Inverse transform
            # Note: order of transforms is reversed
            for transform in reversed(transform_chain):
                if transform is not None:
                    pred_aug = transform.apply_inverse_to_pred(pred_aug)

            preds.append(pred_aug)

        return preds

    def __call__(self, batch: torch.Tensor) -> torch.Tensor:
        preds = self.predict(batch)

        # Average predictions, ignoring NaNs
        preds = torch.stack(preds, dim=0)
        preds = torch.nanmean(preds, dim=0)

        return preds


from cilcdjango.core.media import SharedMediaMixin, make_shared_media_url
from cilcdjango.core.util import get_app_setting

from django import forms
from django.conf import settings
from django.core.urlresolvers import reverse
from django.forms.widgets import flatatt
from django.utils.encoding import force_unicode, smart_unicode
from django.utils.html import escape
from django.utils.safestring import mark_safe
from django.utils.translation import ugettext as _

import datetime
import re

#-------------------------------------------------------------------------------
#  Helpers
#-------------------------------------------------------------------------------

def _update_attr_classes(attrs, classes):
	"""
	Add the classes contained in the `classes` iterable to the "class" key of
	the attributes passed to the widget's constructor in the `attrs` dict.
	"""
	if not attrs:
		attrs = {}
	attrs['class'] = u" ".join(set(re.split(r'\s+', attrs.get('class', '')) + classes)).strip()
	return attrs

#-------------------------------------------------------------------------------
#  Date / Time Widgets
#-------------------------------------------------------------------------------

class DatePickerWidget(forms.DateTimeInput, SharedMediaMixin):
	"""A widget that lets a user use a JavaScript date picker to select a date."""

	def render(self, name, value, attrs=None):

		#  Attempt to turn a previous date value into one capable of being set
		#  as the input field's value attribute.
		try:
			previous_date = value.strftime(self.format)
		except AttributeError:
			if value is None or value == "None":
				previous_date = ""
			else:
				previous_date = value
		current_value = "value='%s'" % escape(previous_date)

		#  Render the widget as a simple text field, which is transformed into a
		#  datepicker widget through JavaScript
		form_html = [u"<input type='text' name='%s' id='id_%s' size='12' class='uses-date-picker' %s />" % (name, name, current_value) ]
		return mark_safe(u'\n'.join(form_html))

	class SharedMedia:

		css = {
			'screen': ('core/css/libs/jquery_ui/jquery.ui.css',)
		}

		js = (
			'core/js/libs/jquery/jquery.ui.js',
			'core/js/widgets/date_picker.js'
		)

class SimpleTimeWidget(forms.TextInput):
	"""A simple time widget that ignores seconds."""

	_CLOCK_ICON = "core/images/clock_icon_tiny.png"
	_ICON_CLASSES = ["clock-icon"]

	def __init__(self, *args, **kwargs):

		default_args = {'size': 4, 'class': 'dt-time-input'}
		try:
			kwargs['attrs'].update(default_args)
		except KeyError:
			kwargs['attrs'] = default_args
		super(SimpleTimeWidget, self).__init__(*args, **kwargs)

		self.time_format = "%H:%M"

	def render(self, name, value, attrs=None):
		"""Show the field's time value in HH:MM format."""

		final_attrs = self.build_attrs(attrs, type=self.input_type, name=name)

		#  Attempt to transform an existing time value into a value capable of
		#  being an attribute of the input tag that this widget is rendered as.
		try:
			value = value.strftime(self.time_format)
		except AttributeError:
			try:
				value = datetime.datetime.strptime(value, "%H:%M").time().strftime(self.time_format)
			except (ValueError, AttributeError, TypeError):
				value = ''
		final_attrs['value'] = force_unicode(value)
		return mark_safe(
			u'<input%(input)s /> <img src="%(icon_src)s" class="%(icon_class)s" alt="%(icon_alt)s" />' % {
				'input': flatatt(final_attrs),
				'icon_src': make_shared_media_url(self._CLOCK_ICON),
				'icon_class': u" ".join(self._ICON_CLASSES),
				'icon_alt': _("clock icon")
			})

class DateTimeWidget(forms.MultiWidget):
	"""A widget that encapsulates a date picker and a time input."""

	def __init__(self, *args, **kwargs):

		kwargs['widgets'] = [ DatePickerWidget(format='%m/%d/%Y'), SimpleTimeWidget() ]
		super(DateTimeWidget, self).__init__(*args, **kwargs)

	def decompress(self, value):
		"""
		Break the datetime.datetime instance in `value` into its date and time
		components.
		"""
		try:
			return [value.date(), value.time()]
		except AttributeError:
			return [None, None]

#-------------------------------------------------------------------------------
#  Text Widgets
#-------------------------------------------------------------------------------

class RichTextEditorWidget(forms.Textarea, SharedMediaMixin):
	"""A textarea that uses CKEditor to allow the user to input rich text."""

	def __init__(self, editor_type="default", auto_focus=False, attrs=None):

		super(RichTextEditorWidget, self).__init__(attrs)
		self.auto_focus  = auto_focus
		self.editor_type = editor_type

	def render(self, name, value, attrs=None):

		final_attrs = self.build_attrs(attrs)
		final_attrs['name'] = name

		#  Set custom classes for the textarea for use by the JavaScript code
		classes = getattr(final_attrs, 'class', [])
		classes.append("uses-rte")
		classes.append("editor-%s" % self.editor_type)
		if self.auto_focus:
			classes.append('auto-focus')
		final_attrs['class'] = " ".join(classes)

		html = [u'<textarea%s>%s</textarea>' % (flatatt(final_attrs), escape(smart_unicode(value or '')))]
		return mark_safe(u'\n'.join(html))

	class SharedMedia:

		js = (
			'core/js/libs/ckeditor/ckeditor.js',
			get_app_setting('RTE_CONFIG_FILE'),
			'core/js/widgets/rich_text.js'
		)

class WordCounterWidget(forms.Textarea, SharedMediaMixin):
	"""
	A textarea that counts the number of words entered and updates this count as
	the user types.
	"""

	_CSS_CLASSES = {
		'widget':    "uses-word-counter",
		'max_words': "max-words_",
		'min_words': "min-words_"
	}

	def __init__(self, min_words=None, max_words=None, attrs=None):
		"""
		Add special classes to the textarea that are used by JavaScript to
		generate the markup for the word counter.
		"""

		#  Add classes for the max and min words, if they are specified
		classes = []
		if min_words or max_words:
			classes.append(self._CSS_CLASSES['widget'])
		if min_words:
			classes.append("%s%s" % (self._CSS_CLASSES['min_words'], min_words))
		if max_words:
			classes.append("%s%s" % (self._CSS_CLASSES['max_words'], max_words))

		super(WordCounterWidget, self).__init__(
			attrs=_update_attr_classes(attrs, classes)
		)

	class SharedMedia:

		js = (
			'core/js/widgets/word_counter.js',
		)


from deseq import DESeq
from plots import Plots
import json
import numpy as np
import pandas as pd
from utils import replace_colnames, txt_2_dataframe
from pydeseq2.dds import DeseqDataSet
from pydeseq2.ds import DeseqStats
from sanbomics.tools import id_map
import scanpy as sc
import gseapy as gp
from gseapy.plot import gseaplot
import seaborn as sns
from gseapy import dotplot
import matplotlib.pyplot as plt
from datetime import datetime
from loguru import logger
date = datetime.now().strftime("%Y_%m_%d-%I_%M_%S_%p")

def run_pipeline(CONFIG_PATH: str):
    """_summary_

    Args:
        CONFIG_PATH (str): _description_
    """

    logger.info("Loading configuration parameters.")

    # Open config file
    config = json.load(open(CONFIG_PATH, "r"))

    # DEG Parameters
    control = config["differential_expression"]["Condition"]["control"]
    experiment = config["differential_expression"]["Condition"]["experiment"]
    species = config["differential_expression"]["species"]
    padj = config["differential_expression"]["thresholds_for_significant_genes"]["padj"]
    log2FoldChange = config["differential_expression"]["thresholds_for_significant_genes"]["log2FoldChange"]
    baseMean = config["differential_expression"]["thresholds_for_significant_genes"]["baseMean"]
    count_matrix_path = config["differential_expression"]["count_matrix"]["path"]
    count_matrix_gene_column = config["differential_expression"]["count_matrix"]["gene_id_column_name"]

    # Gene Set enrichment analysis parameters
    perform_GSEA = config["gene_set_enrichment_analysis"]["perform_GSEA"]
    gene_sets = config["gene_set_enrichment_analysis"]["gene_sets"]
    permutations_num = config["gene_set_enrichment_analysis"]["permutations"]
    filter_by_organ = config["gene_set_enrichment_analysis"]["filter_by_organ"]

    # Plotting parameters
    plot_volcano = config["plotting"]["plot_volcano"]["plot"]
    plot_pca = config["plotting"]["plot_pca"]["plot"]
    plot_heatmap = config["plotting"]["plot_heatmap"]["plot"]
    plot_dotplot = config["plotting"]["plot_dotplot"]["plot"]
    plot_gsea = config["plotting"]["plot_gsea"]["plot"]

    ##########################################################
    # Read input data from bash processing
    count_matrix = pd.read_csv(r"{}".format(count_matrix_path), sep="\t")
    # diff_exp_matrix = txt_2_dataframe(diff_exp_matrix_path)

    # Convert ENSG to gene names
    ensg_to_gene_mapper = id_map(species=species)

    # Instatiate DESeq class
    deseq = DESeq(gene_sets, permutations_num,
                  config, ensg_to_gene_mapper)

    # Pre-process count matrix
    count_matrix_processed, metadata = deseq.pre_processing(
        count_matrix=count_matrix, gene_id_col_name=count_matrix_gene_column)

    # Run DEG method
    dds, stat_res, dds_stats_df, significant_genes, ranked_by_stat = deseq.DEG(
        count_matrix_processed=count_matrix_processed, metadata=metadata, experiment=experiment, control=control, padj=padj, log2FoldChange=log2FoldChange, baseMean=baseMean)

    # Run GSEA method if true
    if perform_GSEA:

        go_ontologies, go_ontologies_df = deseq.GSEA(
            ranked_by_stat, filter_by_organ)

        vis = Plots(plot_volcano, plot_pca,
                    plot_heatmap, plot_gsea, plot_dotplot, config)
        vis.run_plots(dds, dds_stats_df,
                      significant_genes, ensg_to_gene_mapper, go_ontologies)
    else:
        vis = Plots(plot_volcano, plot_pca,
                    plot_heatmap, plot_gsea=False, plot_dotplot=False, config=config)
        vis.run_plots(dds, dds_stats_df,
                      significant_genes, ensg_to_gene_mapper)

    logger.info("DESeq pipeline complete.")

if __name__ == "__main__":
    run_pipeline(CONFIG_PATH=r".\src\scripts\config.json")


"""Test XDISPLOC, rfc-1096_."""
# std imports
import asyncio

# local imports
import telnetlib3
import telnetlib3.stream_writer
from telnetlib3.tests.accessories import unused_tcp_port, bind_host

# 3rd party
import pytest

async def test_telnet_server_on_xdisploc(bind_host, unused_tcp_port):
    """Test Server's callback method on_xdisploc()."""
    # given
    from telnetlib3.telopt import IAC, WILL, SB, SE, IS, XDISPLOC

    _waiter = asyncio.Future()
    given_xdisploc = "alpha:0"

    class ServerTestXdisploc(telnetlib3.TelnetServer):
        def on_xdisploc(self, xdisploc):
            super().on_xdisploc(xdisploc)
            _waiter.set_result(self)

    await telnetlib3.create_server(
        protocol_factory=ServerTestXdisploc, host=bind_host, port=unused_tcp_port
    )

    reader, writer = await asyncio.open_connection(host=bind_host, port=unused_tcp_port)

    # exercise,
    writer.write(IAC + WILL + XDISPLOC)
    writer.write(IAC + SB + XDISPLOC + IS + given_xdisploc.encode("ascii") + IAC + SE)

    # verify,
    srv_instance = await asyncio.wait_for(_waiter, 0.5)
    assert srv_instance.get_extra_info("xdisploc") == "alpha:0"

async def test_telnet_client_send_xdisploc(bind_host, unused_tcp_port):
    """Test Client's callback method send_xdisploc()."""
    # given
    _waiter = asyncio.Future()
    given_xdisploc = "alpha"

    class ServerTestXdisploc(telnetlib3.TelnetServer):
        def on_xdisploc(self, xdisploc):
            super().on_xdisploc(xdisploc)
            _waiter.set_result(xdisploc)

        def begin_advanced_negotiation(self):
            from telnetlib3.telopt import DO, XDISPLOC

            super().begin_advanced_negotiation()
            self.writer.iac(DO, XDISPLOC)

    await telnetlib3.create_server(
        protocol_factory=ServerTestXdisploc, host=bind_host, port=unused_tcp_port
    )

    reader, writer = await telnetlib3.open_connection(
        host=bind_host,
        port=unused_tcp_port,
        xdisploc=given_xdisploc,
        connect_minwait=0.05,
    )

    recv_xdisploc = await asyncio.wait_for(_waiter, 0.5)
    assert recv_xdisploc == given_xdisploc


# -*- coding: UTF-8 -*-
import paddle.fluid as fluid
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt

model_save_dir = '/Users/jiayinzhu/Downloads/catdog.inference.model'
picDir = '/Users/jiayinzhu/Documents/Python_codes_of_mine/test_pics/1.jpg'

def load_image(file):
    # 打开图片
    im = Image.open(file)
    # 将图片调整为跟训练数据一样的大小32*32，ANTIALIAS即抗锯齿.
    im = im.resize((32, 32), Image.ANTIALIAS)
    # 建立图片矩阵 类型为float32
    im = np.array(im).astype(np.float32)
    # 矩阵转置
    im = im.transpose((2, 0, 1))
    # 将像素值从【0-255】转换为【0-1】
    im = im / 255.0
    # print(im)
    im = np.expand_dims(im, axis=0)
    # 保持和之前输入image维度一致
    return im

# prepare to predict
place = fluid.CPUPlace()
infer_exe = fluid.Executor(place)
inference_scope = fluid.core.Scope()
with fluid.scope_guard(inference_scope):
    img = Image.open(picDir)
    plt.imshow(img)  # 根据数组绘制图像
    plt.show()  # 显示图像
    img = load_image(picDir)
    # 从指定目录中加载推理model:inference model
    [inference_program,  # 预测用的program
     feed_target_names,  # 是一个str列表，它包含需要在推理 Program 中提供数据的变量的名称。
     # fetch_targets：是一个 Variable 列表，从中我们可以得到推断结果。
     fetch_targets] = fluid.io.load_inference_model(model_save_dir,
                                                    infer_exe)  # infer_exe: 运行 inference model的 executor

    results = infer_exe.run(inference_program,  # 运行预测程序
                            feed={feed_target_names[0]: img},  # 喂入要预测的img
                            fetch_list=fetch_targets)  # 得到推测结果
    print('预测结果：', results)
    # [np.array([[4.8919302e-27, 1.6038109e-28, 6.2789530e-24, 1.0000000e+00,1.6038109e-28, 5.5096767e-09, 2.9356311e-13, 1.6038109e-28,6.8492938e-21, 1.6038109e-28]],0)]
    # label_list = [
    #     "飞机", "汽车", "鸟", "猫猫", "鹿鹿", "狗狗", "青蛙", "马马",
    #     "船", "大卡车"
    # ]
    label_list = [
        "猫猫",  "狗狗"
    ]
    predict_stuff = label_list[np.argmax(results[0][0][3:6:2])]
print("这一定是一只可爱的 %s" % predict_stuff)


import pytest
from src.helpers.api_client import APIError
from src.test_cases.base_test import APIBaseTest
from src.models.api.case import Case
from src.models.api.milestone import Milestone
from src.models.api.run import Run
from src.models.api.result import Result
from src.models.api.section import Section

class Statuses:
    passed = 1
    blocked = 2
    untested = 3
    retest = 4
    failed = 5

@pytest.mark.api
class TestRunsAndResultsAPI(APIBaseTest):

    @classmethod
    def setup_class(cls):
        super().setup_class()

        section = Section(name="Some Section")
        created = cls.client.add_section(section, cls.project.id)
        cls.section = created
        cls.cases = []
        for title in ["Case 1", "Case 2", "Case 3"]:
            case = Case(title=title, type_id=7, priority_id=2, estimate="10m")
            case = cls.client.add_case(case, cls.section.id)
            cls.cases.append(case)

    def teardown_method(self):
        for run in self.client.get_runs(self.project.id):
            try:
                self.client.delete_run(run)
            except APIError:
                # closed runs can't be deleted
                pass

    @pytest.mark.testrail(id=4057)
    def test_c4057_add_run(self):
        run = Run(name="A Test Run", description="This is a test run", include_all=True)
        created = self.client.add_run(run, self.project.id)

        assert created.name == run.name
        assert created.description == run.description
        assert created.include_all
        assert not created.is_completed
        assert created.project_id == self.project.id
        assert created.passed_count == 0
        assert created.retest_count == 0
        assert created.blocked_count == 0
        assert created.failed_count == 0
        assert created.untested_count == 3

    @pytest.mark.testrail(id=4068)
    def test_c4068_get_run(self):
        original = Run(name="A Test Run", description="This is a test run", include_all=True)
        created = self.client.add_run(original, self.project.id)

        run = self.client.get_run(created.id)
        assert run.id == created.id
        assert run.name == created.name
        assert run.description == created.description
        assert run.include_all

    @pytest.mark.testrail(id=4069)
    def test_c4069_get_runs(self):
        runs = {}
        run_names = ["Test Run 1", "Test Run 2", "Test Run 3"]
        for name in run_names:
            run = Run(name=name, include_all=True)
            run = self.client.add_run(run, self.project.id)
            runs[name] = run

        created = self.client.get_runs(self.project.id)
        assert len(created) == len(runs)
        assert set(run.name for run in created) == set(run_names)
        created_dict = {run.name:run for run in created}
        for name in run_names:
            original = runs[name]
            created_run = created_dict[name]

            assert created_run.name == original.name
            assert created_run.include_all
            assert not created_run.is_completed
            assert created_run.project_id == self.project.id
            assert created_run.passed_count == 0
            assert created_run.retest_count == 0
            assert created_run.blocked_count == 0
            assert created_run.failed_count == 0
            assert created_run.untested_count == 3

    @pytest.mark.testrail(id=4070)
    def test_c4070_update_run(self):
        original = Run(name="A Test Run", description="This is a test run", include_all=True)
        created = self.client.add_run(original, self.project.id)

        milestone = Milestone(name="A New Milestone", description="This is a description of a milestone")
        milestone = self.client.add_milestone(milestone, self.project.id)

        created.name = "A New Name"
        created.description = "A different description"
        created.milestone_id = milestone.id
        created.include_all = False
        created.case_ids = [case.id for case in self.cases][:2]
        run = self.client.update_run(created)

        assert run.name == created.name
        assert run.description == created.description
        assert run.milestone_id == created.milestone_id
        assert not run.include_all
        assert run.untested_count == 2

    @pytest.mark.testrail(id=4112)
    def test_c4112_add_run_empty_run(self):
        original = Run(name="A Test Run", description="This is a test run", include_all=False)
        created = self.client.add_run(original, self.project.id)

        assert created.passed_count == 0
        assert created.retest_count == 0
        assert created.blocked_count == 0
        assert created.failed_count == 0
        assert created.untested_count == 0

    @pytest.mark.testrail(id=5776)
    def test_c5776_delete_run(self):
        original = Run(name="A Test Run", description="This is a test run", include_all=True)
        created = self.client.add_run(original, self.project.id)
        runs = self.client.get_runs(self.project.id)

        assert created.id in set(run.id for run in runs)

        self.client.delete_run(created)
        updated_runs = self.client.get_runs(self.project.id)

        assert created.id not in set(run.id for run in updated_runs)

    @pytest.mark.testrail(id=5833)
    def test_c5833_close_run(self):
        original = Run(name="A Test Run", description="This is a test run", include_all=True)
        created_run = self.client.add_run(original, self.project.id)

        updated_run = self.client.close_run(created_run)
        assert updated_run.is_completed
        assert updated_run.completed_on > 0

    @pytest.mark.testrail(id=4073)
    def test_c4073_add_result(self):
        case_id = self.cases[0].id
        original_run = Run(name="A Test Run", case_ids=[case_id], include_all=False)
        created_run = self.client.add_run(original_run, self.project.id)

        tests = self.client.get_tests(created_run.id)
        assert len(tests) == 1
        test_id = tests[0].id

        result = Result(status_id=Statuses.passed, elapsed="1m 45s")
        created = self.client.add_result(result, test_id)

        assert created.elapsed == result.elapsed
        assert created.status_id == Statuses.passed
        assert created.test_id == test_id

    @pytest.mark.testrail(id=4074)
    def test_c4074_add_result_for_case(self):
        case_id = self.cases[1].id
        original_run = Run(name="A Test Run", include_all=True)
        created_run = self.client.add_run(original_run, self.project.id)

        result = Result(status_id=Statuses.failed, elapsed="1m 45s")
        created_result = self.client.add_result_for_case(result, created_run.id, case_id)

        assert created_result.status_id == Statuses.failed
        assert created_result.elapsed == result.elapsed
        test_id = created_result.test_id

        test = self.client.get_test(test_id)
        assert test.case_id == case_id
        assert test.status_id == Statuses.failed

    @pytest.mark.testrail(id=4075)
    def test_c4075_add_results(self):
        original_run = Run(name="A Test Run", include_all=True)
        created_run = self.client.add_run(original_run, self.project.id)

        tests = self.client.get_tests(created_run.id)

        results = []
        statuses = [Statuses.passed, Statuses.failed, Statuses.blocked]
        expected_results = {}
        for status, test in zip(statuses, tests):
            results.append(Result(status_id=status, test_id=test.id))
            expected_results[test.id] = status
        created_results = self.client.add_results(results, created_run.id)

        assert len(created_results) == len(results)
        assert {result.test_id: result.status_id for result in created_results} == expected_results

    @pytest.mark.testrail(id=4076)
    def test_c4076_add_results_for_cases(self):
        original_run = Run(name="A Test Run", include_all=True)
        created_run = self.client.add_run(original_run, self.project.id)

        results = []
        statuses = [Statuses.passed, Statuses.failed, Statuses.blocked]
        expected_results = {}
        for status, case in zip(statuses, self.cases):
            results.append(Result(status_id=status, case_id=case.id))
            expected_results[case.id] = status

        created_results = self.client.add_results_for_cases(results, created_run.id)

        assert len(created_results) == len(results)

        tests = self.client.get_tests(created_run.id)

        assert {test.case_id: test.status_id for test in tests} == expected_results
        test_dict = {test.id: test.status_id for test in tests}
        assert {result.test_id: result.status_id for result in created_results} == test_dict

    @pytest.mark.testrail(id=4077)
    def test_c4077_get_results(self):
        case_id = self.cases[1].id
        original_run = Run(name="A Test Run", case_ids=[case_id], include_all=False)
        created_run = self.client.add_run(original_run, self.project.id)

        tests = self.client.get_tests(created_run.id)
        assert len(tests) == 1
        test_id = tests[0].id

        statuses = [Statuses.failed, Statuses.failed, Statuses.retest, Statuses.passed]
        original_results = []
        expected_results = {}
        for index, status in enumerate(statuses):
            comment = f"Result number {index}"
            result = Result(status_id=status, test_id=test_id, comment=comment)
            original_results.append(result)
            expected_results[comment] = result

        self.client.add_results(original_results, created_run.id)

        results = self.client.get_results(test_id)
        assert len(results) == len(expected_results)
        for result in results:
            expected = expected_results.pop(result.comment)
            assert result.status_id == expected.status_id
            assert result.test_id == expected.test_id
            assert result.comment == expected.comment

    @pytest.mark.testrail(id=4078)
    def test_c4078_get_results_for_case(self):
        case_id = self.cases[2].id
        original_run = Run(name="A Test Run", case_ids=[case_id], include_all=False)
        created_run = self.client.add_run(original_run, self.project.id)

        statuses = [Statuses.retest, Statuses.failed, Statuses.retest, Statuses.passed]
        original_results = []
        expected_results = {}
        for index, status in enumerate(statuses):
            comment = f"Result number {index}"
            result = Result(status_id=status, comment=comment, case_id=case_id)
            original_results.append(result)
            expected_results[comment] = result

        self.client.add_results_for_cases(original_results, created_run.id)

        results = self.client.get_results_for_case(created_run.id, case_id)
        assert len(results) == len(expected_results)
        for result in results:
            expected = expected_results.pop(result.comment)
            assert result.status_id == expected.status_id
            assert result.comment == expected.comment

    @pytest.mark.testrail(id=4079)
    def test_c4079_get_results_for_run(self):
        original_run = Run(name="A Test Run", include_all=True)
        created_run = self.client.add_run(original_run, self.project.id)

        statuses = [Statuses.retest, Statuses.failed, Statuses.retest, Statuses.passed]
        original_results = []
        expected_results = {}
        counter = 0
        for status in statuses:
            for case in self.cases:
                comment = f"Result number {counter}"
                counter += 1
                result = Result(status_id=status, comment=comment, case_id=case.id)
                original_results.append(result)
                expected_results[comment] = result

        self.client.add_results_for_cases(original_results, created_run.id)

        results = self.client.get_results_for_run(created_run.id)
        assert len(results) == len(expected_results)
        for result in results:
            expected = expected_results.pop(result.comment)
            assert result.status_id == expected.status_id
            assert result.comment == expected.comment

    @pytest.mark.testrail(id=9362)
    @pytest.mark.parametrize("params, result", [
        ({'limit': 5}, 5),
        ({'limit': 2}, 2)
    ])
    def test_c9362_get_runs_with_valid_limit(self, params, result):
        created_runs = []
        try:
            for item in range(10):
                run = Run(name=f"Test Run {item}")
                resp = self.client.add_run(run, self.project.id)
                created_runs.append(resp)

            runs = self.client.get_runs(self.project.id, **params)
            assert len(runs) == result
        finally:
            for item in created_runs:
                self.client.delete_run(item)

    @pytest.mark.testrail(id=9363)
    @pytest.mark.parametrize("params, result", [
        ({'limit': -1}, [400, "Field :limit is not a valid natural number."]),
        ({'limit': 0}, [400, "Field :limit is too small (minimum 1)."]),
        ({'limit': 251}, [400, "Field :limit is too large (maximum 250)."])
    ])
    def test_c9363_get_runs_limit_out_of_range(self, params, result):
        created_runs = []
        try:
            for item in range(10):
                run = Run(name=f"Test Run {item}")
                resp = self.client.add_run(run, self.project.id)
                created_runs.append(resp)

            with pytest.raises(APIError) as e_info:
                self.client.get_runs(self.project.id, **params)
            error = e_info.value
            assert error.status_code == result[0]
            assert error.error == result[1]
        finally:
            for item in created_runs:
                self.client.delete_run(item)

    @pytest.mark.testrail(id=9364)
    @pytest.mark.parametrize("params, result", [
        ({'limit': '"'}, [400, 'Invalid characters in GET: [limit] [\"]']),
        ({'limit': 'o'}, [400, "Field :limit is not a valid natural number."])
    ])
    def test_c9364_get_runs_limit_char(self, params, result):
        created_runs = []
        try:
            for item in range(3):
                run = Run(name=f"Test Run {item}")
                resp = self.client.add_run(run, self.project.id)
                created_runs.append(resp)

            with pytest.raises(APIError) as e_info:
                self.client.get_runs(self.project.id, **params)

            error = e_info.value
            assert error.status_code == result[0]
            assert error.error == result[1]
        finally:
            for item in created_runs:
                self.client.delete_run(item)

    @pytest.mark.testrail(id=9365)
    @pytest.mark.parametrize("params, result", [
        ({'offset': 2}, ['2', None]),
        ({'offset': 3}, ['1', None])
    ])
    def test_c9365_get_runs_with_offset(self, params, result):
        created_runs = []
        try:
            for item in range(5):
                run = Run(name=f"Test Run {item}")
                resp = self.client.add_run(run, self.project.id)
                created_runs.append(resp)

            runs_len = len(self.client.get_runs(self.project.id))
            runs_with_params = self.client.get_runs(self.project.id, **params)

            result[1] = runs_len - int(params['offset'])

            assert result[0] in runs_with_params[0].name
            assert len(runs_with_params) == result[1]
        finally:
            for item in created_runs:
                self.client.delete_run(item)

    @pytest.mark.testrail(id=9366)
    @pytest.mark.parametrize("params, result", [
        ({'offset': -1}, [400, "Field :offset is not a valid natural number."]),
        ({'offset': 'o'}, [400, "Field :offset is not a valid natural number."]),
        ({'offset': '\"'}, [400, 'Invalid characters in GET: [offset] [\"]'])
    ])
    def test_c9366_get_runs_invalid_offset(self, params, result):
        created_runs = []
        try:
            for item in range(5):
                run = Run(name=f"Test Run {item}")
                resp = self.client.add_run(run, self.project.id)
                created_runs.append(resp)

            with pytest.raises(APIError) as e_info:
                self.client.get_runs(self.project.id, **params)

            error = e_info.value
            assert error.status_code == result[0]
            assert error.error == result[1]
        finally:
            for item in created_runs:
                self.client.delete_run(item)


import boto3
from botocore.exceptions import ClientError

ec2 = boto3.client('ec2', region_name='us-east-1')

response = ec2.describe_vpcs()
vpc_id = response.get('Vpcs', [{}])[0].get('VpcId', '')

try:
    # CHANGE
    response = ec2.create_security_group(GroupName='MyDemoSG',
                                         Description='MyDemoSG Description',
                                         VpcId=vpc_id)
    security_group_id = response['GroupId']
    print('Security Group Created %s in vpc %s.' % (security_group_id, vpc_id))

    data = ec2.authorize_security_group_ingress(
        GroupId=security_group_id,
        IpPermissions=[
            {'IpProtocol': 'tcp',
             'FromPort': 80,
             'ToPort': 80,
             'IpRanges': [{'CidrIp': '0.0.0.0/0'}]},
            {'IpProtocol': 'tcp',
             'FromPort': 22,
             'ToPort': 22,
             'IpRanges': [{'CidrIp': '0.0.0.0/0'}]}
        ])
    print('Ingress Successfully Set %s' % data)
except ClientError as e:
    print(e)


import re

from openpyxl import Workbook

def clean(text):
    if isinstance(text, (int, float)):
        return text

    if not text:
        return ''

    text = repr(text).replace(r"\'", '')
    for c in ['\r\n', '\n\r', u'\n', u'\r', u'\t', u'\xa0', '\\']:
        text = text.replace(c, ' ')
    return re.sub(' +', ' ', text).strip()

class ConvertCsvScriptV2:
    sheet_row_index = 1

    source_csv = 'source data.csv'
    output_file_name = 'DashWare Logger File.xlsx'

    file_headers = [
        "Uptime", "Time", "Latitude", "Longitude", "Elevation", "Speed",
        "Satellites View", "Temperature", "Pressure", "Humidity", "Light Intensity",
    ]

    def __init__(self):
        super().__init__()
        self.wb = Workbook()
        self.sheet = self.wb.active
        self.parse_records()

        self.wb.save(self.output_file_name)

    def parse_records(self):
        initial_rows = True

        for row in open(self.source_csv, encoding='utf-8').readlines():
            try:
                if clean(row) and initial_rows:
                    if "Uptime [s]" in row:
                        initial_rows = False
                        self.create_xls_write_headers()
                        continue
                    self.write_one_row_xls(row.strip())

                if not clean(row) or not row[0].isdigit():
                    continue
                rs = row.split(';')
                if len(rs) < 17:
                    continue
                item = {}
                item["Uptime"] = rs[0]
                item["Time"] = rs[2].split('T')[-1].split('.')[0]
                item["Latitude"] = rs[4]
                item["Longitude"] = rs[5]
                item["Elevation"] = rs[7]
                item["Speed"] = rs[8]
                item["Satellites View"] = rs[9]
                item["Temperature"] = rs[13]
                item["Pressure"] = rs[14]
                item["Humidity"] = rs[15]
                item["Light Intensity"] = rs[16]
                self.write_xls(item)
            except Exception as exc:
                print(exc)

    def create_xls_write_headers(self):
        for index, value in enumerate(self.file_headers):
            self.sheet.cell(row=self.sheet_row_index, column=index + 1).value = value
        self.sheet_row_index += 1

    def write_xls(self, item):
        for idx, key in enumerate(self.file_headers):
            self.sheet.cell(row=self.sheet_row_index, column=idx + 1).value = item[key]

        self.sheet_row_index += 1
        print(item)

    def write_one_row_xls(self, text):
        self.sheet.cell(row=self.sheet_row_index, column=1).value = text
        self.sheet_row_index += 1
        print(text)

if __name__ == "__main__":
    ConvertCsvScriptV2()