#!/usr/bin/env python
"""

    exceptions.py

"""

################################################################################
#
#   exceptions.py
#
#
#   Copyright (c) 10/9/2009 Leo Goodstadt
#   
#   Permission is hereby granted, free of charge, to any person obtaining a copy
#   of this software and associated documentation files (the "Software"), to deal
#   in the Software without restriction, including without limitation the rights
#   to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#   copies of the Software, and to permit persons to whom the Software is
#   furnished to do so, subject to the following conditions:
#   
#   The above copyright notice and this permission notice shall be included in
#   all copies or substantial portions of the Software.
#   
#   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#   IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#   OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
#   THE SOFTWARE.
#################################################################################

import sys, os
from collections import defaultdict


#88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

#   Exceptions


#88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

#if __name__ != '__main__':
#    import task
    
class error_task(Exception):
    def __init__(self, *errmsg):
        Exception.__init__(self, *errmsg)
        
        # list of associated tasks
        self.tasks = []

        # error message
        self.main_msg = ""
        
    def get_main_msg(self):
        """
        Make main message with lists of task names
        Prefix with new lines for added emphasis
        """
        # turn tasks names into 'def xxx(...): format
        task_names = []
        for t in self.tasks:
            task_name = t._name.replace('__main__.', '')
            if t.action_names[t._action_type] != "task_mkdir":
                task_name = "'def %s(...):'" % (task_name)
            task_names.append(task_name)
        
        task_names = "\n".join(task_names)
        if len(self.main_msg):
            return "\n\n" + self.main_msg + " for\n\n%s\n" % task_names
        else:
            return "\n\n%s\n" % task_names
        
        
    def __str__(self):
        #indent
        msg = self.get_main_msg() + " ".join(map(str, self.args))
        return "    " + msg.replace("\n", "\n    ")

    def specify_task (self, task, main_msg):
        self.tasks.append(task)
        self.main_msg = main_msg
        return self

class error_task_contruction(error_task):
    """
    Exceptions when contructing pipeline tasks
    """

    def __init__(self, task, main_msg, *errmsg):
        error_task.__init__(self, *errmsg)
        self.specify_task (task, main_msg)

class RethrownJobError(error_task):
    """
    Wrap up one or more exceptions rethrown across process boundaries

        See multiprocessor.Server.handle_request/serve_client for an analogous function
    """
    def __init__(self, job_exceptions):
        error_task.__init__(self)
        self.args = job_exceptions
    def __str__(self):
        message = ["\nOriginal exception%s:\n" % ("s" if len(self.args) > 1 else "")]
        #
        #   For each exception:
        #       turn original exception stack message into an indented string
        #
        for i, (task_name, job_name, exception_name, exception_value, exception_stack) in enumerate(self.args):
            message += "\nException #%d\n" % (i + 1)
            message += "%s%s:\n" % (exception_name, exception_value)
            message += "for %s.%s\n\n%s\n" % (task_name, job_name, exception_stack)
        return (self.get_main_msg() + "".join(message)).replace("\n", "\n    ")

class task_FilesArgumentsError(error_task):
    pass
class task_FilesreArgumentsError(error_task):
    pass
class MissingInputFileError(error_task):
    pass
class JobSignalledBreak(error_task):
    pass
class PostTaskArgumentError(error_task):
    pass

class error_task_get_output(error_task_contruction):
    pass
class error_task_transform_inputs_multiple_args(error_task_contruction):
    pass
class error_task_transform(error_task_contruction):
    pass
class error_task_merge(error_task_contruction):
    pass
class error_task_collate(error_task_contruction):
    pass
class error_task_collate_inputs_multiple_args(error_task_contruction):
    pass
class error_task_split(error_task_contruction):
    pass
class error_task_files_re(error_task_contruction):
    pass
class error_task_files(error_task_contruction):
    pass
class error_task_parallel(error_task_contruction):
    pass
class error_making_directory(error_task):
    pass
class error_duplicate_task_name(error_task):
    pass
class error_decorator_args(error_task):
    pass
class error_task_name_lookup_failed(error_task):
    pass
class error_task_decorator_takes_no_args(error_task):
    pass
