#compare a lookup table of spectra at ages and metallicities to
#calls to fsps.sps.get_spectrum() for different metallicities
import time, os, subprocess, re, sys
import numpy as np
#import matplotlib.pyplot as pl
import fsps
from prospect import sources as sps_basis
from prospect.models import sedmodel

def run_command(cmd):
    """
    Open a child process, and return its exit status and stdout.

    """
    child = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE,
                             stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    out = [s for s in child.stdout]
    w = child.wait()
    return os.WEXITSTATUS(w), out

# Check to make sure that the required environment variable is present.
try:
    ev = os.environ["SPS_HOME"]
except KeyError:
    raise ImportError("You need to have the SPS_HOME environment variable")

# Check the SVN revision number.
cmd = ["svnversion", ev]
stat, out = run_command(" ".join(cmd))
fsps_vers = int(re.match("^([0-9])+", out[0]).group(0))

sps = fsps.StellarPopulation(zcontinuous=True)
print('FSPS version = {}'.format(fsps_vers))
print('Zs={0}, N_lambda={1}'.format(sps.zlegend, len(sps.wavelengths)))
print('single age')

def spec_from_fsps(z, t, s):
    t0 = time.time()
    sps.params['logzsol'] = z
    sps.params['sigma_smooth'] = s
    sps.params['tage'] = t
    wave, spec  = sps.get_spectrum(peraa=True, tage = sps.params['tage'])
    #print(spec.shape)
    return time.time()-t0

def mags_from_fsps(z, t, s):
    t0 = time.time()
    sps.params['zred']=t
    sps.params['logzsol'] = z
    sps.params['sigma_smooth'] = s
    sps.params['tage'] = t
    mags  = sps.get_mags(tage = sps.params['tage'], redshift=0.0)
    #print(spec.shape)
    return time.time()-t0

def spec_from_ztinterp(z, t, s):
    t0 = time.time()
    sps.params['logzsol'] = z
    sps.params['sigma_smooth'] = s
    sps.params['tage'] = t
    sps.params['imf3'] = s
    spec, m, l  = sps.ztinterp(sps.params['logzsol'], sps.params['tage'], peraa=True)
    #print(spec.shape)
    return time.time()-t0

if sys.argv[1] == 'mags':
    from_fsps = mags_from_fsps
    print('timing get_mags')
    print('nbands = {}'.format(len(sps.get_mags(tage=1.0))))
elif sys.argv[1] == 'spec':
    from_fsps = spec_from_fsps
    print('timing get_spectrum')
elif sys.argv[1] == 'ztinterp':
    from_fsps = spec_from_ztinterp
    print('timing get_spectrum')
elif sys.argv[1] == 'sedpy':
    from sedpy import observate
    nbands = len(sps.get_mags(tage=1.0))
    fnames = nbands * ['sdss_r0']
    filters = observate.load_filters(fnames)

    def mags_from_sedpy(z, t, s):
        t0 = time.time()
        sps.params['logzsol'] = z
        sps.params['sigma_smooth'] = s
        sps.params['tage'] = t
        wave, spec  = sps.get_spectrum(peraa=True,
                                       tage = sps.params['tage'])
        mags = observate.getSED(wave, spec, filters)
        return time.time()-t0

    from_fsps = mags_from_sedpy

sps.params['add_neb_emission'] = False
sps.params['smooth_velocity'] = True
sps.params['sfh'] = 0

ntry = 30
zz = np.random.uniform(-1,0,ntry)
tt = np.random.uniform(0.1,4,ntry)
ss = np.random.uniform(1,2.5,ntry)

#make sure all z's already compiled
_ =[from_fsps(z, 1.0, 0.0) for z in [-1, -0.8, -0.6, -0.4, -0.2, 0.0]]
all_dur = []
print('no neb emission:')
dur_many = np.zeros(ntry)
for i in xrange(ntry):
    dur_many[i] = from_fsps(zz[i], tt[i], ss[i])
print('<t/call>={0}s, sigma_t={1}s'.format(dur_many.mean(), dur_many.std()))
all_dur += [dur_many]

print('no neb emission, no smooth:')
dur_many = np.zeros(ntry)
for i in xrange(ntry):
    dur_many[i] = from_fsps(zz[i], tt[i], 0.0)
print('<t/call>={0}s, sigma_t={1}s'.format(dur_many.mean(), dur_many.std()))
all_dur += [dur_many]

sps.params['add_neb_emission'] = True
print('neb emission:')
dur_many = np.zeros(ntry)
for i in xrange(ntry):
    dur_many[i] = from_fsps(zz[i], tt[i], ss[i])
print('<t/call>={0}s, sigma_t={1}s'.format(dur_many.mean(), dur_many.std()))
all_dur += [dur_many]

print('neb emission, no smooth:')
dur_many = np.zeros(ntry)
for i in xrange(ntry):
    dur_many[i] = from_fsps(zz[i], tt[i], 0.0)
print('<t/call>={0}s, sigma_t={1}s'.format(dur_many.mean(), dur_many.std()))
all_dur += [dur_many]


"""
=======================
Morphological Filtering
=======================

Morphological image processing is a collection of non-linear operations related
to the shape or morphology of features in an image, such as boundaries,
skeletons, etc. In any given technique, we probe an image with a small shape or
template called a structuring element, which defines the region of interest or
neighborhood around a pixel.

In this document we outline the following basic morphological operations:

1. Erosion
2. Dilation
3. Opening
4. Closing
5. White Tophat
6. Black Tophat
7. Skeletonize
8. Convex Hull

To get started, let's load an image using ``io.imread``. Note that morphology
functions only work on gray-scale or binary images, so we set ``as_grey=True``.
"""

import matplotlib.pyplot as plt
from skimage.data import data_dir
from skimage.util import img_as_ubyte
from skimage import io

phantom = img_as_ubyte(io.imread(data_dir+'/phantom.png', as_grey=True))
fig, ax = plt.subplots()
ax.imshow(phantom, cmap=plt.cm.gray)

"""
.. image:: PLOT2RST.current_figure

Let's also define a convenience function for plotting comparisons:
"""

def plot_comparison(original, filtered, filter_name):

    fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(8, 4), sharex=True, sharey=True)
    ax1.imshow(original, cmap=plt.cm.gray)
    ax1.set_title('original')
    ax1.axis('off')
    ax1.set_adjustable('box-forced')
    ax2.imshow(filtered, cmap=plt.cm.gray)
    ax2.set_title(filter_name)
    ax2.axis('off')
    ax2.set_adjustable('box-forced')

"""
Erosion
=======

Morphological ``erosion`` sets a pixel at (i, j) to the *minimum over all
pixels in the neighborhood centered at (i, j)*. The structuring element,
``selem``, passed to ``erosion`` is a boolean array that describes this
neighborhood. Below, we use ``disk`` to create a circular structuring element,
which we use for most of the following examples.
"""

from skimage.morphology import erosion, dilation, opening, closing, white_tophat
from skimage.morphology import black_tophat, skeletonize, convex_hull_image
from skimage.morphology import disk

selem = disk(6)
eroded = erosion(phantom, selem)
plot_comparison(phantom, eroded, 'erosion')

"""
.. image:: PLOT2RST.current_figure

Notice how the white boundary of the image disappears or gets eroded as we
increase the size of the disk. Also notice the increase in size of the two
black ellipses in the center and the disappearance of the 3 light grey
patches in the lower part of the image.

Dilation
========

Morphological ``dilation`` sets a pixel at (i, j) to the *maximum over all
pixels in the neighborhood centered at (i, j)*. Dilation enlarges bright
regions and shrinks dark regions.
"""

