#
##
##  This file is part of pyFormex 1.0.5  (Sat Feb 16 10:40:32 CET 2019)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: http://pyformex.org
##  Project page:  http://savannah.nongnu.org/projects/pyformex/
##  Copyright 2004-2018 (C) Benedict Verhegghe (benedict.verhegghe@ugent.be)
##  Distributed under the GNU General Public License version 3 or later.
##
##  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 3 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/.
##

# This is the only pyFormex module that is imported by the main script,
# so this is the place to put startup code
"""pyFormex main module

This module contains the main function of pyFormex, which is run by the
startup script.
"""
from __future__ import absolute_import, division, print_function

import sys, os

import pyformex as pf
from pyformex.config import Config
from pyformex import utils
from pyformex import software

###########################  main  ################################

def filterWarnings():
    pf.debug("Current warning filters: %s" % pf.cfg['warnings/filters'], pf.DEBUG.WARNING)
    try:
        for w in pf.cfg['warnings/filters']:
            utils.filterWarning(*w)
    except:
        pf.debug("Error while processing warning filters: %s" % pf.cfg['warnings/filters'], pf.DEBUG.WARNING)


def refLookup(key):
    """Lookup a key in the reference configuration."""
    try:
        return pf.refcfg[key]
    except:
        pf.debug("!There is no key '%s' in the reference config!"%key, pf.DEBUG.CONFIG)
        return None


def prefLookup(key):
    """Lookup a key in the user's preferences configuration."""
    return pf.prefcfg[key]


def printcfg(key):
    try:
        print("!! refcfg[%s] = %s" % (key, pf.refcfg[key]))
    except KeyError:
        pass
    print("!! cfg[%s] = %s" % (key, pf.cfg[key]))


def remove_pyFormex(pyformexdir, executable):
    """Remove the pyFormex installation."""
    if not executable.endswith('/pyformex'):
        print("The --remove option can only be executed from the pyformex command. Try: pyformex --remove.")
        return

    if pf.installtype == 'D':
        print("It looks like this version of pyFormex was installed from a distribution package. You should use your distribution's package tools to remove the pyFormex installation.")
        return

    if pf.installtype in 'SG':
        print("It looks like you are running pyFormex directly from a source tree at %s. I will not remove it. If you have enough privileges, you can just remove the whole source tree from the file system." % pyformexdir)
        return

    # If we get here, this is probably an install from tarball release

    # Remove the installation
    import glob
    #print("pyformexdir = %s" % pyformexdir)
    prefixdir = os.path.commonprefix(['/usr/local', pyformexdir])
    #print(pyformexdir, prefixdir)
    bindir = os.path.join(prefixdir,'bin')
    egginfo = "%s-%s*.egg-info" % (pyformexdir, pf.__version__.replace('-a', '_a'))
    #print(egginfo)
    egginfo = glob.glob(egginfo)
    #print("egginfo = %s" % egginfo)
    script = os.path.join(bindir, 'pyformex')
    scripts = [ script ] + glob.glob(script+'-*')
    gtsexe = ['gtscoarsen', 'gtsinside', 'gtsrefine', 'gtsset', 'gtssmooth' ]
    scripts += [ os.path.join(bindir, g) for g in gtsexe ]
    scripts = [ p for p in scripts if os.path.exists(p) ]
    datadir = os.path.join(prefixdir, 'share')
    #print("datadir = %s" % datadir)
    data = utils.prefixFiles(datadir+'/man/man1', [
        'pyformex.1', 'pyformex-dxfparser.1', 'pyformex-postabq.1',
        'gtscoarsen.1', 'gtsinside.1', 'gtsrefine.1', 'gtsset.1',
        'gtssmooth.1'])
    data += utils.prefixFiles(datadir, [
        'applications/pyformex.desktop',
        'pixmaps/pyformex-64x64.png',
        'pixmaps/pyformex.xpm',
        ])
    data = [ p for p in data if os.path.exists(p) ]
    other_files = scripts + egginfo + data
    #print(other_files)
    print("""
BEWARE!
This procedure will remove:
- the complete pyFormex installation in %s,
- the pyFormex executable(s) (%s) in %s,
- some pyFormex data files (%s) in %s.
You should only use this on a pyFormex installed from a source .tar.gz release
with the command 'python setup.py install'.
You need proper permissions to actually delete the files.
After succesful removal, you will not be able to run this pyFormex again,
unless you re-install it.
""" % (
    pyformexdir,
    ', '.join([ os.path.basename(p) for p in scripts ]), bindir,
    ', '.join([ os.path.basename(p) for p in data ]), datadir
    ))

    s = pf.userInput("Are you sure you want to remove pyFormex? yes/NO: ")
    if s == 'yes':
        print("Removing pyformex tree: %s" % pyformexdir)
        print("Removing other files: %s" % other_files)
        utils.removeTree(pyformexdir)
        for f in other_files:
            if os.path.exists(f):
                print("Removing %s" % f)
                try:
                    os.remove(f)
                except:
                    print("Could not remove %s" % f)
            else:
                print("No such file: %s" % f)

        print("\nBye, bye! I won't be back until you reinstall me!")
    elif s.startswith('y') or s.startswith('Y'):
        print("You need to type exactly 'yes' to remove me.")
    else:
        print("Thanks for letting me stay this time.")
    sys.exit()


