import logging
import re

from multiqc import config
from multiqc.base_module import BaseMultiqcModule, ModuleNoSamplesFound
from multiqc.plots import bargraph

log = logging.getLogger(__name__)


class MultiqcModule(BaseMultiqcModule):
    """
    The module parses the log file generated by [Porechop](https://github.com/rrwick/Porechop), which is a tool
    for demultiplexing and processing nanopore data.

    The module takes summary statistics of numbers of adapters and number of reads trimmed and displays them in
    the General Stats table as well as barplots.
    """

    def __init__(self):
        super(MultiqcModule, self).__init__(
            name="Porechop",
            anchor="porechop",
            href="https://github.com/rrwick/Porechop",
            info="Finds and removes adapters from Oxford Nanopore reads.",
            extra="""
            Adapters on the ends of reads are trimmed off, and when a read has an adapter in its middle, 
            it is treated as chimeric and chopped into separate reads. Porechop performs thorough alignments to 
            effectively find adapters, even at low sequence identity.
            """,
            # doi="",  # No DOI available
        )

        # Find and load reports
        self.porechop_data = dict()

        # Find all files for porechop
        for f in self.find_log_files("porechop", filehandles=True):
            self.parse_logs(f)

        self.porechop_data = self.ignore_samples(self.porechop_data)

        if len(self.porechop_data) == 0:
            raise ModuleNoSamplesFound

        log.info(f"Found {len(self.porechop_data)} reports")

        # Superfluous function call to confirm that it is used in this module
        # Replace None with actual version if it is available
        self.add_software_version(None)

        # Write data to file
        self.write_data_file(self.porechop_data, "porechop")

        self.porechop_general_stats()
        if max(len(v) for v in self.porechop_data.values()) > 1:
            self.start_trim_barplot()
            self.end_trim_barplot()
            self.middle_split_barplot()
        self.no_adapters_found()

    def parse_logs(self, f):
        """Parsing Logs. Note: careful of ANSI formatting log"""

        def get_float(val):
            """Get float from string"""
            val = val.replace(",", "")
            try:
                return float(val)
            except ValueError:
                return val

        for line in f["f"]:
            # Find line after loading reads, and remove suffixes for sample name
            if "Loading reads" in line:
                s_name = next(f["f"]).rstrip()
                s_name = self.clean_s_name(s_name, f)
                self.add_data_source(f, s_name=s_name)
                if s_name in self.porechop_data:
                    log.debug(f"Duplicate sample name found! Overwriting: {s_name}")
                self.porechop_data[s_name] = {}

            # Find each valid metric, clean up for plain integer
            # 10,000 reads loaded
            if "reads loaded" in line:
                reads_loaded = re.search(r"([\d,]+)\s*reads loaded", line)
                if reads_loaded:
                    self.porechop_data[s_name]["Input Reads"] = get_float(reads_loaded.group(1))

            # 7,100 / 10,000 reads had adapters trimmed from their start (425,196 bp removed)
            if "reads had adapters trimmed from their start" in line:
                adapter_start = re.search(
                    r"([\d,]+)\s*/\s*([\d,]+)\s*reads had adapters trimmed from their start \(([\d,]+) bp removed\)",
                    line,
                )
                if adapter_start:
                    self.porechop_data[s_name]["Start Trimmed"] = get_float(adapter_start.group(1))
                    self.porechop_data[s_name]["Start Trimmed Total"] = get_float(adapter_start.group(2))
                    self.porechop_data[s_name]["Start Trimmed (bp)"] = get_float(adapter_start.group(3))
                    self.porechop_data[s_name]["Start Untrimmed"] = (
                        self.porechop_data[s_name]["Start Trimmed Total"] - self.porechop_data[s_name]["Start Trimmed"]
                    )
                    try:
                        self.porechop_data[s_name]["Start Trimmed Percent"] = (
                            self.porechop_data[s_name]["Start Trimmed"]
                            / self.porechop_data[s_name]["Start Trimmed Total"]
                        ) * 100
                    except ZeroDivisionError:
                        pass

            # 4,849 / 10,000 reads had adapters trimmed from their end (283,192 bp removed)'
            if "reads had adapters trimmed from their end" in line:
                end_trimmed = re.search(
                    r"([\d,]+)\s*/\s*([\d,]+)\s*reads had adapters trimmed from their end \(([\d,]+) bp removed\)", line
                )
                if end_trimmed:
                    self.porechop_data[s_name]["End Trimmed"] = get_float(end_trimmed.group(1))
                    self.porechop_data[s_name]["End Trimmed Total"] = get_float(end_trimmed.group(2))
                    self.porechop_data[s_name]["End Trimmed (bp)"] = get_float(end_trimmed.group(3))
                    self.porechop_data[s_name]["End Untrimmed"] = (
                        self.porechop_data[s_name]["End Trimmed Total"] - self.porechop_data[s_name]["End Trimmed"]
                    )
                    try:
                        self.porechop_data[s_name]["End Trimmed Percent"] = (
                            self.porechop_data[s_name]["End Trimmed"]
                            / self.porechop_data[s_name]["Start Trimmed Total"]
                            * 100
                        )
                    except ZeroDivisionError:
                        pass

            # 7 / 10,000 reads were split based on middle adapters
            if "reads were split based on middle adapters" in line:
                split_stats = re.search(r"([\d,]+)\s*/\s*([\d,]+)\s*reads were split based on middle adapters", line)
                if split_stats:
                    self.porechop_data[s_name]["Middle Split"] = get_float(split_stats.group(1))
                    self.porechop_data[s_name]["Middle Split Total"] = get_float(split_stats.group(2))
                    self.porechop_data[s_name]["Middle Not-Split"] = (
                        self.porechop_data[s_name]["Middle Split Total"] - self.porechop_data[s_name]["Middle Split"]
                    )
                    try:
                        self.porechop_data[s_name]["Middle Split Percent"] = (
                            self.porechop_data[s_name]["Middle Split"]
                            / self.porechop_data[s_name]["Middle Split Total"]
                            * 100
                        )
                    except ZeroDivisionError:
                        pass

    def porechop_general_stats(self):
        """Porechop General Stats Table"""
        headers = {
            "Input Reads": {
                "title": f"Input Reads ({config.read_count_prefix})",
                "description": f"Number of reads loaded into Porechop ({config.read_count_prefix})",
                "scale": "Greens",
                "shared_key": "read_count",
                "modify": lambda x: x * config.read_count_multiplier,
            },
            "Start Trimmed": {
                "title": f"Start Trimmed ({config.read_count_prefix})",
                "description": "Number of reads that had adapters trimmed from the start ({})".format(
                    config.read_count_prefix
                ),
                "scale": "Purples",
                "shared_key": "read_count",
                "modify": lambda x: x * config.read_count_multiplier,
                "hidden": True,
            },
            "Start Trimmed Percent": {
                "title": "Start Trimmed",
                "description": "Percent of reads that had adapters trimmed from the start",
                "suffix": "%",
                "max": 100,
                "scale": "RdYlGn",
            },
            "End Trimmed": {
                "title": f"End Trimmed ({config.read_count_prefix})",
                "description": "Number of reads that had adapters trimmed from the end ({})".format(
                    config.read_count_prefix
                ),
                "scale": "Purples",
                "shared_key": "read_count",
                "modify": lambda x: x * config.read_count_multiplier,
                "hidden": True,
            },
            "End Trimmed Percent": {
                "title": "End Trimmed",
                "description": "Percent of reads that had adapters trimmed from the end",
                "suffix": "%",
                "max": 100,
                "scale": "RdYlGn",
            },
            "Middle Split": {
                "title": f"Middle Split ({config.read_count_prefix})",
                "description": f"Number of reads split based on middle adapters ({config.read_count_prefix})",
                "scale": "Purples",
                "shared_key": "read_count",
                "modify": lambda x: x * config.read_count_multiplier,
                "hidden": True,
            },
            "Middle Split Percent": {
                "title": "Middle Split",
                "description": "Percent of reads that were split based on middle adapters",
                "suffix": "%",
                "max": 100,
                "scale": "RdYlGn",
            },
        }

        self.general_stats_addcols(self.porechop_data, headers)

    def start_trim_barplot(self):
        """Barplot of number of reads adapter trimmed at read start"""
        cats = {
            "Start Trimmed": {"name": "Start Trimmed", "color": "#7cb5ec"},
            "Start Untrimmed": {"name": "Start Untrimmed", "color": "#f7a35c"},
        }
        config = {
            "id": "porechop-starttrim-barplot",
            "title": "Porechop: Read Start Adapter Timmed",
            "ylab": "Read Counts",
        }
        self.add_section(
            name="Reads adapter-trimmed read start",
            anchor="porechop-starttrim",
            description="Shows the number of reads that had adapters removed from read start.",
            plot=bargraph.plot(self.porechop_data, cats, config),
        )

    def end_trim_barplot(self):
        """Barplot of number of reads adapter trimmed at read end"""
        cats = {
            "End Trimmed": {"name": "End Trimmed", "color": "#7cb5ec"},
            "End Untrimmed": {"name": "End Untrimmed", "color": "#f7a35c"},
        }
        config = {
            "id": "porechop-endtrim-barplot",
            "title": "Porechop: Read End Adapter Timmed",
            "ylab": "Read Counts",
        }
        self.add_section(
            name="Reads adapter-trimmed read end",
            anchor="porechop-endtrim",
            description="Shows the number of reads that had adapters removed from read end.",
            plot=bargraph.plot(self.porechop_data, cats, config),
        )

    def middle_split_barplot(self):
        """Barplot of number of reads adapter trimmed at read end"""
        cats = {
            "Middle Split": {"name": "Split Reads", "color": "#7cb5ec"},
            "Middle Not-Split": {"name": "Unsplit Reads", "color": "#f7a35c"},
        }
        config = {
            "id": "porechop-middlesplit-barplot",
            "title": "Porechop: Middle Split",
            "ylab": "Read Counts",
        }
        self.add_section(
            name="Middle split reads",
            anchor="porechop-middlesplit",
            description="Shows the number of reads that were split due to adapter being present in middle of read.",
            plot=bargraph.plot(self.porechop_data, cats, config),
        )

    def no_adapters_found(self):
        """Show any samples that did not have any trimming"""
        no_adapters = []
        for s_name in self.porechop_data:
            if len(self.porechop_data[s_name]) == 1:
                no_adapters.append(s_name)
        if len(no_adapters):
            self.add_section(
                name="No adapters found",
                anchor="porechop-noadapters",
                description="The following samples did not have any adapters found - output reads were unchanged from input reads:",
                content=f"""
                    <ul>
                        <li><code>{'</code></li><li><code>'.join(no_adapters)}</code></li>
                    </ul>
                """,
            )