dilated = dilation(phantom, selem)
plot_comparison(phantom, dilated, 'dilation')

"""
.. image:: PLOT2RST.current_figure

Notice how the white boundary of the image thickens, or gets dilated, as we
increase the size of the disk. Also notice the decrease in size of the two
black ellipses in the centre, and the thickening of the light grey circle in
the center and the 3 patches in the lower part of the image.

Opening
=======

Morphological ``opening`` on an image is defined as an *erosion followed by a
dilation*. Opening can remove small bright spots (i.e. "salt") and connect
small dark cracks.
"""

opened = opening(phantom, selem)
plot_comparison(phantom, opened, 'opening')

"""
.. image:: PLOT2RST.current_figure

Since ``opening`` an image starts with an erosion operation, light regions that
are *smaller* than the structuring element are removed. The dilation operation
that follows ensures that light regions that are *larger* than the structuring
element retain their original size. Notice how the light and dark shapes in the
center their original thickness but the 3 lighter patches in the bottom get
completely eroded. The size dependence is highlighted by the outer white ring:
The parts of the ring thinner than the structuring element were completely
erased, while the thicker region at the top retains its original thickness.

Closing
=======

Morphological ``closing`` on an image is defined as a *dilation followed by an
erosion*. Closing can remove small dark spots (i.e. "pepper") and connect
small bright cracks.

To illustrate this more clearly, let's add a small crack to the white border:
"""

phantom = img_as_ubyte(io.imread(data_dir+'/phantom.png', as_grey=True))
phantom[10:30, 200:210] = 0

closed = closing(phantom, selem)
plot_comparison(phantom, closed, 'closing')

"""
.. image:: PLOT2RST.current_figure

Since ``closing`` an image starts with an dilation operation, dark regions
that are *smaller* than the structuring element are removed. The dilation
operation that follows ensures that dark regions that are *larger* than the
structuring element retain their original size. Notice how the white ellipses
at the bottom get connected because of dilation, but other dark region retain
their original sizes. Also notice how the crack we added is mostly removed.

White tophat
============

The ``white_tophat`` of an image is defined as the *image minus its
morphological opening*. This operation returns the bright spots of the image
that are smaller than the structuring element.

To make things interesting, we'll add bright and dark spots to the image:
"""

phantom = img_as_ubyte(io.imread(data_dir+'/phantom.png', as_grey=True))
phantom[340:350, 200:210] = 255
phantom[100:110, 200:210] = 0

w_tophat = white_tophat(phantom, selem)
plot_comparison(phantom, w_tophat, 'white tophat')

"""
.. image:: PLOT2RST.current_figure

As you can see, the 10-pixel wide white square is highlighted since it is
smaller than the structuring element. Also, the thin, white edges around most
of the ellipse are retained because they're smaller than the structuring
element, but the thicker region at the top disappears.

Black tophat
============

The ``black_tophat`` of an image is defined as its morphological **closing
minus the original image**. This operation returns the *dark spots of the
image that are smaller than the structuring element*.
"""

b_tophat = black_tophat(phantom, selem)
plot_comparison(phantom, b_tophat, 'black tophat')

"""
.. image:: PLOT2RST.current_figure

As you can see, the 10-pixel wide black square is highlighted since it is
smaller than the structuring element.

Duality
-------

As you should have noticed, many of these operations are simply the reverse
of another operation. This duality can be summarized as follows:

1. Erosion <-> Dilation
2. Opening <-> Closing
3. White tophat <-> Black tophat

Skeletonize
===========

Thinning is used to reduce each connected component in a binary image to a
*single-pixel wide skeleton*. It is important to note that this is performed
on binary images only.

"""

from skimage import img_as_bool
horse = ~img_as_bool(io.imread(data_dir+'/horse.png', as_grey=True))

sk = skeletonize(horse)
plot_comparison(horse, sk, 'skeletonize')

"""
.. image:: PLOT2RST.current_figure

As the name suggests, this technique is used to thin the image to 1-pixel wide
skeleton by applying thinning successively.

Convex hull
===========

The ``convex_hull_image`` is the *set of pixels included in the smallest
convex polygon that surround all white pixels in the input image*. Again note
that this is also performed on binary images.

"""

hull1 = convex_hull_image(horse)
plot_comparison(horse, hull1, 'convex hull')

"""
.. image:: PLOT2RST.current_figure

As the figure illustrates, ``convex_hull_image`` gives the smallest polygon
which covers the white or True completely in the image.

If we add a small grain to the image, we can see how the convex hull adapts to
enclose that grain:
"""

import numpy as np

horse2 = np.copy(horse)
horse2[45:50, 75:80] = 1

hull2 = convex_hull_image(horse2)
plot_comparison(horse2, hull2, 'convex hull')

"""
.. image:: PLOT2RST.current_figure

Additional Resources
====================

1. `MathWorks tutorial on morphological processing
<http://www.mathworks.com/help/images/morphology-fundamentals-dilation-and-erosion.html>`_
2. `Auckland university's tutorial on Morphological Image Processing
<http://www.cs.auckland.ac.nz/courses/compsci773s1c/lectures/ImageProcessing-html/topic4.htm>`_
3. http://en.wikipedia.org/wiki/Mathematical_morphology

"""

plt.show()


#!/usr/bin/python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import subprocess
import sys
import tempfile
import time

script_dir = os.path.dirname(__file__)
sys.path.append(os.path.join(script_dir,
                             '../../tools/browser_tester'))

import browser_tester
import browsertester.browserlauncher

# This script extends browser_tester to check for the presence of
# Breakpad crash dumps.

# This reads a file of lines containing 'key:value' pairs.
# The file contains entries like the following:
#   plat:Win32
#   prod:Chromium
#   ptype:nacl-loader
#   rept:crash svc
def ReadDumpTxtFile(filename):
  dump_info = {}
  fh = open(filename, 'r')
  for line in fh:
    if ':' in line:
      key, value = line.rstrip().split(':', 1)
      dump_info[key] = value
  fh.close()
  return dump_info

def StartCrashService(browser_path, dumps_dir, windows_pipe_name,
                      cleanup_funcs, crash_service_exe):
  # Find crash_service.exe relative to chrome.exe.  This is a bit icky.
  browser_dir = os.path.dirname(browser_path)
  proc = subprocess.Popen([os.path.join(browser_dir, crash_service_exe),
                           '--v=1',  # Verbose output for debugging failures
                           '--dumps-dir=%s' % dumps_dir,
                           '--pipe-name=%s' % windows_pipe_name])

  def Cleanup():
    # Note that if the process has already exited, this will raise
    # an 'Access is denied' WindowsError exception, but
    # crash_service.exe is not supposed to do this and such
    # behaviour should make the test fail.
    proc.terminate()
    status = proc.wait()
    sys.stdout.write('crash_dump_tester: %s exited with status %s\n'
                     % (crash_service_exe, status))

  cleanup_funcs.append(Cleanup)

def ListPathsInDir(dir_path):
  if os.path.exists(dir_path):
    return [os.path.join(dir_path, name)
            for name in os.listdir(dir_path)]
  else:
    return []

def GetDumpFiles(dumps_dirs):
  all_files = [filename
               for dumps_dir in dumps_dirs
               for filename in ListPathsInDir(dumps_dir)]
  sys.stdout.write('crash_dump_tester: Found %i files\n' % len(all_files))
  for dump_file in all_files:
    sys.stdout.write('  %s (size %i)\n'
                     % (dump_file, os.stat(dump_file).st_size))
  return [dump_file for dump_file in all_files
          if dump_file.endswith('.dmp')]