def savePreferences():
    """Save the preferences.

    The name of the preferences file is determined at startup from
    the configuration files, and saved in ``pyformex.preffile``.
    If a local preferences file was read, it will be saved there.
    Otherwise, it will be saved as the user preferences, possibly
    creating that file.
    If ``pyformex.preffile`` is None, preferences are not saved.
    """
    if pf.preffile is None:
        return

    # Create the user conf dir
    prefdir = os.path.dirname(pf.preffile)
    if not os.path.exists(prefdir):
        try:
            os.makedirs(prefdir)
        except:
            print("The path where your user preferences should be stored can not be created!\nPreferences are not saved!")
            return


    # Cleanup up the prefcfg
    del pf.prefcfg['__ref__']

    # Currently erroroneously processed, therefore not saved
    del pf.prefcfg['render']['light0']
    del pf.prefcfg['render']['light1']
    del pf.prefcfg['render']['light2']
    del pf.prefcfg['render']['light3']

    pf.debug("="*60, pf.DEBUG.CONFIG)
    pf.debug("!!!Saving config:\n%s" % pf.prefcfg, pf.DEBUG.CONFIG)

    try:
        pf.prefcfg.write(pf.preffile)
        res = "Saved"
    except:
        res = "Could not save"
    pf.debug("%s preferences to file %s" % (res, pf.preffile), pf.DEBUG.CONFIG)
    return res=="Saved"


def apply_config_changes(cfg):
    """Apply incompatible changes in the configuration

    cfg is the user configuration that is to be saved.
    """
    # Safety checks
    if not isinstance(cfg['warnings/filters'], list):
        cfg['warnings/filters'] = []

    # Adhoc changes
    if isinstance(cfg['gui/dynazoom'], str):
        cfg['gui/dynazoom'] = [ cfg['gui/dynazoom'], '' ]

    for i in range(8):
        t = "render/light%s"%i
        try:
            cfg[t] = dict(cfg[t])
        except:
            pass

    for d in [ 'scriptdirs', 'appdirs' ]:
        if d in cfg:
            scriptdirs = []
            for i in cfg[d]:
                if i[1] == '' or os.path.isdir(i[1]):
                    scriptdirs.append(tuple(i))
                elif i[0] == '' or os.path.isdir(i[0]):
                    scriptdirs.append((i[1], i[0]))
            cfg[d] = scriptdirs

    # Rename settings
    for old, new in [
        ('history', 'gui/scripthistory'),
        ('gui/history', 'gui/scripthistory'),
        ('raiseapploadexc', 'showapploaderrors'),
        ('webgl/xtkscript', 'webgl/script'),
        ]:
        if old in cfg:
            if new not in cfg:
                cfg[new] = cfg[old]
            del cfg[old]

    # Delete settings
    for key in [
        'input/timeout', 'filterwarnings',
        'render/ambient', 'render/diffuse', 'render/specular', 'render/emission',
        'render/material', 'canvas/propcolors', 'Save changes', 'canvas/bgmode',
        'canvas/bgcolor2', '_save_',
        ]:
        if key in cfg:
            print("DELETING CONFIG VARIABLE %s" % key)
            del cfg[key]


