# -*- coding: utf-8 -*-
# __author__ = 'jinlinfang, shicaiping'

from mbio.api.database.lnc_rna.api_base import ApiBase
from biocluster.api.database.base import Base, report_check
from bson.objectid import ObjectId
import datetime
import json
import os
import re
import subprocess
import unittest

class Rmats(ApiBase):
    '''
    last_modify: 2019.05.10
    '''
    def __init__(self, bind_object):
        super(Rmats, self).__init__(bind_object)
        self._project_type = 'lnc_rna'
        self._RMATS_DETAIL_TABLE_HEAD = [
            "event_id", "type", "novel", "old", "gene", "gene_symbol", "chr", "strand",
            "exonStart_0base", "exonEnd", "upstreamES", "upstreamEE", "downstreamES",
            "downstreamEE",
            "longExonStart_0base", "longExonEnd", "shortES", "shortEE", "flankingES",
            "flankingEE",
            "riExonStart_0base", "riExonEnd", "1stExonStart_0base", "1stExonEnd",
            "2ndExonStart_0base",
            "2ndExonEnd",
            "diff_JunctionCountOnly", "IJC_SAMPLE_1", "SJC_SAMPLE_1", "IJC_SAMPLE_2",
            "SJC_SAMPLE_2",
            "IncFormLen_JunctionCountOnly", "SkipFormLen_JunctionCountOnly",
            "PValue_JunctionCountOnly",
            "FDR_JunctionCountOnly", "IncLevel1_JunctionCountOnly",
            "IncLevel2_JunctionCountOnly",
            "average_IncLevel1_JunctionCountOnly", "average_IncLevel2_JunctionCountOnly",
            "IncLevelDifference_JunctionCountOnly",
            "increase_inclusion_SAMPLE1_JunctionCountOnly",
            "increase_exclusion_SAMPLE1_JunctionCountOnly",
            "increase_inclusion_SAMPLE2_JunctionCountOnly",
            "increase_exclusion_SAMPLE2_JunctionCountOnly",
            "diff_ReadsOnTargetAndJunctionCounts", "IC_SAMPLE_1", "SC_SAMPLE_1",
            "IC_SAMPLE_2",
            "SC_SAMPLE_2",
            "IncFormLen_ReadsOnTargetAndJunctionCounts",
            "SkipFormLen_ReadsOnTargetAndJunctionCounts",
            "PValue_ReadsOnTargetAndJunctionCounts", "FDR_ReadsOnTargetAndJunctionCounts",
            "IncLevel1_ReadsOnTargetAndJunctionCounts",
            "IncLevel2_ReadsOnTargetAndJunctionCounts",
            "average_IncLevel1_ReadsOnTargetAndJunctionCounts",
            "average_IncLevel2_ReadsOnTargetAndJunctionCounts",
            "IncLevelDifference_ReadsOnTargetAndJunctionCounts",
            "increase_inclusion_SAMPLE1_ReadsOnTargetAndJunctionCounts",
            "increase_exclusion_SAMPLE1_ReadsOnTargetAndJunctionCounts",
            "increase_inclusion_SAMPLE2_ReadsOnTargetAndJunctionCounts",
            "increase_exclusion_SAMPLE2_ReadsOnTargetAndJunctionCounts",
            "diff_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts",
            "diff_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts"
        ]
        self._RMATS_DETAIL_MONGO_TABLE_FIELD_DIC = {
            'event_id': 'event_id', 'type': 'type', 'novel_as': 'novel',
            'gid': 'gene', 'gname': 'gene_symbol', 'chr': 'chr',
            'strand': 'strand', 'es': 'exonStart_0base', 'ee': 'exonEnd',
            'up_es': 'upstreamES', 'up_ee': 'upstreamEE',
            'down_es': 'downstreamES', 'down_ee': 'downstreamEE',
            'les': 'longExonStart_0base', 'lee': 'longExonEnd',
            'ses': 'shortES', 'see': 'shortEE', 'fes': 'flankingES',
            'fee': 'flankingEE', 'ries': 'riExonStart_0base',
            'riee': 'riExonEnd', 'firstes': '1stExonStart_0base',
            'firstee': '1stExonEnd', 'secondes': '2ndExonStart_0base',
            'secondee': '2ndExonEnd', 'diff_jc': 'diff_JunctionCountOnly',
            'ijc_s1': 'IJC_SAMPLE_1', 'sjc_s1': 'SJC_SAMPLE_1',
            'ijc_s2': 'IJC_SAMPLE_2', 'sjc_s2': 'SJC_SAMPLE_2',
            'inclen_jc': 'IncFormLen_JunctionCountOnly',
            'skiplen_jc': 'SkipFormLen_JunctionCountOnly',
            'pvalue_jc': 'PValue_JunctionCountOnly',
            'fdr_jc': 'FDR_JunctionCountOnly',
            'inc1_jc': 'IncLevel1_JunctionCountOnly',
            'inc2_jc': 'IncLevel2_JunctionCountOnly',
            'aver_inc1_jc': 'average_IncLevel1_JunctionCountOnly',
            'aver_inc2_jc': 'average_IncLevel2_JunctionCountOnly',
            'inc_diff_jc': 'IncLevelDifference_JunctionCountOnly',
            'upinc_s1_jc': 'increase_inclusion_SAMPLE1_JunctionCountOnly',
            'upexc_s1_jc': 'increase_exclusion_SAMPLE1_JunctionCountOnly',
            'upinc_s2_jc': 'increase_inclusion_SAMPLE2_JunctionCountOnly',
            'upexc_s2_jc': 'increase_exclusion_SAMPLE2_JunctionCountOnly',
            'diff_all': 'diff_ReadsOnTargetAndJunctionCounts',
            'ic_s1': 'IC_SAMPLE_1', 'sc_s1': 'SC_SAMPLE_1',
            'ic_s2': 'IC_SAMPLE_2', 'sc_s2': 'SC_SAMPLE_2',
            'inclen_all': 'IncFormLen_ReadsOnTargetAndJunctionCounts',
            'skiplen_all': 'SkipFormLen_ReadsOnTargetAndJunctionCounts',
            'pvalue_all': 'PValue_ReadsOnTargetAndJunctionCounts',
            'fdr_all': 'FDR_ReadsOnTargetAndJunctionCounts',
            'inc1_all': 'IncLevel1_ReadsOnTargetAndJunctionCounts',
            'inc2_all': 'IncLevel2_ReadsOnTargetAndJunctionCounts',
            'aver_inc1_all': 'average_IncLevel1_ReadsOnTargetAndJunctionCounts',
            'aver_inc2_all': 'average_IncLevel2_ReadsOnTargetAndJunctionCounts',
            'inc_diff_all': 'IncLevelDifference_ReadsOnTargetAndJunctionCounts',
            'upinc_s1_all': 'increase_inclusion_SAMPLE1_ReadsOnTargetAndJunctionCounts',
            'upexc_s1_all': 'increase_exclusion_SAMPLE1_ReadsOnTargetAndJunctionCounts',
            'upinc_s2_all': 'increase_inclusion_SAMPLE2_ReadsOnTargetAndJunctionCounts',
            'upexc_s2_all': 'increase_exclusion_SAMPLE2_ReadsOnTargetAndJunctionCounts',
            'diff_jc_and_all': 'diff_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts',
            'diff_jc_or_all': 'diff_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts'
        }
        self._RMATS_TYPE_MONGO_TABLE_FIELD_DIC = {
            'old_a5ss': 'A5SS_old_event_id_set_no',
            'novel_a5ss': 'A5SS_novel_event_id_no', 'total_a5ss': 'A5SS_all_event_id_no',
            'old_se': 'SE_old_event_id_set_no',
            'novel_se': 'SE_novel_event_id_no', 'total_se': 'SE_all_event_id_no',
            'old_mxe': 'MXE_old_event_id_set_no',
            'novel_mxe': 'MXE_novel_event_id_no', 'total_mxe': 'MXE_all_event_id_no',
            'old_ri': 'RI_old_event_id_set_no',
            'novel_ri': 'RI_novel_event_id_no', 'total_ri': 'RI_all_event_id_no',
            'old_a3ss': 'A3SS_old_event_id_set_no',
            'novel_a3ss': 'A3SS_novel_event_id_no', 'total_a3ss': 'A3SS_all_event_id_no',
            'total_total': 'total_as_events_no',
            'novel_total': 'total_as_novel_events_no'
        }
        self._RMATS_STATS_MONGO_TABLE_FIELD_DIC = {
            'all_a5ss': 'A5SS_ReadsOnTargetAndJunctionCounts_event_id_set_no',
            'jcandall_a5ss': 'A5SS_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts_set_no',
            'jc_a5ss': 'A5SS_JunctionCountOnly_event_id_set_no',
            'jcorall_a5ss': 'A5SS_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts_set_no',
            'all_se': 'SE_ReadsOnTargetAndJunctionCounts_event_id_set_no',
            'jcandall_se': 'SE_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts_set_no',
            'jc_se': 'SE_JunctionCountOnly_event_id_set_no',
            'jcorall_se': 'SE_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts_set_no',
            'all_mxe': 'MXE_ReadsOnTargetAndJunctionCounts_event_id_set_no',
            'jcandall_mxe': 'MXE_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts_set_no',
            'jc_mxe': 'MXE_JunctionCountOnly_event_id_set_no',
            'jcorall_mxe': 'MXE_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts_set_no',
            'all_ri': 'RI_ReadsOnTargetAndJunctionCounts_event_id_set_no',
            'jcandall_ri': 'RI_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts_set_no',
            'jc_ri': 'RI_JunctionCountOnly_event_id_set_no',
            'jcorall_ri': 'RI_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts_set_no',
            'all_a3ss': 'A3SS_ReadsOnTargetAndJunctionCounts_event_id_set_no',
            'jcandall_a3ss': 'A3SS_JunctionCountOnly_and_ReadsOnTargetAndJunctionCounts_set_no',
            'jc_a3ss': 'A3SS_JunctionCountOnly_event_id_set_no',
            'jcorall_a3ss': 'A3SS_JunctionCountOnly_or_ReadsOnTargetAndJunctionCounts_set_no',
            'jc_total': 'total_JunctionCountOnly_event_id_set_no',
            'all_total': 'total_ReadsOnTargetAndJunctionCounts_event_id_set_no',
        }
        self._RMATS_PSI_MONGO_TABLE_FIELD_DIC = {
            'a3ss_s1_all_exc': 'A3SS_SAMPLE_1_ReadsOnTargetAndJunctionCounts_exclusion',
            'a3ss_s1_all_inc': 'A3SS_SAMPLE_1_ReadsOnTargetAndJunctionCounts_inclusion',
            'a3ss_s1_all_total': 'A3SS_SAMPLE_1_ReadsOnTargetAndJunctionCounts_total',
            'a3ss_s1_jc_exc': 'A3SS_SAMPLE_1_JunctionCountOnly_exclusion',
            'a3ss_s1_jc_inc': 'A3SS_SAMPLE_1_JunctionCountOnly_inclusion',
            'a3ss_s1_jc_total': 'A3SS_SAMPLE_1_JunctionCountOnly_total',
            'a3ss_s2_all_exc': 'A3SS_SAMPLE_2_ReadsOnTargetAndJunctionCounts_exclusion',
            'a3ss_s2_all_inc': 'A3SS_SAMPLE_2_ReadsOnTargetAndJunctionCounts_inclusion',
            'a3ss_s2_all_total': 'A3SS_SAMPLE_2_ReadsOnTargetAndJunctionCounts_total',
            'a3ss_s2_jc_exc': 'A3SS_SAMPLE_2_JunctionCountOnly_exclusion',
            'a3ss_s2_jc_inc': 'A3SS_SAMPLE_2_JunctionCountOnly_inclusion',
            'a3ss_s2_jc_total': 'A3SS_SAMPLE_2_JunctionCountOnly_total',
            'a5ss_s1_all_exc': 'A5SS_SAMPLE_1_ReadsOnTargetAndJunctionCounts_exclusion',
            'a5ss_s1_all_inc': 'A5SS_SAMPLE_1_ReadsOnTargetAndJunctionCounts_inclusion',
            'a5ss_s1_all_total': 'A5SS_SAMPLE_1_ReadsOnTargetAndJunctionCounts_total',
            'a5ss_s1_jc_exc': 'A5SS_SAMPLE_1_JunctionCountOnly_exclusion',
            'a5ss_s1_jc_inc': 'A5SS_SAMPLE_1_JunctionCountOnly_inclusion',
            'a5ss_s1_jc_total': 'A5SS_SAMPLE_1_JunctionCountOnly_total',
            'a5ss_s2_all_exc': 'A5SS_SAMPLE_2_ReadsOnTargetAndJunctionCounts_exclusion',
            'a5ss_s2_all_inc': 'A5SS_SAMPLE_2_ReadsOnTargetAndJunctionCounts_inclusion',
            'a5ss_s2_all_total': 'A5SS_SAMPLE_2_ReadsOnTargetAndJunctionCounts_total',
            'a5ss_s2_jc_exc': 'A5SS_SAMPLE_2_JunctionCountOnly_exclusion',
            'a5ss_s2_jc_inc': 'A5SS_SAMPLE_2_JunctionCountOnly_inclusion',
            'a5ss_s2_jc_total': 'A5SS_SAMPLE_2_JunctionCountOnly_total',
            'mxe_s1_all_exc': 'MXE_SAMPLE_1_ReadsOnTargetAndJunctionCounts_exclusion',
            'mxe_s1_all_inc': 'MXE_SAMPLE_1_ReadsOnTargetAndJunctionCounts_inclusion',
            'mxe_s1_all_total': 'MXE_SAMPLE_1_ReadsOnTargetAndJunctionCounts_total',
            'mxe_s1_jc_exc': 'MXE_SAMPLE_1_JunctionCountOnly_exclusion',
            'mxe_s1_jc_inc': 'MXE_SAMPLE_1_JunctionCountOnly_inclusion',
            'mxe_s1_jc_total': 'MXE_SAMPLE_1_JunctionCountOnly_total',
            'mxe_s2_all_exc': 'MXE_SAMPLE_2_ReadsOnTargetAndJunctionCounts_exclusion',
            'mxe_s2_all_inc': 'MXE_SAMPLE_2_ReadsOnTargetAndJunctionCounts_inclusion',
            'mxe_s2_all_total': 'MXE_SAMPLE_2_ReadsOnTargetAndJunctionCounts_total',
            'mxe_s2_jc_exc': 'MXE_SAMPLE_2_JunctionCountOnly_exclusion',
            'mxe_s2_jc_inc': 'MXE_SAMPLE_2_JunctionCountOnly_inclusion',
            'mxe_s2_jc_total': 'MXE_SAMPLE_2_JunctionCountOnly_total',
            'ri_s1_all_exc': 'RI_SAMPLE_1_ReadsOnTargetAndJunctionCounts_exclusion',
            'ri_s1_all_inc': 'RI_SAMPLE_1_ReadsOnTargetAndJunctionCounts_inclusion',
            'ri_s1_all_total': 'RI_SAMPLE_1_ReadsOnTargetAndJunctionCounts_total',
            'ri_s1_jc_exc': 'RI_SAMPLE_1_JunctionCountOnly_exclusion',
            'ri_s1_jc_inc': 'RI_SAMPLE_1_JunctionCountOnly_inclusion',
            'ri_s1_jc_total': 'RI_SAMPLE_1_JunctionCountOnly_total',
            'ri_s2_all_exc': 'RI_SAMPLE_2_ReadsOnTargetAndJunctionCounts_exclusion',
            'ri_s2_all_inc': 'RI_SAMPLE_2_ReadsOnTargetAndJunctionCounts_inclusion',
            'ri_s2_all_total': 'RI_SAMPLE_2_ReadsOnTargetAndJunctionCounts_total',
            'ri_s2_jc_exc': 'RI_SAMPLE_2_JunctionCountOnly_exclusion',
            'ri_s2_jc_inc': 'RI_SAMPLE_2_JunctionCountOnly_inclusion',
            'ri_s2_jc_total': 'RI_SAMPLE_2_JunctionCountOnly_total',
            'se_s1_all_exc': 'SE_SAMPLE_1_ReadsOnTargetAndJunctionCounts_exclusion',
            'se_s1_all_inc': 'SE_SAMPLE_1_ReadsOnTargetAndJunctionCounts_inclusion',
            'se_s1_all_total': 'SE_SAMPLE_1_ReadsOnTargetAndJunctionCounts_total',
            'se_s1_jc_exc': 'SE_SAMPLE_1_JunctionCountOnly_exclusion',
            'se_s1_jc_inc': 'SE_SAMPLE_1_JunctionCountOnly_inclusion',
            'se_s1_jc_total': 'SE_SAMPLE_1_JunctionCountOnly_total',
            'se_s2_all_exc': 'SE_SAMPLE_2_ReadsOnTargetAndJunctionCounts_exclusion',
            'se_s2_all_inc': 'SE_SAMPLE_2_ReadsOnTargetAndJunctionCounts_inclusion',
            'se_s2_all_total': 'SE_SAMPLE_2_ReadsOnTargetAndJunctionCounts_total',
            'se_s2_jc_exc': 'SE_SAMPLE_2_JunctionCountOnly_exclusion',
            'se_s2_jc_inc': 'SE_SAMPLE_2_JunctionCountOnly_inclusion',
            'se_s2_jc_total': 'SE_SAMPLE_2_JunctionCountOnly_total',
            'total_s1_all_exc': 'SAMPLE_1_ReadsOnTargetAndJunctionCounts_exclusion_total',
            'total_s1_all_inc': 'SAMPLE_1_ReadsOnTargetAndJunctionCounts_inclusion_total',
            'total_s1_all_total': 'SAMPLE_1_ReadsOnTargetAndJunctionCounts_total',
            'total_s1_jc_exc': 'SAMPLE_1_JunctionCountOnly_exclusion_total',
            'total_s1_jc_inc': 'SAMPLE_1_JunctionCountOnly_inclusion_total',
            'total_s1_jc_total': 'SAMPLE_1_JunctionCountOnly_total',
            'total_s2_all_exc': 'SAMPLE_2_ReadsOnTargetAndJunctionCounts_exclusion_total',
            'total_s2_all_inc': 'SAMPLE_2_ReadsOnTargetAndJunctionCounts_inclusion_total',
            'total_s2_all_total': 'SAMPLE_2_ReadsOnTargetAndJunctionCounts_total',
            'total_s2_jc_exc': 'SAMPLE_2_JunctionCountOnly_exclusion_total',
            'total_s2_jc_inc': 'SAMPLE_2_JunctionCountOnly_inclusion_total',
            'total_s2_jc_total': 'SAMPLE_2_JunctionCountOnly_total'
        }

    def parse_dic_file(self, dic_file):
        return dict([(arr[0], arr[1]) for arr in [line.strip().split('\t') for line in open(dic_file).readlines()]])

    @report_check
    def add_sg_splicing_rmats(self, splicing_id=None, outpath=None, compare_plan=None, params=None, s3_output=None, gene_type_tsv=None):
        if splicing_id:
            splicing_id = ObjectId(splicing_id)
        else:
            project_sn = self.bind_object.sheet.project_sn
            task_id = self.bind_object.sheet.id
            test_group = compare_plan.split('|')[0]
            ctrl_group = compare_plan.split('|')[1]
            time_now = datetime.datetime.now()
            name = 'Splicing_{}_vs_{}_{}'.format(test_group, ctrl_group, time_now.strftime('%Y%m%d_%H%M%S'))
            insert_data = {
                'project_sn': project_sn,
                'task_id': task_id,
                'name': name,
                'desc': 'alternative splicing main table',
                'created_ts': time_now.strftime('%Y-%m-%d %H:%M:%S'),
                'params': json.dumps(params, sort_keys=True, separators=(',', ':')),
                'status': 'start',
                'result_dir': outpath,
                'compare_plan': compare_plan
            }
            splicing_id = self.create_db_table('sg_splicing_rmats', [insert_data])
            self.bind_object.logger.info('succeed in creating table in sg_splicing_rmats')
        src_detail_file = os.path.join(outpath, 'all_events_detail_big_table.txt')
        self.add_sg_splicing_rmats_detail(splicing_id=splicing_id, src_file=src_detail_file, type_file=gene_type_tsv)
        type_stats_data = self.parse_dic_file(dic_file=os.path.join(outpath, 'event_type.file.txt'))
        self.add_sg_splicing_rmats_type_stats(splicing_id=splicing_id, src_dic=type_stats_data)
        self.add_sg_splicing_rmats_stats(splicing_id=splicing_id, outpath=outpath)
        insert_dict = {'status': 'end', 'main_id': splicing_id}
        if s3_output:
            insert_dict.update({'result_dir': os.path.join(s3_output, os.path.basename(outpath))})
        self.update_db_record('sg_splicing_rmats', splicing_id, insert_dict=insert_dict)

    def add_sg_splicing_rmats_detail(self, splicing_id, src_file, type_file):
        gene_type_dict = dict()
        for line in open(type_file):
            items = line.strip().split('\t')
            if len(items) > 3:
                gene_type_dict[items[0]] = items[2]
        rmats_detail_file_head_index_dic = dict(zip(self._RMATS_DETAIL_TABLE_HEAD, range(64)))
        subprocess.call('sed -i /^[[:space:]]*$/d %s' % (src_file), shell=True)
        file_content = open(src_file)
        file_content.readline()
        data_lst = []
        while 1:
            data = {}
            line = file_content.readline()
            if not line.strip():
                break
            arr = re.split('\t+', line.strip())
            if len(arr) != 64:
                raise Exception('line in rmats big detail file %s is not legal: not 64 columns: %s' % (src_file, line))
            data['splicing_id'] = splicing_id
            for field in self._RMATS_DETAIL_MONGO_TABLE_FIELD_DIC.keys():
                data[field] = arr[rmats_detail_file_head_index_dic[self._RMATS_DETAIL_MONGO_TABLE_FIELD_DIC[field]]]

            try:
                data['inc_diff_jc'] = float(data['inc_diff_jc'])
                data['inc_diff_all'] = float(data['inc_diff_all'])
            except:
                pass
            data["pvalue_jc"] = float(data["pvalue_jc"]) if data["pvalue_jc"] != "null" else data["pvalue_jc"]
            data["pvalue_all"] = float(data["pvalue_all"]) if data["pvalue_all"] != "null" else data["pvalue_all"]
            data["fdr_jc"] = float(data["fdr_jc"]) if data["fdr_jc"] != "null" else data["fdr_jc"]
            data["fdr_all"] = float(data["fdr_all"]) if data["fdr_all"] != "null" else data["fdr_all"]
            data['splicing_id'] = splicing_id
            data['no_diff'] = 'no' if data['diff_jc_or_all'] == 'yes' else 'yes'
            if data['gid'] in gene_type_dict:
                data.update({'rna_type': gene_type_dict[data['gid']]})
            else:
                data.update({'rna_type': 'other'})
            data_lst.append(data)
        collection_obj = self.db['sg_splicing_rmats_detail']
        file_content = open(src_file)
        if len(file_content.readlines()) >= 2:
            collection_obj.insert_many(data_lst)
            self.bind_object.logger.info('succeed in creating tables in sg_splicing_rmats_detail')
        return True

    def add_sg_splicing_rmats_stats(self, splicing_id=None, outpath=None):
        task_id = self.db['sg_splicing_rmats'].find_one({"_id": splicing_id})["task_id"]
        compare_plan = self.db['sg_splicing_rmats'].find_one({"_id": splicing_id})['compare_plan']
        test_group = compare_plan.split('|')[0]
        ctrl_group = compare_plan.split('|')[1]
        group = {test_group: 's1', ctrl_group: 's2'}
        time_now = datetime.datetime.now()
        name = 'Splicing_stat_{}_vs_{}_{}'.format(test_group, ctrl_group, time_now.strftime('%Y%m%d_%H%M%S'))
        params = {
            'task_id': task_id,
            'submit_location': 'splicingrmats_stat',
            'task_type': 2,
            'splicing_id': str(splicing_id),
            'delta_psi': '0',
            'significant_diff': 'fdr',
            'significant_value': '0.05',
        }
        insert_data = {
            'task_id': task_id,
            'name': name,
            'desc': 'alternative splicing statistics main table',
            'created_ts': time_now.strftime('%Y-%m-%d %H:%M:%S'),
            'params': json.dumps(params, sort_keys=True, separators=(',', ':')),
            'status': 'start',
            'group': group
        }
        collection_obj = self.db['sg_splicing_rmats_stats']
        stat_id = collection_obj.insert_one(insert_data).inserted_id
        if stat_id:
            self.bind_object.logger.info('succeed in creating table in sg_splicing_rmats_stats')
        self.update_db_record('sg_splicing_rmats_stats', stat_id, status='end', main_id=stat_id)
        diff_stats_file = os.path.join(outpath, 'event_stats.file.txt')
        psi_stat_file = os.path.join(outpath, 'psi_stats.file.txt')
        psi_data = self.parse_dic_file(dic_file=psi_stat_file)
        diff_stats_data = self.parse_dic_file(dic_file=diff_stats_file)
        diff_stats_dic = self.add_sg_splicing_rmats_diff_stats(stat_id=stat_id, src_dic=diff_stats_data, outpath=outpath)
        psi_dic = self.add_sg_splicing_rmats_psi(stat_id=stat_id, src_dic=psi_data)
        rmats_graph_id = self.add_sg_splicing_rmats_graph(stat_id=stat_id, stats=diff_stats_dic, psi=psi_dic, outpath=outpath)
        if rmats_graph_id:
            self.update_db_record('sg_splicing_rmats_stats', stat_id, status='end')

    def add_sg_splicing_rmats_type_stats(self, splicing_id, src_dic):
        tmp_dic = {}
        for field in self._RMATS_TYPE_MONGO_TABLE_FIELD_DIC.keys():
            tmp_dic[field] = int(src_dic[self._RMATS_TYPE_MONGO_TABLE_FIELD_DIC[field]])
        tmp_dic['old_total'] = int(tmp_dic['total_total']) - int(tmp_dic['novel_total'])
        as_index = dict(zip(range(3), ['old', 'novel', 'total']))
        stats_dic = {
            'as_stats': {'a3ss': [0, 0, 0], 'a5ss': [0, 0, 0], 'mxe': [0, 0, 0], 'se': [0, 0, 0], 'ri': [0, 0, 0],
                         'total': [0, 0, 0]},
            'stats_pie': {'novel': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                          'old': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                          'total': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, }},
            'splicing_id': splicing_id
        }
        for type in stats_dic['as_stats'].keys():
            for i in range(3):
                stats_dic['as_stats'][type][i] = int(tmp_dic[as_index[i] + '_' + type])
        for kind in stats_dic['stats_pie'].keys():
            for type in stats_dic['stats_pie'][kind].keys():
                stats_dic['stats_pie'][kind][type] = tmp_dic[kind + '_' + type.lower()]
        collection_obj = self.db['sg_splicing_rmats_type_stats']
        _id = collection_obj.insert_one(stats_dic).inserted_id
        if _id:
            self.bind_object.logger.info('succeed in creating table in sg_splicing_rmats_type_stats')
        return tmp_dic

    def add_sg_splicing_rmats_diff_stats(self, stat_id, src_dic, outpath=None):
        tmp_dic = {}
        for field in self._RMATS_STATS_MONGO_TABLE_FIELD_DIC.keys():
            tmp_dic[field] = int(src_dic[self._RMATS_STATS_MONGO_TABLE_FIELD_DIC[field]])
        tmp_dic['jcandall_total'] = sum(
            [tmp_dic['jcandall_mxe'], tmp_dic['jcandall_se'], tmp_dic['jcandall_ri'],
             tmp_dic['jcandall_a3ss'], tmp_dic['jcandall_a5ss']])
        tmp_dic['jcorall_total'] = sum(
            [tmp_dic['jcorall_mxe'], tmp_dic['jcorall_se'], tmp_dic['jcorall_ri'],
             tmp_dic['jcorall_a3ss'], tmp_dic['jcorall_a5ss']])
        diff_index = dict(zip(range(4), ['jc', 'all', 'jcandall', 'jcorall']))
        stats_dic = {
            'diff_stats':{'a3ss': [0, 0, 0, 0], 'a5ss': [0, 0, 0, 0], 'mxe': [0, 0, 0, 0], 'se': [0, 0, 0, 0],
                          'ri': [0, 0, 0, 0], 'total': [0, 0, 0, 0]},
            'stat_id': stat_id,
        }
        for type in stats_dic['diff_stats'].keys():
            for i in range(4):
                stats_dic['diff_stats'][type][i] = int(tmp_dic[diff_index[i] + '_' + type])

        # export to excel on 20201221
        splicing_stats = os.path.join(outpath, 'splicing_stats.xls')
        with open(splicing_stats, 'w') as ss:
            ss.write('AS type\tJunctionCountOnly(JC)\tReadsOnTargetAndJunctionCounts(JCEC)\tJC&JCEC\tJC|JCEC\n')
            for key, value in stats_dic['diff_stats'].items():
                ss.write('{}\t{}\n'.format(key, '\t'.join([str(i) for i in value])))

        collection_obj = self.db['sg_splicing_rmats_diff_stats']
        _id = collection_obj.insert_one(stats_dic).inserted_id
        if _id:
            self.bind_object.logger.info('succeed in creating table in sg_splicing_rmats_diff_stats')
        return tmp_dic

    def add_sg_splicing_rmats_psi(self, stat_id, src_dic):
        tmp_dic = {}
        for field in self._RMATS_PSI_MONGO_TABLE_FIELD_DIC.keys():
            tmp_dic[field] = int(src_dic[self._RMATS_PSI_MONGO_TABLE_FIELD_DIC[field]])
        psi_dic = {
            's1_jc':
                {'a3ss': [0, 0, 0], 'a5ss': [0, 0, 0], 'mxe': [0, 0, 0], 'se': [0, 0, 0], 'ri': [0, 0, 0],
                 'total': [0, 0, 0]
                 },
            's2_jc':
                {'a3ss': [0, 0, 0], 'a5ss': [0, 0, 0], 'mxe': [0, 0, 0], 'se': [0, 0, 0], 'ri': [0, 0, 0],
                 'total': [0, 0, 0]
                 },
            's1_all':
                {'a3ss': [0, 0, 0], 'a5ss': [0, 0, 0], 'mxe': [0, 0, 0], 'se': [0, 0, 0], 'ri': [0, 0, 0],
                 'total': [0, 0, 0]
                 },
            's2_all':
                {'a3ss': [0, 0, 0], 'a5ss': [0, 0, 0], 'mxe': [0, 0, 0], 'se': [0, 0, 0], 'ri': [0, 0, 0],
                 'total': [0, 0, 0]
                 },
            'stat_id': stat_id
        }
        psi_index = dict(zip(range(3), ['exc', 'inc', 'total']))
        for choice in ['s1_all', 's1_jc', 's2_all', 's2_jc']:
            for type in psi_dic[choice].keys():
                for i in range(3):
                    psi_dic[choice][type][i] = tmp_dic[type + '_' + choice + '_' + psi_index[i]]

        collection_obj = self.db['sg_splicing_rmats_psi']
        _id = collection_obj.insert_one(psi_dic).inserted_id
        if _id:
            self.bind_object.logger.info('succeed in creating table in sg_splicing_rmats_psi')

        return tmp_dic

    def add_sg_splicing_rmats_graph(self, stat_id, stats, psi, outpath=None):
        graph_dic = {'psi_bar': {'s1': {'jc': {'exc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                               'inc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                               'total': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, }
                                               }, 'all': {'exc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                                          'inc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                                          'total': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0,
                                                                    'A5SS': 0, }}},
                                 's2': {'jc': {'exc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                               'inc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                               'total': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, }},
                                        'all': {'exc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                                'inc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                                'total': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, }}}},
                     'diff_pie': {'jc': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                  'all': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                  'jcandall': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, },
                                  'jcorall': {'A3SS': 0, 'MXE': 0, 'RI': 0, 'SE': 0, 'A5SS': 0, }},
                     'stat_id': stat_id}

        for src in graph_dic['diff_pie'].keys():
            for type in graph_dic['diff_pie'][src].keys():
                graph_dic['diff_pie'][src][type] = stats[src + '_' + type.lower()]
        for sample in graph_dic['psi_bar'].keys():
            for src in graph_dic['psi_bar'][sample].keys():
                for psi_kind in graph_dic['psi_bar'][sample][src].keys():
                    for type in graph_dic['psi_bar'][sample][src][psi_kind].keys():
                        graph_dic['psi_bar'][sample][src][psi_kind][type] = psi[
                            '_'.join([type.lower(), sample, src, psi_kind])]

        # export to excel on 20201215
        as_type = ['A3SS', 'MXE', 'RI', 'SE', 'A5SS']
        main_collection = self.db['sg_splicing_rmats_stats']
        sample_dict = main_collection.find_one({'_id': stat_id})['group']
        for sample in graph_dic['psi_bar'].keys():
            for k, v in sample_dict.items():    # get sample name
                if sample == v:
                    sample_name = k
            for src in graph_dic['psi_bar'][sample].keys():
                if src == 'all':                  # rename diff method
                    method = 'JCEC'
                else:
                    method = src.upper()
                stats_file = os.path.join(outpath, '{}_splicing_{}_stats.xls'.format(sample_name, method))
                with open(stats_file, 'w') as stats:
                    stats.write('AS type\tExclusion\tInclusion\tTotal events\n')
                    for i in as_type:
                        bar_list = list()
                        for psi_kind in graph_dic['psi_bar'][sample][src].keys():
                            bar_list.append(str(graph_dic['psi_bar'][sample][src][psi_kind].get(i)))
                        stats.write('{}\t{}\n'.format(i, '\t'.join(bar_list)))

        collection_obj = self.db['sg_splicing_rmats_graph']
        _id = collection_obj.insert_one(graph_dic).inserted_id
        if _id:
            self.bind_object.logger.info('succeed in creating table in sg_splicing_rmats_graph')
            return _id


