from __future__ import division, print_function
import math
import os
import sys
import warnings

from matplotlib import MatplotlibDeprecationWarning as mplDeprecation

warnings.warn("QT3-based backends are deprecated and will be removed after"
              " the v1.2.x release. Use the equivalent QT4 backend instead.",
              mplDeprecation)

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

from matplotlib._pylab_helpers import Gcf
from matplotlib.figure import Figure
from matplotlib.mathtext import MathTextParser
from matplotlib.widgets import SubplotTool

try:
    import qt
except ImportError:
    raise ImportError("Qt backend requires pyqt to be installed."
                      " NOTE: QT3-based backends will not work in"
                      " Python 3.")

backend_version = "0.9.1"
def fn_name(): return sys._getframe(1).f_code.co_name

DEBUG = False

cursord = {
    cursors.MOVE          : qt.Qt.PointingHandCursor,
    cursors.HAND          : qt.Qt.WaitCursor,
    cursors.POINTER       : qt.Qt.ArrowCursor,
    cursors.SELECT_REGION : qt.Qt.CrossCursor,
    }

def draw_if_interactive():
    """
    Is called after every pylab drawing command
    """
    if matplotlib.is_interactive():
        figManager =  Gcf.get_active()
        if figManager != None:
            figManager.canvas.draw()

def _create_qApp():
    """
    Only one qApp can exist at a time, so check before creating one
    """
    if qt.QApplication.startingUp():
        if DEBUG: print("Starting up QApplication")
        global qApp
        qApp = qt.QApplication( [" "] )
        qt.QObject.connect( qApp, qt.SIGNAL( "lastWindowClosed()" ),
                            qApp, qt.SLOT( "quit()" ) )
        #remember that matplotlib created the qApp - will be used by show()
        _create_qApp.qAppCreatedHere = True

_create_qApp.qAppCreatedHere = False

class Show(ShowBase):
    def mainloop(self):
        if _create_qApp.qAppCreatedHere:
            qt.qApp.exec_loop()

show = Show()


def new_figure_manager( num, *args, **kwargs ):
    """
    Create a new figure manager instance
    """
    FigureClass = kwargs.pop('FigureClass', Figure)
    thisFig = FigureClass(*args, **kwargs)
    return new_figure_manager_given_figure(num, thisFig)


def new_figure_manager_given_figure(num, figure):
    """
    Create a new figure manager instance for the given figure.
    """
    canvas = FigureCanvasQT(figure)
    manager = FigureManagerQT(canvas, num)
    return manager