def run_pytest(modules):
    """Run the pytests for the specified pyFormex modules.

    Parameters:

    - `modules`: a list of pyFormex modules in Python notation,
      relative to the pyFormex package. If an empty list is supplied,
      all available pytests will be run.

    Test modules are stored under the path `pf.cfg['testdir']`, with the
    same hierarchy as the pyFormex source modules, and are named
    `test_MODULE.py`, where MODULE is the corresponding source module.

    """
    try:
        import pytest
    except:
        print("Can not import pytest")
        pass
    testpath = pf.cfg['testdir']
    print("TESTPATH={}".format(testpath))
    args = [ '--maxfail', '10' ]
    if not modules:
        pytest.main(args+[testpath])
    else:
        print("Running pytests for modules %s" % modules)
        for m in modules:
            path = m.split('.')
            path[-1] = 'test_' + path[-1] + '.py'
            path.insert(0,testpath)
            path = '/'.join(path)
            if os.path.exists(path):
                pytest.main(args+[path])
            else:
                print("No such test module: %s" % path)


def list_modules(pkgs=['all']):
    """Return the list of pure Python modules in a pyFormex subpackage.

    Parameters:

    - `pkgs`: a list of pyFormex subpackage names. The subpackage name is a
      subdirectory of the main pyformex package directory.
      Two special package names are recognized:

      - 'core': returns the modules in the top level pyFormex package
      - 'all': returns all pyFormex modules

      An empty list is interpreted as ['all'].
    """
    modules = []
    if pkgs==[]:
        pkgs = ['all']
    for subpkg in pkgs:
        modules.extend(utils.moduleList(subpkg))
    return modules


def doctest_module(module):
    """Run the doctests in a module's docstrings.

    module is a pyFormex module dotted path. The leading 'pyformex.'
    may be omitted.
    """
    import doctest
    import warnings
    # Note that a non-empty fromlist is needed to make the
    # __import__ function always return the imported module
    # even if a dotted path is specified
    if not module.startswith('pyformex.'):
        module = 'pyformex.' + module
    mod = __import__(module, fromlist=[None])
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        return doctest.testmod(mod,optionflags=doctest.NORMALIZE_WHITESPACE)


def run_doctest(modules):
    """Run the doctests for the specified pyFormex modules.

    Doctests are tests embedded in the docstrings of the Python source.

    Parameters:

    - `modules`: a list of pyFormex modules in Python notation,
      relative to the pyFormex package. If an empty list is supplied,
      all doctests in all pyFormex modules will be run.

    To allow consistent output of floats independent of machine precision,
    numpy's floating point print precision is set to two decimals.
    """
    import numpy
    if utils.checkVersion('numpy','1.14') < 0:
        # does not have the legacy argument
        numpy.set_printoptions(precision=2,suppress=True)
    else:
        numpy.set_printoptions(precision=2,suppress=True,legacy='1.13')
    if not modules:
        modules = ['all']
    for mod in modules:
        if mod in ['core','all'] or mod.endswith('.'):
            todo = utils.moduleList(mod.replace('.',''))
        else:
            todo = [ mod ]
        for m in todo:
            try:
                result = doctest_module(m)
            except Exception as e:
                result = "FAIL"
                pf.debug(e, pf.DEBUG.DOCTEST)

            print("Module {}: {}".format(m,result))


def run_docmodule(module):
    """Print autogenerated documentation for the module.

    module is a pyFormex module dotted path. The leading pyformex.
    may be omitted.
    """
    from . import py2rst
    return py2rst.do_module(module)


def migrateUserConfig():
    """Migrate the user preferences to $HOME/.config

    In older pyFormex versions, the user preferences were stored in
    a directory $HOME/.pyformex/pyformexrc, while in newer versions
    they are stored in $HOME/.config/pyformex/pyformex.conf.
    Running this function will migrate an existing old configuration
    to the new location, if the latter does not exist.
    """
    if not os.path.exists(pf.cfg['userprefs']):
        # Check old place
        olduserprefs = os.path.join(pf.cfg['homedir'], '.pyformex', 'pyformexrc')
        if os.path.exists(olduserprefs):
            print("Migrating your user preferences\n  from %s\n  to %s" % (olduserprefs, pf.cfg['userprefs']))
            # We move the user config from HOME/.pyformex/.pyformexrc
            # to HOME/.config/pyformex/pyformex.conf
            # The directory change is because of changing standards in Linux
            # The filename change is to no clash with the defaults file
            # (which is ./pyformex/pyformexrc) when being executed from
            # the pyformex directory.
            # We link back the new config location to the old one, so that
            # versions < 0.9.1 can still pick up the user config.
            try:
                import shutil
                olddir = os.path.dirname(olduserprefs)
                newdir = pf.cfg['userconfdir']
                # Move old user conf file to new and link back
                oldfile = os.path.join(olddir, 'pyformexrc')
                newfile = os.path.join(olddir, 'pyformex.conf')
                print("Moving %s to %s" % (oldfile, newfile))
                shutil.move(oldfile, newfile)
                newfile = 'pyformex.conf'
                print("Symlinking %s to %s" % (newfile, oldfile))
                os.symlink(newfile, oldfile)
                # Move old config dir to new and link back to old
                print("Moving %s to %s" % (olddir, newdir))
                shutil.move(olddir, newdir)
                print("Symlinking %s to %s" % (newdir, olddir))
                os.symlink(newdir, olddir)

            except:
                raise RuntimeError("Error while trying to migrate your user configuration\nTry moving the config files yourself.\nYou may also remove the config directories %s\n and %s alltogether\n to get a fresh start with default config." % (olddir, newdir))



