import datetime
import hashlib
import logging
import re
import threading
import types

from django.utils.encoding import force_unicode

logger = logging.getLogger(__name__)


class JSVar(unicode):
    """
    A JS variable.

    This is a simple unicode string. This class type acts as a marker that this string is a JS variable name,
    so it must not be quoted by :py:func:`.convert_py_to_js_data` while rendering the JS code.
    """
    pass


class JSFunction(JSVar):
    """
    A JS function name.

    From rendering point of view, rendering this is no different from :py:class:`JSVar`. After all, a JS varible
    can refer a function instance, primitive constant or any other object. They are still all varibles.

    .. tip:: Do use this marker for JS functions. This will make the code clearer, and the purpose more easier to
        understand.
    """
    pass


class JSFunctionInContext(JSVar):
    """
    A JS function name to run in context of some other Html DOM element.

    Like :py:class:`JSFunction`, this too flags the string as JS function, but with a special requirement. The JS function
    needs to be invoked in the context of a Html DOM, such that, ``this`` inside the function refers to that DOM instead of
    ``window``.

    .. tip:: JS functions of this type are warapped inside special another JS function -- ``django_select2.runInContextHelper``.
    """
    pass


def render_js_script(inner_code):
    """
    This wraps ``inner_code`` string inside the following code block::

        <script type="text/javascript">
            jQuery(function ($) {
                // inner_code here
            });
        </script>

    :rtype: :py:obj:`unicode`
    """
    return u"""
    <script type="text/javascript">
        jQuery(function ($) {
            %s
        });
    </script>""" % inner_code


def extract_some_key_val(dct, keys):
    """
    Gets a sub-set of a :py:obj:`dict`.

    :param dct: Source dictionary.
    :type dct: :py:obj:`dict`

    :param keys: List of subset keys, which to extract from ``dct``.
    :type keys: :py:obj:`list` or any iterable.

    :rtype: :py:obj:`dict`
    """
    edct = {}
    for k in keys:
        v = dct.get(k, None)
        if v is not None:
            edct[k] = v
    return edct


def convert_to_js_str(val):
    val = force_unicode(val).replace('\'', '\\\'')
    return u"'%s'" % val

def convert_py_to_js_data(val, id_):
    """
    Converts Python data type to JS data type.

    Practically what this means is, convert ``False`` to ``false``, ``True`` to ``true`` and so on.
    It also takes care of the conversion of :py:class:`.JSVar`, :py:class:`.JSFunction`
    and :py:class:`.JSFunctionInContext`. It takes care of recursively converting lists and dictionaries
    too.

    :param val: The Python data to convert.
    :type val: Any

    :param id_: The DOM id of the element in which context :py:class:`.JSFunctionInContext` functions
        should run. (This is not needed if ``val`` contains no :py:class:`.JSFunctionInContext`)
    :type id_: :py:obj:`str`

    :rtype: :py:obj:`unicode`
    """
    if type(val) == types.BooleanType:
        return u'true' if val else u'false'
    elif type(val) in [types.IntType, types.LongType, types.FloatType]:
        return force_unicode(val)
    elif isinstance(val, JSFunctionInContext):
        return u"django_select2.runInContextHelper(%s, '%s')" % (val, id_)
    elif isinstance(val, JSVar):
        return val  # No quotes here
    elif isinstance(val, dict):
        return convert_dict_to_js_map(val, id_)
    elif isinstance(val, list):
        return convert_to_js_arr(val, id_)
    else:
        return convert_to_js_str(val)


def convert_dict_to_js_map(dct, id_):
    """
    Converts a Python dictionary to JS map.

    :param dct: The Python dictionary to convert.
    :type dct: :py:obj:`dict`

    :param id_: The DOM id of the element in which context :py:class:`.JSFunctionInContext` functions
        should run. (This is not needed if ``dct`` contains no :py:class:`.JSFunctionInContext`)
    :type id_: :py:obj:`str`

    :rtype: :py:obj:`unicode`
    """
    out = u'{'
    is_first = True
    for name in dct:
        if not is_first:
            out += u", "
        else:
            is_first = False

        out += u"%s: " % convert_to_js_str(name)
        out += convert_py_to_js_data(dct[name], id_)

    return out + u'}'


def convert_to_js_arr(lst, id_):
    """
    Converts a Python list (or any iterable) to JS array.

    :param lst: The Python iterable to convert.
    :type lst: :py:obj:`list` or Any iterable

    :param id_: The DOM id of the element in which context :py:class:`.JSFunctionInContext` functions
        should run. (This is not needed if ``lst`` contains no :py:class:`.JSFunctionInContext`)
    :type id_: :py:obj:`str`

    :rtype: :py:obj:`unicode`
    """
    out = u'['
    is_first = True
    for val in lst:
        if not is_first:
            out += u", "
        else:
            is_first = False

        out += convert_py_to_js_data(val, id_)

    return out + u']'


