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

"""
Exemplo: Master Script - Working
    1- For a DNA
    python3.7 CRISPRloci_standalone.py -f Example/NC_005230.fasta -st dna -out results

    2- For a Proteins
    python3.7 CRISPRloci_standalone.py -f Example/NC_005230_proteins.fasta -st protein -out results

    3- For a Virus
    python3.7 CRISPRloci_standalone.py -f Example/NC_034623.fasta -st virus -out results
"""

import argparse
import warnings
import pandas as pd
import numpy as np
import glob
import os
import sys
from os import listdir
from os.path import isfile, join, basename, abspath
from Bio import SeqIO
warnings.filterwarnings('ignore')

#############################################################################
#############################################################################

import pathlib

# get location of this file for accessing auxiliary data and subscripts
mainPath = str(pathlib.Path(__file__).parent.absolute())


#############################################################################
#############################################################################


def crispr_cas_identifier():
    """
    This function runs the CRISPRcasIdentifier tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        dirname_crisp, predictions_crisp, hmm, cassette:
        Returns the result paths to future functions.
    """

    cur_path = str(pathlib.Path().absolute())
    dirname_crisp = folder_main + '/tmp/output-CRISPRidentifier'
    if folder_main[0] != '/':
        dirname_crisp = cur_path + '/' + dirname_crisp
    fasta = args.fasta_file
    if fasta[0] != '/':
        fasta = cur_path + '/' + fasta
    predictions_crisp = dirname_crisp + '/' + 'predictions.csv'
    hmm = dirname_crisp + '/' + 'hmmsearch'
    cassette = dirname_crisp + '/' + 'cassette'
    os.system('python3.7 ' + mainPath + '/CRISPRcasIdentifier/CRISPRcasIdentifier.py -f ' + fasta + ' -r '
              + ''.join(map(str, args.regressors)) + ' -c ' + ''.join(map(str, args.classifiers)) +
              ' -s ' + ''.join(map(str, args.hmm_sets)) + ' -ho ' + hmm + ' -co ' + cassette + ' -st ' +
              args.sequence_type + ' -sc ' + args.sequence_completeness + ' -m ' + args.run_mode + ' -o ' +
              predictions_crisp)

    re_dirname_crisp = summ + '/summary_cas_identifier.csv'
    summary_crisp = summ + '/summary_crisp.csv'
    if len(os.listdir(cassette)) != 0:
        summary_crisp_dna(cassette + '/HMM2019_cassettes.csv', predictions_crisp, re_dirname_crisp, summary_crisp)
    return re_dirname_crisp, summary_crisp


def crispr_cas_identifier_protein():
    """
    This function runs the CRISPRcasIdentifier tool directly on the protein files
    Generated by the Casboundary tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        re_dirname_crisp: Returns the result paths to future functions.
    """

    cas_id = 1
    re_dirname_crisp = summ + '/summary_cas_identifier.csv'
    dir_fastas = glob.glob(dirname_cas + '/predictions' + '/*.fasta')\
                 + glob.glob(dirname_cas + '/predictions' + '/*.fa')\
                 + glob.glob(dirname_cas + '/predictions' + '/*.fna')\
                 + glob.glob(dirname_cas + '/predictions' + '/*.faa')
    dir_fastas.sort()
    for fasta in dir_fastas:
        dirname_crisp = folder_main + '/tmp/output-CRISPRcasIdentifier/Cassette' + str(cas_id)
        predictions_crisp = dirname_crisp + '/' + 'predictions.csv'
        hmm = dirname_crisp + '/' + 'hmmsearch'
        cassette = dirname_crisp + '/' + 'cassette'
        os.system('python3.7 ' + mainPath + '/CRISPRcasIdentifier/CRISPRcasIdentifier.py -f ' + fasta + ' -r '
                  + ''.join(map(str, args.regressors)) + ' -c ' + ''.join(map(str, args.classifiers)) +
                  ' -s ' + ''.join(map(str, args.hmm_sets)) + ' -ho ' + hmm + ' -co ' + cassette + ' -st ' +
                  'protein' + ' -sc ' + args.sequence_completeness + ' -m ' + args.run_mode + ' -o ' +
                  predictions_crisp)
        summary_casidentifier_protein(cassette + '/HMM2019_cassettes.csv', predictions_crisp, re_dirname_crisp, cas_id)
        cas_id += 1
        # clean up hmmsearch output files
        os.system('rm -rf ' + hmm)
    return re_dirname_crisp


def crispr_identify():
    """
    This function runs the CRISPRidentify tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        dirname_identify: Returns the result paths to future functions.
    """

    cur_path = str(pathlib.Path().absolute())
    dirname_identify = folder_main + '/tmp/output-CRISPRidentify'
    if folder_main[0] != '/':
        dirname_identify = cur_path + '/' + dirname_identify
    fasta = args.fasta_file
    if fasta[0] != '/':
        fasta = cur_path + '/' + fasta
    os.chdir(mainPath + '/CRISPRidentify/')
    os.system('python3.7 CRISPRidentify.py --file ' + fasta + ' --model ' + args.model +
              ' --result_folder ' + dirname_identify +
              ' --strand ' + str(args.strand) +
              ' --cas ' + str(args.cas) + ' --is_element ' + str(args.is_element) +
              ' --parallel ' + str(args.parallel) + ' --cpu ' + str(args.n_cpus) +
              ' --fast_run ' + str(args.fast_run) + ' --degenerated ' + str(args.degenerated) +
              ' --min_len_rep ' + str(args.min_len_rep) +
              ' --max_len_rep ' + str(args.max_len_rep) +
              ' --min_len_spacer ' + str(args.min_len_spacer) +
              ' --max_len_spacer ' + str(args.max_len_spacer) +
              ' --min_repeats ' + str(args.min_repeats) +
              ' --enhancement_max_min ' + str(args.enhancement_max_min) +
              ' --enhancement_start_end ' + str(args.enhancement_start_end) +
              ' --max_identical_spacers ' + str(args.max_identical_spacers) +
              ' --max_identical_cluster_spacers ' + str(args.max_identical_cluster_spacers) +
              ' --margin_degenerated ' + str(args.margin_degenerated) +
              ' --max_edit_distance_enhanced ' + str(args.max_edit_distance_enhanced))
    os.chdir(cur_path)
    return dirname_identify


def casboundary():
    """
    This function runs the Casboundary tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        dirname_cas, hmm_cas: Returns the result paths to future functions.
    """

    cur_path = str(pathlib.Path().absolute())
    dirname_cas = folder_main + '/tmp/output-Casboundary'
    if folder_main[0] != '/':
        dirname_cas = cur_path + '/' + dirname_cas
    hmm_cas = dirname_cas + '/hmmsearch_output_dir'
    fasta = args.fasta_file
    if fasta[0] != '/':
        fasta = cur_path + '/' + fasta
    os.chdir(mainPath + '/Casboundary/')
    os.system('pwd; python3.7 Casboundary.py -f ' + fasta + ' -c '
              + args.sequence_completeness + ' -o ' + dirname_cas +
              ' -n ' + str(args.n_cpus) + ' -g ' + str(args.max_gap) + ' -m '
              + args.model_cas + ' -ho ' + hmm_cas + ' -st ' + args.sequence_type)
    os.chdir(cur_path)
    return dirname_cas, hmm_cas


def crispr_accuracy():
    """
    This function runs the CRISPRAccuracy tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        folder_crispr_acc: Returns the result paths to future functions.
    """

    path_to_structure = os.path.join(results_identify, 'ConsensusRepeat_Struct?.fa')
    fix_path = results_identify + "/"

    number_of_files = len(glob.glob(path_to_structure))
    folder_crispr_acc = folder_main + '/tmp/output-CRISPRAccuracy'
    if number_of_files > 0:
        os.system('mkdir ' + folder_crispr_acc)
        for i in range(1, number_of_files + 1):
            cmd = 'perl -I "' + mainPath + '/CRISPRAccuracy" ' + \
                    mainPath + '/CRISPRAccuracy/CRISPR_structure_accuracies.pl --DR ' \
                    + fix_path + 'ConsensusRepeat' + str(i) + '.fa -a ' \
                    + fix_path + 'CRISPR_' + str(i) + '.fa --str-f DB --msfas ' \
                    + fix_path + 'ConsensusRepeat_Struct' + str(i) + '.fa'

            os.system(cmd)
            # copy data to final result directory
            if os.path.exists('CRISPR_Structure/') is True:
                os.system('mv CRISPR_Structure CRISPR_Structure_' + str(i))
                # generate Secondary structure
                os.system(
                    'python3.7 ' + mainPath + '/CRISPRAccuracy/rnastructure.py -i ' + folder_main + '/tmp/output-CRISPRidentify/*/ConsensusRepeat_Struct' + str(
                        i) +
                    '.fa -o CRISPR_Structure_' + str(i) + '/ConsensusStructure.png')

                folder_result = os.path.join(f'{folder_main}', f'CRISPR_Structure_{i}')
                os.system(f'mv CRISPR_Structure_{i} {folder_result}')

    return folder_crispr_acc


