import sys

from IPython.parallel import Client

rc = Client()
rc.block=True
view = rc[:]
view.run('communicator.py')
view.execute('com = EngineCommunicator()')

# gather the connection information into a dict
ar = view.apply_async(lambda : com.info)
peers = ar.get_dict()
# this is a dict, keyed by engine ID, of the connection info for the EngineCommunicators

# connect the engines to each other:
view.apply_sync(lambda pdict: com.connect(pdict), peers)

# now all the engines are connected, and we can communicate between them:

def broadcast(client, sender, msg_name, dest_name=None, block=None):
    """broadcast a message from one engine to all others."""
    dest_name = msg_name if dest_name is None else dest_name
    client[sender].execute('com.publish(%s)'%msg_name, block=None)
    targets = client.ids
    targets.remove(sender)
    return client[targets].execute('%s=com.consume()'%dest_name, block=None)

def send(client, sender, targets, msg_name, dest_name=None, block=None):
    """send a message from one to one-or-more engines."""
    dest_name = msg_name if dest_name is None else dest_name
    def _send(targets, m_name):
        msg = globals()[m_name]
        return com.send(targets, msg)

    client[sender].apply_async(_send, targets, msg_name)

    return client[targets].execute('%s=com.recv()'%dest_name, block=None)


"""
Decorators for labeling and modifying behavior of test objects.

Decorators that merely return a modified version of the original
function object are straightforward. Decorators that return a new
function object need to use
::

  nose.tools.make_decorator(original_function)(decorator)

in returning the decorator, in order to preserve meta-data such as
function name, setup and teardown functions and so on - see
``nose.tools`` for more information.

"""
import warnings

# IPython changes: make this work if numpy not available
# Original code:
#from numpy.testing.utils import \
#        WarningManager, WarningMessage
# Our version:
from _numpy_testing_utils import WarningManager
try:
    from _numpy_testing_noseclasses import KnownFailureTest
except:
    pass

# End IPython changes

def slow(t):
    """
    Label a test as 'slow'.

    The exact definition of a slow test is obviously both subjective and
    hardware-dependent, but in general any individual test that requires more
    than a second or two should be labeled as slow (the whole suite consists of
    thousands of tests, so even a second is significant).

    Parameters
    ----------
    t : callable
        The test to label as slow.

    Returns
    -------
    t : callable
        The decorated test `t`.

    Examples
    --------
    The `numpy.testing` module includes ``import decorators as dec``.
    A test can be decorated as slow like this::

      from numpy.testing import *

      @dec.slow
      def test_big(self):
          print 'Big, slow test'

    """

    t.slow = True
    return t

def setastest(tf=True):
    """
    Signals to nose that this function is or is not a test.

    Parameters
    ----------
    tf : bool
        If True, specifies that the decorated callable is a test.
        If False, specifies that the decorated callable is not a test.
        Default is True.

    Notes
    -----
    This decorator can't use the nose namespace, because it can be
    called from a non-test module. See also ``istest`` and ``nottest`` in
    ``nose.tools``.

    Examples
    --------
    `setastest` can be used in the following way::

      from numpy.testing.decorators import setastest

      @setastest(False)
      def func_with_test_in_name(arg1, arg2):
          pass

    """
    def set_test(t):
        t.__test__ = tf
        return t
    return set_test

