#!/usr/bin/env python
import os.path
import sys
import tempfile
import argparse
import gemini.version

def add_inheritance_args(parser, min_kindreds=1, depth=True, gt_ll=False,
        allow_unaffected=True, lenient=True, gq=True):
    """Common arguments added to various sub-parsers"""
    parser.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser.add_argument('--columns',
            dest='columns',
            metavar='STRING',
            help='A list of columns that you would like returned. Def. = "*"',
            )
    parser.add_argument('--filter',
            dest='filter',
            metavar='STRING',
            help='Restrictions to apply to variants (SQL syntax)')
    parser.add_argument('--min-kindreds',
            dest='min_kindreds',
            type=int,
            default=min_kindreds,
            help='The min. number of kindreds that must have a candidate variant in a gene.')
    parser.add_argument('--families',
            dest='families',
            help='Restrict analysis to a specific set of 1 or more (comma) separated) families',
            default=None)

    if lenient:
        parser.add_argument("--lenient",
                default=False,
                action="store_true",
                help="Loosen the restrictions on family structure")

    if allow_unaffected:
        parser.add_argument('--allow-unaffected',
                action='store_true',
                help='Report candidates that also impact samples labeled as unaffected.',
                default=False)

    # this is for comp_het, eventually, we could add depth support to that tool.
    if depth:
        parser.add_argument('-d',
            dest='min_sample_depth',
            type=int,
            help="The minimum aligned\
              sequence depth required for\
              each sample in a family (default = 0)",
            default=0)
    if gq:
        parser.add_argument('--min-gq',
            type=int,
            help="The minimum genotype quality required for \
               each sample in a family (default = 0)",
            default=0)

    if gt_ll:
        parser.add_argument('--gt-pl-max',
                            dest='gt_phred_ll',
                            type=int,
                            help="The maximum phred-scaled genotype likelihod"
                                 " (PL) allowed for each sample in a family.",
                            default=None)

def examples(parser, args):

    print("")
    print( "[load] - load a VCF file into a gemini database:")
    print( "   gemini load -v my.vcf my.db")
    print( "   gemini load -v my.vcf -t snpEff my.db")
    print( "   gemini load -v my.vcf -t VEP my.db")
    print("")

    print( "[stats] - report basic statistics about your variants:")
    print( "   gemini stats --tstv my.db")
    print( "   gemini stats --tstv-coding my.db")
    print( "   gemini stats --sfs my.db")
    print( "   gemini stats --snp-counts my.db")
    print("")

    print( "[query] - explore the database with ad hoc queries:")
    print( "   gemini query -q \"select * from variants where is_lof = 1 and aaf <= 0.01\" my.db")
    print( "   gemini query -q \"select chrom, pos, gt_bases.NA12878 from variants\" my.db")
    print( "   gemini query -q \"select chrom, pos, in_omim, clin_sigs from variants\" my.db")
    print("")

    print( "[dump] - convenient \"data dumps\":")
    print( "   gemini dump --variants my.db")
    print( "   gemini dump --genotypes my.db")
    print( "   gemini dump --samples my.db")
    print("")

    print( "[region] - access variants in specific genomic regions:")
    print( "   gemini region --reg chr1:100-200 my.db")
    print( "   gemini region --gene TP53 my.db")
    print("")

    print( "[tools] - there are also many specific tools available")
    print( "   1. Find compound heterozygotes.")
    print( "     gemini comp_hets my.db")
    print("")

    exit()