import bs4
import vk_api
from vk_api.longpoll import VkLongPoll, VkEventType
from data import token, dima_token, my_id, V
from utility import bOptions, bChains, bGroups, bExit, daysInMonths, monthName_to_No, time_to_pairNo, study_groups, return_codes
from utility import c1, c2, c3, c4, fullC1, fullC2, fullC3, fullC4, greetings, gratitudes, response_to_gratitude, swearings
import requests
import random
import json
from bs4 import BeautifulSoup
from time import *
import string
import datetime
import threading
from copy import deepcopy
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select
from replit import db
from background import keep_alive

keep_alive()

session = vk_api.VkApi(token=token)
vk = session.get_api()
longpoll = VkLongPoll(session)
personChains = []
dates_str = []
existing_study_groups = set()
for key in study_groups.keys():
    existing_study_groups.add(study_groups[key])

print("Бот запущен")

# Статусы:
# 0 - человека нет в БД
# 1 - нет ни группы, ни цепочек (мы еще на знакомы, выбери из списка свою учебную группу), ожидается группа
# 2 - выбрана группа, но нет цепочек (для начала выбери свои цепочки обучения ...), ожидаются цепочки
# 3 - смена цепочки, ожидаются цепочки
# 4 - смена группы, ожидается группа
# 10 - главное меню, все есть
# 11 - ожидается дата, чтобы показать расписаниее в этот день
# 12 - выбор определенной недели, ожидается неделя

