# 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)