#!/usr/bin/env python
# -*- coding: utf-8 -*-
# $Id: __init__.py 9501 2015-11-20 07:29:24Z Eric $
#
# Copyright (c) 2015 Nuwa Information Co., Ltd, All Rights Reserved.
#
# Licensed under the Proprietary License,
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at our web site.
#
# See the License for the specific language governing permissions and
# limitations under the License.
#
# $Author: Eric $ (last)
# $Date: 2015-11-20 15:29:24 +0800 (Fri, 20 Nov 2015) $
# $Revision: 9501 $

import os
import sys
import warnings
import importlib

# Default SERVER_MODE values.
DEVELOPMENT = 1
STAGE = 2
PRODUCTION = 3

try:
    import django

    DJANGO_VERSION = (django.VERSION[0] * 100 + django.VERSION[1] * 10 +
                      django.VERSION[2])
except ImportError:
    warnings.warn("Unable to import django, many Iuno packages require it.")

def createWSGIHandler(cloudLogging=False, settingsModule=None, siteDir=None):
    """
    Create WSGIHandler for django and automatically initialize path settings.
             
    @param cloudLogging True if you want to record errors to cloud logging
                        service.               
    @param settingsModule Django settings module.
    @param siteDir Specify SITE_DIR to add to sys.path and site package.
    @return WSGIHandler.
    """       
    if ('DJANGO_SETTINGS_MODULE' not in os.environ or (settingsModule and 
            os.environ['DJANGO_SETTINGS_MODULE'] != settingsModule)):
        os.environ['DJANGO_SETTINGS_MODULE'] = settingsModule
        
    if siteDir:
        import site

        siteDirAbs = os.path.abspath(siteDir)
        site.addsitedir(siteDirAbs)

        if siteDirAbs not in sys.path:
            sys.path.insert(0, siteDirAbs)
    
    from django.core.wsgi import get_wsgi_application               
    handler = get_wsgi_application()
   
    if cloudLogging:
        from Iuno.cloud.logging.middleware import WSGIHandler as _WSGIHandler
        handler = _WSGIHandler(handler)
    
    return handler

from Iuppiter.Util import colored, cprint

def require(k, settingsDict):
    """
    Prompt require message while doing attachSettings.
    
    @param k Settings key.
    @param settingsDict Settings dict.
    """
    if k not in settingsDict:
        raise RuntimeError("'%s' %s" % (
            colored(k, 'yellow', attrs=['bold']),
            colored("must be set in settings.py.", 'white',
                    attrs=['bold'])))

def warnPackage(package):
    """
    Prompt warning message for unable import certain package
    while doing attachSettings.
    
    @param package Package name.
    """
    warnings.warn("%s '%s'" % (
        colored("Unable to import", 'yellow', attrs=['bold']),
        colored(package, 'cyan', attrs=['bold'])))

def warnWithout(k, settingsDict, defaults):
    """
    Prompt warning message for without certain key in settings
    while doing attachSettings.
    
    @param k Settings key.
    @param settingsDict Settings dict.
    @param defaults Defaults dict to provide default value.
    """
    if k not in settingsDict:
        warnings.warn("'%s' %s %s" % (
            colored(k, 'yellow', attrs=['bold']),
            colored(" not set in settings.py, it will be set as ", 'white',
                    attrs=['bold']),
            colored(defaults[k], 'cyan', attrs=['bold'])))

def warnSet(k, settingsDict, defaults):
    """
    Prompt warning message for without set certain key in settings
    while doing attachSettings.
    
    @param k Settings key.
    @param settingsDict Settings dict.
    @param defaults Defaults dict to provide default value.
    """
    if k in settingsDict:
        warnings.warn("'%s' %s %s" % (
            colored(k, 'yellow', attrs=['bold']),
            colored(" shall not be set in settings.py, the recommended "
                    "value is ", 'white', attrs=['bold']),
            colored(defaults[k], 'cyan', attrs=['bold'])))