def convert_to_js_string_arr(lst):
    """
    Converts a Python list (or any iterable) of strings to JS array.

    :py:func:`convert_to_js_arr` can always be used instead of this. However, since it
    knows that it only contains strings, it cuts down on unnecessary computations.

    :rtype: :py:obj:`unicode`
    """
    lst = [convert_to_js_str(l) for l in lst]
    return u"[%s]" % (",".join(lst))


### Auto view helper utils ###

from . import __ENABLE_MULTI_PROCESS_SUPPORT as ENABLE_MULTI_PROCESS_SUPPORT, \
    __MEMCACHE_HOST as MEMCACHE_HOST, __MEMCACHE_PORT as MEMCACHE_PORT, __MEMCACHE_TTL as MEMCACHE_TTL

from . import __GENERATE_RANDOM_ID as GENERATE_RANDOM_ID, __SECRET_SALT as SECRET_SALT

def synchronized(f):
    "Decorator to synchronize multiple calls to a functions."
    f.__lock__ = threading.Lock()

    def synced_f(*args, **kwargs):
        with f.__lock__:
            return f(*args, **kwargs)

    synced_f.__doc__ = f.__doc__
    return synced_f

# Generated Id to field instance mapping.
__id_store = {}
# Field's key to generated Id mapping.
__field_store = {}


ID_PATTERN = r"[0-9_a-zA-Z.:+\- ]+"

def is_valid_id(val):
    """
    Checks if ``val`` is a valid generated Id.

    :param val: The value to check.
    :type val: :py:obj:`str`

    :rtype: :py:obj:`bool`
    """
    regex = "^%s$" % ID_PATTERN
    if re.match(regex, val) is None:
        return False
    else:
        return True

if ENABLE_MULTI_PROCESS_SUPPORT:
    from memcache_wrapped_db_client import Client
    remote_server = Client(MEMCACHE_HOST, str(MEMCACHE_PORT), MEMCACHE_TTL)

@synchronized
def register_field(key, field):
    """
    Registers an Auto field for use with :py:class:`.views.AutoResponseView`.

    :param key: The key to use while registering this field.
    :type key: :py:obj:`unicode`

    :param field: The field to register.
    :type field: :py:class:`AutoViewFieldMixin`

    :return: The generated Id for this field. If given ``key`` was already registered then the
        Id generated that time, would be returned.
    :rtype: :py:obj:`unicode`
    """
    global __id_store, __field_store

    from fields import AutoViewFieldMixin
    if not isinstance(field, AutoViewFieldMixin):
        raise ValueError('Field must extend AutoViewFieldMixin')

    if key not in __field_store:
        # Generating id
        if GENERATE_RANDOM_ID:
            id_ = u"%d:%s" % (len(__id_store), unicode(datetime.datetime.now()))
        else:
            id_ = unicode(hashlib.sha1("%s:%s" % (key, SECRET_SALT)).hexdigest())

        __field_store[key] = id_
        __id_store[id_] = field

        if logger.isEnabledFor(logging.INFO):
            logger.info("Registering new field: %s; With actual id: %s", key, id_)

        if ENABLE_MULTI_PROCESS_SUPPORT:
            logger.info("Multi process support is enabled. Adding id-key mapping to remote server.")
            remote_server.set(id_, key)
    else:
        id_ = __field_store[key]
        if logger.isEnabledFor(logging.INFO):
            logger.info("Field already registered: %s; With actual id: %s", key, id_)
    return id_


def get_field(id_):
    """
    Returns an Auto field instance registered with the given Id.

    :param id_: The generated Id the field is registered with.
    :type id_: :py:obj:`unicode`

    :rtype: :py:class:`AutoViewFieldMixin` or None
    """
    field = __id_store.get(id_, None)
    if field is None and ENABLE_MULTI_PROCESS_SUPPORT:
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug('Id "%s" not found in this process. Looking up in remote server.', id_)
        key = remote_server.get(id_)
        if key is not None:
            id_in_current_instance = __field_store[key]
            if id_in_current_instance:
                field = __id_store.get(id_in_current_instance, None)
                if field:
                    __id_store[id_] = field
            else:
                logger.error('Unknown id "%s".', id_in_current_instance)
        else:
            logger.error('Unknown id "%s".', id_)
    return field

def timer_start(name):
    import sys, time
    if sys.platform == "win32":
        # On Windows, the best timer is time.clock()
        default_timer = time.clock
        multiplier = 1.0
    else:
        # On most other platforms the best timer is time.time()
        default_timer = time.time
        multiplier = 1000.0

    return (name, default_timer, multiplier, default_timer())

def timer_end(t):
    (name, default_timer, multiplier, timeS) = t
    timeE = default_timer()
    logger.debug("Time taken by %s: %0.3f ms" % (name, (timeE - timeS) * multiplier))

def timer(f):
    def inner(*args, **kwargs):
        
        t = timer_start(f.func_name)
        ret = f(*args, **kwargs)
        timer_end(t)

        return ret
    return inner
