#!/opt/conda/conda-bld/bamm_1757987134492/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehol/bin/python
###############################################################################
#                                                                             #
#    bamm                                                                     #
#                                                                             #
#    Get info from the BAM                                                    #
#                                                                             #
#    Copyright (C) Michael Imelfort                                           #
#                                                                             #
###############################################################################
#                                                                             #
#    This library is free software; you can redistribute it and/or            #
#    modify it under the terms of the GNU Lesser General Public               #
#    License as published by the Free Software Foundation; either             #
#    version 3.0 of the License, or (at your option) any later version.       #
#                                                                             #
#    This library is distributed in the hope that it will be useful,          #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of           #
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU        #
#    Lesser General Public License for more details.                          #
#                                                                             #
#    You should have received a copy of the GNU Lesser General Public         #
#    License along with this library.                                         #
#                                                                             #
###############################################################################

__author__ = "Michael Imelfort, Ben Woodcroft, Donovan Parks, Tim Lamberton"
__copyright__ = "Copyright 2014,2015"
__credits__ = ["Michael Imelfort","Ben Woodcroft", "Donovan Parks",
               "Tim Lamberton"]
__license__ = "LGPLv3"
__maintainer__ = "Michael Imelfort"
__email__ = "mike@mikeimelfort.com"
__status__ = "Beta"

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

# system imports
import argparse
import sys
import gzip
import mimetypes
import os

# local imports
try:
    import bamm
except ImportError:
    sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)),'..'))

from bamm import __version__
from bamm.bamParser import BamParser
from bamm.bamFile import BM_coverageType
from bamm.bamMaker import BamScheduler
from bamm.bamExtractor import BamExtractor
from bamm.bamFilter import BamFilter
from bamm.bamProfiler import BamProfiler
from bamm.bammExceptions import printError, printShortUsage, \
    InvalidParameterSetException, BAMFileNotFoundException
from bamm.cWrapper import CT

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

BM_def_trimmed_range = 10.
BM_def_outlier_range = 1.

def which(program):
    '''from: http://stackoverflow.com/questions/377017/test-if-executable-exists-in-python'''
    def is_exe(fpath):
        return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

    fpath, _ = os.path.split(program)
    if fpath:
        if is_exe(program):
            return program
    else:
        for path in os.environ["PATH"].split(os.pathsep):
            path = path.strip('"')
            exe_file = os.path.join(path, program)
            if is_exe(exe_file):
                return exe_file
    return None

