#! /usr/bin/env python
#
# __init__.py
#
# This file is part of NEST.
#
# Copyright (C) 2004 The NEST Initiative
#
# NEST 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.
#
# NEST 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 NEST.  If not, see <http://www.gnu.org/licenses/>.
"""
Initializer of PyNEST.
"""

import sys, os, atexit

# The following is a workaround to make MPI-enabled NEST import
# properly. The basic problem is that the shared object pynestkernel
# dynamically opens other libraries that open other libraries...
try:
    try:
        import dl
    except:
        import DLFCN as dl
    sys.setdlopenflags(dl.RTLD_NOW|dl.RTLD_GLOBAL)
except:
    # this is a hack for Python 2.6 on Mac, where RTDL_NOW is nowhere to
    # be found. See ticket #397
    import ctypes
    sys.setdlopenflags(ctypes.RTLD_GLOBAL) 

import hl_api
import nest.pynestkernel as _kernel

hl_api.nest = _kernel

Datum = _kernel.Datum

sli_push = _kernel.pushsli
hl_api.sps = sli_push
sps = sli_push

sli_pop = _kernel.popsli
hl_api.spp = sli_pop
spp = sli_pop

   
def sli_run(*args):
    raise NESTError("PyNEST is not initialized properly. Please call init() first.")

def sli_func(s, *args, **kwargs):
    """This function is a convenience function for executing the 
       sequence sli_push(args); sli_run(s); y=sli_pop(). It takes
       an arbitrary number of arguments and may have multiple
       return values. The number of return values is determined by
       the SLI function that was called.

       Keyword arguments:
       namespace - string: The sli code is executed in the given SLI namespace.
       litconv   - bool  : Convert string args beginning with / to literals.
       
       Examples:
         r,q = sli_func('dup rollu add',2,3)
         r   = sli_func('add',2,3)
         r   = sli_func('add pop',2,3)
         l   = sli_func('CreateLayer', {...}, namespace='topology')
         opt = sli_func('GetOptions', '/RandomConvergentConnect', litconv=True)
    """

    # check for namespace
    slifun = 'sli_func'  # version not converting to literals
    if kwargs.has_key('namespace'):
        s = kwargs['namespace'] + ' using ' + s + ' endusing'
    elif kwargs.has_key('litconv'):
        if kwargs['litconv']:
            slifun = 'sli_func_litconv'
    elif len(kwargs) > 0:
        hl_api.NESTError("'namespace' and 'litconv' are the only valid keyword arguments.")
    
    sli_push(args)       # push array of arguments on SLI stack
    sli_push(s)          # push command string
    sli_run(slifun)      # SLI support code to execute s on args
    r=sli_pop()          # return value is an array

    if len(r) == 1:        # 1 return value is no tuple
        return r[0]
 
    if len(r) != 0:   
       return tuple(r)   # convert array to tuple

kernel_sr = _kernel.runsli
hl_api.sr = sli_run
sr = sli_run
hl_api.sli_func = sli_func

initialized = False

def catching_sr(cmd):
    """
    Send a command string to the NEST kernel to be executed.
    catching_sr is a wrapper of the kernel_sr to raise errors as Python errors.
    """

    kernel_sr('{'+cmd+'} runprotected')
    if not sli_pop():
        errorname = sli_pop()
        message = sli_pop()
        commandname = sli_pop()
        raise hl_api.NESTError(errorname + ' in ' + commandname + message)


def catch_errors(catchErrors = True):
    """Switch between the catching and non-catching versions or sr"""

    global sr, sli_run

    if catchErrors:
        sli_run = catching_sr
    else:
        sli_run = kernel_sr

    sr = sli_run
    hl_api.sr = sli_run


def init(argv) :
    """Initialize. argv is passed to the NEST kernel."""

    global initialized

    if initialized:
        raise hl_api.NESTError("NEST already initialized.")
        return

    quiet = False
    if argv.count("--quiet") :
        quiet = True
        argv.remove("--quiet")

    initialized |= _kernel.initialize(argv, __path__[0])

    if initialized :

        if not quiet :
            kernel_sr("pywelcome")
        catch_errors(True)

        # Dirty hack to get models completion in iPython shell
        try:
            __IPYTHON__
        except NameError:
            pass
        else:
            try:
                import keyword
                keyword.kwlist += hl_api.Models()
            except ImportError:
                pass

def test ():
    """ Runs a battery of unit tests on PyNEST """
    import nest.tests
    import unittest

    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(nest.tests.suite())


if not 'DELAY_PYNEST_INIT' in os.environ:
    init(sys.argv)

atexit.register(_kernel.finalize)

from hl_api import *