import qt, slicer
from traitlets import CFloat, Unicode, Int, validate, observe
from ipywidgets import Image, FloatSlider, VBox, FileUpload, link
from IPython.display import IFrame

class ViewSliceBaseWidget(Image):
    """This class captures a slice view and makes it available
    for display in the output of a Jupyter notebook cell.
    """

    offsetMin = CFloat(100.0, help="Max value").tag(sync=True)
    offsetMax = CFloat(0.0, help="Min value").tag(sync=True)
    offset = CFloat(0.0, help="Slice offset").tag(sync=True)
    viewName = Unicode(default_value='Red', help="Slice view name.").tag(sync=True)

    def __init__(self, view=None, **kwargs):
        if view:
            self.viewName=view

        self.offsetSlider = FloatSlider(description='Offset')

        self.updateImage()

        self._updateOffsetRange()
        self.l1 = link((self.offsetSlider, 'value'), (self, 'offset'))
        self.l2 = link((self, 'offsetMin'), (self.offsetSlider, 'min'))
        self.l3 = link((self, 'offsetMax'), (self.offsetSlider, 'max'))

        super().__init__(**kwargs)

    @observe('offset')
    def _propagate_offset(self, change):
        offset = change['new']
        sliceWidget = slicer.app.layoutManager().sliceWidget(self.viewName)
        sliceBounds = [0,0,0,0,0,0]
        sliceWidget.sliceController().sliceOffsetSlider().setValue(offset)
        self.updateImage()

    @observe('viewName')
    def _propagate_viewName(self, change):
        self._updateOffsetRange()
        self.updateImage()

    def _updateOffsetRange(self):
        sliceWidget = slicer.app.layoutManager().sliceWidget(self.viewName)
        sliceBounds = [0,0,0,0,0,0]
        sliceWidget.sliceLogic().GetLowestVolumeSliceBounds(sliceBounds)
        positionMin = sliceBounds[4]
        positionMax = sliceBounds[5]
        if self.offsetMin != positionMin:
            self.offsetMin = positionMin
        if self.offsetMax != positionMax:
            self.offsetMax = positionMax
        if self.offset < self.offsetMin:
            self.offset = self.offsetMin
        elif self.offset > self.offsetMax:
            self.offset = self.offsetMax

    def updateImage(self):
        if not self.viewName:
            return
        slicer.app.processEvents()
        sliceWidget = slicer.app.layoutManager().sliceWidget(self.viewName)
        sliceView = sliceWidget.sliceView()
        sliceView.forceRender()
        screenshot = sliceView.grab()
        bArray = qt.QByteArray()
        buffer = qt.QBuffer(bArray)
        buffer.open(qt.QIODevice.WriteOnly)
        screenshot.save(buffer, "PNG")
        self.value = bArray.data()


class ViewSliceWidget(VBox):
    def __init__(self, view=None, **kwargs):
        self.sliceView = ViewSliceBaseWidget(view)
        super().__init__(children=[self.sliceView.offsetSlider, self.sliceView], **kwargs)

class View3DWidget(Image):
    """This class captures a 3D view and makes it available
    for display in the output of a Jupyter notebook cell.
    """

    viewIndex = Int(default_value=0, help="3D view index.").tag(sync=True)

    def __init__(self, view=None, **kwargs):
        if view:
            self.viewIndex=view

        self.updateImage()

        super().__init__(**kwargs)

    @observe('viewIndex')
    def _propagate_viewIndex(self, change):
        self.updateImage()

    def updateImage(self):
        if self.viewIndex == None:
            return
        slicer.app.processEvents()
        widget = slicer.app.layoutManager().threeDWidget(self.viewIndex)
        view = widget.threeDView()
        view.forceRender()
        screenshot = view.grab()
        bArray = qt.QByteArray()
        buffer = qt.QBuffer(bArray)
        buffer.open(qt.QIODevice.WriteOnly)
        screenshot.save(buffer, "PNG")
        self.value = bArray.data()

class FileUploadWidget(FileUpload):
    def __init__(self, **kwargs):
        self.path = None
        self.filename = None
        self.observe(self._handle_upload, names='data')
        super().__init__(**kwargs)

    def _repr_mimebundle_(self, include=None, exclude=None):
      return self.widget

    def _handle_upload(self, change=None, a=None, b=None):
        import os
        metadata = self.widget.metadata[0]
        self.filename = metadata['name']
        baseDir = os.path.dirname(notebookPath())
        self.path = os.path.join(baseDir, self.filename)
        content = self.widget.value[self.filename]['content']
        with open(self.path, 'wb') as f: f.write(content)
        print('Uploaded {0} ({1} bytes)'.format(self.filename, metadata['size']))

class AppWindow(IFrame):
    """Shows interactive screen of a remote desktop session. Requires remte desktop view configured to be displayed at ../desktop URL.
    If multiple kernels are used then the sceen space is shared between them. Make application window full-screen and call `show()`
    to ensure that current window is on top.
    """
    def __init__(self, contents=None, windowScale=None, windowWidth=None, windowHeight=None, **kwargs):
        # Set default size to fill in notebook cell
        if kwargs.get('width', None) is None:
            kwargs['width'] = 960
        if kwargs.get('height', None) is None:
            kwargs['height'] = 768
        AppWindow.setWindowSize(windowWidth, windowHeight, windowScale)
        if contents is None:
            contents = "viewers"
        AppWindow.setContents(contents)
        AppWindow.show()
        super().__init__('../desktop', **kwargs)

    @staticmethod
    def setWindowSize(width=None, height=None, scale=None):
        if width is None:
            width = 1280
        if height is None:
            height = 1024
        if scale is not None:
            width *= scale
            height *= scale
        # make sure the window is not maximized, because then we cannot adjust its size
        currentWindowState = slicer.util.mainWindow().windowState()
        if currentWindowState & qt.Qt.WindowMaximized:
            slicer.util.mainWindow().setWindowState(currentWindowState & ~qt.Qt.WindowMaximized)
        slicer.util.mainWindow().size = qt.QSize(width, height)

    @staticmethod
    def setContents(contents):
        if contents=="viewers":
            slicer.util.findChild(slicer.util.mainWindow(), "PanelDockWidget").hide()
            slicer.util.setStatusBarVisible(False)
            slicer.util.setMenuBarsVisible(False)
            slicer.util.setToolbarsVisible(False)
        elif contents=="full":
            slicer.util.findChild(slicer.util.mainWindow(), "PanelDockWidget").show()
            slicer.util.setStatusBarVisible(True)
            slicer.util.setMenuBarsVisible(True)
            slicer.util.setToolbarsVisible(True)
        else:
            raise ValueError("contents must be 'viewers' or 'full'")

    @staticmethod
    def show():
        mw = slicer.util.mainWindow()
        import os
        if os.name=='nt':
            # On Windows, the main window would just flash if we simply activate it, but it is not raised.
            # We can force raising by minimizing it then un-minimizing it.
            mw.setWindowState(mw.windowState() | qt.Qt.WindowMinimized)  # minimize
            mw.setWindowState(mw.windowState() & ~qt.Qt.WindowMinimized)  # un-minimize
            mw.activateWindow()
        else:
            mw.setWindowState(mw.windowState() & ~qt.Qt.WindowMinimized)  # un-minimize
            mw.raise_()