import sys
import wx
from wx import xrc

from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
        FigureCanvasWxAgg as FigCanvas, \
        NavigationToolbar2WxAgg as NavigationToolbar
from plot_utils import ImagePlot
from square_selector import SquareSelector
from reflexlib import VisirParameter
from reflex import RecipeParameter
import numpy as np
import os
from wx.lib.mixins.listctrl import CheckListCtrlMixin


def add_dialog_buttons(parent, on_help=None):
    btnSizer = wx.StdDialogButtonSizer()
    btnSizerOK = wx.Button(parent, wx.ID_OK)
    btnSizer.AddButton(btnSizerOK)
    btnSizerCancel = wx.Button(parent, wx.ID_CANCEL)
    btnSizer.AddButton(btnSizerCancel)
    if on_help:
        btnSizerHelp = wx.Button(parent, wx.ID_HELP)
        btnSizer.AddButton(btnSizerHelp)
        btnSizerHelp.Bind(wx.EVT_BUTTON, on_help)
    btnSizer.Realize()
    return btnSizer


class PlotPanel(wx.Panel):
    def __init__(self, parent, toolbar=True, id=wx.ID_ANY, **kwargs):
        wx.Panel.__init__(self, parent, id=id, **kwargs)
        self.figure = Figure(figsize=(2, 2))
        self.canvas = FigCanvas(self, -1, self.figure)

        sizer = wx.BoxSizer(wx.VERTICAL)
        if toolbar:
            self.toolbar = NavigationToolbar(self.canvas)
            self.toolbar.Realize()
            sizer.Add(self.toolbar, 0, wx.LEFT | wx.EXPAND)
        sizer.Add(self.canvas, 1, wx.EXPAND)
        self.SetSizer(sizer)


class PlotDialog(wx.Dialog):
    def __init__(self, parent, *args, **kwargs):
        if 'size' not in kwargs:
            kwargs['size'] = wx.Size(500, 500)
        if 'style' not in kwargs:
            kwargs['style'] = (wx.CLOSE_BOX | wx.DEFAULT_DIALOG_STYLE |
                               wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX |
                               wx.RESIZE_BORDER)
        wx.Dialog.__init__(self, parent, *args, **kwargs)
        sizer = wx.BoxSizer(wx.VERTICAL)

        plotPanel = PlotPanel(self)
        self.figure = plotPanel.figure

        sizer.Add(plotPanel, 1, wx.EXPAND)
        sizer.Add(add_dialog_buttons(self), 0, wx.EXPAND)
        sizer.AddSpacer((0, 3), 0, wx.EXPAND, 5)
        self.SetSizer(sizer)


class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin):
    def __init__(self, parent, id, pos, size):
        wx.ListCtrl.__init__(self, parent, id, pos, size, style=wx.LC_REPORT)
        CheckListCtrlMixin.__init__(self)


