#! /usr/bin/python

import argparse, csv, os, sys
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from Bio import SeqIO
from re import finditer
from itertools import product
from textwrap import wrap
from pathlib import Path 
from .__version__ import __version__
from .__help__ import *

import time

# For PyPi releases use .__version__ & .__help__, for running as script remove .'s

complements = {'A': 'T', 'C': 'G', 'G': 'C', 'T': 'A'}

def main():

    #---------#
    # PARSING #
    #---------#
    
    # Create main parser 
    
    parent_parser = argparse.ArgumentParser(usage=None, add_help=False)    
        
    # Make flag for custom help and version arguments 
        
    parent_parser.add_argument("-h", "--help", action="store_true")
    parent_parser.add_argument("-V", "--version", action="store_true")
    
    # Enable subparsers
    
    subparsers = parent_parser.add_subparsers(dest="subparser_name")

    # Create info subparser & arguments
    
    subparser_info = subparsers.add_parser("info", usage=None, add_help=False)
    
    subparser_info.add_argument("inFile", nargs="?")  
    subparser_info.add_argument("-h", "--help", action="store_true")
    subparser_info.add_argument("-V", "--version", action="store_true")

    # Create search subparser & arguments 

    subparser_search = subparsers.add_parser("search", usage=None, add_help=False)
    
    subparser_search.add_argument("inFile", nargs="?")    
    subparser_search.add_argument("query", nargs="?")
    subparser_search.add_argument("-r", "--revcomp", action="store_true")
    subparser_search.add_argument("-o", "--overlap", action="store_true")
    subparser_search.add_argument("-q", "--quiet", action="store_true")
    subparser_search.add_argument("-h", "--help", action="store_true")
    subparser_search.add_argument("-V", "--version", action="store_true")
    
    # Create filter subparser & arguments
    
    subparser_filter = subparsers.add_parser("filter", add_help=False)
    
    subparser_filter.add_argument("inFile", nargs="?")
    subparser_filter.add_argument("-s", "--sequence")
    subparser_filter.add_argument("-min", "--minimum")
    subparser_filter.add_argument("-max", "--maximum")
    subparser_filter.add_argument("-m", "--motif")
    subparser_filter.add_argument("-r", "--revcomp", action="store_true")
    subparser_filter.add_argument("-w", "--wrap")
    subparser_filter.add_argument("-o", "--output")
    subparser_filter.add_argument("-h", "--help", action="store_true")
    subparser_filter.add_argument("-V", "--version", action="store_true")

    # Create repeats subparser & arguments
        
    subparser_repeats = subparsers.add_parser("repeats", add_help=False)
    
    subparser_repeats.add_argument("inFile", nargs="?")
    subparser_repeats.add_argument("repmotif", nargs="?")
    subparser_repeats.add_argument("-r", "--revcomp", action="store_true")
    subparser_repeats.add_argument("-s", "--sort", action="store_true")
    subparser_repeats.add_argument("-q", "--quiet", action="store_true")
    subparser_repeats.add_argument("-c", "--cli", action="store_true")
    subparser_repeats.add_argument("-h", "--help", action="store_true")
    subparser_repeats.add_argument("-V", "--version", action="store_true")

    # Create inter parent subparser & arguments 
        
    subparser_inter = subparsers.add_parser("inter", add_help=False)
       
    subparser_inter.add_argument("-h", "--help", action="store_true")    
    subparser_inter.add_argument("-V", "--version", action="store_true")

    # Enable inter subsubparsers (nested subparsers)

    inter_subsubparsers = subparser_inter.add_subparsers(dest="inter_subsubparser_name")
    
    # Create inter find subsubparser & arguments 
    
    subsubparser_find = inter_subsubparsers.add_parser("find", add_help=False)
    
    subsubparser_find.add_argument("inFile", nargs="?")
    subsubparser_find.add_argument("repmotif", nargs="?")
    subsubparser_find.add_argument("-r", "--revcomp", action="store_true")
    subsubparser_find.add_argument("-t", "--threshold")
    subsubparser_find.add_argument("-s", "--specificity")
    subsubparser_find.add_argument("-min", "--minimum")
    subsubparser_find.add_argument("-max", "--maximum")
    subsubparser_find.add_argument("-a", "--appearances")
    subsubparser_find.add_argument("-v", "--verbose", action="store_true")
    subsubparser_find.add_argument("-o", "--output", action="store_true")
    subsubparser_find.add_argument("-q", "--quiet", action="store_true")
    subsubparser_find.add_argument("-h", "--help", action="store_true") 
    subsubparser_find.add_argument("-V", "--version", action="store_true")
    
    # Create inter position subsubparser & arguments
    
    subsubparser_position = inter_subsubparsers.add_parser("position", add_help=False) 
    
    subsubparser_position.add_argument("inFile", nargs="?")
    subsubparser_position.add_argument("repmotif", nargs="?")
    subsubparser_position.add_argument("inter", nargs="?")
    subsubparser_position.add_argument("-r", "--revcomp", action="store_true")
    subsubparser_position.add_argument("-v", "--verbose", action="store_true")
    subsubparser_position.add_argument("-g", "--graphcrop", action="store_true")
    subsubparser_position.add_argument("-q", "--quiet", action="store_true")
    subsubparser_position.add_argument("-c", "--cli", action="store_true")
    subsubparser_position.add_argument("-h", "--help", action="store_true") 
    subsubparser_position.add_argument("-V", "--version", action="store_true")
    
    # Create inter pattern subsubparser & arguments 
    
    subsubparser_pattern = inter_subsubparsers.add_parser("pattern", add_help=False)
    
    subsubparser_pattern.add_argument("-h", "--help", action="store_true") 
    subsubparser_pattern.add_argument("-V", "--version", action="store_true")
    
    # Create inter correlation subsubparser & arguments

    subsubparser_correlation = inter_subsubparsers.add_parser("correlation", add_help=False)
    
    subsubparser_correlation.add_argument("-h", "--help", action="store_true") 
    subsubparser_correlation.add_argument("-V", "--version", action="store_true")

    # Parse arguments, direct to help if invalid arguments given 
    
    try:
        args = parent_parser.parse_args()
    except: # Can this take and display the invalid argument ??
        show_help()
        
    # Check if custom help flag used and direct accordingly

    if args.help:
        show_help(args)
        
    if args.version:
        print("charlie", __version__)
        sys.exit()

    # Direct to appropriate operating mode function, if none given direct to help

    if args.subparser_name == "info":
        info(args)
    elif args.subparser_name == "search":
        search(args)
    elif args.subparser_name == "filter":
        filter(args)
    elif args.subparser_name == "repeats":
        repeats(args)
    elif args.subparser_name == "inter":
        if args.inter_subsubparser_name == "find":
            inter_find(args)
        elif args.inter_subsubparser_name == "position":
            inter_position(args)
        elif args.inter_subsubparser_name == "pattern":
            inter_pattern(args)
        elif args.inter_subsubparser_name == "correlation":
            inter_correlation(args)
        else:
            show_help(args)
    else:
        show_help(args)

