import amqpstorm
import urllib.parse
import socket
import random
import traceback
import logging
import threading
import multiprocessing
import queue
import time

class Heartbeat_Timeout_Exception(Exception):
	pass

class Message_Publish_Exception(Exception):
	pass

class ConnectorManager:
	def __init__(self, config, runstate, task_queue, response_queue):

		assert 'host'                     in config
		assert 'userid'                   in config
		assert 'password'                 in config
		assert 'virtual_host'             in config
		assert 'task_queue_name'          in config
		assert 'response_queue_name'      in config
		assert 'task_exchange'            in config
		assert 'task_exchange_type'       in config
		assert 'response_exchange'        in config
		assert 'response_exchange_type'   in config
		assert 'master'                   in config
		assert 'synchronous'              in config
		assert 'flush_queues'             in config
		assert 'heartbeat'                in config
		assert 'sslopts'                  in config
		assert 'poll_rate'                in config
		assert 'prefetch'                 in config
		assert 'durable'                  in config
		assert 'socket_timeout'           in config
		assert 'hearbeat_packet_timeout'  in config
		assert 'ack_rx'                   in config

		self.is_master = config['master']

		self.log = logging.getLogger("Main.Connector.Internal(%s)" % config['virtual_host'])
		self.runstate           = runstate
		self.config             = config
		self.task_queue         = task_queue
		self.response_queue     = response_queue

		self.connected          = multiprocessing.Value("i", 0)

		self.had_exception      = multiprocessing.Value("i", 0)
		self.threads_live       = multiprocessing.Value("i", 1)

		self.thread_management_lock = threading.Lock()

		self.info_printerval        = time.time()
		self.last_hearbeat_sent     = time.time()
		self.last_hearbeat_received = time.time()
		self.last_message_received  = time.time()

		self.rx_timeout_lock        = threading.Lock()
		self.heartbeat_timeout_lock = threading.Lock()

		self.session_fetched        = 0
		self.queue_fetched          = 0
		self.active                 = 0
		self.sent_messages = 0
		self.recv_messages = 0

		self.keepalive_num     = 0
		self.prefetch_extended = False

		self.delivered = 0
		self.die_timeout = time.time()

		self.connect_lock = threading.Lock()
		self.__connect()

	def __connect(self):

		self.hearbeat_packet_timeout = self.config['hearbeat_packet_timeout']
		self.keepalive_exchange_name = "keepalive_exchange"+str(id("wat"))

		self.__open_connection()
		self.__reset_channel()

	def __open_connection(self):
		self.log.info("Initializing AMQP connection.")
		self.storm_connection = amqpstorm.Connection(
				hostname     = self.config['host'].split(":")[0],
				username     = self.config['userid'],
				password     = self.config['password'],
				port         = int(self.config['host'].split(":")[1]),
				virtual_host = self.config['virtual_host'],
				heartbeat    = self.config['socket_timeout'] // 2,
				timeout      = self.config['socket_timeout'],
				ssl          = True,
				ssl_options  = {
					'ca_certs'           : self.config['sslopts']['ca_certs'],
					'certfile'           : self.config['sslopts']['certfile'],
					'keyfile'            : self.config['sslopts']['keyfile'],
				}

			)

	def __reset_channel(self):

		if hasattr(self, 'storm_channel'):
			close_funcs = [
				self.storm_channel.stop_consuming,
				self.storm_channel.close,
			]
			for cfunc in close_funcs:
				try:
					cfunc()
				except Exception as e:
					self.log.warning("Warning when tearing down connection!")
					for line in traceback.format_exc().split("\n"):
						self.log.warning(line)

		self.__configure_channel()
		self.__configure_rpc_exchanges()
		self.__configure_keepalive_channel()

		# Re-enqueue any not-acked packets.
		self.storm_channel.basic.recover(requeue=True)
		self.storm_channel._inbound.clear()

		self.__start_consume()

	def __configure_channel(self):

		self.log.info("Connection established. Setting up consumer.")
		self.storm_channel = self.storm_connection.channel(rpc_timeout=self.config['socket_timeout'])

		# Initial QoS is tiny, throttle it up after everything is actually running.
		self.storm_channel.basic.qos(1, global_=True)
		self.prefetch_extended = False

	def __configure_keepalive_channel(self):

		# "NAK" queue, used for keeping the event loop ticking when we
		# purposefully do not want to receive messages
		# THIS IS A SHITTY WORKAROUND for keepalive issues.

		self.log.info("Configuring keepalive channel")
		self.storm_channel.exchange.declare(
					exchange=self.keepalive_exchange_name,
					durable=False,
					auto_delete=True)

		self.storm_channel.queue.declare(
					queue=self.keepalive_exchange_name+'.nak.q',
					durable=False,
					auto_delete=True)

		self.storm_channel.queue.bind(
					queue=self.keepalive_exchange_name+'.nak.q',
					exchange=self.keepalive_exchange_name,
					routing_key="nak")

		self.log.info("Configuring keepalive channel complete")

	def __configure_rpc_exchanges(self):

		self.log.info("Configuring RPC channel.")

		# self.is_master

		self.log.info("Declaring task exchange: '%s' -> '%s' -> '%s'", self.config['task_exchange'], self.config['task_exchange_type'], self.config['durable'])
		self.storm_channel.exchange.declare(
					exchange      = self.config['task_exchange'],
					exchange_type = self.config['task_exchange_type'],
					durable       = self.config['durable']
				)

		self.log.info("Declaring response exchange: '%s' -> '%s' -> '%s'", self.config['response_exchange'], self.config['response_exchange_type'], self.config['durable'])
		self.storm_channel.exchange.declare(
					exchange      =self.config['response_exchange'],
					exchange_type =self.config['response_exchange_type'],
					durable       =self.config['durable']
				)

		# # set up consumer and response queues
		# if self.config['master']:
		# 	# Master has to declare the response queue so it can listen for responses
		# 	self.channel.queue_declare(self.config['response_queue_name'], auto_delete=False, durable=self.config['durable'])
		# 	self.channel.queue_bind(   self.config['response_queue_name'], exchange=self.config['response_exchange'], routing_key=self.config['response_queue_name'].split(".")[0])
		# 	self.log.info("Binding queue %s to exchange %s.", self.config['response_queue_name'], self.config['response_exchange'])

		# if not self.config['master']:
		# 	# Clients need to declare their task queues, so the master can publish into them.
		# 	self.channel.queue_declare(self.config['task_queue_name'], auto_delete=False, durable=self.config['durable'])
		# 	self.channel.queue_bind(   self.config['task_queue_name'], exchange=self.config['task_exchange'], routing_key=self.config['task_queue_name'].split(".")[0])
		# 	self.log.info("Binding queue %s to exchange %s.", self.config['task_queue_name'], self.config['task_exchange'])

		self.log.info("Declaring response queue: '%s' -> '%s'", self.config['response_queue_name'], self.config['durable'])
		self.storm_channel.queue.declare(
					queue         =self.config['response_queue_name'],
					durable       =self.config['durable'],
					auto_delete=False)

		self.log.info("Binding queue '%s' to '%s' with routing key '%s'", self.config['response_queue_name'], self.config['response_exchange'], self.config['response_queue_name'].split(".")[0])
		self.storm_channel.queue.bind(
					queue         =self.config['response_queue_name'],
					exchange      =self.config['response_exchange'],
					routing_key   =self.config['response_queue_name'].split(".")[0])

		self.log.info("Configured.")

	def __poke_keepalive(self):
		mbody = "keepalive %s, random: %s" % (self.keepalive_num, random.random())
		self.storm_channel.basic.publish(body=mbody, exchange=self.keepalive_exchange_name, routing_key='nak',
			properties={
				'correlation_id' : "keepalive_{}".format(self.keepalive_num)
			})
		self.keepalive_num += 1
		self.last_hearbeat_sent = time.time()

	def __do_rx(self):
		self.storm_channel.process_data_events(to_tuple=False, auto_decode=False)

	def handle_rx(self, message):
		# self.log.info("Received message!")
		# self.log.info("Message channel: %s", message.channel)
		# self.log.info("Message properties: %s", message.properties)
		corr_id = message.properties['correlation_id']
		if isinstance(corr_id, (bytes, bytearray)):
			corr_id = corr_id.decode('ascii')
		if corr_id.startswith('keepalive'):
			self.__handle_keepalive_rx(corr_id, message)
		else:
			self.__handle_normal_rx(corr_id, message)

		if self.prefetch_extended is False:
			self.prefetch_extended = True
			self.storm_channel.basic.qos(1000, global_=True)
			self.log.info("Prefetch updated")

	def __handle_keepalive_rx(self, corr_id, message):

		with self.heartbeat_timeout_lock:
			self.last_hearbeat_received = time.time()
		message.ack()
		self.log.info("Heartbeat packet received! %s -> %s", message.body.decode("ascii"), corr_id)

	def __handle_normal_rx(self, corr_id, message):

		with self.rx_timeout_lock:
			self.last_message_received = time.time()

		count = 0
		while self.response_queue.qsize() > 500:
			time.sleep(0.1)
			count += 1
			if count > 100:
				self.log.info("AMQP Connector sleeping while queue is processed.")

		self.response_queue.put(message.body)
		message.ack()

		self.log.info("Message packet received: %s. Queue size: %s", len(message.body), self.response_queue.qsize())

	def __start_consume(self):
		self.log.info("Bound. Triggering consume")
		self.storm_channel.basic.consume(self.handle_rx, queue=self.config['response_queue_name'],    no_ack=False)
		self.storm_channel.basic.consume(self.handle_rx, queue=self.keepalive_exchange_name+'.nak.q', no_ack=False)
		self.log.info("Consume triggered.")

	def __do_tx(self):

		for dummy_x in range(500):

			if self.__should_die():
				self.log.info("Transmit loop saw exit flag. Breaking!")
				return
			# self.log.info("Items in outgoing TX queue: %s", self.task_queue.qsize())
			print("/", end="", flush=True)
			try:
				put = self.task_queue.get_nowait()
			except queue.Empty:
				return

			try:

				# print("Putting item: ", put, self.config['task_exchange'], self.config)

				out_key   = self.config['task_queue_name'].split(".")[0]

				msg_prop = {}
				if self.config['task_queue_name']:
					msg_prop["delivery_mode"] = 2

				if not self.storm_channel:
					raise Message_Publish_Exception("Failed to publish message!")

				self.storm_channel.basic.publish(body=put, exchange=self.config['task_exchange'], routing_key=out_key, properties=msg_prop)

				self.sent_messages += 1
				self.active -= 1

			except amqpstorm.AMQPError as e:
				self.log.error("Error while tx_polling interface!")
				self.task_queue.put(put)
				self.log.error("	%s", e)
				for line in traceback.format_exc().split("\n"):
					self.log.error(line)
				self.had_exception.value = 1
				return
			except Message_Publish_Exception as e:
				self.log.error("Error while publishing message!")
				self.task_queue.put(put)
				self.log.error("	%s", e)
				for line in traceback.format_exc().split("\n"):
					self.log.error(line)
				self.had_exception.value = 1
				return

	def __should_die(self):
		# ret = self.runstate.value != 1 or self.threads_live.value != 1 or self.had_exception.value != 0

		if self.runstate.value == 1:
			self.die_timeout = time.time()
		elif self.response_queue.qsize() > 0:
			pass
		else:
			self.die_timeout -= 500

		ret = self.threads_live.value != 1 or self.had_exception.value != 0 or (time.time() - self.die_timeout > 20)
		if ret:

			self.log.warning("Should die flag! Runstate: %s, threads live: %s, had exception: %s.",
				"running" if self.runstate.value == 1 else "halting",
				"threads alive" if self.threads_live.value == 1 else "threads stopping",
				"yes" if self.had_exception.value == 1 else "no"
				)
		return ret

	def __check_timeouts(self):
		now = time.time()

		if self.info_printerval + 10 < now:
			self.log.info("Interface timeout thread. Ages: heartbeat -> %0.2f, last message -> %0.2f, TX, RX q: (%s, %s).",
					now - self.last_hearbeat_received,
					now - self.last_message_received,
					self.task_queue.qsize(),
					self.response_queue.qsize(),
					)
			self.info_printerval += 10

		# Send heartbeats every 5 seconds.
		if self.last_hearbeat_sent + 5 < now:
			self.__poke_keepalive()

		if self.last_hearbeat_received + self.config['hearbeat_packet_timeout'] < now:
			self.log.error("Heartbeat receive timeout! Triggering reconnect due to missed heartbeat.")
			self.last_hearbeat_received = now
			try:
				self.__reset_channel()
			except:
				self.had_exception.value = 1

		if (self.last_message_received + (self.config['hearbeat_packet_timeout'] * 100) < now or
			self.last_hearbeat_received + (self.config['hearbeat_packet_timeout'] * 100) < now):
			# Attempt recover if we've been idle for a while.
			self.log.info("Reconnect retrigger seems to have not fixed the issue (or just no messages)?")

			self.had_exception.value = 1

	def run(self):

		self.__start_consume()
		while not self.__should_die():
			try:
				print("\\", end="", flush=True)
				self.__do_tx()
				self.__do_rx()
				self.__check_timeouts()
				time.sleep(0.1)
			except Exception as e:
				with open("mq error %s.txt" % time.time(), 'w') as fp:
					fp.write("Error!\n\n")
					fp.write(traceback.format_exc())
				self.log.error("Error!")
				self.log.error("%s", e)
				for line in traceback.format_exc().split("\n"):
					self.log.error(line)
				raise e

	def disconnect(self):
		with self.connect_lock:
			self.prefetch_extended = False
			self.threads_live.value = 0
			self.storm_channel.close()
			self.storm_connection.close()

	def shutdown(self):
		self.log.info("ConnectorManager shutdown called!")
		for dummy_x in range(30):
			qs = self.task_queue.qsize()
			if qs == 0:
				break
			else:
				self.log.warning("Outgoing queue draining. Items: %s", qs)
				self.__do_tx()
				time.sleep(1)
		self.threads_live.value = 0
		self.disconnect()

	def __del__(self):
		self.shutdown()

	@classmethod
	def run_fetcher(cls, config, runstate, tx_q, rx_q):
		'''
		bleh

		'''

		log = logging.getLogger("Main.Connector.Manager(%s)" % config['virtual_host'])

		log.info("Worker thread starting up.")
		try:
			print("Connecting %s" % config['virtual_host'])
			interface = cls(config, runstate, tx_q, rx_q)
			print("Entering monitor-loop %s, runstate: %s" % (config['virtual_host'], runstate.value))
			interface.run()

		except Exception:
			log.error("Exception in connector! Terminating connection...")
			for line in traceback.format_exc().split('\n'):
				log.error(line)

			if runstate.value != 0:
				log.error("Triggering reconnection...")

		try:
			interface.shutdown()
		except Exception:
			log.info("")
			log.error("Failed pre-emptive closing before reconnection. May not be a problem?")
			for line in traceback.format_exc().split('\n'):
				log.error(line)

		log.info("")
		log.info("Worker thread has terminated.")
		log.info("")

