from glob import glob
import os
import argparse
from sourmash.logging import notify
from sourmash.sourmash_args import check_scaled_bounds, check_num_bounds


def add_moltype_args(parser):
    parser.add_argument(
        '--protein', dest='protein', action='store_true',
        help='choose a protein signature; by default, a nucleotide signature is used'
    )
    parser.add_argument(
        '--no-protein', dest='protein', action='store_false',
        help='do not choose a protein signature')
    parser.set_defaults(protein=False)

    parser.add_argument(
        '--dayhoff', dest='dayhoff', action='store_true',
        help='choose Dayhoff-encoded amino acid signatures'
    )
    parser.add_argument(
        '--no-dayhoff', dest='dayhoff', action='store_false',
        help='do not choose Dayhoff-encoded amino acid signatures')
    parser.set_defaults(dayhoff=False)

    parser.add_argument(
        '--hp', '--hydrophobic-polar', dest='hp', action='store_true',
        help='choose hydrophobic-polar-encoded amino acid signatures'
    )
    parser.add_argument(
        '--no-hp', '--no-hydrophobic-polar', dest='hp', action='store_false',
        help='do not choose hydrophobic-polar-encoded amino acid signatures')
    parser.set_defaults(hp=False)

    parser.add_argument(
        '--dna', '--rna', '--nucleotide', dest='dna', default=None, action='store_true',
        help='choose a nucleotide signature (default: True)')
    parser.add_argument(
        '--no-dna', '--no-rna', '--no-nucleotide', dest='dna', action='store_false',
        help='do not choose a nucleotide signature')
    parser.set_defaults(dna=None)


def add_construct_moltype_args(parser):
    add_moltype_args(parser)
    parser.set_defaults(dna=True)


def add_ksize_arg(parser, *, default=None):
    "Add -k/--ksize to argparse parsers, with specified default."
    if default:
        message = f"k-mer size to select; default={default}"
    else:
        message = f"k-mer size to select; no default."

    parser.add_argument(
        '-k', '--ksize', metavar='K', default=default, type=int,
        help=message,
    )

#https://stackoverflow.com/questions/55324449/how-to-specify-a-minimum-or-maximum-float-value-with-argparse#55410582
def range_limited_float_type(arg):
    """ Type function for argparse - a float within some predefined bounds """
    min_val = 0
    max_val = 1
    try:
        f = float(arg)
    except ValueError:
        raise argparse.ArgumentTypeError("\n\tERROR: Must be a floating point number.")
    if f < min_val or f > max_val:
        raise argparse.ArgumentTypeError(f"\n\tERROR: Argument must be >{str(min_val)} and <{str(max_val)}.")
    return f


def add_tax_threshold_arg(parser, containment_default=0.1, ani_default=None):
    parser.add_argument(
        '--containment-threshold', default=containment_default, type=range_limited_float_type,
        help=f'minimum containment threshold for classification; default={containment_default}',
    )
    parser.add_argument(
        '--ani-threshold', '--aai-threshold', default=ani_default, type=range_limited_float_type,
        help=f'minimum ANI threshold (nucleotide gather) or AAI threshold (protein gather) for classification; default={ani_default}',
    )


def add_picklist_args(parser):
    parser.add_argument(
        '--picklist', default=None,
        help="select signatures based on a picklist, i.e. 'file.csv:colname:coltype'"
    )
    parser.add_argument(
        '--picklist-require-all', default=False, action='store_true',
        help="require that all picklist values be found or else fail"
    )


def add_pattern_args(parser):
    parser.add_argument(
        '--include-db-pattern',
        default=None,
        help='search only signatures that match this pattern in name, filename, or md5'
    )
    parser.add_argument(
        '--exclude-db-pattern',
        default=None,
        help='search only signatures that do not match this pattern in name, filename, or md5'
    )


def opfilter(path):
    return not path.startswith('__') and path not in ['utils']


def command_list(dirpath):
    paths = glob(os.path.join(dirpath, '*.py'))
    filenames = [os.path.basename(path) for path in paths]
    basenames = [os.path.splitext(path)[0] for path in filenames if not path.startswith('__')]
    basenames = filter(opfilter, basenames)
    return sorted(basenames)


def add_scaled_arg(parser, default=None):
    parser.add_argument(
        '--scaled', metavar='FLOAT', type=check_scaled_bounds,
        help='downsample to this scaled; value should be between 100 and 1e6'
    )


def add_num_arg(parser, default=0):
    parser.add_argument(
        '-n', '--num-hashes', '--num', metavar='N', type=check_num_bounds, default=default,
        help='num value should be between 50 and 50000'
    )


def check_rank(args):
    """ Check '--rank'/'--position'/'--lin-position' argument matches selected taxonomy."""
    standard_ranks =['strain', 'species', 'genus', 'family', 'order', 'class', 'phylum', 'superkingdom']
    if args.lins:
        if args.rank.isdigit(): 
            return str(args.rank)
        raise argparse.ArgumentTypeError(f"Invalid '--rank'/'--position' input: '{args.rank}'. '--lins' is specified. Rank must be an integer corresponding to a LIN position.")
    elif args.rank in standard_ranks:
        return args.rank
    else:
        raise argparse.ArgumentTypeError(f"Invalid '--rank'/'--position' input: '{args.rank}'. Please choose: 'strain', 'species', 'genus', 'family', 'order', 'class', 'phylum', 'superkingdom'")


def add_rank_arg(parser):
    parser.add_argument(
        '-r', '--rank',
        '--position', '--lin-position',
        help="For non-default output formats. Classify to this rank (tax genome) or summarize taxonomy at this rank and above (tax metagenome). \
              Note that the taxonomy CSV must contain lineage information at this rank, and that LIN positions start at 0. \
              Choices: 'strain', 'species', 'genus', 'family', 'order', 'class', 'phylum', 'superkingdom' or an integer LIN position"
    )


def check_tax_outputs(args, rank_required = ["krona"], incompatible_with_lins = None, use_lingroup_format=False):
    "Handle ouput format combinations"
    # check that rank is passed for formats requiring rank.
    if not args.rank:
        if any(x in rank_required for x in args.output_format):
            raise ValueError(f"Rank (--rank) is required for {', '.join(rank_required)} output formats.")

    if args.lins:
        # check for outputs incompatible with lins
        if incompatible_with_lins:
            if any(x in args.output_format for x in incompatible_with_lins):
                raise ValueError(f"The following outputs are incompatible with '--lins': : {', '.join(incompatible_with_lins)}")
        # check that lingroup file exists if needed
        if args.lingroup:
            if use_lingroup_format and "lingroup" not in args.output_format:
                args.output_format.append("lingroup")
        elif "lingroup" in args.output_format:
            raise ValueError(f"Must provide lingroup csv via '--lingroup' in order to output a lingroup report.")
    elif args.lingroup or "lingroup" in args.output_format:
        raise ValueError(f"Must enable LIN taxonomy via '--lins' in order to use lingroups.")

    # check that only one output format is specified if writing to stdout
    if len(args.output_format) > 1:
        if args.output_base == "-":
            raise ValueError(f"Writing to stdout is incompatible with multiple output formats {args.output_format}")
    elif not args.output_format:
        # change to "human" for 5.0
        args.output_format = ["csv_summary"]

    return args.output_format
