#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006  Donald N. Allingham
# Copyright (C) 2009       Benny Malengier
# Copyright (C) 2009-2010  Stephen George
# Copyright (C) 2010       Doug Blank <doug.blank@gmail.com>
# Copyright (C) 2011       Paul Franklin
#
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#

#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import sys
import os
import signal

import logging

LOG = logging.getLogger(".")

from subprocess import Popen, PIPE

#-------------------------------------------------------------------------
# process 'safe mode'; set up for a temp directory for user data
# actual directory paths set up in const module
if "-S" in sys.argv or "--safe" in sys.argv:
    from tempfile import TemporaryDirectory
    tempdir = TemporaryDirectory(prefix='gramps_')
    os.environ['SAFEMODE'] = tempdir.name

#-------------------------------------------------------------------------
#
# Gramps modules
#
#-------------------------------------------------------------------------
from .gen.const import APP_GRAMPS, USER_DIRLIST, HOME_DIR, ORIG_HOME_DIR
from .gen.constfunc import mac
from .version import VERSION_TUPLE
from .gen.constfunc import win, get_env_var
from .gen.config import config
from .gen.errors import HandleError

#-------------------------------------------------------------------------
#
# Instantiate Localization
#
#-------------------------------------------------------------------------

from .gen.const import GRAMPS_LOCALE as glocale
_ = glocale.translation.gettext

#-------------------------------------------------------------------------
#
# Ensure that output is encoded correctly to stdout and
# stderr. This is much less cumbersome and error-prone than
# encoding individual outputs:
#
#-------------------------------------------------------------------------

try:
    # On Darwin sys.getdefaultencoding() is correct, on Win32 it's
    # sys.stdout.enoding, and on Linux they're both right.
    if mac():
        _encoding = sys.getdefaultencoding()
    else:
        _encoding = sys.stdout.encoding
except:
    _encoding = "UTF-8"
try:
    sys.stdout = open(sys.stdout.fileno(), mode='w', encoding=_encoding,
                  buffering=1, errors='backslashreplace')
    sys.stderr = open(sys.stderr.fileno(), mode='w', encoding=_encoding,
                  buffering=1, errors='backslashreplace')
except:
    pass

#-------------------------------------------------------------------------
#
# Setup logging
#
# Ideally, this needs to be done before any Gramps modules are
# imported, so that any code that is executed as the modules are
# imported can log errors or warnings.  const and constfunc have to be
# imported before this code is executed because they are used in this
# code. That unfortunately initializes GrampsLocale, so it has its own
# logging setup during initialization.
#-------------------------------------------------------------------------
"""Setup basic logging support."""

# Setup a formatter
form = logging.Formatter(fmt="%(asctime)s.%(msecs).03d: %(levelname)s: "
                             "%(filename)s: line %(lineno)d: %(message)s",
                         datefmt='%Y-%m-%d %H:%M:%S')

# Create the log handlers
if win():
    # If running in GUI mode redirect stdout and stderr to log file
    if not sys.stdout:
        logfile = os.path.join(HOME_DIR,
            "Gramps%s%s.log") % (VERSION_TUPLE[0],
            VERSION_TUPLE[1])
        # We now carry out the first step in build_user_paths(), to make sure
        # that the user home directory is available to store the log file. When
        # build_user_paths() is called, the call is protected by a try...except
        # block, and any failure will be logged. However, if the creation of the
        # user directory fails here, there is no way to report the failure,
        # because stdout/stderr are not available, and neither is the logfile.
        if os.path.islink(HOME_DIR):
            pass  # ok
        elif not os.path.isdir(HOME_DIR):
            os.makedirs(HOME_DIR)
        sys.stdout = sys.stderr = open(logfile, "w", encoding='utf-8')
# macOS sets stderr to /dev/null when running without a terminal,
# e.g. if Gramps.app is lauched by double-clicking on it in
# finder. Write to a file instead.
if mac() and not sys.stdin.isatty():
    from tempfile import gettempdir

    log_file_name = 'gramps-' + str(os.getpid()) + '.log'
    log_file_path = os.path.join(gettempdir(), log_file_name)
    log_file_handler = logging.FileHandler(log_file_path, mode='a',
                                           encoding='utf-8')
    log_file_handler.setFormatter(form)
    log_file_handler.setLevel(logging.DEBUG)

    logger = logging.getLogger()
    logger.setLevel(logging.WARNING)
    logger.addHandler(log_file_handler)