def open_fastx(filename):

    filename_ext = (os.path.splitext(filename))[1]

    if filename_ext in [".fasta", ".fa", ".fas", ".fna"]:
        fastx_type = "fasta"
    elif filename_ext in [".fastq", ".fq"]:
        fastx_type = "fastq"
    else:
        sys.exit("\nERROR: Invalid input file type\n")

    recordList = []
    records = SeqIO.parse(open(filename), fastx_type)

    # Open FASTA

    if fastx_type == "fasta":
        for record in records:
            id = record.description
            id = str('>' + id)
            seq = str(record.seq).upper()
            recordList.append([id, seq])
        records_df = pd.DataFrame(recordList, columns=['ID','Seq'])

    # Open FASTQ

    elif fastx_type == "fastq":
        for record in records:
            id = record.description
            id = str('@' + id)
            seq = str(record.seq).upper()
            quality = record.letter_annotations
            recordList.append([id, seq, quality])
        records_df = pd.DataFrame(recordList, columns=['ID','Seq', 'Quality'])

    return records_df, fastx_type

def info(args):
    
    #---------------------#
    # VARIABLE ASSIGNMENT #
    #---------------------#
    
    # Import FASTX file 

    if args.inFile:
        filename = args.inFile
        try:
            records_df, fastx_type = open_fastx(filename)
        except Exception:
            sys.exit("\nERROR: Could not import file\n") # More detail / display raw error?
    else:
        sys.exit("\nERROR: Input file required\n")
    
    #------#
    # MAIN #
    #------#    
        
    # Get file name, ext and type 

    filename_name = (os.path.basename(filename))
        
    # Count records
    
    num_records = len(records_df.index)    
    
    # Seq statistics 
    
    shortest_seq = records_df.Seq.map(len).min()
    longest_seq = records_df.Seq.map(len).max()
    avg_seq_len = round(records_df.Seq.map(len).mean())

    # Phred statistics
    
    if fastx_type == "fastq":
        
        avg_phreds = []
        
        for index in records_df.index:
            arr = np.array(records_df['Quality'][index]['phred_quality'])
            avg_phreds.append(round(np.mean(arr)))

        avg_errors = []
        
        for phred in avg_phreds:
            avg_errors.append(round(10**(-phred/10), 10))

        lowest_error = min(avg_errors)
        highest_error = max(avg_errors)
        avg_error = round(np.mean(np.array(avg_errors)), 10)

    # Output to CLI 
    
    print("\nBASIC")
    print(f"File name:   {filename_name}")
    print(f"File type:   {fastx_type.upper()}")
    print(f"No. records: {num_records:,}")
    print("\nSEQUENCE")
    print(f"Shortest sequence: {shortest_seq:,}")
    print(f"Longest sequence:  {longest_seq:,}")
    print(f"Mean seq. length:  {avg_seq_len:,}")
    if fastx_type == "FASTQ":
        print("\nQUALITY")
        print(f"Lowest Error:  {lowest_error}")
        print(f"Highest Error: {highest_error}")
        print(f"Mean Error:    {avg_error}\n")
    
