#!/usr/bin/env python

import argparse
import sys

from dark import __version__
from dark.consensus import ConsensusError, consensusFromBAM
from dark.sam import SamError


def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter,
        description="Make a consensus sequence.",
    )

    parser.add_argument(
        "--bamFilename",
        required=True,
        help="The BAM file from which the consensus should be called.",
    )

    parser.add_argument(
        "--version", action="store_true", help="Print the version number and exit"
    )

    parser.add_argument(
        "--bamId",
        metavar="ID",
        help=(
            "The BAM file reference name indicating which aligned "
            "reads to make a consensus from. If not given, will be inferred "
            "from the BAM file header."
        ),
    )

    parser.add_argument(
        "--referenceFasta", metavar="FILE", help="The reference FASTA file."
    )

    parser.add_argument(
        "--fastaId",
        metavar="ID",
        help=(
            "The id of the sequence in --referenceFasta to use as a "
            "reference. Only considered if --referenceFasta is used. If not "
            "given and --referenceFasta is, the reference id will be "
            "inferred from reference names in the BAM header, or will be "
            "taken as the id of the first sequence in --referenceFasta."
        ),
    )

    group = parser.add_mutually_exclusive_group()

    group.add_argument(
        "--consensusId",
        metavar="ID",
        help=(
            "The id to use in the consensus sequence in the output FASTA. "
            "If not given, the reference sequence id will be used."
        ),
    )

    group.add_argument(
        "--idLambda",
        metavar='"lambda id: ..."',
        help=(
            "A one-argument function taking and returning a read id. "
            "This can be used to set the id of the consensus sequence based "
            "on the id of the reference sequence. The function will be "
            "called with the id of the BAM reference sequence. E.g., "
            "--idLambda \"lambda id: id.split('_')[0]\" or "
            "--idLambda \"lambda id: id[:10] + '-consensus'\"."
        ),
    )

    parser.add_argument(
        "--minCoverage",
        default=0,
        type=int,
        metavar="N",
        help=(
            "The minimum number of reads that must cover a site for a "
            "consensus base to be called. If zero reads cover a site, the "
            "--noCoverage value is used or if the number is greater than "
            "zero but less than minCoverage, the --lowCoverage value is "
            "used."
        ),
    )

    parser.add_argument(
        "--lowCoverage",
        default="n",
        metavar="N",
        help=(
            "What to do when some reads cover a site, but fewer than "
            'the --minCoverage value. Either set this to "reference" (to '
            "use the base from the reference sequence)or a single character "
            '(e.g., "N").'
        ),
    )

    parser.add_argument(
        "--noCoverage",
        default="N",
        help=(
            "What to do when no reads cover a site, Either set this to "
            '"reference" (to use the base from the reference sequence), or '
            'a single character (e.g., "N").'
        ),
    )

    parser.add_argument(
        "--threshold",
        type=float,
        default=0.6,
        metavar="N",
        help=(
            "The frequency threshold when calling the consensus. If the "
            "frequency of the most-common nucleotide at a site meets this "
            "threshold, that nucleotide will be called. Otherwise, an "
            "ambiguous nucleotide code will be produced, based on the "
            "smallest set of most-frequent nucleotides whose summed "
            "frequencies meet the threshold. If the frequency of the "
            "nucleotide that causes the threshold to be reached is the "
            "same as that of other nucleotides, all such nucleotides will "
            "be included in the ambiguous code."
        ),
    )

    parser.add_argument(
        "--ignoreQuality",
        action="store_true",
        help=(
            "Ignore FASTQ quality scores. All bases of all reads will be "
            "considered equally reliable."
        ),
    )

    parser.add_argument(
        "--includeSoftClipped",
        action="store_true",
        help=(
            "Include information from read bases that were marked as "
            "soft-clipped by the algorithm that made the BAM file. See "
            "https://samtools.github.io/hts-specs/SAMv1.pdf if this makes "
            "no sense to you."
        ),
    )

    parser.add_argument(
        "--compareWithPileupFile",
        metavar="FILE",
        help=(
            "Make a consensus using the pysam pileup function and compare "
            "it to the one made by usign pysam fetch. Write the summary to "
            'this file. If the file is "-", write to standard error.'
        ),
    )

    parser.add_argument(
        "--deletionSymbol",
        default="-",
        metavar="CHAR",
        help=(
            "When a deletion is detected, put this symbol into the "
            "consensus sequence. Use the empty string to have deletions "
            "omitted."
        ),
    )

    parser.add_argument(
        "--strategy",
        default="fetch",
        choices=("fetch",),
        help="The consensus-making strategy to use.",
    )

    parser.add_argument(
        "--deletionThreshold",
        default=0.5,
        type=float,
        metavar="N",
        help=(
            "If some reads have a deletion at a site and some do not, call "
            "the site as a deletion if the fraction of reads with the "
            "deletion is at least this value."
        ),
    )

    parser.add_argument(
        "--insertionCountThreshold",
        default=5,
        type=int,
        metavar="N",
        help=(
            "The number of reads that must have an insertion at an offset "
            "in order for the insertion to be called in the consensus."
        ),
    )

    parser.add_argument(
        "--progress",
        action="store_true",
        help=("Show a progress bar (unless standard error has been redirected)."),
    )

    parser.add_argument(
        "--quiet",
        action="store_true",
        help=(
            "Suppress diagnostic output. Note that this will silence "
            "warnings about differing reference names."
        ),
    )

    args = parser.parse_args()

    if args.version:
        print(__version__)
        sys.exit(0)

    try:
        consensus = consensusFromBAM(
            args.bamFilename,
            bamId=args.bamId,
            referenceFasta=args.referenceFasta,
            fastaId=args.fastaId,
            consensusId=args.consensusId,
            idLambda=args.idLambda,
            threshold=args.threshold,
            minCoverage=args.minCoverage,
            lowCoverage=args.lowCoverage,
            noCoverage=args.noCoverage,
            deletionSymbol=args.deletionSymbol,
            deletionThreshold=args.deletionThreshold,
            insertionCountThreshold=args.insertionCountThreshold,
            ignoreQuality=args.ignoreQuality,
            includeSoftClipped=args.includeSoftClipped,
            strategy=args.strategy,
            compareWithPileupFile=args.compareWithPileupFile,
            progress=args.progress,
            quiet=args.quiet,
        )
    except (ConsensusError, SamError) as e:
        print(f"{e} Exiting.", file=sys.stderr)
        sys.exit(1)
    else:
        print(consensus.toString("fasta"), end="")


if __name__ == "__main__":
    main()