def loadUserConfig():
    """Load the pyFormex configuration

    Note: this function can be called to create a proper configuration
    when pyformex is imported in Python and not started by the
    pyFormex command.
    """
    # Set the config files
    if pf.options.nodefaultconfig:
        sysprefs = []
        userprefs = []
    else:
        sysprefs = [ pf.cfg['siteprefs'] ]
        userprefs = [ pf.cfg['userprefs'] ]
        if os.path.exists(pf.cfg['localprefs']):
            userprefs.append(pf.cfg['localprefs'])

    sysprefs = list(filter(os.path.exists, sysprefs))
    userprefs = list(filter(os.path.exists, userprefs))

    if pf.options.config:
        userprefs.append(utils.tildeExpand(pf.options.config))

    if len(userprefs) == 0:
        # We should always have a place to store the user preferences
        userprefs = [ pf.cfg['userprefs'] ]

    # Use last one to save preferences
    pf.debug("System Preference Files: %s" % sysprefs, pf.DEBUG.CONFIG)
    pf.debug("User Preference Files: %s" % userprefs, pf.DEBUG.CONFIG)
    pf.preffile = os.path.abspath(userprefs.pop())

    # Read sysprefs as reference
    for f in sysprefs:
        pf.debug("Reading config file %s" % f, pf.DEBUG.CONFIG)
        pf.cfg.read(f)

    # Set this as reference config
    pf.refcfg = pf.cfg
    pf.debug("="*60, pf.DEBUG.CONFIG)
    pf.debug("RefConfig: %s" % pf.refcfg, pf.DEBUG.CONFIG)
    pf.cfg = Config(default=refLookup)

    # Read userprefs as reference
    for f in userprefs:
        if os.path.exists(f):
            pf.debug("Reading config file %s" % f, pf.DEBUG.CONFIG)
            pf.cfg.read(f)
        else:
            pf.debug("Skip non-existing config file %s" % f, pf.DEBUG.CONFIG)
    if os.path.exists(pf.preffile):
        pf.debug("Reading config file %s" % pf.preffile, pf.DEBUG.CONFIG)
        pf.cfg.read(pf.preffile)
    else:
        # Create the config file
        pf.debug("Creating config file %s" % pf.preffile, pf.DEBUG.CONFIG)
        try:
            basedir = os.path.dirname(pf.preffile)
            if not os.path.exists(basedir):
                os.makedirs(basedir)
            open(pf.preffile,'a').close()
        except:
            pf.startup_warnings += "Could not create the user configuration file '%s'.\nUser preferences will not be saved.\n" % pf.preffile
            pf.preffile = None

    # Set this as preferences config
    pf.prefcfg = pf.cfg
    pf.debug("="*60, pf.DEBUG.CONFIG)
    pf.debug("Config: %s" % pf.prefcfg, pf.DEBUG.CONFIG)
    pf.cfg = Config(default=prefLookup)

    # Fix incompatible changes in configuration
    apply_config_changes(pf.prefcfg)


