from __future__ import annotations

import math

import wx
import wx.grid as gridlib
import wx.lib.scrolledpanel as scroll_pan

from dials.array_family import flex
from dials.viewer.from_flex_to_wxbitmap import wxbitmap_convert


class grid_frame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=wx.DefaultSize)

    def frame_ini_img(self, in_upper_panel, text_data=None):
        self.img_panel = in_upper_panel

        if text_data is not None:
            self.myGrid = text_data

        self.my_sizer = wx.BoxSizer(wx.VERTICAL)

        if text_data is not None:
            self.my_sizer.Add(
                self.myGrid, proportion=3, flag=wx.EXPAND | wx.ALL, border=3
            )

        self.my_sizer.SetMinSize((900, 600))
        self.SetSizer(self.my_sizer)
        self.my_sizer.Fit(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)

    def OnCloseWindow(self, event):
        wx.Exit()


class flex_3d_frame(wx.Frame):
    def __init__(self, parent, title):
        super().__init__(parent, title=title, size=wx.DefaultSize)
        self.table_exist = False

    def frame_ini_img(self, in_upper_panel, text_data=None):
        self.img_panel = in_upper_panel
        self.myGrid = None

        if text_data is not None:
            self.myGrid = text_data

        self.my_sizer = wx.BoxSizer(wx.VERTICAL)
        self.my_sizer.Add(
            self.img_panel, proportion=2, flag=wx.EXPAND | wx.ALL, border=3
        )

        if text_data is not None:
            self.my_sizer.Add(
                self.myGrid, proportion=3, flag=wx.EXPAND | wx.ALL, border=3
            )

        self.my_sizer.SetMinSize((400, 200))
        self.SetSizer(self.my_sizer)
        self.my_sizer.Fit(self)
        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)

    def OnCloseWindow(self, event):
        if not self.table_exist:
            # print "from flex_3d_frame self.myGrid = None"
            event.Skip()
        else:
            # print "from flex_3d_frame self.myGrid .NEQ. None"
            wx.Exit()


class TupTable(gridlib.GridTableBase):
    def __init__(self, data, rowLabels=None, colLabels=None):
        super().__init__()
        self.data = data
        self.rowLabels = rowLabels
        self.colLabels = colLabels

    def GetNumberRows(self):
        return len(self.data)

    def GetNumberCols(self):
        return len(self.data[0])

    def GetColLabelValue(self, col):
        if self.colLabels:
            return self.colLabels[col]

    def GetRowLabelValue(self, row):
        if self.rowLabels:
            return str(self.rowLabels[row])

    def IsEmptyCell(self, row, col):
        return False

    def GetValue(self, row, col):
        return self.data[row][col]

    def SetValue(self, row, col, value):
        pass