def skipif(skip_condition, msg=None):
    """
    Make function raise SkipTest exception if a given condition is true.

    If the condition is a callable, it is used at runtime to dynamically
    make the decision. This is useful for tests that may require costly
    imports, to delay the cost until the test suite is actually executed.

    Parameters
    ----------
    skip_condition : bool or callable
        Flag to determine whether to skip the decorated test.
    msg : str, optional
        Message to give on raising a SkipTest exception. Default is None.

    Returns
    -------
    decorator : function
        Decorator which, when applied to a function, causes SkipTest
        to be raised when `skip_condition` is True, and the function
        to be called normally otherwise.

    Notes
    -----
    The decorator itself is decorated with the ``nose.tools.make_decorator``
    function in order to transmit function name, and various other metadata.

    """

    def skip_decorator(f):
        # Local import to avoid a hard nose dependency and only incur the
        # import time overhead at actual test-time.
        import nose

        # Allow for both boolean or callable skip conditions.
        if callable(skip_condition):
            skip_val = lambda : skip_condition()
        else:
            skip_val = lambda : skip_condition

        def get_msg(func,msg=None):
            """Skip message with information about function being skipped."""
            if msg is None:
                out = 'Test skipped due to test condition'
            else:
                out = '\n'+msg

            return "Skipping test: %s%s" % (func.__name__,out)

        # We need to define *two* skippers because Python doesn't allow both
        # return with value and yield inside the same function.
        def skipper_func(*args, **kwargs):
            """Skipper for normal test functions."""
            if skip_val():
                raise nose.SkipTest(get_msg(f,msg))
            else:
                return f(*args, **kwargs)

        def skipper_gen(*args, **kwargs):
            """Skipper for test generators."""
            if skip_val():
                raise nose.SkipTest(get_msg(f,msg))
            else:
                for x in f(*args, **kwargs):
                    yield x

        # Choose the right skipper to use when building the actual decorator.
        if nose.util.isgenerator(f):
            skipper = skipper_gen
        else:
            skipper = skipper_func

        return nose.tools.make_decorator(f)(skipper)

    return skip_decorator

def knownfailureif(fail_condition, msg=None):
    """
    Make function raise KnownFailureTest exception if given condition is true.

    If the condition is a callable, it is used at runtime to dynamically
    make the decision. This is useful for tests that may require costly
    imports, to delay the cost until the test suite is actually executed.

    Parameters
    ----------
    fail_condition : bool or callable
        Flag to determine whether to mark the decorated test as a known
        failure (if True) or not (if False).
    msg : str, optional
        Message to give on raising a KnownFailureTest exception.
        Default is None.

    Returns
    -------
    decorator : function
        Decorator, which, when applied to a function, causes SkipTest
        to be raised when `skip_condition` is True, and the function
        to be called normally otherwise.

    Notes
    -----
    The decorator itself is decorated with the ``nose.tools.make_decorator``
    function in order to transmit function name, and various other metadata.

    """
    if msg is None:
        msg = 'Test skipped due to known failure'

    # Allow for both boolean or callable known failure conditions.
    if callable(fail_condition):
        fail_val = lambda : fail_condition()
    else:
        fail_val = lambda : fail_condition

    def knownfail_decorator(f):
        # Local import to avoid a hard nose dependency and only incur the
        # import time overhead at actual test-time.
        import nose
        def knownfailer(*args, **kwargs):
            if fail_val():
                raise KnownFailureTest, msg
            else:
                return f(*args, **kwargs)
        return nose.tools.make_decorator(f)(knownfailer)

    return knownfail_decorator

def deprecated(conditional=True):
    """
    Filter deprecation warnings while running the test suite.

    This decorator can be used to filter DeprecationWarning's, to avoid
    printing them during the test suite run, while checking that the test
    actually raises a DeprecationWarning.

    Parameters
    ----------
    conditional : bool or callable, optional
        Flag to determine whether to mark test as deprecated or not. If the
        condition is a callable, it is used at runtime to dynamically make the
        decision. Default is True.

    Returns
    -------
    decorator : function
        The `deprecated` decorator itself.

    Notes
    -----
    .. versionadded:: 1.4.0

    """
    def deprecate_decorator(f):
        # Local import to avoid a hard nose dependency and only incur the
        # import time overhead at actual test-time.
        import nose

        def _deprecated_imp(*args, **kwargs):
            # Poor man's replacement for the with statement
            ctx = WarningManager(record=True)
            l = ctx.__enter__()
            warnings.simplefilter('always')
            try:
                f(*args, **kwargs)
                if not len(l) > 0:
                    raise AssertionError("No warning raised when calling %s"
                            % f.__name__)
                if not l[0].category is DeprecationWarning:
                    raise AssertionError("First warning for %s is not a " \
                            "DeprecationWarning( is %s)" % (f.__name__, l[0]))
            finally:
                ctx.__exit__()

        if callable(conditional):
            cond = conditional()
        else:
            cond = conditional
        if cond:
            return nose.tools.make_decorator(f)(_deprecated_imp)
        else:
            return f
    return deprecate_decorator


# encoding: utf-8
"""
An application for managing IPython profiles.

To be invoked as the `ipython profile` subcommand.

Authors:

* Min RK

"""