def crispr_strand():
    """
    This function runs the CRISPRstrand tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        dirname_strand: Returns the result paths to future functions.
    """

    cur_path = str(pathlib.Path().absolute())
    dirname_strand = folder_main + '/tmp/output-CRISPRstrand'
    if folder_main[0] != '/':
        dirname_strand = cur_path + '/' + dirname_strand
    fasta = args.fasta_file
    if fasta[0] != '/':
        fasta = cur_path + '/' + fasta
    os.system('mkdir ' + dirname_strand)
    os.chdir(mainPath + '/CRISPRstrand/')
    os.system('python3.7 CRISPRstrand.py -i ' + fasta + ' -type')
    if os.path.exists('Results/CRISPRstrand_Summary.tsv') is True:
        os.system('mv Results/CRISPRstrand_Summary.tsv' + ' ' + dirname_strand)
    os.chdir(cur_path)
    if os.path.exists(dirname_strand + '/CRISPRstrand_Summary.tsv') is True:
        os.system('cp ' + dirname_strand + '/CRISPRstrand_Summary.tsv' +
                  ' ' + summ + '/CRISPRstrand_Summary.tsv')
    return dirname_strand


def crispr_virus():
    curPath = pathlib.Path().absolute()
    dirname_virus = folder_main + '/tmp/output-CRISPRVirus'
    if folder_main[0] != '/' :
        dirname_virus = curPath + '/'  + dirname_virus
    fasta = args.fasta_file
    # correct relative paths
    if fasta[0] != '/' :
        fasta = curPath + '/'  + fasta

    os.chdir(mainPath + '/CRISPRVirusSpacers/')
    #os.system('python3.7 CRISPRVirusSpacers.py -in ' + fasta + ' -out ' + dirname_virus)
    os.system('python3.7 CRISPRVirusSpacers.py -in ' + fasta +  ' -e_cutoff_s ' + str(args.e_cutoff_s) + ' -out ' + dirname_virus)
    os.chdir(curPath)

    dirname_virus = folder_main + '/tmp/output-CRISPRVirus'
    if os.path.exists(dirname_virus) is True:
        os.system('cp ' + dirname_virus + '/Spacer_matched.fasta' + ' ' + summ + '/Spacer_matched.fasta')
        os.system('cp ' + dirname_virus + '/Summary_VirusResults.csv' + ' ' + summ + '/Summary_VirusResults.csv')
        os.system('cp ' + dirname_virus + '/Full_HostVirst_Interaction_Results.csv' + ' ' + summ + '/Full_HostVirst_Interaction_Results.csv')
    else:
        print('This file does not exist: ' + dirname_virus)
    return dirname_virus


def crispr_repeat_similarity():
    curPath = str(pathlib.Path().absolute())
    dirname_repeat = folder_main + '/tmp/output-CRISPRrepeatSimilarity'
    if folder_main[0] != '/' :
        dirname_repeat = curPath + '/' + dirname_repeat
    fasta = args.fasta_file
    # correct relative paths
    if fasta[0] != '/' :
        fasta = curPath + '/' + fasta
    os.chdir(mainPath + '/CRISPRrepeatSimilarity/')
    #os.system('python3.7 CRISPRrepeatSimilarity.py -i ' + fasta + ' -out ' + dirname_repeat)
    os.system('python3.7 CRISPRrepeatSimilarity.py -i ' + fasta + ' -e_cutoff ' + str(args.e_cutoff_r) + ' -out ' + dirname_repeat)
    os.chdir(curPath)
    if os.path.exists(dirname_repeat) is True:
        os.system('cp ' + dirname_repeat + '/RepeatSimilaritiesTable.tab' + ' ' + summ + '/RepeatSimilaritiesTable.tab')
    else:
        print('This file does not exist: ' + dirname_repeat)
    return dirname_repeat


def crispr_self_target():
    """
    This function runs the CRISPRselfTarget tool.

    Parameters:
        The tool's parameters can be seen from the line parser = argparse.ArgumentParser()

    Returns:
        dirname_target: Returns the result paths to future functions.
    """

    cur_path = str(pathlib.Path().absolute())
    dirname_target = folder_main + '/tmp/output-CRISPRselfTarget'
    if folder_main[0] != '/':
        dirname_target = cur_path + '/' + dirname_target
    fasta = args.fasta_file
    if fasta[0] != '/':
        fasta = cur_path + '/' + fasta
    if os.path.exists(fasta) is True:
        if os.path.exists(summ + '/annotations_details.tab') is True:
            if os.path.exists(summ + '/Bona-Fide_Candidates.txt') is True:
                os.chdir(mainPath + '/CRISPRselfTarget/')
                print(os.getcwd())
                os.system('python3.7 CRISPRSelftarget.py -i ' + fasta + ' -a '
                          + summ + '/annotations_details.tab' + ' -tdir '
                          + summ + ' -out ' + dirname_target)
    os.chdir(cur_path)
    if os.path.exists(dirname_target + '/Summary_Selftarget_Results.tab') is True:
        os.system('cp ' + dirname_target + '/Summary_Selftarget_Results.tab' + ' '
                  + summ + '/Summary_Selftarget_Results.tab')
    return dirname_target


def summary_crisp_dna(cassettes, predictions_file, output_cas, output_crisp):
    """
    This function generated the result summary for the crispr_cas_identifier function.

    Important: This function is not used, only backup.

    Parameters:
        cassettes: cassette path
        predictions_file: crispr_cas_identifier_protein predictions
        output_cas: Output in csv - Casidentifier
        output_crisp: Output in csv - Casidentifier + HMM

    Returns:
        summary_crisp.csv: Returns the result summary - Casidentifier.
    """

    df_casset = pd.read_csv(cassettes)
    df_pred = pd.read_csv(predictions_file)
    df_summary = df_casset.drop(['bitscore'], axis=1)
    df_summary['strand'] = np.where(df_summary['strand'] == 1, 'forward', 'reverse')
    df_summary.rename(columns={'Unnamed: 0': 'nameseq'}, inplace=True)
    df_summary.rename(columns={'start': 'Start'}, inplace=True)
    df_summary.rename(columns={'end': 'End'}, inplace=True)
    df_summary.rename(columns={'strand': 'Strand'}, inplace=True)
    df_summary.rename(columns={'annotation': 'CasType'}, inplace=True)
    predictions = []
    for index, row in df_casset.iterrows():
        predictions.append(df_pred.loc[df_pred['cassette_id'] == row['cassette_id'],
                                       'predicted_label'].to_string(index=False))
    df_summary.insert(6, 'predicted_label', predictions)
    df_summary.insert(7, 'Module', 'None')
    df_cas = df_summary[['nameseq', 'cassette_id', 'predicted_label']]
    df_cas.to_csv(output_cas, index=False)
    df_summary.to_csv(output_crisp, index=False)


def summary_crisp_dna_v2(re_dirname_crisp, summary_cas, output_crisp):
    cas_identifier = pd.read_csv(re_dirname_crisp, names=['nameseq', 'cassette_id', 'predicted_label'])
    cas_bound = pd.read_csv(summary_cas)
    cassette_id = []
    predicted_label = []
    for index, row in cas_bound.iterrows():
        cassette_id.append(cas_identifier.loc[cas_identifier['nameseq'] == row['ProteinId'],
                                              'cassette_id'].to_string(index=False))
        predicted_label.append(cas_identifier.loc[cas_identifier['nameseq'] == row['ProteinId'],
                                                  'predicted_label'].to_string(index=False))
    cas_bound.insert(1, 'cassette_id', cassette_id)
    cas_bound.insert(2, 'predicted_label', predicted_label)
    summary_crisp = cas_bound[['ProteinId', 'Start', 'End', 'Strand', 'CasType',
                               'cassette_id', 'predicted_label', 'Module']]
    summary_crisp['Strand'] = np.where(summary_crisp['Strand'] == 1, 'forward', 'reverse')
    summary_crisp.rename(columns={'ProteinId': 'nameseq'}, inplace=True)
    summary_crisp['predicted_label'] = summary_crisp['predicted_label'].str.split('CAS-').str[1]
    return summary_crisp.to_csv(output_crisp, index=False)


def summary_casidentifier_protein(cassettes, predictions_file, output_crisp, cas_id):
    df_casset = pd.read_csv(cassettes)
    df_pred = pd.read_csv(predictions_file)
    df_summary = df_casset.drop(['bitscore', 'annotation'], axis=1)
    df_summary.rename(columns={'Unnamed: 0': 'nameseq'}, inplace=True)
    predictions = []
    for index, row in df_casset.iterrows():
        predictions.append(df_pred.loc[df_pred['cassette_id'] == row['cassette_id'],
                                       'predicted_label'].to_string(index=False))
    df_summary.insert(2, 'predicted_label', predictions)
    df_summary['cassette_id'] = df_summary['cassette_id'].replace(to_replace=1, value=cas_id)
    df_summary.to_csv(output_crisp, mode='a', header=False, index=False)
    return