def doWork( args ):
    ''' Wrapper for global workflows

    this is where the program forks off into make, parse and extract modes

    Inputs:
     args - argparse.args containing user preferences

    Outputs:
     None
    '''
    if(args.subparser_name == 'make'):
        # let's see if samtools and bwa are on the path
        error = False
        if not which('bwa'):
            printError("'bwa' not found on the path.\nPATH is: %s" %
                       os.environ["PATH"])
            error = True
        if not which('samtools'):
            printError("'samtools' not found on the path.\nPATH is: %s" %
                       os.environ["PATH"])
            error = True
        if error:
            printShortUsage('make')
            sys.exit(1)

        # The BamMaker class is able to take care of making fileNames etc
        # this outer wrapper is here to allow us to make multiple BAM files
        # in one go
        # making the class will take care of filenames and make sure that
        # all the parameters are set nicely
        try:
            BS = BamScheduler(args.database,
                              args.alignment_algorithm,
                              args.index_algorithm,
                              os.path.realpath(args.out_folder),
                              paired=args.coupled,
                              interleaved=args.interleaved,
                              singleEnded=args.single,
                              prefix=args.prefix,
                              keptFiles=args.kept,
                              keepFiles=args.keep,
                              outputTam=args.output_tam,
                              keepUnmapped=args.keep_unmapped,
                              numThreads=args.threads,
                              maxMemory=args.memory,
                              forceOverwriting=args.force,
                              extraArguments=args.extras,
                              showCommands=args.show_commands,
                              quiet=args.quiet,
                              silent=args.silent,
                              tmpdir=args.temporary_directory
                              )
        except InvalidParameterSetException as e:
            printError(e)
            printShortUsage('make')
            sys.exit(2)
        except AttributeError as e:
            printShortUsage('make')
            sys.exit(3)

        # create indexes if required
        if(args.kept is False):
            BS.BMs[0].makeDatabase()

        # Now make the TAM/BAM file
        BS.makeBams()

        # clean up if we need to
        if args.keep is False and args.kept is False :
            BS.BMs[0].removeDatabase()

    elif(args.subparser_name == 'parse'):

        if args.bamfiles is None:
            printError("You must supply come bam files to parse")
            printShortUsage('parse')
            sys.exit(1)

        # convert the coverage mode. We know it's legit cause argparse said so
        ct = CT.NONE
        cr = args.cutoff_range
        if args.coverage_mode == "counts":
            ct = CT.COUNT
        elif args.coverage_mode == "cmean":
            ct = CT.C_MEAN
        elif args.coverage_mode == "pmean":
            ct = CT.P_MEAN
        elif args.coverage_mode == "pmedian":
            ct = CT.P_MEDIAN
        elif args.coverage_mode == "tpmean":
            ct = CT.P_MEAN_TRIMMED
            if cr is None:
                cr = [BM_def_trimmed_range]
        elif args.coverage_mode == "opmean":
            ct = CT.P_MEAN_OUTLIER
            if cr is None:
                cr = [BM_def_outlier_range]
        elif args.coverage_mode == 'pvariance':
            ct = CT.P_VARIANCE
        elif args.coverage_mode == "ppc":
            ct = CT.MAPPED_MEAN
        elif args.coverage_mode == "tppc":
            ct = CT.MAPPED_MEAN_TRIMMED
            if cr is None:
                cr = [BM_def_trimmed_range]

        # set this to 0 here so we don't confuse matters downstream
        if cr is None:
            cr = [0.]

        if len(cr) == 1:
            BCT = BM_coverageType(ct, cr[0], cr[0])
        elif len(cr) == 2:
            BCT = BM_coverageType(ct, cr[0], cr[1])
        else:
            printError("cutoff_range takes at most two arguments")
            printShortUsage('parse')
            sys.exit(1)
        BP = BamParser(BCT,
                       minLength=args.length,
                       baseQuality=args.base_quality,
                       mappingQuality=args.mapping_quality,
                       maxMisMatches=args.max_distance,
                       useSuppAlignments=args.use_supplementary,
                       useSecondaryAlignments=args.use_secondary)

        # if called with no mode then just print types to stdout
        if (args.links == "") and (args.coverages == ""):
            doInserts = True
        else:
            doInserts = (args.inserts != "")

        try:
            ret = BP.parseBams(args.bamfiles,
                               doLinks=(args.links != ""),
                               doCovs=(args.coverages != ""),
                               types=args.num_types,
                               threads=args.threads,
                               verbose=args.verbose)
        except BAMFileNotFoundException as e:
            printError(e)
            printShortUsage('parse')
            sys.exit(1)

        if ret == 0:
            # print nice stuff out as required
            if doInserts:
                BP.printBamTypes(args.inserts)
            if args.links != "":
                BP.printLinks(args.links)
            if args.coverages != "":
                BP.printCoverages(args.coverages)

    elif(args.subparser_name == 'extract'):

        if args.groups == [] or args.bamfiles == []:
            printError('-l and -b arguments are mandatory')
            printShortUsage('extract')
            sys.exit(1)

        try:
            # get the groups in list form, regardless of how they were passed
            groups = makeGroupLists(args.groups)

            # now we can make an extractor
            BE = BamExtractor(groups,
                              args.bamfiles,
                              prefix=args.prefix,
                              groupNames=[ \
                                  os.path.basename(group).replace(".bam", "")
                                  for group in args.groups],
                              outFolder=args.out_folder,
                              mixBams=args.mix_bams,
                              mixGroups=args.mix_groups,
                              mixReads=args.mix_reads,
                              interleaved=args.interleave,
                              bigFile=args.no_gzip,
                              headersOnly=args.headers_only,
                              minMapQual=args.mapping_quality,
                              maxMisMatches=args.max_distance,
                              useSuppAlignments=args.use_supplementary,
                              useSecondaryAlignments=args.use_secondary)

        except InvalidParameterSetException as e:
            printError(e)
            printShortUsage('extract')
            sys.exit(1)

        # and then extract!
        BE.extract(args.threads, args.verbose)

    elif(args.subparser_name == 'filter'):

        if args.bamfile is None:
            printError("You must supply a bam file to filter")
            subparsers.choices['filter'].print_help()
            sys.exit(1)

        try:
            BF = BamFilter(args.bamfile,
                    outFolder=args.out_folder,
                    minMapQual=args.mapping_quality,
                    minLength=args.length,
                    maxMisMatches=args.max_distance,
                    minPcId=args.percentage_id,
                    minPcAln=args.percentage_aln,
                    invertMatch=args.invert_match,
                    useSuppAlignments=args.use_supplementary,
                    useSecondaryAlignments=args.use_secondary)
            BF.filter()

        except InvalidParameterSetException as e:
            printError(e)
            subparsers.choices['filter'].print_help()
            sys.exit(1)
                
    elif(args.subparser_name == 'profile'):

        if args.bamfile is None:
            printError("You must supply a bam file to profile")
            subparsers.choices['profile'].print_help()
            sys.exit(1)

        try:
            BP = BamProfiler(args.bamfile,
                    useSuppAlignments=args.use_supplementary,
                    useSecondaryAlignments=args.use_secondary)
                                        
            BP.profile()

        except InvalidParameterSetException as e:
            printError(e)
            subparsers.choices['profile'].print_help()
            sys.exit(1)
                                
    else:
        printError("Programming error: unexpected args subparser '%s' encountered" % args.subparser_name)
        sys.exit(1)