class BeamDialog(wx.Dialog):
    def __init__(self, parent, data=None, dflt_size=50, **kwargs):
        if 'size' not in kwargs:
            kwargs['size'] = wx.Size(500, 500)
        if 'style' not in kwargs:
            kwargs['style'] = (wx.CLOSE_BOX | wx.DEFAULT_DIALOG_STYLE |
                               wx.MAXIMIZE_BOX | wx.MINIMIZE_BOX |
                               wx.RESIZE_BORDER)
        wx.Dialog.__init__(self, parent, **kwargs)
        sizer = wx.BoxSizer(wx.VERTICAL)

        mainsizer = wx.FlexGridSizer(0, 2, 0, 0)
        mainsizer.AddGrowableCol(0)
        mainsizer.AddGrowableRow(0)
        mainsizer.SetFlexibleDirection(wx.BOTH)
        mainsizer.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED)

        ctrlSizer = wx.BoxSizer(wx.VERTICAL)
        self.ctrlPanel = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition,
                                  wx.Size(-1, -1), wx.TAB_TRAVERSAL)

        self.beamBox = CheckListCtrl(self.ctrlPanel, wx.ID_ANY,
                                     wx.DefaultPosition,
                                     wx.Size(150, 150))
        self.beamBox.InsertColumn(0, 'Sign', width=35)
        self.beamBox.InsertColumn(1, 'Position', width=115)
        ctrlSizer.Add(self.beamBox, 0, wx.ALL | wx.EXPAND, 5)

        self.ctrlPanel.SetSizer(ctrlSizer)
        self.ctrlPanel.Layout()

        plotPanel = PlotPanel(self)
        self.figure = plotPanel.figure

        mainsizer.Add(plotPanel, 1, wx.EXPAND)
        mainsizer.Add(self.ctrlPanel, 1, wx.EXPAND | wx.ALL, 5)

        sizer.Add(mainsizer, 1, wx.EXPAND)
        sizer.Add(add_dialog_buttons(self, on_help=self.on_help), 0, wx.EXPAND)
        sizer.AddSpacer((0, 3), 0, wx.EXPAND, 5)
        self.SetSizer(sizer)

        self.data = data
        plt = ImagePlot(self.figure, self.figure.add_subplot(111))
        plt.set_data(data)
        plt.ax.set_title('Input image')
        plt.draw(origin='lower', interpolation='nearest')
        self.sq = SquareSelector(plt.ax, self.on_select, dflt_size=dflt_size)

    def on_help(self, event):
        msg = """
Select the position of the beams in the image with the left mouse button.
The selection does not need to be perfect. The values are only used an initial guess and are refined by the recipe later.

By holding the mouse button and dragging you can change the size of the image around the beam.
Selections can be removed by right clicking them.

The pipeline also needs to know the sign of the beam as due to the chop/nod correction some objects are negative and some positive.
It will try to detect the sign from the selected region.
If the autodetection fails it will prompt for the sign.
The detected or chosen sign can also be changed by clicking the checkbox next to the beam entry on the right.
A checked box represents a positive beam and an unchecked box a negative beam.
"""
        dlg = wx.MessageDialog(self, msg,
                               "Help",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()

    def search_bbox(self, v, col):
        for idx in range(self.beamBox.GetItemCount()):
            item = self.beamBox.GetItem(idx, col)
            if item.GetText() == v:
                    return idx

    def check_sign(self, x, y, w, d):
        lx = int(max(0, x - w / 3))
        ly = int(max(0, y - w / 3))
        hx = int(min(d.shape[1], x + w / 3))
        hy = int(min(d.shape[0], y + w / 3))
        dc = d[ly:hy, lx:hx] - np.median(d[ly:hy, lx:hx])
        if abs(dc.sum()) < dc.std() * np.sqrt(dc.size) * 2:
            while True:
                sdlg = wx.TextEntryDialog(self, 'Beam (%d,%d) sign pos or neg?'
                                          % (int(x), int(y)),
                                         'Set beam sign')
                sdlg.ShowModal()
                sign = sdlg.GetValue().lower()
                sdlg.Destroy()
                if sign in ['pos', 'neg']:
                    break
        else:
            sign = (dc).sum()
            sign = 'pos' if sign > 0 else 'neg'
        return sign

    def on_select(self, data, add):
        st = '%d, %d, %d' % (int(data[0]), int(data[1]), int(data[2]) * 2)
        ind = self.search_bbox(st, 1)
        if not add and ind >= 0:
            self.beamBox.DeleteItem(ind)
        else:
            idx = self.beamBox.InsertStringItem(sys.maxint, '')
            sign = self.check_sign(*data, d=self.data)
            if sign == 'pos':
                self.beamBox.CheckItem(idx)
            self.beamBox.SetStringItem(idx, 1, st)

    def get_selections(self):
        res = []
        for idx in range(self.beamBox.GetItemCount()):
            sign = 'pos' if self.beamBox.IsChecked(idx)else 'neg'
            pos = map(int, self.beamBox.GetItem(idx, 1).GetText().split(','))
            res.append((sign, pos))
        return res


class ParamWidget:
    def __init__(self, parent, param):
        self.param = param
        self.parent = parent
        self.paramLabel = wx.StaticText(parent, label=param.display_name)
        if param.type == 'bool':
            self.paramCtrl = wx.CheckBox(parent, wx.ID_ANY, u"")
        elif param.type == 'list':
            self.paramCtrl = \
                wx.ComboBox(parent, wx.ID_ANY, value=param.value,
                            style=wx.CB_READONLY, choices=param.options)
        elif param.type == 'file':
            self.paramCtrl = wx.FilePickerCtrl(parent, wx.ID_ANY,
                                               wildcard='*.fits')
        else:
            self.paramCtrl = wx.TextCtrl(parent, wx.ID_ANY,
                                         style=(wx.TE_PROCESS_ENTER |
                                                wx.TE_RIGHT))
        self.set_value(param.value)
        self.default = self.get_value()
        if param.description:
            self.paramCtrl.SetToolTipString(param.description)

    def get_value(self):
        try:
            v = self.paramCtrl.GetValue()
        except AttributeError:
            v = self.paramCtrl.GetPath()
        return self.param.to_string(self.param.type, v)

    def is_default(self):
        return self.get_value() == self.default

    def set_value(self, value):
        try:
            self.paramCtrl.SetValue(value)
        except TypeError:
            self.paramCtrl.SetValue(str(value))
        except AttributeError:
            self.paramCtrl.SetPath(value)


class ReflexBaseGui(wx.App):
    def __init__(self, io, dm):
        self.io = io
        self.dm = dm
        wx.App.__init__(self)

    def OnInit(self):
        respath = os.path.join(os.path.dirname(__file__), 'reflexgui.xrc')
        # changed global size, filesList size and removed notebook
        #respath = os.path.join(os.path.dirname(reflex.__file__),
        #                       'reflex_interactive_gui.xrc')
        self.res = xrc.XmlResource(respath)
        self.frame = self.res.LoadFrame(None, 'ReflexInteractiveWxApp')
        if not self.frame:
            raise Exception("Frame not found")

        if hasattr(self.dm, 'setWindowTitle'):
            self.frame.SetTitle(self.dm.setWindowTitle())

        self.plotPanel = xrc.XRCCTRL(self.frame, 'plotPanel')
        self.ctrlPanel = xrc.XRCCTRL(self.frame, 'ctrlPanel')
        self.ctrlPanel.SetScrollbars(0, 1, 0, 1)
        self.ctrlPanel.SetAutoLayout(1)
        self.statusBar = xrc.XRCCTRL(self.frame, 'statusBar')
        self.setDisableCheck = xrc.XRCCTRL(self.frame, 'setDisableCheck')

        self.Bind(wx.EVT_BUTTON, self.onCont, id=xrc.XRCID('contBtn'))
        self.Bind(wx.EVT_BUTTON, self.onRepeat, id=xrc.XRCID('repeatBtn'))
        self.Bind(wx.EVT_BUTTON, self.onHelp, id=xrc.XRCID('helpBtn'))
        self.Bind(wx.EVT_CHECKBOX, self.onSetDisable, self.setDisableCheck)
        self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.onFileInfoPaneChanged)
        self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onItemSelected)

        self.dm.readFitsData(self.io.inputs.in_sof.files +
                             self.io.inputs.in_sof_rec_orig.files)
        self.datasetInfoText = xrc.XRCCTRL(self.frame, 'datasetInfoText')
        self.datasetInfoText.SetLabel("This data belongs to dataset:\n%s" %
                                      self.io.inputs.in_sof.datasetName)
        self.filesInfo       = xrc.XRCCTRL(self.frame, 'filesInfo')
        self.filesList       = xrc.XRCCTRL(self.frame, 'filesList')
        self.setInitSopCheck = xrc.XRCCTRL(self.frame, 'setInitSopCheck')
        if(not self.io.is_init_sop_enable()):
            self.setInitSopCheck.Hide()

        self.setupParameters()
        self.createPlots()
        self.setFilesInfo()
        # normally triggerd by Collapse but that may not run on start on mac
        # which causes broken parameter layout
        self.ctrlPanel.FitInside()
        self.frame.Show()
        return True

    @classmethod
    def show(self, io, dataplotmanager):
        app = ReflexBaseGui(io, dataplotmanager)
        app.MainLoop()
        return app

    def createPlots(self):
        self.figure = Figure()
        self.canvas = FigCanvas(self.plotPanel, wx.ID_ANY, self.figure)
        self.canvas.mpl_connect('motion_notify_event', self.onMotion)
        self.canvas.mpl_connect('axes_enter_event', self.onEnterAxes)
        self.canvas.mpl_connect('axes_leave_event', self.onLeaveAxes)

        self.toolbar = NavigationToolbar(self.canvas)
        self.frame.SetToolBar(self.toolbar)
        self.plotPanel.Bind(wx.EVT_SIZE, self.onResize)
        self.plotPanel.Bind(wx.EVT_IDLE, self.onIdle)
        self.canvas.SetSize(self.plotPanel.GetSize())
        self.needs_resize = False

        self.dm.addSubplots(self.figure)
        self.dm.frame = self.frame
        self.dm.plotProductsGraphics(self.figure, self.canvas)

    def onResize(self, event):
        self.needs_resize = True

    def onMotion(self, event):
        if event.inaxes:
            self.statusBar.SetStatusText(event.inaxes.format_coord(event.xdata,
                                                                   event.ydata))
        else:
            self.statusBar.SetStatusText((''), 0)

    def onEnterAxes(self, event):
        if hasattr(event.inaxes, 'tooltip'):
            tip = self.canvas.GetToolTip()
            if not tip:
                tip = wx.ToolTip(tip=event.inaxes.tooltip)
                self.canvas.SetToolTip(tip)
            else:
                tip.SetTip(event.inaxes.tooltip)
            tip.Enable(True)

    def onLeaveAxes(self, event):
        try:
            self.canvas.GetToolTip().Enable(False)
        except:
            pass

    def onIdle(self, event):
        if self.needs_resize:
            self.canvas.SetSize(self.plotPanel.GetSize())
            self.needs_resize = False

    def onFileInfoPaneChanged(self, event):
        self.filesInfo.GetParent().Layout()
        self.ctrlPanel.FitInside()

    def onItemSelected(self, event):
        clipData = wx.TextDataObject()
        clipData.SetText(self.all_fitsFiles[self.filesList.GetFocusedItem()].name)
        wx.TheClipboard.Open()
        wx.TheClipboard.SetData(clipData)
        wx.TheClipboard.Close()

    def setFilesInfo(self):
        # filesInfo does not get proper class on wxPython3 on macos
        if hasattr(self.filesInfo, 'Collapse'):
            self.filesInfo.Collapse()
        self.filesList.InsertColumn(0, "Category")
        self.filesList.InsertColumn(1, "File Name")
        self.all_fitsFiles = self.io.inputs.in_sof.files + \
                             self.io.inputs.in_sof_rec_orig.files
        for file in self.all_fitsFiles:
            self.filesList.Append([file.category, file.name])
            self.filesList.SetColumnWidth(0, wx.LIST_AUTOSIZE)
            self.filesList.SetColumnWidth(1, wx.LIST_AUTOSIZE)
            self.filesList.SetToolTipString("List of files that have been an"
                "input of this window. If you click on a particular file, you"
                " can double click or press Ctrl-C to copy the full path name"
                " onto the clipboard.")

    def setupParameters(self):
        parTab = xrc.XRCCTRL(self.frame, 'parameterPanel')
        parTab.SetMinSize((170, 340))
        parGrid = wx.FlexGridSizer(cols=2, vgap=0, hgap=0)
        parGrid.AddGrowableCol(1)
        parTab.SetSizer(parGrid, 1)

        sop_params = dict()
        for p in self.io.inputs.in_sop:
            sop_params[(p.recipe, p.displayName)] = p.value

        self.paramWidgets = dict()
        for p in self.dm.setInteractiveParameters():
            if not (p.recipe, p.name) in sop_params:
                continue

            p.value = \
                VisirParameter.to_native(p.type,
                                         sop_params[(p.recipe, p.name)])
            w = ParamWidget(parTab, p)
            parGrid.Add(w.paramLabel,
                        flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL, border=5)
            parGrid.Add(w.paramCtrl,
                        flag=wx.ALL | wx.ALIGN_CENTER_VERTICAL | wx.EXPAND,
                        border=5)
            self.paramWidgets[p.name] = w
        self.dm.parWidgets = self.paramWidgets

    def onCont(self, event):
        changed = []
        for p in self.paramWidgets.values():
            if not p.is_default():
                changed.append(p.param.name)
        if changed:
            if len(changed) > 1:
                s  = 'Parameters "%s" have been changed.' % ','.join(changed)
            else:
                s  = 'Parameter" %s" has been changed.' % changed[0]
            dlg = wx.MessageDialog(self.frame,
                    "%s To take effect the recipe must be rerun. "
                    "Discard changes and continue?" % s,
                                   "Confirm",
                                   wx.OK | wx.CANCEL)
            result = dlg.ShowModal()
            dlg.Destroy()
            if result != wx.ID_OK:
                return
        self.io.outputs.iteration_complete = 'true'
        self.io.outputs.out_sof = self.io.inputs.in_sof
        self.io.outputs.out_sop = self.io.inputs.in_sop

        if hasattr(self, 'set_next_iteration_enable'):
            self.io.outputs.set_enable = self.set_next_iteration_enable

        self.frame.Close(True)

    def verifyParameters(self):
        params = dict()
        for p in self.paramWidgets.values():
            params[p.param.name] = dict(value=p.get_value(),
                                        isdefault=p.is_default())
        s = None
        if hasattr(self.dm, 'verifyParameters'):
            s = self.dm.verifyParameters(params)
        return s

    def onRepeat(self, event):
        s = self.verifyParameters()
        if s:
            dlg = wx.MessageDialog(self.frame, s + '\nIgnore and continue?' ,
                                   "Confirm", wx.OK | wx.CANCEL)
            result = dlg.ShowModal()
            dlg.Destroy()
            if result != wx.ID_OK:
                return
        self.io.outputs.out_sop_loop = list()
        for  p in self.paramWidgets.values():
            param = RecipeParameter(displayName=p.param.name,
                                    value=p.get_value(), recipe=p.param.recipe)
            self.io.outputs.out_sop_loop.append(param)

        self.io.outputs.iteration_complete = 'false'
        self.io.outputs.out_sof_loop = self.io.inputs.in_sof_rec_orig
        if hasattr(self, 'set_next_iteration_enable'):
            self.io.outputs.set_enable = self.set_next_iteration_enable

        self.frame.Close(True)

    def onSetDisable(self, event):
        if self.setDisableCheck.GetValue():
            self.set_next_iteration_enable = 'false'
        else:
            self.set_next_iteration_enable = None

    def onHelp(self, event):
        if hasattr(self.dm, 'setWindowHelp'):
            window_help_msg = self.dm.setWindowHelp()
        else:
            window_help_msg = ''
        general_help_msg = """

    The window has several parts:\n
       1. Plot area. This area shows how good the reduction was performed
       2. Parameter area. This area shows the parameters used for the execution of the recipe. The parameters can be changed if the recipe has to be re-run with new parameters
       3. Button area. These buttons control the interactivity of the window\n
            a) Continue wkf. This button sends the current results to the next recipe in the workflow.\n
            b) Re-run recipe. This button will execute the predict recipe again with the new parameters\n
            c) Help. This button shows this help
       4. Top toolbar area. These buttons allow certain interactivity with the plots (zoom, shift, layout) as well as exporting to png
            """
        dlg = wx.MessageDialog(self.frame, window_help_msg + general_help_msg,
                               "Help",
                               wx.OK | wx.ICON_INFORMATION)
        dlg.ShowModal()
        dlg.Destroy()