def search(args):
    
    #---------------------#
    # VARIABLE ASSIGNMENT #
    #---------------------#
    
    # Import FASTX file 
    
    if args.inFile:
        filename = args.inFile
        try:
            records_df = open_fastx(filename)[0]
        except Exception:
            sys.exit("\nERROR: Could not import file\n") # More detail / display raw error?
    else:
        sys.exit("\nERROR: Input file required\n")
    
    # Check search query sequence has been provided, if so take as input, else return error
    
    if args.query:
        query = args.query
    else:
        sys.exit("\nERROR: Search query sequence required\n")
    
    # Assign flags (True/None)
    
    _revcomp = args.revcomp    
    _overlap = args.overlap
    _quiet = args.quiet
    
    #------#
    # MAIN #
    #------#
    
    # Find reverse complement of query if required 
    
    if _revcomp:
        query_rev = ''.join([complements[base] for base in query[::-1]])
    
    # Calculate total appearances of query and no. records containing query
    
    total_appearances = 0
    records_containing = 0
    
    for index in records_df['Seq']:

        if _overlap:        
            count = sum(1 for _ in finditer(('(?=' + query + ')'), index))
            if _revcomp:
                count_rev = sum(1 for _ in finditer(('(?=' + query_rev + ')'), index))
                count += count_rev
        else:
            count = index.count(query)
            if _revcomp:
                count_rev = index.count(query_rev)
                count += count_rev
        
        total_appearances += count
        
        if count > 0:
            records_containing += 1 

    freq = round(records_containing / (len(records_df.index)), 5)

    # Display results
    
    if _quiet:
        print(f"{records_containing:,} ({freq})")
    else:
        print(f"\nInput File:           {filename}")
        print(f"Records in File:      {len(records_df.index):,}")
        print(f"Search Query:         {query}")
        print(f"Total Appearances:    {total_appearances:,}")
        print(f"Records Containing:   {records_containing:,}")
        print(f"Frequency:            {freq}\n")

