# w.c.s. - web application for online forms
# Copyright (C) 2005-2010  Entr'ouvert
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <http://www.gnu.org/licenses/>.

import copy
import threading

import django.apps
from django.conf import settings
from django.utils import six
from django.utils.encoding import force_text
from django.utils.six.moves import configparser as ConfigParser

from quixote import get_publisher

try:
    import lasso
except ImportError:
    lasso = None


# force_str: compatibility function to produce an encoded string in Python 2
# and a proper unicode string in Python 3
if six.PY2:
    def force_str(s):
        if isinstance(s, str):
            return s
        return s.encode('utf-8')
else:
    force_str = force_text

if six.PY2:
    PICKLE_KWARGS = {}
else:
    # unpickle python2 strings as bytes
    PICKLE_KWARGS = {'encoding': 'bytes'}

def _(message):
    pub = get_publisher()
    if pub is None:
        return message
    return force_str(force_text(pub.gettext(str(message))))

def ngettext(*args):
    pub = get_publisher()
    if pub is None:
        return message
    return force_str(force_text(pub.ngettext(*args)))


from .publisher import get_cfg, get_logger, get_publisher_class
from . import publisher
publisher._ = _


class TenantAwareThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        self.publisher = copy.copy(get_publisher())
        if self.publisher:
            self.publisher.detach()
        super(TenantAwareThread, self).__init__(*args, **kwargs)

    def run(self):
        if self.publisher:
            self.publisher.set_in_thread()
        super(TenantAwareThread, self).run()


class _Timer(TenantAwareThread):
    """Call a function after a specified number of seconds:

            t = Timer(30.0, f, args=[], kwargs={})
            t.start()
            t.cancel()     # stop the timer's action if it's still waiting

    """

    def __init__(self, interval, function, args=[], kwargs={}):
        super(_Timer, self).__init__()
        self.interval = interval
        self.function = function
        self.args = args
        self.kwargs = kwargs
        self.finished = threading.Event()

    def cancel(self):
        """Stop the timer if it hasn't finished yet"""
        self.finished.set()

    def run(self):
        self.finished.wait(self.interval)
        if not self.finished.is_set():
            self.function(*self.args, **self.kwargs)
        self.finished.set()


class _MainThread(TenantAwareThread):

    def __init__(self):
        super(_MainThread, self).__init__(name="MainThread")
        self._Thread__started.set()
        self._set_ident()
        with threading._active_limbo_lock:
            threading._active[threading._get_ident()] = self

    def _set_daemon(self):
        return False

    def _exitfunc(self):
        self._Thread__stop()
        t = threading._pickSomeNonDaemonThread()
        if t:
            if __debug__:
                self._note("%s: waiting for other threads", self)
        while t:
            t.join()
            t = threading._pickSomeNonDaemonThread()
        if __debug__:
            self._note("%s: exiting", self)
        self._Thread__delete()


class _DummyThread(TenantAwareThread):

    def __init__(self):
        super(_DummyThread, self).__init__(name=threading._newname("Dummy-%d"))

        # Thread.__block consumes an OS-level locking primitive, which
        # can never be used by a _DummyThread.  Since a _DummyThread
        # instance is immortal, that's bad, so release this resource.
        del self._Thread__block

        self._Thread__started.set()
        self._set_ident()
        with threading._active_limbo_lock:
            threading._active[threading._get_ident()] = self

    def _set_daemon(self):
        return True

    def join(self, timeout=None):
        assert False, "cannot join a dummy thread"


class AppConfig(django.apps.AppConfig):
    name = 'wcs.qommon'

    def ready(self):
        config = ConfigParser.ConfigParser()
        if settings.WCS_LEGACY_CONFIG_FILE:
            config.read(settings.WCS_LEGACY_CONFIG_FILE)
        if hasattr(settings, 'WCS_EXTRA_MODULES') and settings.WCS_EXTRA_MODULES:
            if not config.has_section('extra'):
                config.add_section('extra')
            for i, extra in enumerate(settings.WCS_EXTRA_MODULES):
                config.set('extra', 'cmd_line_extra_%d' % i, extra)

        threading.Thread = TenantAwareThread
        threading._DummyThread = _DummyThread
        threading._MainThread = _MainThread
        threading._Timer = _Timer

        get_publisher_class().configure(config)
        get_publisher_class().register_tld_names = True
        get_publisher_class().init_publisher_class()

default_app_config = 'wcs.qommon.AppConfig'


if lasso:
    if not hasattr(lasso, 'SAML2_SUPPORT'):
        lasso.SAML2_SUPPORT = False
    if not hasattr(lasso, 'SAML2_NAME_IDENTIFIER_FORMAT_EMAIL'):
        lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
    if not hasattr(lasso, 'SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED'):
        lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'

    if not hasattr(lasso, 'cptrToPy') and not hasattr(lasso, 'OrigProvider'):
        lasso.OrigProvider = lasso.Provider
        # old (SWIG) bindings
        class Provider(lasso.OrigProvider):
            def __init__(self, *args):
                lasso.OrigProvider.__init__(self, *args)
                if self.this is None:
                    raise lasso.Error('Error creating Provider')
        lasso.Provider = Provider