class MyGrid(gridlib.Grid):
    def __init__(self, parent_frame):

        self.parent_fr = parent_frame
        super().__init__(parent_frame)

    def ini_n_intro(self, table_in):
        self.lst_keys = []
        self.data = []
        self.sorted_flags = []

        for key in table_in.keys():
            if key != "shoebox":
                col_label = str(key)
                col_content = list(map(str, table_in[key]))

                str_len = bigger_size(col_label, col_content)

                if str_len > len(col_label):
                    lng_dif = int(float(str_len - len(col_label)) * 1.6)
                    add_str = " " * lng_dif
                    col_label = add_str + col_label + add_str
                else:
                    col_label = "   " + col_label + "   "

                self.lst_keys.append(col_label)
                self.data.append(col_content)
                self.sorted_flags.append(True)

        self.lst_keys.append("lst pos")
        self.data.append(list(range(len(table_in))))
        self.sorted_flags.append(True)

        self.last_col_num = len(self.lst_keys) - 1

        self.data = tuple(zip(*self.data))
        self.EnableEditing(True)
        self.EnableDragGridSize(True)

        self.set_my_table(self.last_col_num)

        self.Bind(gridlib.EVT_GRID_CELL_LEFT_CLICK, self.OnCellLeftClick)
        self.Bind(gridlib.EVT_GRID_LABEL_LEFT_CLICK, self.OnLabelLeftClick)

    def OnLabelLeftClick(self, evt):
        if evt.GetCol() == -1:
            self.repaint_img(evt.GetRow())
        else:
            self.set_my_table(evt.GetCol())

        evt.Skip()

    def set_my_table(self, col_to_sort):
        self.sorted_flags[col_to_sort] = not self.sorted_flags[col_to_sort]

        try:
            tupldata = sorted(
                self.data,
                key=lambda x: int(x[col_to_sort]),
                reverse=self.sorted_flags[col_to_sort],
            )
            print("using key=lambda x: int(x[col_to_sort])")

        except Exception:
            try:
                tupldata = sorted(
                    self.data,
                    key=lambda x: float(x[col_to_sort]),
                    reverse=self.sorted_flags[col_to_sort],
                )
                print("using key=lambda x: float(x[col_to_sort])")
            except Exception:
                tupldata = sorted(
                    self.data,
                    key=lambda x: tuple(eval(str(x[col_to_sort]))),
                    reverse=self.sorted_flags[col_to_sort],
                )
                print("using key=lambda x: tuple(x[col_to_sort])")

        colLabels = tuple(self.lst_keys)
        rowLabels = tuple(range(len(tupldata)))

        tableBase = TupTable(tupldata, rowLabels, colLabels)

        # self.AutoSizeColumns(False)
        self.SetTable(tableBase, takeOwnership=True)
        self.Refresh()
        # self.AutoSizeColumn(1)
        for i in range(len(self.lst_keys)):
            self.AutoSizeColLabelSize(i)

        # self.AutoSizeColumns(True)

    def OnCellLeftClick(self, evt):
        self.repaint_img(evt.GetRow())
        evt.Skip()

    def repaint_img(self, clikd_row):
        new_row = int(self.GetCellValue(clikd_row, self.last_col_num))
        self.parent_fr.img_panel.update_img_w_row_pos(new_row)