def Main(cleanup_funcs):
  parser = browser_tester.BuildArgParser()
  parser.add_option('--expected_crash_dumps', dest='expected_crash_dumps',
                    type=int, default=0,
                    help='The number of crash dumps that we should expect')
  parser.add_option('--expected_process_type_for_crash',
                    dest='expected_process_type_for_crash',
                    type=str, default='nacl-loader',
                    help='The type of Chromium process that we expect the '
                    'crash dump to be for')
  # Ideally we would just query the OS here to find out whether we are
  # running x86-32 or x86-64 Windows, but Python's win32api module
  # does not contain a wrapper for GetNativeSystemInfo(), which is
  # what NaCl uses to check this, or for IsWow64Process(), which is
  # what Chromium uses.  Instead, we just rely on the build system to
  # tell us.
  parser.add_option('--win64', dest='win64', action='store_true',
                    help='Pass this if we are running tests for x86-64 Windows')
  options, args = parser.parse_args()

  temp_dir = tempfile.mkdtemp(prefix='nacl_crash_dump_tester_')
  def CleanUpTempDir():
    browsertester.browserlauncher.RemoveDirectory(temp_dir)
  cleanup_funcs.append(CleanUpTempDir)

  # To get a guaranteed unique pipe name, use the base name of the
  # directory we just created.
  windows_pipe_name = r'\\.\pipe\%s_crash_service' % os.path.basename(temp_dir)

  # This environment variable enables Breakpad crash dumping in
  # non-official builds of Chromium.
  os.environ['CHROME_HEADLESS'] = '1'
  if sys.platform == 'win32':
    dumps_dir = temp_dir
    # Override the default (global) Windows pipe name that Chromium will
    # use for out-of-process crash reporting.
    os.environ['CHROME_BREAKPAD_PIPE_NAME'] = windows_pipe_name
    # Launch the x86-32 crash service so that we can handle crashes in
    # the browser process.
    StartCrashService(options.browser_path, dumps_dir, windows_pipe_name,
                      cleanup_funcs, 'crash_service.exe')
    if options.win64:
      # Launch the x86-64 crash service so that we can handle crashes
      # in the NaCl loader process (nacl64.exe).
      StartCrashService(options.browser_path, dumps_dir, windows_pipe_name,
                        cleanup_funcs, 'crash_service64.exe')
    # We add a delay because there is probably a race condition:
    # crash_service.exe might not have finished doing
    # CreateNamedPipe() before NaCl does a crash dump and tries to
    # connect to that pipe.
    # TODO(mseaborn): We could change crash_service.exe to report when
    # it has successfully created the named pipe.
    time.sleep(1)
  elif sys.platform == 'darwin':
    dumps_dir = temp_dir
    os.environ['BREAKPAD_DUMP_LOCATION'] = dumps_dir
  elif sys.platform.startswith('linux'):
    # The "--user-data-dir" option is not effective for the Breakpad
    # setup in Linux Chromium, because Breakpad is initialized before
    # "--user-data-dir" is read.  So we set HOME to redirect the crash
    # dumps to a temporary directory.
    home_dir = temp_dir
    os.environ['HOME'] = home_dir
    options.enable_crash_reporter = True

  result = browser_tester.Run(options.url, options)

  # Find crash dump results.
  if sys.platform.startswith('linux'):
    # Look in "~/.config/*/Crash Reports".  This will find crash
    # reports under ~/.config/chromium or ~/.config/google-chrome, or
    # under other subdirectories in case the branding is changed.
    dumps_dirs = [os.path.join(path, 'Crash Reports')
                  for path in ListPathsInDir(os.path.join(home_dir, '.config'))]
  else:
    dumps_dirs = [dumps_dir]
  dmp_files = GetDumpFiles(dumps_dirs)

  failed = False
  msg = ('crash_dump_tester: ERROR: Got %i crash dumps but expected %i\n' %
         (len(dmp_files), options.expected_crash_dumps))
  if len(dmp_files) != options.expected_crash_dumps:
    sys.stdout.write(msg)
    failed = True

  for dump_file in dmp_files:
    # Sanity check: Make sure dumping did not fail after opening the file.
    msg = 'crash_dump_tester: ERROR: Dump file is empty\n'
    if os.stat(dump_file).st_size == 0:
      sys.stdout.write(msg)
      failed = True

    # On Windows, the crash dumps should come in pairs of a .dmp and
    # .txt file.
    if sys.platform == 'win32':
      second_file = dump_file[:-4] + '.txt'
      msg = ('crash_dump_tester: ERROR: File %r is missing a corresponding '
             '%r file\n' % (dump_file, second_file))
      if not os.path.exists(second_file):
        sys.stdout.write(msg)
        failed = True
        continue
      # Check that the crash dump comes from the NaCl process.
      dump_info = ReadDumpTxtFile(second_file)
      if 'ptype' in dump_info:
        msg = ('crash_dump_tester: ERROR: Unexpected ptype value: %r != %r\n'
               % (dump_info['ptype'], options.expected_process_type_for_crash))
        if dump_info['ptype'] != options.expected_process_type_for_crash:
          sys.stdout.write(msg)
          failed = True
      else:
        sys.stdout.write('crash_dump_tester: ERROR: Missing ptype field\n')
        failed = True
    # TODO(mseaborn): Ideally we would also check that a backtrace
    # containing an expected function name can be extracted from the
    # crash dump.

  if failed:
    sys.stdout.write('crash_dump_tester: FAILED\n')
    result = 1
  else:
    sys.stdout.write('crash_dump_tester: PASSED\n')

  return result

def MainWrapper():
  cleanup_funcs = []
  try:
    return Main(cleanup_funcs)
  finally:
    for func in cleanup_funcs:
      func()

if __name__ == '__main__':
  sys.exit(MainWrapper())


from itertools import count
from collections import Iterator
from math import ceil
from toolz import merge, accumulate, merge_sorted
import toolz
from operator import getitem, setitem
import pandas as pd
import numpy as np
from pframe import pframe

from .. import threaded
from .core import DataFrame, Series, get, names
from ..compatibility import unicode
from ..utils import ignoring

tokens = ('-%d' % i for i in count(1))

def set_index(f, index, npartitions=None, **kwargs):
    """ Set DataFrame index to new column

    Sorts index and realigns Dataframe to new sorted order.  This shuffles and
    repartitions your data.
    """
    npartitions = npartitions or f.npartitions
    if not isinstance(index, Series):
        index2 = f[index]
    else:
        index2 = index

    divisions = (index2
                  .quantiles(np.linspace(0, 100, npartitions+1)[1:-1])
                  .compute())
    return f.set_partition(index, divisions, **kwargs)

partition_names = ('set_partition-%d' % i for i in count(1))

def set_partition(f, index, divisions, get=threaded.get, **kwargs):
    """ Set new partitioning along index given divisions """
    divisions = unique(divisions)
    name = next(names)
    if isinstance(index, Series):
        assert index.divisions == f.divisions
        dsk = dict(((name, i), (f._partition_type.set_index, block, ind))
                for i, (block, ind) in enumerate(zip(f._keys(), index._keys())))
        f2 = type(f)(merge(f.dask, index.dask, dsk), name,
                       f.column_info, f.divisions)
    else:
        dsk = dict(((name, i), (f._partition_type.set_index, block, index))
                for i, block in enumerate(f._keys()))
        f2 = type(f)(merge(f.dask, dsk), name, f.column_info, f.divisions)

    head = f2.head()
    pf = pframe(like=head, divisions=divisions, **kwargs)

    def append(block):
        pf.append(block)
        return 0

    f2.map_blocks(append).compute(get=get)
    pf.flush()

    return from_pframe(pf)

