# This file is part of Bioy
#
#    Bioy is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Bioy 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 General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with Bioy.  If not, see <http://www.gnu.org/licenses/>.

"""DEPRECATED: use the classifier subcommand
Classify sequences by grouping blast output by matching taxonomic names

Optional grouping by specimen and query sequences
"""
import sys
import logging

from csv import DictReader, DictWriter
from collections import defaultdict
from math import ceil
from operator import itemgetter

from bioy_pkg import sequtils
from bioy_pkg.utils import Opener, opener, Csv2Dict, groupbyl

log = logging.getLogger(__name__)

def build_parser(parser):
    parser.add_argument('blast_file',
            nargs = '?',
            default = sys.stdin,
            type = Opener('r'),
            help = 'CSV tabular blast file of query and subject hits')
    parser.add_argument('--all-one-group',
            dest = 'all_one_group',
            action = 'store_true',
            help = """If --map is not provided, the default behavior is to treat
                    all reads as one group; use this option to treat
                    each read as a separate group [%(default)s]""")
    parser.add_argument('-a', '--asterisk',
            default = 100,
            metavar='PERCENT',
            type = float,
            help = 'Next to any species above a certain threshold [%(default)s]')
    parser.add_argument('--copy-numbers',
            metavar = 'CSV',
            type = Opener(),
            help = 'columns: tax_id, median')
    parser.add_argument('-c', '--coverage',
            default = 95,
            metavar = 'PERCENT',
            type = float,
            help = 'percent of alignment coverage of blast result [%(default)s]')
    parser.add_argument('--details-identity',
            metavar = 'PERCENT',
            help = 'Minimum identity to include blast hits in details file',
            type = float,
            default = 90)
    parser.add_argument('--details-full',
            action = 'store_true',
            help = 'do not limit out_details to only larget cluster per assignment')
    parser.add_argument('--exclude-by-taxid',
            metavar = 'CSV',
            type = lambda f: set(e for e in DictReader(opener(f), fieldnames ='tax_id')),
            default = {},
            help = 'column: tax_id')
    parser.add_argument('--group-def',
            metavar = 'INT',
            action = 'append',
            default = [],
            help = """define a group threshold for a particular rank overriding
                      --target-max-group-size. example: genus:2""")
    parser.add_argument('--group-label',
            metavar = 'LABEL',
            default = 'all',
            help = 'Single group label for reads')
    parser.add_argument('-o', '--out',
            default = sys.stdout,
            type = Opener('w'),
            metavar = 'CSV',
            help = """columns: specimen, max_percent, min_percent, max_coverage,
                      min_coverage, assignment_id, assignment, clusters, reads,
                      pct_reads, corrected, pct_corrected, target_rank, hi, low, tax_ids""")
    parser.add_argument('-m', '--map',
            metavar = 'CSV',
            type = Opener(),
            default = {},
            help = 'columns: name, specimen')
    parser.add_argument('--max-ambiguous',
            metavar = 'INT',
            default = 3,
            type = int,
            help = 'Maximum ambiguous count in reference sequences [%(default)s]')
    parser.add_argument('--max-identity',
            default = 100,
            metavar = 'PERCENT',
            type = float,
            help = 'maximum identity threshold for accepting matches [<= %(default)s]')
    parser.add_argument('--min-cluster-size',
            default = 0,
            metavar = 'INT',
            type = int,
            help = 'minimum cluster size to include in classification output')
    parser.add_argument('--min-identity',
            default = 99,
            metavar = 'PERCENT',
            type = float,
            help = 'minimum identity threshold for accepting matches [> %(default)s]')
    parser.add_argument('-s', '--seq-info',
            required = True,
            metavar = 'CSV',
            type = Opener(),
            help = 'seq info file(s) to match sequence ids to taxids [%(default)s]')
    parser.add_argument('-t', '--taxonomy',
            required = True,
            metavar = 'CSV',
            type = Csv2Dict('tax_id'),
            help = 'tax table of taxids and species names [%(default)s]')
    parser.add_argument('-O', '--out-detail',
            type  = lambda f: DictWriter(opener(f, 'w'), extrasaction = 'ignore', fieldnames = [
                'specimen', 'assignment', 'assignment_id', 'qseqid', 'sseqid', 'pident', 'coverage', 'ambig_count',
                'accession', 'tax_id', 'tax_name', 'target_rank', 'rank', 'hi', 'low'
                ]),
            metavar = 'CSV',
            help = """columns: specimen, assignment, assignment_id,
                      qseqid, sseqid, pident, coverage, ambig_count,
                      accession, tax_id, tax_name, target_rank, rank, hi, low""")
    parser.add_argument('--target-max-group-size',
            metavar = 'INTEGER',
            default = 3,
            type = int,
            help = """group multiple target-rank assignments that
                      excede a threshold to a higher rank [%(default)s]""")
    parser.add_argument('--target-rank',
            metavar='RANK',
            help = 'Rank at which to classify. Default: "%(default)s"',
            default = 'species')
    parser.add_argument('-w', '--weights',
            metavar = 'CSV',
            type = Opener(),
            help = 'columns: name, weight')
    ### csv.Sniffer.has_header is *not* reliable enough
    parser.add_argument('--has-header', action = 'store_true',
            help = 'specify this if blast 
data has a header')

def coverage(start, end, length):
    return (float(end) - float(start) + 1) / float(length) * 100

def mean(l):
    l = list(l)
    return float(sum(l)) / le
n(l) if len(l) > 0 else 0

def condense(queries, floor_rank, max_size, ranks, rank_thresholds, target_rank = None):
    target_rank = target_rank or ranks[0]

    groups = list(groupbyl(queries, key = itemgetter(target_rank)))

    num_groups = len(groups)

    if rank_thresholds.get(target_rank, max_size) < num_groups:
        return queries

    # assign where available target_rank_ids
    # groups without 'i' values remain assigned at previous (higher) rank
    for g in (g for i,g in groups if i):
        for q in g:
            q['target_rank_id'] = q[target_rank]

    # return if we hit the floor
    if target_rank == floor_rank:
        return queries

    # else move down a rank
    target_rank = ranks[ranks.index(target_rank) + 1]

    # recurse down the tax tree
    condensed = []
    for _,g in groups:
        c = condense(g, floor_rank, max_size, ranks, rank_thresholds, target_rank)
        condensed.extend(c)

    return condensed

def action(args):
    ### format format blast data and add additional available information
    fieldnames = None if args.has_header else sequtils.BLAST_HEADER_DEFAULT
    blast_results = DictReader(args.blast_file, fieldnames = fieldnames)
    blast_results = list(blast_results)

    sseqids = set(s['sseqid'] for s in blast_results)
    qseqids = set(s['qseqid'] for s in blast_results)

    # load seq_info and map file
    mapfile = DictReader(args.map, fieldnames = ['name', 'specimen'])
    mapfile = {m['name']:m['specimen'] for m in mapfile if m['name'] in qseqids}