else:
    stderrh = logging.StreamHandler(sys.stderr)
    stderrh.setFormatter(form)
    stderrh.setLevel(logging.DEBUG)

    # Setup the base level logger, this one gets
    # everything.
    l = logging.getLogger()
    l.setLevel(logging.WARNING)
    l.addHandler(stderrh)


def exc_hook(err_type, value, t_b):
    ''' put a hook on to catch any completely unhandled exceptions. '''
    if err_type == KeyboardInterrupt:
        # Ctrl-C is not a bug.
        return
    if err_type == IOError:
        # strange Windows logging error on close
        return
    if err_type == HandleError and 'not found' in value.value:
        # tell Gramps to run check & repair on next start
        config.set('behavior.runcheck', True)
        config.save()
    # Use this to show variables in each frame:
    #from gramps.gen.utils.debug import format_exception
    import traceback
    LOG.error("Unhandled exception\n" +
              "".join(traceback.format_exception(err_type, value, t_b)))

sys.excepthook = exc_hook

from .gen.mime import mime_type_is_defined


#-------------------------------------------------------------------------
#
# Minimum version check
#
#-------------------------------------------------------------------------

MIN_PYTHON_VERSION = (3, 3, 0, '', 0)
if not sys.version_info >= MIN_PYTHON_VERSION:
    logging.warning(_("Your Python version does not meet the "
             "requirements. At least python %(v1)d.%(v2)d.%(v3)d is needed to"
             " start Gramps.\n\n"
             "Gramps will terminate now.") % {
             'v1': MIN_PYTHON_VERSION[0],
             'v2': MIN_PYTHON_VERSION[1],
             'v3': MIN_PYTHON_VERSION[2]})
    sys.exit(1)

#-------------------------------------------------------------------------
#
# Gramps libraries
#
#-------------------------------------------------------------------------
try:
    signal.signal(signal.SIGCHLD, signal.SIG_DFL)
except:
    pass

args = sys.argv

def build_user_paths():
    """ check/make user-dirs on each Gramps session"""
    for path in USER_DIRLIST:
        if os.path.islink(path):
            pass  # ok
        elif not os.path.isdir(path):
            os.makedirs(path)

