#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2024 Illinois Institute of Technology
#
# This software was developed by Illinois Institute of Technology under NSF award 2346499 ("CREASE"),
# as part of the NSF CCRI-CISE (Community Research Infrastructure) program.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
# Author: Nik Sultana

import ipywidgets as widgets
from ipywidgets import interact, interact_manual, Textarea, VBox, Layout, Button, Box, FloatText, Textarea, Dropdown, Label, IntSlider
from IPython.display import display
import os
import queue
import random
import threading
import time

random.seed(a=1)

class EvidentialScreen:
    def __init__(self, ticker_width=50, changes_shown=4, box_width='790px', max_change_description_length=70, source=None):
        self.output = widgets.Output()

        self.ticker_width = ticker_width
        self.changes_shown = changes_shown
        self.box_width = box_width
        self.source = source
        self.max_change_description_length = max_change_description_length

        self.changes = []
        self.traffic = []

        self.row = None
        self.col = None

        self.row = []
        for i in range(self.ticker_width):
            self.row.append(widgets.Label(' '))
        self.col = []
        for i in range(self.changes_shown):
            self.col.append(widgets.Label(' '))

        self.stopped = False

        self.start_button = widgets.Button(description="Start")
        self.stop_button = widgets.Button(description="Stop",
                                     disabled=True,)
        self.reset_button = widgets.Button(description="Reset")

        self.start_button.on_click(self.start_button_clicked)
        self.stop_button.on_click(self.stop_button_clicked)
        self.reset_button.on_click(self.reset_button_clicked)

        display(widgets.HTML("<br/>"))

        self.render()

        buttons = Box([self.start_button, self.stop_button, self.reset_button], layout=Layout(
            display='flex',
            flex_flow='row',
            align_items='stretch',
        ))
        display(buttons)

    def render(self):
        buffered_changes = self.changes[-self.changes_shown:]
        if len(buffered_changes) < self.changes_shown:
            for i in range(self.changes_shown - len(buffered_changes)):
                buffered_changes.append(" ")

        pre_buffered_traffic = self.traffic[-self.ticker_width:]
        if len(pre_buffered_traffic) < self.ticker_width:
            for i in range(self.ticker_width - len(pre_buffered_traffic)):
                pre_buffered_traffic.append(" ")
        buffered_traffic = pre_buffered_traffic
        buffered_traffic.reverse()

        for i in range(self.ticker_width):
            self.row[i].value = buffered_traffic[i]
        form = Box(self.row, layout=Layout(
            display='flex',
            flex_flow='row',
            align_items='stretch',
        ))

        toplabel = widgets.Label('Traffic ticker:')
        toplabel.style.font_weight = 'bold'
        toplabel.style.font_size = 'large'

        toprow = Box([toplabel, form], layout=Layout(
            display='flex',
            flex_flow='row',
            border='solid 1px',
            align_items='stretch',
        ))

        bottomlabel = widgets.Label('Changes:')
        bottomlabel.style.font_weight = 'bold'
        bottomlabel.style.font_size = 'large'

        box1 = VBox([bottomlabel], layout={'height': '100%'}) # , 'width': '50%'

        for i in range(self.changes_shown):
            self.col[i].value = buffered_changes[i]

        change_list = Box(self.col, layout=Layout(
            display='flex',
            flex_flow='column',
            align_items='stretch',
        ))

        box2 = VBox([change_list], layout={'height': '100%'}) # , 'width': '50%'

        bottomrow = Box([box1, box2], layout=Layout(
            display='flex',
            flex_flow='row',
            border='solid 1px',
            align_items='stretch',
        ))

        outer_form = Box([toprow, bottomrow], layout=Layout(
            display='flex',
            flex_flow='column',
            border='solid 1px',
            align_items='stretch',
            width=self.box_width
        ))

        display(outer_form)

    def update_display(self):
        buffered_changes = self.changes[-self.changes_shown:]
        if len(buffered_changes) < self.changes_shown:
            for i in range(self.changes_shown - len(buffered_changes)):
                buffered_changes.append("")

        pre_buffered_traffic = self.traffic[-self.ticker_width:]
        if len(pre_buffered_traffic) < self.ticker_width:
            for i in range(self.ticker_width - len(pre_buffered_traffic)):
                pre_buffered_traffic.append("")
        buffered_traffic = pre_buffered_traffic
        buffered_traffic.reverse()
        buffered_traffic_kept = []
        for symb in buffered_traffic:
            if ' ' != symb and '' != symb:
                buffered_traffic_kept.append(symb)
        buffered_traffic = buffered_traffic_kept
        if len(buffered_traffic) < self.ticker_width:
            for i in range(self.ticker_width - len(buffered_traffic)):
                buffered_traffic.append(" ")

        unchanged = u'\u2591'
        changed = u'\u2593'

        with self.output:
            for i in range(self.ticker_width):
                if '.' == buffered_traffic[i]:
                    self.row[i].style.text_color = 'black'
                    self.row[i].value = unchanged
                elif 'x' == buffered_traffic[i]:
                    self.row[i].style.text_color = 'blue'
                    self.row[i].value = changed
                else: self.row[i].value = ' '
            for i in range(self.changes_shown):
                if len(buffered_changes[i]) <= self.max_change_description_length:
                    self.col[i].value = buffered_changes[i]
                else:
                    self.col[i].value = buffered_changes[i][0:self.max_change_description_length - 3] + "..."

    def thread_start(self):
        if None == self.source:
            for i in range(100):
                if self.stopped: break
                time.sleep(float(random.randrange(5))/10)
                if random.randrange(50) > 48: self.traffic.append('x')
                else: self.traffic.append('.')
                if 'x' == self.traffic[-1]:
                    self.changes.append(str(1 + len(self.changes)) + ": " + str(len(self.changes)) + " -> " + str(1 + len(self.changes)))
                self.update_display()
        else:
            while not self.stopped:
                try:
                    elem = self.source.get(block=False, timeout=None)
                    if '.' == elem: self.traffic.append('.')
                    else:
                        self.traffic.append('x')
                        self.changes.append(str(1 + len(self.changes)) + ": " + elem)
                    self.update_display()
                except queue.Empty:
                    pass

        self.start_button.disabled=False
        self.stop_button.disabled=True

    def start_button_clicked(self, b):
        self.stopped = False
        self.start_button.disabled=True
        self.stop_button.disabled=False
        thread = threading.Thread(target=self.thread_start, args=())
        thread.start()
        self.start_button.disabled=True
        self.stop_button.disabled=False

    def stop_button_clicked(self, b):
        self.stopped = True
        self.start_button.disabled=False
        self.stop_button.disabled=True

    def reset_button_clicked(self, b):
        self.changes = []
        self.traffic = []

        with self.output:
            for i in range(len(self.row)):
                self.row[i].style.text_color = 'black'
                self.row[i].value = ''
            for i in range(len(self.col)):
                self.col[i].value = ''


class FIFO_Dest:
    def __init__(self, source, fifo_name='demo.fifo'):
        self.source = source
        self.fifo_name = fifo_name

        self.relay_thread_stop = False

    def relay_thread_start(self):
        fifo = os.open(self.fifo_name, os.O_RDONLY)
        while not self.relay_thread_stop:
            orig_line = os.read(fifo, 200)
            if '' != orig_line and b'' != orig_line and '>' == orig_line.decode('UTF-8')[0]:
                lines = orig_line.decode('UTF-8').split('\n')
                for line in lines:
                    line = line.strip('\r')
                    if '' == line: continue
                    self.source.put(line[1:])

    def start_relay(self):
        self.relay_thread_stop = False
        self.relay_thread_start = threading.Thread(target=self.relay_thread_start, args=())
        self.relay_thread_start.start()

    def stop_relay(self):
        self.relay_thread_stop = True