def filter(args): 
    
    #---------------------#
    # VARIABLE ASSIGNMENT #
    #---------------------#    

    # Import FASTX file 
    
    if args.inFile:
        filename = args.inFile
        try:
            records_df, fastx_type = open_fastx(filename)
        except Exception:
            sys.exit("\nERROR: Could not import file\n") # More detail / display raw error?
    else:
        sys.exit("\nERROR: Input file required\n")

    # Check if filtering by sequence, if so take sequence as input 

    if args.sequence:
        _seq = True
        seq = args.sequence
    else:
        _seq = False
        
    # Check if filtering by minimum repeat size, if so take minimum repeat size as input 
        
    if args.minimum:
        _minrep = True
        try:
            minrep = int(args.minimum)
        except:
            sys.exit("\nERROR: <min> must be an integer value\n")
        if minrep < 0:
            sys.exit("\nERROR: <min> cannot be a negative value\n")
    else:
        _minrep = False
    
    # Check if filtering by maximum repeat size, if so take maximum repeat size as input 
        
    if args.maximum:
        _maxrep = True
        try:
            maxrep = int(args.maximum)
        except:
            sys.exit("\nERROR: <max> must be an integer value\n")
        if maxrep < 0:
            sys.exit("\nERROR: <max> cannot be a negative value\n")
    else:
        _maxrep = False
    
    if _minrep and _maxrep:
        if minrep > maxrep:
            sys.exit("\nERROR: <min> cannot be greater than <max>\n")
            
    # Take repeat motif if using min or max 

    if _minrep or _maxrep: 
        if args.motif:
            repmotif = args.motif
        else:
            sys.exit("\nERROR: Repeat motif (-m) is required when using -min or -max\n")        
    
    # Assign whether including reverse complements (True/None)
    
    _revcomp = args.revcomp
            
    # Check if wrapping output, if so take width as input
    
    if args.wrap:
        _wrap = True
        try:
            width = int(args.wrap)
        except:
            sys.exit("\nERROR: <width> must be an integer value\n")
        if minrep < 1:
            sys.exit("\nERROR: <width> cannot be less than 1\n")

    else:
        _wrap = False
        
    # Check if outputting to file, if so take output filename as input 
    
    if args.output:
        _output = True
        outFile = args.output
    else:
        _output = False 
    
    #------#
    # MAIN #
    #------#
    
    # Filter by query sequence
    
    if _seq:
        query = seq
        if _revcomp:
            query_rev = ''.join([complements[base] for base in query[::-1]])
            for index in records_df.index:
                record_seq = records_df['Seq'][index]
                if query not in record_seq and query_rev not in record_seq:
                    records_df.drop(index, inplace=True)
        else:
            for index in records_df.index:
                record_seq = records_df['Seq'][index]
                if query not in record_seq:
                    records_df.drop(index, inplace=True)
    
    # Filter by minimum repeat size 
       
    if _minrep:
        query = repmotif * minrep
        if _revcomp:
            query_rev = ''.join([complements[base] for base in query[::-1]])
            for index in records_df.index:
                record_seq = records_df['Seq'][index]
                if query not in record_seq and query_rev not in record_seq:
                    records_df.drop(index, inplace=True)
        else:
            for index in records_df.index:
                record_seq = records_df['Seq'][index]
                if query not in record_seq:
                    records_df.drop(index, inplace=True)
    
    # Filter by maximum repeat size        
    
    if _maxrep:
        query = repmotif * (maxrep + 1)
        if _revcomp:
            query_rev = ''.join([complements[base] for base in query[::-1]])
            for index in records_df.index:
                record_seq = records_df['Seq'][index]
                if query in record_seq or query_rev in record_seq:
                    records_df.drop(index, inplace=True)
        else:
            for index in records_df.index:
                record_seq = records_df['Seq'][index]
                if query in record_seq:
                    records_df.drop(index, inplace=True)

    # Output to new file or CLI 

    # Output as FASTA

    if fastx_type == "fasta":
        if _output:        
            if _wrap:
                with open(outFile, "w") as output:
                    for index in records_df.index:
                        output.write(records_df['ID'][index] + "\n")
                        output.write("\n".join(wrap(records_df['Seq'][index], width)))
                        output.write("\n")
            else:
                with open(outFile, "w") as output:
                    for index in records_df.index:
                        output.write(records_df['ID'][index] + "\n")
                        output.write(records_df['Seq'][index] + "\n")
 
        else:
            if _wrap:
                for index in records_df.index:
                    print(records_df['ID'][index])
                    print("\n".join(wrap(records_df['Seq'][index], width)))
            else:
                for index in records_df.index:
                    print(records_df['ID'][index])
                    print(records_df['Seq'][index])    
    
    # Output as FASTQ 
    
    else:
    
        if _output:        
            if _wrap:
                with open(outFile, "w") as output:
                    for index in records_df.index:
                        output.write(records_df['ID'][index] + "\n")
                        output.write("\n".join(wrap(records_df['Seq'][index], width)))
                        output.write("\n+\n" + "\n".join(wrap("".join(chr(val+33) for val in records_df['Quality'][index]['phred_quality']), width)))
                        output.write("\n")
            else:
                with open(outFile, "w") as output:
                    for index in records_df.index:
                        output.write(records_df['ID'][index] + "\n")
                        output.write(records_df['Seq'][index] + "\n")
                        output.write("+\n" + "".join(chr(val+33) for val in records_df['Quality'][index]['phred_quality']) + "\n")
            
        else:
            if _wrap:
                for index in records_df.index:
                    print(records_df['ID'][index])
                    print("\n".join(wrap(records_df['Seq'][index], width)))
                    print("+\n" + "\n".join(wrap("".join(chr(val+33) for val in records_df['Quality'][index]['phred_quality']), width)))
            else:
                for index in records_df.index:
                    print(records_df['ID'][index])
                    print(records_df['Seq'][index])
                    print("+\n" + "".join(chr(val+33) for val in records_df['Quality'][index]['phred_quality']))
   