def results_summary_casboundary(dirname_cas, output_casboud):
    if len(glob.glob(dirname_cas + "/*.csv")) > 0:
        cas_summary = pd.DataFrame()
        sequences = {}
        for file in glob.glob(dirname_cas + "/*.csv"):
            df = pd.read_csv(file)
            cas_summary = pd.concat([cas_summary, df])
        dir_fastas = glob.glob(dirname_cas + '/*.fasta')\
                     + glob.glob(dirname_cas + '/*.fa')\
                     + glob.glob(dirname_cas + '/*.fna')\
                     + glob.glob(dirname_cas + '/*.faa')
        dir_fastas.sort()
        for file in dir_fastas:
            for seq_record in SeqIO.parse(file, 'fasta'):
                seq = seq_record.seq
                seq = seq.upper()
                name_seq = seq_record.name
                sequences[name_seq] = seq
        length = []
        protein = []
        for index, row in cas_summary.iterrows():
            protein_id = ''.join(map(str, sequences[row['ProteinId']]))
            protein.append(protein_id)
            length.append(len(sequences[row['ProteinId']]))
        cas_summary.insert(7, 'length_protein', length)
        cas_summary.insert(8, 'protein', protein)
        cas_summary.to_csv(output_casboud, index=False)
    else:
        print('This file does not exist: ' + dirname_cas)
    return


def cas_tab_function(summary_crisp, dirname_cas, output_cas_tab):
    print("summary crispr", summary_crisp)
    print("dirname cas", dirname_cas)

    s_crisp = pd.read_csv(summary_crisp)
    sequences = {}
    dir_fastas = glob.glob(dirname_cas + '/*.fasta') \
                 + glob.glob(dirname_cas + '/*.fa') \
                 + glob.glob(dirname_cas + '/*.fna') \
                 + glob.glob(dirname_cas + '/*.faa')
    dir_fastas.sort()
    for file in dir_fastas:
        for seq_record in SeqIO.parse(file, 'fasta'):
            seq = seq_record.seq
            seq = seq.upper()
            name = seq_record.name
            # description = seq_record.description.split(' ')
            # id_seq = description[8].split(';')
            # name = seq_record.name + '_' + id_seq[0]
            sequences[name] = seq
    length = []
    protein = []
    new_nameseq = []
    id_prot = []
    for index, row in s_crisp.iterrows():
        protein.append(''.join(map(str, sequences[row['nameseq']])))
        length.append(len(sequences[row['nameseq']]))
        name = row['nameseq'].split('_ID')
        prot = row['nameseq'].split('_')
        new_nameseq.append(name[0])
        id_prot.append('Prot' + str(prot[-1]))
    s_crisp.insert(7, 'length_protein', length)
    s_crisp.insert(8, 'protein', protein)
    s_crisp.insert(9, 'new_nameseq', new_nameseq)
    s_crisp.insert(10, 'id_prot', id_prot)
    s_crisp['predicted_label'] = s_crisp['predicted_label'].str.strip()
    cas_tab_results = s_crisp[['id_prot', 'new_nameseq', 'CasType', 'predicted_label', 'Module', 'cassette_id', 'Strand',
                               'Start', 'End', 'length_protein', 'protein']]
    cas_tab_results['Strand'] = np.where(cas_tab_results['Strand'] == 'forward', 'plus', 'minus')
    cas_tab_results = cas_tab_results.sort_values(by=['Start'])
    return cas_tab_results.to_csv(output_cas_tab, sep=';', header=False, index=False)


def cas_tab_gff(re_dirname_crisp, output_cas_tab, output_cas_gff):
    """
    This function generated the cas_results.gff file - DNA mode.

    Parameters:
        re_dirname_crisp: crispr_cas_identifier_protein() function - return
        output_cas_tab: cas_results.tab - cas_tab_function function
        output_cas_gff: Output in csv

    Returns:
        cas_results.gff: Returns the result summary - DNA mode.
    """

    cassete = {}
    cassete_gff = []
    cas_ident = pd.read_csv(re_dirname_crisp, names=['nameseq', 'cassette_id', 'predicted_label'])
    for index, row in cas_ident.iterrows():
        name = row['nameseq'].split('_ID')
        cassete[name[0]] = row['cassette_id']
    cas = pd.read_csv(output_cas_tab, sep=';', names=['col1', 'col2', 'col3', 'col4',
                                                      'col5', 'col6', 'col7', 'col8',
                                                      'col9', 'col10', 'col11'])
    for index, row in cas.iterrows():
        cassete_gff.append(cassete[row['col2']])
    cas.insert(11, 'col12', cassete_gff)
    cas['col13'] = cas['col3'].astype(str) + '_' + cas['col5'].astype(str)
    cas['col14'] = 'ID=' + cas['col3'].astype(str) + '-' + cas['col1'].astype(str) \
                   + ';Note=' + cas['col5'].astype(str)\
                   + ';Dbxref=SO:0001459;Ontology_term=Cassette' + cas['col12'].astype(str)
    cas_gff = cas[['col2', 'col13', 'col8', 'col9', 'col10', 'col7', 'col14']]
    cas_gff.insert(1, 'CRISPRloci', 'CRISPRloci')
    cas_gff.insert(7, '-', '.')
    cas_gff['col7'] = np.where(cas_gff['col7'] == 'plus', '+', '-')
    return cas_gff.to_csv(output_cas_gff, sep='\t', header=False, index=False)


def cas_tab_function_protein(summary_cas, re_dirname_crisp, output_cas_tab):
    """
    This function generated the Summary_protein.csv file - Protein mode.

    Parameters:
        summary_cas: summary_casboundary.csv - results_summary_casboundary() function
        re_dirname_crisp: crispr_cas_identifier_protein() function - return
        output_cas_tab: Output in csv

    Returns:
        Summary_protein.csv: Returns the result summary - Protein mode.
    """

    casboundary = pd.read_csv(summary_cas)
    cas_identifier = pd.read_csv(re_dirname_crisp, names=['nameseq', 'cassette_id', 'predicted_label'])
    cassette_id = []
    predicted_label = []
    protein_id = []
    for index, row in casboundary.iterrows():
        cassette_id.append(cas_identifier.loc[cas_identifier['nameseq'] == row['ProteinId'],
                                              'cassette_id'].to_string(index=False))
        predicted_label.append(cas_identifier.loc[cas_identifier['nameseq'] == row['ProteinId'],
                                                  'predicted_label'].to_string(index=False))
    for index, row in casboundary.iterrows():
        name_elements = row['ProteinId'].split('_')
        if len(name_elements) > 2:
            protein_id.append(name_elements[2])
        else:
            protein_id.append(str(index+1))

    casboundary.insert(1, 'cassette_id', cassette_id)
    casboundary.insert(2, 'predicted_label', predicted_label)
    casboundary.insert(3, 'protein_id', protein_id)
    casboundary['predicted_label'] = casboundary['predicted_label'].str.strip()
    cas_tab_results = casboundary[['protein_id', 'ProteinId', 'cassette_id', 'CasType', 'predicted_label',
                                   'Module', 'length_protein', 'protein']]
    cas_tab_results['protein_id'] = pd.to_numeric(cas_tab_results['protein_id'], errors='coerce')
    cas_tab_results = cas_tab_results.sort_values(by=['protein_id'])
    cas_tab_results['protein_id'] = 'Prot' + cas_tab_results['protein_id'].astype(str)
    file = open(output_cas_tab, 'a')
    file.write('ID,InputId,cassetteID,CasType,Subtype,Module,length_protein,protein')
    file.write('\n')
    return cas_tab_results.to_csv(file, mode='a', sep=',', header=False, index=False)