def from_pframe(pf):
    """ Load dask.array from pframe """
    name = next(names)
    dsk = dict(((name, i), (pframe.get_partition, pf, i))
                for i in range(pf.npartitions))

    return DataFrame(dsk, name, pf.columns, pf.divisions)

def unique(divisions):
    """ Polymorphic unique function

    >>> list(unique([1, 2, 3, 1, 2, 3]))
    [1, 2, 3]

    >>> unique(np.array([1, 2, 3, 1, 2, 3]))
    array([1, 2, 3])

    >>> unique(pd.Categorical(['Alice', 'Bob', 'Alice'], ordered=False))
    [Alice, Bob]
    Categories (2, object): [Alice, Bob]
    """
    if isinstance(divisions, np.ndarray):
        return np.unique(divisions)
    if isinstance(divisions, pd.Categorical):
        return pd.Categorical.from_codes(np.unique(divisions.codes),
                divisions.categories, divisions.ordered)
    if isinstance(divisions, (tuple, list, Iterator)):
        return tuple(toolz.unique(divisions))
    raise NotImplementedError()


# Todd Miller   jmiller@stsci.edu

from __future__ import division

import os, sys, math

import Tkinter as Tk, FileDialog
import tkagg                 # Paint image to Tk photo blitter extension
from backend_agg import FigureCanvasAgg

import os.path

import matplotlib
from matplotlib.cbook import is_string_like
from matplotlib.backend_bases import RendererBase, GraphicsContextBase, \
     FigureManagerBase, FigureCanvasBase, NavigationToolbar2, cursors

from matplotlib.figure import Figure
from matplotlib._pylab_helpers import Gcf

import matplotlib.windowing as windowing
from matplotlib.widgets import SubplotTool

import matplotlib.cbook as cbook

rcParams = matplotlib.rcParams
verbose = matplotlib.verbose

backend_version = Tk.TkVersion

# the true dots per inch on the screen; should be display dependent
# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi
PIXELS_PER_INCH = 75

cursord = {
    cursors.MOVE: "fleur",
    cursors.HAND: "hand2",
    cursors.POINTER: "arrow",
    cursors.SELECT_REGION: "tcross",
    }

def round(x):
    return int(math.floor(x+0.5))

def raise_msg_to_str(msg):
    """msg is a return arg from a raise.  Join with new lines"""
    if not is_string_like(msg):
        msg = '\n'.join(map(str, msg))
    return msg

def error_msg_tkpaint(msg, parent=None):
    import tkMessageBox
    tkMessageBox.showerror("matplotlib", msg)

def draw_if_interactive():
    if matplotlib.is_interactive():
        figManager =  Gcf.get_active()
        if figManager is not None:
            figManager.show()

def show():
    """
    Show all the figures and enter the gtk mainloop

    This should be the last line of your script.  This function sets
    interactive mode to True, as detailed on
    http://matplotlib.sf.net/interactive.html
    """
    for manager in Gcf.get_all_fig_managers():
        manager.show()
    import matplotlib
    matplotlib.interactive(True)
    if rcParams['tk.pythoninspect']:
        os.environ['PYTHONINSPECT'] = '1'
    if show._needmain:
        Tk.mainloop()
        show._needmain = False
show._needmain = True

def new_figure_manager(num, *args, **kwargs):
    """
    Create a new figure manager instance
    """
    _focus = windowing.FocusManager()
    FigureClass = kwargs.pop('FigureClass', Figure)
    figure = FigureClass(*args, **kwargs)
    window = Tk.Tk()
    canvas = FigureCanvasTkAgg(figure, master=window)
    figManager = FigureManagerTkAgg(canvas, num, window)
    if matplotlib.is_interactive():
        figManager.show()
    return figManager

class FigureCanvasTkAgg(FigureCanvasAgg):
    keyvald = {65507 : 'control',
               65505 : 'shift',
               65513 : 'alt',
               65508 : 'control',
               65506 : 'shift',
               65514 : 'alt',
               65361 : 'left',
               65362 : 'up',
               65363 : 'right',
               65364 : 'down',
               65307 : 'escape',
               65470 : 'f1',
               65471 : 'f2',
               65472 : 'f3',
               65473 : 'f4',
               65474 : 'f5',
               65475 : 'f6',
               65476 : 'f7',
               65477 : 'f8',
               65478 : 'f9',
               65479 : 'f10',
               65480 : 'f11',
               65481 : 'f12',
               65300 : 'scroll_lock',
               65299 : 'break',
               65288 : 'backspace',
               65293 : 'enter',
               65379 : 'insert',
               65535 : 'delete',
               65360 : 'home',
               65367 : 'end',
               65365 : 'pageup',
               65366 : 'pagedown',
               65438 : '0',
               65436 : '1',
               65433 : '2',
               65435 : '3',
               65430 : '4',
               65437 : '5',
               65432 : '6',
               65429 : '7',
               65431 : '8',
               65434 : '9',
               65451 : '+',
               65453 : '-',
               65450 : '*',
               65455 : '/',
               65439 : 'dec',
               65421 : 'enter',
               }

    def __init__(self, figure, master=None, resize_callback=None):
        FigureCanvasAgg.__init__(self, figure)
        self._idle = True
        t1,t2,w,h = self.figure.bbox.bounds
        w, h = int(w), int(h)
        self._tkcanvas = Tk.Canvas(
            master=master, width=w, height=h, borderwidth=4)
        self._tkphoto = Tk.PhotoImage(
            master=self._tkcanvas, width=w, height=h)
        self._tkcanvas.create_image(w/2, h/2, image=self._tkphoto)
        self._resize_callback = resize_callback
        self._tkcanvas.bind("<Configure>", self.resize)
        self._tkcanvas.bind("<Key>", self.key_press)
        self._tkcanvas.bind("<Motion>", self.motion_notify_event)
        self._tkcanvas.bind("<KeyRelease>", self.key_release)
        for name in "<Button-1>", "<Button-2>", "<Button-3>":
            self._tkcanvas.bind(name, self.button_press_event)
        for name in "<ButtonRelease-1>", "<ButtonRelease-2>", "<ButtonRelease-3>":
            self._tkcanvas.bind(name, self.button_release_event)

        # Mouse wheel on Linux generates button 4/5 events
        for name in "<Button-4>", "<Button-5>":
            self._tkcanvas.bind(name, self.scroll_event)
        # Mouse wheel for windows goes to the window with the focus.
        # Since the canvas won't usually have the focus, bind the
        # event to the window containing the canvas instead.
        # See http://wiki.tcl.tk/3893 (mousewheel) for details
        root = self._tkcanvas.winfo_toplevel()
        root.bind("<MouseWheel>", self.scroll_event_windows)

        self._master = master
        self._tkcanvas.focus_set()

        # a dict from func-> cbook.Scheduler threads
        self.sourced = dict()

        # call the idle handler
        def on_idle(*ignore):
            self.idle_event()
            return True

        # disable until you figure out how to handle threads and interrupts
        #t = cbook.Idle(on_idle)
        #self._tkcanvas.after_idle(lambda *ignore: t.start())

    def resize(self, event):
        width, height = event.width, event.height
        if self._resize_callback is not None:
            self._resize_callback(event)

        # compute desired figure size in inches
	dpival = self.figure.dpi
        winch = width/dpival
        hinch = height/dpival
        self.figure.set_size_inches(winch, hinch)

        self._tkcanvas.delete(self._tkphoto)
        self._tkphoto = Tk.PhotoImage(
            master=self._tkcanvas, width=width, height=height)
        self._tkcanvas.create_image(width/2,height/2,image=self._tkphoto)
        self.resize_event()
        self.show()

    def draw(self):
        FigureCanvasAgg.draw(self)
        tkagg.blit(self._tkphoto, self.renderer._renderer, colormode=2)
        self._master.update_idletasks()

    def blit(self, bbox=None):
        tkagg.blit(self._tkphoto, self.renderer._renderer, bbox=bbox, colormode=2)
        self._master.update_idletasks()

    show = draw

    def draw_idle(self):
        'update drawing area only if idle'
        d = self._idle
        self._idle = False
        def idle_draw(*args):
            self.draw()
            self._idle = True

        if d: self._tkcanvas.after_idle(idle_draw)

    def get_tk_widget(self):
        """returns the Tk widget used to implement FigureCanvasTkAgg.
        Although the initial implementation uses a Tk canvas,  this routine
        is intended to hide that fact.
        """
        return self._tkcanvas

    def motion_notify_event(self, event):
        x = event.x
        # flipy so y=0 is bottom of canvas
        y = self.figure.bbox.height - event.y
        FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=event)

    def button_press_event(self, event):
        x = event.x
        # flipy so y=0 is bottom of canvas
        y = self.figure.bbox.height - event.y
        num = getattr(event, 'num', None)

        if sys.platform=='darwin':
            # 2 and 3 were reversed on the OSX platform I
            # tested under tkagg
            if   num==2: num=3
            elif num==3: num=2

        FigureCanvasBase.button_press_event(self, x, y, num, guiEvent=event)

    def button_release_event(self, event):
        x = event.x
        # flipy so y=0 is bottom of canvas
        y = self.figure.bbox.height - event.y

        num = getattr(event, 'num', None)

        if sys.platform=='darwin':
            # 2 and 3 were reversed on the OSX platform I
            # tested under tkagg
            if   num==2: num=3
            elif num==3: num=2

        FigureCanvasBase.button_release_event(self, x, y, num, guiEvent=event)

    def scroll_event(self, event):
        x = event.x
        y = self.figure.bbox.height - event.y
        num = getattr(event, 'num', None)
        if   num==4: step = -1
        elif num==5: step = +1
        else:        step =  0

        FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)

    def scroll_event_windows(self, event):
        """MouseWheel event processor"""
        # need to find the window that contains the mouse
        w = event.widget.winfo_containing(event.x_root, event.y_root)
        if w == self._tkcanvas:
            x = event.x_root - w.winfo_rootx()
            y = event.y_root - w.winfo_rooty()
            y = self.figure.bbox.height - y
            step = event.delta/120.
            FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=event)

    def _get_key(self, event):
        val = event.keysym_num
        if val in self.keyvald:
            key = self.keyvald[val]
        elif val<256:
            key = chr(val)
        else:
            key = None
        return key

    def key_press(self, event):
        key = self._get_key(event)
        FigureCanvasBase.key_press_event(self, key, guiEvent=event)

    def key_release(self, event):
        key = self._get_key(event)
        FigureCanvasBase.key_release_event(self, key, guiEvent=event)

    def flush_events(self):
        self._master.update()

    def start_event_loop(self,timeout):
        FigureCanvasBase.start_event_loop_default(self,timeout)
    start_event_loop.__doc__=FigureCanvasBase.start_event_loop_default.__doc__

    def stop_event_loop(self):
        FigureCanvasBase.stop_event_loop_default(self)
    stop_event_loop.__doc__=FigureCanvasBase.stop_event_loop_default.__doc__

