import threading
from datetime import datetime
import dateutil.parser
import sys

from redbeans.formats import Format
from redbeans.html import HTMLFormat
from redbeans.latex import LaTeXFormat
from redbeans.creole import tokenize
from redbeans.tokens import *

from . import translators

# Add to this to give additional wiki-based output formats
FORMATS = {'txt': Format,
           'html': HTMLFormat,
           'tex': LaTeXFormat}

class flavor(object):
    # Indexed propvals are OK to search by and are loaded synchonously;
    # non-indexed propvals are loaded asychronously when editing.
    indexed = True
    binary = False
    # A raw propval never gets its contents treated as wikitext.
    raw = False
    # A quick propval can be (top-level) evaluate()d in constant time.
    quick = False
    default = ""
    default_formats = (u'.html',)

    @staticmethod
    def stringify(p):
        return unicode(p)

    @staticmethod
    def getExtensions(propval):
        return FORMATS.keys()
    
    @classmethod
    def evaluate(cls, wikitext, parser, propval=None):
        """This is called only for top-level evaluations."""
        toks = cls.tokenize(wikitext, parser, propval)
        if parser.format.text_based:
            return parser.render(toks)
        else:
            return list(parser.irender(toks))

    @classmethod
    def tokenize(cls, wikitext, parser, propval=None):
        yield Text(cls.stringify(cls.toPython(
            wikitext, parser, propval=propval)))

    @classmethod
    def toPython(cls, wikitext, parser, propval=None):
        import wiki
        raise wiki.WikiException("Flavor %s has no python conversion, so you can't use prop '%s' in an expression!"%(cls.__name__,propval.propname if propval is not None else wikitext))

class text(flavor):
    """Freeform or longer text; not searchable."""
    indexed = False
    big = True

    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        #print "Evaluating test: %s" % repr(wikitext)
        return tokenize(wikitext, parser.error_func)

    @staticmethod
    def toPython(wikitext, parser, propval=None):
        return parser.parse(wikitext, format=Format())

class raw(flavor):
    """Short searchable text that's not evaluated."""
    raw = True
    quick = True

    @staticmethod
    def toPython(wikitext, parser, propval=None):
        return wikitext
    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        yield Text(raw.toPython(
            wikitext, parser, propval=propval))

class macro(text):
    """Macro text only evaluated in the context of another propval."""
    indexed = False
    big = True
    quick = True
    
    @staticmethod
    def evaluate(wikitext, parser, propval=None):
        if propval is not None:
            import wiki, formatting
            nv, deps = formatting.nice_value(propval)
            wiki.addDeps(deps)
            return parser.render([Start(CODEBLOCK), Text(nv),
                                  End(CODEBLOCK)])
        else:
            return text.evaluate(self, wikitext, parser, propval)

class blob(flavor):
    """Binary objects with no markup."""
    indexed = False
    binary = True
    default_formats = (u'.png',u'.svg',u'.jpg')
    image_formats = (u'.png',u'.jpg',u'.gif',u'.svg',u'.eps')

    @staticmethod
    def getExtensions(propval):
        # TODO(xavid): This is a horrible hack.
        while propval.value == '<<parent.this />>':
            propval = propval.element.get_parent().get_propval(
                propval.propname)
        ext = propval.format
        assert ext != 'creole'
        return [ext]+FORMATS.keys()

    @staticmethod
    def evaluate(wikitext, parser, propval=None):
        # TODO(xavid): This is a horrible hack.
        assert isinstance(wikitext, str), wikitext
        if wikitext == '<<parent.this />>':
            assert propval is not None and str(propval.value) == '<<parent.this />>', repr(str(propval.value))
            while propval.value == '<<parent.this />>':
                propval = propval.element.get_parent().get_propval(
                    propval.propname)
            wikitext = propval.value
        if propval is not None:
            ef = propval.format
        else:
            ef = '.dat'
        href = '%s.%s%s' % (propval.element.ename, propval.propname, ef)
        if ef == parser or (ef in FORMATS and
                            hasattr(parser,'get_format')
                            and isinstance(parser.get_format(),FORMATS[ef])):
            return wikitext
        elif ef in blob.image_formats:
            return parser.render([
                Start(LINK, href),
                Entity(IMAGE, '%s.%s' % (propval.element.ename,
                                         propval.propname)),
                End(LINK, href)])
        elif ef == '.txt':
            return parser.render([
                Text(unicode(wikitext,'utf-8'))])
        else:
            return parser.render([
                Start(LINK, href),
                Text(u"[%s data]" % (ef)),
                End(LINK, href)])

    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        assert False

class string(flavor):
    """Short searchable text."""
    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        return tokenize(wikitext, parser.error_func)
    @staticmethod
    def toPython(wikitext,parser,propval=None):
        assert isinstance(wikitext, unicode)
        return parser.parse(wikitext, format=Format())

def str_to_bool(s):
    return s.strip()[0].lower() not in ('n','f','0')

class boolean(flavor):
    """True or false."""
    default = False
    @staticmethod
    def toPython(wikitext,parser,propval=None):
        text = parser.parse(wikitext, format=Format())
        return str_to_bool(text)

