import re
from PyQt5 import uic, QtCore, QtWidgets
from PyQt5.QtWidgets import QMessageBox, QTableWidgetItem
import Modulos.db_citas as cita

class WindowTwo(QtWidgets.QMainWindow):

    switch_window = QtCore.pyqtSignal()

    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        uic.loadUi("interfaces/BuscarCita.ui", self)
        self.setWindowTitle("J-Medic: Buscar Medico")
        self.stylesheet = self.estilos()
        self.setStyleSheet(self.stylesheet)
        self.BuscarIdCita.clicked.connect(self.validar_datos)
        self.BuscarIdPaciente.clicked.connect(self.validar_datos_2)
        self.BuscarFecha.clicked.connect(self.validar_datos_3)
        self.actionRegresar.setShortcut("Ctrl+R")
        self.actionRegresar.triggered.connect(self.switch)
        self.validar()

    def validar(self):
        self.InputIdCita.textChanged.connect(self.validar_id_cita)
        self.InputIdPaciente.textChanged.connect(self.validar_id_paciente)
        pass

    def validar_id_cita(self):
        id_cita = self.InputIdCita.text()
        validar = re.match("^\d{1,}$", id_cita, re.I)
        if id_cita == "":
            self.InputIdCita.setStyleSheet("border: 2px solid yellow;")
            return False
        elif not validar:
            self.InputIdCita.setStyleSheet("border: 2px solid red;")
            return False
        else:
            self.InputIdCita.setStyleSheet("border: 2px solid green;")
            return True

    def validar_datos(self):
        if self.validar_id_cita():
            result= cita.buscar_cita_idcita(int(self.InputIdCita.text()))
            print (result)
            ayuda = result
            try:
                self.tablaCita.setItem(0 , 0, QTableWidgetItem(str(ayuda[0])))
                self.tablaCita.setItem(0 , 1, QTableWidgetItem(str(ayuda[1])))
                self.tablaCita.setItem(0 , 2, QTableWidgetItem(ayuda[2]))
                self.tablaCita.setItem(0 , 3, QTableWidgetItem(ayuda[3]))
                self.tablaCita.setItem(0 , 4, QTableWidgetItem(ayuda[4]))
                self.tablaCita.setItem(0 , 5, QTableWidgetItem(str(ayuda[5])))
                QMessageBox.information(
                self,"Correcto", "Busqueda exitosa", QMessageBox.Discard)
            except:
                QMessageBox.warning(self, "Error", "No se ha encontrado nada", QMessageBox.Discard)
        else:
           QMessageBox.warning(
                self, "Error", "Ingresa los datos correctamente", QMessageBox.Discard)

    def validar_id_paciente(self):
        id_paciente = self.InputIdPaciente.text()
        validar = re.match("^\d{1,}$", id_paciente, re.I)
        if id_paciente == "":
            self.InputIdPaciente.setStyleSheet("border: 2px solid yellow;")
            return False
        elif not validar:
            self.InputIdPaciente.setStyleSheet("border: 2px solid red;")
            return False
        else:
            self.InputIdPaciente.setStyleSheet("border: 2px solid green;")
            return True
    def validar_datos_2(self):
        if self.validar_id_paciente():
            ayuda2= cita.buscar_cita_idpaciente(int(self.InputIdPaciente.text()))
            print(ayuda2)
            try:
                if ayuda2:
                    contador = 0
                    for elements in ayuda2:
                        self.tablaCita.setItem(
                            contador, 0, QTableWidgetItem(str(ayuda2[contador][0])))
                        self.tablaCita.setItem(
                            contador, 1, QTableWidgetItem(str(ayuda2[contador][1])))
                        self.tablaCita.setItem(
                            contador, 2, QTableWidgetItem(ayuda2[contador][2]))
                        self.tablaCita.setItem(
                            contador, 3, QTableWidgetItem(ayuda2[contador][3]))
                        self.tablaCita.setItem(
                            contador, 4, QTableWidgetItem(ayuda2[contador][4]))
                        self.tablaCita.setItem(
                            contador, 5, QTableWidgetItem(str(ayuda2[contador][5])))
                        contador += 1
                    QMessageBox.information(
                        self,"Correcto", "Busqueda exitosa", QMessageBox.Discard)
                else:
                     QMessageBox.warning(
                        self, "Error", "No se ha encontrado nada", QMessageBox.Discard)
            except:
                QMessageBox.warning(self, "Error", "No se ha encontrado nada", QMessageBox.Discard)
        else:
           QMessageBox.warning(
                self, "Error", "Ingresa los datos correctamente", QMessageBox.Discard)

    def validar_fecha(self):
        anio = self.inputA.currentText()
        mes = self.inputM.currentText()
        dia = self.inputD.currentText()
        hora = self.inputH.currentText()
        minutos = self.inputMin.currentText()
        cont = 0
        if anio == "":
            cont+=1

        if mes == "":
            cont+=1

        if dia == "":
            cont+=1

        if hora == "":
            cont+=1

        if minutos == "":
            cont+=1

        fecha=str(anio+mes+dia+hora+minutos)
        if cont>0:
            fecha=""

        if fecha == "":
            return False
        else:
            return True

    def validar_datos_3(self):
        if self.validar_fecha():
            anio = self.inputA.currentText()
            mes = self.inputM.currentText()
            dia = self.inputD.currentText()
            hora = self.inputH.currentText()
            minutos = self.inputMin.currentText()
            fecha=str(anio+mes+dia+hora+minutos)
            ayuda2= cita.buscar_cita_fecha(fecha)
            print(fecha)
            print(ayuda2)
            try:
                if ayuda2:
                    contador = 0
                    for elements in ayuda2:
                        self.tablaCita.setItem(
                            contador, 0, QTableWidgetItem(str(ayuda2[contador][0])))
                        self.tablaCita.setItem(
                            contador, 1, QTableWidgetItem(str(ayuda2[contador][1])))
                        self.tablaCita.setItem(
                            contador, 2, QTableWidgetItem(ayuda2[contador][2]))
                        self.tablaCita.setItem(
                            contador, 3, QTableWidgetItem(ayuda2[contador][3]))
                        self.tablaCita.setItem(
                            contador, 4, QTableWidgetItem(ayuda2[contador][4]))
                        self.tablaCita.setItem(
                            contador, 5, QTableWidgetItem(str(ayuda2[contador][5])))
                        contador += 1
                    QMessageBox.information(
                        self,"Correcto", "Busqueda exitosa", QMessageBox.Discard)
                else:
                     QMessageBox.warning(
                        self, "Error", "No se ha encontrado nada", QMessageBox.Discard)
            except:
                QMessageBox.warning(self, "Error", "No se ha encontrado nada", QMessageBox.Discard)
        else:
           QMessageBox.warning(
                self, "Error", "Ingresa los datos correctamente", QMessageBox.Discard)

    def estilos(self):
        estilo = """
        *{
            font-family:century gothic;
            font-size:24px;
        }

        QWidget{
            background-image: url(Imagenes/pru.jpg);
        }

        QFrame{
            background:rgba(0/0/0/0.8);
            border-radius:15px;
        }

        QPushButton{
            background:#88c7dc;
            color:white;
            border-radius:15px;
        }

        QPushButton:hover{
            color:#333;
            border-radius:15px;
            background:#49ebff;
        }

        QToolButton{
            background:red;
            border-radius:60px;

        }

        QLabel{
            color:white;
            background:transparent;
        }

        QLineEdit{
            background:transparent;
            border:none;
            color:#717072;
            border-bottom:1px solid #717072;
        }
        #InputIdCita{
        background-color: rgb(255, 255, 255);
        color: rgb(0, 0, 0);
        }
        #InputIdPaciente{
        background-color: rgb(255, 255, 255);
        color: rgb(0, 0, 0);
        }

        """
        return estilo

    def switch(self):
        self.switch_window.emit()


# coding: utf-8

import json
import random
import os
import sys
import cv2
import numpy as np

sys.path.append(os.path.dirname(__file__))
import imgutil

def main(config_file):
    with open(config_file) as fp:
        config = json.load(fp)
    img2dataset(
        config['orig_data_dir'],
        config.get('img_size', 100),
        config.get('test_ratio', 0.2),
        config.get('num_class')
    )

def img2dataset(orig_data_dir, img_size, test_ratio, num_class):
    orig_datasets = [d for d in os.listdir(orig_data_dir) if os.path.isdir(os.path.join(orig_data_dir, d))]
    if num_class is not None:
        random.shuffle(orig_datasets)
        target_labels = [orig_labels.pop() for i in range(num_class)]
    else:
        target_labels = sorted(orig_datasets)
        num_class = len(target_labels)

    dataset_label_table = {}
    dataset_dir = os.path.join('dataset', '{0}x{0}_{1}'.format(img_size, num_class))

    for i, orig_label in enumerate(target_labels):
        dataset_label_table[str(i)] = orig_label

        orig_label_dir = os.path.join(orig_data_dir, orig_label)
        for f in os.listdir(orig_label_dir):
            img = cv2.imread(os.path.join(orig_label_dir, f))
            resized_img = cv2.resize(img, (img_size, img_size))
            if random.random() > test_ratio:
                dir_to_save = os.path.join(dataset_dir, 'train', str(i))
            else:
                dir_to_save = os.path.join(dataset_dir, 'test', str(i))
            save_img(dir_to_save, f, resized_img)

    with open(os.path.join(dataset_dir, 'label_table.tsv'), mode='w') as fp:
        for label in sorted(dataset_label_table.keys()):
            fp.write('{}\t{}\n'.format(label, dataset_label_table[label]))