def annotations_details(fasta_file, summary_crisp_input, crisp_candidates, output_annotation):
    """
    This function generated the annotations_details.tab file - DNA mode.

    Parameters:
        fasta_file: fasta file - input
        summary_crisp_input: summary_crisp.csv - summary_crisp_dna_v2() function
        crisp_candidates: summary_identify.csv - generated by the CRISPRidentify tool
        output_annotation: Output in tab

    Returns:
        annotations_details.tab: Returns the result summary - DNA mode.
    """

    header = []
    ################################
    if os.path.exists(fasta_file) is True:
        for seq_record in SeqIO.parse(fasta_file, 'fasta'):
            header.append(seq_record.description)
            header.append(len(seq_record.seq))
    # else:
        # print('This file does not exist: ' + fasta_file)
    ################################
    if os.path.exists(summary_crisp_input) is True or os.path.exists(crisp_candidates) is True:
        file = open(output_annotation, 'a')
        file.write('#' + str(header[0]))
        file.write('\n')
        file.write('%' + str(header[1]))
        file.write('\n')
        file.write('!strand' + '\t\t' + 'slot' + '\t\t' + 'start' + '\t\t' + 'stop' + '\t\t' + 'type' +
                   '\t\t' + 'label' + '\t\t' + 'mouseover' + '\t\t' + 'hyperlink')
        file.write('\n')
        file.close()
    # else:
        # print('This files does not exist: ' + crisp_candidates + ', ' + crisp_candidates)
    ################################
    if os.path.exists(crisp_candidates) is True:
        crisp = pd.read_csv(crisp_candidates)
        if str(args.strand) == 'False':
            crisp['Strand'] = np.where(crisp['Strand'] == 'Reverse', 'Forward', 'Forward')
            crisp['Slot'] = np.where(crisp['Strand'] == 'Forward', 1, 2)
        else:
            crisp['Slot'] = np.where(crisp['Strand'] == 'Forward', 1, 2)
        crisp.insert(4, 'Type', 'CRISPR')
        crisp['ID'] = crisp['ID'].astype(str)
        new_label = []
        for index, row in crisp.iterrows():
            if row['Category'] == 'Bona-fide':
                new_label.append(row['Type'] + row['ID'])
            elif row['Category'] == 'Alternative':
                new_label.append('Alternative')
            else:
                new_label.append(row['Category'] + '(' + row['Type'] + row['ID'] + ')')
        crisp_anot = crisp[['Strand', 'Slot', 'Start', 'End', 'Type']]
        crisp_anot['Strand'] = np.where(crisp_anot['Strand'] == 'Forward', 'forward', 'reverse')
        crisp_anot.insert(5, 'label', new_label)
        crisp_anot.insert(6, 'mouseover', '-')
        crisp_anot.insert(7, 'hyperlink', '-')
        crisp_anot = crisp_anot[crisp_anot['label'] != 'Alternative']
        with open(output_annotation, 'a') as file:
            for index, row in crisp_anot.iterrows():
                file.write('\t\t'.join(map(str, row.values)))
                file.write('\n')
        # crisp_anot.to_csv(file, sep='\t', mode='a', header=False, index=False)
    # else:
        # print('This file does not exist: ' + crisp_candidates)
    ################################
    if os.path.exists(summary_crisp_input) is True:
        summary_crisp = pd.read_csv(summary_crisp_input)
        summary_crisp['slot'] = np.where(summary_crisp['Strand'] == 'forward', 1, 2)
        annotations = summary_crisp[['Strand', 'slot', 'Start', 'End', 'CasType']]
        summary_crisp['predicted_label'] = summary_crisp['predicted_label'].str.strip()
        new_nameseq = []
        for index, row in summary_crisp.iterrows():
            name = row['nameseq'].split('_')
            new_nameseq.append('Prot' + str(name[-1]))
        summary_crisp.insert(1, 'new_nameseq', new_nameseq)
        annotations.insert(5, 'predicted_label', summary_crisp['CasType'] + '_'
                           + summary_crisp['predicted_label']
                           + '(' + summary_crisp['new_nameseq'] + ')')
        annotations.insert(6, 'mouseover', '-')
        annotations.insert(7, 'hyperlink', '-')
        annotations = annotations.sort_values(by=['Start'])
        with open(output_annotation, 'a') as file:
            for index, row in annotations.iterrows():
                file.write('\t\t'.join(map(str, row.values)))
                file.write('\n')
        # annotations.to_csv(file, sep='\t', mode='a', header=False, index=False)
    # else:
        # print('This file does not exist: ' + summary_crisp_input)
    ################################
    return


def annotations_details_virus(fasta_file, results_virus_one, results_virus_two, output_annotation):
    header = []
    #############################################
    if os.path.exists(fasta_file) is True:
        for seq_record in SeqIO.parse(fasta_file, 'fasta'):
            header.append(seq_record.description)
            header.append(len(seq_record.seq))
    else:
        print('This file does not exist: ' + fasta_file)
    #############################################
    if os.path.exists(results_virus_one) is True:
        file = open(output_annotation, 'a')
        file.write('#' + str(header[0]))
        file.write('\n')
        file.write('%' + str(header[1]))
        file.write('\n')
        file.write('!strand' + '\t\t' + 'slot' + '\t\t' + 'start' + '\t\t' + 'stop' + '\t\t' + 'type' +
                   '\t\t' + 'label' + '\t\t' + 'mouseover' + '\t\t' + 'hyperlink')
        file.write('\n')
        summary_virus = pd.read_csv(results_virus_one)
        spacer = []
        i = 1
        for index, row in summary_virus.iterrows():
            spacer.append('Protospacer' + str(i))
            i += 1
        #summary_virus = summary_virus.sort_values(by=['TARGET_ALIGN_START'])
        summary_virus = summary_virus.sort_values(by=['Target.Align.Start'])
        summary_virus.insert(1, 'ID', spacer)
        summary_virus.insert(2, 'SpacerID', summary_virus['SpacerId'])
        #summary_virus.insert(2, 'SPACERS_ID', summary_virus['SPACER_ID'])

        #summary_virus = summary_virus.drop(['SPACER_ID'], axis=1)
        summary_virus = summary_virus.drop(['SpacerId'], axis=1)

        summary_virus.to_csv(results_virus_one, index=False)
    ##############################################
        #summary_virus['slot'] = np.where(summary_virus['SPACER_ORI_ALIGN'] == 'plus', 1, 2)
        #annotations = summary_virus[['SPACER_ORI_ALIGN', 'slot', 'TARGET_ALIGN_START', 'TARGET_ALIGN_END']]
        #annotations = annotations.sort_values(by=['TARGET_ALIGN_START'])

        summary_virus['slot'] = np.where(summary_virus['Spacer.Strand.Align'] == 'plus', 1, 2)
        annotations = summary_virus[['Spacer.Strand.Align', 'slot', 'Target.Align.Start', 'Target.Align.End']]
        annotations = annotations.sort_values(by=['Target.Align.Start'])

        annotations.insert(4, 'type', 'Spacer')
        annotations.insert(5, 'id', spacer)
        annotations.insert(6, 'mouseover', '-')
        annotations.insert(7, 'hyperlink', '-')

        #annotations['SPACER_ORI_ALIGN'] = np.where(annotations['SPACER_ORI_ALIGN'] == 'plus', 'forward', 'reverse')
        annotations['Spacer.Strand.Align'] = np.where(annotations['Spacer.Strand.Align'] == 'plus', 'forward', 'reverse')

        with open(output_annotation, 'a') as file:
            for index, row in annotations.iterrows():
                file.write('\t\t'.join(map(str, row.values)))
                file.write('\n')
        # annotations.to_csv(file, sep='\t', mode='a', header=False, index=False)
    else:
        print('This file does not exist: ' + results_virus_one)
    #############################################
    if os.path.exists(results_virus_two) is True:
        summary_virus_two = pd.read_csv(results_virus_two)
        spacer = []
        i = 1
        for index, row in summary_virus_two.iterrows():
            spacer.append('Protospacer' + str(i))
            i += 1
        #summary_virus_two = summary_virus_two.sort_values(by=['TARGET_ALIGN_START'])
        summary_virus_two = summary_virus_two.sort_values(by=['Target.Align.Start'])

        summary_virus_two.insert(0, 'ID', spacer)
        # summary_virus_two.insert(2, 'SPACERS_ID', summary_virus['SPACER_ID'])
        # summary_virus_two = summary_virus.drop(['SPACER_ID'], axis=1)
        summary_virus_two.to_csv(results_virus_two, index=False)
    else:
        print('This file does not exist: ' + results_virus_two)
    #############################################
    return


def main_summary_function_bkp(summary_crisp_input, crisp_candidates, output_main_summary):
    if os.path.exists(summary_crisp_input) is True:
        if os.path.exists(crisp_candidates) is True:
            label_identifier = {}
            subtype = []
            summary_identifier = pd.read_csv(summary_crisp_input)
            summary_identify = pd.read_csv(crisp_candidates)
            for index, row in summary_identifier.iterrows():
                label_identifier[row['cassette_id']] = row['predicted_label']
            for index, row in summary_identify.iterrows():
                subtype.append(label_identifier[row['ID']])
            summary_identify.insert(10, 'subtype', subtype)
            summary_identify['subtype'] = summary_identify['subtype'].str.strip()
            summary_identify = summary_identify.sort_values(by=['ID'])
            summary_identify['ID'] = 'CRISPR' + summary_identify['ID'].astype(str)
            summary_identify.to_csv(output_main_summary, sep=',', index=False)
        else:
            print('This file does not exist: ' + crisp_candidates)
    else:
        if os.path.exists(crisp_candidates) is True:
            summary_identify = pd.read_csv(crisp_candidates)
            summary_identify.insert(10, 'subtype', 'None')
            summary_identify = summary_identify.sort_values(by=['ID'])
            summary_identify['ID'] = 'CRISPR' + summary_identify['ID'].astype(str)
            summary_identify.to_csv(output_main_summary, sep=',', index=False)
        else:
            print('This file does not exist: ' + crisp_candidates)
    return