def process(buddy_id, msg):
    global personChains, dates_str
    status = 0 if str(buddy_id) not in db.keys() else db[str(buddy_id)]["status"]

    if not status:
        person = requests.get(f"https://api.vk.com/method/users.get?user_ids={buddy_id}&fields=sex&access_token={token}&v={V}").json()
        msgToMe = f'Новый собеседник у бота Артура - [id{buddy_id}|{person["response"][0]["first_name"]} {person["response"][0]["last_name"]}]'
        requests.get(f"https://api.vk.com/method/messages.send?user_id={my_id}&message={msgToMe}&random_id=0&access_token={dima_token}&v={V}")
        if msg.lower() == "начать" or isGreeting(msg.lower()):
            send(user_id=buddy_id, msg=f'Привет, {person["response"][0]["first_name"]}!', kbrd=None)
        sex = 'a' if person["response"][0]["sex"] == 1 else ''
        db[str(buddy_id)] = {"status": 1, "name": person["response"][0]["first_name"], "surname": person["response"][0]["last_name"], "sex": sex, "chains": [], "group": ''}
        for i in range(3):
            bGroups["buttons"][i][0]["color"] = "secondary"
        send(buddy_id, bGroups, "Мы пока не знакомы, выбери свою учебную группу")

    elif status == 1 or status == 4:
        if msg in existing_study_groups:
            db[str(buddy_id)]["group"] = msg
            if status == 1:
                db[str(buddy_id)]["status"] = 2
                for i in range(4):
                    bChains["buttons"][i][0]["color"] = "secondary"
                send(user_id=buddy_id, msg=f"Выбрана группа {msg}", kbrd=None)
                send(user_id=buddy_id, msg="Выбери свои цепочки обучения", attachment='photo-215267285_457239444', kbrd=bChains)
            else:
                db[str(buddy_id)]["status"] = 10
                send(buddy_id, bOptions, f"Выбрана группа {msg}")
        else:
            for i in range(3):
                bGroups["buttons"][i][0]["color"] = "secondary"
            if status == 1:
                send(buddy_id, bGroups, "Выбери, пожалуйста, свою учебную группу <3")
            else:
                group = db[str(buddy_id)]["group"]
                if group == study_groups['12']:
                    bGroups["buttons"][0][0]["color"] = "positive"
                elif group == study_groups['14']:
                    bGroups["buttons"][1][0]["color"] = "positive"
                elif group == study_groups['21']:
                    bGroups["buttons"][2][0]["color"] = "positive"
                send(buddy_id, bGroups, "Выбери группу")

    elif status == 2 or status == 3:
        if msg == c1 or msg == c2 or msg == c3 or msg == c4:
            db[str(buddy_id)]["status"] = 10
            full = ''
            if msg == c1:
                db[str(buddy_id)]["chains"] = [1, 2]
                personChains = [1, 2]
                full = fullC1
            elif msg == c2:
                db[str(buddy_id)]["chains"] = [3, 4]
                personChains = [3, 4]
                full = fullC2
            elif msg == c3:
                db[str(buddy_id)]["chains"] = [2, 3]
                personChains = [2, 3]
                full = fullC3
            elif msg == c4:
                db[str(buddy_id)]["chains"] = [1, 4]
                personChains = [1, 4]
                full = fullC4
            send(buddy_id, bOptions, f'Выбраны цепочки № {personChains[0]} и {personChains[1]}: "{full}"')
        else:
            for i in range(4):
                bChains["buttons"][i][0]["color"] = "secondary"
            if status == 2:
                send(user_id=buddy_id, msg="Выбери, пожалуйста, свои цепочки обучения <3", attachment='photo-215267285_457239444', kbrd=bChains)
            else:
                personChains = db[str(buddy_id)]["chains"]
                if personChains == [1, 2]:
                    bChains["buttons"][0][0]["color"] = "positive"
                elif personChains == [3, 4]:
                    bChains["buttons"][1][0]["color"] = "positive"
                elif personChains == [2, 3]:
                    bChains["buttons"][2][0]["color"] = "positive"
                elif personChains == [1, 4]:
                    bChains["buttons"][3][0]["color"] = "positive"
                send(user_id=buddy_id, msg="Выбери цепочки обучения", attachment='photo-215267285_457239444', kbrd=bChains)

    elif status == 10:
        msg = msg.lower()
        info = db[str(buddy_id)]
        group = info["group"]
        personChains = info["chains"]
        sex = info["sex"]
        if msg == "на сегодня":
            vk.messages.setActivity(user_id=buddy_id, type='typing')
            getSchedule(user_id=buddy_id, num=1, group=group)
        elif msg == "на завтра":
            vk.messages.setActivity(user_id=buddy_id, type='typing')
            getSchedule(user_id=buddy_id, num=2, group=group)
        elif msg == "расписание до конца недели":
            vk.messages.setActivity(user_id=buddy_id, type='typing')
            getSchedule(user_id=buddy_id, num=3, group=group)
        elif msg == "расписание на следующую неделю":
            vk.messages.setActivity(user_id=buddy_id, type='typing')
            getSchedule(user_id=buddy_id, num=4, group=group)
        elif msg == "расписание в определенный день":
            db[str(buddy_id)]["status"] = 11
            send(buddy_id, bExit, "Могу скинуть расписание на любой день в пределах текущего семестра\n\nВведи дату в любом из следующих форматов:\n\n- 8 марта\n- 08.03\n- 8.3")
        elif msg == "расписание в определенную неделю":
            db[str(buddy_id)]["status"] = 12
            status12(buddy_id, group)
        elif msg == "сменить группу":
            db[str(buddy_id)]["status"] = 4
            for i in range(3):
                bGroups["buttons"][i][0]["color"] = "secondary"
            if group == study_groups['12']:
                bGroups["buttons"][0][0]["color"] = "positive"
            elif group == study_groups['14']:
                bGroups["buttons"][1][0]["color"] = "positive"
            elif group == study_groups['21']:
                bGroups["buttons"][2][0]["color"] = "positive"
            send(buddy_id, bGroups, "Выбери группу")
        elif msg == "сменить цепочки":
            db[str(buddy_id)]["status"] = 3
            for i in range(4):
                bChains["buttons"][i][0]["color"] = "secondary"
            if personChains == [1, 2]:
                bChains["buttons"][0][0]["color"] = "positive"
            elif personChains == [3, 4]:
                bChains["buttons"][1][0]["color"] = "positive"
            elif personChains == [2, 3]:
                bChains["buttons"][2][0]["color"] = "positive"
            elif personChains == [1, 4]:
                bChains["buttons"][3][0]["color"] = "positive"
            send(user_id=buddy_id, msg="Выбери цепочки обучения", attachment='photo-215267285_457239444', kbrd=bChains)
        else:
            if msg == "начать" or isGreeting(msg):
                send(buddy_id, bOptions, f'Привет, {db[str(buddy_id)]["name"]}!')
            elif isSwearing(msg):
                if sex == '':
                    send(buddy_id, bOptions, "Сам такой")
                else:
                    send(buddy_id, bOptions, "Сама такая")
            elif isGratitude(msg):
                if sex == '':
                    rand = random.randint(0, len(response_to_gratitude) - 1)
                else:
                    rand = random.randint(0, len(response_to_gratitude) - 2)
                send(buddy_id, bOptions, response_to_gratitude[rand])
            elif len(msg) > 0 and msg[0].isascii() and msg[0].isalpha():
                send(buddy_id, bOptions, "Извини, не понимаю на пендосском (")
            elif "люди" in msg.lower() and buddy_id == my_id:
                msgToMe = ''
                for person in db.keys():
                    if msgToMe:
                        msgToMe += '\n\n'
                    msgToMe += f"[id{person}|{person}]"
                    for info in db[person]:
                        if info == "chains":
                            msgToMe += f"\n{info} -> {f'{db[person][info][0]}, {db[person][info][1]}' if db[person][info] else ''}"
                        else:
                            msgToMe += f"\n{info} -> {db[person][info]}"
                send(my_id, bOptions, msgToMe)
            elif msg.lower()[:5] == "удали" and buddy_id == my_id:
                try:
                    link = msg[msg.find(' ') + 1:].strip()
                    if link == "меня":
                        del db[f'{my_id}']
                    else:
                        link = link[link.find('id') + 2:]
                        del db[str(link)]
                    send(user_id=my_id, msg='✅', kbrd=None)
                except:
                    send(user_id=my_id, msg='❌', kbrd=None)
            else:
                send(buddy_id, bOptions, "Нажми любую кнопку на клавиатуре")

    elif status == 11:
        gotIt: bool
        if msg.lower() == "в главное меню":
            db[str(buddy_id)]["status"] = 10
            send(buddy_id, bOptions, "😉")
            return
        msg = msg.strip()
        day, month = 0, 0
        curr_year = datetime.datetime.now().year
        if curr_year == 2024 or curr_year == 2028 or curr_year == 2032:
            daysInMonths[2] = 29
        if msg.find(' ') != -1:
            try:
                day = int(msg[:msg.find(' ')])
                month = monthName_to_No[msg[msg.rfind(' ') + 1:]]
                gotIt = True
            except:
                gotIt = False
        elif msg.find('.') != -1:
            while msg.count('.') > 1:
                msg = msg[:msg.rfind('.')]
            try:
                day = int(msg[:msg.find('.')])
                month = int(msg[msg.find('.') + 1:])
                gotIt = True
            except:
                gotIt = False
        else:
            gotIt = False
        if gotIt:
            if day < 1 or day > daysInMonths[month]:
                msg = 'Введи дату в одном из представленных ниже формате, либо нажми кнопку "в главное меню"\n\nВозможные форматы:\n- 8 марта\n- 08.03\n- 8.3'
                if month == 2 and day == 29:
                    msg = f'В {curr_year} году в феврале 28 дней. ' + msg
                else:
                    msg = 'Такой даты не существует. ' + msg
                send(buddy_id, bExit, msg)
            else:
                info = db[str(buddy_id)]
                group = info["group"]
                personChains = info["chains"]
                if getScheduleForSpecificDay(buddy_id, group, month, day):
                    db[str(buddy_id)]["status"] = 10
        else:
            send(buddy_id, bExit, 'Неверный формат. Введи дату в одном из представленных ниже формате, либо нажми кнопку "в главное меню"\n\nВозможные форматы:\n- 8 марта\n- 08.03\n- 8.3')

    elif status == 12:
        info = db[str(buddy_id)]
        group = info["group"]
        personChains = info["chains"]
        if msg in dates_str:
            db[str(buddy_id)]["status"] = 10
            getSchedule(buddy_id, 5, group, dates_str.index(msg) + 1)
        elif msg.lower() == "в главное меню":
            db[str(buddy_id)]["status"] = 10
            send(buddy_id, bOptions, "😉")
        else:
            status12(buddy_id, group, True)

