# This code is part of the Biopython distribution and governed by its
# license.  Please see the LICENSE file that should have been included
# as part of this package.
"""Unittests for Bio.Align.Applications interface for PRANK."""

import sys
import os
import unittest
from Bio.Application import _escape_filename
from Bio import AlignIO
from Bio import SeqIO
from Bio import MissingExternalDependencyError
from Bio.Align.Applications import PrankCommandline
from Bio.Nexus.Nexus import NexusError

# Try to avoid problems when the OS is in another language
os.environ["LANG"] = "C"

prank_exe = None
if sys.platform == "win32":
    try:
        # This can vary depending on the Windows language.
        prog_files = os.environ["PROGRAMFILES"]
    except KeyError:
        prog_files = r"C:\Program Files"
    # For Windows, PRANK just comes as a zip file which contains the
    # prank.exe file which the user could put anywhere.  We'll try a few
    # sensible locations under Program Files... and then the full path.
    likely_dirs = [
        "",  # Current dir
        prog_files,
        os.path.join(prog_files, "Prank"),
    ] + sys.path
    for folder in likely_dirs:
        if os.path.isdir(folder):
            if os.path.isfile(os.path.join(folder, "prank.exe")):
                prank_exe = os.path.join(folder, "prank.exe")
                break
        if prank_exe:
            break
else:
    from subprocess import getoutput

    output = getoutput("prank")
    if "not found" not in output and "not recognized" not in output:
        if "prank" in output.lower():
            prank_exe = "prank"
if not prank_exe:
    raise MissingExternalDependencyError(
        "Install PRANK if you want to use the Bio.Align.Applications wrapper."
    )


class PrankApplication(unittest.TestCase):
    def setUp(self):
        self.infile1 = "Fasta/fa01"

    def tearDown(self):
        """Remove generated files.

        output.1.dnd  output.1.fas  output.1.xml  output.2.dnd  output.2.fas  output.2.xml
        """
        if os.path.isfile("output.1.dnd"):
            os.remove("output.1.dnd")
        if os.path.isfile("output.1.fas"):
            os.remove("output.1.fas")
        if os.path.isfile("output.1.xml"):
            os.remove("output.1.xml")
        if os.path.isfile("output.2.dnd"):
            os.remove("output.2.dnd")
        if os.path.isfile("output.2.fas"):
            os.remove("output.2.fas")
        if os.path.isfile("output.2.xml"):
            os.remove("output.2.xml")
        if os.path.isfile("output.1.nex"):
            os.remove("output.1.nex")
        if os.path.isfile("output.2.nex"):
            os.remove("output.2.nex")

    def test_Prank_simple(self):
        """Simple round-trip through app with infile.

        output.?.??? files written to cwd - no way to redirect
        """
        cmdline = PrankCommandline(prank_exe)
        cmdline.set_parameter("d", self.infile1)
        self.assertEqual(str(cmdline), _escape_filename(prank_exe) + " -d=Fasta/fa01")
        self.assertEqual(str(eval(repr(cmdline))), str(cmdline))
        output, error = cmdline()
        self.assertEqual(error, "")
        self.assertIn("Total time", output)

    def test_Prank_simple_with_NEXUS_output(self):
        """Simple round-trip through app with infile, output in NEXUS.

        output.?.??? files written to cwd - no way to redirect
        """
        records = list(SeqIO.parse(self.infile1, "fasta"))
        # Try using keyword argument,
        cmdline = PrankCommandline(prank_exe, d=self.infile1)
        # Try using a property,
        cmdline.d = self.infile1
        cmdline.f = 17  # NEXUS format
        cmdline.set_parameter("dots", True)
        self.assertEqual(
            str(cmdline), _escape_filename(prank_exe) + " -d=Fasta/fa01 -f=17 -dots"
        )
        self.assertEqual(str(eval(repr(cmdline))), str(cmdline))
        stdout, stderr = cmdline()
        self.assertIn("Total time", stdout)
        self.assertEqual(stderr, "")
        try:
            if os.path.isfile("output.best.nex"):
                # Prank v.130820 and perhaps earlier use ".best.*" output names
                nex_fname = "output.best.nex"
            elif os.path.isfile("output.2.nex"):
                # Older Prank versions use ".2.*" output names
                nex_fname = "output.2.nex"
            else:
                raise RuntimeError("Can't find PRANK's NEXUS output (*.nex)")
            align = AlignIO.read(nex_fname, "nexus")
            for old, new in zip(records, align):
                # Old versions of Prank reduced name to 9 chars
                self.assertTrue(old.id == new.id or old.id[:9] == new.id)
                # infile1 has alignment gaps in it
                self.assertEqual(
                    str(new.seq).replace("-", ""), str(old.seq).replace("-", "")
                )
        except NexusError:
            # See bug 3119,
            # Bio.Nexus can't parse output from prank v100701 (1 July 2010)
            pass

    def test_Prank_complex_command_line(self):
        """Round-trip with complex command line."""
        cmdline = PrankCommandline(prank_exe)
        cmdline.set_parameter("d", self.infile1)
        cmdline.set_parameter("-gaprate", 0.321)
        cmdline.set_parameter("gapext", 0.6)
        cmdline.set_parameter("-dots", 1)  # i.e. True
        # Try using a property:
        cmdline.kappa = 3
        cmdline.skipins = True
        cmdline.set_parameter("-once", True)
        cmdline.realbranches = True
        self.assertEqual(
            str(cmdline),
            _escape_filename(prank_exe)
            + " -d=Fasta/fa01"
            + " -dots -gaprate=0.321 -gapext=0.6 -kappa=3"
            + " -once -skipins -realbranches",
        )
        self.assertEqual(str(eval(repr(cmdline))), str(cmdline))
        stdout, stderr = cmdline()
        self.assertIn("Total time", stdout)