def show_settings():
    """
    Shows settings of all of the major components.
    """
    py_str = '%d.%d.%d' % sys.version_info[:3]
    try:
        import gi
        gi.require_version('Gtk', '3.0')
        from gi.repository import Gtk
        try:
            gtkver_str = '%d.%d.%d' % (Gtk.get_major_version(),
                        Gtk.get_minor_version(), Gtk.get_micro_version())
        except:  # any failure to 'get' the version
            gtkver_str = 'unknown version'
    except (ImportError, ValueError):
        gtkver_str = 'not found'
    # no DISPLAY is a RuntimeError in an older pygtk (e.g. 2.17 in Fedora 14)
    except RuntimeError:
        gtkver_str = 'DISPLAY not set'
    # exept TypeError: To handle back formatting on version split

    try:
        from gi.repository import GObject
        try:
            pygobjectver_str = '%d.%d.%d' % GObject.pygobject_version
        except:  # any failure to 'get' the version
            pygobjectver_str = 'unknown version'

    except ImportError:
        pygobjectver_str = 'not found'

    try:
        from gi.repository import Pango
        try:
            pangover_str = Pango.version_string()
        except:  # any failure to 'get' the version
            pangover_str = 'unknown version'

    except ImportError:
        pangover_str = 'not found'

    try:
        import cairo
        try:
            pycairover_str = '%d.%d.%d' % cairo.version_info
            cairover_str = cairo.cairo_version_string()
        except:  # any failure to 'get' the version
            pycairover_str = 'unknown version'
            cairover_str = 'unknown version'

    except ImportError:
        pycairover_str = 'not found'
        cairover_str = 'not found'

    try:
        from gi import Repository
        repository = Repository.get_default()
        if repository.enumerate_versions("OsmGpsMap"):
            import gi
            gi.require_version('OsmGpsMap', '1.0')
            from gi.repository import OsmGpsMap as osmgpsmap
            try:
                osmgpsmap_str = osmgpsmap._version
            except:  # any failure to 'get' the version
                osmgpsmap_str = 'unknown version'
        else:
            osmgpsmap_str = 'not found'

    except ImportError:
        osmgpsmap_str = 'not found'

    try:
        from gi import Repository
        repository = Repository.get_default()
        if repository.enumerate_versions("GExiv2"):
            import gi
            gi.require_version('GExiv2', '0.10')
            from gi.repository import GExiv2
            try:
                gexiv2_str = GExiv2._version
            except:  # any failure to 'get' the version
                gexiv2_str = 'unknown version'
        else:
            gexiv2_str = 'not found'

    except ImportError:
        gexiv2_str = 'not found'
    except ValueError:
        gexiv2_str = 'not new enough'

    try:
        import PyICU
        try:
            pyicu_str = PyICU.VERSION
            icu_str = PyICU.ICU_VERSION
        except:  # any failure to 'get' the version
            pyicu_str = 'unknown version'
            icu_str = 'unknown version'

    except ImportError:
        pyicu_str = 'not found'
        icu_str = 'not found'

    try:
        import bsddb3 as bsddb
        bsddb_str = bsddb.__version__
        bsddb_db_str = str(bsddb.db.version()).replace(', ', '.')\
                                        .replace('(', '').replace(')', '')
        bsddb_location_str = bsddb.__file__
    except:
        try:
            import berkeleydb as bsddb
            bsddb_str = bsddb.__version__
            bsddb_db_str = str(bsddb.db.version()).replace(', ', '.')\
                                                  .replace('(', '').replace(')', '')
            bsddb_location_str = bsddb.__file__
        except:
            bsddb_str = 'not found'
            bsddb_db_str = 'not found'
            bsddb_location_str = 'not found'

    try:
        import sqlite3
        sqlite3_py_version_str = sqlite3.version
        sqlite3_version_str = sqlite3.sqlite_version
        sqlite3_location_str = sqlite3.__file__
    except:
        sqlite3_version_str = 'not found'
        sqlite3_py_version_str = 'not found'
        sqlite3_location_str = 'not found'

    try:
        from .gen.const import VERSION
        gramps_str = VERSION
    except:
        gramps_str = 'not found'

    if hasattr(os, "uname"):
        kernel = os.uname()[2]
    else:
        kernel = None

    lang_str = get_env_var('LANG', 'not set')
    language_str = get_env_var('LANGUAGE', 'not set')
    grampsi18n_str = get_env_var('GRAMPSI18N', 'not set')
    grampshome_str = get_env_var('GRAMPSHOME', 'not set')
    grampsdir_str = get_env_var('GRAMPSDIR', 'not set')
    gramps_resources_str = get_env_var('GRAMPS_RESOURCES', 'not set')

    try:
        dotversion_str = Popen(['dot', '-V'], stderr=PIPE).communicate(input=None)[1]
        if isinstance(dotversion_str, bytes) and sys.stdin.encoding:
            dotversion_str = dotversion_str.decode(sys.stdin.encoding)
        if dotversion_str:
            dotversion_str = dotversion_str.replace('\n', '')[23:27]
    except:
        dotversion_str = 'Graphviz not in system PATH'

    try:
        if win():
            try:
                gsversion_str = Popen(['gswin32c', '--version'], stdout=PIPE).communicate(input=None)[0]
            except:
                gsversion_str = Popen(['gswin64c', '--version'], stdout=PIPE).communicate(input=None)[0]
        else:
            gsversion_str = Popen(['gs', '--version'], stdout=PIPE).communicate(input=None)[0]
        if isinstance(gsversion_str, bytes) and sys.stdin.encoding:
            gsversion_str = gsversion_str.decode(sys.stdin.encoding)
        if gsversion_str:
            gsversion_str = gsversion_str.replace('\n', '')
    except:
        gsversion_str = 'Ghostscript not in system PATH'

    os_path = get_env_var('PATH', 'not set')
    os_path = os_path.split(os.pathsep)

    print("Gramps Settings:")
    print("----------------")
    print(' python    : %s' % py_str)
    print(' gramps    : %s' % gramps_str)
    print(' gtk++     : %s' % gtkver_str)
    print(' pygobject : %s' % pygobjectver_str)
    print(' pango     : %s' % pangover_str)
    print(' cairo     : %s' % cairover_str)
    print(' pycairo   : %s' % pycairover_str)
    print(' osmgpsmap : %s' % osmgpsmap_str)
    print(' GExiv2    : %s' % gexiv2_str)
    print(' ICU       : %s' % icu_str)
    print(' PyICU     : %s' % pyicu_str)
    print(' o.s.      : %s' % sys.platform)
    if kernel:
        print(' kernel    : %s' % kernel)
    print('')
    print("Environment settings:")
    print("---------------------")
    print(' LANG      : %s' % lang_str)
    print(' LANGUAGE  : %s' % language_str)
    print(' GRAMPSI18N: %s' % grampsi18n_str)
    print(' GRAMPSHOME: %s' % grampshome_str)
    print(' GRAMPSDIR : %s' % grampsdir_str)
    if __debug__:
        print(' GRAMPS_RESOURCES : %s' % gramps_resources_str)
    print(' PYTHONPATH:')
    for folder in sys.path:
        print("   ", folder)
    print('')
    print("Non-python dependencies:")
    print("------------------------")
    print(' Graphviz  : %s' % dotversion_str)
    print(' Ghostscr. : %s' % gsversion_str)
    print('')
    print("System PATH env variable:")
    print("-------------------------")
    for folder in os_path:
        print("    ", folder)
    print('')
    print("Databases:")
    print("-------------------------")
    print(' bsddb     :')
    print('     version     : %s' % bsddb_str)
    print('     db version  : %s' % bsddb_db_str)
    print('     location    : %s' % bsddb_location_str)
    print(' sqlite3   :')
    print('     version     : %s' % sqlite3_version_str)
    print('     py version  : %s' % sqlite3_py_version_str)
    print('     location    : %s' % sqlite3_location_str)
    print('')