def main_summary_function(summary_crisp_input, crisp_candidates, output_main_summary):
    if os.path.exists(summary_crisp_input) is True:
        if os.path.exists(crisp_candidates) is True:
            subtype = []
            summary_identify = pd.read_csv(crisp_candidates)
            summary_identifier = pd.read_csv(summary_crisp_input)
            groups_identifier = summary_identifier.groupby('predicted_label').agg(
                min_Start=pd.NamedAgg(column='Start', aggfunc='min'),
                max_End=pd.NamedAgg(column='End', aggfunc='max'))
            groups_identifier['predicted_label'] = groups_identifier.index
            for index, row in summary_identify.iterrows():
                subtype.append(groups_identifier.iloc[(groups_identifier['max_End'] - row['End']).abs().argsort()[:][0]].tolist()[2])
            summary_identify.insert(10, 'subtype', subtype)
            summary_identify['subtype'] = summary_identify['subtype'].str.strip()
            summary_identify = summary_identify.sort_values(by=['ID'])
            summary_identify['ID'] = 'CRISPR' + summary_identify['ID'].astype(str)
            summary_identify.to_csv(output_main_summary, sep=',', index=False)
        else:
            print('This file does not exist: ' + crisp_candidates)
    else:
        if os.path.exists(crisp_candidates) is True:
            summary_identify = pd.read_csv(crisp_candidates)
            summary_identify.insert(10, 'subtype', 'None')
            summary_identify = summary_identify.sort_values(by=['ID'])
            summary_identify['ID'] = 'CRISPR' + summary_identify['ID'].astype(str)
            summary_identify.to_csv(output_main_summary, sep=',', index=False)
        else:
            print('This file does not exist: ' + crisp_candidates)
    return

def generating_summaries_dna(dirname_cas, re_dirname_crisp, dirname_identify):
    """
    This function generated the summary files - DNA mode.

    Parameters:
        dirname_cas: casboundary() function - return
        re_dirname_crisp: crispr_cas_identifier_protein() function - return
        dirname_identify: crispr_identify() function - return

    Returns:
        Returns summary files - DNA mode.
    """

    path_file = args.fasta_file.split('.')
    ################################
    identify = dirname_identify + '/' + path_file[0].split('/')[-1]
    summary_identify = dirname_identify + '/' + path_file[0].split('/')[-1] + '/Summary.csv'
    ################################
    if os.path.exists(identify + '/Bona-Fide_Candidates.txt') is True:
        os.system('cp ' + identify + '/Bona-Fide_Candidates.txt' + ' '
                  + summ + '/Bona-Fide_Candidates.txt')
        os.system('cp ' + identify + '/CRISPR*' + ' '
                  + summ + '/')
        os.system('cp ' + identify + '/Spacers*' + ' '
                  + summ + '/')
    ################################
    if os.path.exists(identify + '/Alternative_Candidates.txt') is True:
        os.system('cp ' + identify + '/Alternative_Candidates.txt' + ' '
                  + summ + '/Alternative_Candidates.txt')
    ################################
    if os.path.exists(identify + '/Possible_Candidates.txt') is True:
        os.system('cp ' + identify + '/Possible_Candidates.txt' + ' '
                  + summ + '/Possible_Candidates.txt')
        os.system('cp ' + identify + '/CRISPR*' + ' '
                  + summ + '/')
        os.system('cp ' + identify + '/Spacers*' + ' '
                  + summ + '/')
    ################################
    if os.path.exists(identify + '/Possible_Discarded_Candidates.txt') is True:
        os.system('cp ' + identify + '/Possible_Discarded_Candidates.txt' + ' '
                  + summ + '/Possible_Discarded_Candidates.txt')
        os.system('cp ' + identify + '/CRISPR*' + ' '
                  + summ + '/')
        os.system('cp ' + identify + '/Spacers*' + ' '
                  + summ + '/')
    ################################
    if os.path.exists(identify + '/Low_Score_Candidates.txt') is True:
        os.system('cp ' + identify + '/Low_Score_Candidates.txt' + ' '
                  + summ + '/Low_Score_Candidates.txt')
        os.system('cp ' + identify + '/CRISPR*' + ' '
                  + summ + '/')
        os.system('cp ' + identify + '/Spacers*' + ' '
                  + summ + '/')
    ################################
    if os.path.exists(identify + '/gff_result') is True:
        os.system('cp -r ' + identify + '/gff_result' + ' ' + summ + '/gff_result')
    ################################
    if os.path.exists(summary_identify) is True:
        os.system('cp ' + summary_identify + ' ' + summ + '/summary_identify.csv')
    ################################
    results_identify = dirname_identify + '/' + path_file[0].split('/')[-1]
    ################################
    summary_cas = summ + '/summary_casboundary.csv'
    if os.path.exists(dirname_cas + '/predictions/') is True:
        results_summary_casboundary(dirname_cas + '/predictions/', summary_cas)
    ################################
    summary_crisp = summ + '/summary_crisp.csv'
    if os.path.exists(summary_cas) is True:
        summary_crisp_dna_v2(re_dirname_crisp, summary_cas, summary_crisp)
    ################################
    annotation_tab = summ + '/annotations_details.tab'
    crisp_candidates = summ + '/summary_identify.csv'
    annotations_details(args.fasta_file, summary_crisp, crisp_candidates, annotation_tab)
    return results_identify, summary_crisp, crisp_candidates


def generating_summaries_cas_result_tab_and_main():
    cas_results = summ + '/cas_results.tab'
    if os.path.exists(summary_crisp) is True:
        if os.path.exists(dirname_cas + '/predictions/') is True:
            try:
                cas_tab_function(summary_crisp, dirname_cas + '/predictions/', cas_results)
            except KeyError:
                try:
                    cas_tab_function(summary_crisp, dirname_cas, cas_results)
                except KeyError:
                    pass
        else:
            print('This file does not exist: ' + dirname_cas + '/predictions/')
    else:
        print('This file does not exist: ' + summary_crisp)

    ################################
    main_summary = summ + '/Main_summary.csv'
    main_summary_function(summary_crisp, crisp_candidates, main_summary)


def generating_cas_results_tab():
    cas_results = summ + '/cas_results.tab'
    print(summary_crisp)
    if os.path.exists(summary_crisp) is True:
        if os.path.exists(dirname_cas + '/predictions/') is True:
            try:
                print("cas result", cas_results)
                cas_tab_function(summary_crisp, dirname_cas + '/predictions/', cas_results)
            except KeyError:
                try:
                    cas_tab_function(summary_crisp, dirname_cas, cas_results)
                except KeyError:
                    pass
        else:
            print('This file does not exist: ' + dirname_cas + '/predictions/')
    else:
        print('This file does not exist: ' + summary_crisp)


def generating_summaries_protein():
    """
    This function generated the summary files - Protein mode.

    Parameters:

    Returns:
        Returns summary files - Protein mode.
    """

    ################################
    summary_cas = summ + '/summary_casboundary.csv'
    if os.path.exists(dirname_cas + '/predictions/') is True:
        results_summary_casboundary(dirname_cas + '/predictions/', summary_cas)
    ################################
    cas_results = summ + '/Summary_protein.csv'
    if os.path.exists(re_dirname_crisp) is True:
        cas_tab_function_protein(summary_cas, re_dirname_crisp, cas_results)
    ################################


def csv_long_name_one_file(path_csv_file, delimiter=",", header_present=True,
                           column_index=0, column_name=None):
    with open(path_csv_file) as f:
        lines = f.readlines()
    if header_present:
        header = lines[0].strip()
        header_elements = header.split(delimiter)
        info_lines = lines[1:]
        if column_name:
            if column_name in header_elements:
                column_index = header_elements.index(column_name)
            else:
                return
    else:
        info_lines = lines

    new_info_lines = []
    for line in info_lines:
        line_elements = line.strip().split(delimiter)
        long_element = line_elements[column_index]
        if len(long_element) > 30:
            long_element = long_element[:25] + ".." + long_element[-5:]
        line_elements = line_elements[:column_index] + [long_element] + line_elements[column_index + 1:]
        new_info_line = delimiter.join(line_elements) + "\n"
        new_info_lines.append(new_info_line)

    with open(path_csv_file, "w") as f:
        if header_present:
            f.write(header + "\n")
        f.writelines(new_info_lines)


