# Copyright 2012 by Christian Brueffer.  All rights reserved.
#
# 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.

import glob
import os
import shutil
import sys
import unittest
from Bio import MissingExternalDependencyError
from Bio import SeqIO
from Bio.Application import ApplicationError
from Bio.Motif.Applications import XXmotifCommandline


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

xxmotif_exe = None
if sys.platform == "win32":
    # TODO
    raise MissingExternalDependencyError("Testing this on Windows is not implemented yet")
else:
    from Bio._py3k import getoutput
    output = getoutput("XXmotif")
    if output.find("== XXmotif version") != -1:
        xxmotif_exe = "XXmotif"

if not xxmotif_exe:
    raise MissingExternalDependencyError(
        "Install XXmotif if you want to use XXmotif from Biopython.")


class XXmotifTestCase(unittest.TestCase):

    def setUp(self):
        self.out_dir = "xxmotif-temp"
        self.files_to_clean = set()

    def tearDown(self):
        for filename in self.files_to_clean:
            if os.path.isfile(filename):
                os.remove(filename)

        if os.path.isdir(self.out_dir):
            shutil.rmtree(self.out_dir)

    def standard_test_procedure(self, cline):
        """Standard testing procedure used by all tests."""
        output, error = cline()

        self.assertTrue(os.path.isdir(self.out_dir))
        self.assertTrue(glob.glob(os.path.join(self.out_dir, "*.meme")))
        self.assertTrue(glob.glob(os.path.join(self.out_dir, "*_MotifFile.txt")))
        self.assertTrue(glob.glob(os.path.join(self.out_dir, "*_Pvals.txt")))
        self.assertTrue(glob.glob(os.path.join(self.out_dir, "*.pwm")))
        self.assertTrue(glob.glob(os.path.join(self.out_dir, "*_sequence.txt")))

        # TODO
        # Parsing the MEME file would be nice, but unfortunately the
        # MEME parser does not like what XXmotif produces yet.

    def copy_and_mark_for_cleanup(self, path):
        """
        XXmotif currently only handles a canonical filename as input, no paths.
        This method copies the specified file in the specified path to the
        current working directory and marks it for removal.
        """
        filename = os.path.split(path)[1]

        shutil.copyfile(path, filename)
        self.add_file_to_clean(filename)

        return filename

    def add_file_to_clean(self, filename):
        """Adds a file for deferred removal by the tearDown routine."""
        self.files_to_clean.add(filename)


class XXmotifTestErrorConditions(XXmotifTestCase):

    def test_empty_file(self):
        """Test a non-existing input file."""
        input_file = "does_not_exist.fasta"
        self.assertFalse(os.path.isfile(input_file))

        cline = XXmotifCommandline(outdir=self.out_dir,
                                   seqfile=input_file)

        try:
            stdout, stderr = cline()
        except ApplicationError as err:
            self.assertEqual(err.returncode, 255)
        else:
            self.fail("Should have failed, returned:\n%s\n%s" % (stdout, stderr))

    def test_invalid_format(self):
        """Test an input file in an invalid format."""
        input_file = self.copy_and_mark_for_cleanup("Medline/pubmed_result1.txt")

        cline = XXmotifCommandline(outdir=self.out_dir,
                                   seqfile=input_file)

        try:
            stdout, stderr = cline()
        except ApplicationError as err:
            self.assertEqual(err.returncode, 255)
        else:
            self.fail("Should have failed, returned:\n%s\n%s" % (stdout, stderr))

    def test_output_directory_with_space(self):
        """Test an output directory containing a space."""
        temp_out_dir = "xxmotif test"
        input_file = self.copy_and_mark_for_cleanup("Fasta/f002")

        try:
            cline = XXmotifCommandline(outdir=temp_out_dir,
                                       seqfile=input_file)
        except ValueError:
            pass
        else:
            self.fail("expected ValueError")


class XXmotifTestNormalConditions(XXmotifTestCase):

    def test_fasta_one_sequence(self):
        """Test a fasta input file containing only one sequence."""
        input_file = "seq.fasta"
        handle = open(input_file, "w")
        record = list(SeqIO.parse("Registry/seqs.fasta", "fasta"))[0]
        SeqIO.write(record, handle, "fasta")
        handle.close()
        del handle, record

        cline = XXmotifCommandline(outdir=self.out_dir,
                                   seqfile=input_file)

        self.add_file_to_clean(input_file)
        self.standard_test_procedure(cline)

    def test_properties(self):
        """Test setting options via properties."""
        input_file = self.copy_and_mark_for_cleanup("Fasta/f002")

        cline = XXmotifCommandline(outdir=self.out_dir,
                                   seqfile=input_file)

        cline.revcomp = True
        cline.pseudo = 20
        cline.startmotif = "ACGGGT"

        self.standard_test_procedure(cline)

    def test_large_fasta_file(self):
        """Test a large fasta input file."""
        input_file = "temp_b_nuc.fasta"
        handle = open(input_file, "w")
        records = list(SeqIO.parse("NBRF/B_nuc.pir", "pir"))
        SeqIO.write(records, handle, "fasta")
        handle.close()
        del handle, records

        cline = XXmotifCommandline(outdir=self.out_dir,
                                   seqfile=input_file)

        self.add_file_to_clean(input_file)
        self.standard_test_procedure(cline)

    def test_input_filename_with_space(self):
        """Test an input filename containing a space."""
        input_file = "temp horses.fasta"
        handle = open(input_file, "w")
        SeqIO.write(SeqIO.parse("Phylip/hennigian.phy", "phylip"), handle, "fasta")
        handle.close()

        cline = XXmotifCommandline(outdir=self.out_dir,
                                   seqfile=input_file)

        self.add_file_to_clean(input_file)
        self.standard_test_procedure(cline)


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