def save_img(dir_to_save, file_name, img):
    if not os.path.exists(dir_to_save):
        os.makedirs(dir_to_save)
    cv2.imwrite(os.path.join(dir_to_save, file_name), img)

if __name__ == '__main__':
    main(sys.argv[1])


#!/usr/bin/env python
"""Command Line Podcast Manager

You can add podcast RSS feeds, download podcasts, and sync them to a directory.
If the podcast ID3 tags are messy or unclear you can also automatically edit
them by setting the id3_edit config.

id3_edit structure:
    "id3_edit": {
        "podcast table id": {
          "album": "string to manually set album to",
          "artist": "string to manually set artist to",
          "title": "optional parameter: if set to copy_item will set to title
                    from the rss feed, otherwise if set to true will set
                    title to rss published date + album config"
        }
    }
"""
import json
import argparse
import urllib.request, urllib.parse, urllib.error
import os
import sys
import shutil
import urllib.parse
import subprocess
import feedparser
from peewee import CharField, ForeignKeyField, DateTimeField, BooleanField, SqliteDatabase, Model, IntegrityError, TextField
from time import mktime, sleep
from datetime import datetime, timedelta
from downloader import Download, DownloadError
import mutagen
from mutagen import easyid3
from bs4 import BeautifulSoup
import unicodedata
import textwrap
from terminaltables import AsciiTable
from terminaltables.width_and_alignment import max_dimensions
from terminaltables.build import flatten
from blessings import Terminal
from gevent import monkey
monkey.patch_all()
import gevent

def load_config():
    f = open('podcli_config.json', 'r')
    return json.load(f)

def get_enclosure(links):
    for x in links:
        if x['rel'] == 'enclosure':
            return x['href']

# Models
db = SqliteDatabase(load_config()['db'])

class PodcastTable(Model):
    feed = CharField(unique=True)
    title = CharField()

    class Meta:
        database = db  # this model uses the podcli database

class EpisodeTable(Model):
    podcast = ForeignKeyField(PodcastTable, related_name='episodes')
    title = CharField()
    published = DateTimeField()
    enclosure = CharField()
    summary = TextField(null=True)
    new = BooleanField()

    class Meta:
        database = db  # this model uses the podcli database

def create_tables():
    PodcastTable.create_table(fail_silently=True)
    EpisodeTable.create_table(fail_silently=True)

def ascii_table_last(ascii_table):
    dimensions = max_dimensions(ascii_table.table_data,
                                ascii_table.padding_left,
                                ascii_table.padding_right)[:3]
    whole_table = ascii_table.table_data
    ascii_table.table_data = (ascii_table.table_data[-1], )
    last_table = flatten(ascii_table.gen_table(*dimensions))
    ascii_table.table_data = whole_table
    return last_table

def get_max_dimensions(ascii_table):
    return max_dimensions(ascii_table.table_data, ascii_table.padding_left,
                          ascii_table.padding_right)[:3]