class flex_arr_img_panel(wx.Panel):
    def __init__(self, parent_frame):
        super().__init__(parent_frame)
        self.show_nums = True
        self.show_mask = True
        self.palette = "black2white"
        self.row_pos = 0
        self.Pframe = parent_frame
        self.local_bbox = (0, 0, 2, 2, 4, 4)

    def ini_n_intro(self, data_in_one, data_in_two=None):
        self.scale = 1.0

        if isinstance(data_in_one, flex.reflection_table):
            self.table = data_in_one
            self.assign_row_pos()

        else:
            self.first_lst_in, self.segn_lst_in = data_in_one, data_in_two
            print("flex array entered")
            self.local_bbox = None

        self.bmp_lst = self._mi_list_of_wxbitmaps()
        self.panel_left = buttons_panel(self)
        self.panel_right = multi_img_scrollable(self, self.bmp_lst)
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        sizer.Add(self.panel_left, 0, wx.EXPAND)
        sizer.Add(self.panel_right, 1, wx.EXPAND)
        self.SetSizer(sizer)
        self.Show(True)

    def assign_row_pos(self):
        try:
            self.first_lst_in, self.segn_lst_in = (
                self.table[self.row_pos]["shoebox"].data,
                self.table[self.row_pos]["shoebox"].mask,
            )

            self.local_bbox = self.table[self.row_pos]["bbox"]

        except Exception:
            self.first_lst_in, self.segn_lst_in = None, None

    def _mi_list_of_wxbitmaps(self, re_scaling=False):
        if not re_scaling:
            if self.show_mask:
                self.lst_bmp_obj = wxbitmap_convert(self.first_lst_in, self.segn_lst_in)
            else:
                self.lst_bmp_obj = wxbitmap_convert(self.first_lst_in, None)
            return self.lst_bmp_obj.get_wxbitmap_lst(
                show_nums=self.show_nums, palette=self.palette, scale=self.scale
            )

        else:
            return self.lst_bmp_obj.scaling(scale=self.scale)

    def update_img_w_row_pos(self, num):
        self.row_pos = num
        self.assign_row_pos()
        self.bmp_lst = self._mi_list_of_wxbitmaps()
        self.panel_right.img_refresh(self.bmp_lst)

    def to_hide_nums(self):
        self.show_nums = False
        self.bmp_lst = self._mi_list_of_wxbitmaps()
        self.panel_right.img_refresh(self.bmp_lst)

    def to_show_nums(self):
        self.show_nums = True
        self.bmp_lst = self._mi_list_of_wxbitmaps()
        self.panel_right.img_refresh(self.bmp_lst)

    def to_show_mask(self):
        self.show_mask = True
        self.bmp_lst = self._mi_list_of_wxbitmaps()
        self.panel_right.img_refresh(self.bmp_lst)

    def to_hide_mask(self):
        self.show_mask = False
        self.bmp_lst = self._mi_list_of_wxbitmaps()
        self.panel_right.img_refresh(self.bmp_lst)

    def to_change_palette(self, palette_name=None):
        if palette_name is None:
            print("Something went wrong")
        else:
            self.palette = palette_name
            self.bmp_lst = self._mi_list_of_wxbitmaps()
            self.panel_right.img_refresh(self.bmp_lst)

    def to_re_zoom(self, rot_sn):
        if rot_sn > 0:
            for ntimes in range(int(math.fabs(rot_sn))):
                self.scale = self.scale * 1.05
                if self.scale > 3.0:
                    # Maximum possible zoom reached
                    self.scale = 3.0

        elif rot_sn < 0:
            for ntimes in range(int(math.fabs(rot_sn))):
                self.scale = self.scale * 0.95
                if self.scale < 0.2:
                    # Minimum possible zoom reached
                    self.scale = 0.2

        self.bmp_lst = self._mi_list_of_wxbitmaps(re_scaling=True)
        self.panel_right.img_refresh(self.bmp_lst)