def repeats(args):
    
    #---------------------#
    # VARIABLE ASSIGNMENT #
    #---------------------#
    
    # Import FASTX file 
    
    if args.inFile:
        filename = args.inFile
        try:
            records_df = open_fastx(filename)[0]
        except Exception:
            sys.exit("\nERROR: Could not import file\n") # More detail / display raw error?
    else:
        sys.exit("\nERROR: Input file required\n")
    
    # Check repeat motif sequence has been provided, if so take as input, else return error
    
    if args.repmotif:
        repmotif = args.repmotif
    else:
        sys.exit("\nERROR: Repeat motif sequence is required\n")
    
    # Assign flags (True/None)
    
    _revcomp = args.revcomp
    _sort = args.sort
    _quiet = args.quiet
    _cli = args.cli
    
    if _cli and _quiet:
        print("\nNOTE: If both --quiet and --cli are used, no output will be given\n")
    
    #------#
    # MAIN #
    #------#
    
    # Create list to write record counts at each repeat size
    
    histogram = []
    
    # Create a set of all indexes 
    
    a = set([i for i in records_df.index])
    
    # Iterate up through repeat lengths until repeat length of each index has been found 
    
    replen = 1
    
    while len(a):
        
        # For each index in set a check if it contains at least replen repeats, if yes store in list b
        
        b = []
        
        query = repmotif * replen
                
        if _revcomp:
            query_rev = ''.join([complements[base] for base in query[::-1]])
            for index in a:
                record_seq = records_df['Seq'][index]
                if query in record_seq or query_rev in record_seq:
                    b.append(index)
        else:
            for index in a:
                record_seq = records_df['Seq'][index]
                if query in record_seq:
                    b.append(index)
    
        # No. indexes with repeat length of replen - 1 is equal to the difference in number of indexes vs. previous iteration
    
        histogram.append([replen - 1, len(a) - len(b)])
    
        # Use set intersection to remove all indexes not in b from a 
    
        a = a & set(b)
            
        # Move to next repeat length
        
        replen += 1 
    
    # Sort by repeat size or desc. no. records 
        
    if _sort:
        histogram = sorted(histogram, key=lambda x:x[1], reverse=True)
    else:
        histogram = sorted(histogram, key=lambda x:x[0])

    # Output to CLI
    
    if not _quiet:
        for list in histogram:
            print(f"{list[0]}\t{list[1]}")

    # Output to csv and graph 

    if not _cli:

        # Create folder for outputs 

        out_path = f"{filename}_repeats"
        
        if os.path.exists(out_path):
            tail = 1
            while os.path.exists(out_path):
                out_path = str(filename) + "_repeats_" + str(tail)
                tail += 1
            
        os.mkdir(out_path)

        # Define names for csv and graph output files

        out_csv = (out_path + "/repeat_lengths.csv")
        out_graph = (out_path + "/repeat_lengths_graph")

        # Export a csv from histogram

        headers = ["Repeat Length", "No. Records"]
        with open(out_csv, 'w') as outcsv:
            writer = csv.writer(outcsv)
            writer.writerow(headers)
            writer.writerows(histogram)

        # Sort by repeat size for plotting if previously by no. records
        
        if _sort:
            histogram = sorted(histogram, key=lambda x:x[0])

        # Write histogram to pandas dataframe for plotting 
        
        df_graph = pd.DataFrame(histogram, columns=headers)

        # Plot and export graph 

        df_graph.plot(x="Repeat Length", y="No. Records", legend=None)
        plt.title(Path(filename).name)
        plt.ylabel("No. Records")
        plt.savefig(out_graph)
    