def parseArguments(args):
    """Process command line arguments

    Parameters:

    - args: a list of command line arguments.

    The arguments of the pyFormex command line can be splitted in
    options and remaining arguments. This function will split the
    options from the other arguments and store them in the variable
    pf.options for access throughout pyFormex. The remaining arguments
    are stored in pf.options.args
    """
    import argparse
    parser = argparse.ArgumentParser(
        prog = pf.__prog__,
#        usage = "%(prog)s [<options>] [ [ scriptname [scriptargs] ] ...]",
        description = pf.Description,
        epilog = "More info on http://pyformex.org",
#        formatter = optparse.TitledHelpFormatter(),
        )
    MO = parser.add_argument # create a shorthand notation
    MO("--version",
       action='version',version = pf.fullVersion())
    MO("--gui",
       action="store_true", dest="gui", default=None,
       help="Start the GUI (this is the default when no scriptname argument is given)",
       )
    MO("--nogui",
       action="store_false", dest="gui", default=None,
       help="Do not start the GUI (this is the default when a scriptname argument is given)",
       )
    MO("--nocanvas",
       action="store_false", dest="canvas", default=True,
       help="Do not add an OpenGL canvas to the GUI (this is for development purposes only!)",
       )
    MO("--interactive",
       action="store_true", dest="interactive", default=False,
       help="Go into interactive mode after processing the command line parameters. This is implied by the --gui option.",
       )
    MO("--uselib", action="store_true", dest="uselib", default=None,
       help="Use the pyFormex C lib if available. This is the default.",
       )
    MO("--nouselib",
       action="store_false", dest="uselib", default=None,
       help="Do not use the pyFormex C-lib.",
       )
    MO("--config",
       action="store", dest="config", default=None,
       help="Use file CONFIG for settings. This file is loaded in addition to the normal configuration files and overwrites their settings. Any changes will be saved to this file.",
       )
    MO("--nodefaultconfig",
       action="store_true", dest="nodefaultconfig", default=False,
       help="Skip the default site and user config files. This option can only be used in conjunction with the --config option.",
       )
    MO("--redirect",
       action="store_true", dest="redirect", default=None,
       help="Redirect standard output to the message board (ignored with --nogui)",
       )
    MO("--noredirect",
       action="store_false", dest="redirect",
       help="Do not redirect standard output to the message board.",
       )
    MO("--debug",
       action="store", dest="debug", default='',
       help="Display debugging information to sys.stdout. The value is a comma-separated list of (case-insensitive) strings corresponding with the attributes of the DebugLevels class. The individual values are OR-ed together to produce a final debug value. The special value 'all' can be used to switch on all debug info.",
       )
    MO("--debuglevel",
       action="store", dest="debuglevel", type=int, default=0,
       help="Display debugging info to sys.stdout. The value is an int with the bits of the requested debug levels set. A value of -1 switches on all debug info. If this option is used, it overrides the --debug option.",
       )
    MO("--mesa",
       action="store_true", dest="mesa", default=False,
       help="Force the use of software 3D rendering through the mesa libs. The default is to use hardware accelerated rendering whenever possible. This flag can be useful when running pyFormex remotely on another host. The hardware accelerated version will not work over remote X.",
       )
    MO("--dri",
       action="store_true", dest="dri", default=None,
       help="Use Direct Rendering Infrastructure. By default, direct rendering will be used if available.",
       )
    MO("--nodri",
       action="store_false", dest="dri", default=None,
       help="Do not use the Direct Rendering Infrastructure. This may be used to turn off the direc rendering, e.g. to allow better capturing of images and movies.",
       )
    MO("--opengl",
       action="store", dest="opengl", default='2.0',
       help="Force the use of a specific OpenGL version. The version should be specified as a string 'a.b'. The default is 2.0",
       )
    MO("--shader",
       action="store", dest="shader", default='',
       help="Force the use of an alternate GPU shader for the OpenGL rendering. If the default selected shader does not work well for your hardware, you can use this option to try one of the alternate shaders. See 'pyformex --detect' for a list of the available shaders.",
       )
    MO("--vtk",
       action="store", dest="vtk", default='default',
       help="Specify which version of python-vtk to use in vtk_itf plugin. The value can be one of: 'standard', 'light', or 'default'. 'standard' is the version as distributed from python-vtk. 'light' is the trimmed vtk6 version as distributed with pyFormex.",
       )
    MO("--newviewports",
       action="store_true", dest="newviewports", default=False,
       help="Use the new multiple viewport canvas implementation. This is an experimental feature only intended for developers.",
       )
    MO("--testcamera",
       action="store_true", dest="testcamera", default=False,
       help="Print camera settings whenever they change.",
       )
    MO("--memtrack",
       action="store_true", dest="memtrack", default=False,
       help="Track memory for leaks. This is only for developers.",
       )
    MO("--fastnurbs",
       action="store_true", dest="fastnurbs", default=False,
       help="Test C library nurbs drawing: only for developers!",
       )
    MO("--bindings",
       action="store", dest="bindings", default=None,
       help="Override the configuration setting for the Qt bindings to be used. Available bindings are 'pyside', 'pyside2', 'pyqt4', 'pyqt5'. A value 'any' may be given to let pyFormex find out which are available and use one of these.",
       )
    MO("--unicode",
       action="store_true", dest="unicode", default=False,
       help="Allow unicode filenames. Beware: this is experimental!",
       )
    MO("--experimental",
       action="store_true", dest="experimental", default=False,
       help="Allow the pyformex/experimental modules to be loaded. Beware: this should only be used if you know what you are doing!",
       )
    MO("--listfiles",
       action="store_true", dest="listfiles", default=False,
       help="List the pyFormex Python source files and exit.",
       )
    MO("--listmodules",
       action="store", dest="listmodules", default=None, metavar='PKG', nargs='*',
       help="List the Python modules in the specified pyFormex subpackage and exit. Specify 'core' to just list the modules in the pyFormex top level. Specify 'all' to list all modules. The default is to list the modules in core, lib, plugins, gui, opengl.",
       )
    MO("--search",
       action="store_true", dest="search", default=False,
       help="Search the pyformex source for a specified pattern and exit. This can optionally be followed by -- followed by options for the grep command and/or '-a' to search all files in the extended search path. The final argument is the pattern to search. '-e' before the pattern will interprete this as an extended regular expression. '-l' option only lists the names of the matching files.",
       )
    MO("--remove",
       action="store_true", dest="remove", default=False,
       help="Remove the pyFormex installation and exit. This option only works when pyFormex was installed from a tarball release using the supplied install procedure. If you install from a distribution package (e.g. Debian), you should use your distribution's package tools to remove pyFormex. If you run pyFormex directly from SVN sources, you should just remove the whole checked out source tree.",
       )
    MO("--whereami",
       action="store_true", dest="whereami", default=False,
       help="Show where the pyformex package is installed and exit.",
       )
    MO("--detect",
       action="store_true", dest="detect", default=False,
       help="Show detected helper software and exit.",
       )
    MO("--doctest",
       action="store", dest="doctest", default=None, metavar='MODULE', nargs='*',
       help="Run the docstring tests for the specified pyFormex modules and exit. MODULE name is specified in Python syntax, relative to pyformex package (e.g. coords, plugins.curve).",
       )
    MO("--pytest",
       action="store", dest="pytest", default=None, metavar='MODULE', nargs='*',
       help="Run the pytest tests for the specified pyFormex modules and exit. MODULE name is specified in Python syntax, relative to pyformex package (e.g. coords, plugins.curve).",
       )
    MO("--docmodule",
       action="store", dest="docmodule", default=None, metavar='MODULE', nargs='*',
       help="Print the autogenerated documentation for module MODULE and exit. This is mostly useful during the generation of the pyFormex reference manual, as the produced result still needs to be run through the Sphinx documentation generator. MODULE is the name of a pyFormex module (Python syntax).",
       )
    MO('-c', "--script",
       action="store", dest="script", default=None, metavar='SCRIPT',
       help="A pyFormex script to be executed at startup. It is executed before any specified script files. This is mostly used in --nogui mode, when the script to execute is very short.",
       )
    MO("args",
       action="store",  nargs='*',  metavar='FILE',
       help="pyFormex script files to be executed on startup. The files should have a .py extension. Their contents will be executed as a pyFormex script. While mostly used with the --nogui option, this will also work in GUI mode.",
       )

    pf.options = parser.parse_args(args)
    # TODO: maybe store pf.parser instead ??
    pf.print_help = parser.print_help

    # Set debug level
    if pf.options.debug and not pf.options.debuglevel:
        pf.options.debuglevel = pf.debugLevel(pf.options.debug.split(','))

    # Check for invalid options
    if pf.options.nodefaultconfig and not pf.options.config:
        print("\nInvalid options: --nodefaultconfig but no --config option\nDo pyformex --help for help on options.\n")
        sys.exit()

    pf.debug("Options: %s" % pf.options, pf.DEBUG.ALL)
    #pf.debug("Other arguments: %s" % pf.options.args, pf.DEBUG.ALL)