class Connector:

	def __init__(self, *args, **kwargs):

		assert args == (), "All arguments must be passed as keyword arguments. Positional arguments: '%s'" % (args, )

		self.log = logging.getLogger("Main.Connector")
		self.thread_management_lock = threading.Lock()

		self.log.info("Setting up AqmpConnector!")

		config = {
			'host'                     : kwargs['host'],
			'userid'                   : kwargs['userid'],
			'password'                 : kwargs['password'],
			'virtual_host'             : kwargs['virtual_host'],
			'task_queue_name'          : kwargs['task_queue'],
			'response_queue_name'      : kwargs['response_queue'],
			'task_exchange_type'       : kwargs['task_exchange_type'],
			'task_exchange'            : kwargs.get('task_exchange',            'tasks.e'),
			'response_exchange'        : kwargs.get('response_exchange',        'resps.e'),
			'response_exchange_type'   : kwargs.get('response_exchange_type',   'direct'),
			'master'                   : kwargs.get('master',                   False),
			'synchronous'              : kwargs.get('synchronous',              True),
			'flush_queues'             : kwargs.get('flush_queues',             False),
			'heartbeat'                : kwargs.get('heartbeat',                 15),
			'sslopts'                  : kwargs.get('ssl',                      None),
			'poll_rate'                : kwargs.get('poll_rate',                  0.25),
			'prefetch'                 : kwargs.get('prefetch',                   1),
			'durable'                  : kwargs.get('durable',                  False),
			'socket_timeout'           : kwargs.get('socket_timeout',            15),

			'hearbeat_packet_timeout'  : kwargs.get('hearbeat_packet_timeout',  30),
			'ack_rx'                   : kwargs.get('ack_rx',                   True),
		}

		assert config['hearbeat_packet_timeout'] > config['socket_timeout'],                                   \
			"Heartbeat time must be greater then socket timeout! Heartbeat interval: %s. Socket timeout: %s" % \
			(config['hearbeat_packet_timeout'], config['socket_timeout'])

		self.log.info("Using virtualhost: '%s'.", config['virtual_host'])
		self.log.info("Comsuming from queue '%s', emitting responses on '%s'.", config['task_queue_name'], config['response_queue_name'])

		# Validity-Check args
		if not config['host']:
			raise ValueError("You must specify a host to connect to!")

		assert        config['task_queue_name'].endswith(".q") is True
		assert    config['response_queue_name'].endswith(".q") is True
		assert     config['task_exchange'].endswith(".e") is True
		assert config['response_exchange'].endswith(".e") is True

		self.is_master = config['master']

		# If we're not the master, swap the queues.
		if not self.is_master:
			config['task_queue_name'], config['response_queue_name']       = config['response_queue_name'], config['task_queue_name']
			config['task_exchange'], config['response_exchange']           = config['response_exchange'], config['task_exchange']
			config['task_exchange_type'], config['response_exchange_type'] = config['response_exchange_type'], config['task_exchange_type']

		# Patch in the port number to the host name if it's not present.
		# This is really clumsy, but you can't explicitly specify the port
		# in the amqp library
		if not ":" in config['host']:
			if config['ssl']:
				config['host'] += ":5671"
			else:
				config['host'] += ":5672"

		self.queue_fetched       = 0
		self.queue_put           = 0

		# set up the task and response queues.
		# These need to be multiprocessing queues because
		# messages can sometimes be inserted from a different process
		# then the interface is created in.
		self.taskQueue = queue.Queue()
		self.responseQueue = queue.Queue()

		self.runstate = multiprocessing.Value("b", 1)

		self.log.info("Starting AMQP interface thread.")

		self.forwarded = 0

		with self.thread_management_lock:
			self.thread = None
		self.__config = config
		self.checkLaunchThread()

	def checkLaunchThread(self):
		queue_overfull = self.taskQueue.qsize() > 10000

		with self.thread_management_lock:
			if self.thread and self.thread.isAlive() and not queue_overfull:
				return
		if queue_overfull:
			self.runstate.value = 0
			for dummy_x in range(30):

				with self.thread_management_lock:
					if not self.thread.isAlive():
						self.thread.join()
						self.thread = None
						break
				time.sleep(1)

		with self.thread_management_lock:

			if self.thread and not self.thread.isAlive():
				self.thread.join()
				self.log.error("")
				self.log.error("")
				self.log.error("")
				self.log.error("Thread has died!")
				self.log.error("")
				self.log.error("")
				self.log.error("")

			self.thread = threading.Thread(
				target=ConnectorManager.run_fetcher,
				args=(self.__config, self.runstate, self.taskQueue, self.responseQueue),
				daemon=True,
				name="RMQ: {}".format(self.__config['virtual_host']))

			self.thread.start()
			while not self.thread.isAlive():
				time.sleep(1)
				self.log.info("Waiting for thread to start")

	def getMessage(self):
		'''
		Try to fetch a message from the receiving Queue.
		Returns the method if there is one, False if there is not.
		Non-Blocking.
		'''
		self.checkLaunchThread()

		try:
			put = self.responseQueue.get_nowait()
			self.queue_fetched += 1
			self.forwarded += 1
			if self.forwarded >= 25:
				self.log.info("Fetched item from proxy queue. Total received: %s, total sent: %s, qsizes: %s, %s", self.queue_fetched, self.queue_put, self.responseQueue.qsize(), self.taskQueue.qsize())
				self.forwarded = 0
			return put
		except queue.Empty:
			return None

	def putMessage(self, message, synchronous=False):
		'''
		Place a message into the outgoing queue.

		the items in the outgoing queue are less then the
		value of synchronous
		'''
		self.checkLaunchThread()
		timeouts = 0
		if synchronous:
			while self.taskQueue.qsize() > synchronous:
				time.sleep(0.1)
				timeouts += 1
				if timeouts > 100:
					return self.taskQueue.qsize()
		self.queue_put += 1
		self.taskQueue.put(message)

		return self.taskQueue.qsize()

	def stop(self):
		'''
		Tell the AMQP interface thread to halt, and then join() on it.
		Will block until the queue has been cleanly shut down.
		'''
		self.log.info("Stopping AMQP interface thread.")
		self.runstate.value = 0
		if self.is_master:
			resp_q = self.responseQueue
		else:
			resp_q = self.taskQueue

		block = 0
		blocklen = 25
		while resp_q.qsize() > 0:
			self.log.info("%s remaining outgoing AMQP items (%s).", resp_q.qsize(), blocklen-block)
			time.sleep(1)
			block += 1
			if block > blocklen:
				break

		self.log.info("%s remaining outgoing AMQP items. Joining on thread.", resp_q.qsize())

		with self.thread_management_lock:
			self.thread.join()

		self.log.info("AMQP interface thread halted.")

	def __del__(self):
		# print("deleter: ", self.runstate, self.runstate.value)
		if self.runstate.value:
			self.stop()


# isomeasurer.py
# ALS 2017/06/01
import os
import astropy.units as u
from astropy.io import fits
import numpy as np
import astropy.table as at
import pickle
import scipy.ndimage as simg

from ..measurer import Measurer
from ... import tabtools
from . import polytools
from . import plottools