def baseOnChains(subject):
    global personChains
    chains = {
        'Технология производства средств информационно-вычислительной техники': 1,
        'Микроконтроллеры': 2,
        'Информационная теория оценок': 3,
        'Надежность информационных систем': 4,
    }
    if type(subject) is str:
        return True
    if subject["name"] in chains and chains[subject["name"]] not in personChains:
        return False
    else:
        return True

def status12(buddy_id, group, buddyIsStupid=False):
    global dates_str
    vk.messages.setActivity(user_id=buddy_id, type='typing')
    if buddyIsStupid:
        send(buddy_id, bExit, "Ответ неверный. Выбери любую неделю выше или нажми кнопку \"в главное меню\" под стандартной клавиатурой 😉")
    else:
        url = f"https://mai.ru/education/studies/schedule/index.php?group={group}"
        soup = getTextFromUrl(url)
        if type(soup) is str:
            db[str(buddy_id)]["status"] = 10
            send(buddy_id, bOptions, soup)
            return
        workWeekChoice = soup.find(id="collapseWeeks")
        dates = workWeekChoice.find_all(class_="w-100 d-block text-center")
        dates_str = [f"{date.next[:date.next.find(' - ')][:-5]} - {date.next[date.next.find(' - ') + 3:][:-5]}" for date in dates]
        dt = datetime.datetime.now()
        weekNo = determineWeekNo([[date.next[:date.next.find(' - ')], date.next[date.next.find(' - ') + 3:]] for date in dates], f"{dt.day}.{dt.month}.{dt.year}")
        buttons = getBWeeks(dates_str, weekNo)
        send(buddy_id, buttons[0], "Учебные недели № 1-9")
        send(buddy_id, buttons[1], "Учебные недели № 10-18")
        send(buddy_id, bExit, "Выбери любую учебную неделю и я скину соответствующее для нее расписание!\nДля выхода в главное меню нажми единственную кнопку под стандартной клавиатурой ⬇")