def makeGroupLists(groups):
    '''Convert a (mixed) collection of fasta files and contigs lists into
    a format that is suitable for the extractor

    Inputs:
     groups - [ fileName ], array of full paths to files containing fasta
              sequences or a contig list

    Outputs:
     An array of arrays of contig identifiers. Each internal array represents
     a distinct group
    '''
    # work out if the groups are lists of contig IDs or just contigs
    # assume that if the file is fasta then the first character will be ">"
    # otherwise it must be a list
    group_lists = []
    for g in groups:
        try:
            read_open = open
            # handle gzipped files
            mime = mimetypes.guess_type(g)
            if mime[1] == 'gzip':
                read_open = gzip.open
        except:
            raise InvalidParameterSetException( \
                'Error when guessing groups file mimetype')

        with read_open(g, "r") as t_fh:
            tmp_groups = []
            first_line = t_fh.readline()
            try:
                if first_line[0] == ">":
                    t = first_line.rstrip()[1:]
                    if t != "":
                        tmp_groups.append(t)
                    for line in t_fh:
                        if line[0] == ">":
                            t = line.rstrip()[1:]
                            if t != "":
                                tmp_groups.append(t)
                else:
                    t = first_line.rstrip()
                    if t != "":
                        tmp_groups.append(t)
                    for line in t_fh:
                        t = line.rstrip()
                        if t != "":
                            tmp_groups.append(t)

                if len(tmp_groups) == 0:
                    raise InvalidParameterSetException( \
                        'No groups in list: %s' % g)
                group_lists.append(tmp_groups)
            except:
                raise InvalidParameterSetException( \
                    'Something is wrong with the supplied groups file')

    if len(group_lists) == 0:
        raise InvalidParameterSetException('No valid groups supplied')

    return group_lists

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