class FigureCanvasQT( qt.QWidget, FigureCanvasBase ):
    keyvald = { qt.Qt.Key_Control : 'control',
                qt.Qt.Key_Shift : 'shift',
                qt.Qt.Key_Alt : 'alt',
               }
    # left 1, middle 2, right 3
    buttond = {1:1, 2:3, 4:2}
    def __init__( self, figure ):
        if DEBUG: print('FigureCanvasQt: ', figure)
        _create_qApp()

        qt.QWidget.__init__( self, None, "QWidget figure" )
        FigureCanvasBase.__init__( self, figure )
        self.figure = figure
        self.setMouseTracking( True )

        w,h = self.get_width_height()
        self.resize( w, h )

    def enterEvent(self, event):
        FigureCanvasBase.enter_notify_event(self, event)

    def leaveEvent(self, event):
        FigureCanvasBase.leave_notify_event(self, event)

    def mousePressEvent( self, event ):
        x = event.pos().x()
        # flipy so y=0 is bottom of canvas
        y = self.figure.bbox.height - event.pos().y()
        button = self.buttond[event.button()]
        FigureCanvasBase.button_press_event( self, x, y, button )
        if DEBUG: print('button pressed:', event.button())

    def mouseDoubleClickEvent( self, event ):
        x = event.pos().x()
        # flipy so y=0 is bottom of canvas
        y = self.figure.bbox.height - event.pos().y()
        button = self.buttond[event.button()]
        FigureCanvasBase.button_press_event( self, x, y, button, dblclick=True )
        if DEBUG: print('button doubleclicked:', event.button())

    def mouseMoveEvent( 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 )
        if DEBUG: print('mouse move')

    def mouseReleaseEvent( self, event ):
        x = event.x()
        # flipy so y=0 is bottom of canvas
        y = self.figure.bbox.height - event.y()
        button = self.buttond[event.button()]
        FigureCanvasBase.button_release_event( self, x, y, button )
        if DEBUG: print('button released')

    def keyPressEvent( self, event ):
        key = self._get_key( event )
        FigureCanvasBase.key_press_event( self, key )
        if DEBUG: print('key press', key)

    def keyReleaseEvent( self, event ):
        key = self._get_key(event)
        FigureCanvasBase.key_release_event( self, key )
        if DEBUG: print('key release', key)

    def resizeEvent( self, event ):
        if DEBUG: print('resize (%d x %d)' % (event.size().width(), event.size().height()))
        qt.QWidget.resizeEvent( self, event )
        w = event.size().width()
        h = event.size().height()
        if DEBUG: print("FigureCanvasQt.resizeEvent(", w, ",", h, ")")
        dpival = self.figure.dpi
        winch = w/dpival
        hinch = h/dpival
        self.figure.set_size_inches( winch, hinch )
        self.draw()

    def resize( self, w, h ):
        # Pass through to Qt to resize the widget.
        qt.QWidget.resize( self, w, h )

        # Resize the figure by converting pixels to inches.
        pixelPerInch = self.figure.dpi
        wInch = w / pixelPerInch
        hInch = h / pixelPerInch
        self.figure.set_size_inches( wInch, hInch )

        # Redraw everything.
        self.draw()

    def sizeHint( self ):
        w, h = self.get_width_height()
        return qt.QSize( w, h )

    def minumumSizeHint( self ):
        return qt.QSize( 10, 10 )

    def _get_key( self, event ):
        if event.key() < 256:
            key = event.text().latin1()
        elif event.key() in self.keyvald:
            key = self.keyvald[ event.key() ]
        else:
            key = None

        # TODO: Handle ctrl, alt, super modifiers. qt4 backend has implemented.

        return key

    def flush_events(self):
        qt.qApp.processEvents()

    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 FigureManagerQT( FigureManagerBase ):
    """
    Public attributes

    canvas      : The FigureCanvas instance
    num         : The Figure number
    toolbar     : The qt.QToolBar
    window      : The qt.QMainWindow
    """

    def __init__( self, canvas, num ):
        if DEBUG: print('FigureManagerQT.%s' % fn_name())
        FigureManagerBase.__init__( self, canvas, num )
        self.canvas = canvas
        self.window = qt.QMainWindow( None, None, qt.Qt.WDestructiveClose )
        self.window.closeEvent = self._widgetCloseEvent

        centralWidget = qt.QWidget( self.window )
        self.canvas.reparent( centralWidget, qt.QPoint( 0, 0 ) )

        # Give the keyboard focus to the figure instead of the manager
        self.canvas.setFocusPolicy( qt.QWidget.ClickFocus )
        self.canvas.setFocus()
        self.set_window_title( "Figure %d" % num )

        self.window._destroying = False

        self.toolbar = self._get_toolbar(self.canvas, centralWidget)

        # Use a vertical layout for the plot and the toolbar.  Set the
        # stretch to all be in the plot so the toolbar doesn't resize.
        self.layout = qt.QVBoxLayout( centralWidget )
        self.layout.addWidget( self.canvas, 1 )

        if self.toolbar:
           self.layout.addWidget( self.toolbar, 0 )

        self.window.setCentralWidget( centralWidget )

        # Reset the window height so the canvas will be the right
        # size.  This ALMOST works right.  The first issue is that the
        # height w/ a toolbar seems to be off by just a little bit (so
        # we add 4 pixels).  The second is that the total width/height
        # is slightly smaller that we actually want.  It seems like
        # the border of the window is being included in the size but
        # AFAIK there is no way to get that size.
        w = self.canvas.width()
        h = self.canvas.height()
        if self.toolbar:
           h += self.toolbar.height() + 4
        self.window.resize( w, h )

        if matplotlib.is_interactive():
            self.window.show()

        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 )

    def _widgetclosed( self ):
        if self.window._destroying: return
        self.window._destroying = True
        Gcf.destroy(self.num)

    def _widgetCloseEvent( self, event ):
        self._widgetclosed()
        qt.QWidget.closeEvent( self.window, event )

    def _get_toolbar(self, canvas, parent):
        # must be inited after the window, drawingArea and figure
        # attrs are set
        if matplotlib.rcParams['toolbar'] == 'classic':
            print("Classic toolbar is not yet supported")
        elif matplotlib.rcParams['toolbar'] == 'toolbar2':
            toolbar = NavigationToolbar2QT(canvas, parent)
        else:
            toolbar = None
        return toolbar

    def resize(self, width, height):
        'set the canvas size in pixels'
        self.window.resize(width, height)

    def show(self):
        self.window.show()

    def destroy( self, *args ):
        if self.window._destroying: return
        self.window._destroying = True
        if self.toolbar: self.toolbar.destroy()
        if DEBUG: print("destroy figure manager")
        self.window.close(True)

    def get_window_title(self):
        return str(self.window.caption())

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