class error_function_is_not_a_task(error_task):
    pass
class error_circular_dependencies(error_task):
    pass
class error_not_a_directory(error_task):
    pass
class error_missing_output(error_task):
    pass
class error_job_signalled_interrupt(error_task):
    pass
class error_node_not_task(error_task):
    pass
class error_missing_runtime_parameter(error_task):
    pass
    


#88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888

#   Testing


#88888888888888888888888888888888888888888888888888888888888888888888888888888888888888888
if __name__ == '__main__':
    import unittest

    #
    #   minimal task object to test exceptions
    # 
    class task:
        class _task (object):
            """
            dummy task
            """
            action_mkdir = 1
            def __init__(self, _name,  _action_type = 0):
                self._action_type   = _action_type
                self._name          = _name


    class Test_exceptions(unittest.TestCase):
    
        #       self.assertEqual(self.seq, range(10))
        #       self.assert_(element in self.seq)
        #       self.assertRaises(ValueError, random.sample, self.seq, 20)
    
    
                
        def test_error_task(self):
            """
                test 
            """
            fake_task1       = task._task("task1")
            fake_task2       = task._task("task2")
            fake_mkdir_task3 = task._task("task3", task._task.action_mkdir)
            fake_mkdir_task4 = task._task("task4", task._task.action_mkdir)
            e = error_task()
            e.specify_task (fake_task1      , "Some message 0")
            e.specify_task (fake_task2      , "Some message 1")
            e.specify_task (fake_mkdir_task3, "Some message 2")
            e.specify_task (fake_mkdir_task4, "Some message 3")
            self.assertEqual(str(e), 
"""    
    
    Some message 3 for
    
    'def task1(...):'
    'def task2(...):'
    task3
    task4
    """)

        def test_RethrownJobError(self):
            """
                test 
            """
            #job_name, exception_name, exception_value, exception_stack
            exception_data = [
                [
                    "task1",
                    "[[temp_branching_dir/a.2, a.1] -> temp_branching_dir/a.3]", 
                    "ruffus.task.MissingInputFileError",  
                    "(instance value)",      
                    "Traceback (most recent call last):\n  File \"what.file.py\", line 333, in some_func\n  somecode(sfasf)\n"
                 ],
                [
                    "task1",
                    "[None -> [temp_branching_dir/a.1, temp_branching_dir/b.1, temp_branching_dir/c.1]]", 
                    "exceptions.ZeroDivisionError:",  
                    "(1)",      
                    "Traceback (most recent call last):\n  File \"anotherfile.py\", line 345, in other_func\n  badcode(rotten)\n"
                 ]
                
            ]
            e = RethrownJobError(exception_data)
            fake_task1       = task._task("task1")
            fake_task2       = task._task("task2")
            fake_mkdir_task3 = task._task("task3", task._task.action_mkdir)
            fake_mkdir_task4 = task._task("task4", task._task.action_mkdir)
            e.specify_task (fake_task1      , "Exceptions running jobs")
            e.specify_task (fake_task2      , "Exceptions running jobs")
            e.specify_task (fake_mkdir_task3, "Exceptions running jobs")
            e.specify_task (fake_mkdir_task4, "Exceptions running jobs")
            self.assertEqual(str(e), 
"""
    
    Exceptions running jobs for
    
    'def task1(...):'
    'def task2(...):'
    task3
    task4
    
    Original exceptions:
    
    Exception #1
    ruffus.task.MissingInputFileError(instance value):
    for task1.[[temp_branching_dir/a.2, a.1] -> temp_branching_dir/a.3]
    
    Traceback (most recent call last):
      File "what.file.py", line 333, in some_func
      somecode(sfasf)
    
    
    Exception #2
    exceptions.ZeroDivisionError:(1):
    for task1.[None -> [temp_branching_dir/a.1, temp_branching_dir/b.1, temp_branching_dir/c.1]]
    
    Traceback (most recent call last):
      File "anotherfile.py", line 345, in other_func
      badcode(rotten)
    
    """)
    


#
#   debug code not run if called as a module
#     
if __name__ == '__main__':
    if sys.argv.count("--debug"):
        sys.argv.remove("--debug")
    unittest.main()



    