class multi_img_scrollable(scroll_pan.ScrolledPanel):
    def __init__(self, outer_panel, i_bmp_in):
        super().__init__(outer_panel)
        self.parent_panel = outer_panel
        self.lst_2d_bmp = i_bmp_in
        self.SetBackgroundColour(wx.Colour(200, 200, 200))

        self.mainSizer = wx.BoxSizer(wx.VERTICAL)

        self.n_img = 0
        self.img_lst_v_sizer = wx.BoxSizer(wx.VERTICAL)

        self.set_scroll_content()

        self.mainSizer.Add(self.img_lst_v_sizer, 0, wx.CENTER | wx.ALL, 10)
        self.SetSizer(self.mainSizer)

        self.SetupScrolling()
        self.SetScrollRate(1, 1)
        self.Mouse_Pos_x = -1
        self.Mouse_Pos_y = -1
        self.old_Mouse_Pos_x = -1
        self.old_Mouse_Pos_y = -1
        self.Bind(wx.EVT_MOUSEWHEEL, self.OnMouseWheel)
        self.Bind(wx.EVT_MOTION, self.OnMouseMotion)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftButDown)
        self.Bind(wx.EVT_LEFT_UP, self.OnLeftButUp)
        self.Bind(wx.EVT_IDLE, self.OnIdle)
        self.scroll_rot = 0

    def OnLeftButDown(self, event):
        self.Bdwn = True
        self.old_Mouse_Pos_x, self.old_Mouse_Pos_y = event.GetPosition()

    def OnLeftButUp(self, event):
        self.Bdwn = False
        _ = event.GetPosition()

    def set_scroll_content(self):
        self.img_lst_v_sizer.Clear(True)

        for lst_1d in self.lst_2d_bmp:
            img_lst_hor_sizer = wx.BoxSizer(wx.HORIZONTAL)

            for i, i_bmp in enumerate(lst_1d):
                local_bitmap = wx.StaticBitmap(self, bitmap=i_bmp)
                if self.parent_panel.local_bbox is None:
                    slice_string = "Slice[" + str(i) + ":" + str(i + 1) + ", :, :]"
                else:
                    bbx = self.parent_panel.local_bbox
                    slice_string = "Image # " + str(bbx[4] + i)

                slice_sub_info_txt = wx.StaticText(self, -1, slice_string)

                sigle_slice_sizer = wx.BoxSizer(wx.VERTICAL)
                sigle_slice_sizer.Clear(True)

                sigle_slice_sizer.Add(
                    local_bitmap, proportion=0, flag=wx.ALIGN_CENTRE | wx.ALL, border=2
                )
                sigle_slice_sizer.Add(
                    slice_sub_info_txt,
                    proportion=0,
                    flag=wx.ALIGN_CENTRE | wx.ALL,
                    border=2,
                )
                img_lst_hor_sizer.Add(
                    sigle_slice_sizer,
                    proportion=0,
                    flag=wx.ALIGN_CENTER | wx.ALL,
                    border=2,
                )

            self.img_lst_v_sizer.Add(img_lst_hor_sizer, 0, wx.CENTER | wx.ALL, 10)

            self.n_img += 1

        self.parent_panel.Pframe.Layout()
        self.SetScrollRate(1, 1)

    def OnMouseMotion(self, event):

        self.Mouse_Pos_x, self.Mouse_Pos_y = event.GetPosition()

    def OnMouseWheel(self, event):
        # Getting amount of scroll steps to do
        sn_mov = math.copysign(1, float(event.GetWheelRotation()))
        self.scroll_rot += sn_mov

        # Getting relative position of scrolling area to keep after scrolling
        v_size_x, v_size_y = self.GetVirtualSize()
        self.Mouse_Pos_x, self.Mouse_Pos_y = event.GetPosition()

        View_start_x, View_start_y = self.GetViewStart()

        self.x_uni = float(View_start_x + self.Mouse_Pos_x) / float(v_size_x)
        self.y_uni = float(View_start_y + self.Mouse_Pos_y) / float(v_size_y)

    def img_refresh(self, i_bmp_new):

        self.lst_2d_bmp = i_bmp_new
        self.set_scroll_content()

    def OnIdle(self, event):
        if self.scroll_rot != 0:

            self.SetScrollRate(1, 1)

            self.parent_panel.to_re_zoom(self.scroll_rot)
            self.scroll_rot = 0
            v_size_x, v_size_y = self.GetVirtualSize()
            new_scroll_pos_x = float(self.x_uni * v_size_x - self.Mouse_Pos_x)
            new_scroll_pos_y = float(self.y_uni * v_size_y - self.Mouse_Pos_y)

            self.Scroll(new_scroll_pos_x, new_scroll_pos_y)

        else:
            if self.old_Mouse_Pos_x != -1 and self.old_Mouse_Pos_y != -1:
                # print "need to move IMG"
                pass

            self.old_Mouse_Pos_x = self.Mouse_Pos_x
            self.old_Mouse_Pos_y = self.Mouse_Pos_y