def showWhereIAm():
    """Show where pyFormex is installed"""
    print(pf.fullVersion())
    print("pyFormex executable: %s" % pf.executable)
    print("pyFormex installation (%s): %s " % (pf.installtype,pf.pyformexdir))
    print("Python sys.path: %s" % sys.path)


def processReportOptions():
    """Process the reporting options

    Returns True if at least one reporting option was found,
    otherwise False.
    """
    ret = False

    if pf.options.docmodule is not None:
        pf.sphinx = True # Flag to pyformex_gts
        for a in pf.options.docmodule:
            run_docmodule(a)
        # This is an all_exclusive option !!
        # So we immediately return
        return True

    if pf.options.experimental:
        sys.path.insert(1,os.path.join(pf.pyformexdir,'experimental'))
        showWhereIAm()

    if pf.options.whereami:
        showWhereIAm()
        ret = True

    if pf.options.detect:
        showWhereIAm()
        print("Detecting installed helper software")
        print(software.reportSoftware())
        ret = True

    if pf.options.listmodules is not None:
        print('\n'.join(list_modules(pf.options.listmodules)))
        ret = True

    if pf.options.pytest is not None:
        run_pytest(pf.options.pytest)
        ret = True

    if pf.options.doctest is not None:
        run_doctest(pf.options.doctest)
        ret = True

    if pf.options.remove:
        showWhereIAm()
        remove_pyFormex(pf.pyformexdir, pf.executable)
        # After this option, we can not continue,
        # so this should be the last option processed
        ret = True

    return ret


