"""
art.py -- ART adapter for Illumina sequencing.

.. versionadded:: 3.1.5
"""

__all__ = ("AVAILABLE_ILLUMINA_ART_SEQUENCER", "ArtAdapter", "patch_frontend_parser")

import argparse
import os

from labw_utils.commonutils.stdlib_helper.logger_helper import get_logger
from labw_utils.typing_importer import List, Tuple, Union, Final, Any, Mapping
from yasim.helper.frontend import patch_frontend_argument_parser
from yasim.llrg_adapter import (
    BaseProcessBasedLLRGAdapter,
    autocopy,
    LLRGInitializationException,
)

AVAILABLE_ILLUMINA_ART_SEQUENCER: Mapping[str, Tuple[str, List[int]]] = {
    "GA1": ("GenomeAnalyzer I", [36, 44]),
    "GA2": ("GenomeAnalyzer II", [50, 75]),
    "HS10": ("HiSeq 1000", [100]),
    "HS20": ("HiSeq 2000", [100]),
    "HS25": ("HiSeq 2500", [125, 150]),
    "HSXn": ("HiSeqX PCR free", [150]),
    "HSXt": ("HiSeqX TruSeq", [150]),
    "MinS": ("MiniSeq TruSeq", [50]),
    "MSv1": ("MiSeq v1", [250]),
    "MSv3": ("MSv3 - MiSeq v3", [250]),
    "NS50": ("NextSeq500 v2", [75]),
}
"""
TODO docs

.. versionadded:: 3.1.5
"""

_lh = get_logger(__name__)


class ArtAdapter(BaseProcessBasedLLRGAdapter):
    """
    Wrapper of ART.

    Cmdline Specs::

        cmd = [
            llrg_executable_path,
            "--fcov", str(self._depth),
            "--in", self._src_fasta_file_path,
            "--samout",
            "--noALN",
            "-l", rlen,
            "-O", os.path.join(self._tmp_dir, "tmp"),
            *other_args
        ]

    .. versionadded:: 3.1.5
    """

    def _pre_execution_hook(self) -> None:
        """Does not need extra preparation"""
        pass

    llrg_name: Final[str] = "art"
    _require_integer_depth: Final[bool] = False
    _capture_stdout: Final[bool] = False
    _is_pair_end: bool

    @staticmethod
    def validate_params(
        sequencer_name: str,
        read_length: int,
        pair_end_fragment_length_mean: int,
        pair_end_fragment_length_std: int,
        is_pair_end: bool,
        **kwargs,
    ) -> Mapping[str, Any]:
        _ = kwargs
        retd = {}
        try:
            sequencer_profile = AVAILABLE_ILLUMINA_ART_SEQUENCER[sequencer_name]
        except KeyError as e:
            raise LLRGInitializationException(f"Sequencer Profile for sequencer {sequencer_name} not found!") from e
        if read_length not in sequencer_profile[1]:
            read_length_used = sequencer_profile[1][0]
            _lh.warning(
                "ART: Read length %d not allowed, would use default %d.",
                read_length,
                read_length_used,
            )
        else:
            read_length_used = read_length
        retd.update({"read_length": read_length_used})
        if is_pair_end:
            if pair_end_fragment_length_mean * pair_end_fragment_length_std == 0:
                raise LLRGInitializationException(
                    "Please set pair_end_fragment_length_mean and pair_end_fragment_length_std in PE simulation"
                )
        else:
            if pair_end_fragment_length_mean != 0 or pair_end_fragment_length_std != 0:
                _lh.warning(
                    "ART: PE Params pair_end_fragment_length_mean pair_end_fragment_length_std "
                    "should not be used in SE mode, ignored."
                )
        return retd

    def __init__(
        self,
        *,
        src_fasta_file_path: str,
        dst_fastq_file_prefix: str,
        depth: Union[int, float],
        llrg_executable_path: str,
        is_trusted: bool,
        sequencer_name: str,
        read_length: int,
        pair_end_fragment_length_mean: int,
        pair_end_fragment_length_std: int,
        is_pair_end: bool,
        preserve_intermediate_files: bool,
        other_args: List[str],
    ):
        """
        Initializer.

        :param src_fasta_file_path: Path of source FASTA.
        :param dst_fastq_file_prefix: Prefix od destination FASTQ.
        :param depth: Targeted sequencing depth. Would NOT be related to actual sequencing depth!
        :param llrg_executable_path: Path to LLRG Executable.
        :param is_trusted: Whether to skip input validation test.
        :param sequencer_name: name of sequencer to simulate.
        :param read_length: Length of read. Should be defined in corresponding sequencer.
        :param pair_end_fragment_length_mean: [PE Only] The mean size of DNA/RNA fragments for paired-end simulations.
        :param pair_end_fragment_length_std: [PE Only] The standard deviation of DNA/RNA fragment size for
            paired-end simulations.
        :param is_pair_end: Whether the simulation should be pair end.
        :param other_args: Other arguments to be appended at the bottom of assembled CMD.
        :raise LLRGInitializationException: On error.
        """
        super().__init__(
            src_fasta_file_path=src_fasta_file_path,
            dst_fastq_file_prefix=dst_fastq_file_prefix,
            depth=depth,
            llrg_executable_path=llrg_executable_path,
            is_trusted=is_trusted,
            preserve_intermediate_files=preserve_intermediate_files,
        )

        if not is_trusted:
            validated_params = ArtAdapter.validate_params(
                sequencer_name=sequencer_name,
                read_length=read_length,
                pair_end_fragment_length_mean=pair_end_fragment_length_mean,
                pair_end_fragment_length_std=pair_end_fragment_length_std,
                is_pair_end=is_pair_end,
            )
            read_length = validated_params["read_length"]
        self._is_pair_end = is_pair_end
        self._cmd = [
            self._llrg_executable_path,
            "--fcov",
            str(self._depth),
            "--in",
            self._src_fasta_file_path,
            "--samout",
            "--noALN",
            "-l",
            str(read_length),
            "--out",
            os.path.join(self._tmp_dir, "tmp"),
            *other_args,
        ]
        if self._is_pair_end:
            self._cmd.extend(
                [
                    "-p",
                    "--sdev",
                    str(pair_end_fragment_length_std),
                    "--mflen",
                    str(pair_end_fragment_length_mean),
                ]
            )

    def _post_execution_hook(self):
        if self._is_pair_end:
            autocopy(
                os.path.join(self._tmp_dir, "tmp1.fq"),
                self._dst_fastq_file_prefix + "_1.fq",
            )
            autocopy(
                os.path.join(self._tmp_dir, "tmp2.fq"),
                self._dst_fastq_file_prefix + "_2.fq",
            )
        else:
            autocopy(
                os.path.join(self._tmp_dir, "tmp.fq"),
                self._dst_fastq_file_prefix + ".fq",
            )

    @property
    def is_pair_end(self) -> bool:
        return self._is_pair_end