class PodCli(object):
    def __init__(self):
        self.config = load_config()
        self.download_dir = self.get_download_dir()
        self.check_download_dir()

    def get_download_dir(self):
        if 'download_folder' in list(self.config.keys()):
            if os.path.isabs(self.config['download_folder']):
                return self.config['download_folder']
            else:
                return os.path.join(
                    os.path.dirname(os.path.realpath(sys.argv[0])),
                    self.config['download_folder'])
        else:
            return os.path.dirname(os.path.realpath(sys.argv[0]))

    def check_download_dir(self):
        if not os.path.exists(self.download_dir):
            os.mkdir(self.download_dir)

    def add_podcast(self, rss_url):
        feed = feedparser.parse(rss_url)
        try:
            PodcastTable.create(feed=rss_url, title=feed['feed']['title'])
        except IntegrityError:
            print('Podcast already exists.')
        for pod in PodcastTable.select():
            print(pod.title, pod.feed)

    def get_summary(self, item):
        text = BeautifulSoup(item["summary"], "html.parser").get_text()
        return unicodedata.normalize("NFKD", text)

    def print_summary(self, summary):
        if summary:
            term = Terminal()
            for line in textwrap.wrap(
                    summary,
                    term.width,
                    initial_indent='    ',
                    subsequent_indent='    '):
                print(line)

    def print_summary_table(self, items=None):
        table_headers = [['title', 'summary']]
        table_data = []
        ascii_table = None
        if not items:
            items = EpisodeTable.select().where(EpisodeTable.new)
        for item in items:
            term = Terminal()
            summ = ""
            for line in textwrap.wrap(
                    item.summary,
                    term.width * 0.7,
                    initial_indent=' ',
                    subsequent_indent=' '):
                summ += line + "\n"
            table_data.append([item.podcast.title, summ])
            ascii_table = AsciiTable(table_headers + table_data)
            ascii_table.inner_row_border = True
        if ascii_table:
            print(ascii_table.table)
        else:
            "Nothing to show"

    def print_download_item(self, item, ascii_table):
        dimensions = get_max_dimensions(ascii_table)
        title = ""
        for line in textwrap.wrap(
                item.podcast.title,
                dimensions[0][0],
                initial_indent=' ',
                subsequent_indent=' '):
            title += line + "\n"
        summ = ""
        for line in textwrap.wrap(
                item.summary,
                dimensions[0][1],
                initial_indent=' ',
                subsequent_indent=' '):
            summ += line + "\n"
        if ascii_table:
            ascii_table.table_data.append([title, summ])

            print(ascii_table_last(ascii_table))
            return ascii_table
        else:
            table_headers = [['title', 'summary']]
            table_data = [[title, summ]]
            ascii_table = AsciiTable(table_headers + table_data)
            ascii_table.inner_row_border = True
            print(ascii_table.table)
            return ascii_table

    def refresh_all(self):
        # print("Refreshing feeds ...")
        spawned = []
        for pod in PodcastTable.select():
            spawned.append(gevent.spawn(self.get_podcast_feed, pod.feed, pod))
        gevent.iwait
        dots = 1
        while True:
            alive = False
            for gl in spawned:
                if not gl.dead:
                    alive = True
            if not alive:
                print("\n")
                break
            message = "Refreshing feeds " + "." * dots
            sys.stdout.write('\x1b[2K\r' + message)
            sys.stdout.flush()
            dots += 1
            if dots > 4:
                dots = 1
            gevent.sleep(0.5)

    def get_podcast_feed(self, url, pod):
        feed = feedparser.parse(url)
        for item in feed['entries']:
            enclosure = self.get_enclosure(item)
            if not enclosure:
                # print('%s has no link, skipping...' % item.title)
                continue
            # If episode enclosure doesn't exist, add it
            if EpisodeTable.select().where(
                    EpisodeTable.enclosure == enclosure).count() < 1:
                dt = datetime.fromtimestamp(mktime(item['published_parsed']))
                summary = self.get_summary(item)
                # print('New Episode: ', pod.title, " -- ", item['title'], dt.strftime('%d/%m/%Y'))
                # self.print_summary(summary)
                # print("\n")
                EpisodeTable.create(
                    podcast=pod,
                    title=item['title'],
                    published=dt,
                    enclosure=enclosure,
                    summary=summary,
                    new=True)
        # print("Refreshed feed: %s" % pod.title)

    def get_enclosure(self, episode):
        if 'links' not in list(episode.keys()):
            return False
        for link in episode['links']:
            if link['rel'] == 'enclosure':
                return link['href']

    def is_downloaded(self, url, filename):
        if not os.path.exists(filename):
            return False
        try:
            df = Download(url, filename)
            filesize = df.get_url_file_size()
        except urllib.error.HTTPError:
            print("HTTTP Error Skipping")
            return True
        if not filesize:
            return False
        elif int(filesize) > os.path.getsize(filename):
            print("filesize mismatch: %s %s" % (filesize,
                                                os.path.getsize(filename)))
            return False
        else:
            return True

    def download_all_new(self):
        print("Downloading ...")
        spawned = []
        ascii_table = None
        for item in EpisodeTable.select().where(EpisodeTable.new):
            filename = self.get_fullpath(item.enclosure)
            if not self.is_downloaded(item.enclosure, filename):
                ascii_table = self.print_download_item(item, ascii_table)
                spawned.append(
                    gevent.spawn(self.download, item.enclosure, filename,
                                 item))
        gevent.joinall(spawned)

    def download(self, url, fullpath, item):
        try:
            df = Download(url, fullpath)
            df.download()
        except urllib.error.HTTPError:
            print("Http Error, skipping")
            return False
        self.check_id3_edit(item.podcast.id, fullpath, item)
        return

    def get_fullpath(self, url):
        return os.path.join(
            self.download_dir,
            urllib.parse.unquote(
                os.path.basename(urllib.parse.urlparse(url).path)))

    def list(self, which):
        if which == 'new':
            self.print_summary_table()
            # for item in EpisodeTable.select().where(EpisodeTable.new):
            #     self.print_summary(item.summary)
            #     print("\n")
        if which == 'pod':
            table_headers = [['id', 'title']]
            table_data = []
            for item in PodcastTable.select():
                table_data.append([str(item.id), item.title])
            ascii_table = AsciiTable(table_headers + table_data)
            ascii_table.inner_row_border = True
            print(ascii_table.table)

    def check_id3_edit(self, podcast_id, filename, item):
        if str(podcast_id) in list(self.config['id3_edit'].keys()):
            id3_config = self.config['id3_edit'][str(podcast_id)]
            album = id3_config['album']
            artist = id3_config['artist']
            if 'title' in list(id3_config.keys()):
                if id3_config['title'] == 'copy_item':
                    title = item.title
                else:
                    title = item.published.strftime('%d/%m-') + album
                self.edit_id3(filename, album, artist, title)
            else:
                self.edit_id3(filename, album, artist)

    def edit_id3(self, filename, album, artist, title=None):
        try:
            audio = easyid3.EasyID3(filename)
        except mutagen.id3.ID3NoHeaderError:
            audio = mutagen.File(filename, easy=True)
            audio.add_tags()
        audio["album"] = album
        audio["artist"] = artist
        audio["genre"] = "Podcast"
        if title:
            audio["title"] = title
        audio.save()

    def sync(self, which):
        if which == 'new':
            print("syncing ...")
            for item in EpisodeTable.select().where(EpisodeTable.new):
                filename = self.get_fullpath(item.enclosure)
                if not os.path.exists(filename):
                    print("Haven't downloaded %s yet." %
                          os.path.basename(filename))
                    continue
                self.print_summary_table([item])
                if self.config["folder_mode"]:
                    pod_dir = os.path.join(self.config['sync_to'],
                                           item.podcast.title)
                    writetopath = os.path.join(pod_dir,
                                               os.path.basename(filename))
                    if not os.path.exists(pod_dir):
                        os.mkdir(pod_dir)
                else:
                    writetopath = os.path.join(self.config['sync_to'],
                                               os.path.basename(filename))
                shutil.copyfile(filename, writetopath)
                item.new = False
                item.save()

    def delete_podcast(self, podcast_id):
        podcast = PodcastTable.select().\
            where(PodcastTable.id == podcast_id).get()
        podcast.delete_instance(recursive=True)

    def delete_old(self, location):
        if location == 'local':
            self.delete_files_local(self.download_dir)
        elif location == 'player':
            self.delete_files(self.config['sync_to'])

    def delete_files(self, direc, num_days=14):
        cur_dir = os.getcwd()
        os.chdir(direc)
        pod_dirs = os.listdir(direc)
        for pod_dir in pod_dirs:
            os.chdir(cur_dir)
            podcast = PodcastTable.select().\
                where(PodcastTable.title == pod_dir).get()
            os.chdir(direc)
            files = os.listdir(pod_dir)
            os.chdir(pod_dir)
            for filename in files:
                file_age = (datetime.now() -\
                    datetime.fromtimestamp(os.path.getctime(filename))).days
                if str(podcast.id) in self.config["podcast_age"].keys():
                    conf_age = self.config["podcast_age"][str(podcast.id)]
                else:
                    conf_age = num_days
                if file_age > num_days or file_age > conf_age:
                    print('removing %s' % (str(filename)))
                    os.remove(filename)
            os.chdir(direc)
        os.chdir(cur_dir)

    def delete_files_local(self, direc, num_days=14):
        cur_dir = os.getcwd()
        os.chdir(direc)
        files = os.listdir(direc)
        for filename in files:
            if (datetime.now() - datetime.fromtimestamp(
                    os.path.getctime(filename))).days > num_days:
                print('removing %s' % (str(filename)))
                os.remove(filename)
        os.chdir(cur_dir)

    def mark_old(self, days, podcast_id=False):
        if podcast_id:
            podcast = PodcastTable.select().where(
                PodcastTable.id == podcast_id).get()
            episodes = EpisodeTable.select().\
                where(EpisodeTable.new, EpisodeTable.podcast == podcast)
        else:
            episodes = EpisodeTable.select().where(EpisodeTable.new)
        for item in episodes:
            if item.published < datetime.now() - timedelta(days):
                print('Marking old: ', item.title)
                item.new = False
                item.save()

    def eject(self):
        while subprocess.call(
            ['diskutil', 'unmount', self.config['eject_point']]):
            print("Attempting to eject.")
            sleep(5)

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument("-a", "--add_podcast", help="add new podcast")
    parser.add_argument(
        "-r",
        "--refresh_all",
        help="refresh all podcasts",
        nargs='?',
        const=True)
    parser.add_argument(
        "-d",
        "--download_all_new",
        help="download all new podcasts",
        nargs='?',
        const=True)
    parser.add_argument(
        "-l",
        "--list",
        help=
        "with no argument lists all new podcasts, with argument pod it lists all podcasts",
        nargs='?',
        const='new')
    parser.add_argument(
        "-s", "--sync", help="sync all new podcasts", nargs='?', const='new')
    parser.add_argument(
        "--delete", help="delete podcast, must specify podcast id")
    parser.add_argument(
        "--delete_old",
        help=
        "delete old episode downloads, defaults to local episodes, other option player",
        nargs='?',
        const='local')
    parser.add_argument(
        "--mark_old",
        help="mark episodes older than arg as old",
        nargs='?',
        const='7')
    parser.add_argument(
        "--mark_old_podcast",
        help="specifies mark_old podcast to mark old episodes",
        nargs='?',
        const=False)
    parser.add_argument(
        "-e", "--eject", help="eject player", nargs='?', const=True)
    args = parser.parse_args()
    podcli = PodCli()

    if args.add_podcast:
        podcli.add_podcast(args.add_podcast)
    if args.refresh_all:
        podcli.refresh_all()
    if args.download_all_new:
        podcli.download_all_new()
    if args.list:
        podcli.list(args.list)
    if args.sync:
        podcli.sync(args.sync)
    if args.delete:
        podcli.delete_podcast(args.delete)
    if args.delete_old:
        podcli.delete_old(args.delete_old)
    if args.eject:
        podcli.eject()
    if args.mark_old:
        podcli.mark_old(int(args.mark_old), args.mark_old_podcast)


import requests
from datetime import datetime
import json
from docx import Document
import os
from collections import defaultdict
from helper import add_table,excel_update

#-------------------------------------------------------------
HOST = 'Host'
memory_tag = "Memory"
cpu_tag = "CPU"
memory_unit = "GB"
cpu_unit = "cores"

memory_queries = {f"{HOST}" : 'avg((uptycs_memory_used/uptycs_total_memory) * 100) by (host_name)',
           'Rule Engine' : "avg(uptycs_app_memory{app_name=~'.*ruleEngine.*'}) by (host_name)",
           'Osquery Ingestion' : "sum(uptycs_app_memory{app_name=~'osqueryIngestion'}) by (host_name)",
           "Kafka" : "avg(uptycs_app_memory{app_name=~'kafka'}) by (host_name)",
           "Trino" : "avg(uptycs_app_memory{app_name='trino'}) by (host_name)",
           "Tls" : "avg(uptycs_app_memory{app_name='tls'}) by (host_name)",
           "EventsDbIngestion" : "avg(uptycs_app_memory{app_name=~'eventsDbIngestion'}) by (host_name)",
           "Logger" : "sum(uptycs_app_memory{app_name=~'.*osqLogger-1.*'}) by (host_name)"
           }

cpu_queries = {f"{HOST}" : 'avg(100-uptycs_idle_cpu) by (host_name)',
           'Rule Engine' : "avg(uptycs_app_cpu{app_name=~'.*ruleEngine.*'}) by (host_name)",
           'Osquery Ingestion' : "avg(uptycs_app_cpu{app_name=~'osqueryIngestion'}) by (host_name)",
           "Kafka" : "avg(uptycs_app_cpu{app_name=~'kafka'}) by (host_name)",
           "Trino" : "avg(uptycs_app_cpu{app_name='trino'}) by (host_name)",
           "Tls" : "avg(uptycs_app_cpu{app_name='tls'}) by (host_name)",
           "EventsDbIngestion" : "avg(uptycs_app_cpu{app_name=~'eventsDbIngestion'}) by (host_name)",
           "Logger" : "sum(uptycs_app_cpu{app_name=~'.*osqLogger-1.*'}) by (host_name)"
           }

