from pathlib import Path

from metacarp.camel_gene_detection.app.camel import Camel
from metacarp.camel_gene_detection.app.components.blast.alignmentextraction import AlignmentExtraction
from metacarp.camel_gene_detection.app.components.filesystemhelper import FileSystemHelper
from metacarp.camel_gene_detection.app.components.genedetection.genedetectionblasthit import GeneDetectionBlastHit
from metacarp.camel_gene_detection.app.error.invalidinputspecificationerror import InvalidInputSpecificationError
from metacarp.camel_gene_detection.app.io.tooliofile import ToolIOFile
from metacarp.camel_gene_detection.app.loggers import logger
from metacarp.camel_gene_detection.app.tools.tool import Tool


class AlignmentExtractor(Tool):
    """
    Tool that extracts alignments from TXT files generated by BLAST.
    The input of this tool:
    - 1 TXT file with all the alignments
    - List of blast hits
     The output:
    - List of alignment files in the same order as the input ('TXT' key in the tool outputs)
    """

    def __init__(self, camel: Camel) -> None:
        """
        Initializes this tool.
        :param camel: Camel instance
        """
        super().__init__('Gene Detection: Alignment Extractor', '0.1', camel)

    def _execute_tool(self) -> None:
        """
        Executes this tool.
        :return: None
        """
        alignments = AlignmentExtraction.get_alignments(self._tool_inputs['TXT'][0].path)
        self._tool_outputs['TXT'] = []
        if 'VAL_Hits' not in self._tool_inputs:
            return
        for hit in [io.value for io in self._tool_inputs['VAL_Hits']]:
            key = AlignmentExtraction.get_key(hit.blast_stats.subject_id, hit.blast_stats.query_id)
            if key in alignments:
                alignment = self.__save_alignment(hit, alignments[key])
                self._tool_outputs['TXT'].append(ToolIOFile(alignment))
            else:
                raise ValueError("No alignment found for: '{}'".format(key))

    def _check_input(self) -> None:
        """
        Checks if the required inputs are specified.
        :return: None
        """
        if 'TXT' not in self._tool_inputs:
            raise InvalidInputSpecificationError("No TXT input found.")
        if 'VAL_Hits' not in self._tool_inputs:
            logger.warning("No blast hits input found")
        super(AlignmentExtractor, self)._check_input()

    def __save_alignment(self, hit: GeneDetectionBlastHit, alignment: str) -> Path:
        """
        Saves the given alignment.
        :param hit: BLAST hit
        :param alignment: Alignment
        :return: Filename of the saved alignment
        """
        path_out = self._folder / f'{FileSystemHelper.make_valid(hit.locus)}.txt'
        alignment = alignment.replace(hit.blast_stats.subject_id, hit.locus)
        alignment = alignment.replace('Query  ', 'Contig ').replace('Sbjct  ', 'Locus  ')
        with path_out.open('w') as output_handle:
            output_handle.write(alignment)
        return path_out