def inter_find(args):

    #---------------------#
    # VARIABLE ASSIGNMENT #
    #---------------------#

    # Import FASTX file 
    
    if args.inFile:
        filename = args.inFile
        try:
            records_df = open_fastx(filename)[0]
        except Exception:
            sys.exit("\nERROR: Could not import file\n") # More detail / display raw error?
    else:
        sys.exit("\nERROR: Input file required\n")

    # Check repeat motif sequence has been provided, if so take as input, else return error

    if args.repmotif:
        repmotif = args.repmotif
    else:
        sys.exit("\nERROR: Repeat motif sequence is required\n")

    # Check if thresholding, if so take threshold as input 

    if args.threshold:
        try:
            threshold = float(args.threshold)
        except:
            sys.exit("\nERROR: Threshold allele frequency must be a number between 0 and 1\n")
        if threshold < 0 or threshold > 1:
            sys.exit("\nERROR: Threshold allele frequency must be a number between 0 and 1\n")
    else:
        threshold = 0.05
        
    # Check if using non-default specificity, if so take as input 
    
    if args.specificity:
        try:
            specificity = int(args.specificity)
        except:
            sys.exit("\nERROR: <min> must be a non-zero integer value\n")
        if specificity <= 0:
            sys.exit("\nERROR: <min> must be a non-zero integer value\n")
    else:
        specificity = 1

    # Check if using non-default minimum interruption size, if so take as input
        
    if args.minimum:
        try:
            mininter = int(args.minimum)
        except:
            sys.exit("\nERROR: <min> must be a non-zero integer value\n")
        if mininter <= 0:
            sys.exit("\nERROR: <min> must be a non-zero integer value\n")
    else:
        mininter = 1

    # Check if using non-default maximum interruption size, if so take as input
        
    if args.maximum:
        try:
            maxinter = int(args.maximum)
        except:
            sys.exit("\nERROR: <max> must be a non-zero integer value\n")
        if maxinter <= 0:
            sys.exit("\nERROR: <max> must be a non-zero integer value\n")
    else:
        maxinter = 3

    if mininter > maxinter:
        sys.exit("\nERROR: <min> cannot be greater than <max>\n")
    
    # Assign flags (True/None)
    
    _revcomp = args.revcomp
    _appearances = args.appearances
    _verbose = args.verbose
    _output = args.output
    _quiet = args.quiet
   
    if not _output and _quiet:
        print("\nNOTE: If --quiet is used without --output, no output will be given\n")
              
    #------#
    # MAIN #
    #------#

    # Build list of possible interruptions 
    
    bases = ['C', 'G', 'A', 'T']
    
    product_interruptions = []
    
    for inter_size in range(mininter, maxinter+1):
        iterator = product(bases, repeat=inter_size)
        for inter in iterator:
            product_interruptions.append(''.join(inter))
            
    interruptions = [i for i in product_interruptions if repmotif not in i]

    # Search for possible interruptions 
    
    outList = []
    
    num_records = len(records_df.index)    
    
    if _appearances:
            
        for inter in interruptions:
            
            total_appearances = 0
            records_containing = 0
            
            query = (repmotif * specificity) + inter + (repmotif * specificity)
        
            # finditer
            
            if _revcomp:
                query_rev = ''.join([complements[base] for base in query[::-1]])
                for seq in records_df['Seq']:
                    count = sum(1 for _ in finditer(('(?=' + query + ')'), seq)) + sum(1 for _ in finditer(('(?=' + query_rev + ')'), seq))
                    total_appearances += count
                    if count:
                        records_containing += 1 
            else:
                for seq in records_df['Seq']:
                    count = sum(1 for _ in finditer(('(?=' + query + ')'), seq))
                    total_appearances += count
                    if count:
                        records_containing += 1 

            freq = round(records_containing / (num_records), 5)
            
            if freq >= threshold:
                if _verbose:
                    outList.append([query, total_appearances, records_containing, freq])
                else:
                    outList.append([inter, total_appearances, records_containing, freq])

        headers = ['Inter', 'Appearances', 'Records', 'Freq']
        
    else:
    
        for inter in interruptions:
            
            records_containing = 0
            
            query = (repmotif * specificity) + inter + (repmotif * specificity)
            
            if _revcomp:
                query_rev = ''.join([complements[base] for base in query[::-1]])
                for seq in records_df['Seq']:
                    if query in seq or query_rev in seq:
                        records_containing += 1 
            else:
                for seq in records_df['Seq']:
                    if query in seq:
                        records_containing += 1 

            freq = round(records_containing / (num_records), 5)
            
            if freq >= threshold:
                if _verbose:
                    outList.append([query, records_containing, freq])
                else:
                    outList.append([inter, records_containing, freq])
                    
        headers = ['Inter', 'Records', 'Freq']
    
    outList = sorted(outList, key=lambda x:x[-1], reverse=True)

    if not _quiet:
        out_df = pd.DataFrame(outList, columns=headers)
        print(f"\n{out_df.to_string(index=False, justify='center')}\n")
    
    if _output:
        
        out_csv = f"{filename}_interfind.csv"

        with open(out_csv, 'w') as outcsv:
            writer =csv.writer(outcsv)
            writer.writerow(headers)
            writer.writerows(outList)
    