def run():
    error = []

    try:
        build_user_paths()
    except OSError as msg:
        error += [(_("Configuration error:"), str(msg))]
        return error
    except msg:
        LOG.error("Error reading configuration.", exc_info=True)
        return [(_("Error reading configuration"), str(msg))]

    if not mime_type_is_defined(APP_GRAMPS):
        error += [(_("Configuration error:"),
                    _("A definition for the MIME-type %s could not "
                      "be found \n\n Possibly the installation of Gramps "
                      "was incomplete. Make sure the MIME-types "
                      "of Gramps are properly installed.")
                    % APP_GRAMPS)]

    # we start with parsing the arguments to determine if we have a cli or a
    # gui session

    if "-v" in sys.argv or "--version" in sys.argv:
        show_settings()
        return error

    from .cli.argparser import ArgParser
    argv_copy = sys.argv[:]
    argpars = ArgParser(argv_copy)

    # if in safe mode we should point the db dir back to the original dir.
    # It is ok to import config here, 'Defaults' command had its chance...
    if 'SAFEMODE' in os.environ:
        config.set('database.path', os.path.join(ORIG_HOME_DIR, 'grampsdb'))

    # On windows the fontconfig handler is a better choice
    if(win() and ('PANGOCAIRO_BACKEND' not in os.environ)):
        os.environ['PANGOCAIRO_BACKEND'] = "fontconfig"

    # Calls to LOG must be after setup_logging() and ArgParser()
    LOG = logging.getLogger(".locale")
    LOG.debug("Encoding: %s", glocale.encoding)
    LOG.debug("Translating Gramps to %s", glocale.language[0])
    LOG.debug("Collation Locale: %s", glocale.collation)
    LOG.debug("Date/Time Locale: %s", glocale.calendar)
    LOG.debug("Currency Locale: %s", glocale.currency)
    LOG.debug("Number-format Locale: %s", glocale.numeric)

    if 'LANG' in os.environ:
        LOG.debug('Using LANG: %s' %
                         get_env_var('LANG'))
    else:
        LOG.debug('environment: LANG is not defined')
    if 'LANGUAGE' in os.environ:
        LOG.debug('Using LANGUAGE: %s' %
                         get_env_var('LANGUAGE'))
    else:
        LOG.debug('environment: LANGUAGE is not defined')

    if argpars.need_gui():
        LOG.debug("A GUI is needed, set it up")
        try:
            from .gui.grampsgui import startgramps
            # no DISPLAY is a RuntimeError in an older pygtk (e.g. F14's 2.17)
        except RuntimeError as msg:
            error += [(_("Configuration error:"), str(msg))]
            return error
        startgramps(error, argpars)
    else:
        # CLI use of Gramps
        argpars.print_help()
        argpars.print_usage()
        from .cli.grampscli import startcli
        startcli(error, argpars)

def main():
    if 'GRAMPS_RESOURCES' not in os.environ:
        resource_path, filename = os.path.split(os.path.abspath(__file__))
        resource_path, dirname = os.path.split(resource_path)
        os.environ['GRAMPS_RESOURCES'] = resource_path
    errors = run()
    if errors and isinstance(errors, list):
        for error in errors:
            logging.warning(error[0] + error[1])

if __name__ == '__main__':
    main()
