"""General helper functions for WAeUP.
"""
import os
import re
import sys
import shutil
import grok
from cStringIO import StringIO
from docutils.core import publish_string
from zope.component.interfaces import IFactory
from zope.interface import implementedBy
from zope.schema import getFieldNames
from zope.schema.fieldproperty import FieldProperty

def removeFileOrDirectory(filepath):
    """Remove a file or directory.

    Different to :func:`shutil.rmtree` we also accept not existing
    paths (returning silently) and if a dir turns out to be a regular
    file, we remove that.
    """
    filepath = os.path.abspath(filepath)
    if not os.path.exists(filepath):
        return
    if os.path.isdir(filepath):
        shutil.rmtree(filepath)
    else:
        os.unlink(filepath)
    return

def copyFileSystemTree(src, dst, overwrite=False, del_old=False):
    """Copy contents of directory src to directory dst.

    Both directories must exists.

    If `overwrite` is true, any same named objects will be
    overwritten. Otherwise these files will not be touched.

    If `del_old` is true, copied files and directories will be removed
    from the src directory.

    This functions returns a list of non-copied files.

    Unix hidden files and directories (starting with '.') are not
    processed by this function.
    """
    if not os.path.exists(src):
        raise ValueError('source path does not exist: %s' % src)
    if not os.path.exists(dst):
        raise ValueError('destination path does not exist: %s' % dst)
    if not os.path.isdir(src):
        raise ValueError('source path is not a directory: %s' % src)
    if not os.path.isdir(dst):
        raise ValueError('destination path is not a directory: %s' % dst)
    not_copied = []
    for item in os.listdir(src):
        if item.startswith('.'):
            continue # We do not copy hidden stuff...
        itemsrc = os.path.join(src, item)
        itemdst = os.path.join(dst, item)

        if os.path.exists(itemdst):
            if overwrite is True:
                removeFileOrDirectory(itemdst)
            else:
                not_copied.append(item)
                continue

        if os.path.isdir(itemsrc):
            shutil.copytree(itemsrc, itemdst)
        else:
            shutil.copy2(itemsrc, itemdst)
        if del_old:
            removeFileOrDirectory(itemsrc)
    return not_copied


def getInnerHTMLPart(html_code):
    """Return the 'inner' part of a complete HTML snippet.

    If there is a form part, get this.

    If there is no form part, try to return the body part contents.

    If there is no body, return as-is.

    Let's see how that works. If we deliver some doc with form, we
    will get that form only:

       >>> doc = '<html><form>My Form</form>Outside the form</html>'
       >>> getInnerHTMLPart(doc)
       '<form>My Form</form>'

    No form? Then seek for a body part and get the contents:

       >>> doc = '<html><body>My Body</body>Trailing Trash</html>'
       >>> getInnerHTMLPart(doc)
       'My Body'

    If none of these is included, return what we got:

       >>> doc = '<html>without body nor form</html>'
       >>> getInnerHTMLPart(doc)
       '<html>without body nor form</html>'

    """

    try:
        result = re.match('^.+(<form[^\>]*>.*</form>).+$', html_code,
                          re.DOTALL).groups()[0]
        return result
    except AttributeError:
        # No <form> part included
        try:
            result = re.match('^.+<body[^\>]*>(.*)</body>.*$', html_code,
                              re.DOTALL).groups()[0]
            return result
        except AttributeError:
            # No <form> and no <body> tag...
            pass
    return html_code

class FactoryBase(grok.GlobalUtility):
    """A factory for things.

    This is a baseclass for easier creation of factories. Factories
    are utilities that are registered under a certain name and return
    instances of certain classes when called.

    In :mod:`waeup.sirp` we use factories extensively for
    batching. While processing a batch some importer looks up a
    factory to create real-world instances that then get filled with
    data from imported CSV files.

    To get rid of reimplementing the same stuff over and over again,
    most notably the methods defined here, we offer this base class
    (which will *not* be registered as a factory itself).

    Real factories can then be created like this:

       >>> import grok
       >>> from waeup.sirp.utils.helpers import FactoryBase
       >>> class MyObject(object):
       ...   # Some class we want to get instances of.
       ...   pass
       >>> class MyObjectFactory(FactoryBase):
       ...   # This is the factory for MyObject instances
       ...   grok.name(u'waeup.sirp.factory.MyObject')
       ...   factory = MyObject

    That's it. It is essential to set the ``factory`` attribute, which
    will determine the class of which instances should be created when
    called. The given name must even be unique amongst all utilities
    registered during runtime. While you can pick any name you like
    you might want to prepend ``waeup.sirp.factory.`` to the name
    string to make sure it does not clash with names of other
    utilities one day.

    Before all this works we have to grok the baseclass once and our
    freshly defined factory. This executes all the component
    registration stuff we don't want to do ourselves. In daily use
    this is done automatically on startup of a :mod:`waeup.sirp`
    system.

       >>> grok.testing.grok('waeup.sirp.utils.helpers')
       >>> grok.testing.grok_component(
       ...    'MyObjectFactory', MyObjectFactory
       ...  )
       True

    After grokking we (and importers) can create objects without
    knowing about the location of the real class definition, just by
    the factory name:

       >>> from zope.component import createObject
       >>> obj = createObject('waeup.sirp.factory.MyObject')
       >>> isinstance(obj, MyObject)
       True

    We can also use the regular utility lookups to find our new
    factory:

       >>> from zope.component import getUtility
       >>> from zope.component.interfaces import IFactory
       >>> factory = getUtility(
       ...   IFactory, name='waeup.sirp.factory.MyObject'
       ...   )
       >>> isinstance(factory, MyObjectFactory)
       True

    And this factory generates `MyObject` instances:

       >>> obj = factory()
       >>> isinstance(obj, MyObject)
       True

    """
    grok.baseclass() # Do not grok this class, do not register us.
    grok.implements(IFactory)
    # You can override any of the following attributes in derived
    # classes. The `grok.name` setting *must* even be set to some
    # unique value.
    grok.name(u'waeup.Factory')
    title = u"Create instances of ``factory``.",
    description = u"This factory instantiates new applicant instances."
    factory = None

    def __call__(self, *args, **kw):
        """The main factory function.

        Returns an instance of the requested object.
        """
        return self.factory()

    def getInterfaces(self):
        # Required by IFactory
        return implementedBy(self.factory)