class isoMeasurer(Measurer):

	def __init__(self, **kwargs):
		"""
		child of Measurer
		do isophotal measurements
		"""
		super(isoMeasurer, self).__init__(**kwargs)

		self.msrtype = 'iso'

	def get_fp_contours(self, imgtag='OIII5008_I', onlycenter=False, suffix=''):
		""" e.g., msr_iso-OIII5008_I{suffix}_contours.pkl
			\or   msr_iso-OIII5008_I{suffix}_contours-ctr.pkl
		"""
		if onlycenter:
			ctrtag = '-ctr'
		else:
			ctrtag = ''

		fp_root = self.get_fp_msrtagroot(imgtag=imgtag, suffix=suffix)
		return fp_root+'_contours{ctrtag}.pkl'.format(ctrtag=ctrtag)

	def make_measurements(self, imgtag='OIII5008_I', isocut=3.e-15*u.Unit('erg / (arcsec2 cm2 s)'), minarea=5, onlycenter=True, centerradius=5.*u.arcsec, plotsuffix='', savecontours=False, plotmsr=False, msrsuffix='', overwrite=False, append=False):
		"""
		make measurements on a map and write to msr_iso.csv.
			if imgtag='OIII5008_I' then measure 'stamp-OIII5008_I.fits'

		Params
		------
		self
		imgtag='OIII5008_I'
		overwrite = False (bool)

		isocut=1.e-15*u.Unit('erg / (arcsec2 cm2 s)'):
			isophote cut
		minarea=0:
			connected contour area (# pix) above the area is counted as part of the isophote measurement
		onlycenter=False:
			whether to consider only the center contours
		centerradius=2.*u.arcsec
		plotsuffix = '':
			plotsuffix label to be attach to the end of the plot or contour file names.
		savecontours=False
		plotmsr=False
		msrsuffix=''
			plotsuffix label in the end of the measurement csv file: msr_iso_{msrsuffix}.csv.
		overwrite=False
		append=False

		Return
		------
		status (bool)

		Write Output
		------------
		e.g., msr_iso.csv
		"""
		fn = self.get_fp_msr(msrsuffix=msrsuffix)

		condi = {'imgtag': imgtag, 'isocut': isocut, 'minarea': minarea, 'onlycenter': onlycenter, 'centerradius': centerradius}

		if append or overwrite or (not tabtools.fn_has_row(fn, condi)):
			print("[isomeasurer] making measurement")

			img = self.get_stamp_img(imgtag=imgtag, wunit=True)
			xc, yc = self._get_xc_yc(img)

			# calc
			if np.all(~np.isnan(img)):
				contours = self._get_contours_from_img(img=img, isocut=isocut, xc=xc, yc=yc, minarea=minarea, onlycenter=onlycenter, centerradius=centerradius)
				tab_msr = self._get_tab_measurements_from_contours(contours=contours, xc=xc, yc=yc)
			else:
				contours = []
				tab_msr = self._get_tab_measurements_nan()

			tab_params = self._get_tab_params(imgtag=imgtag, isocut=isocut, minarea=minarea, onlycenter=onlycenter, centerradius=centerradius)
			tabout = at.hstack([tab_params, tab_msr])

			# output
			tabtools.write_row(fn=fn, row=tabout, condi=condi, overwrite=overwrite, append=append)

			# optional output
			if savecontours:
				fn_contours = self.get_fp_contours(imgtag=imgtag, onlycenter=onlycenter, suffix=plotsuffix)
				write_pickle(contours, fn_contours, overwrite=overwrite)

			if plotmsr:
				fn_plot = self.get_fp_msrplot(imgtag=imgtag, suffix=plotsuffix)
				plottools.make_plot_img_w_contours(fn_plot=fn_plot, img=img, contours=contours)

		else:
			print("[isomeasurer] skip making measurement as files exist")

		return os.path.isfile(fn)

	def make_visualpanel(self, fn=None, compo_bands ='gri', imgtag='OIII5008_I', onlycenter=True, minarea=5, centerradius=5.*u.arcsec, tocolorbar=True, totitle=True, fontsize=12, overwrite=False):
		"""
		make panel figure to visualize the composit and the iso measurements
		saved to e.g., 'msr_iso-OIII5008_I_panel.pdf'

		Params
		------
		fn = None: default: msr_iso_{imgtag}_panel.pdf
		compo_bands ='gri', imgtag='OIII5008_I', overwrite=False

		Return
		------
		status
		"""
		if fn is None:
			fn = self.get_fp_msrplot(imgtag=imgtag, suffix='_panel')
		else:
			fn = self.dir_obj+fn

		if not os.path.isfile(fn) or overwrite:
			print("[isomeasurer] making visual panel")

			# get files ready
			self.make_colorimg(bands=compo_bands, img_type='stamp', overwrite=False)

			# access data
			img_compo = simg.imread(self.dir_obj+'color_stamp-{}.png'.format(compo_bands))
			img_map = self.get_stamp_img(imgtag=imgtag, wunit=False)

			suffix = '_3e-15'
			isocut = 3.e-15*u.Unit('erg / (arcsec2 cm2 s)')
			fn_contours3 = self.get_fp_contours(imgtag=imgtag, onlycenter=onlycenter, suffix=suffix)
			if not os.path.isfile(fn_contours3):
				print("[isomeasurer] re-doing measurements to make contours required for visual panel plots")
				self.make_measurements(imgtag=imgtag, isocut=isocut, plotsuffix=suffix, minarea=minarea, onlycenter=onlycenter, centerradius=centerradius, overwrite=True, savecontours=True, plotmsr=False),

			contours3 = read_pickle(fn_contours3)

			suffix = '_1e-15'
			isocut = 1.e-15*u.Unit('erg / (arcsec2 cm2 s)')
			fn_contours1 = self.get_fp_contours(imgtag=imgtag, onlycenter=onlycenter, suffix=suffix)
			if not os.path.isfile(fn_contours1):
				print("[isomeasurer] re-doing measurements to make contours required for visual panel plots")
				self.make_measurements(imgtag=imgtag, isocut=isocut, plotsuffix=suffix, minarea=minarea, onlycenter=onlycenter, centerradius=centerradius, overwrite=True, savecontours=True, plotmsr=False),

			contours1 = read_pickle(fn_contours1)

			z = self.z
			pixsize = self.pixsize.to_value(u.arcsec)
			legend_suffix = ' at 3'
			name = self.obj.name[4:]

			title_compo = '${}~{}~{}~$'.format(compo_bands[0], compo_bands[1], compo_bands[2])+'$\mathrm{Composite}$'
			title_map = '$\mathrm{[OIII]\lambda 5007~Intensity}$'
			label_cbar = '$I~[10^{-15}~\mathrm{erg~s^{-1}~cm^{-2}~arcsec^{-2}}]$'

			plottools.make_iso_visual_panel(fn, img_compo, img_map, contours1, contours3, z, pixsize, legend_suffix, name, title_compo, title_map, label_cbar, tocolorbar=tocolorbar, totitle=totitle, fontsize=fontsize)

		else:
			print("[isomeasurer] skip making visual panel as files exist")

		return os.path.isfile(fn)

	def _get_tab_params(self, imgtag, isocut, minarea, onlycenter, centerradius):
		"""
		return a one row table of the measurement params
		"""
		tab = at.Table([[imgtag], [str(isocut)], [minarea], [onlycenter], [str(centerradius)], ], names=['imgtag', 'isocut', 'minarea', 'onlycenter', 'centerradius', ])
		return tab

	def _get_tab_measurements_from_contours(self, contours, xc, yc):
		"""
		calculate iso measurements from contours, return a table like:
		"""

		tab = polytools.ShapeParamsTab_from_contours(contours, xc, yc)

		# unit conversion
		area_ars = tab['area_pix'][0]*(self.pixsize/u.arcsec)**2
		dmax_ars = self._pix_to_theta(tab['dmax_pix'][0], wunit=False)
		rmax_ars = self._pix_to_theta(tab['rmax_pix'][0], wunit=False)
		dper_ars = self._pix_to_theta(tab['dper_pix'][0], wunit=False)

		kpc_per_arcsec = np.array(self._get_kpc_proper_per_arcsec())

		area_kpc = area_ars * kpc_per_arcsec**2
		dmax_kpc = dmax_ars * kpc_per_arcsec
		rmax_kpc = rmax_ars * kpc_per_arcsec
		dper_kpc = dper_ars * kpc_per_arcsec

		tab_converted = at.Table(names=['area_kpc', 'dmax_kpc', 'rmax_kpc', 'dper_kpc', 'area_ars', 'dmax_ars', 'rmax_ars', 'dper_ars', ])
		tab_converted.add_row([area_kpc, dmax_kpc, rmax_kpc, dper_kpc, area_ars, dmax_ars, rmax_ars, dper_ars, ])

		tabout = at.hstack([tab_converted, tab])

		return tabout

	def _get_tab_measurements_nan(self):
		"""
		return a tab measurement just like _get_tab_measurements_from_contours() but with entries all nan.
		"""
		names = ['area_kpc', 'dmax_kpc', 'rmax_kpc', 'dper_kpc', 'area_ars', 'dmax_ars', 'rmax_ars', 'dper_ars', 'area_pix', 'dmax_pix', 'rmax_pix', 'dper_pix', 'theta_dmax', 'theta_rmax', 'theta_dper', 'aspectr']

		tabout = at.Table(names=names)
		tabout.add_row([np.nan for i in range(len(names))])

		return tabout

	def _get_contours_from_img(self, img, isocut, xc, yc, minarea=0., onlycenter=False, centerradius=2.*u.arcsec):
		"""
		make contour at isocut of image as python pickle file (fn_contours)
		always overwrite

		Params
		------
		self
		img (array)
		isocut (float or quantity):
			has to be of the same type of unit as image
		minarea (float):
			minimum area (pix) to be considered as contour patch
		onlycenter (bool):
			whether to take only center patches as patches (they all have to pass minarea test as well)
		centerradius (angular quantity):
			if onlycenter = True, then it sets the radius of the center area. only patches overlapping with that area will be considered.

		"""

		# prep
		try:
			img.unit
		except:
			img_nparr = img/isocut
		else:
			img_nparr = np.array((img/isocut).to(u.dimensionless_unscaled))

		# find contours -- satisfy minarea
		contours = polytools.find_largecontours(img=img_nparr, threshold=1., minarea=minarea)

		if onlycenter:  # select only those at the center
			centerradius_pix = self._theta_to_pix(centerradius)
			contours = polytools.select_center_contours(contours, xc, yc, radius=centerradius_pix)

		return contours

def read_pickle(fn):
	with open(fn, 'rb') as handle:
		result = pickle.load(handle)
	return result

def write_pickle(result, fn, overwrite=False):

	if not os.path.isfile(fn) or overwrite:
		with open(fn, 'wb') as handle:
			pickle.dump(result, handle)


import glob
import os