def warnValue(k, settingsDict, defaults, value, msg=''):
    """
    Prompt warning message for user overwritten certain key in settings
    while doing attachSettings.
    
    @param k Settings key.
    @param settingsDict Settings dict.
    @param defaults Defaults dict to provide default value.
    """
    if k in settingsDict:
        warnings.warn("%s '%s' %s %s %s %s" % (
            colored("The recommended value of", 'white', attrs=['bold']),
            colored(k, 'yellow', attrs=['bold']),
            colored("is", 'white', attrs=['bold']),
            colored(value, 'cyan', attrs=['bold']),
            colored(msg if msg else ' ', 'red', attrs=['bold']),
            colored("(default value: %s)" % defaults[k], 'white',
                    attrs=['bold'])))

def update(settingsDict, defaults, config=None):
    """
    Update settings by defaults if the key is not in settings.
    
    @param settingsDict Settings dict.
    @param defaults Defaults dict to provide default value.
    @param config Reserved.
    """
    for k, v in defaults.items():
        if k not in settingsDict:
            settingsDict[k] = v
            
def checkImportPackage(packageName, settingsDict):
    """
    Check the package you want to import and 
    raise an ImportError if the package is not available.
    
    @param packageName Package name.
    @param settingsDict Settings dict.
    """
    try:
        importlib.import_module(packageName)
    except ImportError:
        warnPackage(packageName)
        upperName = packageName.upper()
        
        startWithName = upperName.split('.')[0] + '_' + \
                        upperName.split('.')[1] + '_'
                        
        for k in settingsDict:
            if k.startswith(startWithName):
                # Project use packageName settings but
                # package is not available.
                raise
            
def warnPackageDisable(packageName, settingsDict, defaults):
    """
    Show warning messages if using packages settings but 
    the package is disabled.
    
    @param packageName Package name.
    @param settingsDict Settings dict.
    @param defaults Defaults dict to provide default value.
    """
    upperName = packageName.upper()
    startWithName = upperName.split('.')[0] + '_' + \
                    upperName.split('.')[1] + '_'
    packageEnable = startWithName + 'ENABLE'
    
    if settingsDict[packageEnable]:
        
        for k in settingsDict:
            
            if k.startswith(startWithName):
                # Project use PACKAGE_settings but
                # PACKAGE_ENABLE = False
                warnValue(packageEnable, settingsDict,
                          defaults, True,
                          'because %s are set.' % k)
                break
    