def processReportOptions2():
    """Process the reporting options that depend on the user config

    Returns True if at least one reporting option was found,
    otherwise False.
    """
    if pf.options.search or pf.options.listfiles:
        args = pf.options.args
        extended = False
        if len(args) > 0:
            opts = [ a for a in args if a.startswith('-') ]
            args = [ a for a in args if not a in opts ]
            if '-a' in opts:
                opts.remove('-a')
                extended = True
        if pf.options.search:
            search = args.pop(0)
        if len(args) > 0:
            files = args
        else:
            files = utils.sourceFiles(relative=True, extended=extended)
        if pf.options.listfiles:
            print('\n'.join(files))
        else:
            if "'" in search:
                search.replace("'", "\'")
            print("SEARCH = [%s]" % search)
            cmd = 'grep %s "%s" %s' % (' '.join(opts), search, ''.join([" '%s'" % f for f in files]))
            os.system(cmd)
        return 1

    return 0


def activateWarningFilters():
    """Activate the warning filters

    """
    from pyformex import messages
    filterWarnings()

    def _format_warning(message,category,filename,lineno,line=None):
        """Replace the default warnings.formatwarning

        This allows the warnings being called using a simple mnemonic
        string. The full message is then found from the message module.
        """
        message = messages.getMessage(message)
        message = """..

pyFormex Warning
================
%s

`%s called from:` %s `line:` %s
""" % (message, category.__name__, filename, lineno)
        if line:
            message += "%s\n" % line
        return message

    import warnings
    if pf.cfg['warnings/deprec']:
        # activate DeprecationWarning (since 2.7 default is ignore)
        #warnings.simplefilter('default', DeprecationWarning)
        pass

    if pf.cfg['warnings/nice']:
        warnings.formatwarning = _format_warning


def processArguments(args):
    """Process the non-option arguments on the command line"""


    return res


###########################  app  ################################