class buttons_panel(wx.Panel):
    def __init__(self, outer_panel):
        super().__init__(outer_panel)
        self.parent_panel = outer_panel

        Show_Its_CheckBox = wx.CheckBox(self, -1, "Show I nums")
        Show_Its_CheckBox.SetValue(True)
        Show_Its_CheckBox.Bind(wx.EVT_CHECKBOX, self.OnItsCheckbox)

        self.my_sizer = wx.BoxSizer(wx.VERTICAL)
        self.my_sizer.Add(Show_Its_CheckBox, proportion=0, flag=wx.ALIGN_TOP, border=5)

        if self.parent_panel.segn_lst_in is not None:
            Show_Msk_CheckBox = wx.CheckBox(self, -1, "Show Mask")
            Show_Msk_CheckBox.SetValue(True)
            Show_Msk_CheckBox.Bind(wx.EVT_CHECKBOX, self.OnMskCheckbox)

            self.my_sizer.Add(
                Show_Msk_CheckBox, proportion=0, flag=wx.ALIGN_TOP, border=5
            )

            masck_conv_str = "\n Mask Convention:"
            masck_conv_str += "\n [Valid]                      =  \\\\\\\\\\\\  "
            masck_conv_str += "\n [Foreground]           =  //////  "
            masck_conv_str += "\n [Background]          =  ||||||  "
            masck_conv_str += "\n [BackgroundUsed]  =  ------"

            label_mask = wx.StaticText(self, -1, masck_conv_str)
            self.my_sizer.Add(label_mask, proportion=0, flag=wx.ALIGN_TOP, border=5)

        label_palette = wx.StaticText(self, -1, "\nColour Palettes")

        self.RadButtb2w = wx.RadioButton(self, -1, "black2white")
        self.RadButtw2b = wx.RadioButton(self, -1, "white2black")
        self.RadButtha = wx.RadioButton(self, -1, "hot ascend")
        self.RadButthd = wx.RadioButton(self, -1, "hot descend")

        self.RadButtb2w.Bind(wx.EVT_RADIOBUTTON, self.OnButtb2w)
        self.RadButtw2b.Bind(wx.EVT_RADIOBUTTON, self.OnButtw2b)
        self.RadButtha.Bind(wx.EVT_RADIOBUTTON, self.OnButtha)
        self.RadButthd.Bind(wx.EVT_RADIOBUTTON, self.OnButthd)

        self.my_sizer.Add(label_palette, proportion=0, flag=wx.ALIGN_TOP, border=5)

        self.my_sizer.Add(self.RadButtb2w, proportion=0, flag=wx.ALIGN_TOP, border=5)
        self.my_sizer.Add(self.RadButtw2b, proportion=0, flag=wx.ALIGN_TOP, border=5)
        self.my_sizer.Add(self.RadButtha, proportion=0, flag=wx.ALIGN_TOP, border=5)
        self.my_sizer.Add(self.RadButthd, proportion=0, flag=wx.ALIGN_TOP, border=5)

        self.my_sizer.SetMinSize((50, 300))
        self.SetSizer(self.my_sizer)

    def OnButtb2w(self, event):
        self.OnButtUpdate()

    def OnButtw2b(self, event):
        self.OnButtUpdate()

    def OnButtha(self, event):
        self.OnButtUpdate()

    def OnButthd(self, event):
        self.OnButtUpdate()

    def OnButtUpdate(self):
        print("OnButtUpdate(self):")
        if self.RadButtb2w.GetValue():
            self.parent_panel.to_change_palette("black2white")
        elif self.RadButtw2b.GetValue():
            self.parent_panel.to_change_palette("white2black")
        elif self.RadButtha.GetValue():
            self.parent_panel.to_change_palette("hot ascend")
        else:
            self.parent_panel.to_change_palette("hot descend")

    def OnItsCheckbox(self, event):

        if event.IsChecked():
            self.parent_panel.to_show_nums()
        else:
            self.parent_panel.to_hide_nums()

    def OnMskCheckbox(self, event):
        if event.IsChecked():
            self.parent_panel.to_show_mask()
        else:
            self.parent_panel.to_hide_mask()


def bigger_size(str_label, lst_col):
    lng_label_ini = len(str_label)
    lng_lst_col = len(lst_col)
    lng_final = lng_label_ini

    if lng_lst_col < 30:
        pos_lst = list(range(lng_lst_col))

    else:
        pos_lst = list(range(15))
        pos_lst += list(range(lng_lst_col - 15, lng_lst_col))

    lng_cel_zero = 0
    for pos in pos_lst:
        lng_cel_pos = len(str(lst_col[pos]))
        if lng_cel_pos > lng_cel_zero:
            lng_cel_zero = lng_cel_pos

        if lng_cel_zero > lng_label_ini:
            lng_final = lng_cel_zero

    return lng_final
