from __future__ import absolute_import
from bioat.logger import set_logging_level
from bioat.lib.libcrispr import TARGET_SEQ_LIB, run_target_seq_align
from bioat.lib.libcolor import map_color, make_color_list
from bioat.lib.libalignment import instantiate_pairwise_aligner
from Bio.Seq import Seq
from tabulate import tabulate
from matplotlib.collections import PatchCollection
from matplotlib.patches import Rectangle
from matplotlib import pyplot as plt
import matplotlib
import numpy as np
import pandas as pd

import logging
import sys


class TargetedDeepSequencing:
    def __init__(self):
        pass

    def region_heatmap(
            self,
            input_table: str,
            output_fig: str,
            target_seq: str = None,  # sgRNA
            reference_seq: str = None,  # target locus
            input_table_header: bool = True,
            output_fig_fmt: str = 'pdf',
            output_fig_dpi: int = 100,
            show_indel: bool = True,
            show_index: bool = True,
            box_border: bool = False,
            box_space: float = 0.03,
            min_color: tuple = (250, 239, 230),
            max_color: tuple = (154, 104, 57),
            min_ratio: float = 0.001,
            max_ratio: float = 0.99,
            region_extend_length: int = 25,
            local_alignment_scoring_matrix: tuple = (5, -4, -24, -8),
            local_alignment_min_score: int = 15,
            PAM_priority_weight: float = 1.0,
            get_built_in_target_seq: bool = False,
            log_level: str = 'INFO'
    ):
        """Plot region mutation info using table generated by `bioat bam mpileup_to_table`.

        :param input_table: Path of table generated by `bioat bam mpileup_to_table`.
                This table should contain base mutation information from a short genome region (no more than 1k nt).
        :param output_fig: Path of output figure.
        :param target_seq: The program will attempt to align the sequence information specified by <target_seq> to
        the reference sequence of mpileup.table. The <target_seq> sequence can be sgRNA (if you need to specify the
        PAM sequence you can use this format, ^NGG^.). When PAM is specified, local alignment takes into account PAM
        information. If PAM is not specified, normal local alignment is performed. Formal format of <target_seq> should
        be like:

        'GAGTCCGAGCAGAAGAAGAA^GGG^', ^GGG^ refers to PAM of SpCas9-BE on `EMX1` site;\n
        '^TTTA^GCCCCAATAATCCCCACATGTCA', ^TTTA^ refers to PAM of cpf1-BE on `CDKN2A` site;\n
        'TGCTAGTAACCACGTTCTCCTGATCAAATATCACTCTCCTACTTACAGGA' refers to no PAM on `ND4` site.
        :param input_table_header: whether input_table has header or not
        :param output_fig_fmt: pdf | png
        :param output_fig_dpi: output figure dpi
        :param show_indel: whether show indel info in the out figure or not
        :param show_index: whether show index info in the out figure or not
        :param box_border: whether show box border info in the out figure or not
        :param box_space: space size between two boxes
        :param min_color: min color to plot heatmap with RGB format
        :param max_color: max color to plot heatmap with RGB format
        :param min_ratio: mutation ratio of box lower than min_ratio will show as white
        :param max_ratio: mutation ratio of box higher than min_ratio will show as ??
        :param region_extend_length: expand how many bps for the two side of this region
        :param local_alignment_scoring_matrix: set <align_match_score>  <align_mismatch_score>
                                                <align_gap_open_score> <align_gap_extension_score>
        :param local_alignment_min_score: If alignment score lower than this, consider as no appropriate alignment
        :param PAM_priority_weight: PAM alignment score will multiply by this weight.

        :param get_built_in_target_seq: specify to True to return built-in target_seq sequence.
        :param log_level: 'CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG', 'NOTSET'

        --- demo ---\n
        samtools mpileup test_sorted.bam --reference HK4-AOut-1.ref.upper.fa | gzip > test_sorted.mpileup.gz\n
        bioat bam mpileup_to_table test_sorted.mpileup.gz > test_sorted.mpileup.info.tsv\n
        bioat targeted_deep_sequencing region_heatmap \
            test_sorted.mpileup.info.tsv \
            > test_sorted.mpileup.info.pdf\n
        """
        set_logging_level(level=log_level)
        # set logger
        lib_name = __name__
        function_name = sys._getframe().f_code.co_name
        logger = logging.getLogger(f'{lib_name}.{function_name} ==> ')

        if get_built_in_target_seq:
            logger.info(
                f'You can use <key> in built-in <target_seq> to represent your target_seq:\n' +
                '\t<key>\t<target_seq>\n' +
                ''.join([f'\t{k}\t{v}\n' for k, v in TARGET_SEQ_LIB.items()])
            )
            logger.info('exit because of the defination for get_built_in_target_seq')
            exit(0)
        else:
            # set sgRNA info
            logger.debug(f'load target_seq = {target_seq}')

            if target_seq in TARGET_SEQ_LIB:
                logger.debug(f'target_seq information is abtained from the <key>={target_seq}')
                target_seq = TARGET_SEQ_LIB[target_seq]
                logger.debug(f'target_seq is refered to {target_seq}')
            elif target_seq is None:
                pass
            elif isinstance(target_seq, str):
                logger.debug(f'target_seq is refered to {target_seq}')
            else:
                logger.error("<target_seq> should be set as:"
                             "DNA sequence / <key> in <get_built_in_target_seq> / None,"
                             f"but your <target_seq> is {target_seq}")
                exit(1)

        # set alignment
        aligner = instantiate_pairwise_aligner(*local_alignment_scoring_matrix)

        # load mpileup.info.tsv
        if input_table_header:
            df_bases = pd.read_csv(input_table, sep="\t")
        else:
            df_bases = pd.read_csv(input_table, sep="\t", header=None)

        logger.debug('df_bases.head(10):\n' + tabulate(df_bases.head(8), headers='keys', tablefmt='psql'))

        # ref_seq & ref_seq_rc
        if reference_seq:
            ref_seq = Seq(reference_seq)
            logger.debug(f'load ref_seq from parameter:\n{ref_seq}')
        else:
            ref_seq = Seq("".join(df_bases.ref_base))
            logger.debug(f'load ref_seq from df_bases.ref_base:\n{ref_seq}')

        ref_seq = Seq(ref_seq)
        logger.debug(f'Seq object for ref_seq:\n{ref_seq}')
        ref_seq_rc = ref_seq.reverse_complement()
        logger.debug(f'Seq object for ref_seq_rc (reverse_complement):\n{ref_seq_rc}')

        # target_seq
        if target_seq is None:
            target_seq = ref_seq
            PAM = None
        elif '^' in target_seq:
            if target_seq.find('^') == 0:
                _, PAM, target_seq = target_seq.strip().split('^')
                target_seq = Seq(target_seq.upper())
                PAM = {'PAM': PAM, 'position': 0, 'weight': PAM_priority_weight}
            else:
                target_seq, PAM, _ = target_seq.strip().split('^')
                target_seq = Seq(target_seq.upper())
                PAM = {'PAM': PAM, 'position': len(target_seq), 'weight': PAM_priority_weight}
        else:
            logger.debug(f'no PAM sequence is defined from target_seq = {target_seq}')
            target_seq = Seq(target_seq)
            PAM = None

        logger.info(f'parse target_seq: target_seq={target_seq}, PAM={PAM}')

        # fwd alignment & rev alignment
        # 为了判断target_seq (sgRNA) 是设计在fwd还是rev链上
        if not PAM:
            logger.info('use PAMless mode to align')
            fwd = run_target_seq_align(ref_seq, target_seq, aligner)
            logger.debug(f'final_align_forward:\n{fwd}')
            rev = run_target_seq_align(ref_seq_rc, target_seq, aligner)
            logger.debug(f'final_align_reverse:\n{rev}')
        else:
            logger.info('use PAM mode to align')
            # TODO
            fwd = run_target_seq_align(ref_seq, target_seq, aligner, PAM=PAM)
            logger.debug(f'final_align_forward:\n{fwd}')
            rev = run_target_seq_align(ref_seq_rc, target_seq, aligner, PAM=PAM)
            logger.debug(f'final_align_reverse:\n{rev}')

        # get fwd alignment info
        reference_seq = fwd['alignment']['reference_seq'][fwd['ref_aln_start']: fwd['ref_aln_end'] + 1]
        align_info = fwd['alignment']['aln_info'][fwd['ref_aln_start']: fwd['ref_aln_end'] + 1]
        target_seq = fwd['alignment']['target_seq'][fwd['ref_aln_start']: fwd['ref_aln_end'] + 1]
        aln_score = fwd['aln_score']
        logger.info(
            f"Forward best alignment:\n"
            f"reference  : {reference_seq}\n"
            f"align_info : {align_info}\n"
            f"target_seq : {target_seq}\n"
            f"align_score: {aln_score}"
        )
        # get rev alignment info
        print(rev)
        reference_seq_rev = rev['alignment']['reference_seq'][rev['ref_aln_start']: rev['ref_aln_end'] + 1]
        align_info_rev = rev['alignment']['aln_info'][rev['ref_aln_start']: rev['ref_aln_end'] + 1]
        target_seq_rev = rev['alignment']['target_seq'][rev['ref_aln_start']: rev['ref_aln_end'] + 1]
        aln_score_rev = rev['aln_score']
        logger.info(
            f"Reverse best alignment:\n"
            f"reference  : {reference_seq_rev}\n"
            f"align_info : {align_info_rev}\n"
            f"target_seq : {target_seq_rev}\n"
            f"align_score: {aln_score_rev}"
        )

        # define align direction and final align res
        # make alignment info
        if any([x >= local_alignment_min_score for x in (fwd['aln_score'], rev['aln_score'])]):
            if fwd['aln_score'] >= rev['aln_score']:
                aln_direction = "Forward Alignment"
                aln = fwd
            else:
                aln_direction = "Reverse Alignment"
                aln = rev
                reference_seq = reference_seq_rev
                align_info = align_info_rev
                target_seq = target_seq_rev
                aln_score = aln_score_rev
        else:
            logger.critical("Alignment Error!")
            exit(1)

        # mark aligned all bases in target_seq
        ref_seq_length = len(ref_seq)
        target_seq_aln = [""] * ref_seq_length
        # mark inserted bases in target_seq
        target_seq_aln_insert = [""] * ref_seq_length
        DNA_rev_cmp_dict = {
            "A": "T",
            "T": "A",
            "C": "G",
            "G": "C",
            "N": "N",
            "-": "-"
        }

        reference_gap_count = reference_seq.count("-")
        target_seq_gap_count = target_seq.count("-")

        ref_gap_count = 0
        ref_del_str = ""

        aln_start = aln['ref_aln_start']
        aln_end = aln['ref_aln_end']

        # update target_seq_aln
        logger.debug(f'target_seq_aln (before update) = {target_seq_aln}')
        logger.debug(f'target_seq_aln_insert (before update) = {target_seq_aln_insert}')

        for idx, ref_base in enumerate(reference_seq):

            # reference  : GCCTCTGGAGAGGGAGGAGGG
            # align_info : |.|.|||..|..|||||.||-
            # target_seq : GGCACTGCGGCTGGAGGTGG-
            if ref_base != "-":
                ref_gap_count += 0
                ref_del_str = ""
                target_seq_aln[aln_start + idx - ref_gap_count] = ref_del_str + target_seq[idx]
            else:
                ref_gap_count += 1
                ref_del_str += target_seq[idx]  # for continuous gap
                target_seq_aln_insert[aln_start + idx] = target_seq[idx]
        logger.debug(
            "update target_seq_aln: show ''.join(target_seq_aln)\n"
            f"target_seq_aln = {''.join(target_seq_aln)}\n"
            f"target_seq     = {target_seq}"
        )
        logger.debug("update target_seq_aln: Done")
        logger.debug(f'target_seq_aln (after update) = {target_seq_aln}')
        logger.debug(f'target_seq_aln_insert (after update) = {target_seq_aln_insert}')

        # if reverse alignment
        if aln_direction == "Reverse Alignment":
            # illustrate reverse alignment as forward alignment
            target_seq_aln = target_seq_aln[::-1]
            target_seq_aln_insert = target_seq_aln_insert[::-1]
            logger.debug("use reverse alignment")
            logger.debug(f'target_seq_aln = {target_seq_aln}')
            logger.debug(f'target_seq_aln_insert = {target_seq_aln_insert}')

        # add PAM info# TODO

        # print(target_seq_aln)
        # print(target_seq_aln_insert)
        # target_seq_aln
        # ['-','-','G','G','C','A','G','C','G','G','C','T','G','G','A','A','A','A','A','A','A','A','A','A','A','A',
        # 'A','A','A','G','T','','']
        # target_seq_aln_insert
        # ['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','A','G','','']

        # set plot_region
        # print(aln_start, aln_end, region_extend_length, ref_seq_length)
        # 56 76 25 266
        # print(aln_start - region_extend_length)
        # 31
        # print(aln_end + region_extend_length)
        # 101
        possible_target_region_start = max(aln_start - region_extend_length, 0)
        possible_target_region_end = min(aln_end + region_extend_length, ref_seq_length)  # 266: 0~255 266 - 1
        plot_region = (possible_target_region_start, possible_target_region_end)
        df_bases_select = df_bases.iloc[plot_region[0]: plot_region[1], :]
        logger.debug('df_bases_select:\n' + tabulate(df_bases_select, headers='keys', tablefmt='psql'))

        # make plot
        # set color
        # show indel
        matplotlib.use('Agg')
        np.set_printoptions(suppress=True)

        indel_plot_state = show_indel
        index_plot_state = show_index
        box_border_plot_state = box_border

        # set panel size
        panel_box_width = 0.4
        panel_box_heigth = 0.4
        panel_space = 0.05
        panel_box_space = box_space

        # color part
        base_colors = {"A": "#04E3E3", "T": "#F9B874", "C": "#B9E76B", "G": "#F53798", "N": "#AAAAAA", "-": "#AAAAAA"}

        # make color breaks
        color_break_num = 20
        break_step = 1.0 / color_break_num
        # mutation ratio of box lower than min_ratio will show as white
        min_color_value = min_ratio
        # mutation ratio of box lower than max_ratio will show as white
        max_color_value = max_ratio
        color_break = np.round(np.arange(min_color_value, max_color_value, break_step), 5)
        # min_color: min color to plot heatmap with RGB format
        # max_color: max color to plot heatmap with RGB format
        color_list = make_color_list(min_color, max_color, len(color_break) - 1, "HEX")
        color_list = ["#FFFFFF"] + color_list
        logger.debug(f'color_list = {color_list}')

        # get plot info
        total_box_count = plot_region[1] - plot_region[0]

        # calculate base info and fix zero
        base_sum_count = df_bases_select.loc[:, ["A", "G", "C", "T"]].sum(axis=1).astype(int)
        logger.debug(f'base_sum_count =\n{base_sum_count.values}')
        total_sum_count = df_bases_select[["A", "G", "C", "T", "del_count", "insert_count"]].sum(axis=1).astype(int)
        logger.debug(f'total_sum_count =\n{total_sum_count.values}')
        # fix 0 -> ["A.ratio", list(df_bases_select["A"] / base_sum_count)]
        base_sum_count[base_sum_count == 0] = 1
        total_sum_count[total_sum_count == 0] = 1

        # make plot size
        logger.debug(f'aln =\n{aln}')

        if indel_plot_state:
            panel_height_coef = [.5, .9, .9] + [.5] * 6 + [.5] * 6
            panel_space_coef = [1.] * 3 + [.3] * 3 + [1., .3, 1.] + [.3] * 3 + [1., .3]
            plot_data_list = [
                ["Ref_index", df_bases_select.chr_index],
                ["Target_seq", target_seq_aln[plot_region[0]: plot_region[1]]],
                ["Ref_seq", df_bases_select.ref_base],
                ["A", np.array(df_bases_select["A"])],
                ["G", np.array(df_bases_select["G"])],
                ["C", np.array(df_bases_select["C"])],
                ["T", np.array(df_bases_select["T"])],
                ["Del", np.array(df_bases_select["del_count"])],
                ["Ins", np.array(df_bases_select["insert_count"])],
                ["A %", list(df_bases_select["A"] / base_sum_count)],
                ["G %", list(df_bases_select["G"] / base_sum_count)],
                ["C %", list(df_bases_select["C"] / base_sum_count)],
                ["T %", list(df_bases_select["T"] / base_sum_count)],
                ["Del %", list(df_bases_select["del_count"] / total_sum_count)],
                ["Ins %", list(df_bases_select["insert_count"] / total_sum_count)]
            ]
        else:
            panel_height_coef = [.5, .9, .9] + [.5] * 4 + [.5] * 4
            panel_space_coef = [1.] * 3 + [0.3] * 3 + [1.] + [0.3] * 3
            plot_data_list = [
                ["Ref_index", df_bases_select.chr_index],
                ["Target_seq", target_seq_aln[plot_region[0]: plot_region[1]]],
                ["Ref_seq", df_bases_select.ref_base],
                ["A", np.array(df_bases_select["A"])],
                ["G", np.array(df_bases_select["G"])],
                ["C", np.array(df_bases_select["C"])],
                ["T", np.array(df_bases_select["T"])],
                ["A %", list(df_bases_select["A"] / base_sum_count)],
                ["G %", list(df_bases_select["G"] / base_sum_count)],
                ["C %", list(df_bases_select["C"] / base_sum_count)],
                ["T %", list(df_bases_select["T"] / base_sum_count)]
            ]

        # get box and space info
        box_height_list = np.array(panel_height_coef) * panel_box_heigth
        panel_space_list = np.array(panel_space_coef) * panel_space
        logger.debug(f'box_height_list = {box_height_list}')
        logger.debug(f'panel_space_list = {panel_space_list}')

        # calculate figure total width and height
        figure_width = total_box_count * panel_box_width + \
                       (total_box_count - 1) * panel_box_space + panel_box_width * 2
        figure_height = sum(box_height_list) + sum(panel_space_list)

        logger.debug(f'figure_width = {figure_width}')
        logger.debug(f'figure_height = {figure_height}')

        # make all box_x
        box_x_vec = np.arange(0, figure_width + panel_box_width, panel_box_width + panel_box_space)
        box_x_vec = box_x_vec[:(len(ref_seq) + 1)]
        # logger.debug(f'box_x_vec (x for each column) =\n{box_x_vec}')

        # make box border
        if box_border_plot_state:
            box_edgecolor = "#AAAAAA"
            box_linestyle = "-"
            box_linewidth = 2
            logger.debug(f'box_border_plot_state = {box_border_plot_state}')
            logger.debug(f'box_edgecolor = {box_edgecolor}')
            logger.debug(f'box_linestyle = {box_linestyle}')
            logger.debug(f'box_linewidth = {box_linewidth}')
        else:
            box_edgecolor = "#FFFFFF"
            box_linestyle = "None"
            box_linewidth = 0
            logger.debug(f'box_border_plot_state = {box_border_plot_state}')
            logger.debug(f'box_edgecolor = {box_edgecolor}')
            logger.debug(f'box_linestyle = {box_linestyle}')
            logger.debug(f'box_linewidth = {box_linewidth}')

        # make box_y initialize
        current_y = 0

        logger.debug('start to plot')
        fig = plt.figure(figsize=(figure_width * 1.1, figure_height * 1.1))
        logger.debug(f'set new figure, figsize = ({figure_width * 1.1}, {figure_height * 1.1})')

        plt.set_loglevel("info")
        # will show matplotlib debug log with logging
        ax = fig.add_subplot(111, aspect="equal")
        # will show matplotlib debug log with logging
        plt.xlim([0, figure_width])
        plt.ylim([-figure_height, 0])
        plt.axis("off")
        logger.debug(f'plt.xlim([0, {figure_width}])')
        logger.debug(f'plt.ylim([-{figure_height}, 0])')
        logger.debug('plt.axis("off")')

        # make plot
        text_list = []
        patches = []

        for panel_index in range(len(panel_height_coef)):

            # panel name
            panel_name = plot_data_list[panel_index][0]
            panel_name_x = box_x_vec[0]
            panel_name_y = current_y - box_height_list[panel_index] * 0.5
            text_list.append((panel_name_x, panel_name_y, panel_name, 10))

            # plot panel box
            if panel_name == "Ref_index":
                # don't draw box, only add text
                for index, box_value in enumerate(
                        plot_data_list[panel_index][1]):
                    box_x = box_x_vec[index + 1]
                    text_list.append(
                        (
                            box_x + panel_box_width * 0.5, current_y - box_height_list[panel_index] * 0.5,
                            str(box_value),
                            7
                        )
                    )

                # make next panel_y
                current_y = current_y - (box_height_list[panel_index] + panel_space_list[panel_index])

            elif panel_name in ["Target_seq", "Ref_seq"]:
                for index, box_value in enumerate(plot_data_list[panel_index][1]):
                    if box_value == "":
                        box_fill = False
                        box_color = "#FFFFFF"

                    else:
                        if "Reverse" in aln_direction:
                            if panel_name == "Ref_seq":
                                box_value = "".join([DNA_rev_cmp_dict.get(x) for x in box_value])
                            else:
                                pass

                            box_color = base_colors.get(box_value[0])

                        else:
                            box_color = base_colors.get(box_value[-1])

                        if not box_color:
                            box_fill = False
                            box_color = "#FFFFFF"
                        else:
                            box_fill = True

                    box_x = box_x_vec[index + 1]

                    patches.append(
                        Rectangle(
                            xy=(box_x, current_y - box_height_list[panel_index]),
                            width=panel_box_width,
                            height=box_height_list[panel_index],
                            fill=box_fill,
                            alpha=1,
                            linestyle=box_linestyle,
                            linewidth=box_linewidth,
                            edgecolor=box_edgecolor,
                            facecolor=box_color
                        )
                    )

                    # text
                    text_list.append(
                        (
                            box_x + 0.5 * panel_box_width, current_y - 0.5 * box_height_list[panel_index],
                            str(box_value),
                            16
                        )
                    )

                # make next panel_y
                current_y = current_y - (box_height_list[panel_index] + panel_space_list[panel_index])

            elif panel_name in ["A", "G", "C", "T", "Del", "Ins"]:
                if panel_name in ["Del", "Ins"]:
                    box_ratio = plot_data_list[panel_index][1] / total_sum_count
                else:
                    box_ratio = plot_data_list[panel_index][1] / base_sum_count

                box_color_list = map_color(box_ratio, color_break, color_list)
                logger.debug(f'get box_color_list for {panel_name}')

                for index, box_value in enumerate(plot_data_list[panel_index][1]):
                    box_color = box_color_list[index]
                    box_x = box_x_vec[index + 1]

                    patches.append(
                        Rectangle(
                            xy=(box_x, current_y - box_height_list[panel_index]),
                            width=panel_box_width,
                            height=box_height_list[panel_index],
                            fill=True,
                            alpha=1,
                            linestyle=box_linestyle,
                            linewidth=box_linewidth,
                            edgecolor=box_edgecolor,
                            facecolor=box_color
                        )
                    )

                    # text
                    text_list.append(
                        (
                            box_x + 0.5 * panel_box_width, current_y - 0.5 * box_height_list[panel_index],
                            str(box_value),
                            6
                        )
                    )

                # make next panel_y
                current_y = current_y - (box_height_list[panel_index] + panel_space_list[panel_index])

            else:
                box_color_list = map_color(plot_data_list[panel_index][1], color_break, color_list)
                for index, box_value in enumerate(plot_data_list[panel_index][1]):
                    box_color = box_color_list[index]
                    box_x = box_x_vec[index + 1]

                    patches.append(
                        Rectangle(
                            xy=(box_x, current_y - box_height_list[panel_index]),
                            width=panel_box_width,
                            height=box_height_list[panel_index],
                            fill=True,
                            alpha=1,
                            linestyle=box_linestyle,
                            linewidth=box_linewidth,
                            edgecolor=box_edgecolor,
                            facecolor=box_color
                        )
                    )

                    # text
                    text_list.append(
                        (
                            box_x + 0.5 * panel_box_width, current_y - 0.5 * box_height_list[panel_index],
                            round(box_value * 100, 4),
                            6
                        )
                    )

                # make next panel_y
                if panel_index < len(panel_space_list):
                    current_y = current_y - (box_height_list[panel_index] + panel_space_list[panel_index])

        # plot box
        logger.debug(f'plot rectangles')
        ax.add_collection(PatchCollection(patches, match_original=True))

        logger.debug(f'plot text on each rectangle')
        for text_x, text_y, text_info, text_fontsize in text_list:
            plt.text(
                x=text_x, y=text_y, s=text_info, horizontalalignment='center', verticalalignment='center',
                fontsize=text_fontsize, fontname="Arial"
            )
        # output plot
        fig.savefig(fname=output_fig, bbox_inches='tight', dpi=output_fig_dpi, format=output_fig_fmt)

    # def region_heatmap_merge(self):
    #     pass


if __name__ == '__main__':
    pass
