# Copyright (c) 2004-2005 DoCoMo Euro-Labs GmbH (Munich, Germany).
# Copyright (c) 2001-2005 LOGILAB S.A. (Paris, FRANCE).
#
# http://www.docomolab-euro.com/ -- mailto:tarlano@docomolab-euro.com
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library 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
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
"""\
USAGE: %s [OPTIONS]

Start the Narval interpreter (except with some specific options, see
description below).


Options
```````
   --help              print this help message and exit
   --version           print narval's version and exit
   --create-home       create a fresh narval home and exit
   --stop              stop a currently running interpreter and exit
                            
   --quiet             be the less verbose as possible
   --debug             log debug information
   --profile           run in profiled mode


   --home=dir          overwrite NARVAL_HOME environment variable with
                       the given directory
   --rc-file=file      use <file> instead of default narvalrc.xml init
                         file
   --memory-file=file  uses specified <file> instead of default memory
                       file

   
   --with-shell        starts interactive python shell with '_' as
                       the Narval instance
   --xmlrpc-port=port  listens for xmlrpc client connections on the
                       specified port
   --pyro-port=port    listens for pyro client connections on the
                       specified port
   --max-threads=n     sets maximum number of concurrent threads

   
   --start-plan=plan   execute <plan>  (i.e. <cookbook>.<recipe>)
                       right after starting
   --context=match     used with --start-plan, launch the plan with
                       elements matching the given match expression


   --quit-when-done         quit when no more running plan (only done or
                            failed)
   --quit-after=n           quit after <n> seconds of execution
   --save-mem-on-quit=file  save memory to <file> when quitting


Environment variables
`````````````````````
- NARVAL_SHARE: location of narval's shared resources
- NARVAL_HOME: user's narval home directory (default to ~/.narval)


:version: $Revision:$  
:author: Logilab
"""

__revision__ = "$Id: engine.py,v 1.110 2003/03/18 17:51:04 syt Exp $"
__docformat__ = "restructuredtext en"

import sys
import getopt
import os
from os.path import exists, abspath, basename, join
import socket

from narval import init_log, config
from narval.public import AL_NS


def stop(narval) :
    """wrapper function on narval.shutdown to be used to stop the Narval
    interpreter from a signal handler

    :type narval: `Narval`
    :param narval: the narval intrepreter
    """
    narval.shutdown()

def dump_stats(statfile):
    """display statistics from profiling mode

    :type statfile: str
    :param statfile: path to the file containing profile information
    """
    import pstats
    prof = pstats.Stats(statfile)
    prof.sort_stats('time','calls').print_stats(.25)
    prof.sort_stats('cum','calls').print_stats(.25)
    prof.strip_dirs().sort_stats('cum','calls').print_callers(.25)
    prof.strip_dirs().sort_stats('cum','calls').print_callees()

def usage(status=1):
    """display usage and exit

    :type status: int
    :param status: exit status
    """
    print __doc__ % basename(sys.argv[0])
    sys.exit(status)

    
def run(args=None):
    """command line launcher

    :type args: list
    :param args: command line arguments, without the script name
    """
    long_list = ['help', 'version',
                 'stop', 'create-home', 
                 'debug', 'test', 'quiet', 'profile=',
                 'home=', 'rc-file=', 'memory-file=', 
                 'xmlrpc-port=', 'pyro-port=',
                 'max-threads=',  'with-shell', 'signals',
                 'start-plan=', 'context=',
                 'quit-after=', 'quit-when-done', 'save-mem-on-quit=']
    try:
        optlist, args = getopt.getopt(args or sys.argv[1:], 'h', long_list)
    except getopt.error, ex:
        print 'Error:', ex
        print
        usage(1)
    quiet = [opt for opt in optlist if opt[0] == '--quiet']
    if quiet:
        init_log(LOG_ERR, sid='narval')
    else:
        init_log(LOG_NOTICE, sid='narval')
    # specific home or memory setup ?
    home, mempath, profpath, rcpath = None, None, None, None
    manage_signals = False
    action = 'start'
    narval_opts = []
    for opt, val in optlist:
        if opt in ('-h', '--help'):
            usage(0)
        if opt == '--version':
            from narval.__pkginfo__ import version
            print 'Python %s\nnarval %s' % (sys.version, version)
            sys.exit(0)
        elif opt in ('--create-home', '--stop'):
            action = opt[2:]
        elif opt == '--home':
            home = abspath(val)
        elif opt == '--memory-file' :
            log(LOG_NOTICE, 'using %s as memory file', val)
            mempath = abspath(val)
        elif opt == '--rc-file' :
            log(LOG_NOTICE, 'using %s as configuration file', val)
            rcpath = abspath(val)
        elif opt == '--profile':
            profpath = val
            action = 'profile'
        elif opt == '--signals':
            manage_signals = True
        elif opt != '--quiet':
            narval_opts.append((opt, val))
    # load the configuration
    config.DEFAULT_CONFIGURATOR.build_config()
    # manage narval home        
    if home:
        config.DEFAULT_CONFIGURATOR.set_home_from_command_line(home)
    if action == 'create-home':
        action_create_home()
    else:
        # check user home exists
        try:
            config.check_home()
        except config.ConfigurationError, ex:
            print ex
            sys.exit(1)
        home = config.get_home()
        log(LOG_NOTICE, 'using %s as home', home)
        pid_file = join(home, 'narval.pid')
        if action == 'stop':
            action_stop(pid_file)
        elif action == 'profile':
            import hotshot
            profile = hotshot.Profile(profpath)
            log(LOG_NOTICE,'profiling execution')
            try:
                profile.runcall(action_start, pid_file, narval_opts, rcpath,
                                mempath, manage_signals)
            finally:
                dump_stats(profpath)
        else: # action == 'start'
            action_start(pid_file, narval_opts, rcpath, mempath, manage_signals)
    sys.exit(0)