#-----------------------------------------------------------------------------
#  Copyright (C) 2008-2011  The IPython Development Team
#
#  Distributed under the terms of the BSD License.  The full license is in
#  the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------

#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------

import logging
import os

from IPython.config.application import Application, boolean_flag
from IPython.core.application import (
    BaseIPythonApplication, base_flags, base_aliases
)
from IPython.core.profiledir import ProfileDir
from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
from IPython.utils.traitlets import Unicode, Bool, Dict

#-----------------------------------------------------------------------------
# Constants
#-----------------------------------------------------------------------------

create_help = """Create an IPython profile by name

Create an ipython profile directory by its name or
profile directory path. Profile directories contain
configuration, log and security related files and are named
using the convention 'profile_<name>'. By default they are
located in your ipython directory. Once created, you will
can edit the configuration files in the profile
directory to configure IPython. Most users will create a
profile directory by name,
`ipython profile create myprofile`, which will put the directory
in `<ipython_dir>/profile_myprofile`.
"""
list_help = """List available IPython profiles

List all available profiles, by profile location, that can
be found in the current working directly or in the ipython
directory. Profile directories are named using the convention
'profile_<profile>'.
"""
profile_help = """Manage IPython profiles

Profile directories contain
configuration, log and security related files and are named
using the convention 'profile_<name>'. By default they are
located in your ipython directory.  You can create profiles
with `ipython profile create <name>`, or see the profiles you
already have with `ipython profile list`

To get started configuring IPython, simply do:

$> ipython profile create

and IPython will create the default profile in <ipython_dir>/profile_default,
where you can edit ipython_config.py to start configuring IPython.

"""

_list_examples = "ipython profile list  # list all profiles"

_create_examples = """
ipython profile create foo         # create profile foo w/ default config files
ipython profile create foo --reset # restage default config files over current
ipython profile create foo --parallel # also stage parallel config files
"""

_main_examples = """
ipython profile create -h  # show the help string for the create subcommand
ipython profile list -h    # show the help string for the list subcommand

ipython locate profile foo # print the path to the directory for profile 'foo'
"""

#-----------------------------------------------------------------------------
# Profile Application Class (for `ipython profile` subcommand)
#-----------------------------------------------------------------------------

def list_profiles_in(path):
    """list profiles in a given root directory"""
    files = os.listdir(path)
    profiles = []
    for f in files:
        full_path = os.path.join(path, f)
        if os.path.isdir(full_path) and f.startswith('profile_'):
            profiles.append(f.split('_',1)[-1])
    return profiles

def list_bundled_profiles():
    """list profiles that are bundled with IPython."""
    path = os.path.join(get_ipython_package_dir(), u'config', u'profile')
    files = os.listdir(path)
    profiles = []
    for profile in files:
        full_path = os.path.join(path, profile)
        if os.path.isdir(full_path) and profile != "__pycache__":
            profiles.append(profile)
    return profiles

class ProfileLocate(BaseIPythonApplication):
    description = """print the path an IPython profile dir"""

    def parse_command_line(self, argv=None):
        super(ProfileLocate, self).parse_command_line(argv)
        if self.extra_args:
            self.profile = self.extra_args[0]

    def start(self):
        print self.profile_dir.location

class ProfileList(Application):
    name = u'ipython-profile'
    description = list_help
    examples = _list_examples

    aliases = Dict({
        'ipython-dir' : 'ProfileList.ipython_dir',
        'log-level' : 'Application.log_level',
    })
    flags = Dict(dict(
        debug = ({'Application' : {'log_level' : 0}},
            "Set Application.log_level to 0, maximizing log output."
        )
    ))

    ipython_dir = Unicode(get_ipython_dir(), config=True,
        help="""
        The name of the IPython directory. This directory is used for logging
        configuration (through profiles), history storage, etc. The default
        is usually $HOME/.ipython. This options can also be specified through
        the environment variable IPYTHONDIR.
        """
    )

    def _print_profiles(self, profiles):
        """print list of profiles, indented."""
        for profile in profiles:
            print '    %s' % profile

    def list_profile_dirs(self):
        profiles = list_bundled_profiles()
        if profiles:
            print
            print "Available profiles in IPython:"
            self._print_profiles(profiles)
            print
            print "    The first request for a bundled profile will copy it"
            print "    into your IPython directory (%s)," % self.ipython_dir
            print "    where you can customize it."

        profiles = list_profiles_in(self.ipython_dir)
        if profiles:
            print
            print "Available profiles in %s:" % self.ipython_dir
            self._print_profiles(profiles)

        profiles = list_profiles_in(os.getcwdu())
        if profiles:
            print
            print "Available profiles in current directory (%s):" % os.getcwdu()
            self._print_profiles(profiles)

        print
        print "To use any of the above profiles, start IPython with:"
        print "    ipython --profile=<name>"
        print

    def start(self):
        self.list_profile_dirs()