container_memory_queries = {'Container' : "sum(uptycs_docker_mem_used{}/(1000*1000*1000)) by (container_name)",}
container_cpu_queries = {'Container' : "sum(uptycs_docker_cpu_stats{}) by (container_name)",}

class MC_comparisions:
    def __init__(self,sprint,prom_con_obj,build,doc,curr_ist_start_time,curr_ist_end_time,save_current_build_data_path,fetch_prev_build_data_path,overall_comparisions_docx_path,previous_excel_file_path,current_excel_file_path,show_gb_cores=True):
        self.doc = doc
        self.curr_ist_start_time=curr_ist_start_time
        self.curr_ist_end_time=curr_ist_end_time
        self.show_gb_cores=show_gb_cores
        self.fetch_prev_build_data_path=fetch_prev_build_data_path
        self.save_current_build_data_path=save_current_build_data_path

        self.prom_con_obj=prom_con_obj
        self.PROMETHEUS = self.prom_con_obj.prometheus_path
        self.API_PATH = self.prom_con_obj.prom_api_path

        self.nodes_file_path=prom_con_obj.nodes_file_path
        self.sprint=sprint
        self.build = build
        self.prev_build = '-'
        self.overall_comparisions_docx_path=overall_comparisions_docx_path
        self.previous_excel_file_path=previous_excel_file_path
        self.current_excel_file_path=current_excel_file_path

        self.complete_usage = defaultdict(lambda : 0)
        with open(self.nodes_file_path, 'r') as file:
            self.nodes_data = json.load(file)

        self.summary={  memory_tag:{"increased_or_decreased" :{
                                        "increased":{"TOTAL" : [0,0]} ,
                                        "decreased":{"TOTAL" : [0,0]}
                                        },
                                    "unit":memory_unit,
                                },
                        cpu_tag:    {"increased_or_decreased" :{
                                        "increased":{"TOTAL" : [0,0]} ,
                                        "decreased":{"TOTAL" : [0,0]}
                                    },
                                "unit":cpu_unit,
                            }
                    }

    def extract_data(self,queries,tag,unit):
        final=dict()
        return_overall = dict()
        ist_time_format = '%Y-%m-%d %H:%M'
        start_time = (datetime.strptime(self.curr_ist_start_time, ist_time_format))
        end_time = (datetime.strptime(self.curr_ist_end_time, ist_time_format))
        stu = int(start_time.timestamp())
        etu = int(end_time.timestamp())

        for query in queries:
            final[query] = {}
            PARAMS = {
                'query': queries[query],
                'start': stu,
                'end': etu,
                'step':15
            }
            response = requests.get(self.PROMETHEUS + self.API_PATH, params=PARAMS)
            result = response.json()['data']['result']
            for res in result:
                hostname = res['metric']['host_name']
                if str(hostname).endswith('v') and len(hostname)>1:
                    hostname = str(hostname)[:-1]
                values = [float(i[1]) for i in res['values']]
                avg = sum(values) / len(values)
                final[query][hostname] = {"percentage":avg}
                if tag == memory_tag:
                    final[query][hostname][unit] = avg * float(self.nodes_data[hostname]['ram']) / 100
                    # if hostname in self.nodes_data:
                    #     final[query][hostname][unit] = avg * float(self.nodes_data[hostname]['ram']) / 100
                    # else:
                    #     final[query][hostname][unit] = None
                else:
                    if query == HOST:
                        final[query][hostname][unit] = avg * float(self.nodes_data[hostname]['cores']) / 100
                        # if hostname in self.nodes_data:
                        #     final[query][hostname][unit] = avg * float(self.nodes_data[hostname]['cores']) / 100
                        # else:
                        #     final[query][hostname][unit] = None
                    else:
                        final[query][hostname][unit] = avg/100

        #calculate overall pnodes,dnodes,pgnodes usage
        new_data = final[HOST]

        for node_type in ["pnodes" , "dnodes" , "pgnodes"]:
            new_sum=0
            for node in self.nodes_data[node_type]:
                new_sum+=new_data[node][unit]
            return_overall[node_type] = {f"{unit}":new_sum}

        return final,return_overall

    def extract_container_data(self,queries,tag,unit):
        final=dict()
        ist_time_format = '%Y-%m-%d %H:%M'
        start_time_utc = (datetime.strptime(self.curr_ist_start_time, ist_time_format))
        end_time_utc = (datetime.strptime(self.curr_ist_end_time, ist_time_format))
        stu = int(start_time_utc.timestamp())
        etu = int(end_time_utc.timestamp())
        for query in queries:
            final[query] = {}
            PARAMS = {
                'query': queries[query],
                'start': stu,
                'end': etu,
                'step':15
            }
            response = requests.get(self.PROMETHEUS + self.API_PATH, params=PARAMS)
            result = response.json()['data']['result']
            for res in result:

                hostname = res['metric']['container_name']
                values = [float(i[1]) for i in res['values']]
                avg = sum(values) / len(values)
                # print(f"{query} : {hostname}")
                if tag == memory_tag:
                    final[query][hostname] = {f"{unit}":avg}
                    self.complete_usage[tag]+=avg
                else:
                    final[query][hostname] = {f"{unit}":avg/100}
                    self.complete_usage[tag]+=avg/100

        return final

    def get_complete_container_utilization(self,data):
        data_dict=dict()
        old_data = data['previous']
        new_data = data['current']
        data_dict["title"] = f"Complete resource usage(GB/cores)"
        data_dict["header"] = [ "Metric",self.prev_build,self.build, "Absolute(%)" , "Relative(%)" ]
        data_dict["body"] = []
        excel_dict=[]

        for tag in new_data:
            curr_list=[]
            excel_list=[]
            curr_list.append((f"Complete {tag} usage"))
            excel_list.append((f"Complete {tag} usage"))
            try:
                curr_list.append((f"{old_data[tag]:.2f}"))
            except:
                curr_list.append(('-'))
            curr_list.append((f"{new_data[tag]:.2f}"))
            excel_list.append((round(new_data[tag], 2)))
            try:
                diff = round(old_data[tag]-new_data[tag] , 2)
                relative = round((old_data[tag]-new_data[tag]) / old_data[tag] , 2)
                if diff >0:
                    curr_list.append((str(abs(diff)) + " ⬇" , "green"))
                    excel_list.append((abs(diff) , "green"))
                    curr_list.append((str(abs(relative)) + " ⬇" , "green"))
                    excel_list.append((abs(relative) , "green"))
                elif diff < 0 :
                    curr_list.append((str(abs(diff)) + " ⬆" , "red"))
                    excel_list.append((abs(diff) , "red"))
                    curr_list.append((str(abs(relative)) + " ⬆" ,abs(relative), "red"))
                    excel_list.append((abs(relative) , "red"))
                else:
                    curr_list.append((str(abs(diff))))
                    excel_list.append((abs(diff)))
                    curr_list.append((str(abs(relative))))
                    excel_list.append((abs(relative)))

            except:
                curr_list.append(('-'))
                excel_list.append(('-'))
                curr_list.append(('-'))
                excel_list.append(('-'))

            data_dict['body'].append(curr_list)
            excel_dict.append(excel_list)
        return data_dict,excel_dict

    def get_container_utilization(self,data):
        excel_dict=[]
        data_dict=dict()
        data_dict["header"] = [ "Metric",self.prev_build,self.build, "Absolute(%)" , "Relative(%)" ]
        data_dict["body"] = []
        old_data = data['previous']
        new_data = data['current']
        tag = data['tag']
        unit = data['unit']
        data_dict["title"] = f"Overall Container {tag} usages"
        for query in new_data:
            for host_name in new_data[query]:
                curr_list = []
                excel_list=[]
                curr_list.append((f"{tag} used by container {host_name}"))
                excel_list.append((f"{tag} used by container {host_name}"))
                text = f"{tag} by {host_name}"
                try :
                    curr_list.append((f"{old_data[query][host_name][unit]:.2f} {unit}"))
                except Exception as e:
                    print("Error:", type(e).__name__, "-", str(e))
                    curr_list.append(('-'))

                try:
                    diff = float(old_data[query][host_name][unit]) - float(new_data[query][host_name][unit])
                except:
                    diff = - float(new_data[query][host_name][unit])
                try:
                    relative = float(((old_data[query][host_name][unit]) - float(new_data[query][host_name][unit]))*100 / old_data[query][host_name][unit])
                except:
                    relative=0

                curr_list.append((f"{new_data[query][host_name][unit]:.2f} {unit}"))
                excel_list.append((round(new_data[query][host_name][unit],2)))

                difference_text = f"{abs(diff):.2f} {unit}"
                relative_text = f"{abs(relative):.2f} %"

                if diff<0:
                    curr_list.append((difference_text + " ⬆" , "red"))
                    excel_list.append((round(abs(diff),2), "red"))
                    curr_list.append((relative_text + " ⬆" , "red"))
                    excel_list.append((round(abs(relative),2), "red"))
                    self.summary[tag]["increased_or_decreased"]["increased"]["TOTAL"][0] -= abs(diff)
                    if abs(diff)>1 or abs(relative) > 10:
                        self.summary[tag]["increased_or_decreased"]["increased"][text] = [abs(diff),abs(relative)]

                elif diff >0:
                    curr_list.append((difference_text + " ⬇" , "green"))
                    excel_list.append((round(abs(diff),2), "green"))
                    curr_list.append((relative_text + " ⬇" , "green"))
                    excel_list.append((round(abs(relative),2), "green"))

                    self.summary[tag]["increased_or_decreased"]["decreased"]["TOTAL"][0] -= abs(diff)
                    if abs(diff)>1 or abs(relative) > 10:
                        self.summary[tag]["increased_or_decreased"]["decreased"][text] = [abs(diff) , abs(relative)]
                else:
                    curr_list.append((difference_text))
                    excel_list.append((round(abs(diff),2)))
                    curr_list.append((relative_text))
                    excel_list.append((round(abs(relative),2)))
                data_dict["body"].append(curr_list)
                excel_dict.append(excel_list)
        return data_dict,excel_dict

    def get_summary_dict(self):
        final=[]
        for tag in self.summary:
            unit = self.summary[tag]["unit"]
            for inc_or_dec in self.summary[tag]["increased_or_decreased"]:
                data_dict=dict()
                individual_summary = dict(sorted(self.summary[tag]["increased_or_decreased"][inc_or_dec].items(), key=lambda item: item[1][0], reverse=True))
                data_dict["title"] = f'{tag} usage {inc_or_dec} summary'
                data_dict["header"] = [ "Metric",f'{inc_or_dec} in {unit}' , "Relative val(%)" ]
                data_dict["body"] = []
                for metric in individual_summary:
                    curr_list=[]
                    if metric == "TOTAL":
                        individual_summary[metric][0]=abs(individual_summary[metric][0])
                    curr_list.append((metric))
                    if inc_or_dec == "increased":
                        curr_list.append((f"{individual_summary[metric][0]:.2f}", "red"))
                        curr_list.append((f"{individual_summary[metric][1]:.2f}" , "red"))
                    else:
                        curr_list.append((f"{individual_summary[metric][0]:.2f}" ,  "green"))
                        curr_list.append((f"{individual_summary[metric][1]:.2f}" , "green"))
                    data_dict["body"].append(curr_list)
                final.append(data_dict)
        return final

    def get_average_utilization(self,data):
        data_dict=dict()
        excel_dict=[]
        data_dict["header"] = [ "Metric",self.prev_build,self.build, "Absolute(%)" , "Relative(%)" ]
        data_dict["body"] = []
        old_data = data['previous']
        new_data = data['current']
        tag = data['tag']
        unit = data['unit']
        data_dict["title"] = f"Comparision of Average {tag} utilization"

        for query in new_data:
            for host_name in new_data[query]:
                curr_list = []
                excel_list=[]
                if query==HOST:
                    curr_list.append((f"{tag} used by {host_name}" ))
                    excel_list.append((f"{tag} used by {host_name}" ))
                else:
                    curr_list.append((f"{tag} used by {query} {host_name}"))
                    excel_list.append((f"{tag} used by {query} {host_name}"))
                try :
                    _=f"{old_data[query][host_name]['percentage']:.2f}% ({old_data[query][host_name][unit]:.2f} {unit})"

                    if self.show_gb_cores:
                        curr_list.append((_))
                    else:
                        b=f"{old_data[query][host_name]['percentage']:.2f}%"
                        curr_list.append((b))
                except Exception as e:
                    curr_list.append(('-'))
                    print("Error:", type(e).__name__, "-", str(e))

                __=f"{new_data[query][host_name]['percentage']:.2f}% ({new_data[query][host_name][unit]:.2f} {unit})"

                if self.show_gb_cores:
                    curr_list.append((__))
                else:
                    d=f"{new_data[query][host_name]['percentage']:.2f}%"
                    curr_list.append((d))
                excel_list.append((__))
                try :
                    sign = None
                    percent_diff = float(old_data[query][host_name]['percentage']) - float(new_data[query][host_name]['percentage'])

                    diff = float(old_data[query][host_name][unit]) - float(new_data[query][host_name][unit])
                    row4_text = f"{abs(diff) *100  / (float(old_data[query][host_name][unit])) :.2f}%"

                    __diff = f"{abs(percent_diff):.2f}% ({abs(diff):.2f} {unit})"

                    if self.show_gb_cores:
                        difference_text = __diff
                    else:
                        difference_text = f"{abs(percent_diff):.2f}%"
                    sign = diff
                    # else:
                    #     row4_text = f"{abs(percent_diff) *100  / (float(old_data[query][host_name]['percentage'])) :.2f} %"
                    #     difference_text = f"{abs(percent_diff):.2f}%"
                    #     sign = percent_diff
                    #     __diff = difference_text
                    #     __diff = float(__diff[:-1])

                    __rel = float(row4_text[:-1])

                    if sign<0:
                        curr_list.append((difference_text + " ⬆" , "red"))
                        excel_list.append((__diff  , "red"))
                        curr_list.append((row4_text+ " ⬆" , "red"))
                        excel_list.append((__rel , "red"))
                    elif sign>0:
                        curr_list.append((difference_text + " ⬇" ,"green"))
                        excel_list.append((__diff ,"green"))
                        curr_list.append((row4_text + " ⬇", "green"))
                        excel_list.append((__rel , "green"))
                    else:
                        curr_list.append((difference_text,__diff))
                        excel_list.append((__diff))
                        curr_list.append((row4_text,__rel))
                        excel_list.append((__rel))

                except Exception as e:
                    print("Error:", type(e).__name__, "-", str(e))
                    curr_list.append(('-'))
                    excel_list.append(('-'))
                    curr_list.append(('-'))
                    excel_list.append(('-'))

                data_dict["body"].append(curr_list)
                excel_dict.append(excel_list)

        return data_dict,excel_dict

    def get_overall_utilization(self,data):
        data_dict=dict()
        excel_dict=[]
        data_dict["header"] = [ "Metric",self.prev_build,self.build, "Delta" ]
        data_dict["body"] = []
        old_data = data['overall_previous']
        new_data = data['overall_current']
        tag = data['tag']
        unit = data['unit']
        data_dict["title"] = f"Comparision of Overall {tag} utilization"

        for node_type in new_data:
            curr_list=[]
            excel_list=[]
            curr_list.append((f"Average {tag} used by {node_type}" ))
            excel_list.append((f"Average {tag} used by {node_type}" ))
            try:
                curr_list.append((f"{old_data[node_type][unit]:.2f} {unit}"))
            except:
                curr_list.append(('-'))
            curr_list.append((f"{new_data[node_type][unit]:.2f} {unit}"))
            excel_list.append((round(new_data[node_type][unit],2)))
            try:
                diff = old_data[node_type][unit]-new_data[node_type][unit]
                difference_text = f"{abs(diff *100/ old_data[node_type][unit]):.2f}% ({abs(diff):.2f} {unit})"
                if diff<0:
                    curr_list.append((difference_text + " ⬆" , "red"))
                    excel_list.append((difference_text, "red"))
                elif diff>0:
                    curr_list.append((difference_text + " ⬇" ,"green"))
                    excel_list.append((difference_text,"green"))
                else:
                    curr_list.append((difference_text))
                    excel_list.append((difference_text))
            except Exception as e:
                print("Error:", type(e).__name__, "-", str(e))
                curr_list.append(('-'))
                excel_list.append(('-'))
            data_dict["body"].append(curr_list)
            excel_dict.append(excel_list)
        return data_dict,excel_dict

    def make_comparisions(self):
        past_file_exists=False
        if os.path.exists(self.fetch_prev_build_data_path):
            with open(self.fetch_prev_build_data_path, 'r') as file:
                previous_build_data = json.load(file)
            past_file_exists=True
            self.prev_build = previous_build_data["details"]["build"]
        else:
            previous_build_data={}
        previous_build_data = defaultdict(lambda : 0, previous_build_data)
        print("Extracting current build data ...")

        memory_data =  {'previous' : previous_build_data["memory"],
                        'overall_previous' :previous_build_data["overall_memory"],
                        'tag':memory_tag,
                        'unit':memory_unit}

        cpu_data   =   {'previous' : previous_build_data["cpu"],
                        'overall_previous' :previous_build_data["overall_cpu"],
                        'tag':cpu_tag,
                        "unit":cpu_unit}

        memory_data["current"] , memory_data["overall_current"] = self.extract_data(memory_queries,memory_tag,memory_unit)
        cpu_data["current"] , cpu_data["overall_current"] = self.extract_data(cpu_queries,cpu_tag,cpu_unit)

        container_memory_data = { 'previous' : previous_build_data["container_wise_memory"],
                                'current' : self.extract_container_data(container_memory_queries,memory_tag,memory_unit),
                                'tag':memory_tag,
                                'unit':memory_unit}

        container_cpu_data = {'previous' : previous_build_data["container_wise_cpu"],
                            'current' : self.extract_container_data(container_cpu_queries,cpu_tag,cpu_unit),
                            'tag':cpu_tag,
                            "unit":cpu_unit}

        container_complete_usage = {
            'previous':previous_build_data["complete_usage"],
            "current" : self.complete_usage
        }
        with open(self.save_current_build_data_path, 'r') as file:
            current_build_data = json.load(file)

        current_build_data["overall_memory"]=memory_data["overall_current"]
        current_build_data["overall_cpu"]=cpu_data["overall_current"]
        current_build_data["memory"]=memory_data["current"]
        current_build_data["cpu"]=cpu_data["current"]
        current_build_data["container_wise_memory"] = container_memory_data["current"]
        current_build_data["container_wise_cpu"] = container_cpu_data["current"]
        current_build_data["complete_usage"] = self.complete_usage

        with open(self.save_current_build_data_path, 'w') as file:
            json.dump(current_build_data, file, indent=4)  # indent for pretty formatting

        sheets = {
                "memory trend":self.get_average_utilization(memory_data),
                  "cpu trend":self.get_average_utilization(cpu_data),
                  "container-wise memory trend":self.get_container_utilization(container_memory_data),
                  "container-wise cpu trend":self.get_container_utilization(container_cpu_data),
                  "overall mem":self.get_overall_utilization(memory_data),
                  "overall cpu":self.get_overall_utilization(cpu_data),
                  "complete usage":self.get_complete_container_utilization(container_complete_usage),
                  }
        excel_sheets={}
        table_sheets={}
        for key,val in sheets.items():
            table_data,excel_data=val
            excel_sheets[key] = excel_data
            table_sheets[key] = table_data

        self.doc.add_heading('Average Resource Utilization', level=2)
        self.doc=add_table(self.doc,    table_sheets["memory trend"])
        self.doc=add_table(self.doc,    table_sheets["cpu trend"])

        self.doc.add_heading('Overall Usages', level=2)
        self.doc=add_table(self.doc,    table_sheets["overall mem"])
        self.doc=add_table(self.doc,    table_sheets["overall cpu"])

        #---- container wise -----------
        self.container_doc = Document()
        self.container_doc.add_heading('Container-wise Resource Usage Report', level=0)

        self.container_doc = add_table(self.container_doc , table_sheets["container-wise memory trend"])
        self.container_doc = add_table(self.container_doc , table_sheets["container-wise cpu trend"])
        if past_file_exists:
            self.container_doc.add_heading(f'Usage Increase/decrease Summary', level=2)
            for table in self.get_summary_dict():
                self.container_doc = add_table(self.container_doc , table)

        self.container_doc = add_table(self.container_doc , table_sheets["complete usage"])
        self.container_doc.save(self.overall_comparisions_docx_path)
        #-------------------------------

        #update excel
        excel_update(excel_sheets , self.previous_excel_file_path ,self.current_excel_file_path , build = self.build)
        return self.doc