def getBWeeks(dates, weekNo):
    bWeeks1 = {
        "inline": True,
        "buttons": []
    }
    bWeeks2 = {
        "inline": True,
        "buttons": []
    }
    single = {
        "action": {
            "type": "text",
            "label": "",
        },
        "color": "secondary"
    }
    buttons1, buttons2 = [], []
    for i in range(3):
        l1, l2, l3 = deepcopy(single), deepcopy(single), deepcopy(single)
        l1["action"]["label"] = dates[3 * i]
        l2["action"]["label"] = dates[3 * i + 1]
        l3["action"]["label"] = dates[3 * i + 2]
        buttons1.append([l1, l2, l3])
        l1, l2, l3 = deepcopy(single), deepcopy(single), deepcopy(single)
        l1["action"]["label"] = dates[3 * i + 9]
        l2["action"]["label"] = dates[3 * i + 10]
        l3["action"]["label"] = dates[3 * i + 11]
        buttons2.append([l1, l2, l3])
    if weekNo != -1:
        if weekNo <= 9:
            buttons1[int((weekNo - 1) / 3)][int((weekNo - 1) % 3)]["color"] = "positive"
        else:
            buttons2[int((weekNo - 10) / 3)][int((weekNo - 10) % 3)]["color"] = "positive"
    bWeeks1["buttons"] = buttons1
    bWeeks2["buttons"] = buttons2
    return [bWeeks1, bWeeks2]