def nuscenes_parser(root_path: str):
    """
    Returns a list of absolute image paths contained in the NuScenes dataset.

    Ref: https://www.nuscenes.org/nuscenes

    Dataset structure (top level corrspond to 'root directory'):
    nuimages/
        maps/
        samples/
            CAM_BACK/
                *.jpg
            CAM_BACK_LEFT/
                *.jpg
            CAM_BACK_RIGHT/
                *.jpg
            CAM_FRONT/
                *.jpg
            CAM_FRONT_LEFT/
                *.jpg
            CAM_FRON_RIGHT/
                *.jpg
            LIDAR_TOP/
            ...
            RADAR_FRONT/
            ...
        sweeps/
            CAM_BACK/
                *.jpg
            CAM_BACK_LEFT/
                *.jpg
            CAM_BACK_RIGHT/
                *.jpg
            CAM_FRONT/
                *.jpg
            CAM_FRONT_LEFT/
                *.jpg
            CAM_FRON_RIGHT/
                *.jpg
            LIDAR_TOP/
            ...
            RADAR_FRONT/
            ...
        v1.0-mini/
        v1.0-test/
        v1.0-trainval/

    Args:
        root_path (str): Root path of dataset.

    Returns:
        List of strings corresponding to absolute image paths.
    """
    dataset_img_paths = []

    # Parse 'samples'
    if os.path.isdir(os.path.join(root_path, 'samples')):
        img_paths = glob.glob(f"{root_path}/samples/CAM_*/*.jpg")
        dataset_img_paths += img_paths

    # Parse 'sweeps'
    if os.path.isdir(os.path.join(root_path, 'sweeps')):
        img_paths = glob.glob(f"{root_path}/sweeps/CAM_*/*.jpg")
        dataset_img_paths += img_paths

    return dataset_img_paths


from fastapi import FastAPI
from typing import Optional
from pydantic import BaseModel
import redis

class Word(BaseModel):
    text: str
    frequency: Optional[int] = 0

app = FastAPI()
r = redis.Redis(host='redis', port=6379)

r.set("lol", 42)

@app.get("/word/{word}")
def get_word(word: str):
    if (r.exists(word)):
        return Word(text = word, frequency = r.get(word)).json()
    else:
        return Word(text = word).json()

@app.post("/word/")
def post_word(word: Word):
    if (r.exists(word.text)):
        r.incr(word.text)
    else:
        r.set(word.text, 1)


from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework import status

from f5.models.Permission.Permission import Permission
from f5.models.F5.Usecases.VirtualServers import VirtualServersWorkflow

from f5.serializers.F5.Usecases.VirtualServer import F5WorkflowVirtualServerSerializer as WorkflowVirtualServerSerializer

from f5.controllers.CustomController import CustomController

from f5.helpers.decorators.ReplicateVSCreation import ReplicateVirtualServerCreation
from f5.helpers.Lock import Lock
from f5.helpers.Log import Log

class F5WorkflowVirtualServersController(CustomController):
    @staticmethod
    @ReplicateVirtualServerCreation
    def post(request: Request, assetId: int, partitionName: str) -> Response:
        response = None
        replicaUuid = request.GET.get("__replicaUuid", "") # an uuid in order to correlate actions on logs.

        user = CustomController.loggedUser(request)

        try:
            if Permission.hasUserPermission(groups=user["groups"], action="workflow_virtualServers_post", assetId=assetId, partition=partitionName) or user["authDisabled"]:
                Log.actionLog("Virtual server workflow", user)
                Log.actionLog("User data: "+str(request.data), user)

                serializer = WorkflowVirtualServerSerializer(data=request.data)
                if serializer.is_valid():
                    data = serializer.validated_data["data"]

                    lock = Lock(VirtualServersWorkflow.relatedF5Objects(), locals(), "any")
                    if lock.isUnlocked():
                        lock.lock()

                        VirtualServersWorkflow(assetId, partitionName, data, user, replicaUuid).add()

                        httpStatus = status.HTTP_201_CREATED
                        lock.release()
                    else:
                        httpStatus = status.HTTP_423_LOCKED
                else:
                    httpStatus = status.HTTP_400_BAD_REQUEST
                    response = {
                        "F5": {
                            "error": str(serializer.errors)
                        }
                    }

                    Log.actionLog("User data incorrect: "+str(response), user)
            else:
                httpStatus = status.HTTP_403_FORBIDDEN
        except Exception as e:
            Lock(VirtualServersWorkflow.relatedF5Objects(), locals(), "any").release()

            data, httpStatus, headers = CustomController.exceptionHandler(e)
            return Response(data, status=httpStatus, headers=headers)

        return Response(response, status=httpStatus, headers={
            "Cache-Control": "no-cache"
        })


# PROGRAM Stacks as Linked Lists:

####### DECLARE LINKED LIST #######

class ListNode:
    def __init__(self, value, pointer):
        self.value = value
        self.pointer = pointer
# END ListNode.

nodeD = ListNode(31, None)
nodeC = ListNode(37, nodeD)
nodeB = ListNode(62, nodeC)
nodeA = ListNode(23, nodeB)

HeadNode = nodeA

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

########## SIMPLE PRINT ##########
def PrintNodes():
    print("[", nodeA.value, "] -> [",nodeB.value, "] -> [",nodeC.value, "] -> [",nodeD.value, "] -> NULL")
# END PrintNodes.

PrintNodes()

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

def CreateEmptyList():
    global HeadNode
    HeadNode = None
# END CreateEmptyList.

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

def DeleteAList():
    global HeadNode
    HeadNode = None
# END DeleteAList.

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

def ListIsEmpty():
    global HeadNode
    return HeadNode == None
# END ListIsEmpty.

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

########## PRINT WITH LOOP ##########

def PrintNodesWithLoop():
    global HeadNode
    Current = HeadNode
    if (Current != None):
    # THEN
        while (Current.pointer != None):
        # DO
            print(Current.value)
            Current = Current.pointer
        # ENDWHILE;
        print(Current.value)
    else:
        print("Empty list")
    # ENDIF;
# END PrintNodesWithLoop.

PrintNodesWithLoop()

########## PRINT AND COUNT ##########

def PrintNodesWithLoopAndCount():
    global HeadNode
    Current = HeadNode
    CountNodes = 0
    if (Current != None):
    # THEN
        while (Current.pointer != None):
            # DO
            print(Current.value)
            Current = Current.pointer
            CountNodes = CountNodes + 1
        # ENDWHILE;

        # Print out and count for last node
        CountNodes = CountNodes + 1
        print(Current.value)

        print("Count:", CountNodes)
    else:
        print("Empty list")
    # ENDIF;
# END PrintNodesWithLoopAndCount.

PrintNodesWithLoopAndCount()

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

def FindANode(N):
    global HeadNode
    Current = HeadNode
    while ((Current.pointer != None) and (Current.value != N)):
    # DO
        Current = Current.pointer
    # ENDWHILE;

    # Print out and count for last node
    if (Current.pointer == None):
    # THEN
        print(N, "is not in the list")
    else:
        print("Found value:", Current.value)
    # ENDIF;
# END FindANode.
FindANode(33337)

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

def InsertANode(Pos, N):
    global HeadNode
    Current = HeadNode

    nodeX = ListNode(N, None)

    PositionCounter = 1
    CountNodes = 0
    if Pos == 0:
    # THEN
        HeadNode = nodeX
        nodeX.pointer = Current
    else:
        while (Pos > PositionCounter):
            # DO
            Current = Current.pointer
            PositionCounter = PositionCounter + 1
        # ENDWHILE;

        nodeX.pointer = Current.pointer
        Current.pointer = nodeX
    # ENDIF;
# END InsertANode.
InsertANode(2, 12345)
PrintNodesWithLoopAndCount()

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

def DeleteANode(N):
    global HeadNode
    Previous = None
    Current = HeadNode
    if Current.value == N:
    # THEN
        HeadNode = Current.pointer
    else:
        while ((Current.pointer != None) and (Current.value != N)):
        # DO
            Previous = Current
            Current = Current.pointer
        # ENDWHILE;
        Previous.pointer = Current.pointer
    # ENDIF;
# END DeleteANode.

DeleteANode(12345)
PrintNodesWithLoopAndCount()

#################################
########## S T A C K S ##########
#################################

PrintNodesWithLoopAndCount()
StackTop = HeadNode;
PrintNodesWithLoopAndCount()

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

def CreateEmptyStack():
    global StackTop
    StackTop = None
# END CreateEmptyStack.

CreateEmptyStack()

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

def StackIsEmpty():
    global StackTop
    return StackTop == None
# END StackIsEmpty.

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

def PrintStack():
    global StackTop
    SPrint = StackTop
    print("Printing Stack...")
    while(SPrint != None):
    # DO
        print(SPrint.value)
        SPrint = SPrint.pointer
    # ENDWHILE;
    print("* End of Stack *")
# END QueueIsEmpty.

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

def StackPush(N):
    global StackTop
    nodeX = ListNode(N, None)
    if StackIsEmpty():
    # THEN
        StackTop = nodeX
    else:
        nodeX.pointer = StackTop
        StackTop = nodeX
    # ENDIF;
# END StackPush.

StackPush(35)
StackPush(34)
StackPush(33)
StackPush(32)
PrintStack()

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

def StackPop():
    global StackTop
    if StackIsEmpty():
    # THEN
        print("Stack is empty")
        N = 0
    else:
        N = StackTop.value
        StackTop = StackTop.pointer
    # ENDIF;
    return N
# END StackPop.

PopValue = StackPop()
print("Pop value:", PopValue)
PrintStack()
##################################

def ShowStackTop():
    global StackTop
    if StackIsEmpty():
    # THEN
        print("Stack is empty")
        N = 0
    else:
        N = StackTop.value
        print("Top value:", StackTop.value)
    # ENDIF;
    return N
# END ShowStackTop.

TopValue = ShowStackTop()
PrintStack()
##################################


from amazon.Data import Data
from amazon.Payload import Payload
import re
import copy
import html
from handle.datamanager import Datamanager
from amazon.Platform_dispatcher import PlatformDispatcher
from amazon.Dictionary import Dictionary
from amazon.globals import domain

