"""
A package for automatically fetching files (eg. Ancillary).
"""
import os
import re
import logging
import tempfile

_log = logging.getLogger(__name__)


class DataSource(object):
    """
    A base class for data downloaders.

    Overridden by specific subclasses: HTTP, FTP, RSS and others.
    """

    def __init__(self):
        """
        Base class constructor.
        """
        super(DataSource, self).__init__()

    def trigger(self, reporter):
        """
        Trigger download from the source.

        :type reporter: FetchReporter
        :return:
        """
        raise NotImplementedError("Trigger was not implemented")

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.__dict__)


class FetchReporter(object):
    """
    A series of callbacks to report on the status of downloads.
    """

    def __init__(self):
        """
        Base class constructor.
        """
        super(FetchReporter, self).__init__()

    def file_error(self, uri, message):
        """
        Call on failure of a file.
        :type uri: str
        :type message: str
        """
        pass

    def file_complete(self, uri, name, path):
        """
        Call on completion of a file
        :type uri: str
        :type name: str
        :type path: str
        """
        pass


class FilenameTransform(object):
    """
    A base class for objects that modify output filenames and directories.

    Primarily useful for situations such as putting files in folders by date.
    """

    def __init__(self):
        super(FilenameTransform, self).__init__()

    def transform_filename(self, source_filename):
        """
        Modify output filename
        """
        return source_filename

    def transform_output_path(self, output_path, source_filename):
        """
        Modify output folder path
        """
        return output_path

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, self.__dict__)


class RegexpOutputPathTransform(FilenameTransform):
    """
    Extract fields from a filename using regexp groups.

    Replace patterns matching the group name in the destination path.
    """

    def __init__(self, pattern):
        """
        :type pattern: str
        """
        super(RegexpOutputPathTransform, self).__init__()

        # Validate the pattern immediately on startup.

        # We don't bother keeping the compiled version -- there's no perf. benefit, and
        # serialisation is more complicated.
        try:
            re.compile(pattern)
        except re.error:
            _log.error('Invalid pattern %r', pattern)
            raise

        self.pattern = pattern

    def transform_output_path(self, path, source_filename=None):
        """

        :param path:
        :param source_filename:

        >>> RegexpOutputPathTransform(r'LS8_(?P<year>\\d{4})').transform_output_path('/tmp/out/{year}', 'LS8_2003')
        '/tmp/out/2003'
        >>> RegexpOutputPathTransform(r'LS8_(?P<year>\\d{4})').transform_output_path('/tmp/out', 'LS8_2003')
        '/tmp/out'
        """
        m = re.match(self.pattern, source_filename)

        if not m:
            _log.info('No regexp match for %r', path)
            return path

        groups = m.groupdict()

        return path.format(**groups)


def fetch_file(uri,
               fetch_fn,
               reporter,
               target_filename,
               target_dir,
               filename_transform=None,
               override_existing=False):
    """
    Common code for fetching a file. The actual

    transfer is handled by a function passed in as fetch_fn, and
    so is not specific to a protocol.
    :param uri: A URI identifier for this file.
    :param fetch_fn: Function taking a filename argument to download to.
    :param reporter: The fetch reporter
    :param target_filename: The destination filename
    :param target_dir: The destination directory
    :param filename_transform: A transform for output filenames/folders.
    :param override_existing: Should files be re-downloaded if they already exist?
    """
    if filename_transform:
        target_dir = filename_transform.transform_output_path(
            target_dir,
            source_filename=target_filename
        )
        target_filename = filename_transform.transform_filename(target_filename)

    target_path = os.path.join(target_dir, target_filename)
    if os.path.exists(target_path) and not override_existing:
        _log.info('Path exists %r. Skipping', target_path)
        return

    t = tempfile.mktemp(
        dir=target_dir,
        prefix='.fetch-'
    )
    # TODO: Cleanup tmp files on failure

    fetch_fn(t)

    size_bytes = os.path.getsize(t)
    if size_bytes == 0:
        _log.debug('Empty file returned for file %r', uri)
        reporter.file_error(uri, "Empty return")
        return

    # Move to destination
    os.rename(t, target_path)
    # Report as complete.
    reporter.file_complete(uri, target_filename, target_path)


@@ -96,16 +96,30 @@
 logging%0A
+import shutil%0A
 import t
@@ -4583,24 +4583,50 @@
     return%0A%0A
+    t = None%0A    try:%0A    
     t = temp
@@ -4638,16 +4638,20 @@
 mktemp(%0A
+    
         
@@ -4674,16 +4674,20 @@
         
+    
 prefix='
@@ -4703,52 +4703,19 @@
     
-)%0A
     
-# TODO: Cleanup tmp files on failure%0A%0A
+)%0A%0A    
     
@@ -4727,16 +4727,20 @@
 _fn(t)%0A%0A
+    
     size
@@ -4767,16 +4767,20 @@
 size(t)%0A
+    
     if s
@@ -4799,24 +4799,28 @@
  0:%0A        
+  
+  
 _log.debug('
@@ -4858,16 +4858,20 @@
 ', uri)%0A
+    
         
@@ -4911,32 +4911,36 @@
 eturn%22)%0A        
+    
 return%0A%0A    # Mo
@@ -4935,16 +4935,20 @@
 rn%0A%0A    
+ 
+   
 # Move t
@@ -4965,16 +4965,20 @@
 ion%0A    
+   
+ 
 os.renam
@@ -4991,24 +4991,28 @@
 arget_path)%0A
+    
     # Report
@@ -5025,24 +5025,28 @@
 mplete.%0A    
+ 
+   
 reporter.fil
@@ -5090,10 +5090,88 @@
 et_path)
+%0A    finally:%0A        if t and os.path.exists(t):%0A            shutil.rmtree(t)
 %0A%0A

Clean up temp files on exception.