from __future__ import print_function

from bisect import bisect_left
from itertools import chain, islice
import operator

try:
    from collections.abc import MutableSet
except ImportError:
    from collections import MutableSet

try:
    from .typeutils import make_sentinel
    _MISSING = make_sentinel(var_name='_MISSING')
except ImportError:
    _MISSING = object()

__all__ = ['IndexedSet', 'complement']

_COMPACTION_FACTOR = 8

# TODO: inherit from set()
# TODO: .discard_many(), .remove_many()
# TODO: raise exception on non-set params?
# TODO: technically reverse operators should probably reverse the
# order of the 'other' inputs and put self last (to try and maintain
# insertion order)

class IndexedSet(MutableSet):
    """``IndexedSet`` is a :class:`collections.MutableSet` that maintains
    insertion order and uniqueness of inserted elements. It's a hybrid
    type, mostly like an OrderedSet, but also :class:`list`-like, in
    that it supports indexing and slicing.

    Args:
        other (iterable): An optional iterable used to initialize the set.

    >>> x = IndexedSet(list(range(4)) + list(range(8)))
    >>> x
    IndexedSet([0, 1, 2, 3, 4, 5, 6, 7])
    >>> x - set(range(2))
    IndexedSet([2, 3, 4, 5, 6, 7])
    >>> x[-1]
    7
    >>> fcr = IndexedSet('freecreditreport.com')
    >>> ''.join(fcr[:fcr.index('.')])
    'frecditpo'

    Standard set operators and interoperation with :class:`set` are
    all supported:

    >>> fcr & set('cash4gold.com')
    IndexedSet(['c', 'd', 'o', '.', 'm'])

    As you can see, the ``IndexedSet`` is almost like a ``UniqueList``,
    retaining only one copy of a given value, in the order it was
    first added. For the curious, the reason why IndexedSet does not
    support setting items based on index (i.e, ``__setitem__()``),
    consider the following dilemma::

      my_indexed_set = [A, B, C, D]
      my_indexed_set[2] = A

    At this point, a set requires only one *A*, but a :class:`list` would
    overwrite *C*. Overwriting *C* would change the length of the list,
    meaning that ``my_indexed_set[2]`` would not be *A*, as expected with a
    list, but rather *D*. So, no ``__setitem__()``.

    Otherwise, the API strives to be as complete a union of the
    :class:`list` and :class:`set` APIs as possible.
    """
    def __init__(self, other=None):
        self.item_index_map = dict()
        self.item_list = []
        self.dead_indices = []
        self._compactions = 0
        self._c_max_size = 0
        if other:
            self.update(other)

    # internal functions
    @property
    def _dead_index_count(self):
        return len(self.item_list) - len(self.item_index_map)

    def _compact(self):
        if not self.dead_indices:
            return
        self._compactions += 1
        dead_index_count = self._dead_index_count
        items, index_map = self.item_list, self.item_index_map
        self._c_max_size = max(self._c_max_size, len(items))
        for i, item in enumerate(self):
            items[i] = item
            index_map[item] = i
        del items[-dead_index_count:]
        del self.dead_indices[:]

    def _cull(self):
        ded = self.dead_indices
        if not ded:
            return
        items, ii_map = self.item_list, self.item_index_map
        if not ii_map:
            del items[:]
            del ded[:]
        elif len(ded) > 384:
            self._compact()
        elif self._dead_index_count > (len(items) / _COMPACTION_FACTOR):
            self._compact()
        elif items[-1] is _MISSING:  # get rid of dead right hand side
            num_dead = 1
            while items[-(num_dead + 1)] is _MISSING:
                num_dead += 1
            if ded and ded[-1][1] == len(items):
                del ded[-1]
            del items[-num_dead:]

    def _get_real_index(self, index):
        if index < 0:
            index += len(self)
        if not self.dead_indices:
            return index
        real_index = index
        for d_start, d_stop in self.dead_indices:
            if real_index < d_start:
                break
            real_index += d_stop - d_start
        return real_index

    def _get_apparent_index(self, index):
        if index < 0:
            index += len(self)
        if not self.dead_indices:
            return index
        apparent_index = index
        for d_start, d_stop in self.dead_indices:
            if index < d_start:
                break
            apparent_index -= d_stop - d_start
        return apparent_index

    def _add_dead(self, start, stop=None):
        # TODO: does not handle when the new interval subsumes
        # multiple existing intervals
        dints = self.dead_indices
        if stop is None:
            stop = start + 1
        cand_int = [start, stop]
        if not dints:
            dints.append(cand_int)
            return
        int_idx = bisect_left(dints, cand_int)
        dint = dints[int_idx - 1]
        d_start, d_stop = dint
        if start <= d_start <= stop:
            dint[0] = start
        elif start <= d_stop <= stop:
            dint[1] = stop
        else:
            dints.insert(int_idx, cand_int)
        return

    # common operations (shared by set and list)
    def __len__(self):
        return len(self.item_index_map)

    def __contains__(self, item):
        return item in self.item_index_map

    def __iter__(self):
        return (item for item in self.item_list if item is not _MISSING)

    def __reversed__(self):
        item_list = self.item_list
        return (item for item in reversed(item_list) if item is not _MISSING)

    def __repr__(self):
        return '%s(%r)' % (self.__class__.__name__, list(self))

    def __eq__(self, other):
        if isinstance(other, IndexedSet):
            return len(self) == len(other) and list(self) == list(other)
        return set(self) == set(other)

    @classmethod
    def from_iterable(cls, it):
        "from_iterable(it) -> create a set from an iterable"
        return cls(it)

    # set operations
    def add(self, item):
        "add(item) -> add item to the set"
        if item not in self.item_index_map:
            self.item_index_map[item] = len(self.item_list)
            self.item_list.append(item)

    def remove(self, item):
        "remove(item) -> remove item from the set, raises if not present"
        try:
            didx = self.item_index_map.pop(item)
        except KeyError:
            raise KeyError(item)
        self.item_list[didx] = _MISSING
        self._add_dead(didx)
        self._cull()

    def discard(self, item):
        "discard(item) -> discard item from the set (does not raise)"
        try:
            self.remove(item)
        except KeyError:
            pass

    def clear(self):
        "clear() -> empty the set"
        del self.item_list[:]
        del self.dead_indices[:]
        self.item_index_map.clear()

    def isdisjoint(self, other):
        "isdisjoint(other) -> return True if no overlap with other"
        iim = self.item_index_map
        for k in other:
            if k in iim:
                return False
        return True

    def issubset(self, other):
        "issubset(other) -> return True if other contains this set"
        if len(other) < len(self):
            return False
        for k in self.item_index_map:
            if k not in other:
                return False
        return True

    def issuperset(self, other):
        "issuperset(other) -> return True if set contains other"
        if len(other) > len(self):
            return False
        iim = self.item_index_map
        for k in other:
            if k not in iim:
                return False
        return True

    def union(self, *others):
        "union(*others) -> return a new set containing this set and others"
        return self.from_iterable(chain(self, *others))

    def iter_intersection(self, *others):
        "iter_intersection(*others) -> iterate over elements also in others"
        for k in self:
            for other in others:
                if k not in other:
                    break
            else:
                yield k
        return

    def intersection(self, *others):
        "intersection(*others) -> get a set with overlap of this and others"
        if len(others) == 1:
            other = others[0]
            return self.from_iterable(k for k in self if k in other)
        return self.from_iterable(self.iter_intersection(*others))

    def iter_difference(self, *others):
        "iter_difference(*others) -> iterate over elements not in others"
        for k in self:
            for other in others:
                if k in other:
                    break
            else:
                yield k
        return

    def difference(self, *others):
        "difference(*others) -> get a new set with elements not in others"
        if len(others) == 1:
            other = others[0]
            return self.from_iterable(k for k in self if k not in other)
        return self.from_iterable(self.iter_difference(*others))

    def symmetric_difference(self, *others):
        "symmetric_difference(*others) -> XOR set of this and others"
        ret = self.union(*others)
        return ret.difference(self.intersection(*others))

    __or__  = __ror__  = union
    __and__ = __rand__ = intersection
    __sub__ = difference
    __xor__ = __rxor__ = symmetric_difference

    def __rsub__(self, other):
        vals = [x for x in other if x not in self]
        return type(other)(vals)

    # in-place set operations
    def update(self, *others):
        "update(*others) -> add values from one or more iterables"
        if not others:
            return  # raise?
        elif len(others) == 1:
            other = others[0]
        else:
            other = chain(others)
        for o in other:
            self.add(o)

    def intersection_update(self, *others):
        "intersection_update(*others) -> discard self.difference(*others)"
        for val in self.difference(*others):
            self.discard(val)

    def difference_update(self, *others):
        "difference_update(*others) -> discard self.intersection(*others)"
        if self in others:
            self.clear()
        for val in self.intersection(*others):
            self.discard(val)

    def symmetric_difference_update(self, other):  # note singular 'other'
        "symmetric_difference_update(other) -> in-place XOR with other"
        if self is other:
            self.clear()
        for val in other:
            if val in self:
                self.discard(val)
            else:
                self.add(val)

    def __ior__(self, *others):
        self.update(*others)
        return self

    def __iand__(self, *others):
        self.intersection_update(*others)
        return self

    def __isub__(self, *others):
        self.difference_update(*others)
        return self

    def __ixor__(self, *others):
        self.symmetric_difference_update(*others)
        return self

    def iter_slice(self, start, stop, step=None):
        "iterate over a slice of the set"
        iterable = self
        if start is not None:
            start = self._get_real_index(start)
        if stop is not None:
            stop = self._get_real_index(stop)
        if step is not None and step < 0:
            step = -step
            iterable = reversed(self)
        return islice(iterable, start, stop, step)

    # list operations
    def __getitem__(self, index):
        try:
            start, stop, step = index.start, index.stop, index.step
        except AttributeError:
            index = operator.index(index)
        else:
            iter_slice = self.iter_slice(start, stop, step)
            return self.from_iterable(iter_slice)
        if index < 0:
            index += len(self)
        real_index = self._get_real_index(index)
        try:
            ret = self.item_list[real_index]
        except IndexError:
            raise IndexError('IndexedSet index out of range')
        return ret

    def pop(self, index=None):
        "pop(index) -> remove the item at a given index (-1 by default)"
        item_index_map = self.item_index_map
        len_self = len(item_index_map)
        if index is None or index == -1 or index == len_self - 1:
            ret = self.item_list.pop()
            del item_index_map[ret]
        else:
            real_index = self._get_real_index(index)
            ret = self.item_list[real_index]
            self.item_list[real_index] = _MISSING
            del item_index_map[ret]
            self._add_dead(real_index)
        self._cull()
        return ret

    def count(self, val):
        "count(val) -> count number of instances of value (0 or 1)"
        if val in self.item_index_map:
            return 1
        return 0

    def reverse(self):
        "reverse() -> reverse the contents of the set in-place"
        reversed_list = list(reversed(self))
        self.item_list[:] = reversed_list
        for i, item in enumerate(self.item_list):
            self.item_index_map[item] = i
        del self.dead_indices[:]

    def sort(self, **kwargs):
        "sort() -> sort the contents of the set in-place"
        sorted_list = sorted(self, **kwargs)
        if sorted_list == self.item_list:
            return
        self.item_list[:] = sorted_list
        for i, item in enumerate(self.item_list):
            self.item_index_map[item] = i
        del self.dead_indices[:]

    def index(self, val):
        "index(val) -> get the index of a value, raises if not present"
        try:
            return self._get_apparent_index(self.item_index_map[val])
        except KeyError:
            cn = self.__class__.__name__
            raise ValueError('%r is not in %s' % (val, cn))