class PrankConversion(unittest.TestCase):
    def setUp(self):
        # As these reads are all 36, it can be seen as pre-aligned:
        self.input = "Quality/example.fasta"
        self.output = "temp with space"  # prefix, PRANK will pick extensions

    def conversion(self, prank_number, prank_ext, format):
        """Get PRANK to do a conversion, and check it with SeqIO."""
        filename = "%s.%s" % (self.output, prank_ext)
        if os.path.isfile(filename):
            os.remove(filename)
        cmdline = PrankCommandline(
            prank_exe,
            d=self.input,
            convert=True,
            f=prank_number,
            o='"%s"' % self.output,
        )
        self.assertEqual(
            str(cmdline),
            _escape_filename(prank_exe)
            + " -d=%s" % self.input
            + ' -o="%s"' % self.output
            + " -f=%i" % prank_number
            + " -convert",
        )
        self.assertEqual(str(eval(repr(cmdline))), str(cmdline))
        message, error = cmdline()
        self.assertIn("PRANK", message)
        self.assertTrue(
            ("converting '%s' to '%s'" % (self.input, filename)) in message, message
        )
        self.assertEqual(error, "")
        self.assertTrue(os.path.isfile(filename))
        old = AlignIO.read(self.input, "fasta")
        # Hack...
        if format == "phylip":
            for record in old:
                record.id = record.id[:10]
        new = AlignIO.read(filename, format)
        self.assertEqual(len(old), len(new))
        for old_r, new_r in zip(old, new):
            self.assertEqual(old_r.id, new_r.id)
            self.assertEqual(str(old_r.seq), str(new_r.seq))
        os.remove(filename)

    def test_convert_to_fasta(self):
        """Convert FASTA to FASTA format."""
        self.conversion(8, "fas", "fasta")

    # Prank v.100701 seems to output an invalid file here...
    # def test_convert_to_phylip32(self):
    #    """Convert FASTA to PHYLIP 3.2 format."""
    #    self.conversion(11, "phy", "phylip")

    def test_convert_to_phylip(self):
        """Convert FASTA to PHYLIP format."""
        self.conversion(12, "phy", "phylip")

    # PRANK truncated the record names in the matrix block. An error?
    # def test_convert_to_paup_nexus(self):
    #    """Convert FASTA to PAUP/NEXUS."""
    #    self.conversion(17, "nex", "nexus")

    # We don't support format 18, PAML


if __name__ == "__main__":
    runner = unittest.TextTestRunner(verbosity=2)
    unittest.main(testRunner=runner)