class FigureManagerTkAgg(FigureManagerBase):
    """
    Public attributes

    canvas      : The FigureCanvas instance
    num         : The Figure number
    toolbar     : The tk.Toolbar
    window      : The tk.Window
    """
    def __init__(self, canvas, num, window):
        FigureManagerBase.__init__(self, canvas, num)
        self.window = window
        self.window.withdraw()
        self.window.wm_title("Figure %d" % num)
        self.canvas = canvas
        self._num =  num
        t1,t2,w,h = canvas.figure.bbox.bounds
        w, h = int(w), int(h)
        self.window.minsize(int(w*3/4),int(h*3/4))
        if matplotlib.rcParams['toolbar']=='classic':
            self.toolbar = NavigationToolbar( canvas, self.window )
        elif matplotlib.rcParams['toolbar']=='toolbar2':
            self.toolbar = NavigationToolbar2TkAgg( canvas, self.window )
        else:
            self.toolbar = None
        if self.toolbar is not None:
            self.toolbar.update()
        self.canvas._tkcanvas.pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
        self._shown = False

        def notify_axes_change(fig):
            'this will be called whenever the current axes is changed'
            if self.toolbar != None: self.toolbar.update()
        self.canvas.figure.add_axobserver(notify_axes_change)

        # attach a show method to the figure for pylab ease of use
        self.canvas.figure.show = lambda *args: self.show()

    def resize(self, event):
        width, height = event.width, event.height
        self.toolbar.configure(width=width) # , height=height)

    def show(self):
        """
        this function doesn't segfault but causes the
        PyEval_RestoreThread: NULL state bug on win32
        """

        def destroy(*args):
            self.window = None
            Gcf.destroy(self._num)

        if not self._shown: self.canvas._tkcanvas.bind("<Destroy>", destroy)
        _focus = windowing.FocusManager()
        if not self._shown:
            self.window.deiconify()
            # anim.py requires this
            if sys.platform=='win32' : self.window.update()
        else:
            self.canvas.draw()
        self._shown = True

    def destroy(self, *args):
        if Gcf.get_num_fig_managers()==0 and not matplotlib.is_interactive():
            if self.window is not None:
                self.window.quit()
        if self.window is not None:
            #self.toolbar.destroy()
            self.window.destroy()

            pass
        self.window = None

    def set_window_title(self, title):
        self.window.wm_title(title)

class AxisMenu:
    def __init__(self, master, naxes):
        self._master = master
        self._naxes = naxes
        self._mbar = Tk.Frame(master=master, relief=Tk.RAISED, borderwidth=2)
        self._mbar.pack(side=Tk.LEFT)
        self._mbutton = Tk.Menubutton(
            master=self._mbar, text="Axes", underline=0)
        self._mbutton.pack(side=Tk.LEFT, padx="2m")
        self._mbutton.menu = Tk.Menu(self._mbutton)
        self._mbutton.menu.add_command(
            label="Select All", command=self.select_all)
        self._mbutton.menu.add_command(
            label="Invert All", command=self.invert_all)
        self._axis_var = []
        self._checkbutton = []
        for i in range(naxes):
            self._axis_var.append(Tk.IntVar())
            self._axis_var[i].set(1)
            self._checkbutton.append(self._mbutton.menu.add_checkbutton(
                label = "Axis %d" % (i+1),
                variable=self._axis_var[i],
                command=self.set_active))
            self._mbutton.menu.invoke(self._mbutton.menu.index("Select All"))
        self._mbutton['menu'] = self._mbutton.menu
        self._mbar.tk_menuBar(self._mbutton)
        self.set_active()

    def adjust(self, naxes):
        if self._naxes < naxes:
            for i in range(self._naxes, naxes):
                self._axis_var.append(Tk.IntVar())
                self._axis_var[i].set(1)
                self._checkbutton.append( self._mbutton.menu.add_checkbutton(
                    label = "Axis %d" % (i+1),
                    variable=self._axis_var[i],
                    command=self.set_active))
        elif self._naxes > naxes:
            for i in range(self._naxes-1, naxes-1, -1):
                del self._axis_var[i]
                self._mbutton.menu.forget(self._checkbutton[i])
                del self._checkbutton[i]
        self._naxes = naxes
        self.set_active()

    def get_indices(self):
        a = [i for i in range(len(self._axis_var)) if self._axis_var[i].get()]
        return a

    def set_active(self):
        self._master.set_active(self.get_indices())

    def invert_all(self):
        for a in self._axis_var:
            a.set(not a.get())
        self.set_active()

    def select_all(self):
        for a in self._axis_var:
            a.set(1)
        self.set_active()

