# ----------------------------------------------------------------------
# Titulo: Pantalla_Interfaz_V2.py
# Autor: Aldo Aguilar Nadalini
# Fecha: 20 de abril de 2020
# Descripcion: Interfaz grafica para procesar datos HEX de beacons
#              recolectados de nanosatelite Quetzal-1 (Primer satelite
#              guatemalteco)
# ----------------------------------------------------------------------

# Librerias ------------------------------------------------------------

# Local libraries
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QFileDialog, QInputDialog, QLineEdit, QMessageBox
from PyQt5.QtCore import QThread, pyqtSignal
from datetime import datetime
from collections import deque

# Thrird-party libraries
import pyqtgraph as pg
import zmq

# Own libraries
import Modulo_HEX as mh
from Pantalla_CDHS import Pantalla_CDHS
from Pantalla_RAM import Pantalla_RAM
from Qt_Interfaz import Ui_MainWindow

# **********************************************************************
#               CLASE DE PANTALLA DE INTERFAZ QUETZAL-1
# **********************************************************************

# Pantalla de interfaz hereda de QMainWindow y Ui_MainWindow (Qt
# Designer)
class Pantalla_Interfaz(QtWidgets.QMainWindow, Ui_MainWindow):
	"""
	Clase que engloba las funcionalidades de la pantalla de la interfaz
	grafica para procesar la telemetria recibida del nanosatelite 1U
	Quetzal-1 (primer satelite guatemalteco)

	Atributos
	----------
	current_time : str
		Tiempo actual 'hh-mm-ss'
	current_date : str
		Fecha actual 'yyyy-MM-dd'
	selected_port : bool
		Bandera de puerto TCP seleccionado
	hexSelected : bool
		Bandera de HEX File seleccionado
	playHex : bool
		Bandera de Comenzar en estado PLAY GUI
	steppingBackwards : bool
		Bandera de Estado RETROCEDER GUI
	pen : pyqtgraph.pen
		Marcador para pintar graficas en interfaz
	beacon_counter : int
		Contador No.1 de beacon (cuenta individualmente cada dato de
		beacon)
	num_beacon : int
		Contador No.2 de beacon (cuenta cada paquete de beacon leido)
	beacon_data : list
		Variable que almacena los datos de beacon procesados de linea de
		archivo o linea recibida por puerto TCP

	Métodos
	-------
	connect_slots()
		Realizar conexion de seniales de modificacion de campos en
		ventana con slots de diferentes funciones de clase
	configurar_graficas()
		Funcion para ejecutar la configuracion de las GraphicsView para
		que desplieguen correctamente las graficas de los datos de
		telemetria del satelite
	step_backwards()
		Funcion que permite regresar a linea anterior de datos de
		telemetria leidos desde archivo .dat. Esto es posible cuando la
		interfaz se encuentra en Modo Manual (Boton de pausa presionado)
	toggle_play_pausa()
		Funcion para cambiar entre Modo Manual (pausa) y Modo Automatico
		(play) de interpretacion de los datos procesados del archivo
		.dat. Si se encuentra en Modo Manual, se puede regresar o
		avanzar en los datos del archivo utilizando el boton Step
		Backwards y Step Forwards. En Modo Automatico, se va iterando
		a travez de todos los datos del archivo utilizando el timer (1s)
	step_forward()
		Funcion que permite avanzar a linea siguiente de datos de
		telemetria leidos desde archivo .dat. Esto es posible cuando la
		interfaz se encuentra en Modo Manual (Boton de pausa presionado)
	select_HEX()
		Funcion para realizar la seleccion del archivo .dat del cual se
		extraen los datos HEX de telemetria del satelite para su
		despliegue en la interfaz grafica
	abrir_parametros_RAM()
		Funcion para desplegar ventana en donde se muestran los
		parametros guardados en memoria RAM del satelite
	abrir_telemetria_CDHS()
		Funcion para desplegar ventana en donde se muestran los
		datos de telemetria del modulo CDHS (On-board computer)
	reset_HEX()
		Funcion para comenzar a desplegar datos de telemetria del
		satelite nuevamente desde el principio del documento .dat
		procesado
	conectar_puerto()
		Funcion para realizar la conexion a un puerto TCP para la
		recepcion de datos de telemetria de Quetzal-1 ya decodificados
		por AX25 Decode
	update_incoming_beacon(beacon)
		Funcion para cargar datos recibidos por puerto TCP a funcion
		de parsing de los datos para su consecuente despliegue en GUI
	updatedata()
		Funcion para lectura automatica de cada linea en archivo .dat
		y su procesamiento antes de desplegar la telemetria en interfaz
	update_GUI()
		Funcion para actualizar los datos desplegados en widgets de
		interfaz grafica. Se cargan los datos procesados en updatedata()
		si interfaz se encuentra en Modo Automatico. De lo contrario,
		se cargan los datos procesados en step_backwards() o
		step_forward() en Modo Manual
	"""

	def __init__(self, args, parent=None):
		"""
		Constructor

		Parametros
		----------
		args : varios
			Parametros varios
		parent : None
			Parent class de ventana
		"""

		QtWidgets.QMainWindow.__init__(self, parent)

		# Configuracion estetica de MainWindow
		self.setupUi(self)
		self.setStyleSheet("#MainWindow { border-image: url(./resources/background.jpg) 0 0 0 0 stretch stretch; }")
		self.setWindowIcon(QtGui.QIcon('./resources/emblema_Quetzal-1.png'))       # Configurar imagen de fondo e icono de aplicacion
		self.windows_icon = QtGui.QIcon('./resources/emblema_Quetzal-1.png')

		_maximizado = False
		if (_maximizado == False):
			# Resize de ventana (97.5% de ancho de pantalla, 90% de alto
			# de pantalla)
			screen = QtWidgets.QDesktopWidget().screenGeometry(-1)
			if (screen.width() > 1500):
				width_factor = 0.90
				height_factor = 0.85
			else:
				width_factor = 0.975
				height_factor = 0.90
			#self.setFixedSize(QtCore.QSize(screen.width() * width_factor, screen.height() * height_factor))
			self.resize(QtCore.QSize(screen.width() * width_factor, screen.height() * height_factor))
			self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, True)

			# Centrado de ventana en pantalla
			qr = self.frameGeometry()
			cp = QtWidgets.QDesktopWidget().availableGeometry().center()
			qr.moveCenter(cp)
			self.move(qr.topLeft())
		else:
			self.showMaximized()
			self.setWindowFlag(QtCore.Qt.WindowMaximizeButtonHint, False)    		# Deshabilitar opcion de maximizado de ventana

		# Inicializacion de variables de clase
		now = datetime.now()
		self.current_time = now.strftime("%H:%M:%S")                    # Obtener tiempo actual
		self.current_date = now.strftime("%Y-%m-%d")                    # Obtener fecha actual
		self.selected_port = False									    # Puerto aun no seleccionado
		self.hexSelected = False           								# HEX File aun no seleccionado
		self.playHex = True                								# Comenzar en estado PLAY GUI
		self.steppingBackwards = False     								# Estado RETROCEDER GUI desactivado
		C = pg.hsvColor(0.3,1.0,1.0)
		self.pen = pg.mkPen(color = C,width = 2.5)						# Marcador para pintar graficas en interfaz
		self.beacon_counter = 0                                         # Contador No.1 de beacon (cuenta individualmente cada dato de beacon)
		self.num_beacon = 0                                             # Contador No.2 de beacon (cuenta cada paquete de beacon leido)
		self.beacon_data = 0											# Variable que almacena los datos de beacon procesados de linea de archivo o linea recibida por puerto TCP

		# CDHS Dialog
		self.pantalla_CDHS = Pantalla_CDHS(parent=self)

		# RAM Dialog
		self.pantalla_RAM = Pantalla_RAM(parent=self)

		# Configuracion de timer que actualiza data
		self.timer = QtCore.QTimer()
		self.timer.timeout.connect(self.updatedata)
		self.timer.start(args.interval)

		# Comenzar con todos los botones desactivados (excepto Select
		# HEX y Conectar a puerto)
		self.pushButton.setEnabled(False)
		self.pushButton_2.setEnabled(False)
		self.pushButton_3.setEnabled(False)
		self.pushButton_5.setEnabled(False)
		self.pushButton_6.setEnabled(False)
		self.pushButton_7.setEnabled(False)

		# Ejecutar conexion de seniales
		self.connect_slots()

		# Configuracion de graficas
		self.configurar_graficas()
		print('Starting QUETZAL-1 UHF Desktop')

	def connect_slots(self):
		"""
		Funcion para conectar seniales a botones de ventana

		ACTIVADO POR: Constructor
		"""

		self.pushButton.clicked.connect(self.step_backwards)
		self.pushButton_2.clicked.connect(self.toggle_play_pausa)
		self.pushButton_3.clicked.connect(self.step_forward)
		self.pushButton_4.clicked.connect(self.select_HEX)
		self.pushButton_5.clicked.connect(self.abrir_parametros_RAM)
		self.pushButton_6.clicked.connect(self.abrir_telemetria_CDHS)
		self.pushButton_7.clicked.connect(self.reset_HEX)
		self.pushButton_8.clicked.connect(self.conectar_puerto)

	def configurar_graficas(self):
		"""
		Funcion para ejecutar la configuracion de las GraphicsView para
		que desplieguen correctamente las graficas de los datos de
		telemetria del satelite

		ACTIVADO POR: Constructor
		"""

		# Inicializar fondo negro y graficas verde claro
		pg.setConfigOption('background', 'k')
		pg.setConfigOption('foreground', 'g')

		# Configuracion de Grafica No.1 (Voltaje de Solar Channel)
		self.graphicsView = pg.PlotWidget(self.centralwidget)
		self.gridLayout_4.addWidget(self.graphicsView, 1, 0, 1, 1)
		self.graphicsView.setObjectName("graphicsView")
		self.graphicsView.showGrid(x = True, y = True)
		self.graphicsView.setYRange(3,5)
		ax = self.graphicsView.getAxis('left')
		ax.setTicks([[(3, '3.0'), (3.5, '3.5'), (4, '4.0'), (4.5, '4.5'), (5.0, '5.0')]])
		self.curve = self.graphicsView.plot()
		self.L = deque([0], maxlen = 50)
		self.t = deque([0], maxlen = 50)
		self.graphicsView.setLabel('bottom','- V')

		# Configuracion de Grafica No.2 (Corriente de Solar Channel)
		self.graphicsView_2 = pg.PlotWidget(self.centralwidget)
		self.gridLayout_4.addWidget(self.graphicsView_2, 1, 1, 1, 1)
		self.graphicsView_2.setObjectName("graphicsView_2")
		self.graphicsView_2.showGrid(x = True, y = True)
		self.graphicsView_2.setYRange(0,1500)
		ax_2 = self.graphicsView_2.getAxis('left')
		ax_2.setTicks([[(0, '0'), (250, '250'), (500, '500'), (750, '750'), (1000, '1000'), (1250, '1250'), (1500, '1500'), (1750, '1750')]])
		self.curve_2 = self.graphicsView_2.plot()
		self.L_2 = deque([0], maxlen = 50)
		self.t_2 = deque([0], maxlen = 50)
		self.graphicsView_2.setLabel('bottom','- mA')

		# Configuracion de Grafica No.3 (Voltaje de 3V3 Channel)
		self.graphicsView_3 = pg.PlotWidget(self.centralwidget)
		self.gridLayout_4.addWidget(self.graphicsView_3, 1, 2, 1, 1)
		self.graphicsView_3.setObjectName("graphicsView_3")
		self.graphicsView_3.showGrid(x = True, y = True)
		self.graphicsView_3.setYRange(3,5)
		ax_3 = self.graphicsView_3.getAxis('left')
		ax_3.setTicks([[(3, '3.0'), (3.5, '3.5'), (4, '4.0'), (4.5, '4.5'), (5.0, '5.0')]])
		self.curve_3 = self.graphicsView_3.plot()
		self.L_3 = deque([0], maxlen = 50)
		self.t_3 = deque([0], maxlen = 50)
		self.graphicsView_3.setLabel('bottom','- V')

		# Configuracion de Grafica No.4 (Corriente de 3V3 Channel)
		self.graphicsView_4 = pg.PlotWidget(self.centralwidget)
		self.gridLayout_4.addWidget(self.graphicsView_4, 1, 3, 1, 1)
		self.graphicsView_4.setObjectName("graphicsView_4")
		self.graphicsView_4.showGrid(x = True, y = True)
		self.graphicsView_4.setYRange(0,1500)
		ax_4 = self.graphicsView_4.getAxis('left')
		ax_4.setTicks([[(0, '0'), (250, '250'), (500, '500'), (750, '750'), (1000, '1000'), (1250, '1250'), (1500, '1500'), (1750, '1750')]])
		self.curve_4 = self.graphicsView_4.plot()
		self.L_4 = deque([0], maxlen = 50)
		self.t_4 = deque([0], maxlen = 50)
		self.graphicsView_4.setLabel('bottom','- mA')

	# ---------------------- FUNCIONES DE BOTONES ----------------------

	def step_backwards(self):
		"""
		Funcion que permite regresar a linea anterior de datos de
		telemetria leidos desde archivo .dat. Esto es posible cuando la
		interfaz se encuentra en Modo Manual (Boton de pausa presionado)

		ACTIVADO POR: Click en Step Backwards |<
		"""

		# Ejecutar accion si modo manual esta activado
		if (self.hexSelected == True and self.playHex == False):

			# Reactivar boton de step-forward
			self.pushButton_3.setEnabled(True)

			# FakeBeacon para capturar datos desechados de las graficas
			# y lograr que se vuelvan a agregar al inicio de la misma
			# simulando efecto de retroceso
			fakebeacon_counter = self.beacon_counter - (51 * self.beacon_length)
			num_fakebeacon = self.num_beacon - 51

			# Si ya no hay datos para retroceder, no actualizar
			# FakeBeacon
			self.stopAddingFakebeacon = False
			if (num_fakebeacon < 0):
				self.stopAddingFakebeacon = True

			# Movimiento de lectura en retroceso de beacon a travez de
			# HEX
			self.beacon_counter = self.beacon_counter - (2 * self.beacon_length)
			self.num_beacon = self.num_beacon - 2

			# Si aun quedan beacons para retroceder
			if (self.num_beacon >= 0):
				# Lectura de HEX
				self.beacon_data = mh.beacon_decode(self.hex_list, self.beacon_counter, self.num_beacon, False)
				self.fakebeacon_data = 0
				if (self.stopAddingFakebeacon == False):
					self.fakebeacon_data = mh.beacon_decode(self.hex_list, fakebeacon_counter, num_fakebeacon, False)

				# Actualizar interfaz grafica en modo retroceso
				self.steppingBackwards = True
				self.update_GUI()
				self.steppingBackwards = False
				self.pantalla_CDHS.update_LCD(self.beacon_data)
				self.pantalla_RAM.update_LCD(self.beacon_data)
			else:
				# Si ya no se puede retroceder, deshabilitar boton de
				# step backward hasta que se habilite modo PLAY GUI o se
				# ejecute un step forward
				self.pushButton.setEnabled(False)
				self.beacon_counter = 0
				self.num_beacon = 0

			# Movimiento de lectura de beacon a travez de HEX
			self.beacon_counter = self.beacon_counter + self.beacon_length
			self.num_beacon += 1

	def toggle_play_pausa(self):
		"""
		Funcion para cambiar entre Modo Manual (pausa) y Modo Automatico
		(play) de interpretacion de los datos procesados del archivo
		.dat. Si se encuentra en Modo Manual, se puede regresar o
		avanzar en los datos del archivo utilizando el boton Step
		Backwards y Step Forwards. En Modo Automatico, se va iterando
		a travez de todos los datos del archivo utilizando el timer (1s)

		ACTIVADO POR: Click en Boton Pausa/Play
		"""

		if (self.playHex == True):
			# Estado de Pausa de analisis automatico
			self.playHex = False
			# Cambiar icono de boton a simbolo de Play
			icon = QtGui.QIcon()
			icon.addPixmap(QtGui.QPixmap("./resources/Play2.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
			self.pushButton_2.setIcon(icon)								# Cambiar icono de Boton Play/Pausa a simbolo de Play
			self.pushButton.setEnabled(True)      					    # Habilitar boton de Step Backward
			self.pushButton_3.setEnabled(True)      					# Habilitar boton de Step Forward
			self.pushButton_7.setEnabled(False)     					# Deshabilitar boton de Reset Hex
		else:
			# Estado de Play de analisis automatico
			self.playHex = True
			# Cambiar icono de boton a simbolo de Pausa
			icon = QtGui.QIcon()
			icon.addPixmap(QtGui.QPixmap("./resources/Pause.ico"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
			self.pushButton_2.setIcon(icon)								# Cambiar icono de Boton Play/Pausa a simbolo de Pausa
			self.pushButton.setEnabled(False)     						# Deshabilitar boton de Step Backward
			self.pushButton_3.setEnabled(False)     					# Deshabilitar boton de Step Forward
			self.pushButton_7.setEnabled(True)      					# Habilitar boton de Reset Hex

	def step_forward(self):
		"""
		Funcion que permite avanzar a linea siguiente de datos de
		telemetria leidos desde archivo .dat. Esto es posible cuando la
		interfaz se encuentra en Modo Manual (Boton de pausa presionado)

		ACTIVADO POR: Click en Step Forwards >|
		"""

		# Ejecutar accion si modo manual esta activado
		if (self.hexSelected == True and self.playHex == False):

			# Reactivar boton de step-backward
			self.pushButton.setEnabled(True)

			# Mientras aun hayan beacons por leer
			if (self.num_beacon < self.beacon_amount):

			  # Lectura de HEX
			  self.beacon_data = mh.beacon_decode(self.hex_list, self.beacon_counter, self.num_beacon, False)

			  # Movimiento de lectura de beacon a travez de HEX
			  self.beacon_counter = self.beacon_counter + self.beacon_length
			  self.num_beacon += 1

			  # Actualizar interfaz grafica
			  self.update_GUI()
			  self.pantalla_CDHS.update_LCD(self.beacon_data)
			  self.pantalla_RAM.update_LCD(self.beacon_data)
			else:
			  # Si ya no hay beacons nuevos, deshabilitar boton de step
			  # forward hasta que se ejecute un step backward
			  self.pushButton_3.setEnabled(False)

	def select_HEX(self):
		"""
		Funcion para realizar la seleccion del archivo .dat del cual se
		extraen los datos HEX de telemetria del satelite para su
		despliegue en la interfaz grafica

		ACTIVADO POR: Click en Select HEX
		"""

		if (self.hexSelected == False):
			# Seleccionar archivo en buscador de files
			filedialog = QtWidgets.QFileDialog()
			fname = QFileDialog.getOpenFileName(filedialog, 'Open file', '/home')
			# Procesar archivo para generar listado HEX
			self.hex_list = mh.HEX_select(fname[0])
			self.beacon_length = 137
			self.beacon_amount = len(self.hex_list)/self.beacon_length
			self.beacon_counter = 0
			self.num_beacon = 0
			self.beacon_data = 0
			# HEX File seleccionado
			self.hexSelected = True
			# Habilitar botones de GUI para analizar datos
			self.pushButton.setEnabled(False)
			self.pushButton_2.setEnabled(True)
			self.pushButton_3.setEnabled(False)
			self.pushButton_4.setEnabled(False)
			self.pushButton_5.setEnabled(True)
			self.pushButton_6.setEnabled(True)
			self.pushButton_7.setEnabled(True)
			self.pushButton_8.setEnabled(False)

	def abrir_parametros_RAM(self):
		"""
		Funcion para desplegar ventana en donde se muestran los
		parametros guardados en memoria RAM del satelite

		ACTIVADO POR: Click en RAM Params
		"""

		if (self.hexSelected or (self.selected_port and self.beacon_data != 0)):
			self.pantalla_RAM.update_LCD(self.beacon_data)
			self.pantalla_RAM.show()

	def abrir_telemetria_CDHS(self):
		"""
		Funcion para desplegar ventana en donde se muestran los
		datos de telemetria del modulo CDHS (On-board computer)

		ACTIVADO POR: Click en CDHS Telemetry
		"""

		if (self.hexSelected or (self.selected_port and self.beacon_data != 0)):
			self.pantalla_CDHS.update_LCD(self.beacon_data)
			self.pantalla_CDHS.show()

	def reset_HEX(self):
		"""
		Funcion para comenzar a desplegar datos de telemetria del
		satelite nuevamente desde el principio del documento .dat
		procesado

		ACTIVADO POR: Click en Reset HEX
		"""

		# Reiniciar contadores de beacons
		self.beacon_counter = 0
		self.num_beacon = 0

		# Resetear datos desplegados en Grafica No.1 (Voltaje de Solar
		# Channel)
		self.L = deque([0], maxlen = 50)
		self.t = deque([0], maxlen = 50)
		self.curve.setData(self.t, self.L, pen = self.pen)

		# Resetear datos desplegados en Grafica No.2 (Corriente de Solar
		# Channel)
		self.L_2 = deque([0], maxlen = 50)
		self.t_2 = deque([0], maxlen = 50)
		self.curve_2.setData(self.t_2, self.L_2, pen = self.pen)

		# Resetear datos desplegados en Grafica No.3 (Voltaje de 3V3
		# Channel)
		self.L_3 = deque([0], maxlen = 50)
		self.t_3 = deque([0], maxlen = 50)
		self.curve_3.setData(self.t_3, self.L_3, pen = self.pen)

		# Resetear datos desplegados en Grafica No.4 (Corriente de 3V3
		# Channel)
		self.L_4 = deque([0], maxlen = 50)
		self.t_4 = deque([0], maxlen = 50)
		self.curve_4.setData(self.t_4, self.L_4, pen = self.pen)

	# ----------------- FUNCIONES DE CONEXION A PUERTO -----------------

	def conectar_puerto(self):
		"""
		Funcion para realizar la conexion a un puerto TCP para la
		recepcion de datos de telemetria de Quetzal-1 ya decodificados
		por AX25 Decode

		ACTIVADO POR: Click en Conectar a puerto
		"""

		# Inicializar variables
		ip = ''
		port = 0
		_selected_port = False

		# Obtener IP y No. de puerto al que se desea conectar
		idialog = QInputDialog()
		idialog.setWindowIcon(self.windows_icon)
		text, okPressed = QInputDialog.getText(idialog, "Enter IP","Enter IP address of port:", QLineEdit.Normal, "")
		if (okPressed and text != ''):
			ip = text
			if (ip.lower() == 'localhost' or ip.lower() == 'local'):   	# Permitir ingreso de palabra localhost
				ip = '127.0.0.1'
			if (ip.count('.') != 3):								    # Verificacion de formato IPv4
				return None
			idialog = QInputDialog()
			idialog.setWindowIcon(self.windows_icon)
			text, okPressed = QInputDialog.getText(idialog, "Select port","Enter port number:", QLineEdit.Normal, "")
			if (okPressed and text != ''):
				try:
					port = int(text)
					_selected_port = True
				except:
					pass

		# Si se selecciono puerto
		if (_selected_port):

			# Inicializar thread que se encarga de escuchar en puerto
			# seleccionado y emitir senial cuando se recibe un beacon
			self.adapter = TMadapter(self, ip, port)
			self.adapter.link.connect(self.updateLink)
			self.adapter.packet.connect(self.update_incoming_beacon)
			self.adapter.packet_count.connect(self.updatePacketCounter)
			self.adapter.start()

			print('QUETZAL-1 GUI-TCP Thread started')

			# Puerto TCP seleccionado
			self.selected_port = True

			# Habilitar botones de GUI para analizar datos
			self.pushButton.setEnabled(False)
			self.pushButton_2.setEnabled(False)
			self.pushButton_3.setEnabled(False)
			self.pushButton_4.setEnabled(False)
			self.pushButton_5.setEnabled(True)
			self.pushButton_6.setEnabled(True)
			self.pushButton_7.setEnabled(False)
			self.pushButton_8.setEnabled(False)

			qms = QMessageBox()
			qms.setWindowIcon(self.windows_icon)
			QMessageBox.information(qms, 'Successful connection','Successfully connected to:\n- IP: '+ip+'\n- Port: '+str(port), QMessageBox.Ok)
		else:
			return None

	def updatePacketCounter(self, count):
		print('Received packets: ' + str(count))

	def updateLink(self, up):
		if (up):
			print("Connected")
		else:
			print("Not connected")

	def update_incoming_beacon(self, beacon):
		"""
		Funcion para cargar datos recibidos por puerto TCP a funcion
		de parsing de los datos para su consecuente despliegue en GUI

		ACTIVADO POR: Senial packet de TMadapter (recepcion de nuevo
		beacon)

		Parametros
		----------
		beacon : bytearray
			Byte array que corresponde a una linea de datos de beacon
			(137 bytes)
		"""

		try:
			if (len(beacon) == 141):									# Determinar si data recibida corresponde a beacon de Quetzal-1
				# Extraer datos de beacon como linea de HEX bytes
				self.received_HEX = ['{:02x}'.format(b) for b in beacon]
				self.received_HEX = self.received_HEX[4:]				# Eliminar headers de CSP

				# Lectura de HEX recibido por TCP
				self.beacon_data = mh.beacon_decode(self.received_HEX, 0, self.adapter.recvd_packets, False)

				# Actualizar interfaz grafica
				self.update_GUI()
				self.pantalla_CDHS.update_LCD(self.beacon_data)
				self.pantalla_RAM.update_LCD(self.beacon_data)
			else:
				print('Received incorrect beacon of length {LEN} bytes over TCP port '.format(LEN=len(beacon)))
		except Exception as e:
			print('Error occured: {ERR}'.format(ERR=e))

	# ---------------------- FUNCIONES GENERALES -----------------------

	def updatedata(self):
		"""
		Funcion para lectura automatica de cada linea en archivo .dat
		y su procesamiento antes de desplegar la telemetria en interfaz

		ACTIVADO POR: Timer (1000ms)
		"""

		# Ejecutar si modo PLAY GUI automatico esta activado
		if (self.hexSelected == True and self.playHex == True):
			# Mientras aun hayan beacons por leer
			if (self.num_beacon < self.beacon_amount):

			  # Lectura de HEX
			  self.beacon_data = mh.beacon_decode(self.hex_list, self.beacon_counter, self.num_beacon, False)

			  # Movimiento de lectura de beacon a travez de HEX
			  self.beacon_counter = self.beacon_counter + self.beacon_length
			  self.num_beacon += 1

			  # Actualizar interfaz grafica
			  self.update_GUI()
			  self.pantalla_CDHS.update_LCD(self.beacon_data)
			  self.pantalla_RAM.update_LCD(self.beacon_data)

	def update_GUI(self):
		"""
		Funcion para actualizar los datos desplegados en widgets de
		interfaz grafica. Se cargan los datos procesados en updatedata()
		si interfaz se encuentra en Modo Automatico. De lo contrario,
		se cargan los datos procesados en step_backwards() o
		step_forward() en Modo Manual

		ACTIVADO POR: Funciones step_backwards(), step_forward() y
					  updatedata()
		"""

		# Inicializar blinkers
		green_blink = QtGui.QPixmap("./resources/green_blinker.png")
		red_blink = QtGui.QPixmap("./resources/red_blinker.png")

		# Datos de RTC (Real-time clock) -------------------------------
		self.lcdNumber.display(self.beacon_data[0])
		self.lcdNumber_2.display(self.beacon_data[1])
		self.lcdNumber_3.display(self.beacon_data[2])
		self.lcdNumber_4.display(self.beacon_data[3])
		self.lcdNumber_5.display(self.beacon_data[4])
		self.lcdNumber_6.display(self.beacon_data[5])

		# Datos de STATUS ----------------------------------------------

		# ADM Status
		ADM_deployed = int(self.beacon_data[6])
		ADM_deployed = '{0:08b}'.format(ADM_deployed)
		if ((int(ADM_deployed[7]) + int(ADM_deployed[6]) + int(ADM_deployed[5]) + int(ADM_deployed[4])) == 0):
			self.label_15.setPixmap(red_blink)
		elif ((int(ADM_deployed[7]) + int(ADM_deployed[6]) + int(ADM_deployed[5]) + int(ADM_deployed[4])) == 1):
			self.label_15.setPixmap(QtGui.QPixmap("./resources/green_blinker_antenna1.png"))
		elif ((int(ADM_deployed[7]) + int(ADM_deployed[6]) + int(ADM_deployed[5]) + int(ADM_deployed[4])) == 2):
			self.label_15.setPixmap(QtGui.QPixmap("./resources/green_blinker_antenna2.png"))
		elif ((int(ADM_deployed[7]) + int(ADM_deployed[6]) + int(ADM_deployed[5]) + int(ADM_deployed[4])) == 3):
			self.label_15.setPixmap(QtGui.QPixmap("./resources/green_blinker_antenna3.png"))
		elif ((int(ADM_deployed[7]) + int(ADM_deployed[6]) + int(ADM_deployed[5]) + int(ADM_deployed[4])) == 4):
			self.label_15.setPixmap(QtGui.QPixmap("./resources/green_blinker_antenna4.png"))

		# EPS Status
		if (self.beacon_data[7] == 83):
			self.label_16.setPixmap(green_blink)
		elif (self.beacon_data[7] == 69):
			self.label_16.setPixmap(red_blink)

		# HEATER Status
		if (self.beacon_data[8] == 102):
			self.label_17.setPixmap(QtGui.QPixmap("./resources/red_blinker3.png"))
		elif (self.beacon_data[8] == 105):
			self.label_17.setPixmap(QtGui.QPixmap("./resources/green_blinker3.png"))
		elif (self.beacon_data[8] == 150):
			self.label_17.setPixmap(QtGui.QPixmap("./resources/red_blinker2.png"))
		elif (self.beacon_data[8] == 153):
			self.label_17.setPixmap(QtGui.QPixmap("./resources/green_blinker2.png"))

		# ADCS Status
		if (self.beacon_data[9] == 83):
			self.label_18.setPixmap(green_blink)
		elif (self.beacon_data[9] == 69):
			self.label_18.setPixmap(red_blink)

		# PAYLOAD Status
		if (self.beacon_data[10] == 83):
			self.label_19.setPixmap(green_blink)
		elif (self.beacon_data[10] == 69):
			self.label_19.setPixmap(red_blink)

		# Datos Numericos de Telemetria EPS ----------------------------
		self.lcdNumber_15.display(self.beacon_data[17])					# Temperatura EPS
		self.lcdNumber_7.display(self.beacon_data[18])					# State of Charge
		self.progressBar.setValue(self.beacon_data[18])
		self.lcdNumber_8.display(self.beacon_data[19])					# Battery Voltage
		self.lcdNumber_9.display(self.beacon_data[20])					# Average Current
		self.lcdNumber_10.display(self.beacon_data[21])					# Remaining Capacity
		self.lcdNumber_11.display(self.beacon_data[22])					# Average Power
		self.lcdNumber_12.display(self.beacon_data[23])					# State of Health
		self.lcdNumber_13.display(self.beacon_data[28])					# V/I Monitor No.3 (5V0 Channel) Voltage
		self.lcdNumber_14.display(self.beacon_data[29])					# V/I Monitor No.3 (5V0 Channel) Current

		# Modo de actualizacion Forward
		if (self.steppingBackwards == False):

			# Datos Graficos para plotters
			# NOTA: Indexar [-1] en array apunta a ultimo valor de
			# lista. Ayuda al tener arrays de longitudes desconocidas.
			# En este caso, al ultimo elemento de tiempo se le suma 1
			# para siguiente step.

			# V/I Monitor No.1 (Solar Channel) Voltage
			val = self.beacon_data[24]
			self.L.append(val)
			self.t.append(self.t[-1]+1)
			self.curve.setData(self.t, self.L, pen = self.pen)
			self.graphicsView.setLabel('bottom',str(val) + ' V')

			# V/I Monitor No.1 (Solar Channel) Current
			val_2 = self.beacon_data[25]
			self.L_2.append(val_2)
			self.t_2.append(self.t_2[-1]+1)
			self.curve_2.setData(self.t_2, self.L_2,pen = self.pen)
			self.graphicsView_2.setLabel('bottom',str(val_2) + ' mA')

			# V/I Monitor No.2 (3V3 Channel) Voltage
			val_3 = self.beacon_data[26]
			self.L_3.append(val_3)
			self.t_3.append(self.t_3[-1]+1)
			self.curve_3.setData(self.t_3, self.L_3,pen = self.pen)
			self.graphicsView_3.setLabel('bottom',str(val_3) + ' V')

			# V/I Monitor No.2 (3V3 Channel) Current
			val_4 = self.beacon_data[27]
			self.L_4.append(val_4)
			self.t_4.append(self.t_4[-1]+1)
			self.curve_4.setData(self.t_4, self.L_4,pen = self.pen)
			self.graphicsView_4.setLabel('bottom',str(val_4) + ' mA')

		# Modo de actualizacion Backward
		elif (self.steppingBackwards == True):

			# Borrar ultimo dato de listas para retroceder grafica
			self.L.pop()
			self.L_2.pop()
			self.L_3.pop()
			self.L_4.pop()
			self.t.pop()
			self.t_2.pop()
			self.t_3.pop()
			self.t_4.pop()

			self.graphicsView.setLabel('bottom',str(self.L[-1]) + ' V')
			self.graphicsView_2.setLabel('bottom',str(self.L_2[-1]) + ' mA')
			self.graphicsView_3.setLabel('bottom',str(self.L_3[-1]) + ' V')
			self.graphicsView_4.setLabel('bottom',str(self.L_4[-1]) + ' mA')

			# Si aun hay nuevo FakeBeacon al cual retroceder las
			# graficas
			if (self.stopAddingFakebeacon == False):

				# Agregar primer dato de listas para retroceder grafica

				# V/I Monitor No.1 (Solar Channel) Voltage
				val = self.fakebeacon_data[24]
				self.L.appendleft(val)
				self.t.appendleft(self.t[0]-1)

				# V/I Monitor No.1 (Solar Channel) Current
				val_2 = self.fakebeacon_data[25]
				self.L_2.appendleft(val_2)
				self.t_2.appendleft(self.t_2[0]-1)

				# V/I Monitor No.2 (3V3 Channel) Voltage
				val_3 = self.fakebeacon_data[26]
				self.L_3.appendleft(val_3)
				self.t_3.appendleft(self.t_3[0]-1)

				# V/I Monitor No.2 (3V3 Channel) Current
				val_4 = self.fakebeacon_data[27]
				self.L_4.appendleft(val_4)
				self.t_4.appendleft(self.t_4[0]-1)

			self.curve.setData(self.t, self.L, pen = self.pen)
			self.curve_2.setData(self.t_2, self.L_2,pen = self.pen)
			self.curve_3.setData(self.t_3, self.L_3,pen = self.pen)
			self.curve_4.setData(self.t_4, self.L_4,pen = self.pen)

		# Datos Numericos de Telemetria FPB
		self.lcdNumber_39.display(self.beacon_data[30])					# ADCS Current
		self.lcdNumber_40.display(self.beacon_data[31])					# COMMS Current
		self.lcdNumber_41.display(self.beacon_data[32])					# PAYLOAD Current
		self.lcdNumber_42.display(self.beacon_data[33])					# HEATER Current

		# Datos Binarios para blinkers de Communication Flags
		comm_flags = int(self.beacon_data[35])
		comm_flags = '{0:08b}'.format(comm_flags)
		if (int(comm_flags[7]) == 1):
			self.label_102.setPixmap(green_blink)
		else:
			self.label_102.setPixmap(red_blink)
		if (int(comm_flags[6]) == 1):
			self.label_105.setPixmap(green_blink)
		else:
			self.label_105.setPixmap(red_blink)
		if (int(comm_flags[5]) == 1):
			self.label_108.setPixmap(green_blink)
		else:
			self.label_108.setPixmap(red_blink)
		if (int(comm_flags[4]) == 1):
			self.label_111.setPixmap(green_blink)
		else:
			self.label_111.setPixmap(red_blink)
		if (int(comm_flags[3]) == 1):
			self.label_114.setPixmap(green_blink)
		else:
			self.label_114.setPixmap(red_blink)

		# Datos Binarios para blinkers de Transmission Flags
		trans_flags = int(self.beacon_data[36])
		trans_flags = '{0:08b}'.format(trans_flags)
		if (int(trans_flags[7]) == 1):
			self.label_103.setPixmap(green_blink)
		else:
			self.label_103.setPixmap(red_blink)
		if (int(trans_flags[6]) == 1):
			self.label_106.setPixmap(green_blink)
		else:
			self.label_106.setPixmap(red_blink)
		if (int(trans_flags[5]) == 1):
			self.label_109.setPixmap(green_blink)
		else:
			self.label_109.setPixmap(red_blink)
		if (int(trans_flags[4]) == 1):
			self.label_112.setPixmap(green_blink)
		else:
			self.label_112.setPixmap(red_blink)
		if (int(trans_flags[3]) == 1):
			self.label_115.setPixmap(green_blink)
		else:
			self.label_115.setPixmap(red_blink)

		# Datos Binarios para blinkers de FPB Flags
		FPB_flags = int(self.beacon_data[34])
		FPB_flags = '{0:08b}'.format(FPB_flags)
		if (int(FPB_flags[7]) == 1):
			self.label_117.setPixmap(green_blink)
		else:
			self.label_117.setPixmap(red_blink)
		if (int(FPB_flags[6]) == 1):
			self.label_121.setPixmap(green_blink)
		else:
			self.label_121.setPixmap(red_blink)
		if (int(FPB_flags[5]) == 1):
			self.label_125.setPixmap(green_blink)
		else:
			self.label_125.setPixmap(red_blink)
		if (int(FPB_flags[4]) == 1):
			self.label_129.setPixmap(green_blink)
		else:
			self.label_129.setPixmap(red_blink)
		if (int(FPB_flags[3]) == 1):
			self.label_119.setPixmap(green_blink)
		else:
			self.label_119.setPixmap(red_blink)
		if (int(FPB_flags[2]) == 1):
			self.label_123.setPixmap(green_blink)
		else:
			self.label_123.setPixmap(red_blink)
		if (int(FPB_flags[1]) == 1):
			self.label_127.setPixmap(green_blink)
		else:
			self.label_127.setPixmap(red_blink)
		if (int(FPB_flags[0]) == 1):
			self.label_131.setPixmap(green_blink)
		else:
			self.label_131.setPixmap(red_blink)

		# Datos Numericos de Telemetria ADCS ---------------------------

		# Gyroscopes
		self.lcdNumber_19.display(self.beacon_data[37])					# Gyro X
		self.lcdNumber_20.display(self.beacon_data[38])					# Gyro Y
		self.lcdNumber_21.display(self.beacon_data[39])					# Gyro Z

		# Magnetometers
		self.lcdNumber_22.display(self.beacon_data[40])					# Mag X
		self.lcdNumber_23.display(self.beacon_data[41])					# Mag X
		self.lcdNumber_24.display(self.beacon_data[42])					# Mag X

		# ADC No.1
		self.lcdNumber_25.display(self.beacon_data[43])					# Channel No.1
		self.lcdNumber_27.display(self.beacon_data[44])					# Channel No.2
		self.lcdNumber_29.display(self.beacon_data[45])					# Channel No.3
		self.lcdNumber_31.display(self.beacon_data[46])					# Channel No.4
		self.lcdNumber_33.display(self.beacon_data[47])					# Channel No.5
		self.lcdNumber_35.display(self.beacon_data[48])					# Channel No.6

		# ADC No.2
		self.lcdNumber_26.display(self.beacon_data[49])					# Channel No.1
		self.lcdNumber_28.display(self.beacon_data[50])					# Channel No.2
		self.lcdNumber_30.display(self.beacon_data[51])					# Channel No.3
		self.lcdNumber_32.display(self.beacon_data[52])					# Channel No.4
		self.lcdNumber_34.display(self.beacon_data[53])					# Channel No.5
		self.lcdNumber_36.display(self.beacon_data[54])					# Channel No.6

		# Temperaturas
		self.lcdNumber_37.display(self.beacon_data[55])					# BNO055 Temperature
		self.lcdNumber_38.display(self.beacon_data[56])					# TMP100 Temperature

		# Datos Binarios para blinkers de FPB Flags
		trans_flags_A = int(self.beacon_data[57])
		trans_flags_A = '{0:08b}'.format(trans_flags_A)
		if (int(trans_flags_A[7]) == 1):
			self.label_81.setPixmap(green_blink)
		else:
			self.label_81.setPixmap(red_blink)
		if (int(trans_flags_A[6]) == 1):
			self.label_85.setPixmap(green_blink)
		else:
			self.label_85.setPixmap(red_blink)
		if (int(trans_flags_A[5]) == 1):
			self.label_83.setPixmap(green_blink)
		else:
			self.label_83.setPixmap(red_blink)
		if (int(trans_flags_A[4]) == 1):
			self.label_88.setPixmap(green_blink)
		else:
			self.label_88.setPixmap(red_blink)

		# Beacon Data --------------------------------------------------
		self.lcdNumber_16.display(self.beacon_data[58])
		self.lcdNumber_17.display(self.beacon_data[59])
		self.lcdNumber_18.display(self.beacon_data[60])

# **********************************************************************

# **********************************************************************
#                   CLASE DE THREAD DE CONEXION TCP
# **********************************************************************

# Clase TMadapter que hereda de clase QThread
class TMadapter(QThread):
	"""
	Clase de thread que se encarga de ejecutar las conexiones necesarias
	a un socket especifico para la recepcion de datos provenientes de un
	puerto TCP. Recepcion de paquetes con el formato de beacon de
	Quetzal-1 empacado con Cubesat Space Protocol (CSP). Hereda de
	QThread

	Atributos
	---------
	packet : pyqtSignal
		Senial que transporta paquete de beacon recibido por TCP
	link : pyqtSignal
		Senial que transporta bandera de estado de conexion TCP
	packet_count : pyqtSignal
		Senial que transporta cantidad de beacons recibidos por TCP

	Metodos
	-------
	run():
		Ejecucion principal de QThread. Se mantiene escuchando a puerto
		TCP para recepcion de datos, y si no recibe beacon en 15s
		genera excepcion
	"""

	# Seniales de thread
	packet = pyqtSignal(object)											# Senial emitida que contiene paquete de beacon recibido
	link = pyqtSignal(bool)												# Senial emitida que contiene flag de recepcion de datos
	packet_count = pyqtSignal(int)										# Senial emitida que contiene numero de paquetes recibidos

	# Inicializacion de variables globales
	recvd_packets = 0													# Contador de paquetes recibidos
	active = False														# Bandera de thread inicializado
	host = None															# Direccion de puerto a conectarse

	def __init__(self, parent, ip, port):
		"""
		Constructor

		Parametros
		----------
		parent : QMainWindow
			Parent de QThread
		ip : str
			Direccion ip de puerto (usualmente localhost -> 127.0.0.1)
		port : int
			No. de puerto a conectar socket
		"""

		QtCore.QThread.__init__(self, parent)
		self.parent = parent											# Guardado de parent (QMainWindow de interfaz grafica)
		self.active = True												# Thread inicializado
		self.context = zmq.Context()									# ZeroMQ high-performance asynchronous messaging initialization
		self.socket = self.context.socket(zmq.SUB)						# Inicializar socket
		self.host = 'tcp://' + ip + ':' + str(port)						# Inicializar host (direccion de puerto) [Linea original decia tcp://]
		self.socket.connect(self.host)									# Conectar socket a puerto
		self.socket.setsockopt_string(zmq.SUBSCRIBE, "")
		self.socket.setsockopt(zmq.RCVTIMEO, 15000) 					#Timeout de 15s (beacons transmitidos cada 10s)
		print('Started listening on {HOST} for incoming packets from AX25 Decode'.format(HOST=self.host))

	def run(self):
		"""
		Funcion de ejecucion de QThread

		ACTIVADO POR: Llamada de funcion externa
		"""

		while (self.active):											# Si QThread ya se inicializo
			try:
				data = self.socket.recv()								# Extraccion de datos del socket (thread monitorea puerto)
				self.packet.emit(data)									# Enviar paquete en senial que causa actualizacion de GUI
				self.recvd_packets = self.recvd_packets + 1				# Aumentar conteo de paquetes recibidos en socket
				self.packet_count.emit(self.recvd_packets)				# Enviar cantidad de paquetes recibidos
				self.link.emit(True)									# Enviar bandera de conexion exitosa
			except Exception as e:
				print('No packets received in the last 15 seconds...{ERR}'.format(ERR=e))
				self.link.emit(False)									# Enviar bandera de conexion fallida

# ----------------------------------------------------------------------


from typing import Dict, Generator, List
from uuid import uuid4

import pytest
from sqlalchemy.orm import Session

from fidesops.ops.models.connectionconfig import (
    AccessLevel,
    ConnectionConfig,
    ConnectionType,
)
from fidesops.ops.models.datasetconfig import DatasetConfig

@pytest.fixture(scope="function")
def email_connection_config(db: Session) -> Generator:
    name = str(uuid4())
    connection_config = ConnectionConfig.create(
        db=db,
        data={
            "name": name,
            "key": "my_email_connection_config",
            "connection_type": ConnectionType.email,
            "access": AccessLevel.write,
            "secrets": {"to_email": "test@example.com"},
        },
    )
    yield connection_config
    connection_config.delete(db)

@pytest.fixture(scope="function")
def email_dataset_config(
    email_connection_config: ConnectionConfig,
    db: Session,
    example_datasets: List[Dict],
) -> Generator:
    email_dataset = example_datasets[9]
    fides_key = email_dataset["fides_key"]
    email_connection_config.name = fides_key
    email_connection_config.key = fides_key
    email_connection_config.save(db=db)
    dataset = DatasetConfig.create(
        db=db,
        data={
            "connection_config_id": email_connection_config.id,
            "fides_key": fides_key,
            "dataset": email_dataset,
        },
    )
    yield dataset
    dataset.delete(db=db)


import itertools
import logging
from decimal import Decimal
from typing import Iterable, Dict, Set, Optional, List, Any

from abccore.checkpoint_service import CheckpointService
from abcnet.handlers import AbstractItemHandler
from abcnet.structures import Message
from abcnet.timer import SimpleTimer

from abcckpt import ckpt_constants, fast_vrf
from abcckpt.ckptItems import CkptItemType, ValidatorVote, MajorityVotes
from abcckpt.ckptParser import CkptItemsParser
from abcckpt.ckpt_creation_state import StateTransitionObserver
from abcnet.services import ChannelService
from abcckpt.pre_checkpoint import PreCheckpoint, PreCkptItemProcessor, AgentService
from abcckpt.ckpt_creation_state import PreCkptStatus
from abcckpt.vote_cr_handler import VoteCrHandler

logger = logging.getLogger(__name__)

class VoteWrap:

    vote_qualifier: str = None
    voter_info: "VoterInfo" = None
    vote: Optional[ValidatorVote] = None

    is_requested = False
    is_processed = False

    def __eq__(self, other: "VoteWrap"):
        if other is None or not isinstance(other, VoteWrap):
            return False
        return self.vote == other.vote

    def __hash__(self):
        return hash(self.vote)

class VoterInfo:

    validator: bytes = None  # public key of the validator that has voted.

    votes: Set[VoteWrap] = None  # the votes that the public key has casted and signed.

    stake: Decimal = None

    def __init__(self, validator_key: bytes):
        self.validator = validator_key
        self.votes = set()

    @property
    def voted_item_qualifier(self) -> Optional[str]:
        if not self.has_voted:
            # No votes has been casted by this validator which is not possible
            raise ValueError("This object cannot exist with an empty vote set.")
        elif len(self.votes) > 1:
            # Multiple votes are interpreted as None
            return None
        else:
            # If we know who has voted we should know what was voted as both information come in the same item content.
            vote: VoteWrap = next(iter(self.votes))
            assert vote is not None
            return vote.vote.voted_item_id

    @property
    def has_voted(self) -> bool:
        return len(self.votes) > 0

    def get_vote(self, voted_item_id) -> Optional[VoteWrap]:
        for v in self.votes:
            if v.vote is not None and v.vote.voted_item_id == voted_item_id:
                return v
        return None

    def mark_vote_requested(self):
        assert self.has_voted
        voted_item = self.voted_item_qualifier
        vote_wrap = self.get_vote(voted_item)
        if vote_wrap is not None:
            vote_wrap.is_requested = True
        else:
            for vote_wrap in self.votes:
                vote_wrap.is_requested = True

    def incompatible_vote(self, vote_identifier: Optional[str]) -> bool:
        if vote_identifier is None:
            return False
        return vote_identifier != self.voted_item_qualifier

    def __eq__(self, other: "VoterInfo"):
        if other is None or not isinstance(other, VoterInfo):
            return False
        return self.validator == other.validator

    def __hash__(self):
        return hash(self.validator)

class VoteRegistry:

    def __init__(self):
        # Voted items:
        self.item_votes: Dict[str, Set[VoterInfo]] = dict()
        # Voters:
        self.voters: Dict[bytes, VoterInfo] = dict()
        # Vote item ids:
        self.votes: Dict[str, VoteWrap] = dict()

        self.majority_votes: Optional[List[ValidatorVote]] = None

    def has_vote(self, vote_qualifier: str) -> bool:
        return vote_qualifier in self.votes

    def get_or_create_vote(self, vote_qualifier: str) -> VoteWrap:
        assert vote_qualifier is not None
        if vote_qualifier not in self.votes:
            viw = VoteWrap()
            viw.vote_qualifier = vote_qualifier
            self.votes[vote_qualifier] = viw
        return self.votes[vote_qualifier]

    def register_to_voter(self, vw: VoteWrap, vote: ValidatorVote, ckpt_service: CheckpointService) -> bool:
        if vw.vote is not None:
            assert vw.vote is vote
            return False
        assert vw is self.votes[vote.item_qualifier()]
        assert vw.vote is None
        vw.vote = vote
        if vw.vote.pub_key not in self.voters:
            voter_info = VoterInfo(vw.vote.pub_key)
            voter_info.stake = ckpt_service.delegated_stake(vw.vote.pub_key)
            self.voters[vw.vote.pub_key] = voter_info
        else:
            voter_info = self.voters[vw.vote.pub_key]
        vw.voter_info = voter_info
        voter_info.votes.add(vw)
        self.redistribute_vote(voter_info)
        return True

    def redistribute_vote(self, voter_info: VoterInfo):
        voted_item = voter_info.voted_item_qualifier
        if voted_item not in self.item_votes:
            self.item_votes[voted_item] = set()

        self.item_votes[voted_item].add(voter_info)

        for vw in voter_info.votes:
            other_vote = vw.vote.voted_item_id
            if other_vote is voted_item:
                continue
            if other_vote in self.item_votes:
                self.item_votes[other_vote].discard(voter_info)

    def is_emtpy(self):
        return len(self.votes) == 0 and len(self.item_votes) == 0 and len(self.voters) == 0

class Majority:
    voted_item: Optional[str] = None

    transition_buffer_timer: SimpleTimer

    majority_votes_post_timer: SimpleTimer

    def __init__(self, voted_item: Optional[str]):
        self.voted_item = voted_item
        self.transition_buffer_timer = SimpleTimer(ckpt_constants.TRANSITION_BUFFER_TIME, start=True)
        self.majority_votes_post_timer = SimpleTimer(ckpt_constants.MAJORITY_VOTE_POST_PERIOD)

class StabVotingHandler(AbstractItemHandler, StateTransitionObserver):
    """
    Item processor class for processing votes in each step of checkpoint creation. Majority checks and stalemate checks
    are done after evaluating the vote distribution.
    """
    def __init__(self, pc: PreCheckpoint, ckpt_service: CheckpointService,
                 agent_service: Optional[AgentService]=None):
        super().__init__([CkptItemType.VALVOTE, CkptItemType.MAJVOTES], CkptItemsParser())
        self.pc: PreCheckpoint = pc
        self._processors: Dict[PreCkptStatus, PreCkptItemProcessor] = None
        self.vote_cr_handler: Optional[VoteCrHandler] = None
        self.agent_service: Optional[AgentService] = agent_service
        self.ckpt_service: CheckpointService = ckpt_service
        self.prev_registry: VoteRegistry = VoteRegistry()  # Initialize with an empty registry
        self.registry: VoteRegistry = VoteRegistry()

        self.stake_vote_threshold: Optional[Decimal] = None

        self.new_vote_distrib = False
        self.new_vote_found = False
        self.state_transition_performed = False

        self.stalemate_found = False
        self.is_minority_voter = False
        self.stalemate_vote_switch = False
        self.stalemate_timer = SimpleTimer(ckpt_constants.MISSING_VOTES_REQUEST_TIME)

        self.majority: Optional[Majority] = None
        # Timers for maintenance:
        self.timeout_timers = [
            (self.request_missing_votes, SimpleTimer(ckpt_constants.MISSING_VOTES_REQUEST_TIME)),
            (self.share_vote_checklist, SimpleTimer(ckpt_constants.VOTES_CHECKLIST_TIME)),
            (self.share_requested_voted, SimpleTimer(ckpt_constants.REQUESTED_VOTE_RESPONSE_TIME)),
            (self.eval_current_votes_distribution, SimpleTimer(ckpt_constants.VOTE_DISTRIB_EVAL_TIME)),
            (self.prev_state_transition_maj_vote_broadcast, SimpleTimer(ckpt_constants.PREV_VOTES_CHECKLIST_BROADCAST))
        ]

    def set_handlers(self,
                     prio_handler: PreCkptItemProcessor,
                     hash_handler: PreCkptItemProcessor,
                     content_handler: PreCkptItemProcessor,
                     vote_cr_handler: Optional[VoteCrHandler] = None):
        self._processors = {
            PreCkptStatus.AGREE_VALIDATOR: prio_handler,
            PreCkptStatus.AGREE_HASH: hash_handler,
            PreCkptStatus.AGREE_CONTENT: content_handler,
        }
        self.vote_cr_handler = vote_cr_handler

    @property
    def processors(self) -> Dict[PreCkptStatus, PreCkptItemProcessor]:
        if not self._processors:
            raise Exception("Processors not yet initialized. "
                            "Call set_handlers at initialization.")
        return self._processors

    def get_current_processor(self) -> PreCkptItemProcessor:
        if self.current_state.step_status not in self.processors:
            raise Exception("Current step %s does not have a processor.")
        return self.processors[self.current_state.step_status]

    def process_vote(self, vote: ValidatorVote, outside_source=False):
        if vote.state != self.current_state:
            logger.debug("Received a vote from another round. Ignoring vote.. My state: %s\nThe other state: %s",
                           self.current_state, vote.state)
            return

        if vote.signature is None:
            logger.warning("Received a vote with no signature: %s", vote)
            return

        if vote.voted_item_type != self.get_current_vote_item_type():
            logger.debug("Received a vote with an unexpected voted item type: %s", vote)
            return

        vw = self.registry.get_or_create_vote(vote.item_qualifier())
        if vw.is_requested and outside_source:
            logger.debug("Requested vote has been delivered through network. Marking it unrequested: %s", vote)
            vw.is_requested = False
        if vw.is_processed:
            logger.debug("Already processed vote %s. Skipping", vote)
            return

        if not vote.verify_signature():
            logger.debug("A vote received with invalid signature: %s", vote)
            vw.is_processed = True
            return

        new_vote = self.registry.register_to_voter(vw, vote, self.ckpt_service)
        if new_vote:
            if  vote.voted_item_id is not None:
                self.get_current_processor().check_item_exists(vote.voted_item_id)
            self.new_vote_distrib = True
        vw.is_processed = True

    def get_current_vote_item_type(self):
        if self.current_state.step_status == PreCkptStatus.AGREE_VALIDATOR:
            return CkptItemType.PRIORITY
        if self.current_state.step_status == PreCkptStatus.AGREE_HASH:
            return CkptItemType.CKPT_HASH
        if self.current_state.step_status == PreCkptStatus.AGREE_CONTENT:
            return CkptItemType.CKPT_DATA

    def process_majority_votes(self, maj_votes: MajorityVotes, registry: VoteRegistry):
        vote_set = set(maj_votes.votes)
        if len(vote_set) == 0:
            return

        for vote_qualifier in vote_set:
            if registry.has_vote(vote_qualifier):
                vote_wrap = registry.get_or_create_vote(vote_qualifier)
                if vote_wrap.vote is None:
                    continue
                if vote_wrap.voter_info.incompatible_vote(maj_votes.voted_item_qualifier):
                    logger.warning("A majority vote was posted that has incomplete information: %s",
                                   maj_votes.voted_item_qualifier)
                    vote_wrap.voter_info.mark_vote_requested()

    def handle_item_content(self, cs: "ChannelService", msg: Message, item_type: int, item_content: Any):
        if item_type == CkptItemType.MAJVOTES and item_content is not None and isinstance(item_content, MajorityVotes):
            self.process_majority_votes(item_content, self.registry)
            self.process_majority_votes(item_content, self.prev_registry)
        if item_type == CkptItemType.VALVOTE and item_content is not None and isinstance(item_content, ValidatorVote):
            self.process_vote(item_content, outside_source=True)
        logger.debug("Received unexpected item with type: %d, %s", item_type, item_content)

    def handle_item_request(self, cs: "ChannelService", msg: Message, item_type: int, item_qualifier: str):
        if item_type != CkptItemType.VALVOTE:
            logger.debug("Received unexpected item with type: %d, %s", item_type, item_qualifier)
            return
        if self.prev_registry.has_vote(item_qualifier):
            vote = self.prev_registry.get_or_create_vote(item_qualifier)
            if vote.vote is not None:
                logger.debug("Vote %s is requested by the network but it is in the prev registry. "
                             "We will reply to it later.",
                             item_qualifier)
                vote.is_requested = True

        elif self.registry.has_vote(item_qualifier):
            vote_wrap = self.registry.get_or_create_vote(item_qualifier)
            if vote_wrap.vote is not None:
                vote_wrap.is_requested = True
            elif vote_wrap.is_requested:
                logger.debug("Vote %s was requested by the network so we will not send a request ourselves.",
                             item_qualifier)
                vote_wrap.is_requested = False

    def handle_item_checklist(self, cs: "ChannelService", msg: Message, item_type: int, item_qualifier: str):
        if item_type != CkptItemType.VALVOTE:
            logger.debug("Received unexpected item with type: %d, %s", item_type, item_qualifier)
            return
        vw = self.registry.get_or_create_vote(item_qualifier)
        if vw.vote is None:
            self.new_vote_found = True

    def perform_maintenance(self, cs: ChannelService, force_maintenance=False):
        self.check_state_transition()
        for maintenance_method, timer in self.timeout_timers:
            if force_maintenance or timer():
                # Timer of action has reached zero.
                # Perform maintenance action by calling the method that has the action_name.
                maintenance_method(cs)

    def get_missing_votes(self) -> Iterable[VoteWrap]:
        return filter(lambda vw: vw.vote is None, self.registry.votes.values())

    def request_missing_votes(self, cs: ChannelService):
        if not self.new_vote_found:
            # logger.debug("No votes are missing. Not requesting any vote.")
            return
        cs.broadcast_channel().fetch_items([
            (CkptItemType.VALVOTE, vw.vote_qualifier)
            for vw in self.get_missing_votes()
        ])
        self.new_vote_found = False

    def get_present_votes(self) -> Iterable[VoteWrap]:
        return filter(lambda vw: vw.vote is not None, self.registry.votes.values())

    def share_vote_checklist(self, cs: ChannelService):
        vote_checklist = [vw.vote for vw in self.get_present_votes()]
        if vote_checklist:
            cs.broadcast_channel().checklist(vote_checklist)

    def get_requested_votes(self, registry: VoteRegistry) -> Iterable[VoteWrap]:
        return filter(lambda vw: vw.vote is not None and vw.is_requested, registry.votes.values())

    def get_all_requested_votes(self) -> Iterable[VoteWrap]:
        return itertools.chain(self.get_requested_votes(self.registry),
                              self.get_requested_votes(self.prev_registry))

    def share_requested_voted(self, cs: ChannelService):
        requested_votes = list(self.get_all_requested_votes())
        if requested_votes:
            cs.broadcast_channel().items([
                vw.vote for vw in requested_votes
            ])
        for vw in requested_votes:
            vw.is_requested = False

    def eval_current_votes_distribution(self, cs: ChannelService):
        self.find_new_majority()
        if self.majority:
            if not self.state_transition_performed:
                logger.info("Performing state transition from state %s. Majority votes item: %s", self.state,
                            self.majority.voted_item)
                self.ready_transition(cs)
        elif self.stalemate_found:
            if not self.stalemate_vote_switch:
                if self.is_minority_voter or self.stalemate_timer.check():
                    self.switch_vote_to_pass()
            else:
                logger.warning("Stalemate detected but already switched vote.")

    def get_support_of_vote(self, vote_item_qualifier: Optional[str]) -> Decimal:
        if vote_item_qualifier in self.registry.item_votes:
            return sum(map(lambda voter_info: voter_info.stake, self.registry.item_votes[vote_item_qualifier]))
        return Decimal(0.0)

    def get_self_vote_support(self) -> Decimal:
        if self.agent_service is None:
            return Decimal(0)
        max_vote_support = Decimal(0)
        for key in self.agent_service.get_keypairs():
            pub_key = fast_vrf.encode_pub_key(key.public_key())
            if pub_key not in self.registry.voters:
                continue
            voted_item_id = self.registry.voters[pub_key].voted_item_qualifier
            if voted_item_id not in self.registry.item_votes:
                continue
            summed_voting_stake = self.get_support_of_vote(voted_item_id)
            if summed_voting_stake > max_vote_support:
                max_vote_support = summed_voting_stake
        return max_vote_support

    def find_new_majority(self):
        if not self.new_vote_distrib:
            return
        self.new_vote_distrib = False

        majority_voted_item_id: Optional[str] = None
        majority_found = False
        all_votes_stake_sum = Decimal(0.0)
        for voted_item_id in self.registry.item_votes.keys():
            summed_voting_stake = self.get_support_of_vote(voted_item_id)
            all_votes_stake_sum += summed_voting_stake
            if self.is_majority_threshold_surpassed(summed_voting_stake):
                if majority_found:
                    raise Exception("While counting votes two simultaneous majorities. Current state: %s"
                                    "\nVoted item 1:%s"
                                    "\nVoted item 2: %s",
                                    self.current_state, majority_voted_item_id, voted_item_id)
                majority_found = True
                majority_voted_item_id = voted_item_id
        if majority_found:
            self.set_majority(majority_voted_item_id)
            self.stalemate_found = False
            self.is_minority_voter = False
        else:
            self.unset_majority()
            stalemate_condition = self.is_majority_threshold_surpassed(all_votes_stake_sum)
            if stalemate_condition and not self.stalemate_found:
                self.stalemate_found = True
                self.stalemate_timer.reset()
                remaining_votes = self.ckpt_service.stake_sum() - all_votes_stake_sum
                if self.stalemate_found:
                    self.is_minority_voter = not self.is_majority_threshold_surpassed(remaining_votes +
                                                                              self.get_self_vote_support())

    def is_majority_threshold_surpassed(self, vote_stake: Decimal) -> bool:
        try:
            return self.stake_vote_threshold < vote_stake
        except AttributeError:
            return (self.ckpt_service.stake_sum() * Decimal(2 / 3)) < vote_stake # incase of None

    def set_majority(self, voted_item: Optional[str]):
        if self.majority is not None and self.majority.voted_item != voted_item:
            logger.warning("Majority voted item is switched from %s to %s.", self.majority.voted_item, voted_item)
            self.unset_majority()
        logger.info("Found a new majority for voted item %s in state: %s", voted_item, self.current_state)
        self.majority: Optional[Majority] = Majority(voted_item)

    def unset_majority(self):
        if self.majority is not None:
            logger.warning("Majority of votes was found for item id: %s. Unsetting it.", self.majority.voted_item)
            self.majority: Optional[Majority] = None

    def ready_transition(self, cs: ChannelService):
        if not self.majority.transition_buffer_timer.check():
            logger.debug("Majority of votes for item %s  in state %s has been found. Remaining buffer time: %s",
                         self.majority.voted_item, self.current_state, self.majority.transition_buffer_timer)
            self.post_majority_vote(cs)
            return

        if self.majority.voted_item is not None and \
                not self.get_current_processor().check_item_exists(self.majority.voted_item):
            logger.info("The voted item %s in state %s has not been received yet. Waiting..",
                           self.majority.voted_item, self.current_state)
            return

        logger.info("Majority of voted item, %s, has been found and the buffer time has passed. "
                    "Performing transition from state: %s.", self.majority.voted_item, self.current_state)
        self.get_current_processor().transition(self.get_majority_votes(), self.majority.voted_item)
        self.state_transition_performed = True
        self.registry.majority_votes = self.get_majority_votes()

    def switch_vote_to_pass(self):
        assert not self.stalemate_vote_switch
        self.stalemate_vote_switch = True
        if self.vote_cr_handler is not None:
            logger.warning("Switching to pass vote as %sminority voter.", "" if self.is_minority_voter else "non-")
            self.vote_cr_handler.create_vote(None, {
                PreCkptStatus.AGREE_VALIDATOR: CkptItemType.PRIORITY,
                PreCkptStatus.AGREE_HASH: CkptItemType.CKPT_HASH,
                PreCkptStatus.AGREE_CONTENT: CkptItemType.CKPT_DATA,
            }.get(self.pc.state.step_status))

    def post_majority_vote(self, cs: ChannelService):
        if not self.majority.majority_votes_post_timer():
            return
        assert self.majority is not None
        majority_votes = self.get_majority_votes()
        assert len(majority_votes) > 0
        logger.debug("Broadcasting %d many majority votes.", len(majority_votes))
        maj_votes = MajorityVotes(state=self.current_state,
                                  votes=[v.item_qualifier() for v in majority_votes],
                                  voted_item_qualifier=self.majority.voted_item)
        cs.broadcast_channel().items([maj_votes])

    def get_majority_votes(self) -> List[ValidatorVote]:
        majority_voters = filter(lambda vote_info: vote_info.stake > 0,
                                 self.registry.item_votes[self.majority.voted_item])
        votes = list()
        for voter_info in majority_voters:
            assert voter_info.has_voted
            assert voter_info.voted_item_qualifier == self.majority.voted_item
            vote = None
            for voter_vote in voter_info.votes:
                if voter_vote.vote.voted_item_id == self.majority.voted_item:
                    vote = voter_vote.vote
                    break
            if vote is None:
                logger.warning("No pass vote found for a validator. Only multiple different votes: %s",
                               voter_info.votes)
                votes += list(voter_info.votes)
            else:
                votes.append(vote)
        return votes

    def prev_state_transition_maj_vote_broadcast(self, cs: ChannelService):
        # If there is a prev registry post the votes of the previous majority as a checklist so other peers
        # can fetch them and perform the same transition.
        if self.prev_registry is not None and self.prev_registry.majority_votes is not None:
            cs.broadcast_channel().checklist(self.prev_registry.majority_votes)

    def handle_state_transition(self, state):
        self.clear_state()
        if state.is_new_ckpt_round():
            self.stake_vote_threshold = (self.ckpt_service.stake_sum() * Decimal(2 / 3))

    def clear_state(self):
        logger.debug("Cleared vote registry for new state: %s", self.current_state)
        self.prev_registry = self.registry
        self.registry = VoteRegistry()
        self.new_vote_distrib = False
        self.new_vote_found = False
        self.majority: Optional[Majority] = None
        self.state_transition_performed = False

        self.stalemate_found = False
        self.stalemate_vote_switch = False
        self.is_minority_voter = False


#!/usr/bin/env python3

import helper

def get_column(i: int, lines: list[str]) -> list[str]:
    return [line[i] for line in lines]

def main():
    # lines = helper.read_lines("example.txt")
    lines = helper.read_lines("input.txt")
    # for line in lines:
        # print(line)
    no_of_columns = len(lines[0])
    # print(no_of_columns)
    result = []
    for i in range(no_of_columns):
        column = get_column(i, lines)
        zeros = column.count('0')
        ones = column.count('1')
        result.append('0' if zeros > ones else '1')
        # print("".join(column))
    #
    gamma_str = "".join(result)
    epsilon_str = gamma_str.replace('0', 'x').replace('1', '0').replace('x', '1')
    # print(gamma_str)
    # print(epsilon_str)
    gamma = int(gamma_str, 2)
    epsilon = int(epsilon_str, 2)

    print(gamma * epsilon)

##############################################################################

if __name__ == "__main__":
    main()


# the below imports are not required by the executor.py but is required by dank.serverbuilder.py

import re
import random
import zipfile
import concurrent.futures

# required imports for executor.py

import os
import sys
import time
import requests
from packaging import version
from colorama import init, Fore, Style

os.system("title dank.serverbuilder [ initializing ]")

# colors

init(autoreset=True)
red = Fore.RED + Style.BRIGHT
magenta = Fore.MAGENTA + Style.BRIGHT
white = Fore.WHITE + Style.BRIGHT

if __name__ == "__main__":

    project = "dank.serverbuilder"
    current_version = 2.1
    print(f"\n  {white}> {magenta}Version{white}: {current_version}")
    os.chdir(os.path.dirname(sys.argv[0]))

    # remove old build if exists

    try:os.remove(f"{project}.exe")
    except:pass

    # get latest version

    while True:
        try:
            latest_version = requests.get(f"https://raw.githubusercontent.com/SirDankenstien/{project}/main/executor_version.txt").content.decode()
            if "Not Found" in str(latest_version):latest_version = 0
            else:latest_version = float(latest_version)
            break
        except:
            wait = input(f"\n  {white}> {red}Failed to check for an update! Make sure you are connected to the Internet! Press {white}Enter {red}to try again.")

    # version checker / updater

    def download_latest():
        print(f"\n  {white}> {magenta}Downloading {white}{project}-latest.exe{magenta}...")
        data = requests.get(f"https://github.com/SirDankenstien/{project}/blob/main/{project}.exe?raw=true", allow_redirects=True).content
        open(f"{project}-latest.exe","wb").write(data)
        data = None
        print(f"\n  {white}> {magenta}Downloaded!\n\n  {white}> {magenta}Starting in 5s...")
        time.sleep(5)
        os.system(f"start {project}-latest.exe")
        sys.exit()

    if version.parse(str(latest_version)) > version.parse(str(current_version)):
        choice = str(input(f"\n  {white}> {magenta}Update Found{white}: {latest_version}\n\n  {white}> {magenta}Download latest version? {white}[ {magenta}y {white}/ {magenta}n {white}]: {magenta}")).lower()
        if choice == "y":download_latest()

    elif latest_version == current_version:
        print(f"\n  {white}> {magenta}Latest Version!")
    else:
        print(f"\n  {white}> {magenta}Development Version!")
    time.sleep(3)

    # get src from github

    while True:
        try:
            code = str(requests.get(f"https://raw.githubusercontent.com/SirDankenstien/{project}/main/{project}.py").content.decode())
            break
        except:
            wait = input(f"\n  {white}> {red}Failed to get src! Make sure you are connected to the Internet! Press {white}Enter {red}to try again")

    # execute, catch errors if any

    os.system("title dank.serverbuilder")

    try:exec(code)
    except Exception as exp:

        exc_type, exc_obj, exc_tb = sys.exc_info()
        os.system('cls')
        print(f"\n  {white}> {red}Error: {str(exp)} {white}| {red}{exc_type} {white}| {red}Line: {exc_tb.tb_lineno}")
        print(f"\n  {white}> {magenta}Please take a screenshot of this and post it on {white}https://github.com/SirDankenstien/dank.serverbuilder/issues/new")

        if not latest_version == current_version:
            choice = input(f"\n{white}> {magenta}Would you like to download the latest executor [ {white}y {magenta}/ {white}n {magenta}]: {white}").lower()
            if choice == "y":download_latest()

        wait = print(f"\n  {white}> {magenta}Press {white}ENTER {magenta}to continue: ")


import matplotlib.pyplot as plt

class MatrixPlotter(object):
    def __init__(self, name, min_row=0, min_col=0, max_row=None, max_col=None):
        self.name = name
        self.t = []

        # min/max values
        self.min_row = min_row
        self.min_col = min_col
        self.max_row = max_row
        self.max_col = max_col

        self._init_matrix()

    def __iter__(self):
        """Make object iterable."""
        for attr, value in self.__dict__.items():
            yield attr, value

    def _init_matrix(self):
        for i in range(self.min_row, self.max_row):
            for j in range(self.min_col, self.max_col):
                self.__dict__[f"a_{i}_{j}"] = []

    def append(self, t, mat):
        self.t.append(t)

        for i in range(self.min_row, self.max_row):
            for j in range(self.min_col, self.max_col):
                self.__dict__[f"a_{i}_{j}"].append(mat[i][j])

    def plot(self, axes=None, min_t=None, max_t=None, index_from_zero=False):
        """Creates a plot of them matrix entries.

        Param:
        * index_from_zero - whether to label matrix entries starting from
                0 or starting from 1
        """

        plot_rows = self.max_row - self.min_row
        plot_cols = self.max_col - self.min_col

        if axes is None:
            fig, axes = plt.subplots(plot_rows, plot_cols)
            fig.tight_layout()
            st = fig.suptitle(self.name, fontsize="x-large")

            # shift subplots down:
            st.set_y(0.95)
            fig.subplots_adjust(top=0.85)

        for row in range(self.min_row, self.max_row):
            plt_row = row - self.min_row

            for col in range(self.min_col, self.max_col):
                plt_col = col - self.min_col

                label = f"a_{row}_{col}"
                latex_label = (
                    f"$a_{{{row}\_{col}}}$"
                    if index_from_zero
                    else f"$a_{{{row+1}\_{col+1}}}$"
                )

                val = self.__dict__[label]
                min_val, max_val = min(val), max(val)
                range_val = max_val - min_val

                # display of very small values
                if range_val < 0.0001:
                    min_val = min_val - 0.002
                    max_val = max_val + 0.002
                else:
                    min_val = min_val - 0.2 * range_val
                    max_val = max_val + 0.2 * range_val

                if plot_rows > 1:
                    ax_obj = axes[plt_row][plt_col]
                else:
                    ax_obj = axes[plt_col]

                ax_obj.plot(self.t, self.__dict__[label])

                ax_obj.set_title(latex_label)
                ax_obj.set_xlim(left=min_t, right=max_t)
                ax_obj.set_ylim(bottom=min_val, top=max_val)
                ax_obj.grid(True)

        return axes


#!/usr/bin/env python

"""Run multiple copies of the specified command.
"""

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import argparse
import atexit
import os
import socket
import subprocess

import _init_paths  # NOQA
from robovat.utils.logging import logger

def parse_args():
    """Parse arguments.

    Returns:
        args: The parsed arguments.
    """
    parser = argparse.ArgumentParser()

    parser.add_argument(
            '--num_copies',
            dest='num_copies',
            type=int,
            help='Number of copies to run.',
            required=True)

    parser.add_argument(
            '--command',
            dest='command',
            type=str,
            help='The command to run.',
            required=True)

    parser.add_argument(
            '--log',
            dest='log_dir',
            type=str,
            help='The output directory to save the episode history.',
            required=True)

    args = parser.parse_args()

    return args

def main():
    args = parse_args()

    if os.path.isdir(args.log_dir):
        logger.warn('Output directory %s already exists.',
                    os.path.abspath(args.log_dir))

    processes = []
    for key in range(args.num_copies):
        logger.info('Starting copy %d / %d...', key, args.num_copies)

        hostname = socket.gethostname()
        hostname = hostname.split('.')[0]
        log_dir = os.path.abspath(args.log_dir)
        log_dir = os.path.join(log_dir, 'logs')
        if not os.path.isdir(log_dir):
            logger.info('Making output directory %s...', log_dir)
            os.makedirs(log_dir)

        log_path = os.path.join(log_dir, '%s_%02d.log' % (hostname, key))

        command = '%s >> %s' % (args.command, log_path)
        logger.info('Run the command: %s', command)
        process = subprocess.Popen(command, shell=True)
        processes.append(process)

    def cleanup():
        for ind, process in enumerate(processes):
            process.kill()
            logger.info('Killed process %d.', ind)

    atexit.register(cleanup)

    logger.info('All copies have been started. Running episode generation...')

    while True:
        pass

if __name__ == '__main__':
    main()


# -*- coding: utf-8 -*-
from plone import api
from plone.app.testing import setRoles
from plone.app.testing import TEST_USER_ID
from plone.dexterity.interfaces import IDexterityFTI
from docent.fileManager.interfaces import Imanagefiles
from docent.fileManager.testing import DOCENT_FILEMANAGER_INTEGRATION_TESTING  # noqa
from zope.component import createObject
from zope.component import queryUtility

import unittest

class managefilesIntegrationTest(unittest.TestCase):

    layer = DOCENT_FILEMANAGER_INTEGRATION_TESTING

    def setUp(self):
        """Custom shared utility setup for tests."""
        self.portal = self.layer['portal']
        setRoles(self.portal, TEST_USER_ID, ['Manager'])
        self.installer = api.portal.get_tool('portal_quickinstaller')

    def test_schema(self):
        fti = queryUtility(IDexterityFTI, name='managefiles')
        schema = fti.lookupSchema()
        self.assertEqual(Imanagefiles, schema)

    def test_fti(self):
        fti = queryUtility(IDexterityFTI, name='managefiles')
        self.assertTrue(fti)

    def test_factory(self):
        fti = queryUtility(IDexterityFTI, name='managefiles')
        factory = fti.factory
        obj = createObject(factory)
        self.assertTrue(Imanagefiles.providedBy(obj))

    def test_adding(self):
        obj = api.content.create(
            container=self.portal,
            type='managefiles',
            id='managefiles',
        )
        self.assertTrue(Imanagefiles.providedBy(obj))


import requests

from performance import prod_config

class PiwikClient:
    def __init__(self, config):
        """
        Args:
            token: Piwik access token
            piwik_base_url: URL of Piwik instance to query
            :param config:
        """
        self.token = config.PIWIK_AUTH_TOKEN
        self.piwik_base_url = config.PIWIK_BASE_URL
        self.limit = config.PIWIK_LIMIT
        self.period = config.PIWIK_PERIOD

    def get_nb_visits_for_rp(self, date, segment):
        qs = {
            'module': 'API',
            'idSite': '1',
            'format': 'JSON',
            'filter_limit': self.limit,
            'date': date,
            'period': self.period,
            'method': 'VisitsSummary.getVisits',
            'expanded': '1',
            'token_auth': self.token,
            'segment': segment
        }

        response = requests.get(self.piwik_base_url, qs)

        raw_result = response.json()
        return raw_result.get('value', 0)

    def get_nb_visits_for_page(self, date, segment):
        qs = {
            'module': 'API',
            'idSite': '1',
            'format': 'JSON',
            'filter_limit': self.limit,
            'date': date,
            'period': self.period,
            'method': 'Actions.getPageTitles',
            'token_auth': self.token,
            'segment': segment,
        }

        response = requests.get(self.piwik_base_url, qs)

        raw_result = response.json()
        nb_visits = next(iter(raw_result), {}).get('nb_visits', 0)
        return nb_visits

_piwik_client = PiwikClient(prod_config)

def get_segment_query_string(rp_name, journey_type=None, page_title=None):
    segment = f"customVariableValue1=={rp_name}"
    if journey_type:
        segment += f";customVariableValue3=={journey_type}"
    if page_title:
        segment += f";pageTitle={page_title}"
    return segment

def get_all_visits_for_rp_and_journey_type(date_start_string, rp_name, journey_type):
    segment = get_segment_query_string(rp_name, journey_type)
    return _piwik_client.get_nb_visits_for_rp(date_start_string, segment)

def get_all_referrals_for_rp(rp, date_start_string):
    segment_by_rp = get_segment_query_string(rp)
    return _piwik_client.get_nb_visits_for_rp(date_start_string, segment_by_rp)

def get_all_signin_attempts_for_rp(rp, date_start_string):
    journey_type = 'SIGN_IN'
    return get_all_visits_for_rp_and_journey_type(date_start_string, rp, journey_type)

def get_all_signup_attempts_for_rp(rp, date_start_string):
    journey_type = 'REGISTRATION'
    return get_all_visits_for_rp_and_journey_type(date_start_string, rp, journey_type)

def get_all_single_idp_attempts_for_rp(rp, date_start_string):
    journey_type = 'SINGLE_IDP'
    return get_all_visits_for_rp_and_journey_type(date_start_string, rp, journey_type)

def get_visits_will_not_work(rp, date_start_string):
    will_not_work_page = "@GOV.UK Verify will not work for you - GOV.UK Verify - GOV.UK - LEVEL_2"
    journey_type = 'REGISTRATION'
    will_not_work_segment = get_segment_query_string(rp, journey_type, will_not_work_page)

    return _piwik_client.get_nb_visits_for_page(date_start_string, will_not_work_segment)

def get_visits_might_not_work(rp, date_start_string):
    might_not_work_page = "@Why might this not work for me - GOV.UK Verify - GOV.UK - LEVEL_2"
    journey_type = 'REGISTRATION'
    might_not_work_segment = get_segment_query_string(rp, journey_type, might_not_work_page)

    return _piwik_client.get_nb_visits_for_page(date_start_string, might_not_work_segment)


import os
import pandas as pd
from bokeh.plotting import figure, ColumnDataSource
from bokeh.io import curdoc, show, output_file
from bokeh.layouts import column
from bokeh.models.widgets import Tabs, Panel
from bokeh.models import BooleanFilter, CDSView, HoverTool, DatetimeTickFormatter, Slope
from bokeh.palettes import Category20
from bokeh.models.formatters import NumeralTickFormatter
from Harvester.Utils import get_max_min_points
from LoggerApi.Logger import Logger

PATH_FOR_HTML = os.path.dirname(os.getcwd())
PATH_FOR_OTHERS = PATH_FOR_HTML + '/ServiceProvider/static/'
PATH_FOR_HTML += '/ServiceProvider/templates/'

def get_point_data(data_points):
    x = []
    y = []
    for item in data_points:
        x.append(item['x'])
        y.append(item['y'])
    return x, y

class VisualAnalysis(Logger):
    __W_PLOT = 1000
    __H_PLOT = 400
    __TOOLS = 'pan,wheel_zoom,reset,save,box_select,box_zoom'

    __VBAR_WIDTH = 12 * 60 * 60 * 1000
    __RED = Category20[7][6]
    __GREEN = Category20[5][4]
    __BLUE = Category20[3][0]
    __GRAY = Category20[12][11]

    d_formatter = DatetimeTickFormatter(
        hours=["%d %B %Y"],
        days=["%d %B %Y"],
        months=["%d %B %Y"],
        years=["%d %B %Y"],
    )

    def __init__(self, width=1500, height=600, tools=None, bar_width=0):
        super().__init__(VisualAnalysis.__name__, 'VIZ_LOG')
        self.__W_PLOT = width
        self.__H_PLOT = height
        if bar_width != 0:
            self.__VBAR_WIDTH = bar_width
        if tools is not None:
            self.__TOOLS = tools

    @staticmethod
    def __to_col_data_source(data):
        stock_data = ColumnDataSource(data=dict(date=[], open=[], close=[], high=[], low=[], MFI=[], MACD_Hist=[],
                                                MACD=[], MACD_Signal=[], ADX=[], index=[]))
        data['date'] = pd.to_datetime(data['time'], format='%Y-%m-%d')
        stock_data.data = stock_data.from_df(data)

        return stock_data

    def get_candlestick(self, data, show_graph=True, p=None):
        """Candle stick representation of the stock."""
        stock_data = self.__to_col_data_source(data)
        title = "{} Data ({}, Code:{})".format(stock_data.data['security_name'][0],
                                               stock_data.data['symbol'][0], stock_data.data['code'][0])
        if p is None:
            p = figure(x_axis_type="datetime", plot_width=self.__W_PLOT, plot_height=self.__H_PLOT, tools=self.__TOOLS
                       , title=title, toolbar_location='above')

        inc = stock_data.data['close'] > stock_data.data['open']
        dec = stock_data.data['open'] > stock_data.data['close']
        view_inc = CDSView(source=stock_data, filters=[BooleanFilter(inc)])
        view_dec = CDSView(source=stock_data, filters=[BooleanFilter(dec)])

        self.__set_xy_settings(p)

        p.segment(x0='date', x1='date', y0='low', y1='high', color=self.__GREEN, source=stock_data, view=view_inc)
        p.segment(x0='date', x1='date', y0='low', y1='high', color=self.__RED, source=stock_data, view=view_dec)

        main_vbar_green = p.vbar(x='date', width=self.__VBAR_WIDTH, top='open', bottom='close', fill_color=self.__GREEN,
                                 line_color=self.__GREEN, source=stock_data, view=view_inc, name="price")
        main_vbar_red = p.vbar(x='date', width=self.__VBAR_WIDTH, top='open', bottom='close', fill_color=self.__RED,
                               line_color=self.__RED, source=stock_data, view=view_dec, name="price")

        hover_tool = HoverTool(tooltips=[
            ("Date", "@time{%d-%b-%y}"),
            ("Open", "@open{Rs.0,0.00}"),
            ("Close", "@close{Rs.0,0.00}"),
            ("Volume", "@volume{(0.00 a)}")
        ], renderers=[main_vbar_green, main_vbar_red], formatters={"@time": 'datetime'})
        p.tools.append(hover_tool)

        elements = list()
        elements.append(p)
        curdoc().add_root(column(elements))
        curdoc().title = stock_data.data['security_name'][0] + '_CS'
        if show_graph:
            output_file(PATH_FOR_OTHERS + stock_data.data['symbol'][0] + '_CS.html',
                        '{} {}'.format(stock_data.data['security_name'][0], stock_data.data['code'][0]))
            show(p)

        return p

    def get_trend_line(self, data, col_name, show_graph=True, p=None):
        """Close price/ MFI/ ADX in line format"""
        stock_data = self.__to_col_data_source(data)
        max_min_data = self.__to_col_data_source(get_max_min_points(data, 'close'))
        title = "{} Data ({}, Code:{})".format(stock_data.data['security_name'][0],
                                               stock_data.data['symbol'][0], stock_data.data['code'][0])

        if p is None:
            p = figure(x_axis_type="datetime", plot_width=self.__W_PLOT, plot_height=self.__H_PLOT, tools=self.__TOOLS
                       , title=title, toolbar_location='above')

        if col_name == 'close':
            color = self.__BLUE
        elif col_name == 'MFI':
            color = self.__GRAY
        else:
            color = self.__GREEN
        main_line = p.line(x='date', y=col_name, source=stock_data, line_color=color, name=col_name)
        if col_name is 'close':
            p.circle_dot(x='date', y='close', source=max_min_data, line_color=self.__BLUE)

        self.__set_xy_settings(p)

        hover_tool = HoverTool(tooltips=[
            ("Date", "@time{%d-%b-%y}"),
            (col_name.upper(), "@" + col_name + "{0,0.00}"),
            ("Close", "@close{Rs.0,0.00}"),
            ("Volume", "@volume{(0.00 a)}")
        ], renderers=[main_line], formatters={"@time": 'datetime'})
        p.tools.append(hover_tool)

        elements = list()
        elements.append(p)
        curdoc().add_root(column(elements))
        curdoc().title = stock_data.data['security_name'][0] + '_' + col_name

        if show_graph:
            output_file(PATH_FOR_OTHERS + stock_data.data['symbol'][0] + '_{}.html'.format(col_name),
                        '{} {}'.format(stock_data.data['security_name'][0], stock_data.data['code'][0]))
            show(p)

        return p

    def get_macd_plot(self, data, show_graph=False, p=None):
        """MACD indicator plot."""
        stock_data = self.__to_col_data_source(data)

        title = "{} Data ({}, Code:{})".format(stock_data.data['security_name'][0],
                                               stock_data.data['symbol'][0], stock_data.data['code'][0])

        if p is None:
            p = figure(x_axis_type="datetime", plot_width=self.__W_PLOT, plot_height=self.__H_PLOT, tools=self.__TOOLS
                       , title=title, toolbar_location='above')

        macd_hist = p.vbar(x='date', width=self.__VBAR_WIDTH, top='MACD_Hist', fill_color=self.__GRAY,
                           line_color=self.__GRAY, source=stock_data, name="macd_hist")
        macd_line = p.line(x='date', y='MACD', source=stock_data, line_color=self.__BLUE, name="macd_line")
        macd_signal = p.line(x='date', y='MACD_Signal', source=stock_data, line_color=self.__RED, name="macd_signal")

        self.__set_xy_settings(p)

        hover_tool = HoverTool(tooltips=[
            ("Date", "@time{%d-%b-%y}"),
            ("MACD", "@MACD{(0, 0.00)}"),
            ("MACD_S", "@MACD_Signal{(0, 0.00)}"),
            ("MACD_Hist", "@MACD_Hist{(0, 0.00)}")
        ], renderers=[macd_hist, macd_line, macd_signal], formatters={"@time": 'datetime'})
        p.tools.append(hover_tool)

        elements = list()
        elements.append(p)
        curdoc().add_root(column(elements))
        curdoc().title = stock_data.data['security_name'][0] + 'MACD'

        if show_graph:
            output_file(PATH_FOR_OTHERS + stock_data.data['symbol'][0] + '_MACD.html',
                        '{} {}'.format(stock_data.data['security_name'][0], stock_data.data['code'][0]))
            show(p)

        return p

    def get_volume_hist(self, data, show_graph=False, p=None):
        """Volume traded histogram."""
        stock_data = self.__to_col_data_source(data)
        title = "{} Data ({}, Code:{})".format(stock_data.data['security_name'][0],
                                               stock_data.data['symbol'][0], stock_data.data['code'][0])

        if p is None:
            p = figure(x_axis_type="datetime", plot_width=self.__W_PLOT, plot_height=self.__H_PLOT - 200,
                       tools=self.__TOOLS
                       , title=title, toolbar_location='above', y_range=(0, data['volume'].max()))

        main_vbar = p.vbar(x='date', width=self.__VBAR_WIDTH, top='volume', fill_color=self.__GRAY,
                           line_color=self.__GRAY, source=stock_data, name="volume")

        self.__set_xy_settings(p)

        hover_tool = HoverTool(tooltips=[
            ("Date", "@time{%d-%b-%y}"),
            ("Open", "@open{Rs.0,0.00}"),
            ("Close", "@close{Rs.0,0.00}"),
            ("Volume", "@volume{(0.00 a)}")
        ], renderers=[main_vbar], formatters={"@time": 'datetime'})
        p.tools.append(hover_tool)

        elements = list()
        elements.append(p)
        curdoc().add_root(column(elements))
        curdoc().title = stock_data.data['security_name'][0] + 'VOL'

        if show_graph:
            output_file(PATH_FOR_OTHERS + stock_data.data['symbol'][0] + '_VOL.html',
                        '{} {}'.format(stock_data.data['security_name'][0], stock_data.data['code'][0]))
            show(p)

        return p

    def __set_xy_settings(self, p):
        p.xaxis.formatter = self.d_formatter
        p.xaxis.major_label_orientation = 3.14 / 4
        p.grid.grid_line_alpha = 0.3

        p.yaxis.formatter = NumeralTickFormatter(format='0,0[.]000')
        p.x_range.range_padding = 0.05
        p.xaxis.ticker.desired_num_ticks = 40
        p.xaxis.major_label_orientation = 3.14 / 4

    def show_all(self, items, stock_name):
        panels = []
        if 'cd_stick' in items.keys():
            panels.append(Panel(child=column(items['cd_stick'], items['volume']), title='CandleStick/Volume'))
        if 'price' in items.keys():
            panels.append(Panel(child=column(items['price'], items['volume']), title='Price/Volume'))
        if 'MACD' in items.keys():
            panels.append(Panel(child=column(items['MACD'], items['cd_stick']), title='MACD'))
        if 'MFI' in items.keys():
            panels.append(Panel(child=column(items['MFI'], items['cd_stick']), title='MFI'))
        if 'ADX' in items.keys():
            panels.append(Panel(child=column(items['ADX'], items['cd_stick']), title='ADX'))
        if 'PITCH_FORK' in items.keys():
            panels.append(Panel(child=column(items['PITCH_FORK']), title="MOD. SCHIFF'S PITCHFORK"))
        tabs = Tabs(tabs=panels)
        elements = list()
        elements.append(tabs)
        curdoc().add_root(column(elements))
        curdoc().title = stock_name + '_TABS'
        output_file(PATH_FOR_HTML + stock_name + '_TABS.html')
        show(tabs)
        self.add("INFO", "TABS VIEW ON.")

    def get_line(self, point_a, point_b, p=None, colour='gray', show_graph=False):
        """Other util plots"""
        title = 'UTIL_PLOT'

        if p is None:
            p = figure(x_axis_type="datetime", plot_width=self.__W_PLOT, plot_height=self.__H_PLOT - 200,
                       tools=self.__TOOLS
                       , title=title, toolbar_location='above', y_range=(-abs(point_a['y'] + 0.2 * point_a['y']),
                                                                         abs(point_a['y'] + 0.2 * point_a['y'])))

        if colour is 'b':
            color = self.__BLUE
        elif colour is 'g':
            color = self.__GREEN
        elif colour is 'r':
            color = self.__RED
        else:
            color = self.__GRAY
        p.line([point_a['x'], point_b['x']], [point_a['y'], point_b['y']], line_width=4, color=color)
        p.circle([point_a['x'], point_b['x']], [point_a['y'], point_b['y']], size=10, color=color)
        self.__set_xy_settings(p)

        elements = list()
        elements.append(p)
        curdoc().add_root(column(elements))

        if show_graph:
            show(p)

        return p

    def get_slope_for_pitch_fork(self, m, b, plot_data, p=None, show_graph=False, layout=False, debug=False):
        if p is None:
            p = figure(x_axis_type="datetime", plot_width=self.__W_PLOT, plot_height=self.__W_PLOT - 800,
                       tools=self.__TOOLS, toolbar_location='above')

        point_data = {'MID': get_point_data(plot_data['MID']), 'REV': get_point_data(plot_data['REV'])}

        p = self.get_line(plot_data['point_1'], plot_data['point_2'], p=p, colour='b')
        p = self.get_line(plot_data['point_2'], plot_data['point_3'], p=p, colour='b')
        p = self.get_line(plot_data['mid_23'], plot_data['mid_12'], p=p)
        p = self.get_line(plot_data['point_2'], plot_data['GRAD_UP'], p=p)  # UP
        p = self.get_line(plot_data['point_3'], plot_data['GRAD_DN'], p=p)  # DN
        p = self.get_line(plot_data['mid_23'], plot_data['GRAD_MID'], p=p, colour='b')  # MID
        p = self.get_line(plot_data['m1'], plot_data['GRAD_UPM'], p=p, colour='g')  # UPM
        p = self.get_line(plot_data['m2'], plot_data['GRAD_DNM'], p=p, colour='r')  # DNM

        if debug:
            p.circle(x=point_data['MID'][0], y=point_data['MID'][1], size=10, color=self.__GRAY)
            p.circle(x=point_data['REV'][0], y=point_data['REV'][1], size=10, color=self.__RED)

        if layout:
            p.add_layout(Slope(gradient=m, y_intercept=b['UP'], line_color=self.__GRAY))
            p.add_layout(Slope(gradient=m, y_intercept=b['UPM'], line_color=self.__GREEN))
            p.add_layout(Slope(gradient=m, y_intercept=b['MID'], line_color=self.__BLUE))
            p.add_layout(Slope(gradient=m, y_intercept=b['DNM'], line_color=self.__RED))
            p.add_layout(Slope(gradient=m, y_intercept=b['DN'], line_color=self.__GRAY))

        self.__set_xy_settings(p)

        elements = list()
        elements.append(p)
        curdoc().add_root(column(elements))

        if show_graph:
            show(p)

        return p


import requests
from bs4 import BeautifulSoup
import json

url = "https://technews.tw/"
response = requests.get(url)
soup = BeautifulSoup(response.text, 'html.parser')

paragraph = soup.find("ul", class_="screen")

outputList = []

for para1 in paragraph.find_all("li", class_="block2014"):
    outputDict = {}
    outputDict['category'] = para1.find("div", class_="cat01").text
    outputDict['sum_title'] = para1.find("h3").text
    outputDict['sum_title_url'] = para1.find("a").get("href")
    outputDict['spotlist'] = []

    for spot in para1.find_all("li", class_="spotlist"):
        spot_info = {}
        spot_info['title'] = spot.find("a").text
        spot_info['url'] = spot.find("a").get("href")
        outputDict['spotlist'].append(spot_info)

    outputList.append(outputDict)

with open('output.json', 'w', encoding='utf-8') as fp:
    json.dump(outputList, fp, ensure_ascii=False, indent=4)


from itertools import product
import math
def Prime(n):
    flag=0
    for i in range(2,int(math.sqrt(n))+1):
        if n%i==0:
            flag=1
            break
    if flag==0:return True
    else:return False
def PrimeNumbers(N):
    l=[]
    for i in range(2,N+1):
        if Prime(i)==True:l.append(i)
    return l

if __name__=='__main__':
    T=int(input())
    for i in range(T):
        N=int(input())
        p=PrimeNumbers(N)
        #print(p)
        pairs=list(product(p,repeat=2))
        #print(pairs)
        for j in range(len(pairs)):
            if pairs[j][0]*pairs[j][1]<=N:
                print(pairs[j][0],pairs[j][1],end=' ')
        print('')


from transaction import Wallet, Transaction, TransactionOutput
from blockchain import Blockchain, Block
import time

def main():
    bc = Blockchain()

    walletA = Wallet('A')
    walletB = Wallet('B')
    coinbase = Wallet('C')

    genesis_trx = Transaction(coinbase.public_key, walletA.public_key, 1000000, None)
    genesis_trx.generate_signature(coinbase.private_key, coinbase.password)
    genesis_trx.transaction_id = '0'
    genesis_trx.outputs.append(TransactionOutput(genesis_trx.recipient, genesis_trx.value, genesis_trx.transaction_id))
    bc.genesis_trx = genesis_trx
    bc.UTXOs[bc.genesis_trx.outputs[0].id] = bc.genesis_trx.outputs[0]

    print('Creating and mining genesis block...')
    genesis = Block('0')
    genesis.add_transaction(genesis_trx)
    bc.add(genesis)

    f=open("time.csv", "w+")

    for i in range(1000000):
        print("%d: " % i, end='')
        start = time.time()
        block = Block(bc.get_last_hash())
        # print("Wallet A's balance is:", walletA.get_balance())
        # print("Wallet A is attempting to send funds(1) to Wallet B...")
        block.add_transaction(walletA.send_funds(walletB.public_key, 1))
        bc.add(block)
        end = time.time()
        f.write(("%d,%f\n" % (i, end-start)))
        # print("Wallet A's balance is:", walletA.get_balance())
        # print("Wallet B's balance is:", walletB.get_balance())

    print("Wallet B's balance is:", walletB.get_balance())
    print("Wallet B is attempting to send funds(1000000) to Wallet A...")

    start = time.time()
    block = Block(bc.get_last_hash())
    block.add_transaction(walletB.send_funds(walletA.public_key, 1000000))
    bc.add(block)
    end = time.time()

    print("Wallet A's balance is:", walletA.get_balance())
    print("Wallet B's balance is:", walletB.get_balance())

    print("Time elapsed: %fs" % (end-start))

    bc.is_valid()

    f.close()

if __name__ == "__main__":
    main()