#------------------------------------------------------------------------------
# Copyright (c) 2005, Enthought, Inc.
# All rights reserved.
#
# This software is provided without warranty under the terms of the BSD
# license included in enthought/LICENSE.txt and may be redistributed only
# under the conditions described in the aforementioned license.  The license
# is also available online at http://www.enthought.com/licenses/BSD.txt
# Thanks for using Enthought open source!
#
# Author: Enthought, Inc.
# Description: <Enthought pyface package component>
#------------------------------------------------------------------------------
""" An interactive Python shell. """

import os
import __builtin__

from wx.py.shell import Shell as PyShellBase

import wx

# Enthought library imports.
from enthought.traits.api import Event, Any
from enthought.util.clean_strings import python_name
from enthought.util.wx.drag_and_drop import PythonDropTarget

# Local imports.
from key_pressed_event import KeyPressedEvent
from widget import Widget


class PyShell(PyShellBase):

    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
                 introText='', locals=None, InterpClass=None, *args, **kwds):
        self.handlers=[]

        # save a reference to the original raw_input() function since
        # wx.py.shell dosent reassign it back to the original on destruction
        self.raw_input = __builtin__.raw_input

        super(PyShell,self).__init__(parent, id, pos, size, style, introText,
                                     locals, InterpClass, *args, **kwds)

    def hidden_push(self, command):
        """ Send a command to the interpreter for execution without adding
            output to the display.
        """
        wx.BeginBusyCursor()
        try:
            self.waiting = True
            self.more = self.interp.push(command)
            self.waiting = False
            if not self.more:
                self.addHistory(command.rstrip())
                for handler in self.handlers:
                    handler()
        finally:
            # This needs to be out here to make this works with
            # enthought.util.refresh.refresh()
            wx.EndBusyCursor()



    def push(self, command):
        """Send command to the interpreter for execution."""
        self.write(os.linesep)
        self.hidden_push(command)
        self.prompt()


    def Destroy(self):
        """Cleanup before destroying the control...namely, return std I/O and
        the raw_input() function back to their rightful owners!
        """
        self.redirectStdout(False)
        self.redirectStderr(False)
        self.redirectStdin(False)
        __builtin__.raw_input = self.raw_input
        self.destroy()
        super(PyShellBase, self).Destroy()


class PythonShell(Widget):
    """ An interactive Python shell. """

    # fixme: Hack for demo.
    command_executed = Event

    #### 'PythonShell' interface ##############################################

    # A key has been pressed.
    key_pressed = Event(KeyPressedEvent)
    
    # The 'locals' dictionary that the shell interpreter should use (should be
    # a dictionary or None, which means use __main__.__dict__):
    locals = Any

    ###########################################################################
    # 'object' interface.
    ###########################################################################

    def __init__(self, parent, **traits):
        """ Creates a new pager. """

        # Base class constructor.
        super(PythonShell, self).__init__(**traits)

        # Create the toolkit-specific control that represents the widget.
        self.control = self._create_control(parent)

        # Set up to be notified whenever a Python statement is executed:
        self.control.handlers.append(self._on_command_executed)

        return

    ###########################################################################
    # 'Shell' interface.
    ###########################################################################

    def bind(self, name, value):
        """ Binds a name to a value in the interpreter's namespace. """

        self.control.interp.locals[name] = value

        return

    def execute_command(self, command, hidden=True):
        """ Execute a command in the interpreter.

        If 'hidden' is True then nothing is shown in the shell - not even
        a blank line.

        """

        if hidden:
            result = self.control.hidden_push(command)

        else:
            result = self.control.push(command)

        return result

    ###########################################################################
    # Private interface.
    ###########################################################################

    def _create_control(self, parent):
        """ Creates the toolkit-specific control for the widget. """

        shell = PyShell(parent, -1, locals = self.locals)

        # Listen for key press events.
        wx.EVT_CHAR(shell, self._on_char)

        # Enable the shell as a drag and drop target.
        shell.SetDropTarget(PythonDropTarget(self))

        return shell

    def _python_name(self, name):
        """ Attempt to make a valid Python identifier out of a name. """
        return python_name(name)

    def _is_identifier(self, name):
        """ Returns True if a name is a valid Python identifier. """

        # fixme: Is there a built-in way to do this?
        try:
            is_identifier = eval(name, {name : True})

        except:
            is_identifier = False

        return is_identifier


    ###########################################################################
    # 'PythonDropTarget' handler interface
    ###########################################################################

    def on_drop(self, x, y, obj, default_drag_result):
        """ Called when a drop occurs on the shell. """

        # If we can't create a valid Python identifier for the name of an
        # object we use this instead.
        name = 'dragged'

        if hasattr(obj, 'name') \
           and type(obj.name) is str and len(obj.name) > 0:
            python_name = self._python_name(obj.name)

            # Make sure that the name is actually a valid Python identifier.
            if self._is_identifier(python_name):
                name = python_name

        self.control.interp.locals[name] = obj
        self.control.run(name)
        self.control.SetFocus()

        # We always copy into the shell since we don't want the data
        # removed from the source
        return wx.DragCopy

    def on_drag_over(self, x, y, obj, default_drag_result):
        """ Always returns wx.DragCopy to indicate we will be doing a copy."""
        return wx.DragCopy

    ##### trait event handlers ################################################

    def _on_command_executed(self):
        """ Called when a command has been executed in the shell. """

        self.command_executed = self

        return

    #### wx event handlers ####################################################

    def _on_char(self, event):
        """ Called whenever a change is made to the text of the document. """

        self.key_pressed = KeyPressedEvent(
            alt_down     = event.AltDown() == 1,
            control_down = event.ControlDown() == 1,
            shift_down   = event.ShiftDown() == 1,
            key_code     = event.GetKeyCode(),
            event        = event
        )

        # Give other event handlers a chance.
        event.Skip()

        return

#### EOF ######################################################################