def complement(wrapped):
    """Given a :class:`set`, convert it to a **complement set**.

    Whereas a :class:`set` keeps track of what it contains, a
    `complement set
    <https://en.wikipedia.org/wiki/Complement_(set_theory)>`_ keeps
    track of what it does *not* contain. For example, look what
    happens when we intersect a normal set with a complement set::

    >>> list(set(range(5)) & complement(set([2, 3])))
    [0, 1, 4]

    We get the everything in the left that wasn't in the right,
    because intersecting with a complement is the same as subtracting
    a normal set.

    Args:
        wrapped (set): A set or any other iterable which should be
           turned into a complement set.

    All set methods and operators are supported by complement sets,
    between other :func:`complement`-wrapped sets and/or regular
    :class:`set` objects.

    Because a complement set only tracks what elements are *not* in
    the set, functionality based on set contents is unavailable:
    :func:`len`, :func:`iter` (and for loops), and ``.pop()``. But a
    complement set can always be turned back into a regular set by
    complementing it again:

    >>> s = set(range(5))
    >>> complement(complement(s)) == s
    True

    .. note::

       An empty complement set corresponds to the concept of a
       `universal set <https://en.wikipedia.org/wiki/Universal_set>`_
       from mathematics.

    Complement sets by example
    ^^^^^^^^^^^^^^^^^^^^^^^^^^

    Many uses of sets can be expressed more simply by using a
    complement. Rather than trying to work out in your head the proper
    way to invert an expression, you can just throw a complement on
    the set. Consider this example of a name filter::

        >>> class NamesFilter(object):
        ...    def __init__(self, allowed):
        ...        self._allowed = allowed
        ...
        ...    def filter(self, names):
        ...        return [name for name in names if name in self._allowed]
        >>> NamesFilter(set(['alice', 'bob'])).filter(['alice', 'bob', 'carol'])
        ['alice', 'bob']

    What if we want to just express "let all the names through"?

    We could try to enumerate all of the expected names::

       ``NamesFilter({'alice', 'bob', 'carol'})``

    But this is very brittle -- what if at some point over this
    object is changed to filter ``['alice', 'bob', 'carol', 'dan']``?

    Even worse, what about the poor programmer who next works
    on this piece of code?  They cannot tell whether the purpose
    of the large allowed set was "allow everything", or if 'dan'
    was excluded for some subtle reason.

    A complement set lets the programmer intention be expressed
    succinctly and directly::

       NamesFilter(complement(set()))

    Not only is this code short and robust, it is easy to understand
    the intention.

    """
    if type(wrapped) is _ComplementSet:
        return wrapped.complemented()
    if type(wrapped) is frozenset:
        return _ComplementSet(excluded=wrapped)
    return _ComplementSet(excluded=set(wrapped))