def ReST2HTML_w_warnings(source_string):
    """Convert a reStructuredText string to HTML preserving warnings.

    Returns a tuple ``(<HTML_CODE>, <WARNINGS>)``, both being
    strings. Where ``<HTML_CODE>`` is the HTML code generated from the
    source string (in unicode), ``<WARNINGS>`` is a string containing
    any warning messages or ``None``.

    Regular multi-line ReStructuredText strings will be returned as
    HTML code:

        >>> from waeup.sirp.utils.helpers import ReST2HTML
        >>> source = '''
        ... Headline
        ... ========
        ...
        ... - A list item
        ... - Another item
        ...
        ... Thanks for watching!
        ... '''
        >>> html, warnings = ReST2HTML_w_warnings(source)
        >>> print html
        <div class="document" id="headline">
        <h1 class="title">Headline</h1>
        <BLANKLINE>
        <ul class="simple">
        <li>A list item</li>
        <li>Another item</li>
        </ul>
        <p>Thanks for watching!</p>
        </div>

    Here no warnings happened, so the `warnings` are ``None``:

        >>> warnings is None
        True

    If warnings happen then they can be retrieved in the returned
    ``warnings``. We try to render an erraneous document:

        >>> source = '''
        ... Headline
        ... ======
        ...
        ... Thanks for watching!
        ... '''
        >>> html, warnings = ReST2HTML_w_warnings(source)
        >>> print html
        <div class="document" id="headline">
        <h1 class="title">Headline</h1>
        <BLANKLINE>
        <p>Thanks for watching!</p>
        </div>

        >>> print warnings
        <string>:3: (WARNING/2) Title underline too short.
        <BLANKLINE>
        Headline
        ======
        <BLANKLINE>

    As you can see, the warnings are not displayed inline the document
    but can be retrieved from the returned warnings, which is a string
    or ``None``.
    """
    warnings = StringIO()
    fulldoc = publish_string(
        source_string, writer_name='html4css1',
        settings_overrides={
            'report_level': 0,
            'warning_stream': warnings,
            })
    warnings.seek(0)
    warning_msgs = warnings.read()
    if warning_msgs:
        # Render again, this time with no warnings inline...
        fulldoc =  publish_string(
        source_string, writer_name='html4css1',
        settings_overrides={
            'report_level': 10000,
            'halt_level': 10000,
            'warning_stream': warnings,
            })
    if warning_msgs == '':
        warning_msgs = None
    result = getInnerHTMLPart(fulldoc).strip()
    if not isinstance(result, unicode):
        result = result.decode('utf-8')
    return result, warning_msgs

def ReST2HTML(source_string):
    """Render a string containing ReStructuredText to HTML.

    Any warnings about too short headings, etc. are silently
    discarded. Use :func:`ReST2HTML_w_warnings` if you want to get any
    warnings.

    The returned string will be unicode.

    A regular document will be rendered like this:

        >>> source = '''
        ... Headline
        ... ========
        ...
        ... Thanks for watching!
        ... '''
        >>> html = ReST2HTML(source)
        >>> print html
        <div class="document" id="headline">
        <h1 class="title">Headline</h1>
        <BLANKLINE>
        <p>Thanks for watching!</p>
        </div>

    A document with markup problems (here: the underline is too short)
    will look similar:

        >>> source = '''
        ... Headline
        ... ======
        ...
        ... Thanks for watching!
        ... '''
        >>> html = ReST2HTML(source)
        >>> print html
        <div class="document" id="headline">
        <h1 class="title">Headline</h1>
        <BLANKLINE>
        <p>Thanks for watching!</p>
        </div>

    """
    html, warnings = ReST2HTML_w_warnings(source_string)
    return html

def attrs_to_fields(cls):
    """Turn the attributes of a class into FieldProperty instances.

    With Python >= 2.6 we can even use this function as a class decorator.
    """
    iface = list(implementedBy(cls))[0]
    for field_name in getFieldNames(iface):
        setattr(cls, field_name, FieldProperty(iface[field_name]))
    return cls
