#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    openslides.main
    ~~~~~~~~~~~~~~~

    Main script to start and set up OpenSlides.

    :copyright: 2011, 2012 by OpenSlides team, see AUTHORS.
    :license: GNU GPL, see LICENSE for more details.
"""

# for python 2.5 support
from __future__ import with_statement

import base64
import ctypes
import optparse
import os
import socket
import sys
import tempfile
import threading
import time
import webbrowser

import django.conf
from django.core.management import execute_from_command_line

CONFIG_TEMPLATE = """#!/usr/bin/env python
# -*- coding: utf-8 -*-

import openslides.main
from openslides.global_settings import *

# Use 'DEBUG = True' to get more details for server errors
# (Default for releases: 'False')
DEBUG = False
TEMPLATE_DEBUG = DEBUG

DBPATH = %(dbpath)s

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': DBPATH,
        'USER': '',
        'PASSWORD': '',
        'HOST': '',
        'PORT': '',
    }
}

# Set timezone
TIME_ZONE = 'Europe/Berlin'

# Make this unique, and don't share it with anybody.
SECRET_KEY = %(default_key)r

# Add OpenSlides plugins to this list (see example entry in comment)
INSTALLED_PLUGINS = (
#    'pluginname',
)

INSTALLED_APPS += INSTALLED_PLUGINS
"""

KEY_LENGTH = 30

# sentinel used to signal that the database ought to be stored
# relative to the portable's directory
_portable_db_path = object()


def process_options(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    parser = optparse.OptionParser(
        description="Run openslides using django's builtin webserver")
    parser.add_option("-a", "--address", help="IP Address to listen on")
    parser.add_option("-p", "--port", type="int", help="Port to listen on")
    parser.add_option(
        "--syncdb", action="store_true",
        help="Update/create database before starting the server")
    parser.add_option(
        "--reset-admin", action="store_true",
        help="Make sure the user 'admin' exists and uses 'admin' as password")
    parser.add_option(
        "-s", "--settings", help="Path to the openslides configuration.")
    parser.add_option(
        "--no-reload", action="store_true",
        help="Do not reload the development server")

    opts, args = parser.parse_args(argv)
    if args:
        sys.stderr.write("This command does not take arguments!\n\n")
        parser.print_help()
        sys.exit(1)

    return opts


def main(argv=None):
    opts = process_options(argv)
    _main(opts)


def win32_portable_main(argv=None):
    """special entry point for the win32 portable version"""

    opts = process_options(argv)

    database_path = None

    if opts.settings is None:
        portable_dir = get_portable_path()
        try:
            fd, test_file = tempfile.mkstemp(dir=portable_dir)
        except OSError:
            portable_dir_writeable = False
        else:
            portable_dir_writeable = True
            os.close(fd)
            os.unlink(test_file)

        if portable_dir_writeable:
            opts.settings = os.path.join(
                portable_dir, "openslides", "settings.py")
            database_path = _portable_db_path

    _main(opts, database_path=database_path)


def _main(opts, database_path=None):
    # Find the path to the settings
    settings_path = opts.settings
    if settings_path is None:
        settings_path = get_user_config_path('openslides', 'settings.py')

    # Create settings if necessary
    if not os.path.exists(settings_path):
        create_settings(settings_path, database_path)

    # Set the django environment to the settings
    setup_django_environment(settings_path)

    # Find url to openslides
    addr, port = detect_listen_opts(opts.address, opts.port)
    if port == 80:
        url = "http://%s" % addr
    else:
        url = "http://%s:%d" % (addr, port)

    # Create Database if necessary
    if not database_exists() or opts.syncdb:
        run_syncdb()
        set_system_url(url)
        create_or_reset_admin_user()

    # Reset Admin
    elif opts.reset_admin:
        create_or_reset_admin_user()

    # Start OpenSlides
    if opts.no_reload:
        extra_args = ['--noreload']
    else:
        extra_args = []
    start_openslides(addr, port, start_browser_url=url, extra_args=extra_args)


def create_settings(settings_path, database_path=None):
    settings_module = os.path.dirname(settings_path)

    if database_path is _portable_db_path:
        database_path = get_portable_db_path()
        dbpath_value = 'openslides.main.get_portable_db_path()'
    else:
        if database_path is None:
            database_path = get_user_data_path('openslides', 'database.sqlite')
        dbpath_value = repr(fs2unicode(database_path))

    settings_content = CONFIG_TEMPLATE % dict(
        default_key=base64.b64encode(os.urandom(KEY_LENGTH)),
        dbpath=dbpath_value)

    if not os.path.exists(settings_module):
        os.makedirs(settings_module)

    if not os.path.exists(os.path.dirname(database_path)):
        os.makedirs(os.path.dirname(database_path))

    with open(settings_path, 'w') as file:
        file.write(settings_content)


def setup_django_environment(settings_path):
    settings_file = os.path.basename(settings_path)
    settings_module_name = "".join(settings_file.split('.')[:-1])
    if '.' in settings_module_name:
        print "'.' is not an allowed character in the settings-file"
        sys.exit(1)
    settings_module_dir = os.path.dirname(settings_path)
    sys.path.append(settings_module_dir)
    os.environ[django.conf.ENVIRONMENT_VARIABLE] = '%s' % settings_module_name


def detect_listen_opts(address, port):
    if address is None:
        try:
            address = socket.gethostbyname(socket.gethostname())
        except socket.error:
            address = "127.0.0.1"

    if port is None:
        # test if we can use port 80
        s = socket.socket()
        port = 80
        try:
            s.bind((address, port))
            s.listen(-1)
        except socket.error:
            port = 8000
        finally:
            s.close()

    return address, port


def database_exists():
    """Detect if database exists"""
    # can't be imported in global scope as they already require
    # the settings module during import
    from django.db import DatabaseError
    from django.core.exceptions import ImproperlyConfigured
    from openslides.participant.models import User

    try:
        # TODO: Use another model, the User could be deactivated
        User.objects.count()
    except DatabaseError:
        return False
    except ImproperlyConfigured:
        print "Your settings file seems broken"
        sys.exit(0)
    else:
        return True


def run_syncdb():
    # now initialize the database
    argv = ["", "syncdb", "--noinput"]
    execute_from_command_line(argv)
    execute_from_command_line(["", "loaddata", "groups_de"])


def set_system_url(url):
    # can't be imported in global scope as it already requires
    # the settings module during import
    from openslides.config.models import config

    key = "participant_pdf_system_url"
    if key in config:
        return
    config[key] = url


def create_or_reset_admin_user():
    # can't be imported in global scope as it already requires
    # the settings module during import
    from openslides.participant.models import User
    try:
        admin = User.objects.get(username="admin")
        print("Password for user admin was reset to 'admin'")
    except User.DoesNotExist:
        admin = User()
        admin.username = 'admin'
        admin.last_name = 'Administrator'
        print("Created default admin user")

    admin.is_superuser = True
    admin.default_password = 'admin'
    admin.set_password(admin.default_password)
    admin.save()


def start_openslides(addr, port, start_browser_url=None, extra_args=[]):
    argv = ["", "runserver", '--noreload'] + extra_args

    argv.append("%s:%d" % (addr, port))

    if start_browser_url:
        start_browser(start_browser_url)
    execute_from_command_line(argv)


def start_browser(url):
    browser = webbrowser.get()

    def f():
        time.sleep(1)
        browser.open(url)

    t = threading.Thread(target=f)
    t.start()


def fs2unicode(s):
    if isinstance(s, unicode):
        return s
    fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding()
    return s.decode(fs_encoding)


def get_user_config_path(*args):
    if sys.platform == "win32":
        return win32_get_app_data_path(*args)

    config_home = os.environ.get(
        'XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config'))

    return os.path.join(fs2unicode(config_home), *args)


def get_user_data_path(*args):
    if sys.platform == "win32":
        return win32_get_app_data_path(*args)

    data_home = os.environ.get(
        'XDG_DATA_HOME', os.path.join(
            os.path.expanduser('~'), '.local', 'share'))

    return os.path.join(fs2unicode(data_home), *args)


def get_portable_path(*args):
    # NOTE: sys.executable will be the path to openslides.exe
    #       since it is essentially a small wrapper that embeds the
    #       python interpreter

    exename = os.path.basename(sys.executable).lower()
    if exename != "openslides.exe":
        raise Exception(
            "Cannot determine portable path when "
            "not running as portable")

    portable_dir = fs2unicode(os.path.dirname(os.path.abspath(sys.executable)))
    return os.path.join(portable_dir, *args)


def get_portable_db_path():
    return get_portable_path('openslides', 'database.sqlite')


def win32_get_app_data_path(*args):
    shell32 = ctypes.WinDLL("shell32.dll")
    SHGetFolderPath = shell32.SHGetFolderPathW
    SHGetFolderPath.argtypes = (
        ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p, ctypes.c_uint32,
        ctypes.c_wchar_p)
    SHGetFolderPath.restype = ctypes.c_uint32

    CSIDL_LOCAL_APPDATA = 0x001c
    MAX_PATH = 260

    buf = ctypes.create_unicode_buffer(MAX_PATH)
    res = SHGetFolderPath(0, CSIDL_LOCAL_APPDATA, 0, 0, buf)
    if res != 0:
        raise Exception("Could not deterime APPDATA path")

    return os.path.join(buf.value, *args)


if __name__ == "__main__":
    main()