def _norm_args_typeerror(other):
    '''normalize args and raise type-error if there is a problem'''
    if type(other) in (set, frozenset):
        inc, exc = other, None
    elif type(other) is _ComplementSet:
        inc, exc = other._included, other._excluded
    else:
        raise TypeError('argument must be another set or complement(set)')
    return inc, exc

def _norm_args_notimplemented(other):
    '''normalize args and return NotImplemented (for overloaded operators)'''
    if type(other) in (set, frozenset):
        inc, exc = other, None
    elif type(other) is _ComplementSet:
        inc, exc = other._included, other._excluded
    else:
        return NotImplemented, None
    return inc, exc

class _ComplementSet(object):
    """
    helper class for complement() that implements the set methods
    """
    __slots__ = ('_included', '_excluded')

    def __init__(self, included=None, excluded=None):
        if included is None:
            assert type(excluded) in (set, frozenset)
        elif excluded is None:
            assert type(included) in (set, frozenset)
        else:
            raise ValueError('one of included or excluded must be a set')
        self._included, self._excluded = included, excluded

    def __repr__(self):
        if self._included is None:
            return 'complement({0})'.format(repr(self._excluded))
        return 'complement(complement({0}))'.format(repr(self._included))

    def complemented(self):
        '''return a complement of the current set'''
        if type(self._included) is frozenset or type(self._excluded) is frozenset:
            return _ComplementSet(included=self._excluded, excluded=self._included)
        return _ComplementSet(
            included=None if self._excluded is None else set(self._excluded),
            excluded=None if self._included is None else set(self._included))

    __invert__ = complemented

    def complement(self):
        '''convert the current set to its complement in-place'''
        self._included, self._excluded = self._excluded, self._included

    def __contains__(self, item):
        if self._included is None:
            return not item in self._excluded
        return item in self._included

    def add(self, item):
        if self._included is None:
            if item in self._excluded:
                self._excluded.remove(item)
        else:
            self._included.add(item)

    def remove(self, item):
        if self._included is None:
            self._excluded.add(item)
        else:
            self._included.remove(item)

    def pop(self):
        if self._included is None:
            raise NotImplementedError  # self.missing.add(random.choice(gc.objects()))
        return self._included.pop()

    def intersection(self, other):
        try:
            return self & other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __and__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(included=inc - self._excluded)
            else:  # - -
                return _ComplementSet(excluded=self._excluded.union(other._excluded))
        else:
            if inc is None:  # + -
                return _ComplementSet(included=exc - self._included)
            else:  # + +
                return _ComplementSet(included=self._included.intersection(inc))

    __rand__ = __and__

    def __iand__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                self._excluded = inc - self._excluded  # TODO: do this in place?
            else:  # - -
                self._excluded |= exc
        else:
            if inc is None:  # + -
                self._included -= exc
                self._included, self._excluded = None, self._included
            else:  # + +
                self._included &= inc
        return self

    def union(self, other):
        try:
            return self | other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __or__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(excluded=self._excluded - inc)
            else:  # - -
                return _ComplementSet(excluded=self._excluded.intersection(exc))
        else:
            if inc is None:  # + -
                return _ComplementSet(excluded=exc - self._included)
            else:  # + +
                return _ComplementSet(included=self._included.union(inc))

    __ror__ = __or__

    def __ior__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                self._excluded -= inc
            else:  # - -
                self._excluded &= exc
        else:
            if inc is None:  # + -
                self._included, self._excluded = None, exc - self._included   # TODO: do this in place?
            else:  # + +
                self._included |= inc
        return self

    def update(self, items):
        if type(items) in (set, frozenset):
            inc, exc = items, None
        elif type(items) is _ComplementSet:
            inc, exc = items._included, items._excluded
        else:
            inc, exc = frozenset(items), None
        if self._included is None:
            if exc is None:  # - +
                self._excluded &= inc
            else:  # - -
                self._excluded.discard(exc)
        else:
            if inc is None:  # + -
                self._included &= exc
                self._included, self._excluded = None, self._excluded
            else:  # + +
                self._included.update(inc)

    def discard(self, items):
        if type(items) in (set, frozenset):
            inc, exc = items, None
        elif type(items) is _ComplementSet:
            inc, exc = items._included, items._excluded
        else:
            inc, exc = frozenset(items), None
        if self._included is None:
            if exc is None:  # - +
                self._excluded.update(inc)
            else:  # - -
                self._included, self._excluded = exc - self._excluded, None
        else:
            if inc is None:  # + -
                self._included &= exc
            else:  # + +
                self._included.discard(inc)

    def symmetric_difference(self, other):
        try:
            return self ^ other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __xor__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(excluded=self._excluded - inc)
            else:  # - -
                return _ComplementSet(included=self._excluded.symmetric_difference(exc))
        else:
            if inc is None:  # + -
                return _ComplementSet(excluded=exc - self._included)
            else:  # + +
                return _ComplementSet(included=self._included.symmetric_difference(inc))

    __rxor__ = __xor__

    def symmetric_difference_update(self, other):
        inc, exc = _norm_args_typeerror(other)
        if self._included is None:
            if exc is None:  # - +
                self._excluded |= inc
            else:  # - -
                self._excluded.symmetric_difference_update(exc)
                self._included, self._excluded = self._excluded, None
        else:
            if inc is None:  # + -
                self._included |= exc
                self._included, self._excluded = None, self._included
            else:  # + +
                self._included.symmetric_difference_update(inc)

    def isdisjoint(self, other):
        inc, exc = _norm_args_typeerror(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return inc.issubset(self._excluded)
            else:  # - -
                return False
        else:
            if inc is None:  # + -
                return self._included.issubset(exc)
            else:  # + +
                return self._included.isdisjoint(inc)

    def issubset(self, other):
        '''everything missing from other is also missing from self'''
        try:
            return self <= other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __le__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return False
            else:  # - -
                return self._excluded.issupserset(exc)
        else:
            if inc is None:  # + -
                return self._included.isdisjoint(exc)
            else:  # + +
                return self._included.issubset(inc)

    def __lt__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return False
            else:  # - -
                return self._excluded > exc
        else:
            if inc is None:  # + -
                return self._included.isdisjoint(exc)
            else:  # + +
                return self._included < inc

    def issuperset(self, other):
        '''everything missing from self is also missing from super'''
        try:
            return self >= other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __ge__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return not self._excluded.intersection(inc)
            else:  # - -
                return self._excluded.issubset(exc)
        else:
            if inc is None:  # + -
                return False
            else:  # + +
                return self._included.issupserset(inc)

    def __gt__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return not self._excluded.intersection(inc)
            else:  # - -
                return self._excluded < exc
        else:
            if inc is None:  # + -
                return False
            else:  # + +
                return self._included > inc

    def difference(self, other):
        try:
            return self - other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __sub__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(excluded=self._excluded | inc)
            else:  # - -
                return _ComplementSet(included=exc - self._excluded)
        else:
            if inc is None:  # + -
                return _ComplementSet(included=self._included & exc)
            else:  # + +
                return _ComplementSet(included=self._included.difference(inc))

    def __rsub__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        # rsub, so the expression being evaluated is "other - self"
        if self._included is None:
            if exc is None:  # - +
                return _ComplementSet(included=inc & self._excluded)
            else:  # - -
                return _ComplementSet(included=self._excluded - exc)
        else:
            if inc is None:  # + -
                return _ComplementSet(excluded=exc | self._included)
            else:  # + +
                return _ComplementSet(included=inc.difference(self._included))

    def difference_update(self, other):
        try:
            self -= other
        except NotImplementedError:
            raise TypeError('argument must be another set or complement(set)')

    def __isub__(self, other):
        inc, exc = _norm_args_notimplemented(other)
        if inc is NotImplemented:
            return NotImplemented
        if self._included is None:
            if exc is None:  # - +
                self._excluded |= inc
            else:  # - -
                self._included, self._excluded = exc - self._excluded, None
        else:
            if inc is None:  # + -
                self._included &= exc
            else:  # + +
                self._included.difference_update(inc)
        return self

    def __eq__(self, other):
        return (
            type(self) is type(other)
            and self._included == other._included
            and self._excluded == other._excluded) or (
            type(other) in (set, frozenset) and self._included == other)

    def __hash__(self):
        return hash(self._included) ^ hash(self._excluded)

    def __len__(self):
        if self._included is not None:
            return len(self._included)
        raise NotImplementedError('complemented sets have undefined length')

    def __iter__(self):
        if self._included is not None:
            return iter(self._included)
        raise NotImplementedError('complemented sets have undefined contents')

    def __bool__(self):
        if self._included is not None:
            return bool(self._included)
        return True

    __nonzero__ = __bool__  # py2 compat


import os
from IPython.display import HTML, Javascript, display

def show_navbar():
    # Display the base HTML container for the menu
    display(HTML('

'))

    # Import the menu styles
    menu_path = os.path.join(os.path.dirname(__file__), 'static/menu.css')
    menu_file = open(menu_path, 'r')
    menu_css = menu_file.read()
    display(HTML(f''))

    # Build the menu with Javascript
    menu_path = os.path.join(os.path.dirname(__file__), 'static/menu.js')
    menu_file = open(menu_path, 'r')
    menu_js = menu_file.read()
    display(Javascript(menu_js))