def getScheduleForSpecificDay(user_id, group, month, day):
    global personChains
    vk.messages.setActivity(user_id=user_id, type='typing')
    url = f"https://mai.ru/education/studies/schedule/index.php?group={group}"
    soup = getTextFromUrl(url)
    if type(soup) is str:
        db[str(user_id)]["status"] = 10
        send(user_id, bOptions, soup)
        return

    workWeekChoice = soup.find(id="collapseWeeks")
    dates = workWeekChoice.find_all(class_="w-100 d-block text-center")
    dates = [[date.next[:date.next.find(' - ')], date.next[date.next.find(' - ') + 3:]] for date in dates]
    neededDate = f"{day}.{month}.{datetime.datetime.now().year}"
    todayDate = str(datetime.date.today()).replace('-', '.')
    todayDate = todayDate[8:] + '.' + todayDate[5:7] + '.' + todayDate[:4]
    if isInDateInterval(neededDate, [dates[0][0], dates[-1][1]]):
        weekNo = determineWeekNo(dates, neededDate)
        if isSame(dates[weekNo - 1][1], neededDate):
            if isSame(todayDate, neededDate):
                send(user_id, bOptions, "Сегодня воскресенье, занятий нет")
            elif isInDateInterval(neededDate, ['28.07.2002', todayDate]):
                send(user_id, bOptions, "Это воскресенье, занятий не было")
            else:
                send(user_id, bOptions, "Это воскресенье, занятий не будет")
        else:
            url += f"&week={weekNo}"
            text = requests.get(url=url).text
            soup = BeautifulSoup(text, 'lxml')
            schedule = soup.find(class_="step mb-5")
            week = [tag.string.strip() if tag.string else tag for tag in schedule.find_all(class_="mb-4")]

            sub = {}
            weekDay = []
            for i in range(len(week)):
                if type(week[i]) is str:
                    current_day = int(week[i][week[i].find('\xa0') + 1:week[i].rfind('\xa0')])
                    current_month = monthName_to_No[week[i][week[i].rfind('\xa0') + 1:]]
                    if weekDay:
                        break
                    elif current_day == day:
                        weekDay.append(week[i])
                    elif current_day > day and current_month == month:
                        if isInDateInterval(neededDate, ['28.07.2002', todayDate]):
                            send(user_id, bOptions, "В этот день не было занятий")
                        else:
                            send(user_id, bOptions, "В этот день не будет занятий")
                        return True
                else:
                    if not weekDay:
                        if i == len(week) - 1:
                            if isInDateInterval(neededDate, ['28.07.2002', todayDate]):
                                send(user_id, bOptions, "В этот день не было занятий")
                            else:
                                send(user_id, bOptions, "В этот день не будет занятий")
                            return True
                        continue
                    sub['name'] = week[i].p.next.strip()
                    span = week[i].find(class_='text-nowrap')
                    if span:
                        sub['name'] += ' ' + span.next.strip()
                    sub['type'] = week[i].find(class_='badge').next.strip()
                    time_prepod_classroom = week[i].find('ul')
                    time_prepod_classroom = list(filter(lambda x: x != '\n', time_prepod_classroom))
                    sub['time'] = time_prepod_classroom[0].next
                    if len(time_prepod_classroom) >= 3:
                        sub['prepod'] = time_prepod_classroom[1].a.next
                        sub['prepod_link'] = 'https://mai.ru' + time_prepod_classroom[1].a['href']
                        sub['classroom'] = [room.contents[-1] for room in time_prepod_classroom[2:]]
                    elif len(time_prepod_classroom) == 2:
                        sub['classroom'] = [time_prepod_classroom[1].contents[-1]]
                    else:
                        print(f"в time_prepod_classrom 1 значение, а {len(time_prepod_classroom)}")
                        print(sub)
                    weekDay.append(sub)
                    sub = {}

            weekDay = list(filter(baseOnChains, weekDay))
            msgToSend = weekDay[0] + '\n---------------------------------\n'
            for j in range(1, len(weekDay)):
                if j != 1:
                    msgToSend += '\n\n'
                msgToSend += f"{time_to_pairNo[weekDay[j]['time'][:2]]} пара:\n{weekDay[j]['name']} ({weekDay[j]['type']})"
                msgToSend += '\n' + weekDay[j]['time']
                msgToSend += '\nАудитория: ' + ' / '.join(weekDay[j]['classroom'])
                if 'prepod' in weekDay[j]:
                    shortenedLink = vk.utils.getShortLink(url=weekDay[j]["prepod_link"])["short_url"]
                    msgToSend += '\n' + weekDay[j]['prepod'] + '\n*** ' + shortenedLink + ' ***'
            send(user_id, bOptions, msgToSend)
        return True
    else:
        db[str(user_id)]["status"] = 10
        send(user_id, bOptions, f"Дата должна находится в интервале от {dates[0][0]} до {dates[-1][1]}")
        return False