class NavigationToolbar(Tk.Frame):
    """
    Public attriubutes

      canvas   - the FigureCanvas  (gtk.DrawingArea)
      win   - the gtk.Window

    """
    def _Button(self, text, file, command):
        file = os.path.join(rcParams['datapath'], 'images', file)
        im = Tk.PhotoImage(master=self, file=file)
        b = Tk.Button(
            master=self, text=text, padx=2, pady=2, image=im, command=command)
        b._ntimage = im
        b.pack(side=Tk.LEFT)
        return b

    def __init__(self, canvas, window):
        self.canvas = canvas
        self.window = window

        xmin, xmax = canvas.figure.bbox.intervalx
        height, width = 50, xmax-xmin
        Tk.Frame.__init__(self, master=self.window,
                          width=width, height=height,
                          borderwidth=2)

        self.update()  # Make axes menu

        self.bLeft = self._Button(
            text="Left", file="stock_left.ppm",
            command=lambda x=-1: self.panx(x))

        self.bRight = self._Button(
            text="Right", file="stock_right.ppm",
            command=lambda x=1: self.panx(x))

        self.bZoomInX = self._Button(
            text="ZoomInX",file="stock_zoom-in.ppm",
            command=lambda x=1: self.zoomx(x))

        self.bZoomOutX = self._Button(
            text="ZoomOutX", file="stock_zoom-out.ppm",
            command=lambda x=-1: self.zoomx(x))

        self.bUp = self._Button(
            text="Up", file="stock_up.ppm",
            command=lambda y=1: self.pany(y))

        self.bDown = self._Button(
            text="Down", file="stock_down.ppm",
            command=lambda y=-1: self.pany(y))

        self.bZoomInY = self._Button(
            text="ZoomInY", file="stock_zoom-in.ppm",
            command=lambda y=1: self.zoomy(y))

        self.bZoomOutY = self._Button(
            text="ZoomOutY",file="stock_zoom-out.ppm",
            command=lambda y=-1: self.zoomy(y))

        self.bSave = self._Button(
            text="Save", file="stock_save_as.ppm",
            command=self.save_figure)

        self.pack(side=Tk.BOTTOM, fill=Tk.X)

    def set_active(self, ind):
        self._ind = ind
        self._active = [ self._axes[i] for i in self._ind ]

    def panx(self, direction):
        for a in self._active:
            a.xaxis.pan(direction)
        self.canvas.draw()

    def pany(self, direction):
        for a in self._active:
            a.yaxis.pan(direction)
        self.canvas.draw()

    def zoomx(self, direction):

        for a in self._active:
            a.xaxis.zoom(direction)
        self.canvas.draw()

    def zoomy(self, direction):

        for a in self._active:
            a.yaxis.zoom(direction)
        self.canvas.draw()

    def save_figure(self):
        fs = FileDialog.SaveFileDialog(master=self.window,
                                       title='Save the figure')
        try:
            self.lastDir
        except AttributeError:
            self.lastDir = os.curdir

        fname = fs.go(dir_or_file=self.lastDir) # , pattern="*.png")
        if fname is None: # Cancel
            return

        self.lastDir = os.path.dirname(fname)
        try:
            self.canvas.print_figure(fname)
        except IOError, msg:
            err = '\n'.join(map(str, msg))
            msg = 'Failed to save %s: Error msg was\n\n%s' % (
                fname, err)
            error_msg_tkpaint(msg)

    def update(self):
        _focus = windowing.FocusManager()
        self._axes = self.canvas.figure.axes
        naxes = len(self._axes)
        if not hasattr(self, "omenu"):
            self.set_active(range(naxes))
            self.omenu = AxisMenu(master=self, naxes=naxes)
        else:
            self.omenu.adjust(naxes)

class NavigationToolbar2TkAgg(NavigationToolbar2, Tk.Frame):
    """
    Public attriubutes

      canvas   - the FigureCanvas  (gtk.DrawingArea)
      win   - the gtk.Window
    """
    def __init__(self, canvas, window):
        self.canvas = canvas
        self.window = window
        self._idle = True
        #Tk.Frame.__init__(self, master=self.canvas._tkcanvas)
        NavigationToolbar2.__init__(self, canvas)

    def destroy(self, *args):
        del self.message
        Tk.Frame.destroy(self, *args)

    def set_message(self, s):
        self.message.set(s)

    def draw_rubberband(self, event, x0, y0, x1, y1):
        height = self.canvas.figure.bbox.height
        y0 =  height-y0
        y1 =  height-y1
        try: self.lastrect
        except AttributeError: pass
        else: self.canvas._tkcanvas.delete(self.lastrect)
        self.lastrect = self.canvas._tkcanvas.create_rectangle(x0, y0, x1, y1)

        #self.canvas.draw()

    def release(self, event):
        try: self.lastrect
        except AttributeError: pass
        else:
            self.canvas._tkcanvas.delete(self.lastrect)
            del self.lastrect

    def set_cursor(self, cursor):
        self.window.configure(cursor=cursord[cursor])

    def _Button(self, text, file, command):
        file = os.path.join(rcParams['datapath'], 'images', file)
        im = Tk.PhotoImage(master=self, file=file)
        b = Tk.Button(
            master=self, text=text, padx=2, pady=2, image=im, command=command)
        b._ntimage = im
        b.pack(side=Tk.LEFT)
        return b

    def _init_toolbar(self):
        xmin, xmax = self.canvas.figure.bbox.intervalx
        height, width = 50, xmax-xmin
        Tk.Frame.__init__(self, master=self.window,
                          width=width, height=height,
                          borderwidth=2)

        self.update()  # Make axes menu

        self.bHome = self._Button( text="Home", file="home.ppm",
                                   command=self.home)

        self.bBack = self._Button( text="Back", file="back.ppm",
                                   command = self.back)

        self.bForward = self._Button(text="Forward", file="forward.ppm",
                                     command = self.forward)

        self.bPan = self._Button( text="Pan", file="move.ppm",
                                  command = self.pan)

        self.bZoom = self._Button( text="Zoom",
                                   file="zoom_to_rect.ppm",
                                   command = self.zoom)

        self.bsubplot = self._Button( text="Configure Subplots", file="subplots.ppm",
                                   command = self.configure_subplots)

        self.bsave = self._Button( text="Save", file="filesave.ppm",
                                   command = self.save_figure)
        self.message = Tk.StringVar(master=self)
        self._message_label = Tk.Label(master=self, textvariable=self.message)
        self._message_label.pack(side=Tk.RIGHT)
        self.pack(side=Tk.BOTTOM, fill=Tk.X)

    def configure_subplots(self):
        toolfig = Figure(figsize=(6,3))
        window = Tk.Tk()
        canvas = FigureCanvasTkAgg(toolfig, master=window)
        toolfig.subplots_adjust(top=0.9)
        tool =  SubplotTool(self.canvas.figure, toolfig)
        canvas.show()
        canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)

    def save_figure(self):
        from tkFileDialog import asksaveasfilename
        from tkMessageBox import showerror
        filetypes = self.canvas.get_supported_filetypes().copy()
        default_filetype = self.canvas.get_default_filetype()

        # Tk doesn't provide a way to choose a default filetype,
        # so we just have to put it first
        default_filetype_name = filetypes[default_filetype]
        del filetypes[default_filetype]

        sorted_filetypes = filetypes.items()
        sorted_filetypes.sort()
        sorted_filetypes.insert(0, (default_filetype, default_filetype_name))

        tk_filetypes = [
            (name, '*.%s' % ext) for (ext, name) in sorted_filetypes]

        fname = asksaveasfilename(
            master=self.window,
            title='Save the figure',
            filetypes = tk_filetypes,
            defaultextension = self.canvas.get_default_filetype()
            )

        if fname == "" or fname == ():
            return
        else:
            try:
                # This method will handle the delegation to the correct type
                self.canvas.print_figure(fname)
            except Exception, e:
                showerror("Error saving file", str(e))

    def set_active(self, ind):
        self._ind = ind
        self._active = [ self._axes[i] for i in self._ind ]

    def update(self):
        _focus = windowing.FocusManager()
        self._axes = self.canvas.figure.axes
        naxes = len(self._axes)
        #if not hasattr(self, "omenu"):
        #    self.set_active(range(naxes))
        #    self.omenu = AxisMenu(master=self, naxes=naxes)
        #else:
        #    self.omenu.adjust(naxes)
        NavigationToolbar2.update(self)

    def dynamic_update(self):
        'update drawing area only if idle'
        # legacy method; new method is canvas.draw_idle
        self.canvas.draw_idle()