def csv_long_name_fix(result_folder_path):
    for column_index, flag_header, file_name in zip([0, 0, 1],
                                                    [True, False, True],
                                                    ["summary_casboundary.csv", "summary_cas_identifier.csv", "Summary_protein.csv"]):
        complete_path = os.path.join(result_folder_path, file_name)
        if os.path.isfile(complete_path):
            csv_long_name_one_file(complete_path, header_present=flag_header, column_index=column_index)


def gb_into_fasta_input(gb_file):
    dict_proteins = gb_file_parser_protein(gb_file)
    dna_result = gb_file_parser_dna(gb_file)

    if dict_proteins:
        with open("protein_input.fa", "w") as f:
            for header, line in dict_proteins.items():
                translation, interval_info = line
                start, end, strand = interval_info
                header = ">" + " # ".join([str(x) for x in [header, start, end, strand]])
                f.write(f"{header}\n")
                f.write(f"{translation}\n")

    if dna_result:
        header, dna_line = dna_result
        with open("dna_input.fa", "w") as f:
            f.write(f"{header}\n")
            f.write(f"{dna_line}\n")

    return dna_result, dict_proteins


def gb_file_parser_dna(genbank_file):
    recs = [rec for rec in SeqIO.parse(genbank_file, "genbank")]
    for rec in recs:
        fasta_seq = rec.format("fasta")
        lines = fasta_seq.split("\n")
        header = lines[0]
        dna = lines[1:]
        dna = "".join(dna)
        if set(dna) != {"N"}:
            return header, dna


def gb_file_parser_protein(genbank_file):
    protein_dict = {}
    recs = [rec for rec in SeqIO.parse(genbank_file, "genbank")]
    for rec in recs:
        feats = [feat for feat in rec.features if feat.type == "CDS"]
        for feat in feats:
            locus_tag = None
            old_locus_tag = None
            protein_id = None
            translation = None
            interval_info = None

            try:
                locus_tag = feat.qualifiers["locus_tag"]
            except KeyError:
                pass

            try:
                old_locus_tag = feat.qualifiers["old_locus_tag"]
            except KeyError:
                pass

            try:
                protein_id = feat.qualifiers["protein_id"]
            except KeyError:
                pass

            try:
                translation = feat.qualifiers["translation"][0]
            except KeyError:
                pass

            try:
                location = feat.location
                start = location.start.position
                end = location.end.position
                strand = location.strand
                interval_info = start, end, strand
            except KeyError:
                pass

            headers = [locus_tag, old_locus_tag, protein_id]
            if any([locus_tag, old_locus_tag, protein_id]):
                header = next(header for header in headers if header is not None)[0]
                if translation:
                    if interval_info:
                        protein_dict[header] = translation, interval_info

    return protein_dict


def fill_in_locations_cas_boundary_summary(path_to_summary_file, dict_protein_from_gb):
    header = None
    with open(path_to_summary_file) as f:
        lines = f.readlines()
        if lines:
            header = lines[0]
            info_lines = lines[1:]
            corrected_lines = []
            for line in info_lines:
                line_elements = line.split(",")
                protein_id = line_elements[0]
                translation, interval_info = dict_protein_from_gb[protein_id]
                start, end, strand = interval_info
                line_elements[1] = str(start)
                line_elements[2] = str(end)
                line_elements[3] = str(strand)

                corrected_lines.append(",".join(line_elements))

    if header:
        with open(path_to_summary_file, "w") as f:
            f.write(header)
            f.writelines(corrected_lines)


def fill_in_locations_cas_boundary_cassetes(path_to_folder, dict_protein_from_gb):
    onlyfiles = [f for f in listdir(path_to_folder) if isfile(join(path_to_folder, f))]
    for file_name in onlyfiles:
        if ".csv" in file_name:
            full_path = join(path_to_folder, file_name)
            fill_in_locations_cas_boundary_summary(full_path, dict_protein_from_gb)


def move_to_results(file_path, result_path):
    base_name = basename(file_path)
    destination = join(result_path, base_name)
    cmd = f"cp {file_path} {destination}"
    os.system(cmd)
    return destination


def split_input_fasta(input_fasta_path):
    folder_path = input_fasta_path.split(basename(input_fasta_path))[0]
    with open(input_fasta_path) as f:
        lines_fasta = f.readlines()

    header_indexes = [index for index, line in enumerate(lines_fasta) if line[0] == ">" ]
    if len(header_indexes) != 3:
        print("You provided incomplete fasta file")
        raise ValueError
    else:
        chunks = split_into_chunks(lines_fasta)
        dna, repeat, repeat_and_structure = split_by_category(chunks)
        repeat_seq, structure_seq = split_seq_from_structure(repeat_and_structure)
        if not all([dna, repeat, repeat_and_structure]):
            print("Did not split the input file into chunks")
            raise ValueError

    with open(f"{folder_path}Accuracy_independent_DNA.fa", "w") as f:
        f.write(">DNA\n")
        f.write("".join([l.strip() for l in dna]))

    with open(f"{folder_path}Accuracy_independent_repeat.fa", "w") as f:
        f.write(">repeat\n")
        f.write("".join([l.strip() for l in repeat]))

    with open(f"{folder_path}Accuracy_independent_repeat_structure.fa", "w") as f:
        f.write(">repeat_and_structure\n")
        f.write("".join([l.strip() for l in repeat_seq]) + "\n")
        f.write("".join([l.strip() for l in structure_seq]))


def split_into_chunks(lines_fasta):
    header_indexes = [index for index, line in enumerate(lines_fasta) if line[0] == ">"]
    chunks = []
    chunk = []
    for index, line in enumerate(lines_fasta):
        if index in header_indexes:
            if chunk:
                chunks.append(chunk)
                chunk = []
        else:
            chunk.append(line)
    if chunk:
        chunks.append(chunk)

    return chunks


def split_by_category(chunks):
    for chunk in chunks:
        if not chunk:
            raise ValueError

    dna = None
    repeat = None
    repeat_and_structure = None
    string_chunks = ["".join([l.strip() for l in chunk]) for chunk in chunks]
    for chunk, string_chunk in zip(chunks, string_chunks):
        if set(string_chunk).intersection({".", "(", ")"}):
            repeat_and_structure = chunk
        else:
            if not dna:
                dna = chunk
            else:
                len_dna = len("".join(x.strip() for x in dna))
                len_current = len(string_chunk)
                if len_dna > len_current:
                    repeat = chunk
                else:
                    repeat = dna
                    dna = chunk
    return dna, repeat, repeat_and_structure


def split_seq_from_structure(repeat_and_structure):
    lines_seq = [l for l in repeat_and_structure if not set(l).intersection({".", "(", ")"})]
    lines_struct = [l for l in repeat_and_structure if set(l).intersection({".", "(", ")"})]
    return lines_seq, lines_struct


def run_accuracy_alone(result_folder):
    absolute_path_accuracy = abspath("CRISPRAccuracy/CRISPR_structure_accuracies.pl")
    cur_path = str(pathlib.Path().absolute())
    os.chdir("CRISPRAccuracy/")
    cmd = f"perl {absolute_path_accuracy} --DR {result_folder}/Accuracy_independent_repeat.fa -a {result_folder}/Accuracy_independent_DNA.fa --str-f DB --msfas {result_folder}/Accuracy_independent_repeat_structure.fa"
    print(cmd)
    os.system(cmd)
    os.chdir(cur_path)


def multiline_fasta_check(file):
    with open(file, "r") as f:
        lines = f.readlines()
    number_of_inputs = sum([1 for line in lines if ">" in line])
    return number_of_inputs != 1


def multiline_fasta_handle(file, folder):
    dict_dna = {}
    with open(file) as f:
        lines = f.readlines()
    cur_dna = ''
    for line_index, line in enumerate(lines):
        if line[0] == ">":
            if cur_dna:
                dict_dna[cur_key] = cur_dna
            cur_key = line.strip()
            cur_dna = ''
        else:
            cur_dna += line.strip()

    if cur_dna:
        dict_dna[cur_key] = cur_dna



    for key, value in dict_dna.items():
        file_name = key[1:].replace(" ", "").replace("|", "").replace("/", "").replace(".", "").replace(",", "") + ".fasta"
        with open(join(folder, file_name), "w") as f:
            f.write(key)
            f.write('\n')
            f.write(value)

    return [key[1:].replace(" ", "").replace("|", "").replace("/", "").replace(".", "").replace(",", "") for key in dict_dna.keys()]