def restricted(conv, defa, help, stringify=None):
    class rflav(flavor):
        __doc__ = help
        default = defa
        @staticmethod
        def toPython(wikitext,parser,propval=None):
            text = parser.parse(wikitext, format=Format())
            try:
                show = conv(text)
            except ValueError:
                show = defa
            return show
    if stringify is not None:
        rflav.stringify = staticmethod(stringify)
    return rflav

integer = restricted(int, -1, """An integral number.""")

TIMESTAMP_FORMAT = "%Y-%m-%d %H:%M:%S"
# TODO(xavid): This would probably be cleaner with restricted as a superclass.
#              Probably everything should stop being static and integer should
#              be an instance of class restricted.
# TODO(xavid): Handle invalid dates as N/A, not as long ago.
# We could use a more human-readable format for stringify, but it can't
# be pretty.date() unless we make it add a dependency on DISCORDIA.
timestamp = restricted(lambda w: dateutil.parser.parse(w),
                       None, """A date and time.""",
                       lambda p: (unicode(p.strftime(TIMESTAMP_FORMAT))
                                  if p is not None else u""))
timestamp.now = staticmethod(lambda: datetime.strftime(
    datetime.now(), TIMESTAMP_FORMAT))

class Reference(object):
    def __init__(self, element, args):
        self.element = element
        self.args = args
        for a in args:
            assert isinstance(a, unicode), args

    def has_propval(self, propname):
        return self.element.has_propval(propname)
    
    def __repr__(self):
        return "[Reference: %s/%s]" % (self.element.ename, '/'.join(self.args))
    def __eq__(self, other):
        return (self.element == getattr(other, 'element', None)
                and self.args == getattr(other, 'args', None))
def maybe_ref(element, args):
    if args is not None:
        return Reference(element, args)
    else:
        return element

class references(flavor):
    """A list of links to other elements."""
    default = []

    @staticmethod
    def _enames(wikitext, propval=None):
        import wiki
        refs = wiki.get_reference_enames(wikitext, propval=propval)
        #print >>sys.stderr, "refs=", refs
        return refs

    @staticmethod
    def toPython(wikitext,parser,propval=None):
        from . import structure
        return [maybe_ref(e,a) for e,a
                in ((structure.get_element(e), a)
                    for e,a in references._enames(wikitext, propval))
                if e is not None]

    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        for ename,args in references._enames(wikitext, propval):
            yield Start(UNORDERED_ITEM, 1)
            if args:
                yield Entity(LINK, ename + '/' + '/'.join(args))
            else:
                yield Entity(LINK, ename)
            yield End(UNORDERED_ITEM, 1)

class reference(flavor):
    """A single link to another element."""
    default = None

    @staticmethod
    def _ename(wikitext, propval=None):
        import wiki
        refs = wiki.get_reference_enames(wikitext, propval=propval)
        if len(refs) > 1:
            raise wiki.WikiException("Expected 1 reference, got %d!"
                                     % (len(refs)))
        if len(refs) == 0:
            return None
        else:
            return refs.pop()[0]

    @staticmethod
    def toPython(wikitext,parser,propval=None):
        from . import structure
        return structure.get_element(reference._ename(wikitext, propval))

    MARKUP = u'[[%s]]'

    @staticmethod
    def element_to_string(element):
        from . import wiki
        val, deps, metadata = wiki.evaluate(reference.MARKUP % element.ename,
                                            'txt', element=element,
                                            flavor=reference)
        return val

    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        ename = reference._ename(wikitext, propval)
        if ename is not None:
            yield Entity(LINK, ename)

class labeledrefs(flavor):
    """A list of links to other elements with label text."""
    default = []

    @staticmethod
    def _ename_label_pairs(wikitext, propval=None):
        import wiki
        reflabels = wiki.get_reference_enames_with_labels(wikitext,
                                                          propval=propval)
        return reflabels

    @staticmethod
    def toPython(wikitext,parser,propval=None):
        from . import structure
        ret = []
        def make_render_label(l):
            def render_label(argstr, content):
                return l
            return render_label
        for e,l in ((structure.get_element(e), l)
                    for e,l,a in
                    labeledrefs._ename_label_pairs(wikitext, propval)):
            if e is not None:
                ret.append([e, make_render_label(l)])
        return ret

    @staticmethod
    def tokenize(wikitext, parser, propval=None):
        for ename,label,args in labeledrefs._ename_label_pairs(wikitext,
                                                               propval):
            yield Start(UNORDERED_ITEM, 1)
            yield Entity(LINK, ename)
            yield Text(u": ")
            if label is not None:
                for t in label:
                    yield t
            else:
                yield Error("No label!")
            yield End(UNORDERED_ITEM, 1)

FLAVORS = {
    'text': text,
    'string': string,
    'integer': integer,
    'boolean': boolean,
    'references': references,
    'reference': reference,
    'labeledrefs': labeledrefs,
    'blob': blob,
    'macro': macro,
    'raw': raw,
    'timestamp': timestamp,
    }