class CustomHelpFormatter(argparse.HelpFormatter):
    def _split_lines(self, text, width):
        return text.splitlines()

    def _get_help_string(self, action):
        h = action.help
        if '%(default)' not in action.help:
            if action.default != '' and \
               action.default != [] and \
               action.default != None \
               and action.default != False:
                if action.default is not argparse.SUPPRESS:
                    defaulting_nargs = [argparse.OPTIONAL,
                                        argparse.ZERO_OR_MORE]

                    if action.option_strings or action.nargs in defaulting_nargs:

                        if '\n' in h:
                            lines = h.splitlines()
                            lines[0] += ' (default: %(default)s)'
                            h = '\n'.join(lines)
                        else:
                            h += ' (default: %(default)s)'
        return h

    def _fill_text(self, text, width, indent):
        return ''.join([indent + line for line in text.splitlines(True)])

def printHelp():
    print('''\

                              ...::: BamM :::...

                    Working with the BAM, not against it...

   -------------------------------------------------------------------------
                                  version: %s
   -------------------------------------------------------------------------

    bamm make     ->  Make BAM/TAM files (sorted + indexed)
    bamm parse    ->  Get coverage profiles / linking reads / insert types
    bamm extract  ->  Extract reads / headers from BAM files
    bamm filter   ->  Filter BAM file reads

    USE: bamm OPTION -h to see detailed options
    ''' % __version__)