def inter_position(args):

    #---------------------#
    # VARIABLE ASSIGNMENT #
    #---------------------#

    # Import FASTX file 
    
    if args.inFile:
        filename = args.inFile
        try:
            records_df = open_fastx(filename)[0]
        except Exception:
            sys.exit("\nERROR: Could not import file\n") # More detail / display raw error?
    else:
        sys.exit("\nERROR: Input file required\n")
    
    # Check repeat motif sequence has been provided, if so take as input, else return error

    if args.repmotif:
        repmotif = args.repmotif
    else:
        sys.exit("\nERROR: Repeat motif sequence is required\n")
        
    # Check interruption sequence has been provided, if so take as input, else return error

    if args.inter:
        inter = args.inter
    else:
        sys.exit("\nERROR: Interruption sequence is required\n")
        
    # Assign flags (True/None)
    
    _revcomp = args.revcomp
    _verbose = args.verbose
    _graphcrop = args.graphcrop
    _quiet = args.quiet
    _cli = args.cli
    
    if _cli and _quiet:
        print("\nNOTE: If both --quiet and --cli are used, no output will be given\n")
    
    #------#
    # MAIN #
    #------#
    
    # GENERATE PRECEDING HISTOGRAM
        
    # Create list to store histogram
    
    pre_histogram = []
    
    # Create a set of all indexes 
    
    a = set([i for i in records_df.index])
    
    # Iterate up through no. preceding motifs until all indexes binned
    
    pre_motifs = 1
    
    while len(a):
        
        # For each index remaining in set a, check if it contains the current no. pre motifs
        # If yes, store in list b
        
        b = []
        
        query = (repmotif * pre_motifs) + inter + repmotif
                
        if _revcomp:
            query_rev = ''.join([complements[base] for base in query[::-1]])
            for index in a:
                record_seq = records_df['Seq'][index]
                if query in record_seq or query_rev in record_seq:
                    b.append(index)
        else:
            for index in a:
                record_seq = records_df['Seq'][index]
                if query in record_seq:
                    b.append(index)
    
        # No. indexes with pre motifs of (pre_motifs - 1) is equal to the decrease in indexes 
        # from previous iteration
    
        pre_histogram.append([pre_motifs - 1, len(a) - len(b)])
    
        # Use set intersection to remove all indexes not in b from a 
    
        a = a & set(b)
            
        # Move to next highest no. pre motifs 
        
        pre_motifs += 1 
    
    # Sort by no. preceding motifs 
        
    pre_histogram = sorted(pre_histogram, key=lambda x:x[0])

    # GENERATE FOLLOWING HISTOGRAM
    
    # See previous for comments 
    
    fol_histogram = []
    
    a = set([i for i in records_df.index])
    
    fol_motifs = 1
    
    while len(a):
            
        b = []
        
        query = repmotif + inter + (repmotif * fol_motifs)
                
        if _revcomp:
            query_rev = ''.join([complements[base] for base in query[::-1]])
            for index in a:
                record_seq = records_df['Seq'][index]
                if query in record_seq or query_rev in record_seq:
                    b.append(index)
        else:
            for index in a:
                record_seq = records_df['Seq'][index]
                if query in record_seq:
                    b.append(index)

        fol_histogram.append([fol_motifs - 1, len(a) - len(b)])
    
        a = a & set(b)
        
        fol_motifs += 1 
        
    fol_histogram = sorted(fol_histogram, key=lambda x:x[0])

    # OUTPUT TO CSV & GRAPHS
    
    # Create dataframes for exports & further analysis
    
    pre_headers = ["Preceding", "No. Records"]
    fol_headers = ["Following", "No. Records"]
    pre_df = pd.DataFrame(pre_histogram, columns=pre_headers)
    fol_df = pd.DataFrame(fol_histogram, columns=fol_headers)

    if not _cli:

        # Create folder for outputs 

        out_path = f"{filename}_interposition"
        
        if os.path.exists(out_path):
            tail = 1
            while os.path.exists(out_path):
                out_path = str(filename) + "_interposition_" + str(tail)
                tail += 1
            
        os.mkdir(out_path)
        
        # Output to csv 
        
        pre_csv = (out_path + "/pre.csv")    
        with open(pre_csv, 'w') as outcsv:
            writer = csv.writer(outcsv)
            writer.writerow(pre_headers)
            writer.writerows(pre_histogram)
        
        fol_csv = (out_path + "/fol.csv")
        with open(fol_csv, 'w') as outcsv:
            writer = csv.writer(outcsv)
            writer.writerow(pre_headers)
            writer.writerows(fol_histogram)

        # Output to graph
            
        pre_graph = (out_path + "/pre_graph")
        if _graphcrop:
            pre_df[1:].plot(x="Preceding", y="No. Records", legend=None)
        else:
            pre_df.plot(x="Preceding", y="No. Records", legend=None)
        plt.title(Path(filename).name)
        plt.ylabel("No. Records")
        plt.savefig(pre_graph)
        
        fol_graph = (out_path + "/fol_graph")    
        if _graphcrop:
            fol_df[1:].plot(x="Following", y="No. Records", legend=None)
        else:
            fol_df.plot(x="Following", y="No. Records", legend=None)
        plt.title(Path(filename).name)
        plt.ylabel("No. Records")
        plt.savefig(fol_graph)   

    # Output to CLI
    
    if not _quiet:
    
        total_records = pre_df['No. Records'].sum()
        num_uninterrupted = (pre_df['No. Records'][0])
        num_interrupted = (total_records - num_uninterrupted) 
        freq_interrupted = num_interrupted / total_records
        
        pre_modal = pre_df['No. Records'][1:].idxmax()
        fol_modal = fol_df['No. Records'][1:].idxmax()
        
        a = 0
        b = 0
        for seq in records_df['Seq']:
            query = (pre_modal * repmotif) + inter + (fol_modal * repmotif)
            if _revcomp:
                query_rev = ''.join([complements[base] for base in query[::-1]])
                if query in seq or query_rev in seq:
                    a += 1 
                    query_extra_pre = ((pre_modal + 1) * repmotif) + inter + (fol_modal * repmotif)
                    query_extra_pre_rev = ''.join([complements[base] for base in query[::-1]])
                    query_extra_fol = (pre_modal * repmotif) + inter + ((fol_modal + 1) * repmotif)
                    query_extra_fol_rev = ''.join([complements[base] for base in query[::-1]])
                    if query_extra_pre in seq or query_extra_fol in seq or query_extra_pre_rev in seq or query_extra_fol_rev in seq:
                        b += 1             
            else:
                if query in seq:
                    a += 1 
                    query_extra_pre = ((pre_modal + 1) * repmotif) + inter + (fol_modal * repmotif)
                    query_extra_fol = (pre_modal * repmotif) + inter + ((fol_modal + 1) * repmotif)
                    if query_extra_pre in seq or query_extra_fol in seq:
                        b += 1 

        num_modal_pattern = a-b

        freq_modal_pattern = num_modal_pattern / num_interrupted
        
        if _verbose:           
            print(f"\n{pre_df.sort_values('No. Records', ascending=False).head(10).to_string(index=False, justify='center')}")
            print(f"\n{fol_df.sort_values('No. Records', ascending=False).head(10).to_string(index=False, justify='center')}")
            
        print(f"\nRecords with interruption:   {freq_interrupted:.2%}")    
        print(f"Modal no. preceding motifs:  {pre_modal} ({pre_df['No. Records'][pre_modal]/num_interrupted:.2%} of interrupted)")
        print(f"Modal no. following motifs:  {fol_modal} ({fol_df['No. Records'][fol_modal]/num_interrupted:.2%} of interrupted)")
        print(f"Modal interruption pattern:  ({repmotif}){pre_modal}({inter})({repmotif}){fol_modal} ({freq_modal_pattern:.2%} of interrupted)\n")
    
def inter_pattern(args):
    
    #----------------#
    # IN DEVELOPMENT #
    #----------------#
    
    show_help(args)
    
def inter_correlation(args):
    
    #----------------#
    # IN DEVELOPMENT #
    #----------------#
    
    show_help(args)
    
def show_help(args=None):
    
    if not args: 
        print(help_main)
    elif args.subparser_name == "info":
        print(help_info)
    elif args.subparser_name == "search":
        print(help_search)
    elif args.subparser_name == "filter":
        print(help_filter)
    elif args.subparser_name == "repeats":
        print(help_repeats)
    elif args.subparser_name == "inter":
        if args.inter_subsubparser_name == "find":
            print(help_inter_find)
        elif args.inter_subsubparser_name == "position":
            print(help_inter_position)
        elif args.inter_subsubparser_name == "pattern":
            print(help_inter_pattern)
        elif args.inter_subsubparser_name == "correlation":
            print(help_inter_correlation)
        else:
            print(help_inter)      
    else:    
        print(help_main)
    sys.exit()

if __name__ == "__main__":
    
    main()