class Serie(Data):
    @classmethod
    def insertar_series(cls, amazon, data, contenido):
        payload_serie, episodes = cls.get_payloads_series(amazon, data, contenido)
        serie_plataformas, packages_platforms = PlatformDispatcher.get_serie_platforms(episodes)
        lista_payloads = cls.get_complete_series(payload_serie, episodes, serie_plataformas, packages_platforms)
        print(f'Agregada/s {len(lista_payloads)} series/s')
        amazon.lista_contenidos.append(lista_payloads)
        print(f'Agregado/s {len(episodes)} capitulos/s')
        amazon.lista_episodios.append(episodes)

    @staticmethod
    def get_season_number(dictionary): return dictionary['seasonNumber']

    @staticmethod
    def get_season_deeplink(dictionary, id_season):
        serie = list(dictionary['seasons'].keys())[0]
        for season in dictionary['seasons'][serie]:
            if season['seasonId'] == id_season:
                return f"{domain()}{season['seasonLink']}"

    @staticmethod
    def get_parent_title(dictionary):
        parent_title = dictionary['parentTitle']
        titulo = re.sub(r"-?\sSeason\s+$", '', parent_title).strip()
        return html.unescape(titulo)

    @staticmethod
    def get_episode_season(deeplink_temporada):
        try:
            season = re.search(r"\d{1,3}$", deeplink_temporada).group(0)
            return int(season)
        except AttributeError:
            link_nuevo = re.sub(r"\?.*$", '', deeplink_temporada)
            season = re.search(r"\d{1,3}$", link_nuevo).group(0)
            return int(season)

    @classmethod
    def get_payload_serie(cls, dictionary, url, created_at):
        id_serie = cls.get_id(url)
        title_id = dictionary['pageTitleId']
        detail = cls.get_header_detail(dictionary)[title_id]
        year = cls.get_year(detail)
        synopsis = cls.get_synopsis(detail)
        image = cls.get_images(detail)
        genre = cls.get_genre(detail)
        rating = cls.get_rating(detail)
        is_original = cls.is_original(detail)
        is_branded = cls.is_branded(detail)
        director = cls.get_director(detail)
        cast = cls.get_cast(detail)
        seasons = cls.get_data_seasons(dictionary)
        title = cls.get_parent_title(detail)
        payload = Payload(title=title, id_=id_serie, year=year, synopsis=synopsis, seasons=seasons,
                          clean_title=title, image=image, genres=genre, rating=rating, directors=director,
                          cast=cast, deeplink_web=url, is_original=is_original, is_branded=is_branded,
                          createdAt=created_at)
        return payload.payload_serie()

    @classmethod
    def get_data_seasons(cls, dictionary):
        data_seasons = []
        seasons = dictionary['detail']['detail']
        for season in seasons:
            id_season = season
            temporada = seasons[season]
            synopsis = cls.get_synopsis(temporada)
            title = cls.get_title(temporada)
            number = Serie.get_season_number(temporada)
            deeplink = cls.get_season_deeplink(dictionary, id_season)
            image = cls.get_images(temporada)
            director = cls.get_director(temporada)
            cast = cls.get_cast(temporada)
            is_original = cls.is_original(temporada)
            year = cls.get_year(temporada)
            payload = Payload(id_=id_season, synopsis=synopsis, title=title, deeplink_web=deeplink, number=number,
                              image=image, directors=director, cast=cast, is_original=is_original, year=year)
            data_seasons.append(payload.payload_season())
        return data_seasons

    @classmethod
    def get_episode_number(cls, dictionary): return dictionary['episodeNumber']

    @classmethod
    def get_payload_episode(cls, payload, createdAt, amazon):
        lista_capitulos = []
        soups_temporadas= {}
        for temporada in payload['Seasons']:
            deeplink_temporada = temporada['Deeplink']
            soup = Datamanager._getSoupSelenium(amazon, deeplink_temporada)
            soups_temporadas[temporada['Id']] = soup
            episodes = cls.get_data(soup, tipo_contenido='episode')
            parent_id = payload['Id']
            parent_title = payload['Title']
            capitulos = episodes['detail']['detail']
            cant_ep = cls.payload_episode(capitulos, parent_id, parent_title, deeplink_temporada, lista_capitulos, createdAt)
            temporada['Episodes'] = cant_ep
        return lista_capitulos, soups_temporadas

    @classmethod
    def payload_episode(cls, capitulos, parent_id, parent_title, deeplink_temporada, lista_capitulos, createdAt):
        cant_ep = 0
        for capitulo in capitulos:
            episode = capitulos[capitulo]
            if episode['titleType'] == 'episode':
                id_contenido = capitulo
                year = cls.get_year(episode)
                synopsis = cls.get_synopsis(episode)
                genre = cls.get_genre(episode)
                rating = cls.get_rating(episode)
                is_original = cls.is_original(episode)
                episode_number = cls.get_episode_number(episode)
                season = cls.get_episode_season(deeplink_temporada)
                # parent_title = cls.get_parent_title(episode)
                title = cls.get_title(episode)
                duration = cls.get_duration(episode)
                director = cls.get_director(episode)
                cast = cls.get_cast(episode)
                payload = Payload(parent_title=parent_title, parent_id=parent_id, id_=id_contenido, title=title,
                                  episode=episode_number, season=season, year=year, duration=duration, rating=rating,
                                  deeplink_web=deeplink_temporada, synopsis=synopsis, is_original=is_original,
                                  directors=director, cast=cast,
                                  genres=genre, createdAt= createdAt)
                if episode_number > 0:
                    cant_ep += 1
                    lista_capitulos.append(Payload.payload_episode(payload))
        return cant_ep

    @classmethod
    def completar_season(cls, serie_copy, episodios):
        for season in serie_copy['Seasons']:
            season['Episodes'] = cls.cant_ep_season(episodios, season['Number'], serie_copy['PlatformCode'])

    @staticmethod
    def cant_ep_season(episodes, season_num, platform_code):
        cant = 0
        for episode in episodes:
            if episode['PlatformCode'] == platform_code and season_num == episode['Season']:
                cant += 1
        return cant

    @classmethod
    def get_complete_series(cls,serie, episodios, serie_plataformas, packages_platforms):
        series = []
        for plataforma in serie_plataformas:
            serie_copy = copy.deepcopy(serie)
            serie_copy['PlatformCode'] = cls.get_platform_clean(plataforma)
            serie_copy['Packages'] = packages_platforms[plataforma]
            serie_copy['IsBranded'] = cls.is_prime(serie_copy['PlatformCode'])
            serie_copy['Provider'] = cls.get_provider(serie_copy['PlatformCode'])
            cls.completar_season(serie_copy, episodios)
            serie_copy['Seasons'] = cls.clean_seasons(serie_copy['Seasons'])
            series.append(serie_copy)
        return series

    @staticmethod
    def clean_seasons(seasons):
        correct_season = []
        for season in seasons:
            if  season['Episodes'] > 0:
                correct_season.append(season)

        return correct_season if correct_season else None

    @classmethod
    def get_payloads_series(cls, amazon, data, contenido):
        payload_serie = cls.get_payload_serie(data, contenido, amazon._created_at)
        payload_episodes, soups_seasons = cls.get_payload_episode(payload_serie, amazon._created_at, amazon)
        platforms_and_packages = cls.get_platforms_and_packages(soups_seasons, amazon.dispatcher)
        episodes = cls.get_episodes_with_packages(payload_episodes, platforms_and_packages)
        return payload_serie, episodes

    @classmethod
    def get_platforms_and_packages(cls, soups, dispatcher):
        platforms = []
        for soup in soups:
            dict_serie = Dictionary.get_dictionary_serie(soups[soup], 'state')
            dict_common = Dictionary.get_dictionary(soups[soup], 'state')
            platformas = dispatcher.dispatch_episodes(dict_serie, dict_common)
            if platformas:
                platforms += platformas
        return platforms

    @classmethod
    def get_episodes_with_packages(cls, payloads, platforms):
        list_ = []
        for payload in payloads:
            platforms_code = cls.get_platforms_epi(payload['Id'], platforms)
            for platform in platforms_code:
                pay = copy.deepcopy(payload)
                pay['PlatformCode'] = cls.get_platform_clean(platform)
                pay['Packages'] = platforms_code[platform]
                pay['Provider'] = cls.get_provider(pay['PlatformCode'])
                list_.append(pay)
        return list_

    @staticmethod
    def get_platforms_epi(id_, platforms):
        for element in platforms:
            key = element.keys()
            if id_ in key:
                return element[id_]


print('Ex_1')
"""
Задание 1
Реализуйте класс «Автомобиль».
Необходимо хранить в полях класса:
название модели, год выпуска, производителя, объем двигателя, цвет машины, цену.
Реализуйте методы класса для ввода данных, вывода данных,
реализуйте доступ к отдельным полям через методы класса (property)
"""

class Auto:
    def __init__(self, make, model, year, engine_volume, color, price):
        self.__make = make.upper()
        self.__model = model.upper()
        self.__year = year
        self.__engine_volume = engine_volume
        self.__color = color.upper()
        self.__price = price

    def __str__(self):
        return f"\nCAR FOR SALE INFORMATION:\n" \
              f"MAKE: {self.__make} \n" \
              f"MODEL: {self.__model}\n" \
              f"YEAR: {self.__year}\n" \
              f"ENGINE: {self.__engine_volume}\n" \
              f"COLOR: {self.__color}\n" \
              f"PRICE: {self.__price}\n"

    @classmethod
    def input_auto(cls):
        dict = {}
        fields = ['make', 'model', 'year', 'engine', 'color', 'price']
        types = [str, str, int, float, str, int]

        for field, converter in zip(fields, types):
            dict[field] = converter(input(f'{field.upper()}: '))

        return cls(*dict.values())

    def print(self):
        print(self)

    @property
    def make(self):
        return self.__make

    @property
    def color(self):
        return self.__color.upper()

    @color.setter
    def color(self, value):
        self.__color = value.upper()

    @property
    def year(self):
        return self.__year

    @year.setter
    def year(self, value):
        self.__year = value

auto1 = Auto('bmw', 'x4', 2020, 3.0, 'white', 65_000)
auto2 = Auto.input_auto()

print('Ex_2')
"""
Реализуйте класс «Книга». Необходимо хранить в полях класса:
название книги, год выпуска, издателя, жанр, автора, цену.
Реализуйте методы класса для ввода данных, вывода данных,
реализуйте доступ к отдельным полям через методы класса.
Реализуйте хранение атрибута каталожный номер (как порядковый номер созданного объекта)
"""

print('Ex_3')
"""
Реализуйте класс «Стадион». Необходимо хранить в полях класса:
название стадиона, дату открытия, страну, город, вместимость.
Реализуйте методы класса для ввода данных, вывода данных,
реализуйте доступ к отдельным полям через методы класса"""


import numpy as np
import math

def randomize_by_weight_warping(predefine_params, from_idx, to_idx, sampling_step=0.025):

	# calculate a weight
	def calculate_weight(len_param):
		w = np.ones(len_param)
		w[:math.ceil(len_param/2)] = 1/math.ceil(len_param/2)
		w[math.ceil(len_param/2):] = 0
		return w

	rand_param = []

	# randomize
	for i in range(predefine_params.shape[1]):

		from_param = predefine_params[from_idx][i]
		to_param = predefine_params[to_idx][i]

		# if the position of the parameter change
		if to_param - from_param != 0:
			# if value of 'from' less than 'to', start at 'from'
			if from_param < to_param:
				# calculate probability
				prob = calculate_weight(len(np.arange(from_param, to_param, step=sampling_step)))

				rand_param.append(np.random.choice(np.arange(from_param, to_param, step=sampling_step), p=prob))
			# else, start at 'to'
			else:
				# calculate probability
				prob = calculate_weight(len(np.arange(to_param, from_param, step=sampling_step)))

				rand_param.append(np.random.choice(np.arange(to_param, from_param, step=sampling_step), p=prob))

		else:
			# If the parameter at the same position
			rand_param.append(from_param)

	return rand_param

def randomize_by_percent_change(predefine_params, from_idx, to_idx):

	percent_change = np.random.uniform(0, high=1, size=1)[0]
	return [((predefine_params[to_idx][i] - predefine_params[from_idx][i])*percent_change) + predefine_params[from_idx][i] for i in  range(predefine_params.shape[1])]

def randomize_params(predefine_params, param_high, param_low, sampling_step=0.025):

	'''
	Same as randomize_based_on_predefine_params() but the formula to calculate TRX and TRY are from
	JD.speaker
	'''
	rand_param = []
	# find min and max along row
	min_param = np.min(predefine_params, axis=0)
	max_param = np.max(predefine_params, axis=0)

	for j, _ in enumerate(param_high):
		# if the parameter is WC, automatically set to 0.0
		if j == 8:
			rand_param.append(0.0)
		# auto calculate TRX from TCY
		elif j == 15:
			rand_param.append(rand_param[10]*0.9380 - 5.1100)
		# calculate TRY from TCX
		elif j == 16:
			rand_param.append(rand_param[9]*0.8310 -3.0300)
		else:
			rand_param.append(np.random.choice(np.arange(min_param[j], max_param[j], step=sampling_step)))

	return rand_param