if __name__ == '__main__':
    #-------------------------------------------------
    # intialise the options parser
    parser = argparse.ArgumentParser(add_help=False)
    subparsers = parser.add_subparsers(help="--", dest='subparser_name')

    #-------------------------------------------------
    # make a BAM file
    make_parser = subparsers.add_parser('make',
                                        formatter_class=CustomHelpFormatter,
                                        help='make a BAM/TAM file (sorted + indexed)',
                                        add_help=False,
                                        description='make a BAM/TAM file (sorted + indexed)',
                                        epilog='''Example usage:

 Produce a sorted, indexed BAM file with reads mapped onto contigs.fa using 40 threads
   bamm make -d contigs.fa -i reads1_interleaved.fq.gz reads2_interleaved.fq.gz -c reads3_1.fq.gz reads3_2.fq.gz -t 40

 Produce a 3 sorted, indexed BAM files with reads mapped onto contigs.fa.gz
   bamm make -d contigs.fa.gz -i reads1_interleaved.fq.gz reads2_interleaved.fq.gz -s reads4_singles.fq.gz

Extra arguments are passed on a "per-mode" basis using the format:

    <BWA_MODE>:'<ARGS>'

For example, the argument:

    --extras "mem:-k 25"

tells bwa mem to use a minimum seed length of 25bp.
Multiple modes are separated by commas. For example:

    --extras "aln:-O 12 -E 3,sampe:-n 15"

Passes the arguments "-O 12 -E 3" to bwa aln and the arguments "-n 15" to bwa sampe.

********************************************************************************
*** WARNING ***
********************************************************************************

Values passed using --extras are not checked by BamM. This represents a
significant security risk if BamM is being run with elevated privileges.
Thus you should NEVER run 'bamm make' as root or some other powerful user,
ESPECIALLY if you are providing access to multiple / unknown users.

********************************************************************************
''')

    make_req_args = make_parser.add_argument_group('required argument')
    make_req_args.add_argument("-d", "--database", help="contigs to map onto (in fasta format)", required=True)

    make_reads_args = make_parser.add_argument_group('reads to map (specify one or more arguments)')
    make_reads_args.add_argument("-i", "--interleaved", nargs='+', default=[], help="map interleaved sequence files (as many as you like) EX: -i reads1_interleaved.fq.gz reads2_interleaved.fna")
    make_reads_args.add_argument("-c", "--coupled", nargs='+', default=[], help="map paired sequence files (as many sets as you like) EX: -c reads1_1.fq.gz reads1_2.fq.gz reads2_1.fna reads2_2.fna")
    make_reads_args.add_argument("-s", "--single", nargs='+', default=[], help="map Single ended sequence files (as many as you like) EX: -s reads1_singles.fq.gz reads2_singles.fna")

    make_opt_args = make_parser.add_argument_group('optional arguments')
    make_opt_args.add_argument('-p', '--prefix', default='', help="prefix to apply to BAM/TAM files (None for reference name)")
    make_opt_args.add_argument('-o', '--out_folder', default=".", help="write to this folder")

    make_opt_args.add_argument("--index_algorithm", default=None, help="algorithm bwa uses for indexing 'bwtsw' or 'is' [None for auto]")
    make_opt_args.add_argument("--alignment_algorithm", default="mem", help="algorithm bwa uses for alignment 'mem', 'bwasw' or 'aln'")
    make_opt_args.add_argument("--extras", default="", help='extra arguments to use during mapping. Format is "<BWA_MODE1>:\'<ARGS>\',<BWA_MODE2>:\'<ARGS>\'"')

    make_opt_args.add_argument("-k", "--keep", action="store_true", default=False,
                           help="keep all generated database index files (see also --kept)")
    make_opt_args.add_argument("-K", "--kept", action="store_true", default=False,
                           help="assume database indices already exist (e.g. previously this script was run with -k/--keep)")
    make_opt_args.add_argument("-f", "--force", action="store_true", default=False,
                           help="force overwriting of index files if they are present")

    make_opt_args.add_argument("--output_tam", action="store_true", default=False, help="output TAM file instead of BAM file")
    make_opt_args.add_argument("-u", "--keep_unmapped", action="store_true", default=False, help="Keep unmapped reads in BAM output")

    make_opt_args.add_argument("-t", "--threads", type=int, default=1, help="maximum number of threads to use")
    make_opt_args.add_argument("-m", "--memory", default=None, help="maximum amount of memory to use per samtools sort thread (default 2GB). Suffix K/M/G recognized")
    make_opt_args.add_argument("--temporary_directory", default=None, help="temporary directory for working with BAM files (default do not use)")
    make_opt_args.add_argument('-h', '--help', action="help", help="show this help message and exit")

    make_opt_args.add_argument('--show_commands', action="store_true", default=False, help="show all commands being run")
    make_opt_args.add_argument('--quiet', action="store_true", default=False, help="suppress output from the mapper")
    make_opt_args.add_argument('--silent', action="store_true", default=False, help="suppress all output")

    #-------------------------------------------------
    # determine linking reads
    parse_parser = subparsers.add_parser('parse',
                                        formatter_class=CustomHelpFormatter,
                                        help='get bamfile type and/or coverage profiles and/or linking reads',
                                        add_help=False,
                                        description='get bamfile type and/or coverage profiles and/or linking reads',
                                        epilog='''Example usage:

 Calculate insert distribution, print to STDOUT
   bamm parse -b my.bam

 Calculate contig-wise coverage
   bamm parse -b my.bam -c output_coverage.tsv

 Calculate linking information on 2 bam files
   bamm parse -b first.bam second.bam -l output_links.tsv

Coverage calculation modes:
* opmean:    outlier pileup coverage: average of reads overlapping each base,
             after bases with coverage outside mean +/- 1 standard deviation
             have been excluded. The number of standard deviation used for the
             cutoff can be changed with --cutoff_range.
* pmean:     pileup coverage: average of number of reads overlapping each base
* tpmean:    trimmed pileup coverage: average of reads overlapping each base,
             after bases with in the top and bottom 10% have been excluded. The
             10% range can be changed using --cutoff_range and should be
             specified as a percentage (e.g., 15 for 15%).
* counts:    absolute number of reads mapping
* cmean:     like 'counts' except divided by the length of the contig
* pmedian:   median pileup coverage: median of number of reads overlapping each
             base
* pvariance: variance of pileup coverage: variance of number of reads
             overlapping each base
* ppc:       percentage of bases covered by pileup
* tppc:      percentage of trimmed bases coverged by pileup (see tpmean)

The 'cutoff_range' variable is used for trimmed mean and outlier mean. This
parameter takes at most two values. The first is the lower cutoff and the
second is the upper. If only one value is supplied then lower == upper.'''
                                        )

    parser_req_args = parse_parser.add_argument_group('required argument')
    parser_req_args.add_argument('-b', '--bamfiles', nargs='+', help="bam files to parse", required=True)

    parser_opt_args = parse_parser.add_argument_group('optional arguments')
    parser_opt_args.add_argument('-c', '--coverages', help="filename to write coverage profiles to [default: don't calculate coverage]", default="")
    parser_opt_args.add_argument('-l', '--links', help="filename to write pairing links to [default: don't calculate links]", default="")
    parser_opt_args.add_argument('-i', '--inserts', help="filename to write bamfile insert distributions to [default: print to STDOUT]", default="")

    parser_opt_args.add_argument('-n', '--num_types', nargs='+', help="number of insert/orientation types per BAM", type=int)
    parser_opt_args.add_argument('-m', '--coverage_mode', help="how to calculate coverage (requires --coverages)", default='pmean',
        choices=['pmean', 'opmean', 'tpmean', 'counts', 'cmean', 'pmedian', 'pvariance', 'ppc', 'tppc'])
    parser_opt_args.add_argument('-r', '--cutoff_range', nargs='+', type=float, help="range used to calculate upper and lower bounds when calculating coverage", default=None)

    parser_opt_args.add_argument('--length', help="minimum Q length", type=int, default=50)
    parser_opt_args.add_argument('--base_quality', help="base quality threshold (Qscore)", type=int, default=20)
    parser_opt_args.add_argument('--mapping_quality', help="mapping quality threshold", type=int, default=0)
    parser_opt_args.add_argument('--max_distance', type=int, default=1000, help="maximum allowable edit distance from query to reference")
    parser_opt_args.add_argument('--use_secondary', action="store_true", default=False, help="use reads marked with the secondary flag")
    parser_opt_args.add_argument('--use_supplementary', action="store_true", default=False, help="use reads marked with the supplementary flag")

    parser_opt_args.add_argument('-v', '--verbose', action="store_true", default=False, help="be verbose")
    parser_opt_args.add_argument('-t', '--threads', help="maximum number of threads to use", type=int, default=1)
    parser_opt_args.add_argument('-h', '--help', action="help", help="show this help message and exit")

    #-------------------------------------------------
    # read extractor
    extract_parser = subparsers.add_parser('extract',
                                        formatter_class=CustomHelpFormatter,
                                        help='extract reads from bamfile(s)',
                                        add_help=False,
                                        description='Extract reads which hit the given references')
    extract_req_args = extract_parser.add_argument_group('required arguments')
    extract_req_args.add_argument('-b', '--bamfiles', nargs='+', default=[], help="bam files to parse", required=True)
    extract_req_args.add_argument('-g', '--groups', nargs='+', default=[], help="files containing reference names (1 per line) or contigs file in fasta format", required=True)

    extract_opt_args = extract_parser.add_argument_group('optional arguments')
    extract_opt_args.add_argument('-p', '--prefix', default="", help="prefix to apply to output files")
    extract_opt_args.add_argument('-o', '--out_folder', default=".", help="write to this folder")

    extract_opt_args.add_argument('--mix_bams', action="store_true", default=False, help="use the same file for multiple bam files")
    extract_opt_args.add_argument('--mix_groups', action="store_true", default=False, help="use the same files for multiple groups")
    extract_opt_args.add_argument('--mix_reads', action="store_true", default=False, help="use the same files for paired/unpaired reads")
    extract_opt_args.add_argument('--interleave', action="store_true", default=False, help="interleave paired reads in ouput files")

    extract_opt_args.add_argument('--mapping_quality', help="mapping quality threshold", type=int, default=0)
    extract_opt_args.add_argument('--use_secondary', action="store_true", default=False, help="use reads marked with the secondary flag")
    extract_opt_args.add_argument('--use_supplementary', action="store_true", default=False, help="use reads marked with the supplementary flag")
    extract_opt_args.add_argument('--max_distance', type=int, default=1000, help="maximum allowable edit distance from query to reference")

    extract_opt_args.add_argument('--no_gzip', action="store_true", default=False, help="do not gzip output files")
    extract_opt_args.add_argument('--headers_only', action="store_true", default=False, help="extract only (unique) headers")

    extract_opt_args.add_argument('-v', '--verbose', action="store_true", default=False, help="be verbose")
    extract_opt_args.add_argument('-t', '--threads', help="maximum number of threads to use", type=int, default=1)
    extract_opt_args.add_argument('-h', '--help', action="help", help="show this help message and exit")

    #-------------------------------------------------
    # read filter
    filter_parser = subparsers.add_parser('filter',
                                        formatter_class=CustomHelpFormatter,
                                        help='filter bamfile',
                                        add_help=False,
                                        description='Apply stringency filter to Bam file reads')
    filter_req_args = filter_parser.add_argument_group('required arguments')
    filter_req_args.add_argument('-b', '--bamfile', default="", help="bam file to filter", required=True)

    filter_opt_args = filter_parser.add_argument_group('optional arguments')
    filter_opt_args.add_argument('-o', '--out_folder', default=".", help="write to this folder (output file has '_filtered.bam' suffix)")

    filter_opt_args.add_argument('--mapping_quality', help="mapping quality threshold", type=int, default=0)
    filter_opt_args.add_argument('--max_distance', type=int, default=1000, help="maximum allowable edit distance from query to reference")
    filter_opt_args.add_argument('--length', help="minimum allowable query length", type=int, default=0)
    filter_opt_args.add_argument('--percentage_id', help="minimum base identity of mapped region (between 0 and 1)", type=float, default=0.)
    filter_opt_args.add_argument('--percentage_aln', help="minimum fraction of read mapped (between 0 and 1)", type=float, default=0.)
    filter_opt_args.add_argument('--use_secondary', action="store_true", default=False, help="use reads marked with the secondary flag")
    filter_opt_args.add_argument('--use_supplementary', action="store_true", default=False, help="use reads marked with the supplementary flag")
    filter_opt_args.add_argument('-v', '--invert_match', action="store_true", default=False, help="select unmapped reads")

    filter_opt_args.add_argument('-h', '--help', action="help", help="show this help message and exit")

    #-------------------------------------------------
    # read profiler
    profile_parser = subparsers.add_parser('profile',
                                        formatter_class=CustomHelpFormatter,
                                        help='profile bamfile',
                                        add_help=False,
                                        description='Show quality metrics for Bam file reads')
    profile_req_args = profile_parser.add_argument_group('required arguments')
    profile_req_args.add_argument('-b', '--bamfile', default="", help="bam file to filter", required=True)

    profile_opt_args = profile_parser.add_argument_group('optional arguments')
    profile_opt_args.add_argument('--use_secondary', action="store_true", default=False, help="use reads marked with the secondary flag")
    profile_opt_args.add_argument('--use_supplementary', action="store_true", default=False, help="use reads marked with the supplementary flag")

    profile_opt_args.add_argument('-h', '--help', action="help", help="show this help message and exit")

    #-------------------------------------------------
    # get and check options
    args = None
    if(len(sys.argv) == 1):
        printHelp()
        sys.exit(0)

    elif(sys.argv[1] == '-v' or \
         sys.argv[1] == '--v' or \
         sys.argv[1] == '-version' or \
         sys.argv[1] == '--version'):
        print("BamM: version %s %s %s" % (__version__,
                                          __copyright__,
                                          __author__))
        sys.exit(0)
    elif(sys.argv[1] == '-h' or \
         sys.argv[1] == '--h' or \
         sys.argv[1] == '-help' or \
         sys.argv[1] == '--help'):
        printHelp()
        sys.exit(0)
    else:
        args = parser.parse_args()

    # profiling happens here. If you'd like to track the speed your code runs at
    # then set the following to True and voila!
    if(False):
        import cProfile
        cProfile.run('doWork(args)', 'profile')
        ##########################################
        ##########################################
        # Use this in python console!
        #import pstats
        #p = pstats.Stats('prof')
        #p.sort_stats('cumulative').print_stats(10)
        #p.sort_stats('time').print_stats(10)
        ##########################################
        ##########################################
    else:
        doWork(args)

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