def main():
    #########################################
    # create the top-level parser
    #########################################
    parser = argparse.ArgumentParser(prog='gemini', formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("-v", "--version", help="Installed gemini version",
                        action="version",
                        version="%(prog)s " + str(gemini.version.__version__))
    parser.add_argument('--annotation-dir', dest='annotation_dir',
                             help='Path to the annotation database.\n'
                                'This argument is optional and if given will take precedence over the default location stored in the gemini config file.')
    subparsers = parser.add_subparsers(title='[sub-commands]', dest='command')

    #########################################
    # $ gemini examples
    #########################################
    parser_examples = subparsers.add_parser('examples',
                                            help='show usage examples')
    parser_examples.set_defaults(func=examples)

    #########################################
    # $ gemini load
    #########################################
    parser_load = subparsers.add_parser('load',
                                        help='load a VCF file in gemini database')
    parser_load.add_argument('db', metavar='db',
                             help='The name of the database to be created.')
    parser_load.add_argument('-v', dest='vcf',
                             help='The VCF file to be loaded.')
    parser_load.add_argument('-t', dest='anno_type',
                             default=None, choices=["snpEff", "VEP",  "BCFT", "all"],
                             help="The annotations to be used with the input vcf.")
    parser_load.add_argument('-p', dest='ped_file',
                             help='Sample information file in PED+ format.',
                             default=None)
    parser_load.add_argument('--skip-gerp-bp',
                             dest='skip_gerp_bp',
                             action='store_true',
                             help='Do not load GERP scores at base pair resolution. Loaded by default.',
                             default=False)
    parser_load.add_argument('--skip-cadd',
                             dest='skip_cadd',
                             action='store_true',
                             help='Do not load CADD scores. Loaded by default',
                             default=False)
    parser_load.add_argument('--skip-gene-tables',
                             dest='skip_gene_tables',
                             action='store_true',
                             help='Do not load gene tables. Loaded by default.',
                             default=False)
    parser_load.add_argument('--save-info-string',
                             dest='skip_info_string',
                             action='store_false',
                             help='Load INFO string from VCF file. Not loaded by default',
                             default=True)
    parser_load.add_argument('--no-load-genotypes',
                             dest='no_load_genotypes',
                             action='store_true',
                             help='Genotypes exist in the file, but should not be stored.',
                             default=False)
    parser_load.add_argument('--no-genotypes',
                             dest='no_genotypes',
                             action='store_true',
                             help='There are no genotypes in the file (e.g. some 1000G VCFs)',
                             default=False)
    parser_load.add_argument('--cores', dest='cores',
                             default=1,
                             type=int,
                             help="Number of cores to use to load in parallel.")
    parser_load.add_argument('--scheduler', dest='scheduler', default=None,
                             choices=["lsf", "sge", "slurm", "torque"],
                             help='Cluster scheduler to use.')
    parser_load.add_argument('--queue', dest='queue',
                             default=None, help='Cluster queue to use.')
    parser_load.add_argument('--tempdir', dest='tempdir',
                             default=tempfile.gettempdir(),
                             help='Temp directory for storing intermediate files when loading in parallel.')
    parser_load.add_argument('--passonly',
                             dest='passonly',
                             default=False,
                             action='store_true',
                             help="Keep only variants that pass all filters.")
    parser_load.add_argument('--test-mode',
                         dest='test_mode',
                         action='store_true',
                         help='Load in test mode (faster)',
                         default=False)
    parser_load.add_argument('--skip-pls',
                         action='store_true',
                         help='dont create columns for phred-scaled genotype likelihoods',
                         default=False)
    def load_fn(parser, args):
        from gemini import gemini_load
        if args.vcf != "-":
            args.vcf = os.path.abspath(args.vcf)
        gemini_load.load(parser, args)

    parser_load.set_defaults(func=load_fn)

    #########################################
    # $ gemini amend
    #########################################
    parser_amend = subparsers.add_parser('amend',
                                         help="Amend an already loaded GEMINI database.")
    parser_amend.add_argument('db',
                              metavar='db',
                              help='The name of the database to be amended.')
    parser_amend.add_argument('--sample',
                              metavar='sample',
                              default=None,
                              help='New sample information file to load')
    parser_amend.add_argument('--clear',
                              default=False,
                              action="store_true",
                              help='Set all values in this column to NULL before loading.')

    def amend_fn(parser, args):
        from gemini import gemini_amend
        gemini_amend.amend(parser, args)
    parser_amend.set_defaults(func=amend_fn)

    #########################################
    # $ gemini load_chunk
    #########################################
    parser_loadchunk = subparsers.add_parser('load_chunk',
                                             help='load a VCF file in gemini database')
    parser_loadchunk.add_argument('db',
                                  metavar='db',
                                  help='The name of the database to be created.')
    parser_loadchunk.add_argument('-v',
                                  dest='vcf',
                                  help='The VCF file to be loaded.')
    parser_loadchunk.add_argument('-t',
                                  dest='anno_type',
                                  default=None,
                                  metavar='STRING',
                                  help="The annotations to be used with the input vcf. Options are:\n"
                                  "  snpEff  - Annotations as reported by snpEff.\n"
                                  "  BCFT  - Annotations as reported by bcftools.\n"
                                  "  VEP     - Annotations as reported by VEP.\n"
                                  )
    parser_loadchunk.add_argument('-o',
                                  dest='offset',
                                  help='The starting number for the variant_ids',
                                  default=None)
    parser_loadchunk.add_argument('-p',
                                  dest='ped_file',
                                  help='Sample information file in PED+ format.',
                                  default=None)
    parser_loadchunk.add_argument('--no-load-genotypes',
                                  dest='no_load_genotypes',
                                  action='store_true',
                                  help='Genotypes exist in the file, but should not be stored.',
                                  default=False)
    parser_loadchunk.add_argument('--no-genotypes',
                                  dest='no_genotypes',
                                  action='store_true',
                                  help='There are no genotypes in the file (e.g. some 1000G VCFs)',
                                  default=False)
    parser_loadchunk.add_argument('--skip-gerp-bp',
                                  dest='skip_gerp_bp',
                                  action='store_true',
                                  help='Do not load GERP scores at base pair resolution. Loaded by default.',
                                  default=False)
    parser_loadchunk.add_argument('--skip-cadd',
                                 dest='skip_cadd',
                                 action='store_true',
                                 help='Do not load CADD scores. Loaded by default',
                                 default=False)
    parser_loadchunk.add_argument('--skip-gene-tables',
                             dest='skip_gene_tables',
                             action='store_true',
                             help='Do not load gene tables. Loaded by default.',
                             default=False)
    parser_loadchunk.add_argument('--skip-info-string',
                                  dest='skip_info_string',
                                  action='store_true',
                                  help='Do not load INFO string from VCF file to reduce DB size. Loaded by default',
                                  default=False)
    parser_loadchunk.add_argument('--passonly',
                                  dest='passonly',
                                  default=False,
                                  action='store_true',
                                  help="Keep only variants that pass all filters.")
    parser_loadchunk.add_argument('--test-mode',
                         dest='test_mode',
                         action='store_true',
                         help='Load in test mode (faster)',
                         default=False)
    parser_loadchunk.add_argument('--skip-pls',
                         action='store_true',
                         help='dont create columns for phred-scaled genotype likelihoods',
                         default=False)
    parser_loadchunk.add_argument('--tempdir', dest='tempdir',
                                  default=tempfile.gettempdir(),
                                  help='Local (non-NFS) temp directory to use for working around SQLite locking issues '
                                       'on NFS drives.')

    def loadchunk_fn(parser, args):
        from gemini import gemini_load_chunk
        if args.vcf != "-":
            args.vcf = os.path.abspath(args.vcf)
        gemini_load_chunk.load(parser, args)
    parser_loadchunk.set_defaults(func=loadchunk_fn)

    #########################################
    # $ gemini merge_chunks
    #########################################
    parser_mergechunks = subparsers.add_parser('merge_chunks',
            help='combine intermediate db files into the final gemini ')
    parser_mergechunks.add_argument('--db',
            dest='db',
            help='The name of the final database to be loaded.')
    parser_mergechunks.add_argument('--vcf',
            dest='vcf',
            help='Original VCF file, for retrieving extra annotation fields.')
    parser_mergechunks.add_argument('-t', dest='anno_type',
            default=None, choices=["snpEff", "VEP",  "BCFT", "all"],
            help="The annotations to be used with the input vcf.")
    parser_mergechunks.add_argument('--chunkdb',
            nargs='*',
            dest='chunkdbs',
            action='append')
    parser_mergechunks.add_argument('--tempdir', dest='tempdir',
            default=tempfile.gettempdir(),
            help='Local (non-NFS) temp directory to use for working around SQLite locking issues on NFS drives.')
    parser_mergechunks.add_argument('--index', dest='index',
            action='store_true',
            help='Create all database indexes. If multiple merges are used to create a database, only the last merge '
                 'should create the indexes.')
    parser_mergechunks.add_argument('--skip-pls',
                         action='store_true',
                         help='dont create columns for phred-scaled genotype likelihoods',
                         default=False)

    def mergechunk_fn(parser, args):
        from gemini import gemini_merge_chunks
        gemini_merge_chunks.merge_chunks(parser, args)
    parser_mergechunks.set_defaults(func=mergechunk_fn)

    #########################################
    # $ gemini query
    #########################################
    parser_query = subparsers.add_parser('query',
            help='issue ad hoc SQL queries to the DB')
    parser_query.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser_query.add_argument('-q',
            dest='query',
            metavar='QUERY_STR',
            help='The query to be issued to the database')
    parser_query.add_argument('--gt-filter',
            dest='gt_filter',
            metavar='STRING',
            help='Restrictions to apply to genotype values')
    parser_query.add_argument('--show-samples',
                              dest='show_variant_samples',
                              action='store_true',
                              default=False,
                              help=('Add a column of all sample names with a variant to each '
                                    'variant.'))
    parser_query.add_argument('--show-families',
                              dest='show_families',
                              action='store_true',
                              default=False,
                              help=('Add a column listing all of the families '
                                    'with a variant to each variant.'))
    parser_query.add_argument('--family-wise',
                              dest='family_wise',
                              default=False,
                              action='store_true',
                              help=('Perform the sample-filter on a family-wise '
                                    'basis.'))
    parser_query.add_argument('--min-kindreds',
                              dest='min_kindreds',
                              default=1,
                              type=int,
                              help=('Minimum number of families for a variant passing '
                                    'a family-wise filter to be in.'))
    parser_query.add_argument('--sample-delim',
            dest='sample_delim',
            metavar='STRING',
            help='The delimiter to be used with the --show-samples option.',
            default=',')

    parser_query.add_argument('--header',
            dest='use_header',
            action='store_true',
            help='Add a header of column names to the output.',
            default=False)
    parser_query.add_argument('--sample-filter',
                              dest='sample_filter',
                              help='SQL filter to use to filter the sample table',
                              default=None)
    parser_query.add_argument('--in',
                              dest='in_subject',
                              nargs='*',
                              help=('A variant must be in either all, none or any '
                                    'samples passing the --sample-query filter.'),
                              choices=['all', 'none', 'any', 'only', 'not'],
                              default=['any'])
    parser_query.add_argument('--format',
                              dest='format',
                              default='default',
                              help='Format of output (JSON, TPED or default)')
    parser_query.add_argument('--region',
                              dest='region',
                              default=None,
                              help=('Restrict query to this region, '
                                    'e.g. chr1:10-20.'))
    parser_query.add_argument('--carrier-summary-by-phenotype',
                              dest='carrier_summary',
                              default=None,
                              help=('Output columns of counts of carriers and '
                                    'non-carriers stratified by the given '
                                    'sample phenotype column'))
    parser_query.add_argument('--dgidb',
                              dest='dgidb',
                              action='store_true',
                              help='Request drug-gene interaction info from DGIdb.',
                              default=False)
    parser_query.add_argument('--use-bcolz',
                              dest='bcolz',
                              action='store_true',
                              help='use a (previously created) bcolz index to speed genotype queries',
                              default=False)

    def query_fn(parser, args):
        from gemini import gemini_query
        gemini_query.query(parser, args)

    parser_query.set_defaults(func=query_fn)

    #########################################
    # $ gemini dump
    #########################################
    parser_dump = subparsers.add_parser('dump',
            help='shortcuts for extracting data from the DB')
    parser_dump.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser_dump.add_argument('--variants',
            dest='variants',
            action='store_true',
            help='Report all rows/columns from the variants table.',
            default=False)
    parser_dump.add_argument('--genotypes',
            dest='genotypes',
            action='store_true',
            help='Report all rows/columns from the variants table \nwith one line per sample/genotype.',
            default=False)
    parser_dump.add_argument('--samples',
            dest='samples',
            action='store_true',
            help='Report all rows/columns from the samples table.',
            default=False)
    parser_dump.add_argument('--header',
            dest='use_header',
            action='store_true',
            help='Add a header of column names to the output.',
            default=False)
    parser_dump.add_argument('--sep',
            dest='separator',
            metavar='STRING',
            help='Output column separator',
            default="\t")
    parser_dump.add_argument('--tfam',
                             dest='tfam',
                             action='store_true',
                             default=False,
                             help='Output sample information to TFAM format.')
    def dump_fn(parser, args):
        from gemini import gemini_dump
        gemini_dump.dump(parser, args)
    parser_dump.set_defaults(func=dump_fn)

    #########################################
    # $ gemini region
    #########################################
    parser_region = subparsers.add_parser('region',
            help='extract variants from specific genomic loci')
    parser_region.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser_region.add_argument('--reg',
            dest='region',
            metavar='STRING',
            help='Specify a chromosomal region chr:start-end')
    parser_region.add_argument('--gene',
            dest='gene',
            metavar='STRING',
            help='Specify a gene of interest')
    parser_region.add_argument('--header',
            dest='use_header',
            action='store_true',
            help='Add a header of column names to the output.',
            default=False)
    parser_region.add_argument('--columns',
            dest='columns',
            metavar='STRING',
            help='A list of columns that you would like returned. Def. = "*"',
            )
    parser_region.add_argument('--filter',
            dest='filter',
            metavar='STRING',
            help='Restrictions to apply to variants (SQL syntax)')
    parser_region.add_argument('--show-samples',
                               dest='show_variant_samples',
                               action='store_true',
                               default=False,
                                help=('Add a column of all sample names with a variant to each '
                                      'variant.'))
    parser_region.add_argument('--format',
                              dest='format',
                              default='default',
                              help='Format of output (JSON, TPED or default)')
    def region_fn(parser, args):
        from gemini import gemini_region
        gemini_region.region(parser, args)
    parser_region.set_defaults(func=region_fn)

    #########################################
    # $ gemini stats
    #########################################
    parser_stats = subparsers.add_parser('stats',
            help='compute useful variant stastics')
    parser_stats.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser_stats.add_argument('--tstv',
            dest='tstv',
            action='store_true',
            help='Report the overall ts/tv ratio.',
            default=False)
    parser_stats.add_argument('--tstv-coding',
            dest='tstv_coding',
            action='store_true',
            help='Report the ts/tv ratio in coding regions.',
            default=False)
    parser_stats.add_argument('--tstv-noncoding',
            dest='tstv_noncoding',
            action='store_true',
            help='Report the ts/tv ratio in non-coding regions.',
            default=False)
    parser_stats.add_argument('--snp-counts',
            dest='snp_counts',
            action='store_true',
            help='Report the count of each type of SNP (A->G, G->T, etc.).',
            default=False)
    parser_stats.add_argument('--sfs',
            dest='sfs',
            action='store_true',
            help='Report the site frequency spectrum of the variants.',
            default=False)
    parser_stats.add_argument('--mds',
            dest='mds',
            action='store_true',
            help='Report the pairwise genetic distance between the samples.',
            default=False)
    parser_stats.add_argument('--vars-by-sample',
            dest='variants_by_sample',
            action='store_true',
            help='Report the number of variants observed in each sample.',
            default=False)
    parser_stats.add_argument('--gts-by-sample',
            dest='genotypes_by_sample',
            action='store_true',
            help='Report the count of each genotype class obs. per sample.',
            default=False)
    parser_stats.add_argument('--summarize',
            dest='query',
            metavar='QUERY_STR',
            default=None,
            help='The query to be issued to the database to summarize')
    parser_stats.add_argument('--gt-filter',
            dest='gt_filter',
            metavar='STRING',
            help='Restrictions to apply to genotype values')
    def stats_fn(parser, args):
        from gemini import gemini_stats
        gemini_stats.stats(parser, args)
    parser_stats.set_defaults(func=stats_fn)

    #########################################
    # gemini annotate
    #########################################
    parser_get = subparsers.add_parser('annotate',
            help='Add new columns for custom annotations')
    parser_get.add_argument('db',
            metavar='db',
            help='The name of the database to be updated.')
    parser_get.add_argument('-f',
            dest='anno_file',
            required=True,
            help='The TABIX\'ed BED file containing the annotations')
    parser_get.add_argument('-c',
            dest='col_names',
            help='The name(s) of the BED column(s) to be added to the variant table.'
            'If the input file is a VCF, then this is the name of the info field to pull.')
    parser_get.add_argument('-a',
            dest='anno_type',
            help='How should the annotation file be used? (def. extract)',
            default="extract",
            choices=['boolean', 'count', 'extract'])
    parser_get.add_argument('-e',
            dest='col_extracts',
            help='Column(s) to extract information from for list annotations.'
            'If the input is VCF, then this defaults to the fields specified in `-c`.')
    parser_get.add_argument('-t',
            dest='col_types',
            help='What data type(s) should be used to represent the new values '
                 'in the database? '
                 'Any of {integer, float, text}')
    parser_get.add_argument('-o',
            dest='col_operations',
            help='Operation(s) to apply to the extract column values '
                  'in the event that a variant overlaps multiple annotations '
                  'in your annotation file (-f).'
                  'Any of {sum, mean, median, min, max, mode, list, uniq_list, first, last}')
    parser_get.add_argument('--region-only',
            dest='region_only',
            action='store_true',
            default=False,
            help='If set, only region coordinates will be considered when annotating variants.'
                 'The default is to annotate using region coordinates as well as REF and ALT variant values'
                 'This option is only valid if annotation is a VCF file')
    def annotate_fn(parser, args):
        from gemini import gemini_annotate
        gemini_annotate.annotate(parser, args)
    parser_get.set_defaults(func=annotate_fn)

    #########################################
    # gemini windower
    #########################################
    parser_get = subparsers.add_parser('windower',
            help='Compute statistics across genome \"windows\"')
    parser_get.add_argument('db',
            metavar='db',
            help='The name of the database to be updated.')
    parser_get.add_argument('-w',
            dest='window_size',
            type=int,
            default=1000000,
            help='The name of the column to be added to the variant table.')
    parser_get.add_argument('-s',
            dest='step_size',
            type=int,
            default=0,
            help="The step size for the windows in bp.\n")
    parser_get.add_argument('-t',
            dest='analysis_type',
            help='The type of windowed analysis requested.',
            choices=['nucl_div', 'hwe'],
            default='hwe')
    parser_get.add_argument('-o',
            dest='op_type',
            help='The operation that should be applied to the -t values.',
            choices=['mean', 'median', 'min', 'max', 'collapse'],
            default='mean')
    def windower_fn(parser, args):
        from gemini import gemini_windower
        gemini_windower.windower(parser, args)
    parser_get.set_defaults(func=windower_fn)

    #########################################
    # gemini db_info
    #########################################
    parser_get = subparsers.add_parser('db_info',
            help='Get the names and types of cols. database tables')
    parser_get.add_argument('db',
            metavar='db',
            help='The name of the database to be updated.')
    def dbinfo_fn(parser, args):
        from gemini import gemini_dbinfo
        gemini_dbinfo.db_info(parser, args)
    parser_get.set_defaults(func=dbinfo_fn)

    #########################################
    # $ gemini comp_hets
    #########################################
    parser_comp_hets = subparsers.add_parser('comp_hets',
            help='Identify compound heterozygotes')
    add_inheritance_args(parser_comp_hets, gt_ll=True, lenient=False)

    parser_comp_hets.add_argument('--gene-where', dest="where",
            default="is_exonic = 1 or impact_severity != 'LOW'",
            help="""SQL clause to limit variants to genes. a reasonable alternative could be "gene != ''" """)

    parser_comp_hets.add_argument('--pattern-only',
            action='store_true',
            help='find compound hets by inheritance pattern, without regard to affection',
            default=False)

    parser_comp_hets.add_argument('--max-priority',
            type=float,
            help='Default (1) is to show only confident compound hets. Set to 2' \
             + ' or higher to include pairs that are less likely true comp-hets',
            default=1.6)

    def comp_hets_fn(parser, args):
        from gemini.gim import CompoundHet
        CompoundHet(args).run()
    parser_comp_hets.set_defaults(func=comp_hets_fn)

    #########################################
    # x-linked-recessive / dominant
    #########################################
    parser_xr = subparsers.add_parser('x_linked_recessive', help='X-linked recessive variants')
    parser_xd = subparsers.add_parser('x_linked_dominant', help='X-linked dominant variants')
    parser_xdn = subparsers.add_parser('x_linked_de_novo', help='X-linked de novo variants')

    parser_xr.add_argument("-X", dest='X',
                           help="name of X chrom (if not default 'chrX' or 'X')",
                           default=[], action='append')
    parser_xd.add_argument("-X", dest='X',
                           help="name of X chrom (if not default 'chrX' or 'X')",
                           default=[], action='append')

    parser_xdn.add_argument("-X", dest='X',
                            help="name of X chrom (if not default 'chrX' or 'X')",
                            default=[], action='append')

    add_inheritance_args(parser_xr, lenient=False, gt_ll=False)
    add_inheritance_args(parser_xd, lenient=False, gt_ll=False)
    add_inheritance_args(parser_xdn, lenient=False, gt_ll=False)

    def x_rec_fn(parser, args):
        from gemini.gim import XRec
        XRec(args).run()
    parser_xr.set_defaults(func=x_rec_fn)

    def x_dom_fn(parser, args):
        from gemini.gim import XDom
        XDom(args).run()
    parser_xd.set_defaults(func=x_dom_fn)

    def x_denovo_fn(parser, args):
        from gemini.gim import XDenovo
        XDenovo(args).run()
    parser_xdn.set_defaults(func=x_denovo_fn)

    #########################################
    # $ gemini pathways
    #########################################
    parser_pathway = subparsers.add_parser('pathways',
            help='Map genes and variants to KEGG pathways')
    parser_pathway.add_argument('db',
            metavar='db',
            help='The name of the database to be queried')
    parser_pathway.add_argument('-v',
            dest='version',
            default='68',
            metavar='STRING',
            help="Version of ensembl genes to use. "
                 "Supported versions: 66 to 71\n"
            )
    parser_pathway.add_argument('--lof',
            dest='lof',
            action='store_true',
            help='Report pathways for indivs/genes/sites with LoF variants',
            default=False)
    def pathway_fn(parser, args):
        from gemini import tool_pathways
        tool_pathways.pathways(parser, args)
    parser_pathway.set_defaults(func=pathway_fn)

    #########################################
    # $ gemini lof_sieve
    #########################################
    parser_lof_sieve = subparsers.add_parser('lof_sieve',
            help='Prioritize LoF mutations')
    parser_lof_sieve.add_argument('db',
            metavar='db',
            help='The name of the database to be queried')
    def lof_sieve_fn(parser, args):
        from gemini import tool_lof_sieve
        tool_lof_sieve.lof_sieve(parser, args)
    parser_lof_sieve.set_defaults(func=lof_sieve_fn)

    #########################################
    # $ gemini burden
    #########################################
    burden_help = ("Gene-level genetic burden tests. By default counts all "
                   "variants with high impact in coding regions "
                   "as contributing to burden.")

    parser_burden = subparsers.add_parser('burden',
                                          help=burden_help)
    parser_burden.add_argument('--nonsynonymous', action='store_true',
                               default=False,
                               help=("Count all nonsynonymous variants as "
                                     "contributing burden."))
    parser_burden.add_argument('--cases',
                               dest='cases',
                               nargs='*',
                               help=('Space separated list of cases for '
                                     'association testing.'))
    parser_burden.add_argument('--controls',
                               nargs='*',
                               dest='controls',
                               help=('Space separated list of controls for '
                                     'association testing.'))
    parser_burden.add_argument('--calpha',
                               action='store_true',
                               default=False,
                               help="Run the C-alpha association test.")
    parser_burden.add_argument('--permutations',
                               default=0,
                               type=int,
                               help=("Number of permutations to run for the "
                                     "C-alpha test (try 1000 to start)."))
    parser_burden.add_argument('--min-aaf',
                               dest='min_aaf',
                               type=float,
                               default=0.0,
                               help='The min. alt. allele frequency for a '
                                     'variant to be included.')
    parser_burden.add_argument('--max-aaf',
                               dest='max_aaf',
                               type=float,
                               default=1.0,
                               help='The max. alt. allele frequency for a '
                                     'variant to be included.')
    parser_burden.add_argument('--save_tscores', default=False,
                               action='store_true',
                               help='Save the permuted T-scores to a file.')
    parser_burden.add_argument('db',
                               metavar='db',
                               help='The name of the database to be queried.')

    def burden_fn(parser, args):
        from gemini import tool_burden_tests
        tool_burden_tests.burden(parser, args)
    parser_burden.set_defaults(func=burden_fn)

    #########################################
    # $ gemini interactions
    #########################################
    parser_interaction = subparsers.add_parser('interactions',
            help='Find interaction partners for a gene in sample variants(default mode)')
    parser_interaction.add_argument('db',
            metavar='db',
            help='The name of the database to be queried')
    parser_interaction.add_argument('-g',
            dest='gene',
            help='Gene to be used as a root in BFS/shortest_path')
    parser_interaction.add_argument('-r',
            dest='radius',
            type=int,
            help="Set filter for BFS:\n"
                 "valid numbers starting from 0")
    parser_interaction.add_argument("--edges",
            help="edges file (default is hprd). Format is geneA|geneB\ngeneA|geneC...")

    parser_interaction.add_argument('--var',
            dest='var_mode',
            help='var mode: Returns variant info (e.g. impact, biotype) for interacting genes',
            action='store_true',
            default=False)
    def interactions_fn(parser, args):
        from gemini import tool_interactions
        tool_interactions.genequery(parser, args)
    parser_interaction.set_defaults(func=interactions_fn)

    #########################################
    # gemini lof_interactions
    #########################################
    parser_interaction = subparsers.add_parser('lof_interactions',
            help='Find interaction partners for a lof gene in sample variants(default mode)')
    parser_interaction.add_argument('db',
            metavar='db',
            help='The name of the database to be queried')
    parser_interaction.add_argument('-r',
            dest='radius',
            type=int,
            help="set filter for BFS:\n")
    parser_interaction.add_argument("--edges",
            help="edges file (default is hprd). Format is geneA|geneB\ngeneA|geneC...")
    parser_interaction.add_argument('--var',
            dest='var_mode',
            help='var mode: Returns variant info (e.g. impact, biotype) for interacting genes',
            action='store_true',
            default=False)
    def lof_interactions_fn(parser, args):
        from gemini import tool_interactions
        tool_interactions.lofgenequery(parser, args)
    parser_interaction.set_defaults(func=lof_interactions_fn)

    #########################################
    # $ gemini autosomal_recessive
    #########################################
    parser_auto_rec = subparsers.add_parser('autosomal_recessive',
            help='Identify variants meeting an autosomal \
                  recessive inheritance model')
    add_inheritance_args(parser_auto_rec, gt_ll=True)


    def autosomal_recessive_fn(parser, args):
        from gemini.gim import AutoRec
        AutoRec(args).run()
    parser_auto_rec.set_defaults(func=autosomal_recessive_fn)

    #########################################
    # $ gemini autosomal_dominant
    #########################################
    parser_auto_dom = subparsers.add_parser('autosomal_dominant',
            help='Identify variants meeting an autosomal \
                  dominant inheritance model')
    add_inheritance_args(parser_auto_dom, gt_ll=True)

    def autosomal_dominant_fn(parser, args):
        from gemini.gim import AutoDom
        AutoDom(args).run()
    parser_auto_dom.set_defaults(func=autosomal_dominant_fn)

    #########################################
    # $ gemini de_novo
    #########################################
    parser_de_novo = subparsers.add_parser('de_novo',
            help='Identify candidate de novo mutations')

    add_inheritance_args(parser_de_novo, min_kindreds=1, gt_ll=True)

    def de_novo_fn(parser, args):
        from gemini.gim import DeNovo
        DeNovo(args).run()
    parser_de_novo.set_defaults(func=de_novo_fn)


    #########################################
    # $ gemini mendel violations
    #########################################
    parser_mendel = subparsers.add_parser('mendel_errors',
            help='Identify candidate violations of Mendelian inheritance')
    add_inheritance_args(parser_mendel, gt_ll=True, allow_unaffected=False)
    parser_mendel.add_argument('--only-affected',
        action='store_true',
        help='only consider candidates from affected samples.',
                default=False)

    def mendel_fn(parser, args):
        from gemini.gim import MendelViolations
        MendelViolations(args).run()
    parser_mendel.set_defaults(func=mendel_fn)


    #########################################
    # $ gemini browser
    #########################################
    parser_browser = subparsers.add_parser('browser',
            help='Browser interface to gemini')
    parser_browser.add_argument('db', metavar='db',
            help='The name of the database to be queried.')
    parser_browser.add_argument('--use', metavar='use', default='builtin',
            help='Which browser to use: builtin or puzzle', choices=('builtin', 'puzzle'))
    parser_browser.add_argument('--host', metavar='host', default='localhost',
            help='Hostname, default: localhost.')
    parser_browser.add_argument('--port', metavar='port', default='8088',
            help='Port, default: 8088.')


    def browser_fn(parser, args):
        from gemini import gemini_browser
        gemini_browser.browser_main(parser, args)
    parser_browser.set_defaults(func=browser_fn)


    #########################################
    # $ gemini set_somatic
    #########################################
    parser_set_somatic = subparsers.add_parser("set_somatic",
                          help="Tag somatic mutations (is_somatic) by comparint tumor/normal pairs.")
    parser_set_somatic.add_argument('db', metavar='db',
            help='The name of the database to be updated.')

    parser_set_somatic.add_argument('--min-depth',
            dest='min_depth',
            type=float,
            default=0,
            help='The min combined depth for tumor + normal (def: %(default)s).')

    parser_set_somatic.add_argument('--min-qual',
            dest='min_qual',
            type=float,
            default=0,
            help='The min variant quality (VCF QUAL) (def: %(default)s).')

    parser_set_somatic.add_argument('--min-somatic-score',
            dest='min_somatic_score',
            type=float,
            default=0,
            help='The min somatic score (SSC) (def: %(default)s).')

    parser_set_somatic.add_argument('--max-norm-alt-freq',
            dest='max_norm_alt_freq',
            type=float,
            default=0,
            help='The max freq. of the alt. allele in the normal sample (def: %(default)s).')

    parser_set_somatic.add_argument('--max-norm-alt-count',
            dest='max_norm_alt_count',
            type=int,
            default=0,
            help='The max count. of the alt. allele in the normal sample (def: %(default)s).')

    parser_set_somatic.add_argument('--min-norm-depth',
            dest='min_norm_depth',
            type=int,
            default=0,
            help='The minimum depth allowed in the normal sample to believe somatic (def: %(default)s).')

    parser_set_somatic.add_argument('--min-tumor-alt-freq',
            dest='min_tumor_alt_freq',
            type=float,
            default=0,
            help='The min freq. of the alt. allele in the tumor sample (def: %(default)s).')

    parser_set_somatic.add_argument('--min-tumor-alt-count',
            dest='min_tumor_alt_count',
            type=int,
            default=0,
            help='The min count. of the alt. allele in the tumor sample (def: %(default)s).')

    parser_set_somatic.add_argument('--min-tumor-depth',
            dest='min_tumor_depth',
            type=int,
            default=0,
            help='The minimum depth allowed in the tumor sample to believe somatic (def: %(default)s).')

    parser_set_somatic.add_argument('--chrom',
            dest='chrom',
            metavar='STRING',
            help='A specific chromosome on which to tag somatic mutations. (def: %(default)s).',
            default=None,
            )

    parser_set_somatic.add_argument('--dry-run',
            dest='dry_run',
            action='store_true',
            help='Don\'t set the is_somatic flag, just report what _would_ be set. For testing parameters.',
            default=False)

    def set_somatic_fn(parser, args):
        from gemini import gemini_set_somatic
        gemini_set_somatic.set_somatic(parser, args)
    parser_set_somatic.set_defaults(func=set_somatic_fn)

    #########################################
    # $ gemini actionable_mutations
    #########################################
    parser_actionable_mut = subparsers.add_parser("actionable_mutations",
                          help="Retrieve genes with actionable somatic mutations via COSMIC and DGIdb.")
    parser_actionable_mut.add_argument('db', metavar='db',
            help='The name of the database to be queried.')
    def get_actionable_mut_fn(parser, args):
        from gemini import gemini_actionable_mutations
        gemini_actionable_mutations.get_actionable_mutations(parser, args)
    parser_actionable_mut.set_defaults(func=get_actionable_mut_fn)


    #########################################
    # $ gemini update
    #########################################
    parser_update = subparsers.add_parser("update", help="Update gemini software and data files.")
    parser_update.add_argument("--devel", help="Get the latest development version instead of the release",
                               action="store_true", default=False)
    parser_update.add_argument("--dataonly", help="Only update data, not the underlying libraries.",
                               action="store_true", default=False)
    parser_update.add_argument("--nodata", help="Do not install data dependencies",
                               dest="install_data", action="store_false", default=True)
    parser_update.add_argument("--extra", help="Add additional non-standard genome annotations to include",
                               action="append", default=[], choices=["gerp_bp","cadd_score"])
    parser_update.add_argument("--tooldir", help="Directory for third party tools (ie /usr/local) update")
    def update_fn(parser, args):
        from gemini import gemini_update
        gemini_update.release(parser, args)
    parser_update.set_defaults(func=update_fn)


    #########################################
    # $ gemini roh
    #########################################
    parser_hom_run = subparsers.add_parser('roh',
            help='Identify runs of homozygosity')
    parser_hom_run.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser_hom_run.add_argument('--min-snps',
            dest='min_snps',
            metavar="INTEGER",
            type=int,
            default=25,
            help='Minimum number of homozygous snps expected in a run (def. 25)')
    parser_hom_run.add_argument('--min-total-depth',
            dest='min_total_depth',
            metavar="INTEGER",
            type=int,
            default=20,
            help="""The minimum overall sequencing depth required"""
                 """for a SNP to be considered (def = 20).""")
    parser_hom_run.add_argument('--min-gt-depth',
            dest='min_genotype_depth',
            metavar="INTEGER",
            type=int,
            default=0,
            help="""The minimum required sequencing depth underlying a given sample's genotype"""
                 """for a SNP to be considered (def = 0).""")
    parser_hom_run.add_argument('--min-size',
            metavar="INTEGER",
            dest='min_size',
            type=int,
            default=100000,
            help='Minimum run size in base pairs (def. 100000)')
    parser_hom_run.add_argument('--max-hets',
            metavar="INTEGER",
            dest='max_hets',
            type=int,
            default=1,
            help='Maximum number of allowed hets in the run (def. 1)')
    parser_hom_run.add_argument('--max-unknowns',
            metavar="INTEGER",
            type=int,
            dest='max_unknowns',
            default=3,
            help='Maximum number of allowed unknowns in the run (def. 3)')
    parser_hom_run.add_argument('-s',
            dest='samples',
            default=None,
            help='Comma separated list of samples to screen for ROHs. e.g S120,S450')
    def homozygosity_runs_fn(parser, args):
        from gemini.tool_homozygosity_runs import run
        run(parser, args)
    parser_hom_run.set_defaults(func=homozygosity_runs_fn)

    #########################################
    # bcolz indexing
    #########################################
    bci = subparsers.add_parser('bcolz_index', help='index an existing gemini'
                                ' database so it can use bcolze for faster '
                                ' genotype queries.')
    bci.add_argument('db', help='The path of the database to indexed with bcolz.')
    bci.add_argument('--cols', help='list of gt columns to index. default is all')

    def bci_fn(parser, args):
        from gemini.gemini_bcolz import create
        if args.cols:
            create(args.db, [x.strip() for x in args.cols.split(",")])
        else:
            create(args.db)
    bci.set_defaults(func=bci_fn)

    #########################################
    # $ gemini fusions
    #########################################
    parser_fusions = subparsers.add_parser('fusions',
                                         help="Identify somatic fusion genes from a GEMINI database.")
    parser_fusions.add_argument('db',
                              metavar='db',
                              help='The name of the database to be queried.')
    parser_fusions.add_argument('--in_cosmic_census',
                                action='store_true',
                                help='One or both genes in fusion is in COSMIC cancer census')
    parser_fusions.add_argument('--min_qual',
                                dest='min_qual',
                                metavar='FLOAT',
                                type=float,
                                default=None,
                                help='The min variant quality (VCF QUAL) (def: %(default)s).')
    parser_fusions.add_argument('--evidence_type',
                                metavar='STR',
                                dest='evidence_type',
                                type=str,
                                default=None,
                                help='The supporting evidence types for the variant ("PE", "SR", or "PE,SR").')

    def fusions_fn(parser, args):
        from gemini.tool_fusions import run
        run(parser, args)
    parser_fusions.set_defaults(func=fusions_fn)

    #########################################
    # genewise
    #########################################

    from gemini.genewise import add_args
    parser_genewise = subparsers.add_parser('gene_wise')
    add_args(parser_genewise)
    def genewise_run(parser, args):
        from gemini.genewise import run
        run(args)
    parser_genewise.set_defaults(func=genewise_run)

    #########################################
    # $ gemini QC
    #########################################
    parser_qc = subparsers.add_parser('qc',
            help='Quality control tools')
    parser_qc.add_argument('db',
            metavar='db',
            help='The name of the database to be queried.')
    parser_qc.add_argument('--mode',
            dest='mode',
            metavar="STRING",
            default='sex',
            help='What type of QC should be run? [sex]')
    parser_qc.add_argument('--chrom',
            dest='chrom',
            metavar="STRING",
            default='chrX',
            help='Which chromosome should the sex test be applied to? [chrX]')
    def qc_fn(parser, args):
        from gemini.tool_qc import run
        run(parser, args)
    parser_qc.set_defaults(func=qc_fn)

    #######################################################
    # parse the args and call the selected function
    #######################################################
    import operator
    subparsers._choices_actions.sort(key=operator.attrgetter('dest'))
    for k in sorted(subparsers.choices):
        subparsers.choices[k] = subparsers.choices.pop(k)

    args = parser.parse_args()

    # make sure database is found if provided
    if len(sys.argv) > 2 and sys.argv[1] not in \
       ["load", "merge_chunks", "load_chunk"]:
        if hasattr(args, "db") and args.db is not None and not os.path.exists(args.db):
            if not "://" in args.db:
                sys.stderr.write("Requested GEMINI database (%s) not found. "
                                 "Please confirm the provided filename.\n"
                                 % args.db)
    elif len(sys.argv) > 2 and sys.argv[1] == "load":
        if xor(args.scheduler, args.queue):
            parser.error("If you are using the IPython parallel loading, you "
                         "must specify both a scheduler with --scheduler and a "
                         "queue to use with --queue.")
    try:
        args.func(parser, args)
    except IOError as e:
        if e.errno != 32:  # ignore SIGPIPE
            raise

def xor(arg1, arg2):
    return bool(arg1) ^ bool(arg2)


if __name__ == "__main__":
    main()