from random import randint, random
import numpy as np
from geometry import rotate, area as get_area
from math import pi
import pickle

PATH = 'data/room.pickle'

class Generator:
    '''
    Generator of rooms, can have either: 4, 6 or 8 corners

    Methods:
        - generate_plans
        - load_plans
    '''
    def __init__(self, dimension, MARGIN=160):
        # store the postion of the dps, split in two list, one for each side (SW - NE)
        self.dps = [[],[]]
        self.dim = dimension
        self.MARGIN = MARGIN

    def create_seeds(self, coef_marge=1.5):
        marge = self.MARGIN * coef_marge
        seed1 = [randint(marge, self.dim[0]//3 - marge),randint(marge, self.dim[1]//3 - marge)]
        seed2 = [randint(self.dim[0]*2//3, self.dim[0] - marge), randint(self.dim[1]*2//3, self.dim[0] - marge)]
        self.dps[0].append(seed1)
        self.dps[1].append(seed2)

    def generate4(self):
        dp = [self.dps[0][0][0], self.dps[1][0][1]]
        self.dps[0].append(dp)
        dp = [self.dps[1][0][0], self.dps[0][0][1]]
        self.dps[1].append(dp)
        return self.return_result()

    def create_way(self,dp1, dp2, direction, limit=False):
        '''
        Create 3 datapoints between dp1 and dp2

        Arguments:
            dp1 (x,y): datapoint in NW
            dp2 (x,y): datapoint in SE
            direction (bool): if True way go in SW, if False in NE
            limit (x,y):
        '''
        rand_bool = bool(randint(0,1))
        if direction: # true -> SW
            # create the two random datapoints
            if rand_bool:
                # check for limit
                if limit:
                    rdp1_rlimit = randint(limit[1] + self.MARGIN, self.dim[1]-2*self.MARGIN)
                    rdp2_rlimit = randint(dp1[0] + self.MARGIN, limit[0] - self.MARGIN)
                else:
                    # pick random coordinate in limit
                    rdp1_rlimit = randint(dp1[1] + self.MARGIN, self.dim[1]-2*self.MARGIN)
                    rdp2_rlimit = randint(dp1[0] + self.MARGIN, dp2[0] - self.MARGIN)
            else:
                if limit:
                    rdp1_rlimit = randint(limit[1] + self.MARGIN, dp2[1]-self.MARGIN)
                    rdp2_rlimit = randint(2*self.MARGIN, limit[0] - self.MARGIN)
                else:
                    # pick random coordinate in limit
                    rdp1_rlimit = randint(dp1[1] + self.MARGIN, dp2[1]-self.MARGIN)
                    rdp2_rlimit = randint(2*self.MARGIN, dp2[0] - self.MARGIN)

            rdp1 = [dp1[0], rdp1_rlimit]
            rdp2 = [rdp2_rlimit ,dp2[1]]
        else: # false -> NE
            # create the two random datapoints
            if rand_bool:
                if limit:
                    rdp1_rlimit = randint(2*self.MARGIN, limit[1] - self.MARGIN)
                    rdp2_rlimit = randint(limit[0] + self.MARGIN, dp2[0] - self.MARGIN)
                else:
                    # pick random coordinate in limit
                    rdp1_rlimit = randint(2*self.MARGIN, dp2[1] - self.MARGIN)
                    rdp2_rlimit = randint(dp1[0] + self.MARGIN, dp2[0] - self.MARGIN)

            else:
                if limit:
                    rdp1_rlimit = randint(dp1[1] + self.MARGIN, limit[1] - self.MARGIN)
                    rdp2_rlimit = randint(limit[0] + self.MARGIN, self.dim[0] - 2*self.MARGIN)
                else:
                    # pick random coordinate in limit
                    rdp1_rlimit = randint(dp1[1] + self.MARGIN, dp2[1] - self.MARGIN)
                    rdp2_rlimit = randint(dp1[0] + self.MARGIN, self.dim[0] - 2*self.MARGIN)

            rdp1 = [dp2[0], rdp1_rlimit]
            rdp2 = [rdp2_rlimit ,dp1[1]]

        ldp = [rdp2[0],rdp1[1]]
        return [rdp1, ldp, rdp2]

    def generate6(self):
        way = self.create_way(self.dps[0][0], self.dps[1][0], True)
        self.dps[0].extend(way)
        self.dps[1].append([self.dps[1][0][0], self.dps[0][0][1]])
        return self.return_result()

    def generate8(self):
        # create_way() with limit fail ~10% of the time so try again in this case
        done = False
        while not done:
            try:
                way_SW = self.create_way(self.dps[0][0], self.dps[1][0], True)
                way_NE = self.create_way(self.dps[0][0], self.dps[1][0], False, limit=way_SW[1])
                done = True
            except:
                pass
        self.dps[0].extend(way_SW)
        self.dps[1].extend(way_NE)
        return self.return_result()

    def return_result(self):
        result = self.dps[0]
        result.extend(self.dps[1])
        return result

    @staticmethod
    def rotate(plan):
        rotated_plan = []
        for dp in plan:
            rotated_plan.append(dp[::-1])
        return rotated_plan

    def generate(self, ncorner):

        self.dps = [[],[]]

        if ncorner == 4:
            # create seeds
            self.create_seeds()
            return self.generate4()
        elif ncorner == 6:
            # create seeds
            self.create_seeds()
            return self.generate6()
        elif ncorner == 8:
            # create seeds
            self.create_seeds()
            return self.generate8()

    def load_plans(self):
        '''
        Load previously generated plans
        '''
        with open(PATH,'rb') as file:
            plans = pickle.load(file)

        return plans

    def generate_plans(self, n, nwall):
        '''
        Generate n plans with a given number of corners, each plan is rotated of a random angle
        Store the generated plans in a .pickle
        Return the generated plans
        '''
        plans = []
        for _ in range(n):
            plan = self.generate(nwall)
            angle = 2*pi * random()
            plans.append(rotate(plan, angle))

        with open(PATH,'wb') as file:
            pickle.dump(plans, file)

        return plans

def get_mean_area(plans, scale_factor=200):
    '''Return the mean area of the given plans'''

    plans = np.array(plans)
    plans = plans / scale_factor
    areas = np.zeros((plans.shape[0]))

    for i in range(plans.shape[0]):
        area = get_area(plans[i,0], plans[i,1], plans[i,2])
        areas[i] = area

    return np.mean(areas)

if __name__ == '__main__':

    g = Generator((1600,1600))

    plans = g.generate_plans(200, 4)

    print(f'meters: {get_mean_area(plans):.1f}, pixels: {get_mean_area(plans, 1):.0f}')


# -*- coding: utf-8 -*-
"""
Created on Mon Sep 16 08:30:57 2019

@author: HP27
"""

import statsmodels.api as sm
from statsmodels.formula.api import ols
moore = sm.datasets.get_rdataset("Moore", "carData", cache=True) # load
data = moore.data
data = data.rename(columns={"partner.status" :
                            "partner_status"}) # make name pythonic
moore_lm = ols('conformity ~ C(fcategory, Sum)*C(partner_status, Sum)',
                data=data).fit()
table = sm.stats.anova_lm(moore_lm, typ=2) # Type 2 Anova DataFrame
print(table)


import os
import shutil

from data import students

STUDENTS_DIRPATH = os.path.join(os.path.dirname(__file__), "submissions")
GROUPS_DIRPATH = os.path.join(os.path.dirname(__file__), "group_submissions")

dirs = os.listdir(STUDENTS_DIRPATH)
dirs = [d for d in dirs if d not in [".DS_Store", ".gitignore", "grades.csv"]]
dirs.sort()

print("---------------------")
print(f"PROCESSING {len(dirs)} DIRECTORIES:")

for dirname in dirs:
    parts = dirname.split("(")
    #student_name = parts[0]
    net_id = parts[-1].replace(")", "")
    group_id = [s["group_id"] for s in students if s["net_id"] == net_id][0]

    attachments_dirpath = os.path.join(STUDENTS_DIRPATH, dirname, "Submission attachment(s)")
    attachment_filenames = os.listdir(attachments_dirpath)
    for attachment_filename in attachment_filenames:
        attachment_filepath = os.path.join(attachments_dirpath, attachment_filename)
        print("...", group_id, net_id, attachment_filename)

        file_extension = "pdf"
        if file_extension not in attachment_filename:
                file_extension = attachment_filename.split(".")[-1]

        group_submission_filepath = os.path.join(GROUPS_DIRPATH, f"{group_id}.{file_extension}")
        shutil.copyfile(attachment_filepath, group_submission_filepath)


# ------------------------------------------------------------------------ #
# Title: Assignment 05
# Description: Working with Dictionaries and Files
#              When the program starts, load each "row" of data
#              in "ToDoList.txt" into a python Dictionary.
#              Add each dictionary "row" to a python list "table"
# ChangeLog (Who,When,What):
# RR,1.1.2030,Created started script
# C,11.13.22,Added code to complete assignment 5 for step 3-7
# ------------------------------------------------------------------------ #

# -- Data -- #
# declare variables and constants
objFile = "ToDoList.txt"   # An object that represents a file
#strData = ""  # A row of text data from the file
dicRow = {}    # A row of data separated into elements of a dictionary {Task,Priority}
lstTable = []  # A list that acts as a 'table' of rows
strMenu = """
    Menu of Options
    1) Show current data
    2) Add a new item.
    3) Remove an existing item.
    4) Save Data to File
    5) Exit Program
    """  # A menu of user options
strChoice = "" # A Capture the user option selection
lstRow =[]

# -- Processing -- #
# Step 1 - When the program starts, load any data you have
# in a text file called ToDoList.txt into a python list of dictionaries rows (like Lab 5-2)
# TODO: Add Code Here
objFile =  open("ToDoList.txt", 'r')
for row in objFile:
    lstRow= row.split(',')
    dicRow = {'Task': lstRow[0], 'Priority': lstRow[1].strip() }
    lstTable.append(dicRow)
objFile.close()

# -- Input/Output -- #
# Step 2 - Display a menu of choices to the user
while (True):
    print(strMenu)
    strChoice = str(input("Which option would you like to perform? [1 to 5] - "))
    print()  # adding a new line for looks
    # Step 3 - Show the current items in the table
    if (strChoice.strip() == '1'):
        # TODO: Add Code Here
        print('Task    |    Priority')
        for i in lstTable:
            print(i["Task"] + ' | ' + i['Priority'])

        continue # goes back to the top of while loop
    # Step 4 - Add a new item to the list/Table
    elif (strChoice.strip() == '2'):
        # TODO: Add Code Here
        while (True):
            str_task = input('Task: ')
            str_value = input('Priority: ')
            lstTable.append({"Task": str_task, "Priority": str_value})
            str_choice = input('Exit? ("y/n"): ')
            if str_choice.lower() == 'y':
                break
        continue
    # Step 5 - Remove a new item from the list/Table
    elif (strChoice.strip() == '3'):
        # TODO: Add Code Here
        while (True):
            tracker = 0
            str_task = input('Enter Task to remove: ')
            for row in lstTable:
                if row["Task"].lower() == str_task.lower():
                    print('This row was removed: ', row)
                    lstTable.remove(row)

                    print('new table is: ', lstTable)
                    tracker = + 1
            if tracker >= 1:
                pass
            else:
                print('row not found')
            str_choice = input('Exit? ("y/n"): ')
            if str_choice.lower() == 'y':
                break
        continue
    # Step 6 - Save tasks to the ToDoToDoList.txt file
    elif (strChoice.strip() == '4'):
        # TODO: Add Code Here
        objFile =  open("ToDoList.txt", 'w')
        for row in lstTable:
            objFile.write(row['Task'] + ',' + row['Priority'] + '\n')
        objFile.close()

        print('The following data was saved to the file: ')
        for row in lstTable:
            print(row["Task"],',',row['Priority'])

        continue
    # Step 7 - Exit program
    elif (strChoice.strip() == '5'):
        # TODO: Add Code Here
        break  # and Exit the program, breaks the while loop


#Etalon_Power_main.py

import matplotlib.pyplot as plt

import Etalon_Power_def

param = 0.01
m = 4096

wlcol, PTetacol,PRetacol = Etalon_Power_def.proc1(param,m)

print('')
print('Etalon_Power_main.py')
print('Phase Information is not given.')
print('')

fig = plt.figure(figsize = (10,6), facecolor='lightblue')

ax1 = fig.add_subplot(1, 2, 1)
ax2 = fig.add_subplot(1, 2, 2, sharey=ax1)

ax1.plot(wlcol,PTetacol)
ax1.set_xlabel("Wavelength")
ax1.set_ylabel("Transmission")
ax1.set_ylim(0,1.1)

ax2.plot(wlcol,PRetacol)
ax2.set_xlabel("Wavelength")
ax2.set_ylabel("Reflection")
ax2.set_ylim(0,1.1)

plt.show()


def reverseWords(s):
    """
    easy method that can be used in first round - please try using data structures
    """
    # s = s.strip()
    # s2 = ""
    # lst = s.split()
    # return ' '.join(iter(lst[::-1]))

    i = 0
    N = len(s)
    s2 = ""
    while i < N:
        while i < N and s[i] == " ":
            i += 1
        j = i + 1
        while j < N and s[j] != " ":
            j += 1
        if s2 != "":
            s2 = s[i:j] + " " + s2
        else:
            s2 += s[i:j]
        i = j + 1
    return s2

s = "the sky is blue"
s2 = "  hello world!  "
s3 = "a good   example"
print(reverseWords(s2))


import networkx as nx
import json
import plotly.graph_objects as go
import sys
import numpy as np
import math
try:
    from sklearn.manifold import MDS
    import scipy
except ImportError:
    pass

# load
path = sys.argv[1]
name = path.split('/')[-1]

with open(path, 'r') as f:
    data = json.loads(f.read())

G = nx.readwrite.json_graph.node_link_graph(data, directed=True, multigraph=False)
G = nx.OrderedDiGraph(G) # for safety w/ MDS code below

# Parse command line args
focal_nodes = []
depth = None
trawl = lambda _set, member: _set.union(G.successors(member)).union(G.predecessors(member))

args = iter(sys.argv[2:])
for arg in args:
    if arg == "--depth":
        depth = int(next(args))
    elif arg == "--upstream":
        trawl = lambda _set, member: _set.union(G.predecessors(member))
    elif arg == "--downstream":
        trawl = lambda _set, member: _set.union(G.successors(member))
    else:
        try:
            int(arg)
            focal_nodes.append(arg)
        except ValueError:
            print(f"Argument {arg} not recognized!")
            sys.exit(1)

if not depth:
    depth = 2

# Get subgraph as specified in arguments
subtitle = ""
if len(focal_nodes) > 0:
    # filter graph to neighborhood
    keepers = set(focal_nodes)
    for _ in range(depth):
        for k in keepers.copy():
            keepers = trawl(keepers, k)
    droppers = np.setdiff1d(G.nodes, list(keepers)) # np doesn't like sets...
    G.remove_nodes_from(droppers)

    #subtitle = f"'{G.nodes[focal_nodes[0]]['text']}'"

# Initial layout

try:
    # start with MDS, if scikit available
    adj = nx.convert_matrix.to_scipy_sparse_matrix(G)
    dist = scipy.sparse.csgraph.dijkstra(adj, directed=False)
    nodes = list(G.nodes)
    coords = MDS(dissimilarity='precomputed').fit_transform(dist)
    pos = {nodes[i]: coords[i] for i in range(len(nodes))}
except (NameError, ValueError):
    print("(You may get better graph-drawing results if python lib scikit-learn is available)")
    try:
        pos = nx.drawing.layout.planar_layout(G)
    except nx.exception.NetworkXException:
        #pos = nx.drawing.layout.spectral_layout(G)
        pos = nx.drawing.layout.kamada_kawai_layout(G)

# Layout
#pos = nx.drawing.layout.spring_layout(G, k=150/math.sqrt(len(G.nodes)), iterations=1000, threshold=1e-3, pos=pos)

# Plotting

def arrow(x0, y0, x1, y1):
    vector = np.array((x1-x0, y1-y0))
    midpoint = np.array([(x0 + x1)/2, (y1+y0)/2])
    scale = .05
    orthpoint = midpoint - scale * vector
    antivector = scale * np.array([vector[1], -1 * vector[0]])
    return [orthpoint + antivector, midpoint, orthpoint - antivector,
            orthpoint + antivector, (None, None)]

# Mostly lifted from plotly docs
# edited to make edges separate traces s.t. can vary colors
edges_x = []
edges_y = []
texts = []
edge_colors = []
for edge in G.edges():
    x0, y0 = pos[edge[0]]
    x1, y1 = pos[edge[1]]
    x = [x0, (x0+x1)/2, x1]
    y = [y0, (y0+y1)/2, y1]

    edges_x.append(x)
    edges_y.append(y)
    # try throwing text in for hovering?
    text = G.edges[edge[0], edge[1]]['text']
    texts.append(["", text, ""])
    # color from valence
    c = int(G.edges[edge[0], edge[1]]['valence'])
    edge_colors.append(c)

arrows_x = []
arrows_y = []
arrow_colors = []
for edge in G.edges():
    ar = arrow(*pos[edge[0]], *pos[edge[1]])
    x,y = zip(*ar)
    arrows_x.append(x)
    arrows_y.append(y)
    # color
    c = int(G.edges[edge[0], edge[1]]['valence'])
    arrow_colors.append(c)

# translate to colors
edge_colors = np.where(np.equal(edge_colors, -1), 'red', np.where(np.equal(edge_colors, 1), 'green', 'gray'))
arrow_colors = np.where(np.equal(arrow_colors, -1), 'red', np.where(np.equal(arrow_colors, 1), 'green', 'gray'))

edge_traces = []
for x,y,t,c in zip(edges_x, edges_y, texts, edge_colors):
    edge_traces.append(go.Scatter(
        x=x, y=y,
        line=dict(width=0.5,
                  color=c
        ),
        hovertemplate='%{text}<extra></extra>', text=t,
        mode='lines'))

arrow_traces = []
for x,y,c in zip(arrows_x, arrows_y, arrow_colors):
    arrow_traces.append(go.Scatter(
        x=x, y=y,
        line=dict(width=0.5, color=c),
        mode='lines',
        hoverinfo='skip'))

node_x = []
node_y = []
node_text = []
node_color = []
for node in G.nodes():
    x, y = pos[node]
    node_x.append(x)
    node_y.append(y)
    node_text.append(G.nodes[node]['text'])
    node_color.append(int(G.nodes[node]['valence']))

node_color = np.where(np.equal(node_color, -1), 'red', np.where(np.equal(node_color, 1), 'green', 'gray'))

node_trace = go.Scatter(
    x=node_x, y=node_y,
    mode='markers+text',
    #hovertemplate='%{text}<extra></extra>',
    hoverinfo='skip',
    text=node_text,
    textposition='top center',
    marker=dict(
        size=10,
        line_width=2,
        color=node_color)
)

fig = go.Figure(data=arrow_traces + edge_traces + [node_trace],
                layout=go.Layout(
                    title=f'{name}:',
                    titlefont_size=16,
                    showlegend=False,
                    hovermode='closest',
                    hoverdistance=500,
                    margin=dict(b=20,l=5,r=5,t=40),
                    annotations=[ dict(
                        showarrow=False,
                        xref="paper", yref="paper",
                        x=0.005, y=-0.002 ) ],
                    xaxis=dict(showgrid=False, zeroline=False, showticklabels=False),
                    yaxis=dict(showgrid=False, zeroline=False, showticklabels=False))
)
fig.show()


import sys

import pandas as pd

def jsons_to_excel(list_of_jsons, sepby="sheets", filename=None):
    """

    Args:
        list_of_jsons (list): A list of jsons to write to a spreadsheet
        sepby (str): "sheets" or "rows", sheets writes each json to a separate
            excel sheet while rows writes each json to the next row.
        filename (str): name of excel file to write, defaults to write in the
            format <repo>_issues.xlsx

    Returns:
        pathlib.Path: filepath to excel file containing jsons
    """

    if filename is None:
        reponame = list_of_jsons[0][0]["url"].split("/")[-3]
        filename = "{}_issues.xlsx".format(reponame)

    with pd.ExcelWriter(filename) as writer:
        if sepby == "sheets":
            for i, json_issue in enumerate(list_of_jsons, 1):
                sheet_name = "page {}".format(i)
                df = tidy_df(pd.DataFrame(json_issue))
                df.to_excel(writer, sheet_name=sheet_name, engine='xlsxwriter')
        elif sepby == "rows":
            df = tidy_df(pd.DataFrame(list_of_jsons[0]))
            for json_issue in list_of_jsons[1:]:
                df = df.append(tidy_df(pd.DataFrame(json_issue)),
                               ignore_index=True)
            df.to_excel(writer, sheet_name="all issues", engine='xlsxwriter')
        else:
            print("Invalid sepby option!")
            sys.exit(1)

def tidy_df(df, columns_to_keep=None):
    """
    Removes useless columns and tidies the dataframe.

    Args:
        df (pd.DataFrame): data frame to tidy.
        columns_to_keep (list): list of column names to keep, will remove all
            others.

    Returns:
        pd.DataFrame: tidied data frame.
    """

    if columns_to_keep is None:
        columns_to_keep = [
            "html_url", "number", "title", "created_at", "updated_at",
            "closed_at", "body", "labels"
        ]

    columns_to_drop = [col for col in df.columns if col not in columns_to_keep]

    if "labels" in columns_to_keep:
        df.labels = df.labels.apply(lambda x: get_label_name_from_dict(x))

    return df.drop(columns_to_drop, axis=1)

def get_label_name_from_dict(labels_dict_list):
    """
    Parses the labels dict and returns just the names of the labels.

    Args:
        labels_dict_list (list): list of dictionaries

    Returns:
        str: name of each label separated by commas.
    """

    label_names = [a_dict["name"] for a_dict in labels_dict_list]

    return ",".join(label_names)


from __future__ import unicode_literals
import frappe
from frappe import msgprint, _
from frappe.utils import getdate

from frappe.model.document import Document

class FiscalYear(Document):

	def set_as_default(self):
		frappe.db.set_value("Global Defaults", None, "current_fiscal_year", self.name)
		frappe.get_doc("Global Defaults").on_update()

		# clear cache
		frappe.clear_cache()

		msgprint(_("{0} is now the default Fiscal Year. Please refresh your browser for the change to take effect.").format(self.name))

	def validate(self):
		year_start_end_dates = frappe.db.sql("""select year_start_date, year_end_date
			from `tabFiscal Year` where name=%s""", (self.name))

		if year_start_end_dates:
			if getdate(self.year_start_date) != year_start_end_dates[0][0] or getdate(self.year_end_date) != year_start_end_dates[0][1]:
				frappe.throw(_("Cannot change Fiscal Year Start Date and Fiscal Year End Date once the Fiscal Year is saved."))

	def on_update(self):
		# validate year start date and year end date
		if getdate(self.year_start_date) > getdate(self.year_end_date):
			frappe.throw(_("Fiscal Year Start Date should not be greater than Fiscal Year End Date"))

		if (getdate(self.year_end_date) - getdate(self.year_start_date)).days > 366:
			frappe.throw(_("Fiscal Year Start Date and Fiscal Year End Date cannot be more than a year apart."))

		year_start_end_dates = frappe.db.sql("""select name, year_start_date, year_end_date
			from `tabFiscal Year` where name!=%s""", (self.name))

		for fiscal_year, ysd, yed in year_start_end_dates:
			if (getdate(self.year_start_date) == ysd and getdate(self.year_end_date) == yed) \
				and (not frappe.flags.in_test):
					frappe.throw(_("Fiscal Year Start Date and Fiscal Year End Date are already set in Fiscal Year {0}").format(fiscal_year))


import numpy as np
from seal import Plaintext, PublicKey, Ciphertext, CKKSEncoder, SEALContext, Encryptor, \
    Decryptor, Evaluator, SecretKey, RelinKeys, GaloisKeys, DoubleVector, EncryptionParameters, scheme_type, \
    CoeffModulus, KeyGenerator

from smart.constants import POLY_MODULUS_DEGREE, PRIME_SIZE_LIST, SCALE
from smart.seal_matrix import CipherMatrix, Matrix
from smart.seal_vector import Vector

class SealOps:
    @classmethod
    def with_env(cls):
        parms = EncryptionParameters(scheme_type.CKKS)
        parms.set_poly_modulus_degree(POLY_MODULUS_DEGREE)
        parms.set_coeff_modulus(CoeffModulus.Create(POLY_MODULUS_DEGREE, PRIME_SIZE_LIST))

        context = SEALContext.Create(parms)

        keygen = KeyGenerator(context)
        public_key = keygen.public_key()
        secret_key = keygen.secret_key()
        relin_keys = keygen.relin_keys()
        galois_keys = keygen.galois_keys()

        return cls(context=context, public_key=public_key, secret_key=secret_key, relin_keys=relin_keys,
                   galois_keys=galois_keys, poly_modulus_degree=POLY_MODULUS_DEGREE, scale=SCALE)

    def __init__(self, context: SEALContext, scale: float, poly_modulus_degree: int, public_key: PublicKey = None,
                 secret_key: SecretKey = None, relin_keys: RelinKeys = None, galois_keys: GaloisKeys = None):
        self.scale = scale
        self.context = context
        self.encoder = CKKSEncoder(context)
        self.evaluator = Evaluator(context)
        self.encryptor = Encryptor(context, public_key)
        self.decryptor = Decryptor(context, secret_key)
        self.relin_keys = relin_keys
        self.galois_keys = galois_keys
        self.poly_modulus_degree_log = np.log2(poly_modulus_degree)

    def encrypt(self, matrix: np.array):
        matrix = Matrix.from_numpy_array(array=matrix)
        cipher_matrix = CipherMatrix(rows=matrix.rows, cols=matrix.cols)

        for i in range(matrix.rows):
            encoded_row = Plaintext()
            self.encoder.encode(matrix[i], self.scale, encoded_row)
            self.encryptor.encrypt(encoded_row, cipher_matrix[i])

        return cipher_matrix

    def decrypt(self, cipher_matrix: CipherMatrix) -> Matrix:
        matrix = Matrix(rows=cipher_matrix.rows, cols=cipher_matrix.cols)

        for i in range(matrix.rows):
            row = Vector()
            encoded_row = Plaintext()
            self.decryptor.decrypt(cipher_matrix[i], encoded_row)
            self.encoder.decode(encoded_row, row)
            matrix[i] = row

        return matrix

    def add(self, matrix_a: CipherMatrix, matrix_b: CipherMatrix) -> CipherMatrix:
        self.validate_same_dimension(matrix_a, matrix_b)

        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)
        for i in range(matrix_a.rows):
            a_tag, b_tag = self.get_matched_scale_vectors(matrix_a[i], matrix_b[i])
            self.evaluator.add(a_tag, b_tag, result_matrix[i])

        return result_matrix

    def add_plain(self, matrix_a: CipherMatrix, matrix_b: np.array) -> CipherMatrix:
        matrix_b = Matrix.from_numpy_array(matrix_b)
        self.validate_same_dimension(matrix_a, matrix_b)

        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        for i in range(matrix_a.rows):
            row = matrix_b[i]
            encoded_row = Plaintext()
            self.encoder.encode(row, self.scale, encoded_row)
            self.evaluator.mod_switch_to_inplace(encoded_row, matrix_a[i].parms_id())
            self.evaluator.add_plain(matrix_a[i], encoded_row, result_matrix[i])

        return result_matrix

    def multiply_plain(self, matrix_a: CipherMatrix, matrix_b: np.array) -> CipherMatrix:
        matrix_b = Matrix.from_numpy_array(matrix_b)
        self.validate_same_dimension(matrix_a, matrix_b)

        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        for i in range(matrix_a.rows):
            row = matrix_b[i]
            encoded_row = Plaintext()
            self.encoder.encode(row, self.scale, encoded_row)
            self.evaluator.mod_switch_to_inplace(encoded_row, matrix_a[i].parms_id())
            self.evaluator.multiply_plain(matrix_a[i], encoded_row, result_matrix[i])

        return result_matrix

    def dot_vector(self, a: Ciphertext, b: Ciphertext) -> Ciphertext:
        result = Ciphertext()

        self.evaluator.multiply(a, b, result)
        self.evaluator.relinearize_inplace(result, self.relin_keys)
        self.vector_sum_inplace(result)
        self.get_vector_first_element(result)
        self.evaluator.rescale_to_next_inplace(result)

        return result

    def dot_vector_with_plain(self, a: Ciphertext, b: DoubleVector) -> Ciphertext:
        result = Ciphertext()

        b_plain = Plaintext()
        self.encoder.encode(b, self.scale, b_plain)

        self.evaluator.multiply_plain(a, b_plain, result)
        self.vector_sum_inplace(result)
        self.get_vector_first_element(result)

        self.evaluator.rescale_to_next_inplace(result)

        return result

    def get_vector_range(self, vector_a: Ciphertext, i: int, j: int) -> Ciphertext:
        cipher_range = Ciphertext()

        one_and_zeros = DoubleVector([0.0 if x < i else 1.0 for x in range(j)])
        plain = Plaintext()
        self.encoder.encode(one_and_zeros, self.scale, plain)
        self.evaluator.mod_switch_to_inplace(plain, vector_a.parms_id())
        self.evaluator.multiply_plain(vector_a, plain, cipher_range)

        return cipher_range

    def dot_matrix_with_matrix_transpose(self, matrix_a: CipherMatrix, matrix_b: CipherMatrix):
        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        rows_a = matrix_a.rows
        cols_b = matrix_b.rows

        for i in range(rows_a):
            vector_dot_products = []
            zeros = Plaintext()

            for j in range(cols_b):
                vector_dot_products += [self.dot_vector(matrix_a[i], matrix_b[j])]

                if j == 0:
                    zero = DoubleVector()
                    self.encoder.encode(zero, vector_dot_products[j].scale(), zeros)
                    self.evaluator.mod_switch_to_inplace(zeros, vector_dot_products[j].parms_id())
                    self.evaluator.add_plain(vector_dot_products[j], zeros, result_matrix[i])
                else:
                    self.evaluator.rotate_vector_inplace(vector_dot_products[j], -j, self.galois_keys)
                    self.evaluator.add_inplace(result_matrix[i], vector_dot_products[j])

        for vec in result_matrix:
            self.evaluator.relinearize_inplace(vec, self.relin_keys)
            self.evaluator.rescale_to_next_inplace(vec)

        return result_matrix

    def dot_matrix_with_plain_matrix_transpose(self, matrix_a: CipherMatrix, matrix_b: np.array):
        matrix_b = Matrix.from_numpy_array(matrix_b)
        result_matrix = CipherMatrix(rows=matrix_a.rows, cols=matrix_a.cols)

        rows_a = matrix_a.rows
        cols_b = matrix_b.rows

        for i in range(rows_a):
            vector_dot_products = []
            zeros = Plaintext()

            for j in range(cols_b):
                vector_dot_products += [self.dot_vector_with_plain(matrix_a[i], matrix_b[j])]

                if j == 0:
                    zero = DoubleVector()
                    self.encoder.encode(zero, vector_dot_products[j].scale(), zeros)
                    self.evaluator.mod_switch_to_inplace(zeros, vector_dot_products[j].parms_id())
                    self.evaluator.add_plain(vector_dot_products[j], zeros, result_matrix[i])
                else:
                    self.evaluator.rotate_vector_inplace(vector_dot_products[j], -j, self.galois_keys)
                    self.evaluator.add_inplace(result_matrix[i], vector_dot_products[j])

        for vec in result_matrix:
            self.evaluator.relinearize_inplace(vec, self.relin_keys)
            self.evaluator.rescale_to_next_inplace(vec)

        return result_matrix

    @staticmethod
    def validate_same_dimension(matrix_a, matrix_b):
        if matrix_a.rows != matrix_b.rows or matrix_a.cols != matrix_b.cols:
            raise ArithmeticError("Matrices aren't of the same dimension")

    def vector_sum_inplace(self, cipher: Ciphertext):
        rotated = Ciphertext()

        for i in range(int(self.poly_modulus_degree_log - 1)):
            self.evaluator.rotate_vector(cipher, pow(2, i), self.galois_keys, rotated)
            self.evaluator.add_inplace(cipher, rotated)

    def get_vector_first_element(self, cipher: Ciphertext):
        one_and_zeros = DoubleVector([1.0])
        plain = Plaintext()
        self.encoder.encode(one_and_zeros, self.scale, plain)
        self.evaluator.multiply_plain_inplace(cipher, plain)

    def get_matched_scale_vectors(self, a: Ciphertext, b: Ciphertext) -> (Ciphertext, Ciphertext):
        a_tag = Ciphertext(a)
        b_tag = Ciphertext(b)

        a_index = self.context.get_context_data(a.parms_id()).chain_index()
        b_index = self.context.get_context_data(b.parms_id()).chain_index()

        # Changing the mod if required, else just setting the scale
        if a_index < b_index:
            self.evaluator.mod_switch_to_inplace(b_tag, a.parms_id())

        elif a_index > b_index:
            self.evaluator.mod_switch_to_inplace(a_tag, b.parms_id())

        a_tag.set_scale(self.scale)
        b_tag.set_scale(self.scale)

        return a_tag, b_tag