def getSchedule(user_id, num, group, weekNo=None):
    url = f"https://mai.ru/education/studies/schedule/index.php?group={group}"
    if num == 5:
        url += f"&week={weekNo}"
    elif num == 3 or num == 4:
        soup = getTextFromUrl(url)
        if type(soup) is str:
            send(user_id, bOptions, soup)
            return
        workWeekChoice = soup.find(id="collapseWeeks")
        todayDate = str(datetime.date.today()).replace('-', '.')
        todayDate = todayDate[8:] + '.' + todayDate[5:7] + '.' + todayDate[:4]
        dates = workWeekChoice.find_all(class_="w-100 d-block text-center")
        dates = [[date.next[:date.next.find(' - ')], date.next[date.next.find(' - ') + 3:]] for date in dates]
        weekNo = determineWeekNo(dates, todayDate)
        if num == 4:
            weekNo += 1
        if weekNo == -1:
            msg = f"В диалоге с [id{user_id}|user] weekNo стала -1, а так низя:"
            url = f"https://api.vk.com/method/messages.send?user_id={my_id}&message={msg}&random_id=0&access_token={dima_token}&v={V}"
            requests.get(url=url)
            send(user_id=user_id, msg="Fatal error", kbrd=None)
            exit(-1)
        if num == 3 and isSame(todayDate, dates[weekNo - 1][1]):
            send(user_id, bOptions, "Эта неделя заканчивается сегодня")
            return
        url += f"&week={weekNo}"
    try:
        soup = getTextFromUrl(url)
        if type(soup) is str:
            send(user_id, bOptions, soup)
            return
        schedule = soup.find(class_="step mb-5")
        week = [tag.string.strip() if tag.string else tag for tag in schedule.find_all(class_="mb-4")]
    except AttributeError:
        schedule = bs4.BeautifulSoup(getStepMb5HTML(group, weekNo), 'lxml')
        week = [tag.string.strip() if tag.string else tag for tag in schedule.find_all(class_="mb-4")]

    sub = {}
    day = []
    days = []
    for i in range(len(week)):
        if type(week[i]) is str:
            if i != 0:
                days.append(day)
                day = []
                if num == 1:
                    break
                if len(days) == 2 and num == 2:
                    break
            day.append(week[i])
        else:
            sub['name'] = week[i].p.next.strip()
            span = week[i].find(class_='text-nowrap')
            if span:
                sub['name'] += ' ' + span.next.strip()
            sub['type'] = week[i].find(class_='badge').next.strip()
            time_prepod_classroom = week[i].find('ul')
            time_prepod_classroom = list(filter(lambda x: x != '\n', time_prepod_classroom))
            sub['time'] = time_prepod_classroom[0].next
            if len(time_prepod_classroom) >= 3:
                sub['prepod'] = time_prepod_classroom[1].a.next
                sub['prepod_link'] = 'https://mai.ru' + time_prepod_classroom[1].a['href']
                sub['classroom'] = [room.contents[-1] for room in time_prepod_classroom[2:]]
            elif len(time_prepod_classroom) == 2:
                sub['classroom'] = [time_prepod_classroom[1].contents[-1]]
            else:
                print(f"в time_prepod_classrom 1 значение, а {len(time_prepod_classroom)}")
                print(sub)
            day.append(sub)
            sub = {}
        if i == len(week) - 1:
            days.append(day)

    todayDate = (datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(hours=3)).strftime("%d.%m.%Y")
    todayMonth = int(todayDate[3:5])
    todayDay = int(todayDate[:2])
    tomorrow = int((datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(days=1, hours=3)).strftime("%d"))
    firstDay = int(days[0][0][days[0][0].find('\xa0') + 1:days[0][0].rfind('\xa0')])
    if num == 1 and firstDay != todayDay:
        send(user_id, bOptions, "Сегодня занятий нет")
        return
    if num == 2:
        haveSchedule = False
        for x in days:
            if int(x[0][x[0].find('\xa0')+1:x[0].rfind('\xa0')]) == tomorrow:
                days = [x]
                haveSchedule = True
                break
        if not haveSchedule:
            send(user_id, bOptions, "Завтра занятий не будет")
            return

    for i in range(len(days)):
        days[i] = list(filter(baseOnChains, days[i]))

    if not days:
        msg = f"[id{user_id}|Fatal error:] days == [] в getSchedule()"
        url = f"https://api.vk.com/method/messages.send?user_id={my_id}&message={msg}&random_id=0&access_token={dima_token}&v={V}"
        requests.get(url=url)
        vk.messages.markAsRead(peer_id=user_id)
        return
    for i in range(len(days)):
        if num == 3:
            date = days[i][0]
            if date.count('\xa0') < 2:
                msg = f'[id{user_id}|В функции getSchedule в строке days[i][0] меньше 2 символов "backslash xa0"]'
                url = f"https://api.vk.com/method/messages.send?user_id={my_id}&message={msg}&random_id=0&access_token={dima_token}&v={V}"
                requests.get(url=url)
                vk.messages.markAsRead(peer_id=user_id)
                send(user_id=user_id, msg="Возникла непредвиденная ошибка, она будет исправлена в ближайшее время", kbrd=None)
                return
            day = int(date[date.find('\xa0') + 1:date.rfind('\xa0')])
            month = monthName_to_No[date[date.rfind('\xa0') + 1:]]
            isPast: bool
            if month < todayMonth:
                isPast = True
            elif month > todayMonth:
                isPast = False
            else:
                if day < todayDay:
                    isPast = True
                else:
                    isPast = False
            if isPast:
                continue
        answer = days[i][0] + '\n---------------------------------\n'
        for j in range(1, len(days[i])):
            if j != 1:
                answer += '\n\n'
            answer += time_to_pairNo[days[i][j]['time'][:2]] + ' пара:\n' + days[i][j]['name'] + ' (' + days[i][j]['type'] + ')'
            answer += '\n' + days[i][j]['time']
            answer += '\nАудитория: ' + ' / '.join(days[i][j]['classroom'])
            if 'prepod' in days[i][j]:
                shortenedLink = vk.utils.getShortLink(url=days[i][j]["prepod_link"])["short_url"]
                answer += '\n' + days[i][j]['prepod'] + '\n*** ' + shortenedLink + ' ***'
        if i == len(days) - 1:
            send(user_id, bOptions, answer)
        else:
            send(user_id, None, answer)