def run(args=[]):
    """The pyFormex main function.

    After pyFormex launcher script has correctly set up the Python import
    paths, this function is executed. It is responsible for reading the
    configuration file(s), processing the command line options and starting
    the application.

    The basic configuration file is 'pyformexrc' located in the pyFormex
    main directory. It should always be present and be left unchanged.
    If you want to make changes, copy (parts of) this file to another location
    where you can change them. Then make sure pyFormex reads you modifications
    file. By default, pyFormex will try to read the following
    configuration files if they are present (and in this order)::

        default settings:     <pyformexdir>/pyformexrc   (always loaded)
        system-wide settings: /etc/pyformex.conf
        user settings:        <configdir>/pyformex/pyformex.conf
        local settings        $PWD/.pyformexrc

    Also, an extra config file can be specified in the command line, using
    the --config option. The system-wide and user settings can be skipped
    by using the --nodefaultconfig option.

    Config files are loaded in the above order. Settings always override
    those loaded from a previous file.

    When pyFormex exits, the preferences that were changed are written to the
    last read config file. Changed settings are those that differ from the
    settings obtained after loading all but the last config file.
    If none of the optional config files exists, a new user settings file
    will be created, and an error will occur if the <configdir> path is
    not writable.

    """
    if pf.isString(args):
        args = args.split()

    ## Parse the arguments ##
    parseArguments(args)

    ## Process special options which do not start pyFormex ##
    if processReportOptions():
        return

    ## Migrate the user configuration files ##
    migrateUserConfig()

    ## Load the user configuration ##
    loadUserConfig()

    ## Process special options which do not start pyFormex ##
    if processReportOptions2():
        return

    ## Process options that override the config ##

    if pf.options.bindings is not None:
        pf.cfg['gui/bindings'] = pf.options.bindings
    delattr(pf.options, 'bindings') # avoid abuse

    if pf.options.redirect is not None:
        pf.cfg['gui/redirect'] = pf.options.redirect
    delattr(pf.options, 'redirect') # avoid abuse

    utils.setSaneLocale()

    ## Check required modules ##
    software.checkModule('numpy', fatal=True)
    # minimal supported numpy version
    software.requireModule('numpy','1.10')


    # Initialize the libraries
    if pf.options.uselib is None:
        pf.options.uselib = pf.cfg['uselib']
    from pyformex import lib

    # TODO:
    # without this, we get a crash. Maybe config related?
    pf.cfg['gui/startup_warning'] = None

    ## Activate the warning filters
    activateWarningFilters()

    # Make sure pf.PF is a Project
    from pyformex.project import Project
    pf.PF = Project()

    # Set application paths
    pf.debug("Loading AppDirs", pf.DEBUG.INFO)
    from pyformex import apps
    apps.setAppDirs()

    # Start the GUI if needed
    # Importing the gui should be done after the config is set !!
    # Set default --nogui if first remaining argument is a pyformex script.
    args = pf.options.args
    if pf.options.gui is None:
        pf.options.gui = not (len(args) > 0 and utils.is_pyFormex(args[0]))
    if pf.options.gui:
        if pf.options.mesa:
            os.environ['LIBGL_ALWAYS_SOFTWARE'] = '1'
        from pyformex.gui import guimain
        pf.debug("GUI version", pf.DEBUG.INFO)
        res = guimain.startGUI(args)
        if res != 0:
            print("Could not start the pyFormex GUI: %s" % res)
            return res # EXIT

    # Display the startup warnings and messages
    if pf.startup_warnings:
        if pf.cfg['startup_warnings']:
            pf.warning(pf.startup_warnings)
        else:
            print("*** WARNING ***\n"+pf.startup_warnings+"****************\n")
    if pf.startup_messages:
        print(pf.startup_messages)

    if pf.options.debuglevel & pf.DEBUG.INFO:
        # NOTE: inside an if to avoid computing the report when not printed
        pf.debug(software.reportSoftware(), pf.DEBUG.INFO)

    #
    # Qt4 may have changed the locale.
    # Since a LC_NUMERIC setting other than C may cause lots of troubles
    # with reading and writing files (formats become incompatible!)
    # we put it back to a sane setting
    #
    utils.setSaneLocale()

    # Startup done
    pf.started = True
    print("pyFormex started from %s" % pf.executable)

    # Prepend the inline script
    if pf.options.script:
        args[0:0] = [ '-c', pf.options.script ]

    # Prepend the autorun script
    ar = pf.cfg.get('autorun', '')
    if ar and os.path.exists(ar):
        args[0:0] = [ ar ]

    # remaining args are interpreted as scripts/apps and their parameters
    res = 0
    if args:
        pf.debug("Remaining args: %s" % args, pf.DEBUG.INFO)
        from pyformex.script import processArgs
        res = processArgs(args)

        if res:
            if pf.options.gui:
                print("There was an error while executing a script")
            else:
                return res # EXIT

    else:
        pf.debug("stdin is a tty: %s" % sys.stdin.isatty(), pf.DEBUG.INFO)
        # Play script from stdin
        # Can we check for interactive session: stdin connected to terminal?
        #from script import playScript
        #playScript(sys.stdin)

    # after processing all args, and we have a gui, go into interactive mode
    if pf.options.gui and pf.app:
        res = guimain.runGUI()

        #Save the preferences that have changed
        savePreferences()

    # Exit
    return res


# End