def patch_frontend_parser(parser: argparse.ArgumentParser) -> argparse.ArgumentParser:
    """
    Patch argument parser with ART arguments.

    .. versionadded:: 3.1.5
    """
    parser.add_argument(
        "--sequencer_name",
        required=False,
        help="Name of Illumina Sequencer to Simulate: "
        + ", ".join(
            (
                f"{name} -- {AVAILABLE_ILLUMINA_ART_SEQUENCER[name][0]}"
                for name in AVAILABLE_ILLUMINA_ART_SEQUENCER.keys()
            )
        ),
        nargs="?",
        choices=AVAILABLE_ILLUMINA_ART_SEQUENCER.keys(),
        type=str,
        action="store",
        default="HS25",
    )
    parser.add_argument(
        "--read_length",
        required=False,
        help="Read length. Sequencer -- Read Length Table: "
        + ", ".join((f"{v[0]} -- {v[1]}" for v in AVAILABLE_ILLUMINA_ART_SEQUENCER.values())),
        nargs="?",
        type=int,
        action="store",
        default=0,
    )
    parser.add_argument(
        "--pair_end_fragment_length_mean",
        required=False,
        help="[PE Only] The mean size of DNA/RNA fragments for paired-end simulations",
        nargs="?",
        type=int,
        action="store",
        default=0,
    )
    parser.add_argument(
        "--pair_end_fragment_length_std",
        required=False,
        help="[PE Only] The standard deviation of DNA/RNA fragment size for paired-end simulations.",
        nargs="?",
        type=int,
        action="store",
        default=0,
    )
    parser.add_argument(
        "--is_pair_end",
        required=False,
        help="Whether to use Pair End (PE) Simulation",
        action="store_true",
    )
    parser = patch_frontend_argument_parser(parser, "--preserve_intermediate_files")
    return parser