create_flags = {}
create_flags.update(base_flags)
# don't include '--init' flag, which implies running profile create in other apps
create_flags.pop('init')
create_flags['reset'] = ({'ProfileCreate': {'overwrite' : True}},
                        "reset config files in this profile to the defaults.")
create_flags['parallel'] = ({'ProfileCreate': {'parallel' : True}},
                        "Include the config files for parallel "
                        "computing apps (ipengine, ipcontroller, etc.)")

class ProfileCreate(BaseIPythonApplication):
    name = u'ipython-profile'
    description = create_help
    examples = _create_examples
    auto_create = Bool(True, config=False)

    def _copy_config_files_default(self):
        return True

    parallel = Bool(False, config=True,
        help="whether to include parallel computing config files")
    def _parallel_changed(self, name, old, new):
        parallel_files = [   'ipcontroller_config.py',
                            'ipengine_config.py',
                            'ipcluster_config.py'
                        ]
        if new:
            for cf in parallel_files:
                self.config_files.append(cf)
        else:
            for cf in parallel_files:
                if cf in self.config_files:
                    self.config_files.remove(cf)

    def parse_command_line(self, argv):
        super(ProfileCreate, self).parse_command_line(argv)
        # accept positional arg as profile name
        if self.extra_args:
            self.profile = self.extra_args[0]

    flags = Dict(create_flags)

    classes = [ProfileDir]

    def init_config_files(self):
        super(ProfileCreate, self).init_config_files()
        # use local imports, since these classes may import from here
        from IPython.frontend.terminal.ipapp import TerminalIPythonApp
        apps = [TerminalIPythonApp]
        try:
            from IPython.frontend.qt.console.qtconsoleapp import IPythonQtConsoleApp
        except Exception:
            # this should be ImportError, but under weird circumstances
            # this might be an AttributeError, or possibly others
            # in any case, nothing should cause the profile creation to crash.
            pass
        else:
            apps.append(IPythonQtConsoleApp)
        try:
            from IPython.frontend.html.notebook.notebookapp import NotebookApp
        except ImportError:
            pass
        except Exception:
            self.log.debug('Unexpected error when importing NotebookApp',
                           exc_info=True
            )
        else:
            apps.append(NotebookApp)
        if self.parallel:
            from IPython.parallel.apps.ipcontrollerapp import IPControllerApp
            from IPython.parallel.apps.ipengineapp import IPEngineApp
            from IPython.parallel.apps.ipclusterapp import IPClusterStart
            from IPython.parallel.apps.iploggerapp import IPLoggerApp
            apps.extend([
                IPControllerApp,
                IPEngineApp,
                IPClusterStart,
                IPLoggerApp,
            ])
        for App in apps:
            app = App()
            app.config.update(self.config)
            app.log = self.log
            app.overwrite = self.overwrite
            app.copy_config_files=True
            app.profile = self.profile
            app.init_profile_dir()
            app.init_config_files()

    def stage_default_config_file(self):
        pass

class ProfileApp(Application):
    name = u'ipython-profile'
    description = profile_help
    examples = _main_examples

    subcommands = Dict(dict(
        create = (ProfileCreate, ProfileCreate.description.splitlines()[0]),
        list = (ProfileList, ProfileList.description.splitlines()[0]),
    ))

    def start(self):
        if self.subapp is None:
            print "No subcommand specified. Must specify one of: %s"%(self.subcommands.keys())
            print
            self.print_description()
            self.print_subcommands()
            self.exit(1)
        else:
            return self.subapp.start()