class NavigationToolbar2QT( NavigationToolbar2, qt.QWidget ):
    def __init__( self, canvas, parent ):
        self.canvas = canvas
        self.buttons = {}

        qt.QWidget.__init__( self, parent )

        # Layout toolbar buttons horizontally.
        self.layout = qt.QHBoxLayout( self )
        self.layout.setMargin( 2 )

        NavigationToolbar2.__init__( self, canvas )

    def _init_toolbar( self ):
        basedir = os.path.join(matplotlib.rcParams[ 'datapath' ],'images')

        for text, tooltip_text, image_file, callback in self.toolitems:
            if text == None:
                self.layout.addSpacing( 8 )
                continue

            fname = os.path.join(basedir, image_file + '.ppm')
            image = qt.QPixmap()
            image.load( fname )

            button = qt.QPushButton( qt.QIconSet( image ), "", self )
            qt.QToolTip.add( button, tooltip_text )

            self.buttons[ text ] = button

            # The automatic layout doesn't look that good - it's too close
            # to the images so add a margin around it.
            margin = 4
            button.setFixedSize( image.width()+margin, image.height()+margin )

            qt.QObject.connect( button, qt.SIGNAL( 'clicked()' ),
                                getattr( self, callback ) )
            self.layout.addWidget( button )

        self.buttons[ 'Pan' ].setToggleButton( True )
        self.buttons[ 'Zoom' ].setToggleButton( True )

        # Add the x,y location widget at the right side of the toolbar
        # The stretch factor is 1 which means any resizing of the toolbar
        # will resize this label instead of the buttons.
        self.locLabel = qt.QLabel( "", self )
        self.locLabel.setAlignment( qt.Qt.AlignRight | qt.Qt.AlignVCenter )
        self.locLabel.setSizePolicy(qt.QSizePolicy(qt.QSizePolicy.Ignored,
                                                      qt.QSizePolicy.Ignored))
        self.layout.addWidget( self.locLabel, 1 )

        # reference holder for subplots_adjust window
        self.adj_window = None


    def destroy( self ):
        for text, tooltip_text, image_file, callback in self.toolitems:
            if text is not None:
                qt.QObject.disconnect( self.buttons[ text ],
                                       qt.SIGNAL( 'clicked()' ),
                                       getattr( self, callback ) )

    def pan( self, *args ):
        self.buttons[ 'Zoom' ].setOn( False )
        NavigationToolbar2.pan( self, *args )

    def zoom( self, *args ):
        self.buttons[ 'Pan' ].setOn( False )
        NavigationToolbar2.zoom( self, *args )

    def dynamic_update( self ):
        self.canvas.draw()

    def set_message( self, s ):
        self.locLabel.setText( s )

    def set_cursor( self, cursor ):
        if DEBUG: print('Set cursor' , cursor)
        qt.QApplication.restoreOverrideCursor()
        qt.QApplication.setOverrideCursor( qt.QCursor( cursord[cursor] ) )

    def draw_rubberband( self, event, x0, y0, x1, y1 ):
        height = self.canvas.figure.bbox.height
        y1 = height - y1
        y0 = height - y0

        w = abs(x1 - x0)
        h = abs(y1 - y0)

        rect = [ int(val)for val in (min(x0,x1), min(y0, y1), w, h) ]
        self.canvas.drawRectangle( rect )

    def configure_subplots(self):
        self.adj_window = qt.QMainWindow(None, None, qt.Qt.WDestructiveClose)
        win = self.adj_window
        win.setCaption("Subplot Configuration Tool")

        toolfig = Figure(figsize=(6,3))
        toolfig.subplots_adjust(top=0.9)
        w = int (toolfig.bbox.width)
        h = int (toolfig.bbox.height)

        canvas = self._get_canvas(toolfig)
        tool = SubplotTool(self.canvas.figure, toolfig)
        centralWidget = qt.QWidget(win)
        canvas.reparent(centralWidget, qt.QPoint(0, 0))
        win.setCentralWidget(centralWidget)

        layout = qt.QVBoxLayout(centralWidget)
        layout.addWidget(canvas, 1)

        win.resize(w, h)
        canvas.setFocus()
        win.show()

    def _get_canvas(self, fig):
        return FigureCanvasQT(fig)

    def save_figure(self, *args):
        filetypes = self.canvas.get_supported_filetypes_grouped()
        sorted_filetypes = filetypes.items()
        sorted_filetypes.sort()
        default_filetype = self.canvas.get_default_filetype()

        start = self.canvas.get_default_filename()
        filters = []
        selectedFilter = None
        for name, exts in sorted_filetypes:
            exts_list = " ".join(['*.%s' % ext for ext in exts])
            filter = '%s (%s)' % (name, exts_list)
            if default_filetype in exts:
                selectedFilter = filter
            filters.append(filter)
        filters = ';;'.join(filters)

        fname = qt.QFileDialog.getSaveFileName(
            start, filters, self, "Save image", "Choose a filename to save to",
            selectedFilter)
        if fname:
            try:
                self.canvas.print_figure( unicode(fname) )
            except Exception as e:
                qt.QMessageBox.critical(
                    self, "Error saving file", str(e),
                    qt.QMessageBox.Ok, qt.QMessageBox.NoButton)

    def set_history_buttons( self ):
        canBackward = ( self._views._pos > 0 )
        canForward = ( self._views._pos < len( self._views._elements ) - 1 )
        self.buttons[ 'Back' ].setEnabled( canBackward )
        self.buttons[ 'Forward' ].setEnabled( canForward )

# set icon used when windows are minimized
try:
    # TODO: This is badly broken
    qt.window_set_default_icon_from_file (
        os.path.join( matplotlib.rcParams['datapath'], 'images', 'matplotlib.svg' ) )
except:
    verbose.report( 'Could not load matplotlib icon: %s' % sys.exc_info()[1] )


def error_msg_qt( msg, parent=None ):
    if not is_string_like( msg ):
        msg = ','.join( map( str,msg ) )

    qt.QMessageBox.warning( None, "Matplotlib", msg, qt.QMessageBox.Ok )

def exception_handler( type, value, tb ):
    """Handle uncaught exceptions
    It does not catch SystemExit
    """
    msg = ''
    # get the filename attribute if available (for IOError)
    if hasattr(value, 'filename') and value.filename != None:
        msg = value.filename + ': '
    if hasattr(value, 'strerror') and value.strerror != None:
        msg += value.strerror
    else:
        msg += str(value)

    if len( msg ) : error_msg_qt( msg )


FigureManager = FigureManagerQT