FigureManager = FigureManagerTkAgg


#!/usr/bin/env python
"""Entry point module for the command-line
   interface. The kmos executable should be
   on the program path, import this modules
   main function and run it.

   To call kmos command as you would from the shell,
   use ::

       kmos.cli.main('...')

   Every command can be shortened as long as it is non-ambiguous, e.g. ::

    kmos ex <xml-file>

   instead of ::

    kmos export <xml-file>

   etc.

"""

#    Copyright 2009-2013 Max J. Hoffmann (mjhoffmann@gmail.com)
#    This file is part of kmos.
#
#    kmos 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.
#
#    kmos 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 kmos.  If not, see <http://www.gnu.org/licenses/>.

from __future__ import with_statement
import os
import shutil

usage = {}
usage['all'] = """kmos help all
    Display documentation for all commands.
                """
usage['benchmark'] = """kmos benchmark
    Run 1 mio. kMC steps on model in current directory
    and report runtime.
                     """

usage['build'] = """kmos build
    Build kmc_model.%s from *f90 files in the
    current directory.

    Additional Parameters ::
        -d/--debug
            Turn on assertion statements in F90 code

        -n/--no-compiler-optimization
            Do not send optimizing flags to compiler.
                 """ % ('pyd' if os.name == 'nt' else 'so')

usage['help'] = """kmos help <command>
    Print usage information for the given command.
                """

usage['export'] = """kmos export <xml-file> [<export-path>]
    Take a kmos xml-file and export all generated
    source code to the export-path. There try to
    build the kmc_model.%s.

    Additional Parameters ::

        -s/--source-only
            Export source only and don't build binary

        -b/--backend (local_smart|lat_int)
            Choose backend. Default is "local_smart".
            lat_int is EXPERIMENTAL and not made
            for production, yet.

        -d/--debug
            Turn on assertion statements in F90 code.
            (Only active in compile step)

           --acf
            Build the modules base_acf.f90 and proclist_acf.f90. Default is false.
            This both modules contain functions to calculate ACF (autocorrelation function) and MSD (mean squared displacement).

        -n/--no-compiler-optimization
            Do not send optimizing flags to compiler.
                    """ % ('pyd' if os.name == 'nt' else 'so')

usage['settings-export'] = """kmos settings-export <xml-file> [<export-path>]
    Take a kmos xml-file and export kmc_settings.py
    to the export-path.
                    """

usage['edit'] = """kmos edit <xml-file>
    Open the kmos xml-file in a GUI to edit
    the model.
                """

usage['import'] = """kmos import <xml-file>
    Take a kmos xml-file and open an ipython shell
    with the project_tree imported as pt.
                  """
usage['rebuild'] = """kmos rebuild
    Export code and rebuild binary module from XML
    information included in kmc_settings.py in
    current directory.

    Additional Parameters ::
        -d/--debug
            Turn on assertion statements in F90 code
                    """

usage['shell'] = """kmos shell
    Open an interactive shell and create a KMC_Model in it
               run == shell
               """
usage['run'] = """kmos run
    Open an interactive shell and create a KMC_Model in it
               run == shell
               """

usage['version'] = """kmos version
    Print version number and exit.
                   """

usage['view'] = """kmos view
    Take a kmc_model.%s and kmc_settings.py in the
    same directory and start to simulate the
    model visually.

    Additional Parameters ::
        -v/--steps-per-frame <number>
            Number of steps per frame

                 """ % ('pyd' if os.name == 'nt' else 'so')

usage['xml'] = """kmos xml
    Print xml representation of model to stdout
               """

def get_options(args=None, get_parser=False):
    import optparse
    import os
    from glob import glob
    import kmos

    parser = optparse.OptionParser(
        'Usage: %prog [help] ('
        + '|'.join(sorted(usage.keys()))
        + ') [options]',
        version=kmos.__version__)

    parser.add_option('-s', '--source-only',
                      dest='source_only',
                      action='store_true',
                      default=False)

    parser.add_option('-p', '--path-to-f2py',
                      dest='path_to_f2py',
                      default='f2py')

    parser.add_option('-b', '--backend',
                      dest='backend',
                      default='local_smart')
    parser.add_option('-a', '--avoid-default-state',
                      dest='avoid_default_state',
                      action='store_true',
                      default=False,
                      )

    parser.add_option('-v', '--steps-per-frame',
                      dest='steps_per_frame',
                      type='int',
                      default='50000')

    parser.add_option('-d', '--debug',
                      default=False,
                      dest='debug',
                      action='store_true')

    parser.add_option('-n', '--no-compiler-optimization',
                      default=False,
                      dest='no_optimize',
                      action='store_true')

    parser.add_option('-o', '--overwrite',
                      default=False,
                      action='store_true')

    parser.add_option('-l', '--variable-length',
                      dest='variable_length',
                      default=95,
                      type='int')

    parser.add_option('-c', '--catmap',
                      default=False,
                      action='store_true')

    parser.add_option('--acf',
                      dest='acf',
                      action='store_true',
                      default=False,
                      )

    try:
        from numpy.distutils.fcompiler import get_default_fcompiler
        from numpy.distutils import log
        log.set_verbosity(-1, True)
        fcompiler = get_default_fcompiler()
    except:
        fcompiler = 'gfortran'

    parser.add_option('-f', '--fcompiler',
                      dest='fcompiler',
                      default=os.environ.get('F2PY_FCOMPILER', fcompiler))

    if args is not None:
        options, args = parser.parse_args(args.split())
    else:
        options, args = parser.parse_args()
    if len(args) < 1:
        parser.error('Command expected')
    if get_parser:
        return options, args, parser
    else:
        return options, args

def match_keys(arg, usage, parser):
    """Try to match part of a command against
       the set of commands from usage. Throws
       an error if not successful.

    """
    possible_args = [key for key in usage if key.startswith(arg)]
    if len(possible_args) == 0:
        parser.error('Command "%s" not understood.' % arg)
    elif len(possible_args) > 1:
        parser.error(('Command "%s" ambiguous.\n'
                      'Could be one of %s\n\n') % (arg, possible_args))
    else:
        return possible_args[0]