def getStepMb5HTML(group, weekNo):
    browser = webdriver.Chrome()
    url = 'https://mai.ru/education/studies/schedule/index.php'
    browser.get(url)
    browser.implicitly_wait(10)

    Select(browser.find_element(By.ID, "department")).select_by_value("Институт №3")
    Select(browser.find_element(By.ID, "course")).select_by_value(group[4:5])
    browser.find_element(By.XPATH, '//button[text()="Отобразить"]').click()
    browser.implicitly_wait(10)
    browser.find_element(By.LINK_TEXT, group).click()
    browser.implicitly_wait(10)

    result = browser.find_element(By.CSS_SELECTOR, ".step.mb-5").get_attribute('outerHTML')
    browser.quit()
    return result

def isSame(date1, date2):
    if date1.count('.') != 2 or date2.count('.') != 2:
        print("\nОшибка в сравнении дат: даты в неверном формате")
        return False
    day1, day2 = int(date1[:date1.find('.')]), int(date2[:date2.find('.')])
    month1, month2 = int(date1[date1.find('.')+1:date1.rfind('.')]), int(date2[date2.find('.')+1:date2.rfind('.')])
    year1, year2 = int(date1[date1.rfind('.')+1:]), int(date2[date2.rfind('.')+1:])
    return day1 == day2 and month1 == month2 and year1 == year2

def determineWeekNo(dates, todayDate):
    for i in range(len(dates)):
        if isInDateInterval(todayDate, dates[i]):
            return i + 1
    return -1

def isInDateInterval(todayDate, dateInterval):
    moreOrEqualToLeft = True
    lessOrEqualToRight = True
    leftBoundary = dateInterval[0]
    rightBoundary = dateInterval[1]

    if int(todayDate[todayDate.rfind('.') + 1:]) < int(leftBoundary[leftBoundary.rfind('.') + 1:]):
        moreOrEqualToLeft = False
    elif int(todayDate[todayDate.rfind('.') + 1:]) > int(leftBoundary[leftBoundary.rfind('.') + 1:]):
        moreOrEqualToLeft = True
    else:
        if todayDate.count('.') == 2:
            if int(todayDate[todayDate.find('.') + 1:todayDate.rfind('.')]) < int(leftBoundary[3:5]):
                moreOrEqualToLeft = False
            elif int(todayDate[todayDate.find('.') + 1:todayDate.rfind('.')]) > int(leftBoundary[3:5]):
                moreOrEqualToLeft = True
            else:
                if int(todayDate[:todayDate.find('.')]) < int(leftBoundary[:2]):
                    moreOrEqualToLeft = False
                else:
                    moreOrEqualToLeft = True
        else:
            if int(todayDate[:todayDate.find('.')]) < int(leftBoundary[:2]):
                moreOrEqualToLeft = False
            else:
                moreOrEqualToLeft = True

    if int(todayDate[todayDate.rfind('.') + 1:]) > int(rightBoundary[rightBoundary.rfind('.') + 1:]):
        lessOrEqualToRight = False
    elif int(todayDate[todayDate.rfind('.') + 1:]) < int(rightBoundary[rightBoundary.rfind('.') + 1:]):
        lessOrEqualToRight = True
    else:
        if todayDate.count('.') == 2:
            if int(todayDate[todayDate.find('.') + 1:todayDate.rfind('.')]) > int(rightBoundary[3:5]):
                lessOrEqualToRight = False
            elif int(todayDate[todayDate.find('.') + 1:todayDate.rfind('.')]) < int(rightBoundary[3:5]):
                lessOrEqualToRight = True
            else:
                if int(todayDate[:todayDate.find('.')]) > int(rightBoundary[:2]):
                    lessOrEqualToRight = False
                else:
                    lessOrEqualToRight = True
        else:
            if int(todayDate[:todayDate.find('.')]) > int(rightBoundary[:2]):
                lessOrEqualToRight = False
            else:
                lessOrEqualToRight = True

    return moreOrEqualToLeft and lessOrEqualToRight

def send(user_id, kbrd, msg="", attachment=""):
    for _ in range(3):
        try:
            if kbrd:
                kbrdStr = json.dumps(kbrd, ensure_ascii=False).encode("utf-8")
                kbrdStr = str(kbrdStr.decode("utf-8"))
                vk.messages.send(user_id=user_id, message=msg, attachment=attachment, random_id=0, keyboard=kbrdStr)
            else:
                vk.messages.send(user_id=user_id, message=msg, attachment=attachment, random_id=0)
        except:
            to_me = f"[id{user_id}|Ошибка отправления сообщения]"
            requests.get(f"https://api.vk.com/method/messages.send?user_id={my_id}&message={to_me}&random_id=0&access_token={dima_token}v={V}")
            sleep(0.5)
            continue
        break

def getTextFromUrl(url):
    resp = requests.get(url=url)
    if resp.status_code != 200:
        if resp.status_code not in return_codes:
            return f"Не удалось выполнить запрос, неизвестная ошибка ({resp.status_code})"
        else:
            msg = f"Не удалось выполнить запрос"
            if resp.status_code >= 500:
                msg += ": ошибка на стороне маёвского сервера"
            msg += f"\n{resp.status_code} -> {return_codes[resp.status_code]}"
            return msg
    else:
        return BeautifulSoup(resp.text, 'lxml')

def isGreeting(text):
    text = text.strip().lower()
    for greeting in greetings:
        if greeting == text:
            return True
        if greeting in text:
            before = text.find(greeting) - 1
            after = text.find(greeting) + len(greeting)
            punct = string.punctuation + ' '
            if (before == -1 or text[before] in punct) and (after == len(text) or text[after] in punct):
                return True
    return False

def isGratitude(text):
    text = text.strip().lower()
    for gratitude in gratitudes:
        if gratitude in text or text in gratitude and len(text) > 0:
            return True
    return False

def isSwearing(text):
    text = text.strip().lower()
    for swearing in swearings:
        if swearing == text:
            return True
        if swearing in text:
            before = text.find(swearing) - 1
            after = text.find(swearing) + len(swearing)
            punct = string.punctuation + ' '
            if (before == -1 or text[before] in punct) and (after == len(text) or text[after] in punct):
                return True
    return False

if __name__ == '__main__':
    while True:
        try:
            for event in longpoll.listen():
                if event.type == VkEventType.MESSAGE_NEW and event.from_user and event.to_me:
                    threading.Thread(process(event.user_id, event.text)).start()
        except requests.exceptions.ConnectionError as e:
            log_message = f"Error: {str(e)}\n{datetime.datetime.now().strftime('%d.%m.%Y, %H:%M:%S')}\n\n"
            with open("ErrorLog.txt", "a", encoding="utf-8") as file:
                file.write(log_message)
            print(log_message)
            sleep(1)
        except requests.exceptions.ReadTimeout as e:
            log_message = f"Error: {str(e)}\n{datetime.datetime.now().strftime('%d.%m.%Y, %H:%M:%S')}\n\n"
            with open("ErrorLog.txt", "a", encoding="utf-8") as file:
                file.write(log_message)
            print(log_message)
            sleep(1)