def attachSettings(settingsDict):
    """
    Attach default settings to settings.py.
    It must be called in settings by passing locals().

    @param settingsDict locals() in settings.py.
    """
    settingsPath = os.path.abspath(settingsDict['__file__'])
    projectPath = os.path.dirname(settingsPath)
    projectName = os.path.split(projectPath)[-1]

    #=========================================================================
    # Server Mode.
    serverModeDefaults = {
        'DEVELOPMENT': DEVELOPMENT,
        'STAGE': STAGE,
        'PRODUCTION': PRODUCTION,
        'SERVER_MODE': (DEVELOPMENT if 'SERVER_MODE' not in os.environ else
                                       globals()[(os.environ['SERVER_MODE'])]),
    }

    # warnWithout('SERVER_MODE', settingsDict, serverModeDefaults)    
    reversedServerModes = {v: k for k, v in serverModeDefaults.items() 
                                if k != 'SERVER_MODE'}
                                
    update(settingsDict, serverModeDefaults)
    
    print colored('SERVER_MODE', 'yellow', attrs=['bold']),
    print colored(" = ", 'white', attrs=['bold']),
    print colored(reversedServerModes[settingsDict['SERVER_MODE']], 
                  'cyan', attrs=['bold'])    
    #=========================================================================

    #=========================================================================
    # Debugging.
    DEBUG = True if settingsDict['SERVER_MODE'] not in (
        settingsDict['STAGE'], settingsDict['PRODUCTION']) else False
        
    # If environment variable specified DEBUG, we use it ignore SERVER_MODE.
    DEBUG = (DEBUG if 'DEBUG' not in os.environ 
                   else {'true': True, 'false': False}[
                       os.environ['DEBUG'].lower()])

    debugDefaults = {
        'DEBUG': DEBUG,
        'TEMPLATE_DEBUG': DEBUG,
    }

    update(settingsDict, debugDefaults)
    #=========================================================================

    #=========================================================================
    # Project settings.
    projectDefaults = {
        'APP_NAME': projectName.lower(),
        'APP_VERSION': '0.1.0.0000',
    }

    warnWithout('APP_NAME', settingsDict, projectDefaults)
    warnWithout('APP_VERSION', settingsDict, projectDefaults)

    update(settingsDict, projectDefaults)

    # Backward compatible:
    # FIXME: Replace all APPLICATION_VERSION to APP_VERSION.
    if 'APPLICATION_VERSION' not in settingsDict:
        settingsDict['APPLICATION_VERSION'] = settingsDict['APP_VERSION']
    #=========================================================================

    #=========================================================================
    # Administration.
    ADMINS = (
        ('Bear', 'bear@tracedig.com'),
    )
    
    ALLOWED_HOSTS = (
        'localhost', 
        '127.0.0.1', 
        '.nuwainfo.com', 
        '.tracedig.com'
    )

    adminDefaults = {
        'ADMINS': ADMINS,
        'MANAGERS': ADMINS,
        'ALLOWED_HOSTS': ALLOWED_HOSTS,
    }
    
    require('SECRET_KEY', settingsDict)

    warnWithout('ADMINS', settingsDict, adminDefaults)

    update(settingsDict, adminDefaults)
    #=========================================================================

    #=========================================================================
    # Database.
    if 'DATABASE_NAME_PREFIX' in settingsDict:
        dbNamePrefix = settingsDict['DATABASE_NAME_PREFIX']
    else:
        if settingsDict['SERVER_MODE'] == settingsDict['DEVELOPMENT']:
            dbNamePrefix = ''
        elif settingsDict['SERVER_MODE'] == settingsDict['STAGE']:
            dbNamePrefix = ''
        else: # PRODUCTION
            if sys.platform.startswith('win'):
                dbNamePrefix = ''
            else: # Deployed production default uses user name as prefix.
                import getpass
                dbNamePrefix = '%s_' % getpass.getuser()

    if 'DATABASE_URL' in os.environ:
        import dj_database_url
        dd = dj_database_url.config(default=os.environ['DATABASE_URL'])
        
        _get = lambda k: settingsDict.get('DATABASE_%s' % k, dd[k])
       
        databaseDefaults = {
            'DATABASE_ENGINE': dd['ENGINE'],
            'DATABASE_NAME': dd['NAME'],
            'DATABASE_USER': dd['USER'],
            'DATABASE_PASSWORD': dd['PASSWORD'],
            'DATABASE_HOST': dd['HOST'],
            'DATABASE_PORT': dd['PORT'],
            'DATABASES': {
                'default': {
                    'NAME': _get('NAME'),
                    'USER': _get('USER'),
                    'PASSWORD': _get('PASSWORD'),
                    'HOST': _get('HOST'),
                    'PORT': _get('PORT'),        
                }
            },
        }
        
        if 'DATABASE_ENGINE' in settingsDict:
            databaseDefaults['DATABASES']['default']['ENGINE'] = (
                'django.db.backends.%s' % settingsDict['DATABASE_ENGINE'])
        else:
            databaseDefaults['DATABASES']['default']['ENGINE'] = dd['ENGINE']
        
    else:
        if settingsDict['SERVER_MODE'] == settingsDict['DEVELOPMENT']:
            dd = {
                'DATABASE_ENGINE': 'sqlite3',
                'DATABASE_NAME': '%s%s%s.db' % (dbNamePrefix,
                                                settingsDict['APP_NAME'],
                                                '_Debug' if DEBUG else ''),
                'DATABASE_USER': '',
                'DATABASE_PASSWORD': '',
                'DATABASE_HOST': '',
                'DATABASE_PORT': '',
            }
        else:
            dbName = '%s%s%s' % (dbNamePrefix, settingsDict['APP_NAME'],
                                 '_Debug' if DEBUG else '')
            dd = {
                'DATABASE_ENGINE': 'mysql',
                'DATABASE_NAME': dbName,
                'DATABASE_USER': dbName,
                'DATABASE_PASSWORD': '%s-RX-0' % settingsDict['APP_NAME'],
                # Support docker linked database as service_db.
                'DATABASE_HOST': ('service_db' 
                    if 'SERVICE_DB_NAME' in os.environ 
                    else 'localhost'),
                'DATABASE_PORT': '',
            }

        _get = lambda k: settingsDict.get(k, dd[k])

        databaseDefaults = {
            'DATABASES': {
                'default': {
                    'NAME': _get('DATABASE_NAME'),
                    'ENGINE': 'django.db.backends.%s' % _get('DATABASE_ENGINE'),
                    'USER': _get('DATABASE_USER'),
                    'PASSWORD': _get('DATABASE_PASSWORD'),
                    'HOST': _get('DATABASE_HOST'),
                    'PORT': _get('DATABASE_PORT'),
                },
            },
        }
        databaseDefaults.update(dd)

    update(settingsDict, databaseDefaults)
    #=========================================================================

    #=========================================================================
    # I18n.
    i18nDefaults = {
        # On Unix systems, a value of None will cause Django to use the same
        # timezone as the operating system.
        # If running in a Windows environment this must be set to the same as 
        # your system time zone.
        'TIME_ZONE': ('Asia/Taipei' if sys.platform.startswith('win')
                                    else None),
        'LANGUAGE_CODE': 'en-us',
        'USE_I18N': True,
        'LANGUAGES': (
            ('zh-tw', 'Traditional Chinese'),
            ('zh-cn', 'Simplified Chinese'),
            ('en-us', 'English'),
        ),
        'LOCALE_PATHS': (
            os.path.join(projectPath, 'locale'),
        ),
    }

    update(settingsDict, i18nDefaults)
    #=========================================================================

    #=========================================================================
    # static files.
    if settingsDict['SERVER_MODE'] == settingsDict['DEVELOPMENT']:
        staticURL = '/static/'
    elif settingsDict['SERVER_MODE'] == settingsDict['STAGE']:
        staticURL = ('http://stage.tracedig.com/static/%s/' %
                     settingsDict['APP_NAME'])
    else: # PRODUCTION
        staticURL = ('http://static.tracedig.com/static/%s/' %
                     settingsDict['APP_NAME'])
    
    if not DEBUG:   
        staticDefaults = {
            'STATIC_ROOT': os.path.join(projectPath, 'static'),
            'STATIC_URL': staticURL,
            'MEDIA_ROOT' : os.path.join(projectPath, 'media/'),
            'MEDIA_URL' : '/static/media/',
        }
    else:
        staticDefaults = {
            'STATICFILES_DIRS': (
                os.path.join(projectPath, 'static'),
            ),
            'STATIC_URL': staticURL,
            'MEDIA_ROOT' : os.path.join(projectPath, 'media/'),
            'MEDIA_URL' : '/static/media/',
        }

    update(settingsDict, staticDefaults)
    #=========================================================================

    #=========================================================================
    # Email.
    emailDefaults = {
        'DEFAULT_FROM_EMAIL': 'test@nuwainfo.com',
        'EMAIL_HOST': "mail.nuwainfo.com",
        'EMAIL_PORT': 587,
        'EMAIL_USE_TLS': True,
        'EMAIL_HOST_USER': "test@nuwainfo.com",
        'EMAIL_HOST_PASSWORD': "test25025529",
    }

    update(settingsDict, emailDefaults)
    #=========================================================================

    #=========================================================================
    # URL.
    rootUrlDefaults = {
        'ROOT_URLCONF': '%s.urls' % projectName,
    }
    
    warnWithout('ROOT_URLCONF', settingsDict, rootUrlDefaults)
    
    update(settingsDict, rootUrlDefaults)
    #=========================================================================

    #=========================================================================
    # Site.
    siteDefaults = {
        'SITE_ID': settingsDict['SERVER_MODE'],
    }

    update(settingsDict, siteDefaults)
    #=========================================================================

    #=========================================================================
    #  Django templates.
    tSettings = {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },             
    }
    
    templatesSettings = settingsDict.get('TEMPLATES', ())
    for p in templatesSettings:
        update(p, tSettings)
    
    templatesDefaults = {
        'TEMPLATES' : [
            tSettings,
        ]
    }

    update(settingsDict, templatesDefaults)
    #=========================================================================

    #=========================================================================
    # Django plugins.
    pluginsDefaults = {
        'MIDDLEWARE_CLASSES': (
            'django.contrib.sessions.middleware.SessionMiddleware',
            'django.middleware.common.CommonMiddleware',
            'django.middleware.csrf.CsrfViewMiddleware',
            'django.contrib.auth.middleware.AuthenticationMiddleware',
            'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
            'django.contrib.messages.middleware.MessageMiddleware',
            'django.middleware.clickjacking.XFrameOptionsMiddleware',
            'django.middleware.security.SecurityMiddleware',
        ),
        'INSTALLED_APPS': (
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'Iuppiter.extension',
            'Iuppiter.i18n',
            'Iuno.member',
        ),
    }

    warnWithout('INSTALLED_APPS', settingsDict, pluginsDefaults)

    update(settingsDict, pluginsDefaults)
    #=========================================================================

    #=========================================================================
    # Iuno.cloud.
    checkImportPackage('Iuno.cloud', settingsDict)
    import Iuno.cloud
    
    iunoCloudDefaults = {
        'IUNO_CLOUD_ENABLE': False,
        'IUNO_CLOUD_SERVICES': None,
        'IUNO_CLOUD_QUEUE_SETTINGS': None,
        'IUNO_CLOUD_MAIL_SETTINGS': None,
        'IUNO_CLOUD_LOGGING_SETTINGS': None,
        'IUNO_CLOUD_BROWSER_SETTINGS': None,
        'IUNO_CLOUD_LOGGING_FILE': None,
        'IUNO_CLOUD_LOGGING_LEVEL': 'DEBUG' if DEBUG else 'INFO',
    }

    update(settingsDict, iunoCloudDefaults)

    if settingsDict['IUNO_CLOUD_ENABLE']:
        attachCloudKws = {'cache': True}
        if settingsDict['IUNO_CLOUD_SERVICES'] is not None:
            attachCloudKws['services'] = settingsDict['IUNO_CLOUD_SERVICES']

        cloudServiceSettings = {}
        for k in iunoCloudDefaults.keys():
            if k.startswith('IUNO_CLOUD_') and k.endswith('_SETTINGS'):
                srv = k[len('IUNO_CLOUD_'):-len('_SETTINGS')].lower()
                if settingsDict[k] is not None:
                    cloudServiceSettings[srv] = settingsDict[k]

        if cloudServiceSettings:
            attachCloudKws['serviceSettings'] = cloudServiceSettings

        if settingsDict['IUNO_CLOUD_LOGGING_FILE'] is not None:
            attachCloudKws['loggingFile'] = \
                settingsDict['IUNO_CLOUD_LOGGING_FILE']

        if settingsDict['IUNO_CLOUD_LOGGING_LEVEL'] is not None:
            attachCloudKws['loggingLevel'] = \
                settingsDict['IUNO_CLOUD_LOGGING_LEVEL']

        Iuno.cloud.attachSettings(settingsDict, **attachCloudKws)
    else: # IUNO_CLOUD_ENABLE = False
        warnPackageDisable('Iuno.cloud', settingsDict, iunoCloudDefaults)
    #=========================================================================
    
    #=========================================================================
    # Iuno.member.
    checkImportPackage('Iuno.member', settingsDict)
    import Iuno.member
    
    iunoMemberDefaults = {
        'IUNO_MEMBER_ENABLE': False,
        'IUNO_MEMBER_FACEBOOK_LOGIN_ENABLE': False,
        'IUNO_MEMBER_GMAIL_LOGIN_ENABLE': False,
        'IUNO_MEMBER_YAHOO_LOGIN_ENABLE': False,
        'IUNO_MEMBER_MSN_LIVE_LOGIN_ENABLE': False,
    }

    update(settingsDict, iunoMemberDefaults)

    if settingsDict['IUNO_MEMBER_ENABLE']:
        Iuno.member.attachSettings(
                        settingsDict,
                        settingsDict['IUNO_MEMBER_FACEBOOK_LOGIN_ENABLE'],
                        settingsDict['IUNO_MEMBER_GMAIL_LOGIN_ENABLE'],
                        settingsDict['IUNO_MEMBER_YAHOO_LOGIN_ENABLE'],
                        settingsDict['IUNO_MEMBER_MSN_LIVE_LOGIN_ENABLE']
                    )
    else: # IUNO_MEMBER_ENABLE = False
        warnPackageDisable('Iuno.member', settingsDict, iunoMemberDefaults)
    #=========================================================================
    
    #=========================================================================
    # Patch.
    from Iuno import Patch

    Patch.fixDjango(settingsDict)
    Patch.extendAll(settingsDict)

    if settingsDict['SERVER_MODE'] == settingsDict['DEVELOPMENT']:
        Patch.attachDebugSettings(settingsDict)
    #=========================================================================