def main(args=None):
    """The CLI main entry point function.

    The optional argument args, can be used to
    directly supply command line argument like

    $ kmos <args>

    otherwise args will be taken from STDIN.

    """

    from glob import glob

    options, args, parser = get_options(args, get_parser=True)

    global model, pt, np, cm_model

    if not args[0] in usage.keys():
        args[0] = match_keys(args[0], usage, parser)

    if args[0] == 'benchmark':
        from sys import path
        path.append(os.path.abspath(os.curdir))
        nsteps = 1000000
        from time import time
        from kmos.run import KMC_Model
        model = KMC_Model(print_rates=False, banner=False)
        time0 = time()
        try:
            model.proclist.do_kmc_steps(nsteps)
        except:  # kmos < 0.3 had no model.proclist.do_kmc_steps
            model.do_steps(nsteps)

        needed_time = time() - time0
        print('Using the [%s] backend.' % model.get_backend())
        print('%s steps took %.2f seconds' % (nsteps, needed_time))
        print('Or %.2e steps/s' % (1e6 / needed_time))
        model.deallocate()
    elif args[0] == 'build':
        from kmos.utils import build
        build(options)
    elif args[0] == 'edit':
        from kmos import gui
        gui.main()
    elif args[0] == 'settings-export':
        import kmos.types
        import kmos.io
        from kmos.io import ProcListWriter

        if len(args) < 2:
            parser.error('XML file and export path expected.')
        if len(args) < 3:
            out_dir = '%s_%s' % (os.path.splitext(args[1])[0], options.backend)
            print('No export path provided. Exporting to %s' % out_dir)
            args.append(out_dir)

        xml_file = args[1]
        export_dir = args[2]
        project = kmos.types.Project()
        project.import_file(xml_file)

        writer = ProcListWriter(project, export_dir)
        writer.write_settings()

    elif args[0] == 'export':
        import kmos.types
        import kmos.io
        from kmos.utils import build
        if len(args) < 2:
            parser.error('XML file and export path expected.')
        if len(args) < 3:
            out_dir = '%s_%s' % (os.path.splitext(args[1])[0], options.backend)

            print('No export path provided. Exporting to %s' % out_dir)
            args.append(out_dir)

        xml_file = args[1]
        export_dir = os.path.join(args[2], 'src')

        project = kmos.types.Project()
        project.import_file(xml_file)

        project.shorten_names(max_length=options.variable_length)

        kmos.io.export_source(project,
                              export_dir,
                              options=options)

        if ((os.name == 'posix'
           and os.uname()[0] in ['Linux', 'Darwin'])
           or os.name == 'nt') \
           and not options.source_only:
            os.chdir(export_dir)
            build(options)
            for out in glob('kmc_*'):
                if os.path.exists('../%s' % out) :
                    if options.overwrite :
                        overwrite = 'y'
                    else:
                        overwrite = raw_input(('Should I overwrite existing %s ?'
                                               '[y/N]  ') % out).lower()
                    if overwrite.startswith('y') :
                        print('Overwriting {out}'.format(**locals()))
                        os.remove('../%s' % out)
                        shutil.move(out, '..')
                    else :
                        print('Skipping {out}'.format(**locals()))
                else:
                    shutil.move(out, '..')

    elif args[0] == 'settings-export':
        import kmos.io
        pt = kmos.io.import_file(args[1])
        if len(args) < 3:
            out_dir = os.path.splitext(args[1])[0]
            print('No export path provided. Exporting kmc_settings.py to %s'
                  % out_dir)
            args.append(out_dir)

        if not os.path.exists(args[2]):
            os.mkdir(args[2])
        elif not os.path.isdir(args[2]):
            raise UserWarning("Cannot overwrite %s; Exiting;" % args[2])
        writer = kmos.io.ProcListWriter(pt, args[2])
        writer.write_settings()

    elif args[0] == 'help':
        if len(args) < 2:
            parser.error('Which help do you  want?')
        if args[1] == 'all':
            for command in sorted(usage):
                print(usage[command])
        elif args[1] in usage:
            print('Usage: %s\n' % usage[args[1]])
        else:
            arg = match_keys(args[1], usage, parser)
            print('Usage: %s\n' % usage[arg])

    elif args[0] == 'import':
        import kmos.io
        if not len(args) >= 2:
            raise UserWarning('XML file name expected.')
        pt = kmos.io.import_xml_file(args[1])
        if len(args) == 2:
            sh(banner='Note: pt = kmos.io.import_xml(\'%s\')' % args[1])
        elif len(args) == 3: # if optional 3rd argument is given, store model there and exit
            pt.save(args[2])

    elif args[0] == 'rebuild':
        from time import sleep
        print('Will rebuild model from kmc_settings.py in current directory')
        print('Please do not interrupt,'
              ' build process, as you will most likely')
        print('loose the current model files.')
        sleep(2.)
        from sys import path
        path.append(os.path.abspath(os.curdir))
        from tempfile import mktemp
        if not os.path.exists('kmc_model.so') \
           and not os.path.exists('kmc_model.pyd'):
            raise Exception('No kmc_model.so found.')
        if not os.path.exists('kmc_settings.py'):
            raise Exception('No kmc_settings.py found.')

        from kmos.run import KMC_Model

        model = KMC_Model(print_rates=False, banner=False)
        tempfile = mktemp()
        f = file(tempfile, 'w')
        f.write(model.xml())
        f.close()

        for kmc_model in glob('kmc_model.*'):
            os.remove(kmc_model)
        os.remove('kmc_settings.py')
        main('export %s -b %s .' % (tempfile, options.backend))
        os.remove(tempfile)
        model.deallocate()

    elif args[0] in ['run', 'shell']:
        from sys import path
        path.append(os.path.abspath(os.curdir))
        from kmos.run import KMC_Model

        # useful to have in interactive mode
        import numpy as np
        try:
            from matplotlib import pyplot as plt
        except:
            plt = None

        if options.catmap:
            import catmap
            import catmap.cli.kmc_runner
            seed = catmap.cli.kmc_runner.get_seed_from_path('.')
            cm_model = catmap.ReactionModel(setup_file='{seed}.mkm'.format(**locals()))
            catmap_message = '\nSide-loaded catmap_model {seed}.mkm into cm_model = ReactionModel(setup_file="{seed}.mkm")'.format(**locals())
        else:
            catmap_message = ''

        try:
            model = KMC_Model(print_rates=False)
        except:
            print("Warning: could not import kmc_model!"
                  " Please make sure you are in the right directory")
        sh(banner='Note: model = KMC_Model(print_rates=False){catmap_message}'.format(**locals()))
        try:
            model.deallocate()
        except:
            print("Warning: could not deallocate model. Was is allocated?")

    elif args[0] == 'version':
        from kmos import VERSION
        print(VERSION)

    elif args[0] == 'view':
        from sys import path
        path.append(os.path.abspath(os.curdir))
        from kmos import view
        view.main(steps_per_frame=options.steps_per_frame)

    elif args[0] == 'xml':
        from sys import path
        path.append(os.path.abspath(os.curdir))
        from kmos.run import KMC_Model
        model = KMC_Model(banner=False, print_rates=False)
        print(model.xml())

    else:
        parser.error('Command "%s" not understood.' % args[0])

def sh(banner):
    """Wrapper around interactive ipython shell
    that factors out ipython version depencies.

    """

    from distutils.version import LooseVersion
    import IPython
    if hasattr(IPython, 'release'):
        try:
            from IPython.terminal.embed import InteractiveShellEmbed
            InteractiveShellEmbed(banner1=banner)()

        except ImportError:
            try:
                from IPython.frontend.terminal.embed \
                    import InteractiveShellEmbed
                InteractiveShellEmbed(banner1=banner)()

            except ImportError:
                from IPython.Shell import IPShellEmbed
                IPShellEmbed(banner=banner)()
    else:
        from IPython.Shell import IPShellEmbed
        IPShellEmbed(banner=banner)()