def action_create_home():
    config.create_home()
    print "initialized narval home in %s"  % config.get_home()

    
def action_stop(pid_file):
    try:
        pid = open(pid_file).read().strip()
    except IOError:
        print 'no pid file found'
        sys.exit(1)
    import signal
    try:
        os.kill(int(pid), signal.SIGTERM)
    except OSError:
        # suppose it's a "No Such Process" error
        pass
    # remove the pid file FIXME: ensure process has been killed
    os.remove(pid_file)

    
def action_start(pid_file, narval_opts, rcpath, mempath, profpath,
                 manage_signals=True):
    # create pid lock file if it doesn't exist yet
    if exists(pid_file):
        print '''\
A narval pid file has been found (%s), which seems to indicate that a
narval is already running using that home. Stop the running narval or
remove the pid file manually before rerunning this command.
''' % pid_file
        sys.exit(1)
    if not ('--quit-when-done', '') in narval_opts:
        open(pid_file, 'wt').write(str(os.getpid()))
    # create Narval instance
    from narval.engine import Narval
    narval = Narval(rcpath)
    narval.post_event( ('load_protocol_handlers',) )
    narval.post_event( ('load_interfaces',) )
    narval.post_event( ('load_elements',) )
    narval.post_event( ('load_recipes',) )
    narval.post_event( ('load_modules',) )
    narval.post_event( ('load_memory', mempath) )
    if manage_signals:
        import signal
        signal.signal(signal.SIGINT, lambda x, y, z=narval: stop(z))
        signal.signal(signal.SIGTERM, lambda x, y, z=narval: stop(z))
        log(LOG_INFO, "managing signals")
    # process narval options
    narval_options(narval, narval_opts)
    narval.run()


def narval_options(narval, optlist):
    """configure the narval interpreter according to options

    :type narval: `Narval`
    :param narval: the narval intrepreter

    :type optlist: list
    :param optlist: (option, value) tuples
    """
    start_plan, context_match = None, None
    # process options
    for opt, val in optlist:
        if opt == '--debug':
            from narval.listeners import PlanListenerLogImpl, \
                 MemoryListenerLogImpl
            narval.debug = 1            
            init_log(LOG_DEBUG, sid='narval')
            narval.start_com_service()
            narval.com_service.add_listener('plan', PlanListenerLogImpl())
            narval.com_service.add_listener('memory', MemoryListenerLogImpl())
        elif opt == '--with-shell':
            narval.post_event( ('start_shell_service', ) )
        elif opt == '--quit-after' :
            narval.schedule_event(('quit',), when=int(val))
        elif opt == '--quit-when-done':
            narval.quit_when_done = True
        elif opt == '--save-mem-on-quit' :
            narval.save_mem_on_quit = val
        elif opt == '--start-plan' :
            start_plan = val
        elif opt == '--context' :
            context_match = val
        elif opt == '--test' :
            narval.test_mode = True
        elif opt in ('--pyro-port', '--xmlrpc-port'):
            proto = opt.split('-')[2]
            listen_xml = '<al:listen-on type="%s" host="%s" port="%d" \
xmlns:al="%s"/>' % (proto, socket.gethostname(), int(val), AL_NS)
            narval.add_element(listen_xml)
        elif opt == '--max-threads' :
            narval.max_threads = int(val)
    if start_plan:
        narval.start_plan(start_plan, context_match or [])
    elif context_match:
        log(LOG_ERR, '--context require --start-plan')
    
if __name__ == "__main__":
    run(sys.argv[1:])
