
from __future__ import absolute_import, division, print_function

import pickle

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib import patches

import six
import numpy as np

from autobahn.wamp.exception import SerializationError

from hcam_widgets.tkutils import get_root
from hcam_widgets.widgets import IntegerEntry
from hcam_widgets.mimic import Mimic
from hcam_devices.models.slide import BLOCK_POS, UNBLOCK_POS

from twisted.internet.defer import inlineCallbacks

if not six.PY3:
    import Tkinter as tk
else:
    import tkinter as tk


class SlideFrame(tk.LabelFrame, Mimic):
    """
    Self-contained widget to deal with the focal plane slide
    """

    def __init__(self, master, show_mimic=True):
        """
        master  : containing widget
        """
        Mimic.__init__(self, height=200, width=100)
        tk.LabelFrame.__init__(
            self, master, text='Focal plane slide', padx=10, pady=4)

        # Top for table of buttons
        top = tk.Frame(self)

        # Define the buttons
        width = 8
        self.connect = tk.Button(top, fg='black', text='conn', width=width,
                                 command=lambda: self.action('connection.connect'))

        self.home = tk.Button(top, fg='black', text='home', width=width,
                              command=lambda: self.action('stage.home'))

        self.block = tk.Button(top, fg='black', text='block', width=width,
                               command=lambda: self.action('block'))

        self.unblock = tk.Button(top, fg='black', text='unblock', width=width,
                                 command=lambda: self.action('unblock'))

        self.gval = IntegerEntry(top, UNBLOCK_POS, None, True, width=4)

        self.goto = tk.Button(top, fg='black', text='goto', width=width,
                              command=lambda: self.action('goto',
                                                          self.gval.value()))

        self.reset = tk.Button(top, fg='black', text='reset', width=width,
                               command=lambda: self.action('reset'))

        self.stop = tk.Button(top, fg='black', text='stop', width=width,
                              command=lambda: self.action('stage.stop'))

        self.enable = tk.Button(top, fg='black', text='enable', width=width,
                                command=lambda: self.action('enable'))

        self.disable = tk.Button(top, fg='black', text='disable', width=width,
                                 command=lambda: self.action('disable'))

        self.restore = tk.Button(top, fg='black', text='restore', width=width,
                                 command=lambda: self.action('restore'))

        # arrange the permanent ones
        self.connect.grid(row=0, column=0)
        self.home.grid(row=0, column=1)
        self.block.grid(row=0, column=2)
        self.unblock.grid(row=1, column=2)
        self.goto.grid(row=1, column=0)
        self.gval.grid(row=1, column=1)

        # set others according to expertlevel
        self.setExpertLevel()

        g = get_root(self).globals

        # widget for messages
        self.label = tk.Text(top, height=4, width=30, bg=g.COL['log'])
        self.label.configure(state=tk.NORMAL, font=g.ENTRY_FONT)
        self.label.grid(row=4, column=0, columnspan=3)

        # mimic
        self.showing_mimic = show_mimic
        if show_mimic:
            self.canvas = FigureCanvasTkAgg(self.figure, top)
            self.canvas.get_tk_widget().grid(row=5, column=0, columnspan=3)

        top.pack(pady=2)

    def update_mimic(self, telemetry):
        """
        Use current telemetry to update Mimic of the slide
        """
        if not telemetry or not self.showing_mimic:
            return

        try:
            pos = telemetry['position']['current'].value
        except Exception:
            pos = telemetry['position']['current']

        state = telemetry['state']
        connection_state = state['connection']
        stage_state = state['stage']

        ccd_color, slide_color = ('b', 'k')  # chip slide (stopped)
        if ('error' in connection_state or 'offline' in connection_state
                or 'homed' not in stage_state):
            slide_color = 'r'
        elif 'moving' in stage_state:
            slide_color = 'y'

        ll = (0, 0)
        width = 2048
        height = 1024
        ccd = patches.Rectangle(ll, width, height, color=ccd_color, fill=False)
        slide_origin = 3500
        slide_corners = np.array([
            ((-50, slide_origin + 50)),  # top left
            (-50, pos),  # bottom left
            (2048+50, pos),  # bottom right
            (2048+50, slide_origin + 50)  # top right
        ])
        slide = patches.Polygon(slide_corners, closed=False, alpha=0.8, color=slide_color)
        self.ax.clear()
        self.ax.add_patch(ccd)
        self.ax.add_patch(slide)
        self.ax.set_xlim(-80, 2048 + 80)
        self.ax.set_ylim(-200, slide_origin + 100)
        self.ax.set_aspect('equal')
        self.ax.set_axis_off()
        self.canvas.draw()

    def setExpertLevel(self):
        """
        Modifies widget according to expertise level, which in this
        case is just matter of hiding or revealing the LED option
        and changing the lower limit on the exposure button.
        """
        level = 1

        if level == 0:
            self.reset.grid_forget()
            self.enable.grid_forget()
            self.disable.grid_forget()
            self.restore.grid_forget()
            self.stop.grid_forget()
        else:
            self.stop.grid(row=2, column=0)
            self.disable.grid(row=2, column=1)
            self.enable.grid(row=2, column=2)
            self.reset.grid(row=3, column=0)
            self.restore.grid(row=3, column=1)

    @inlineCallbacks
    def action(self, *comm):
        """
        Send a command to the focal plane slide
        """
        session = get_root(self).globals.session
        if not session:
            self.print_message('no session')
            return

        if comm[0] == 'block':
            topic = "hipercam.slide.rpc.stage.move"
            self.set_slide_target_position(BLOCK_POS)
        elif comm[0] == 'unblock':
            topic = "hipercam.slide.rpc.stage.move"
            self.set_slide_target_position(UNBLOCK_POS)
        elif comm[0] == 'goto':
            topic = "hipercam.slide.rpc.stage.move"
            self.set_slide_target_position(int(comm[1]))
        else:
            topic = f"hipercam.slide.rpc.{comm[0]}"

        try:
            yield session.call(topic)
        except SerializationError:
            pass

    def set_slide_target_position(self, pos):
        topic = "hipercam.slide.target_position"
        session = get_root(self).globals.session
        if session:
            session.publish(topic, pos)

    def send_message(self, topic, msg):
        session = get_root(self).globals.session
        if session:
            session.publish(topic, msg)

    def print_message(self, msg):
        self.label.delete(1.0, tk.END)
        self.label.insert(tk.END, msg+'\n')

    def on_telemetry(self, package_data):
        telemetry = pickle.loads(package_data)

        try:
            pos = telemetry['position']['current'].value
            targ = telemetry['position']['target'].value
        except Exception:
            pos = telemetry['position']['current']
            targ = telemetry['position']['target']

        state = telemetry['state']
        if 'error' in state['connection'] or 'offline' in state['connection']:
            self.connect.config(
                command=lambda: self.action('connection.connect'),
                text='conn')
        else:
            self.connect.config(
                command=lambda: self.action('connection.disconnect'),
                text='disconn')

        str = f"{telemetry['timestamp'].iso}:\n"
        status = "/".join(state['stage'][4:])
        str += f"pos: curr={pos:.0f}, targ={targ:.0f}\n{status}\n\n"
        self.update_mimic(telemetry)
        self.print_message(str)