#############################################################################
#############################################################################
if __name__ == '__main__':
    print('\n')
    print('###################################################################################')
    print('##########                   CRISPR: Master Script                        #########')
    print('###################################################################################')
    print('\n')

    parser = argparse.ArgumentParser()

    ###################################################################################
    ##########                      Start CRISPRcasidentifier                 #########
    ###################################################################################

    parser.add_argument('-f', '--fasta', dest='fasta_file',
                        help='Fasta file path (it can be either protein or DNA, see -st and -sc for details).',
                        metavar='/path/to/file.fa')

    parser.add_argument('-r', '--regressors', nargs='+', dest='regressors',
                        help='List of regressors. Available options: CART, ERT or SVM (default: ERT).', default='ERT',
                        metavar='reg1 reg2', choices=['CART', 'ERT', 'SVM'])

    parser.add_argument('-c', '--classifiers', nargs='+', dest='classifiers',
                        help='List of classifiers. Available options: CART, ERT or SVM (default: ERT).', default='ERT',
                        metavar='clf1 clf2', choices=['CART', 'ERT', 'SVM'])

    parser.add_argument('-s', '--hmm-sets', nargs='+', dest='hmm_sets',
                        help='List of HMM sets. Available options: HMM1 to HMM5 and HMM2019 (default: HMM2019).',
                        metavar='HMMi HMMj', default='HMM2019', choices=['HMM1', 'HMM2', 'HMM3', 'HMM4', 'HMM5', 'HMM2019'])

    parser.add_argument('-st', '--sequence-type', nargs='?', dest='sequence_type', default="protein",
                        help='Sequence type. Available options: dna or protein (default: protein).', metavar='seq_type',
                        choices=['dna', 'protein', 'repeat', 'virus', 'accuracy', 'genbank'])

    parser.add_argument('-sc', '--sequence-completeness', nargs='?', dest='sequence_completeness',
                        help='Sequence completeness (used only if sequence type is dna). Available options: complete or partial (default: complete).',
                        default='complete', metavar='seq_comp', choices=['complete', 'partial'])

    parser.add_argument('-m', '--mode', nargs='?', dest='run_mode',
                        help='Run mode. Available options: classification, regression or combined (default: combined).',
                        default='combined', metavar='mode', choices=['classification', 'regression', 'combined'])

    ###################################################################################
    ##########                      End CRISPRcasidentifier                   #########
    ###################################################################################

    ###################################################################################
    ##########                      Start CRISPRidentify                      #########
    ###################################################################################

    parser.add_argument('--model', type=str, default="ALL",
                        help='model_to_use (default: ALL)')

    parser.add_argument('--additional_model', type=str, default=None,
                        help='model_to_use (default: None)')

    parser.add_argument('--result_folder', type=str, default="Results",
                        help='folder with the result (default: Results)')

    parser.add_argument('--pickle_report', type=str, default='',
                        help='pickled report file (default: None)')

    parser.add_argument('--strand', type=str, default=True,
                        help='CRISPR array orientation prediction (default: True)')

    parser.add_argument('--cas', type=str, default=False,
                        help='cas genes computation (default: False)')

    parser.add_argument('--is_element', type=str, default=True,
                        help='is element computation (default: True)')

    parser.add_argument('--parallel', type=str, default=True,
                        help='parallel computations (default: True)')

    parser.add_argument('--fast_run', type=str, default=False,
                        help='fast run option (default: False)')

    parser.add_argument('--degenerated', type=bool, default=True,
                        help='degenerated_repeat_computation (default: True)')

    parser.add_argument('--min_len_rep', type=int, default=21,
                        help='min avg. length of the repeats (default: 21)')

    parser.add_argument('--max_len_rep', type=int, default=55,
                        help='max avg. length of the repeats (default: 55)')

    parser.add_argument('--min_len_spacer', type=int, default=18,
                        help='min avg. length of spacers (default: 18)')

    parser.add_argument('--max_len_spacer', type=int, default=78,
                        help='max avg. length of spacers (default: 78)')

    parser.add_argument('--min_repeats', type=int, default=3,
                        help='min number of repeats (default: 3)')

    parser.add_argument('--enhancement_max_min', type=bool, default=True,
                        help='enhancement with filter (default: True)')

    parser.add_argument('--enhancement_start_end', type=bool, default=True,
                        help='enhancement with start end omitting (default: True)')

    parser.add_argument('--max_identical_spacers', type=int, default=4,
                        help='maximum number of identical spacers in the array (default: 4)')

    parser.add_argument('--max_identical_cluster_spacers', type=int, default=3,
                        help='maximum number of consecutive identical spacers in the array (default: 3)')

    parser.add_argument('--margin_degenerated', type=int, default=30,
                        help='maximum length of the spacer margin for the degenerated search (default: 30)',)

    parser.add_argument('--max_edit_distance_enhanced', type=int, default=6,
                        help='maximum edit distance for the evaluated array enhancement (default: 6)')

    ###################################################################################
    ##########                      End CRISPRidentify                        #########
    ###################################################################################

    ###################################################################################
    ##########                      Start Casboundary                         #########
    ###################################################################################

    parser.add_argument('-cg', '--maximum-gap', nargs='?', dest='max_gap',
                        help='Maximum number of contiguous gaps allowed in a cassette. Available options: 0 <= gap <= 3 (default: 1).',
                        type=int, choices=range(4), default=2)

    parser.add_argument('-cm', '--model_cas', nargs='?', dest='model_cas',
                        help='Which ML model will be used. Available obtions: ert and dnn (default: ert).',
                        choices=['ert', 'dnn', 'ERT', 'DNN'], default='ert')

    ###################################################################################
    ##########                        End Casboundary                         #########
    ###################################################################################

    ###################################################################################
    ##########                    Start - Master Script Arguments             #########
    ###################################################################################

    parser.add_argument('-output', '--output-dir', nargs='?', dest='output_dir',
                        help='Main output folder (default: Output).', default='Output')

    parser.add_argument('-cpu', '--number-of-cpus', nargs='?', dest='n_cpus',
                        help='Number of CPUs to use.', default=4, type=int)

    #### CRISPRrepeatSimilarity
    parser.add_argument('-evalue_r', '--e-cutoff-repeat', nargs='?', dest='e_cutoff_r',
                        help='The number of expected hits. ', default="0.1", type=str)
    
    #### CRISPRVirusSpacers
    parser.add_argument('-evalue_s', '--e-cutoff-spacer', nargs='?', dest='e_cutoff_s',
                        help='The number of expected hits. ', default="1e-7", type=str)
    

    ###################################################################################
    ##########                    End - Master Script Arguments               #########
    ###################################################################################

    args = parser.parse_args()


    #############################################################################
    # main folder - Generating the master-folder with keys - Better control
    root = os.getcwd()
    folder_main = args.output_dir
    # var = (datetime.now() + timedelta(hours=9)).strftime('%H%M%S')
    if os.path.isdir(folder_main) is True:
        print("Directory already exists: " + folder_main)
    else:
        os.system('mkdir ' + folder_main)

    os.system('mkdir -p ' + folder_main + '/tmp')
    #############################################################################

    if args.sequence_type == 'dna' or args.sequence_type == 'DNA':
        multifasta_flag = multiline_fasta_check(args.fasta_file)
        if not multifasta_flag:
            summ = os.path.abspath(folder_main)

            # Casboundary - Ready to use
            dirname_cas, hmm_cas = casboundary()

            # CRISPRcasIdentifier - Ready to use
            # dirname_crisp, predictions_crisp, hmm, cassette = crispr_cas_identifier()
            if len(os.listdir(dirname_cas + '/predictions/raw_cassettes/')) != 0:
                re_dirname_crisp = crispr_cas_identifier_protein()
            else:
                re_dirname_crisp, summary_crisp = crispr_cas_identifier()

            # CRISPRidentify - Ready to use
            dirname_identify = crispr_identify()

            # Generating summaries

            results_identify, summary_crisp, crisp_candidates = generating_summaries_dna(dirname_cas, re_dirname_crisp,
                                                                                         dirname_identify)

            # Please  check whether the files exists before doing anything
            # Run the jar script

            if os.path.exists(summ + '/annotations_details.tab') is True:
                os.system(
                    'java -jar ' + mainPath + '/CRISPRloci_webserver_visualization/CRISPRloci_visualization.jar -i ' +
                    summ + '/annotations_details.tab -o ' + summ + '/annotations_details.xml -p ' +
                    summ + '/annotations_details.svg -H 100 -W 1000')
            else:
                print('This file does not exist: ' + summ + '/annotations_details.tab')

            generating_summaries_cas_result_tab_and_main()
            csv_long_name_fix(summ)

            # crispr_self_target - Ready to use
            dirname_target = crispr_self_target()

            # CRISPRAccuracy - Ready to use
            #folder_crispr_acc = crispr_accuracy()

            # remove hmmsearch output
            #os.system('rm -rf ' + hmm_cas)

        else:
            summ_old = os.path.abspath(folder_main)
            file_names = multiline_fasta_handle(args.fasta_file, folder_main)
            for file_name in file_names:
                result_local_folder = join(summ_old, file_name)
                cmd = f"mkdir {result_local_folder}"
                os.system(cmd)

                file_name += ".fasta"
                args.fasta_file = join(summ_old, file_name)

                summ = result_local_folder
                folder_main = summ

                # Casboundary - Ready to use
                dirname_cas, hmm_cas = casboundary()

                # CRISPRcasIdentifier - Ready to use
                # dirname_crisp, predictions_crisp, hmm, cassette = crispr_cas_identifier()
                if len(os.listdir(dirname_cas + '/predictions/raw_cassettes/')) != 0:
                    re_dirname_crisp = crispr_cas_identifier_protein()
                else:
                    re_dirname_crisp, summary_crisp = crispr_cas_identifier()

                # CRISPRidentify - Ready to use
                dirname_identify = crispr_identify()

                # Generating summaries

                results_identify, summary_crisp, crisp_candidates = generating_summaries_dna(dirname_cas,
                                                                                             re_dirname_crisp,
                                                                                             dirname_identify)

                # Please  check whether the files exists before doing anything
                # Run the jar script

                if os.path.exists(summ + '/annotations_details.tab') is True:
                    os.system(
                        'java -jar ' + mainPath + '/CRISPRloci_webserver_visualization/CRISPRloci_visualization.jar -i ' +
                        summ + '/annotations_details.tab -o ' + summ + '/annotations_details.xml -p ' +
                        summ + '/annotations_details.svg -H 100 -W 1000')
                else:
                    print('This file does not exist: ' + summ + '/annotations_details.tab')

                generating_summaries_cas_result_tab_and_main()
                csv_long_name_fix(summ)

                # crispr_self_target - Ready to use
                dirname_target = crispr_self_target()

                # CRISPRAccuracy - Ready to use
                #folder_crispr_acc = crispr_accuracy()

                # remove hmmsearch output
                #os.system('rm -rf ' + hmm_cas)




    #############################################################################

    elif args.sequence_type == 'Protein' or args.sequence_type == 'protein':
        summ = str(os.path.abspath(folder_main))
        dirname_cas, hmm_cas = casboundary()
        re_dirname_crisp = crispr_cas_identifier_protein()
        generating_summaries_protein()
        csv_long_name_fix(summ)
        # remove hmmsearch output
        #os.system('rm -rf ' + hmm_cas)

    #############################################################################

    elif args.sequence_type == 'Repeat' or args.sequence_type == 'repeat':
        summ = str(os.path.abspath(folder_main))
        # CRISPRstrand - Ready to use
        dirname_strand = crispr_strand()
        # crispr_repeat_similarity - Ready to use
        dirname_repeat = crispr_repeat_similarity()

    #############################################################################
    #############################################################################

    elif args.sequence_type == 'Virus' or args.sequence_type == 'virus':
        summ = str(os.path.abspath(folder_main))
        # CRISPRVirusSpacers - Ready to use
        crispr_virus()

        annotation_tab = summ + '/annotations_details.tab'
        #results_virus_one = summ + '/Output.csv'
        #results_virus_two = summ + '/Summary_VirusResults.csv'
        results_virus_one = summ + '/Full_HostVirst_Interaction_Results.csv'
        results_virus_two = summ + '/Summary_VirusResults.csv'
        annotations_details_virus(args.fasta_file, results_virus_one, results_virus_two, annotation_tab)

        if os.path.exists(summ + '/annotations_details.tab') is True:
            os.system('java -jar '+mainPath+'/CRISPRloci_webserver_visualization/CRISPRloci_visualization.jar -i ' +
                      summ + '/annotations_details.tab -o ' + summ + '/annotations_details.xml -p ' +
                      summ + '/annotations_details.svg -H 100 -W 1000')
        else:
            print('This file does not exist: ' + summ + '/annotations_details.tab')


    #############################################################################
    #############################################################################
    elif args.sequence_type == 'Genbank' or args.sequence_type == 'genbank':
        summ = str(os.path.abspath(folder_main))

        fasta = args.fasta_file
        dict_dna, dict_protein = gb_into_fasta_input(fasta)

        if dict_dna and not dict_protein:
            args.fasta_file = "dna_input.fa"

            # Casboundary - Ready to use
            dirname_cas, hmm_cas = casboundary()

            # CRISPRcasIdentifier - Ready to use
            # dirname_crisp, predictions_crisp, hmm, cassette = crispr_cas_identifier()
            if len(os.listdir(dirname_cas + '/predictions/raw_cassettes/')) != 0:
                re_dirname_crisp = crispr_cas_identifier_protein()
            else:
                re_dirname_crisp, summary_crisp = crispr_cas_identifier()

            # CRISPRidentify - Ready to use
            dirname_identify = crispr_identify()

            # Generating summaries

            results_identify, summary_crisp, crisp_candidates = generating_summaries_dna(dirname_cas, re_dirname_crisp,
                                                                                         dirname_identify)

            # Please  check whether the files exists before doing anything
            # Run the jar script

            if os.path.exists(summ + '/annotations_details.tab') is True:
                os.system(
                    'java -jar ' + mainPath + '/CRISPRloci_webserver_visualization/CRISPRloci_visualization.jar -i ' +
                    summ + '/annotations_details.tab -o ' + summ + '/annotations_details.xml -p ' +
                    summ + '/annotations_details.svg -H 100 -W 1000')
            else:
                print('This file does not exist: ' + summ + '/annotations_details.tab')

            generating_summaries_cas_result_tab_and_main()
            csv_long_name_fix(summ)

            # crispr_self_target - Ready to use
            dirname_target = crispr_self_target()

            # CRISPRAccuracy - Ready to use
            #folder_crispr_acc = crispr_accuracy()

            # remove hmmsearch output
            #os.system('rm -rf ' + hmm_cas)

        if dict_protein and not dict_dna:
            args.fasta_file = "protein_input.fa"
            args.sequence_type = "protein"
            dirname_cas, hmm_cas = casboundary()
            re_dirname_crisp = crispr_cas_identifier_protein()
            generating_summaries_protein()

            csv_long_name_fix(summ)

            summary_cas = summ + '/summary_casboundary.csv'
            fill_in_locations_cas_boundary_summary(summary_cas, dict_protein)

            summary_crisp = summ + '/summary_crisp.csv'
            summary_crisp_dna_v2(re_dirname_crisp, summary_cas, summary_crisp)

            generating_cas_results_tab()
            # remove hmmsearch output
            os.system('rm -rf ' + hmm_cas)

        if dict_dna and dict_protein:
            args.fasta_file = "protein_input.fa"
            args.sequence_type = "protein"
            dirname_cas, hmm_cas = casboundary()
            re_dirname_crisp = crispr_cas_identifier_protein()
            generating_summaries_protein()
            csv_long_name_fix(summ)

            summary_cas = summ + '/summary_casboundary.csv'
            fill_in_locations_cas_boundary_summary(summary_cas, dict_protein)

            tmp_cas_boundary = dirname_cas + '/predictions/'
            tmp_cas_boundary_row = dirname_cas + '/predictions/raw_cassettes/'
            fill_in_locations_cas_boundary_cassetes(tmp_cas_boundary, dict_protein)
            fill_in_locations_cas_boundary_cassetes(tmp_cas_boundary_row, dict_protein)

            # remove hmmsearch output
            #os.system('rm -rf ' + hmm_cas)

            args.fasta_file = "dna_input.fa"

            # CRISPRidentify - Ready to use
            dirname_identify = crispr_identify()

            # Generating summaries

            results_identify, summary_crisp, crisp_candidates = generating_summaries_dna(dirname_cas, re_dirname_crisp,
                                                                                         dirname_identify)

            # Please  check whether the files exists before doing anything
            # Run the jar script

            if os.path.exists(summ + '/annotations_details.tab') is True:
                os.system(
                    'java -jar ' + mainPath + '/CRISPRloci_webserver_visualization/CRISPRloci_visualization.jar -i ' +
                    summ + '/annotations_details.tab -o ' + summ + '/annotations_details.xml -p ' +
                    summ + '/annotations_details.svg -H 100 -W 1000')
            else:
                print('This file does not exist: ' + summ + '/annotations_details.tab')

            generating_summaries_cas_result_tab_and_main()
            csv_long_name_fix(summ)

            main_summary = summ + '/Main_summary.csv'
            main_summary_function(summary_crisp, crisp_candidates, main_summary)

            csv_long_name_fix(summ)

            # crispr_self_target - Ready to use

            dirname_target = crispr_self_target()

            # CRISPRAccuracy - Ready to use

            #folder_crispr_acc = crispr_accuracy()


    #############################################################################
    #############################################################################

    else:
        print('This option does not exist: ' + args.sequence_type)

    #############################################################################
    #############################################################################

    # Example: Master Script - Working

    # 1- For a DNA 
    # python3.7 master_script.py -f Example/NC_005230.fasta -st dna

    # 2- For a Proteins 
    # python3.7 master_script.py -f Example/NC_005230_proteins.fasta -st  protein

    # 3- For a Repeat
    # python3.7 master_script.py -f Example/Input3.fa -st repeat

################################################################################
################################################################################

