import datetime
import pytest
import subprocess
import csv
import os
import time
import Classes.Logging
from selenium.webdriver.common.keys import Keys
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from os import path
from selenium.webdriver.common.action_chains import ActionChains
#from webdriver_manager.chrome import ChromeDriverManager
#from webdriver_manager.firefox import GeckoDriverManager

# Basisadresse zur Jira
# dient zum erzeugen der korrekten URL zum Akzaptanzkriterium
jira_base_url = "https://jira.lak.local:8443/browse/"

# Anzahl der Kopfzeilen innerhalb einer GeVo/Test Datei
kopfzeilen = 5

# Web Applikation URL
download_target_path = 'http://www.seleniumeasy.com/test/basic-first-form-demo.html'
#
test_browser = "ch"
# Browser Paths
ChromePath = 'Browser/Chrome/GoogleChromePortable.exe'
FirefoxPath = 'Browser/Chrome/FirefoxPortable.exe'
# Webdriver Paths
ChromeDriverPath = 'Browser/Chrome/chromedriver'
FirefoxDriverPath = 'Browser/Firefox/geckodriver'

# Ablageort für GeVo Dateien
gevo_ordner = "Tests/GeVos/"

# Name der Übersichtsdatei
name_overview_file = "Testlauf-Übersicht.md.html"
# Steuert ob pro Tag mehre Übersichtdateien möglich sind
# True = Übersichtsdatei wird mit jeder Ausführung Überschrieben
# False = Mit jeder Ausführung wird eine Datei, auf Basis der Variablen "name_overview_file" mit einem Index (erster freier Index) erstellt
#         Bsp. Testlauf-Übersicht_1.md.html
overview_single_file = True

# zusätzliche Testfallspezifische Testdaten, welche zur Laufzeit des Tests durch vom Nutzer verwendete Aktionen
# erzeugt/geändert/verwendet werden
testfall_variablen = {}
waitcount = 20

# Im Testablauf können Testdaten referenziert werden.
# mittels der folgenden Identifier werden die Referenzen ermittelt
testdaten_identifier_anfang = "[@@"
testdaten_identifier_ende = "@@]"
testdaten_identifier_ende_int = "@@:int]"
testdaten_identifier_ende_float = "@@:float]"

###### Übergebenes Skript ausführen #######
def testfaelle_ausfuehren(self, url, testliste, zielordner, browser, headless, loglevel):
    # global die browser variable setzen
    global test_browser
    test_browser = browser

    # Tabelle für die, nach Abschluss des Testlaufs, erzeugte Übersicht
    test_overview = []
    test_overview.append("Testfallame,Testfall-Ordner,Akzeptanzkriterium,TF-Gesamt,TF-Erfolgreich,TF-Fehlerhaft")
    # prüfen ob der übergebene Zielordner existiert und tatsächlich ein Verzeichnis ist
    if path.exists(zielordner) and path.isdir(zielordner):
        datum = datetime.datetime.now().strftime("%Y%m%d")
        zielordner = zielordner + datum + "\\"
        Classes.Logging.logfile_overview_anlegen(zielordner, name_overview_file, overview_single_file)
        for i in range(len(testliste)):
            pfad = testliste[i]
            # Dokumentenheader für Logdatei erzeugen
            # Prüfen, ob die Übergebene Skriptdatei existiert
            if path.exists(pfad):
                resultat = testskript_ausfuehren(self, url, pfad, zielordner, browser, headless, loglevel)
                # Liste aller Testergebnisse um das durchgeführte erweitern
                test_overview.append(resultat)
            else:
                print(pfad + " - Skript nicht gefunden")
        Classes.Logging.logfile_overview_abschließen(test_overview)
    else:
        # Classes.Logging.fehler_nachricht_anlegen("Fehler", "Zielordner wurde nicht gefunden - Testlauf wurde abgebrochen")
        pytest.fail("Fehler beim ausführen der Testfälle - Zielordner nicht gefunden\n->'" + zielordner + "'")

##### Testskript ausführen #####
def testskript_ausfuehren(self, url, pfad, zielordner, browser, headless, loglevel):
    # Variablen zur Erstellung der Test-Übersicht
    anzahl_testfälle = 0
    anzahl_erfolgreich = 0
    anzahl_fehlerhaft = 0
    testfall_akzeptanzkriterium = ""

    # Testfall Datei einlesen
    aktionsliste, pfad, tablemeta, tablesteps, tf_name, testfall_akzeptanzkriterium = datei_auslesen(pfad, "testfall")
    # Testdaten einlesen
    ## den Pfad für die Testdatendatei erzeugen
    testdatenpfad = (path.dirname(path.normpath(pfad)) + "\\Testdaten\\" + path.basename(path.normpath(pfad))).replace(".tc", ".csv")
    tablemeta.append("Testdaten-Datei,[Datei](file:/" + testdatenpfad + ")")
    ## Prüfen, ob der erzeugte Pfad auch existiert
    if not path.exists(testdatenpfad):
        #Classes.Logging.fliesstext_eintrag(testdatenpfad)
        #Classes.Logging.fehler_nachricht_anlegen("Testdaten Datei nicht gefunden", "Es wurde keine Testdatendatei
        #unter dem erwarteten Pfad ("+testdatenpfad+") gefunden")
        pytest.fail("Testdatendatei nicht gefunden")
        return
    ## aus dem erzeugten Pfad/Testdaten-Datei wird ein Data-Dictionary erzeugt
    testdaten_dict = dict_aus_datei_erzeugen(testdatenpfad)
    #pprint(testdaten_dict[0])
    # Testlauf für
    t = 0
    ## Uhrzeit der Testdurchführung wird gesichert (dient zur Ablage der Testablauf-Dokumentation)
    result = 0
    uhrzeit = datetime.datetime.now().strftime("%H_%M_%S")
    for t in range(len(testdaten_dict)):
        self.driver, log_ordner = test_vorbereiten(self, headless, browser, url, zielordner, loglevel, uhrzeit, tf_name, tablemeta, tablesteps, t+1)
        if self.driver is None:
            test_abschliessen(result)
            continue;
        for i in range(len(aktionsliste)):
            # Kopfzeile wird übersprungen
            # Eintrag innerhalb der CSV wird gesplitted und Variablen zugewiesen
            if aktionsliste[i] == "":
                continue
            splitted = aktionsliste[i].split(";")
            if len(splitted) < 5:
                nachricht = "Die Aktion in der **Zeile " + str(i + 1) + "** hat nicht genügend Parameter"
                print (nachricht)
                Classes.Logging.fehler_nachricht_anlegen("Fehler in Testdurchführungs-Datei (*.tc)",nachricht)
                return 0
            art = splitted[0].lower()
            aktion = splitted[1].lower()
            identifier = splitted[2].lower()
            element = splitted[3]
            parameter = splitted[4]
            optional = False
            if len(splitted) > 5:
                if splitted[5].lower() == "optional":
                        optional = True
            result = 1
            # sofern ein Parameter Identifikator für die Testdaten übergeben wurde, so wird dieser ersetzt
            # Bsp. Bei Buttons wird kein weiterer Parameter erwartet, somit wäre "parameter" in diesem fall ""
            if art == "steuerung":
                result = aktion_ausfuehren(self.driver, aktion, identifier, element, parameter, testdaten_dict, t, optional)
            elif art == "gevo":
                result = gevo_ausfuehren(self.driver, aktion, testdaten_dict, t)
            else:
                result = 0
                break
            if result == 0:
                break
        if result == 0:
            anzahl_fehlerhaft = anzahl_fehlerhaft + 1
        else:
            anzahl_erfolgreich = anzahl_erfolgreich + 1
        test_abschliessen(result)
        # die Testfall Variablen werden nach jedem Testlauf zurückgesetzt
        # Stellt sicher, dass für jeden Testlauf die Variablen vor ihrer Verwendung neu angelegt werden
        global testfall_variablen
        testfall_variablen = {}
        t = t + 1
    anzahl_testfälle = len(testdaten_dict)
    ### Um eine Testablaufsübersicht zu erstellen wird pro Testfall eine Übersicht über
    ### die durchgeführten Testläufe dokumentiert
    return tf_name + "," + log_ordner + "," + testfall_akzeptanzkriterium + "," + str(anzahl_testfälle) + "," + \
           str(anzahl_erfolgreich) + "," + str(anzahl_fehlerhaft)

###### GeVo ausführen
def gevo_ausfuehren(driver, aktion, testdaten_dict, tesfall_von_testdaten_dict):
    #### 0. GeVo Informationen in Log-Schreiben.
    pfad = gevo_ordner + aktion + ".gevo"
    if not path.exists(pfad):
        print("Pfad nicht gefunden - " + pfad)
        Classes.Logging.fehler_nachricht_anlegen(
            "Fehler beim ausführen des GeVos","Der GeVo konnte nicht gefunden werden - **"
                                              + pfad + "**. Der Testlauf wurde abgebrochen")
        return 0
    gevo_aktionsliste, pfad, tablemeta, tablesteps, gevo_name, gevo_akzeptanzkriterium = datei_auslesen(pfad, "gevo")

    Classes.Logging.fliesstext_eintrag("Der GeVo-Ablauf mit folgenden Steps wurde geladen")
    Classes.Logging.dokumenten_header_anlegen(tablemeta, tablesteps, True)
    #### 1. GeVo-Datei öffnen
    #### 2. Schleife über GeVo-Aktionen
    #### 3. Pro Aktion "aktion_ausführen" aufrufen
    k = 0
    for k in range(len(gevo_aktionsliste)):
        gevo_splitted = gevo_aktionsliste[k].split(";")
        if len(gevo_splitted) < 5:
            nachricht = "Die Aktion in der **Zeile " + str(k + 1) + "** hat nicht genügend Parameter"
            print (nachricht)
            Classes.Logging.fehler_nachricht_anlegen("Fehler in GeVo-Datei (*.gevo)",nachricht)
            return 0

        gevo_aktion = gevo_splitted[1].lower()
        gevo_identifier = gevo_splitted[2].lower()
        gevo_element = gevo_splitted[3]
        gevo_parameter = gevo_splitted[4]
        optional = False
        if len(gevo_splitted) > 5:
            if gevo_splitted[5].lower() == "optional":
                    optional = True
        result = aktion_ausfuehren(driver, gevo_aktion, gevo_identifier, gevo_element, gevo_parameter,
                                   testdaten_dict, tesfall_von_testdaten_dict, optional)
        k = k + 1
        if result == 0:
            return 0
    return 1

# Liest die übergebene Datei gemäß der folgenden Vorgabe aus
# Zeile 1 = Name
# Zeile 2 = Beschreibung
# Zeile 3 = Akzeptanzkriterien (die ";"-separierten Akzeptanzkriterien werden mit der Jira Basis URL verkettet)
# Zeile 4 = <bisher nicht verwendet>
# Zeile 5 = <bisher nicht verwendet>
# Zeile 6ff. = Testablauf (wird in in Liste geschrieben)
def datei_auslesen(pfad, art):
    akzeptanzkriterium = ""
    name_aus_datei = ""
    tablemeta = []
    tablemeta.append("Feld,Wert")
    tablesteps = []
    aktionsliste = []
    tablesteps.append("Step #,typ,Aktion,Parameter")
    print(pfad)
    cnt = 1
    try:
        with open(pfad, 'r', encoding="UTF-8") as fp:
            line = fp.readline()
            cnt = 1
            while line:
                if cnt == 1:
                    name_aus_datei = line.strip()
                    text = "Testfall-Name"
                    if art.lower() == "gevo":
                        text = "GeVo-Name"
                    tablemeta.append(text + "," + line.strip())
                elif cnt == 2:
                    text = "Testfall-Beschreibung"
                    if art.lower() == "gevo":
                        text = "GeVo-Beschreibung"
                    tablemeta.append(text + "," + line.strip())
                elif cnt == 3:
                    if not line.strip() == "":
                        if ";" in line.strip():
                            splitted_akz = line.strip().split(";")
                            for i in range(len(splitted_akz)):
                                akzeptanzkriterium = akzeptanzkriterium + jira_base_url + \
                                                              splitted_akz[i] + "
"
                        else:
                            akzeptanzkriterium = jira_base_url + line.strip()
                        tablemeta.append(
                            "Akzeptanzkriterium(**Jira-#**)," + akzeptanzkriterium)
                    else:
                        tablemeta.append(
                            "Akzeptanzkriterium(**Jira-#**)," + "nicht hinterlegt")
                elif cnt > kopfzeilen:
                    if str(line.strip()) != "" and "---!>" not in str(line.strip()):
                        aktionsliste.append(str(line.strip()))
                        aktion = line.strip().split(";")
                        tablesteps.append(str(cnt - kopfzeilen) + "," + aktion[0] + "," + aktion[1] + "," + aktion[4])
                line = fp.readline()
                cnt += 1
    except Exception as e:
        nachricht = ""
        if art.lower() == "gevo":
            nachricht = "Fehler beim öffnen/auslesen der .gevo Datei " + pfad
            Classes.Logging.fehler_nachricht_anlegen("Fehler in GeVo-Datei (*.gevo)",nachricht)
        else:
            nachricht = "Fehler beim öffnen/auslesen der .tc Datei " + pfad
            Classes.Logging.fehler_nachricht_anlegen("Fehler in Testablauf-Datei (*.tc)",nachricht)
        zusatz = "**Datei**:" + pfad + "
**Zeile**: " + str(cnt) + "
**Meldung**:"
        Classes.Logging.exception_log("GUI-Automation -> Datei auslesen", zusatz + str(e))
        pytest.fail(nachricht)
    return aktionsliste, pfad, tablemeta, tablesteps, name_aus_datei, akzeptanzkriterium

###### aktion ausführen
def aktion_ausfuehren(driver, aktion, identifier, element, parameter, testdaten_dict, t, optional):
    type = ""
    par_alt = ""
    result = 0

    element, par_alt, parameter, erfolgreich = platzhalter_ersetzen(testdaten_dict, aktion, element, parameter, t)
    ### Prüfen ob die Platzhalter korrekt ersetzt wurden.
    if erfolgreich == False:
        return 0
    #############################
    ## Abbilden der Aktionen
    ## weitere Aktionen können mit weiteren elif-Blöcken und dazugehörigen Funktionen, analog zu
    #  den bereits bestehenden, ergänzt werden.
    if aktion == "element_klicken":
        #### Element Klicken
        Classes.Logging.ueberschrift_anlegen(2, 'Element Klicken')
        result = element_klicken(driver, identifier, element)
    elif aktion == "element_prüfen":
        #### Element pruefen
        Classes.Logging.ueberschrift_anlegen(2, 'Element prüfen')
        Classes.Logging.testdaten_ersetzung(par_alt, parameter, type)
        result = element_pruefen(driver, identifier, element, parameter)
    elif aktion == "element_enthält_wert_prüfen":
        Classes.Logging.ueberschrift_anlegen(2, 'Element auf enthaltenen Wert prüfen')
        result = element_enthaelt_wert_pruefen(driver, identifier, element, parameter)
    elif aktion == "element_enthält_wert_nicht":
        Classes.Logging.ueberschrift_anlegen(2, 'Wert in Element nicht vorhanden prüfen')
        result = element_enthaelt_wert_nicht(driver, identifier, element, parameter)
    elif aktion == "element_setzen":
        #### Eingabewert
        Classes.Logging.ueberschrift_anlegen(2, 'Element setzen')
        Classes.Logging.testdaten_ersetzung(par_alt, parameter, type)
        result = element_setzen(driver, identifier, element, parameter)
    elif aktion == "element_ueber_label_setzen":
        #### Eingabewert
        Classes.Logging.ueberschrift_anlegen(2, 'Element über Label setzen')
        Classes.Logging.testdaten_ersetzung(par_alt, parameter, type)
        result = element_ueber_label_setzen(driver, identifier, element, parameter)
    elif aktion == "variable_setzen":
        Classes.Logging.ueberschrift_anlegen(2, 'Variable setzen')
        result = variablen_setzen(driver, identifier, element, parameter)
    elif aktion == "wechseln_zu":
        Classes.Logging.ueberschrift_anlegen(2, 'Wechseln zu')
        result = wechseln_zu(driver, identifier, element, parameter)
    elif aktion == "schalter_setzen":
        Classes.Logging.ueberschrift_anlegen(2, 'Schalter setzen')
        result = schalter_setzen(driver, identifier, element, parameter)
    elif aktion == "schalter_prüfen":
        Classes.Logging.ueberschrift_anlegen(2, 'Schalter prüfen')
        result = schalter_prüfen(driver, identifier, element, parameter)
    elif aktion == "element_ein_ausklappen":
        Classes.Logging.ueberschrift_anlegen(2, 'Element ein-/ausgeklappt prüfen')
        result = element_ein_ausklappen(driver, identifier, element, parameter)
    elif aktion == "warten_auf_element":
        Classes.Logging.ueberschrift_anlegen(2, 'Warten auf Element')
        result = warten_auf_element(driver, identifier, element, parameter)
    elif aktion == "warte":
        Classes.Logging.ueberschrift_anlegen(2, 'Warte in Sekunden')
        result = warte(driver, identifier, parameter)
    elif aktion == "wartezeit_timeout_setzen":
        Classes.Logging.ueberschrift_anlegen(2, 'Wartezeit Timeout setzen')
        result = wartezeit_timeout_setzen(parameter)
    else:
        Classes.Logging.warnung_anlegen(
            "Die angegebene Aktion **" + aktion + "** ist nicht vorhanden. der Testschritt wurde übersprungen")
    ### Wenn das ein optionaler Schritt war wird das Ergebnis im Fehlerfall auf positiv gesetzt
    ### Fehlermeldungen im Log bleiben erhalten
    if optional == True and result == 0:
        Classes.Logging.warnung_anlegen("Aufgrund des Zusatzes **'Optional'** an der Aktion wurde "
                                        "der Fehler ignoriert. Der Testlauf wird fortgesetzt")
        result = 1
    return result
#####
def platzhalter_ersetzen(testdaten_dict, aktion, element, parameter, t):
    ### Platzhalter für Variablen und Testdaten ersetzen
    par_alt = ""
    if parameter != "" and testdaten_identifier_anfang in parameter and (
            testdaten_identifier_ende in parameter or testdaten_identifier_ende_int in parameter or testdaten_identifier_ende_float in parameter):
        par_alt = parameter
        type = ""
        if testdaten_identifier_ende_int in parameter:
            type = "intzahl"
        if testdaten_identifier_ende_float in parameter:
            type = "floatzahl"
        # Platzhalter werden aus dem Parameter entfernt
        temp_par = parameter.replace(testdaten_identifier_anfang, "").replace(testdaten_identifier_ende, "").replace(
            testdaten_identifier_ende_int, "").replace(testdaten_identifier_ende_float, "")
        try:
            # Sollte hinter jedem Eintrag zu "temp_par" innerhalb des Testdaten_Dics eine Liste stecken
            # so wird der Wert in einen String umgewandelt und die entsprechenden Platzhalter für eine Liste
            # entfernt.
            parameter = str(testdaten_dict[t][temp_par]).replace("['", "").replace("']", "")
            if type == "intzahl":
                parameter = int(parameter)
            if type == "floatzahl":
                parameter = float(parameter)
        except Exception as e:
            Classes.Logging.ueberschrift_anlegen(2, 'Testdatum')
            table = []
            table.append("Beschreibung,Wert")
            table.append("Aktion," + aktion)
            table.append("Parameter," + parameter)
            Classes.Logging.fliesstext_eintrag("Beim Auflösen der Referenz (auf Testdatum oder Testvariable) ist ein "
                                               "Fehler aufgetreten. Die folgende Aktion wurde nicht durchgeführt.")
            Classes.Logging.tabelle_anlegen(table)
            Classes.Logging.fehler_nachricht_anlegen(
                "Fehler beim lesen des Testdatum", "Das Testdatum **" + temp_par + "** konnte nicht gelesen werden, "
                "da sie wohlmöglich nicht in der Testdaten-Datei hinterlegt ist. Der Testlauf wurde abgebrochen")
            Classes.Logging.exception_log("GUI-Automation -> Platzhalter ersetzen", str(e))
            return  "", "", "", False
        if testdaten_identifier_anfang in element and (
            testdaten_identifier_ende in element or testdaten_identifier_ende_int in element or testdaten_identifier_ende_float in element):
            element = element.replace(par_alt, parameter)
        type = "testdaten"
    #### Variable innerhalb der Parameter mit dem hinterlegten Wert erstetzen
    elif aktion != "variable_setzen" and parameter != "" and "[var:" in parameter and "]" in parameter:
        par_alt = parameter
        temp_par = parameter.replace("[var:", "").replace("]", "")
        if temp_par in testfall_variablen:
            try:
                parameter = testfall_variablen[temp_par]
            except Exception as e:
                Classes.Logging.exception_log("GUI-Automation -> Platzhalter ersetzen", str(e))
                Classes.Logging.fehler_nachricht_anlegen("Fehler beim ersetzen der Variablen",
                                                     "Die variable **" + temp_par + "** konnte nicht ersetzt werden. "
                                                                                "Der Testlauf wurde abgebrochen")
                return "", "", "", False
        else:
            Classes.Logging.ueberschrift_anlegen(2, 'Testvariable')
            table = []
            table.append("Beschreibung,Wert")
            table.append("Aktion," + aktion)
            table.append("Parameter," + parameter)
            Classes.Logging.fliesstext_eintrag("Beim Auflösen der Referenz (auf Testdatum oder Testvariable) ist ein "
                                               "Fehler aufgetreten. Die folgende Aktion wurde nicht durchgeführt.")
            Classes.Logging.tabelle_anlegen(table)
            Classes.Logging.fehler_nachricht_anlegen("Fehler beim lesen der Variablen",
                                                     "Die variable **" + temp_par + "** konnte nicht gelesen werden. "
                                                                                "Der Testlauf wurde abgebrochen")
            # return 0 zum abbrechen des Testlaufs
            return "", "", "", False
        if element != "" and "[var:" in element and "]" in element:
            element = element.replace(par_alt, parameter)
    ## Wenn zum Beispiel bei der Aktion "element_klicken" der Linktext als Wert übergeben wird, wird dieser
    ## in der Variablen "element" gespeichert.
    if element == "":
        element = parameter

    return element, par_alt, parameter, True

#######
def test_vorbereiten(self, headless, browser, url, zielordner, loglevel, uhrzeit, tf_name, tablemeta, tablesteps, testfallnr):
    log_ordner = zielordner + tf_name.replace(" ", "_") + "\\" + uhrzeit
    zielordner = log_ordner + "\\TF_" + str(testfallnr) + "\\"
    global download_target_path
    download_target_path = zielordner
    if not path.exists(zielordner):
        os.makedirs(zielordner)
    # Logfile anlegen
    Classes.Logging.logfile_anlegen(zielordner, "log.md.html", loglevel)
    # Logfile Header anlegen
    Classes.Logging.dokumenten_header_anlegen(tablemeta, tablesteps)
    # Webdriver erzeugen
    self.driver = webbrowser_anlegen(browser, headless)
    if self.driver is None:
        Classes.Logging.fehler_nachricht_anlegen("Fehler", "Webdriver nicht gefunden")
        #pytest.fail("Webdriver nicht korrekt initiiert")
        return None,""
    try:
        self.driver.fullscreen_window()
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Test vorbereiten", "Fehler beim wechseln zum Vollbildmodus.
" + str(e))
        return None
    Classes.Logging.log_webdriver(self.driver, browser)
    result = 0
    result = URLOeffnen(self.driver, url)
    if result == 0:
        self.driver.close()
        self.driver.quit()
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim öffnen der Seite",
                                                 "Beim öffnen der Seite ist ein Fehler aufgetreten")
        #pytest.fail("Fehler beim öffnen der Seite")
        return None,""
    warten_auf_element(self.driver,"tag_name", "iframe")
    wechseln_zu(self.driver, "tag_name", "iframe", "frame")
    return self.driver, log_ordner

###### Am Ende der Testdurchführung wird der Testlauf abgeschlossen.
###### Die Log Datei wird abgeschlossen und die offenen Tasks werden gekillt
def test_abschliessen(result = 0):
    ## Da diese Funktion standardmäßig am Ende der Testfalldurchführung aufgerufen wird,
    ## muss geprüft werden. ob der Testabschluss bereits vorgenommen wurde. Im Falle eines
    ## erfolgreichen Test wird die Funktion nach vollständiger Beendigung des Testlaufs
    ## nicht erneut durchgeführt (standardmäßig würde der Aufrauf mit result == 0 (negativ) am
    ## Ende des Testlaufs erneut erfolgen
    if not path.exists(Classes.Logging.LogFilePath):
        return
    if Classes.Logging.testabschluss_pruefen() == 1:
        return
    Classes.Logging.ueberschrift_anlegen(1, "Testfall Abschluss")
    if result == 0:
        Classes.Logging.fehler_nachricht_anlegen("Fehler", "Der Testlauf war nicht erfolgreich")
    elif result == 1:
        Classes.Logging.erfolg_nachricht_anlegen("Der Testlauf war erfolgreich")
    Classes.Logging.ueberschrift_anlegen(2, "Prozesse bereinigen")
    if test_browser.lower() == "firefox" or test_browser.lower() == "ff":
        kill_tasks("firefox")
    elif test_browser.lower() == "chrome" or test_browser.lower() == "ch":
        kill_tasks("chrome")
    Classes.Logging.ueberschrift_anlegen(2, "Durchführunsdauer")
    Classes.Logging.logfile_abschliessen()

###### Webseite per Uebergebene URL oeffnen
def webbrowser_anlegen(browser, headless):
    # WebDriver Object
    driver = None
    try:
        if browser.lower() == "firefox" or browser.lower() == "ff":
            kill_tasks("firefox")
            if not path.exists(FirefoxDriverPath) and not path.exists(FirefoxPath):
                Classes.Logging.fehler_nachricht_anlegen(
                    "Fehler beim Erzeugen des WebDrivers", "Der WebDriver für Firefox konnte nicht erzeugt werden. "
                    "Überprüfen Sie die Ablage des portablen Browsers und GeckDriver")
                return None
            options = webdriver.FirefoxOptions()
            if headless == "1":
                options.add_argument('--headless')
            options.add_argument("--start-maximized")
            options.binary_location = FirefoxPath
            driver = webdriver.Firefox(executable_path=FirefoxDriverPath, options=options)
            #driver = webdriver.Firefox(executable_path=GeckoDriverManager.install(), options=options)
        elif browser.lower() == "chrome" or browser.lower() == "ch":
            # Sicherstellen, dass keine Prozesse von vorherigen läufen mehr offen sind
            kill_tasks("chrome")
            if not path.exists(ChromeDriverPath) and not path.exists(ChromePath):
                Classes.Logging.fehler_nachricht_anlegen(
                    "Fehler beim Erzeugen des WebDrivers", "Der WebDriver für Chrome konnte nicht erzeugt werden. "
                    "Überprüfen Sie die Ablage des portablen Browsers und ChromeDriver")
                return None
            options = webdriver.ChromeOptions()
            # Tests ohne Menuleiste
            options.add_argument("--no-sandbox")  # Bypass OS security model
            # if headless == 1:
            if headless == "1":
                options.add_argument('--headless')
            # options.add_argument("--kiosk")
            # options.add_argument('--disable-extensions')
            options.add_argument("--disable-gpu")
            options.add_argument("--remote-debugging-port=9222")
            options.add_argument("--disable-dev-shm-usage")  # overcome limited resource problems
            # options.add_argument("--start-maximized")
            options.add_argument("--silent")
            options.add_argument("--disable-infobars")
            prefs = {"profile.default_content_settings.popups": 0,
                     "download.default_directory": download_target_path,
                     "directory_upgrade": True}
            options.add_experimental_option("prefs", prefs)
            options.binary_location = ChromePath

            driver = webdriver.Chrome(executable_path=ChromeDriverPath, options=options)
            #driver = webdriver.Chrome(executable_path=ChromeDriverManager.install(), options=options)
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Webbrowser anlegen", "Fehler beim anlegen des Webdrivers
" + str(e))
        return None
    return driver

###### beendet alle Tasks für den übergebenen Browser
def kill_tasks(b):
    command_1 = ""
    command_2 = ""
    ausgabe_err_1 = ""
    ausgabe_1 = ""
    ausgabe_err_2 = ""
    ausgabe_2 = ""
    if b == "chrome":
        command_1 = 'TASKKILL /f /IM CHROMEDRIVER.EXE'
        proc = subprocess.Popen(command_1,
                                shell=True,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                )
        proc.wait()
        ausgabe_1, ausgabe_err_1 = proc.communicate()
        command_2 = 'TASKKILL /f /IM CHROME.EXE'
        proc = subprocess.Popen(command_2,
                                shell=True,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                )
        proc.wait()
        ausgabe_2, ausgabe_err_2 = proc.communicate()
    elif b == "firefox":
        command_1 = 'TASKKILL /f /IM GECKODRIVER.EXE'
        proc = subprocess.Popen(command_1,
                                shell=True,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                )
        proc.wait()
        ausgabe_1, ausgabe_err_1 = proc.communicate()
        command_2 = 'TASKKILL /f /IM FIREFOX.EXE'
        proc = subprocess.Popen(command_2,
                                shell=True,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.PIPE,
                                )
        proc.wait()
        ausgabe_2, ausgabe_err_2 = proc.communicate()

    ### Ausgabe der Resultate in die Log-Datei
    Classes.Logging.log_taskkill(command_1, ausgabe_1, ausgabe_err_1, command_2, ausgabe_2, ausgabe_err_2)

###### Webseite per Uebergebene URL oeffnen
def URLOeffnen(driver, pageUrl):
    Classes.Logging.ueberschrift_anlegen(2, "Webseite öffnen")
    driver.maximize_window()
    try:
        driver.get(pageUrl)
        Classes.Logging.fliesstext_eintrag("Die URL wurde mit dem Browser geöffnet")
    except Exception as e:
        Classes.Logging.fliesstext_eintrag("Fehler beim öffnen Webseite")
        Classes.Logging.exception_log("GUI-Automation -> URLOeffnen", str(e))
    result = benutzer_anmelden(driver)
    if result == 1:
        table = []
        table.append("Beschreibung,Wert")
        table.append("URL," + pageUrl)
        Classes.Logging.tabelle_anlegen(table)
        Classes.Logging.screenshot_anlegen(driver)
        return 1
    else:
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim Erzeugen des WebDrivers",
                                                 "Bei der Anmeldung ist ein Fehler aufgetreten. Die "
                                                 "Anmeldung konnte nicht durchgeführt werden")
        return 0

###############
###### Webseite per Uebergebene URL oeffnen
def benutzer_anmelden(driver):
    ## Benutzername eintragen
    ## Logging
    Classes.Logging.ueberschrift_anlegen(2, 'Anmeldung')
    lowered_page_title = str(driver.title).lower()
    if lowered_page_title == "sign in to your account":
        #### Es wurde bereits eine Anmeldung mit einem Nutzer durchgeführt
        #### Die Benutzerdaten sind hinterlegt und müssen zur Anmeldung ausgewählt werden.
        lowered_pagesource = str(driver.page_source).lower()
        if lowered_pagesource.find("mnaas@soka-dach.de"):
            Classes.Logging.fliesstext_eintrag("Es wird eine Anmeldung durchgeführt")
            #Benutzername setzen
            element_setzen(driver, "name", "loginfmt", "mnaas@soka-dach.de")
            # nicht notwendig, da ein Enter bei elemente_setzen durchgeführt wird - Next Klicken
            #element_klicken(driver, "id","idSIButton9")
            # Passwort setzen
            element_setzen(driver, "name", "passwd", "start123#")
            # nicht notwendig, da ein Enter bei elemente_setzen durchgeführt wird -  SignIn Klicken
            #element_klicken(driver, "id","idSIButton9")
            # Stay signed in Klicken
            element_klicken(driver, "id","idSIButton9")
            Classes.Logging.fliesstext_eintrag(
                "Es wurde eine neue Benutzeranmeldung durchgeführt")
            return 1
        else:
            #### Benutzername muss eingetragen werden
            #### Passwort muss eingetragen werden.
            #### Anmelden klicken
            element_pruefen(driver, "xpath", "//div[contains(text(),'mnaas@soka-dach.de')]", "mnaas@soka-dach.de")
            element_klicken(driver, "xpath", "//div[contains(text(),'mnaas@soka-dach.de')]")
            #nicht notwendig, da ein Enter bei elemente_setzen durchgeführt wird -  SignIn Klicken
            #element_klicken(driver, "id","idSIButton9")
            # Stay signed in Klicken
            element_klicken(driver, "id","idSIButton9")
            Classes.Logging.fliesstext_eintrag(
                "Aufgrund von bereits hinterlegten Anmeldedaten erfolgt die Anmeldung über Auswahl des hinterlegten Nutzers")
            return 1
    elif lowered_page_title == "microsoft dynamics nav":
        #### Anmeldung war nicht notwendig, Startseite von Microsoft NAV wird bereits angezeigt
        Classes.Logging.notiz_anlegen(
            "Aufgrund einer bereits bestehenen Anmeldung musste keine erneute Anmeldung durchgeführt werden.")
        return 1
    else:
        ####
        Classes.Logging.fehler_nachricht_anlegen("Fehlerhafter Seitentitel",
            "Der Seitentitel der geöffneten Seite entsprach keinem der erwarteten Titel - " + driver.title)
        return 0

########################
# Verwaltet die zusätzlichen Variablen
# type = read  ->  Variable wird ausgelesen
# type = write   ->  Variable wird gesetzt/aktualisiert
def variablen_setzen(driver, identifier_type, identifier, parameter):
    element = get_element(driver, identifier_type, identifier)
    global testfall_variablen
    wert = element.text
    parameter = parameter.replace("[var:","").replace("]","")
    if parameter not in testfall_variablen:
        ## Logging
        eintrag = 'Die Variable (**%s**) mit dem Wert (**%s**) wurde hinzugefügt' % (parameter, wert)
        Classes.Logging.fliesstext_eintrag(eintrag)
    else:
        ## Logging
        eintrag = 'Die Variable (**%s**) mit dem Wert (**%s**) wurde auf den Wert (**%s**) geändert' % (parameter, testfall_variablen[parameter], wert)
        Classes.Logging.fliesstext_eintrag(eintrag)
    try:
        testfall_variablen[parameter] = wert
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Variable Setzen", str(e))

###### Element setzen
def element_setzen(driver, identifier_type, identifier, wert):
    ## Wert eintragen
    ########################
    element = get_element(driver, identifier_type, identifier)
    ## Logging
    if element is None:
        eintrag = 'Fehler beim setzen von (** %s **) auf den Wert (** %s **)' % (identifier, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim setzen des Elementes", eintrag)
        return 0
    # Wenn über den Testlauf ein RETURN gesendet wird, wird der Inhalt nicht geleert und an das
    # Element ausschließlich ein RETURN gesendet
    if wert == "[RETURN]":
        try:
            element.send_keys(Keys.RETURN)
            return 1
        except Exception as e:
            eintrag = 'Fehler beim senden der ENTER-Taste für das Element (** %s **)' % (identifier)
            Classes.Logging.fehler_nachricht_anlegen("Fehler beim setzen des Elementes", eintrag)
            Classes.Logging.exception_log("GUI-Automation -> Element setzen", str(e))
            return 0
    try:
        element.clear()
    except Exception as e:
        Classes.Logging.notiz_anlegen("Das Element " + identifier + " konnte nicht geleert werden")
        Classes.Logging.exception_log("GUI-Automation -> Element setzen", str(e))
    try:
        element.send_keys(wert)
    except Exception as e:
        eintrag = 'Fehler beim setzen von (** %s **) auf den Wert (** %s **)' % (identifier, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim setzen des Elementes", eintrag)
        Classes.Logging.exception_log("GUI-Automation -> Element setzen", str(e))
        return 0
    try:
        element.send_keys(Keys.RETURN)
    except Exception as e:
        Classes.Logging.notiz_anlegen("RETURN konnte nicht an das Element + " + identifier + " gesendet werden")
        Classes.Logging.exception_log("GUI-Automation -> Element setzen", str(e))
    eintrag = 'Das Element (** %s **) wurde auf den Wert (** %s **) gesetzt' % (identifier, wert)
    Classes.Logging.fliesstext_eintrag(eintrag)

    styled_screenshot(driver, element)

    return 1

############### Funktioniert bisher noch nocht
############### Funktion muss noch gefixed werden
def element_ueber_label_setzen(driver, identifier_type, identifier, wert):
    label_element = get_element(driver, identifier_type, identifier)
    if label_element is None:
        eintrag = 'Fehler beim setzen von (**%s**) auf den Wert (**%s**)' % (identifier, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim setzen des Elementes", eintrag)
        return 0

    label_id = str(label_element.get_attribute("for"))
    eintrag = 'Über das Label wurde die folgende Input-ID identifiziert (**%s**)' % (label_id)
    Classes.Logging.fliesstext_eintrag(eintrag)

    styled_screenshot(driver, element)

    eingabe_element = get_element(driver, "id", label_id)

    if eingabe_element is None:
        eintrag = 'Fehler beim setzen von (**%s**) auf den Wert (**%s**)' % (identifier, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim setzen des Elementes", eintrag)
        return 0

    styled_screenshot(driver, element)

    eingabe_element.clear()
    eingabe_element.send_keys(wert)
    return 1

###### Element klicken
def element_klicken(driver, identifier_type, identifier):
    # Finding "Show Your Message" button element by css selector using both id and class name. And clicking it.
    element = get_element(driver, identifier_type, identifier, True)
    if element is None:
        eintrag = 'Fehler beim Klicken auf das Element (**%s**). Das Element wurde nicht gefunden.' % (identifier)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim klicken auf das Elementes", eintrag)
        return 0
    eintrag = 'Es wurde auf das Element (**%s**) geklickt' % (identifier)
    Classes.Logging.fliesstext_eintrag(eintrag)

    styled_screenshot(driver, element)
    try:
        element.click()
        ## Logging
    except Exception as e:
        eintrag = 'Element (**%s**) - konnte nicht geklickt werden' % (identifier)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim klicken des Elementes", eintrag)
        Classes.Logging.exception_log("GUI-Automation -> Element klicken", str(e))
        #pytest.fail(str(element) + " - Element konnte nicht geklickt werden")
        return 0
    return 1

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

###### Element pruefen
def element_pruefen(driver, identifier_type, identifier, wert):
    # Checking whether the input text and output text are same using assertion.
    element = get_element(driver, identifier_type, identifier)
    ## Logging
    eintrag = ""
    if identifier == "":
        eintrag = 'Es wird geprüft, dass das Element (**%s**) exakt dem folgenden Wert entspricht (**%s**)' % (wert, wert)
    else:
        eintrag = 'Es wird geprüft, dass das Element (**%s**) exakt dem folgenden Wert entspricht (**%s**)' % (identifier, wert)

    Classes.Logging.fliesstext_eintrag(eintrag)
    Classes.Logging.notiz_anlegen("Der Vergleich ist Case-Sensitiv")
    if element is None:
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        #pytest.fail(element + " = None")
        return 0
    try:
        WebDriverWait(driver, waitcount).until(EC.textToBePresentInElementLocated(element, wert))
    except Exception as e:
        eintrag = 'Das Element (**%s**) hat innerhalb von **%s** Sekunden nicht den erwarteten Wert (**%s**) angenommen' % (identifier, str(waitcount), wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        Classes.Logging.exception_log("GUI-Automation -> Element prüfen", str(e))
        return 0
    styled_screenshot(driver, element)

    if wert == element.text:
        eintrag = 'Der Wert (**%s**) entspricht dem erwarteten Wert (**%s**)' % (element.text, wert)
        Classes.Logging.erfolg_nachricht_anlegen(eintrag)
        return 1
    else:
        eintrag = 'Der Wert (**%s**) entspricht **nicht** dem erwarteten Wert (**%s**)' % (element.text, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        return 0

###### Element enthält prüfen
def element_enthaelt_wert_pruefen(driver, identifier_type, identifier, wert):
    # Checking whether the input text and output text are same using assertion.
    element = get_element(driver, identifier_type, identifier)
    ## Logging
    eintrag = ""
    if identifier == "":
        eintrag = 'Es wird gepürft, dass das Element (**%s**) den folgenden Wert enthält (**%s**)' % (wert, wert)
    else:
        eintrag = 'Es wird gepürft, dass das Element (**%s**) den folgenden Wert enthält (**%s**)' % (identifier, wert)

    Classes.Logging.fliesstext_eintrag(eintrag)
    Classes.Logging.notiz_anlegen("Der Vergleich ist Case-Sensitiv")
    if element is None:
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        #pytest.fail(element + " = None")
        return 0
    count = 0
    sleeptime = 1
    while True:
        try:
            if wert in element.text:
                higlight_wert = " _" + wert + "_ "
                wert_markiert = element.text.replace(wert, higlight_wert)
                eintrag = 'Der Wert (**%s**) ist in dem geprüften Wert (**%s**) enthalten. Es wurden (**%s**) Sekunden gewartet' % (wert, wert_markiert, (count)*sleeptime)
                Classes.Logging.erfolg_nachricht_anlegen(eintrag)
                return 1
        except Exception as e:
            element = get_element(driver, identifier_type, identifier)
            print("Fehler beim ermittelt des Inhalts für das Element " + str(element))
            Classes.Logging.exception_log("GUI-Automation -> Element enthält Wert prüfem", str(e))
        time.sleep(sleeptime)
        count = count + 1
        if count >= waitcount:
            break
    if wert not in element.text:
        eintrag = 'Der Wert (**%s**) ist in dem geprüften Wert (**%s**) **nicht** enthalten' % (wert , element.text)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        return 0

###### Element enthält prüfen
def element_enthaelt_wert_nicht(driver, identifier_type, identifier, wert):
    # Checking whether the input text and output text are same using assertion.
    element = get_element(driver, identifier_type, identifier)
    ## Logging
    eintrag = ""
    if identifier == "":
        eintrag = 'Es wird gepürft, dass das Element (**%s**) den folgenden Wert enthält (**%s**)' % (wert, wert)
    else:
        eintrag = 'Es wird gepürft, dass das Element (**%s**) den folgenden Wert enthält (**%s**)' % (identifier, wert)

    Classes.Logging.fliesstext_eintrag(eintrag)
    Classes.Logging.notiz_anlegen("Der Vergleich ist Case-Sensitiv")
    if element is None:
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        #pytest.fail(element + " = None")
        return 0
    count = 0
    sleeptime = 1
    while True:
        try:
            if wert not in element.text:
                higlight_wert = " _" + wert + "_ "
                wert_markiert = element.text.replace(wert, higlight_wert)
                eintrag = 'Der Wert (**%s**) ist in dem geprüften Wert (**%s**) **nicht** mehr enthalten. Es wurden (**%s**) Sekunden gewartet' % (wert, wert_markiert, (count)*sleeptime)
                styled_screenshot(driver, element)
                Classes.Logging.erfolg_nachricht_anlegen(eintrag)
                return 1
        except Exception as e:
            element = get_element(driver, identifier_type, identifier)
            print("Fehler beim ermittelt des Inhalts für das Element " + str(element))
            zusatz = "
**Das Element wurde erneut gesetzt und der Testlauf wird fortgesetzt.**"
            Classes.Logging.exception_log("GUI-Automation -> Element enthält Wert nicht", str(e) + zusatz)
        time.sleep(sleeptime)
        count = count + 1
        if count >= waitcount:
            break
    if wert in element.text:
        eintrag = 'Der Wert (**%s**) ist in dem geprüften Wert (**%s**) **weiterhin** enthalten' % (wert , element.text)
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim prüfen des Elementes", eintrag)
        styled_screenshot(driver, element)
        return 0
###############
def wechseln_zu(driver, identifier_type, identifier, type = "frame"):
    if type == "frame":
        try:
            driver.switch_to.frame(get_element(driver, identifier_type, identifier))
            #wait(driver, 10).until(EC.frame_to_be_available_and_switch_to_it(get_element(driver, identifier_type, identifier)))
        except Exception as e:
            Classes.Logging.notiz_anlegen("Es konnte nicht zu dem Frame gewechselt werden")
            Classes.Logging.exception_log("GUI-Automation -> Wechseln zu", str(e))
    if type == "form":
        try:
            get_element(driver, identifier_type, identifier)
        except Exception as e:
            Classes.Logging.notiz_anlegen("Es konnte nicht zu der Form gewechselt werden")
            Classes.Logging.exception_log("GUI-Automation -> Wechseln zu", str(e))
    eintrag = 'Es wurde zu folgendem Element (**%s**) gewechselt' % (identifier)
    Classes.Logging.fliesstext_eintrag(eintrag)

###############
def schalter_setzen(driver, identifier_type, identifier, wert):
    element = get_element(driver, identifier_type, identifier)
    if wert is None:
        wert = ""
    if element is None:
        eintrag = 'Fehler beim setzen des Schalter (**%s**) - auf den Wert' % (identifier, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler setzen des Schalters", eintrag)
        #pytest.fail(element + " = None")
        return 0
    schalter_wert = str(element.get_attribute("title")).lower()
    styled_screenshot(driver, element)

    if schalter_wert == str(wert).lower():
        eintrag = 'Das Element (**%s**) entspricht dem erwarteten Wert (%s)' % (identifier, wert)
        Classes.Logging.erfolg_nachricht_anlegen(eintrag)
    else:
        eintrag = 'Das Element (**%s**) entspricht nicht dem erwarteten Wert (%s) und wird nun geklickt' % (identifier, wert)
        Classes.Logging.fliesstext_eintrag(eintrag)
        element_klicken(driver, identifier_type, identifier)
        schalter_setzen(driver, identifier_type, identifier, wert)
        #driver.execute_script("arguments[0].title = '" + str(wert) + "'", element)
    return 1

##### Zum prüfen von Checkboxen mittels Titel des Input-Tags
def schalter_prüfen(driver, identifier_type, identifier, wert):
    element = get_element(driver, identifier_type, identifier)
    if element is None:
        eintrag = 'Fehler beim setzen des Schalter (**%s**) - auf den Wert - Das Element wurde nicht gefunden' % (identifier, wert)
        Classes.Logging.fehler_nachricht_anlegen("Fehler setzen des Schalters", eintrag)
        #pytest.fail(element + " = None")
        return 0

    styled_screenshot(driver, element)

    if str(element.get_attribute("title")).lower() == str(wert).lower():
        eintrag = 'Das Element (**%s**) entspricht dem erwarteten Wert (%s)' % (identifier, wert)
        Classes.Logging.erfolg_nachricht_anlegen(eintrag)
        return 1
    else:
        eintrag = 'Das Element (**%s**) entspricht nicht dem erwarteten Wert (%s) - aktueller Wert ist (%s)' % (identifier, wert, element.get_attribute("title"))
        Classes.Logging.fehler_nachricht_anlegen("Schalter entspricht nicht erwartetem Wert", eintrag)
        return 0
    return 1
###############

def element_ein_ausklappen(driver, identifier_type, identifier, ausklappen = "ausklappen"):
    element = get_element(driver, identifier_type, identifier)
    if element is None:
        eintrag = 'Fehler beim ein/ausklappen des Elements (**%s**) - Das Element wurde nicht gefunden' % (identifier)
        Classes.Logging.fehler_nachricht_anlegen("Fehler setzen des Schalters", eintrag)
        #pytest.fail(element + " = None")
        return 0

    styled_screenshot(driver, element)
    class_value = str(element.get_attribute('class'))
    if ("expanded" in class_value and str(ausklappen).lower() == "einklappen") or ("collapsed" in class_value and str(ausklappen).lower() == "ausklappen"):
        new_identifier = identifier + "/div[1]/span[@class=\"ms-nav-columns-caption icon-RightCaret-after\"]"
        eintrag = 'Das Element (**%s**) wird durch einen Klick ein-/ausgeklappt' % (new_identifier)
        Classes.Logging.fliesstext_eintrag(eintrag)
        element_klicken(driver, identifier_type, new_identifier)
        # erneuter Aufruf zum prüfen, ob das Element korrekt gesetzt wurde
        element_ein_ausklappen(driver, identifier_type, identifier, ausklappen)
    else:
        eintrag = 'Das Element (**%s**) mit der Klasse (**%s**) und dem Inhalt (**%s**) ' \
                  'entspricht dem erwarteten Wert ausklappen = (**%s**)' \
                  % (identifier, class_value, element.text, str(ausklappen))
        Classes.Logging.fliesstext_eintrag(eintrag)

    return 1

###############
def warten_auf_element(driver, identifier_type, identifier, type = "appear"):
    count = int(waitcount/5)
    if str(type).lower() == "appear":
        while True:
            element = get_element(driver, identifier_type, identifier, show_error=False)
            time.sleep(1)
            count = count - 1
            if element is not None or count <= 0:
                break
        if element is None:
            Classes.Logging.fehler_nachricht_anlegen(
                "Fehler beim Warten", "Fehler beim Warten auf das anzeigen des Elements - **" +
                identifier + "**. Es wurde nach **" + str(int(waitcount/5)) + "** Versuchen abgebrochen")
            return 0
        else:
            Classes.Logging.erfolg_nachricht_anlegen("Das Element - **" + identifier + "** ist vorhanden.")
            return 1
    elif str(type).lower() == "gone":
        while True:
            element = get_element(driver, identifier_type, identifier, show_error=False)
            time.sleep(1)
            count = count - 1
            if element is None or count <= 0:
                break
        if element is None:
            Classes.Logging.erfolg_nachricht_anlegen("Das Element - **" + identifier + "** ist nicht mehr vorhanden.")
            return 1
        else:
            Classes.Logging.fehler_nachricht_anlegen(
                "Fehler beim Warten", "Fehler beim Warten auf das verschwinden des Elements - **" +
                identifier + "**. Es wurde nach **" + str(int(waitcount/5)) + "** Versuchen abgebrochen")
            return 0
    return 0

###############
def warte(driver, identifier_type, wert):
    wartezeit = 0
    if str(identifier_type).lower() == "sekunden":
        wartezeit = int(wert)
        time.sleep(wartezeit)
    elif str(identifier_type).lower() == "minuten":
        wartezeit = int(wert) * 60
        time.sleep(wartezeit)
    Classes.Logging.notiz_anlegen("Es wurde, gemäß Testfallablauf, **" + str(wartezeit) + " Sekunde(n)** gewartet")
    return 1

def wartezeit_timeout_setzen(wert):
    global waitcount
    if str(wert).lower() == "default":
        waitcount = 20;
    else:
        waitcount = int(wert)
    Classes.Logging.notiz_anlegen("Die Wartezeit bis zu einem Timeout wurde auf *" + str(waitcount) + "* sekunden gesetzt")
###############

def get_element(driver, identifier_type, identifier, is_button = True, show_error = True):
    element = None
    identifier = str(identifier)

    identifier_type_lower = identifier_type.lower()
    by_type = By.ID
    if identifier_type_lower == "id":
        by_type = By.ID
    elif identifier_type_lower == "xpath":
        by_type = By.XPATH
    elif identifier_type_lower == "link_text":
        by_type = By.LINK_TEXT
    elif identifier_type_lower == "partial_link_text":
        by_type = By.PARTIAL_LINK_TEXT
    elif identifier_type_lower == "name":
        by_type = By.NAME
    elif identifier_type_lower == "tag_name":
        by_type = By.TAG_NAME
    elif identifier_type_lower == "class_name":
        by_type = By.CLASS_NAME
    elif identifier_type_lower == "css_selector":
        by_type = By.CSS_SELECTOR
    else:
        Classes.Logging.fehler_nachricht_anlegen("Fehler beim identifizieren",
                                                 "Es wurde ein nicht untertützter Identifier-Typ übergeben - **" + identifier_type + "**")
        return None
    if is_button == False:
        element = WebDriverWait(driver, waitcount).until(EC.presence_of_element_located((by_type, identifier)))
    else:
        try:
            element = WebDriverWait(driver, waitcount).until(EC.element_to_be_clickable((by_type, identifier)))
            WebDriverWait(driver, waitcount).until(EC.visibility_of(element))
            #element = WebDriverWait(driver, waitcount).until(EC.element_to_be_clickable(element))
        except Exception as e:
            Classes.Logging.exception_log("GUI-Automation -> Get Element", str(e))
            if len(driver.find_elements(by_type, identifier)) > 1:
                Classes.Logging.fehler_nachricht_anlegen(
                    "Fehler beim identifizieren", "Fehler beim identifizieren des Elements - **" +
                    identifier + "**. Es kein eindeutiges Element gefunden")
                element = None
            elif len(driver.find_elements(by_type, identifier)) == 0:
                Classes.Logging.fehler_nachricht_anlegen(
                    "Fehler beim identifizieren", "Fehler beim identifizieren des Elements - **" +
                    identifier + "**. Es konnte kein Element identifiziert werden.")
                element = None
            return element
    if element is None:
        if show_error == True:
            Classes.Logging.fehler_nachricht_anlegen(
                "Fehler beim identifizieren", "Fehler beim identifizieren des Elements - **" +
                identifier + "**. Es wurde nach **" + str(waitcount) + "** Sekunden ein Timeout erzeugt")
        return None
    actions = ActionChains(driver)
    try:
        actions.move_to_element(element).perform()
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Get Element", str(e))
        if show_error == True:
            Classes.Logging.fehler_nachricht_anlegen("Fehler bei dem Wechsel zum Element",
                                                     "Es konnte nicht mittels switch zum Element gewechselt werden")
    eintrag = ""
    if identifier != "":
        eintrag = 'Das folgende Element (**%s**) wurde mittels (**%s**) selektiert' % (identifier, identifier_type)
    else:
        eintrag = 'Das folgende Element (**%s**) wurde mittels (**%s**) selektiert' % (identifier, identifier_type)
    Classes.Logging.fliesstext_eintrag(eintrag)
    Classes.Logging.screenshot_von_element_anlegen(element)
    return element

#### Die Testdatendatei wird in ein Dictionary geselen
#### Das Dictionary besteht aus einem Eintrag in dem alle alle Keys mit deren testfallspezifischen Werte hinterlegt sind
#### Der Wert eines Testfalls kann wie folgt ermittelt werden
####     - dict_list[0][<Key-Name>][<Testfall-Nr>]
def dict_aus_datei_erzeugen(testdatenpfad):
    delimiter = ';'
    dict_list = []
    # Wenn der Testdatenpfad nicht gefunden wird, wird ein Element im Dict angelegt.
    # Grund können Testfälle ohne Testdaten-Datei (nur fixe Aktionswerte) sein.
    if not path.exists(testdatenpfad):
        dict_list.append("keine Testdaten")
    try:
        input_file = csv.DictReader(open(testdatenpfad, 'r'), delimiter=delimiter)
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Dict aus Datei erzeugen", str(e))
        Classes.Logging.fehler_nachricht_anlegen("Fehler","Fehler bei lesen der Testdaten-Datei")
        return []
    csv_dict = {elem: [] for elem in input_file.fieldnames}
    for row in input_file:
        for key in csv_dict.keys():
            csv_dict[key].append(row[key])
        dict_list.append(csv_dict.copy())
        csv_dict.clear()
        csv_dict = {elem: [] for elem in input_file.fieldnames}
    return dict_list

#### Erzeugt einen Screenshot mit hervorgehobenem Element
#### zu diesem Zweck wird ein
####    1. Style an das Element angefügt
####    2. Screenshot über die Logging-Klasse angelegt
####    3. der ursprüngliche Style wieder an das Element angefügt
def styled_screenshot(driver, element):
    try:
        # aktuellen Style sichern um ihn wieder zurücksetzen zu können
        original_style = element.get_attribute('style')
        # Style zum hervorheben von Elementen übergeben/setzen
        style_setzen(driver, element, "background: yellow; border: 2px solid red;")
        # Screenshot erstellen
        Classes.Logging.screenshot_anlegen(driver)
        # Style zurücksetzen
        style_setzen(driver, element, original_style)
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Styled Screenshot", str(e))
        Classes.Logging.notiz_anlegen("Der Style zum markieren innerhalb des Screenshots konnte nicht gesetzt werden")

def style_setzen(driver, element, style):
    try:
        # übergebenen Style setzen
        driver.execute_script("arguments[0].setAttribute('style', arguments[1]);",
                          element, style)
    except Exception as e:
        Classes.Logging.exception_log("GUI-Automation -> Style setzen", str(e))
        Classes.Logging.notiz_anlegen("Der übergebene Style für das element **" + str(element) + "** konnte nicht gesetzt werden")
        # Fehlverhalten hat keine Auswirkungen auf den Testablauf


# -*- coding: utf-8 -*-

from django import forms
import datetime

class BaseForm(forms.Form):

    def clean(self):
        cleaned_data = self.cleaned_data
        if cleaned_data.get('from_date'):
            if cleaned_data.get('to_date'):
                if cleaned_data.get('to_date') < cleaned_data.get('from_date'):
                    msg = u"A data final não pode ser menor que a data inicial."
                    self._errors["to_date"] = self.error_class([msg])
                    del cleaned_data["to_date"]
            if cleaned_data.get('from_date').__class__.__name__ == 'date':
                if cleaned_data.get('from_date') > datetime.date.today():
                    msg = u"A data inicial não pode ser maior que a data atual."
                    self._errors["from_date"] = self.error_class([msg])
                    del cleaned_data["from_date"]
            elif cleaned_data.get('from_date').__class__.__name__ == 'datetime':
                if cleaned_data.get('from_date') > datetime.datetime.today():
                    msg = u"A data inicial não pode ser maior que a data atual."
                    self._errors["from_date"] = self.error_class([msg])
                    del cleaned_data["from_date"]
        if cleaned_data.get('dia'):
            if cleaned_data.get('dia').__class__.__name__ == 'date':
                if cleaned_data.get('dia') > datetime.date.today():
                    msg = u"A data do dia não pode ser maior que a data atual."
                    self._errors["dia"] = self.error_class([msg])
                    del cleaned_data["dia"]
        return cleaned_data

class CustomMultipleChoiceField(forms.MultipleChoiceField):
    def to_python(self, value):
        if value:
            newvalue = value[:]
            choicesdict = dict(self.choices)
            for v in value:
                if not choicesdict.has_key(v):
                    newvalue.remove(v)
            return newvalue
        else:
            return value

class CustomChoiceField(forms.ChoiceField):
    def to_python(self, value):
        choicesdict = dict(self.choices)
        if not choicesdict.has_key(value):
            return ""
        return value


#!/usr/bin/env python

#
# This work is licensed under the terms of the MIT license.
# For a copy, see <https://opensource.org/licenses/MIT>.

#
# This file contains the basic scenario and scenario manager classes
# These must not be modified and are for reference only!

"""
This module provides the Scenario and ScenarioManager implementations.
"""

import time
import py_trees

class Scenario(object):

    """
    Basic scenario class. This class holds the behavior_tree describing the
    scenario and the test criteria.

    The user must not modify this function.

    Important parameters:
    - behavior_tree: User defined scenario with py_tree
    - criteria_list: List of user defined test criteria with py_tree
    - timeout (default = 60s): Timeout of the scenario in seconds
    - terminate_on_failure: Terminate scenario on first failure
    """

    def __init__(self, behavior_tree, timeout=60, terminate_on_failure=False):
        self.behavior = behavior_tree

        # Create node for timeout
        self.timeout_node = py_trees.timers.Timer(
            name="TimeOut", duration=timeout)

        # Create overall py_tree
        self.scenario = py_trees.composites.Parallel(
            name="Scenario with TimeOut and TestCriteria",
            policy=py_trees.common.ParallelPolicy.SUCCESS_ON_ONE)
        self.scenario.add_child(self.behavior)
        self.scenario.add_child(self.timeout_node)
        self.scenario.setup(timeout=1)

class ScenarioManager(object):

    """
    Basic scenario manager class. This class holds all functionality
    required to start, and analyze a scenario.

    The user must not modify this function.
    """

    def __init__(self, scenario):
        """
        Init requires scenario as input
        """
        self.scenario = scenario
        self.scenario_tree = scenario.scenario
        self.scenario_duration = 0

        # To print the scenario tree uncomment the next line
        # py_trees.display.render_dot_tree(self.scenario_tree)

    def run_scenario(self):
        """
        Execute scenario
        """
        start = time.time()
        while self.scenario_tree.status != py_trees.common.Status.SUCCESS:
            # print("\n--------- Tick ---------\n")
            self.scenario_tree.tick_once()
            # print("\n")
            # py_trees.display.print_ascii_tree(
            #    self.scenario_tree, show_status=True)

            if self.scenario_tree.status == py_trees.common.Status.FAILURE:
                print("Terminated due to failure")
                break

            # Sleep for a small time to avoid high cpu load
            time.sleep(0.1)

        end = time.time()
        self.scenario_duration = end - start

    def run_scenario_per_frame(self):
        """
        Execute scenario
        """
        if self.scenario_tree.status != py_trees.common.Status.SUCCESS:
            # print("\n--------- Tick ---------\n")
            self.scenario_tree.tick_once()
            # print("\n")
            # py_trees.display.print_ascii_tree(
            #    self.scenario_tree, show_status=True)

            if self.scenario_tree.status == py_trees.common.Status.FAILURE:
                print("Terminated due to failure")


"""Module to convert the given OCR number to decimal."""

MODELS = {
    " _ | ||_|   ": "0",
    "     |  |   ": "1",
    " _  _||_    ": "2",
    " _  _| _|   ": "3",
    "   |_|  |   ": "4",
    " _ |_  _|   ": "5",
    " _ |_ |_|   ": "6",
    " _   |  |   ": "7",
    " _ |_||_|   ": "8",
    " _ |_| _|   ": "9",
}

LEN_CHAR = 3
LEN_LINE = 4

def convert(input_grid: list[str]) -> str:
    """Convert the given input_grid to the decimal value or return whether the number is garbled.

    :param input_grid: list[str] - OCR number to check
    :return: str - the converted OCR number to decimal, or a "?" if garbled
    """
    if len(input_grid) % 4 != 0:
        raise ValueError("Number of input lines is not a multiple of four")

    if not all(len(line) % 3 == 0 for line in input_grid):
        raise ValueError("Number of input columns is not a multiple of three")

    if not all(len(input_grid[0]) == len(line) for line in input_grid):
        raise ValueError("Number of input columns should match across all rows")

    output = []
    num_chars = len(input_grid[0]) // LEN_CHAR
    num_nums = len(input_grid) // LEN_LINE

    for num_row in range(num_nums):
        inner_output = []

        for num_char in range(num_chars):
            try:
                line = "".join(chars[(num_char * LEN_CHAR):(num_char * LEN_CHAR) + LEN_CHAR] for chars in input_grid[num_row * LEN_LINE:num_row * LEN_LINE + LEN_LINE])
                inner_output.append(MODELS[line])
            except KeyError:
                inner_output.append("?")

        output.append("".join(inner_output))

    return ",".join(output)


"""
Implementation of the Aho-Corasick algorithm for pattern matching
"""

# FILE INPUT & INITIALIZATIONS #

lines = [line.rstrip('\n') for line in open('patterns.txt')]
pattern_dir = dict()
for pat in lines:
    pattern_dir.update({pat: 0})

abc = ['A', 'T', 'C', 'G']

# GOTO() #

def goTo(lines):
    output = ['' for i in range(20)]
    g = {}
    newstate = 0
    for pattern in lines:
        state = 0
        j = 0
        while (pattern[j], state) in g:
            state = g[(pattern[j], state)]
            j += 1
        for p in range(j, len(pattern)):
            newstate += 1
            g[(pattern[p], state)] = newstate
            state = newstate
        output[state] = pattern
    for char in abc:
        if (char, 0) not in g: g[(char, 0)] = 0
    return g, output

# FAILURE_FUN() #

def failureFun(g, output):
    f = [0 for i in range(20)]
    queue = []
    for key in abc:
        if g[(key, 0)] != 0:
            queue.append(g[(key, 0)])
            f[g[(key, 0)]] = 0
    while queue:
        r = queue.pop(0)
        for key in abc:
            if (key, r) in g:
                queue.append(g[(key, r)])
                state = f[r]
                while (key, state) not in g:
                    state = f[state]
                f[g[(key, r)]] = g[(key, state)]
                if output[f[g[(key, r)]]] != '': output[g[(key, r)]] += (', ' + output[f[g[(key, r)]]])
    return f

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

# Pattern Matching Machine #

def Automaton(g, f, output):
    text = 'CTAATGTTGAATGGCCACTACCGTGAATGCCGTGTGAATGCTA'
    state = 0
    skip_count = 0
    comp_count = 0
    for i, char in enumerate(text):
        comp_count += 1
        while (char, state) not in g:
            state = int(f[state])
            if not state: skip_count += 1
        state = g[(char, state)]
        if len(output[state]) >= 2:
            print ('In position ' + str(i) + ' found pattern(s) ' + output[state])
            for pat_count in pattern_dir:
                if pat_count in output[state]:
                    pattern_dir[pat_count] += 1
    print ('#Skip/count: ' + str(skip_count))
    print (pattern_dir)
    print ('#Comparisons: ' + str(comp_count))

if __name__ == "__main__":
    g, output = goTo(lines)
    f = failureFun(g, output)
    Automaton(g, f, output)


# -*- coding: utf-8 -*-

#############################################
#              dependencies                 #
#############################################

import gym # the env
import sys
import os.path
import matplotlib.pyplot as plt
import numpy as np
from datetime import datetime as dt  # for the logs ans saves
# networks
# --------
from neuralNetworkMaster import mlp
from neuralNetworkMaster import elman
from neuralNetworkMaster import jordan
from neuralNetworkMaster import operators
# others
import global_vars as g
from graph_init import *
from save_and_load import *
from log_writer import *

#############################################
#                  code                     #
#############################################

#===========================================#
# function create_gammas:
# in: rl_gamma
# out: float array gammas: gammas[i] = rl_gamma ** i
#===========================================#

def create_gammas(rl_gamma, rl_epsilon):
    if (rl_gamma < 0 or rl_gamma >= 1 or rl_epsilon <= 0):
        raise Exception('Uncorrect gamma or epsilon')
    gammas = [1, rl_gamma]
    power = 1
    while gammas[power] > rl_epsilon:
        gammas.append(gammas[power]*rl_gamma)
        power += 1
    return gammas

#===========================================#
# function breadth_propagate:
# in: float array observation, the vector given by the OpenAI Gym env at every movements
# out: (id, result) the id of the networks which gave the answer to "out" node, and the result
#-------------------------------------------#
# two points:
# - fifo.pop(0) isn't enough when the operators take several parameters,
#   so we capture all the results to propagate in the current networks,
#   store their index (they will be removed from the fifo stack) into a
#   lifo stack to be sure one pop won't prevent the others (as we pop
#   in the decreasing order of index)
# - we don't know, out of this function, which network's should throw
#   the result into "out", so we need to return the result (and we are
#   sure that the execution will reach "out" thanks to breadth_init())
#===========================================#

def breadth_propagate(observation):
    fifo = [ (i, observation) for i in g.global_links_array[0] ] # Doesn't visit the network of index 0 as his type is "in", it is a faked network.
    while fifo: # The list fifo isn't empty
        # propagation
        # -----------
        (id_network, result_to_propagate) = fifo.pop(0)
        result_array_to_propagate = [result_to_propagate]
        pop_indexes = []
        for i in range(len(fifo)):
            (id_prime, result_prime) = fifo[i]
            if id_prime == id_network: # Gathers several parameters, for operators
                pop_indexes.insert(0,i)
                result_array_to_propagate.append(result_prime)
        for i in pop_indexes:
            fifo.pop(i)

        result = g.networks[id_network].propagate_forward(*result_array_to_propagate)
        g.networks_answers_sequences[id_network].insert(0,np.copy(result)) # Saving the sequences for the reinforcement learning.
        # fifo completion for breadth propag
        # ----------------------------------
        # we differ case with the operator of decomposition since it needs to communique different results to the networks after it.
        j = 0
        for i in g.global_links_array[id_network]:
            if (g.global_types_array[i] == "out"):
                return id_network, result
            if g.global_types_array[id_network] == "op_decomp":
                fifo += [(i, result[j])]
                j += 1
            else :
                fifo += [(i, result)]

#===========================================#
# function reinforcement_learning:
# in:
# out:
#===========================================#

# def reinforcement_learning(done, current_score, max_score, gammas, rl_epsilon, id_result_giver, errors): # no need to alter anything since a new game wiil begin just after this function, which is called at defeat or at victory

#     power = 0
#     # building errors that will be backwarded into all the networks
#     # last_answer = networks_answers_sequences[id_result_giver][current_score]
#     for gamma in gammas:
#         # answer_to_bw = networks_answers_sequences[id_result_giver][current_score - power]
#         # real_target = target + last_answer - answer_to_bw
#         sign = np.sign(g.networks_answers_sequences[id_result_giver][power])
#         if (done) :
#             target = -sign * g.global_punishs_array[id_result_giver]
#         elif (current_score == max_score-1) :
#             target =  sign * g.global_rewards_array[id_result_giver]
#         errors.insert( 0, g.networks[id_result_giver].propagate_backward(target, gammas[power]) )
#         power += 1

    # backwarding the errors
    # for id_net in range(1, g.nb_networks-1): # all networks except "in", "out" and
    #     if id_net != id_result_giver :     # <-- the result giver (already done)
    #         for id_err in range(len(errors)):
    #             target = g.networks_answers_sequences[id_net][current_score - id_err] + errors[id_err]
    #             if (done) :
    #                 real_target = presumed_target * g.global_punishs_array[id_result_giver]
    #             elif (current_score == max_score-1) :
    #                 real_target = presumed_target * g.global_rewards_array[id_result_giver]
    #             g.networks[id_net].propagate_backward(target, gammas[id_err])

# Trying to debug
# -------------
def reinforcement_learning(done, current_score, max_score, gammas, rl_epsilon, id_result_giver, errors): # no need to alter anything since a new game wiil begin just after this function, which is called at defeat or at victory
    if done: # Defeat
        power = 0
        for gamma in gammas:
            sign = np.sign(g.networks_answers_sequences[id_result_giver][power][0])
            g.networks[id_result_giver].propagate_backward(-sign*g.global_punishs_array[id_result_giver], gammas[power])
            power += 1

    else: # victory
        power = 0
        for gamma in gammas:
            sign = np.sign(g.networks_answers_sequences[id_result_giver][power])
            g.networks[id_result_giver].propagate_backward(sign*g.global_rewards_array[id_result_giver]/2, gammas[power])
            power += 1

#===========================================#
# function" multi_networks:
# in: string array p_types_array:       networks' types (among "in" and "out" for the first and last elements which are the limits of the graph,
#                                       "mlp", "elman" and  "jordan" for the networks, or "op_plus", "op_minus", etc for the operators)
#     float array p_weights_array:      weight_multiplicator used for each network's init
#     float array p_rewards_array:      victory_reward for each network
#     float array p_punishs_array:      defeat_punishment for each network
#                                       ## weights, rewards and punishs = 0 for the types "in", "out" and the operators
#
#     positive int list array p_inner_sizes_array:      Each element i of global_inner_sizes_array corresponds to the network of index i.
#                                                       so global_inner_sizes_array[i] is the list containing the sizes of all layers,
#                                                       except the first one which depends of the last layer size of the network before.
#
#     positive int list array p_links_array:            Each element i of global_links_array corresponds to the network of index i.
#                                                       So global_links_array[i] is the list containing the index of all the reachable
#                                                       neighbors of the network of index i.
#
#     string user_name                        used for saving the logs and the results
#     string game_name,                       ex 'CartPole-v0'
#     positive int game_observation_size,     number of dimensions for the observation vector
#     positive int game_decision_size,        positive int: number of dimensions for the decision vector
#     positive int max_score = 200,           positive int: score which makes the game end (200 is the basic OpenAI gym value)
#     positive int nb_epochs = 1000,          positive int: lenght of a simulation (in term of number of games victories or deafeats)
#     positive int nb_simulations = 10,        positive int: number of simulations
#     positive int save_frequency = 100,      positive int: the networks are saved at frequency of save_frequency*epochs
#     flaot in [0,1[ rl_gamma = 0.6,          used for the reinforced learning (if gamma = 0 there won't have reinforced learning)
#     rl_epsilon = 0.05,
#     was_loaded = False
#
# out: nothing
#===========================================#

def multi_networks (p_types_array,
                    p_weights_array,
                    p_rewards_array,
                    p_punishs_array,
                    p_inner_sizes_array,
                    p_links_array,
                    user_name,
                    game_name,
                    game_observation_size,
                    game_decision_size,
                    max_score = 200,
                    nb_epochs = 1000,
                    nb_simulations = 10,
                    save_frequency = 100,
                    rl_gamma = 0.6,
                    rl_epsilon = 0.05,
                    was_loaded = False
                    ):

    # assigning the global vars valide for all the simulations
    # --------------------------------------------------------
    g.global_types_array = p_types_array
    g.global_init_weights_array = p_weights_array
    g.global_rewards_array = p_rewards_array
    g.global_punishs_array = p_punishs_array
    g.global_inner_sizes_array = p_inner_sizes_array
    g.global_links_array = p_links_array
    g.nb_networks = len(g.global_types_array)

    # finding cycles --> infinite calcul # NO MORE NEEDED thanks to the grid
    # if(cycle_exists()):
    #     raise Exception("/!\ There is a forbidden cycle")

    # log creation
    log = initiate_log(user_name, game_name, nb_simulations, nb_epochs, max_score, rl_gamma, rl_epsilon, was_loaded)
    # running the game
    env = gym.make(game_name)
    # preparing reinforcement learning
    gammas = create_gammas(rl_gamma, rl_epsilon)  # float array: gammas[i] = rl_gamma ** i, putted here for optimisation because of their are a lot of repetitions
    # prepare the plot
    score_evolution = np.zeros([nb_simulations, nb_epochs])
    # prepare the save
    save_header = [p_types_array, p_weights_array,
                   p_rewards_array, p_punishs_array,
                   p_inner_sizes_array, p_links_array,
                   [user_name, game_name,
                    game_observation_size, game_decision_size,
                    max_score, nb_epochs, nb_simulations,
                    save_frequency, rl_gamma, rl_epsilon]
                   ] # don't put was_loaded into the header, it just serve on the loading
    g.networks_weights = [ [-1 for i in range(g.nb_networks)] for i in range(nb_simulations)]
    save_path = None
    if user_name is not None: # The player is connected
        today = dt.now()
        save_path = "saves/save_" + user_name + "_" + game_name + "_" + str(today.day) + "-" + str(today.month) + "-" + str(today.year) + "_" + str(today.hour) + "-" + str(today.minute) +"-" + str(today.second) + ".npz"
    has_been_saved = False

    ########################
    # BEGINING OF THE GAME #
    ########################
    for i_simul in range (nb_simulations):
        g.networks = [-1 for i in range(g.nb_networks)]
        breadth_init(game_observation_size, game_decision_size)
        # loading
        if was_loaded :  # It remains to update the weights for this simulation.
            for i_net in range(g.nb_networks):   # After the "for", global_load[0] = networks[0] for the next simulation.
                g.networks[net].weights = g.global_load.pop(0).tolist()

        for i_epoch in range(nb_epochs): # One epoch is like one game : it lasts until the victory or the defeat.

            if user_name is not None: # The player is connected
                if(save(save_path, i_epoch, nb_epochs, i_simul, save_frequency, save_header)):   # saving
                    has_been_saved = True

            observation = env.reset()   # new game
            errors = []    # float array: contains the errors compute by backwarding the targets into the decision giver network.
            g.networks_answers_sequences = [ [] for i in range(g.nb_networks) ]   # The answer sequence of each network is saved in order to opperate the reinforced learning.

            for current_score in range(max_score): # For each decision until we win or lost.
                g.networks_answers_sequences[0].append(observation) # if we want to animate the game
                id_result_giver, result = breadth_propagate(observation)
                if result[0] > 0 : # Transforming the networks graph result (so the result of the last network branched onto the "out")
                                # into an action usable by the Open AI environnement
                    action = 1 # often means "right"
                else:
                    action = 0 # often means "left"
                #print "Action is:" + action + "with decision = " + networks_answers_sequences[0][current_score]   # Printing result in order to know if the network makes a "strong" (close to -1 or 1) decision, for debbug.
                observation, reward, done, info = env.step(action)

                # Reinforcement learning
                # ----------------------
                if done or (current_score == max_score-1):  # the game is ended by defeat or victory
                    reinforcement_learning(done, current_score, max_score, gammas, rl_epsilon, id_result_giver, errors)
                    score_evolution[i_simul][i_epoch] = current_score + 1
                    write_log(log, i_simul, i_epoch, current_score+1)
                    break   # in the case of defeat, finish with the current score

    # Made by the frontend now
    # for i in range(nb_simulations) :
    #     plt.plot(range(nb_epochs), score_evolution[i],label="simulation n " + str(i))
    # plt.legend()
    # plt.xlabel("Training epochs")
    # plt.ylabel("Score")
    # plt.title("Each graph result")

    # if (nb_simulations > 1):
    #     plt.figure()
    #     # Return the mean along the row.
    #     plt.plot(range(nb_epochs),np.mean(score_evolution, axis=0)) # (axis=0 is for row) Axis or axes along which the means are computed.
    #     plt.xlabel("Training epochs")
    #     plt.ylabel("Total score")
    #     plt.title("Mean result")

    # plt.show()

    log.close()

    return save_path, has_been_saved, score_evolution


import numpy as np
import cv2

#Load the image
filename = 'planIRL4.jpg'
img = cv2.imread(filename)

#Convert image to grayscale and denoise
imgray = cv2.cvtColor(img,cv2.COLOR_BGR2GRAY)
imgray = cv2.fastNlMeansDenoising(imgray, None, 10, 7, 21)

#Adaptive threshold attempt

imgray = cv2.adaptiveThreshold(imgray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2)
imgray = cv2.fastNlMeansDenoising(imgray, None, 10, 7, 21)
cv2.imwrite('planBW.jpg', imgray)

#Binary threshold conversion (0 or 1)
#ret, imgray = cv2.threshold(imgray, 128, 255, cv2.THRESH_BINARY);

ret,thresh = cv2.threshold(imgray,127,255,0)
im2, contours, hierarchy = cv2.findContours(thresh,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)

#cnt = contours[4]
#cv2.drawContours(img, [cnt], 0, (0,255,0), 3)

cv2.drawContours(img, contours, -1, (0,255,0), 3)

cv2.imwrite('planOUT.jpg', img)


import discord
from discord.ext import commands
import asyncio
from discord.utils import get

class Mod(commands.Cog):
    def __init__(self, client):
        self.client = client

    @commands.command(aliases=['k'])
    @commands.has_permissions(kick_members=True)
    async def kick(self, ctx, member: discord.Member=None, *,reason=None):
        if not member:
            embed = discord.Embed(
                title = 'Please specify a member to Kick.',
                color = discord.Color.green()
            )
            await ctx.send(embed=embed)
            return
        await member.kick(reason=reason)
        embed = discord.Embed(
        title = f'Kick',
        color = discord.Color.green()
        )
        embed.add_field(name = "User :", value = f'> {member.name}', inline = 'true')
        embed.add_field(name = "Reason :", value = f'> {reason}', inline = 'true')
        embed.set_thumbnail(url=member.avatar_url)
        await ctx.send(embed=embed)

    @commands.command(aliases=['b'])
    @commands.has_permissions(ban_members=True)
    async def ban(self, ctx, member: discord.Member=None, *,reason=None):
        if not member:
            embed = discord.Embed(
                title = 'Please specify a member to Ban.',
                color = discord.Color.green()
            )
            await ctx.send(embed=embed)
            return
        await member.ban(reason=reason)
        embed = discord.Embed(
        title = f'Ban',
        description = f'```Reason :``` >  **{reason}**',
        color = discord.Color.green()
        )
        embed.add_field(name = "User :", value = f'> {member.name}', inline = 'true')
        embed.add_field(name = "Reason :", value = f'> {reason}', inline = 'true')
        embed.set_thumbnail(url=member.avatar_url)
        await ctx.send(embed=embed)

    @commands.command(aliases=['ub'])
    @commands.has_permissions(ban_members=True)
    async def unban(self, ctx, member:discord.Member, *,reason=None):
        banned_users = await ctx.guild.bans()
        member_name, member_discriminator = member.split('#')

        for ban_entry in banned_users:
            user = ban_entry.user

            if (user.name, user.discriminator) == (member_name, member_discriminator):
                await ctx.guild.unban(user,reason=reason)
                embed = discord.Embed(
                title = f'UnBan',
                description = f'```Reason :``` >  **{reason}**',
                color = discord.Color.green()
                )
                embed.add_field(name = "User :", value = f'> {member.name}', inline = 'true')
                embed.add_field(name = "Reason :", value = f'> {reason}', inline = 'true')
                embed.set_thumbnail(url=member.avatar_url)
                await ctx.send(embed=embed)
                return

    @commands.command(aliases=['m'])
    @commands.has_permissions(kick_members=True)
    async def mute(self, ctx, member: discord.Member=None,mute_minutes=0, *,reason=None):
        if get(ctx.guild.roles, name="Muted"):
            role = discord.utils.get(ctx.guild.roles, name='Muted')
            if not member:
                embed = discord.Embed(
                    title = 'Please specify a member to Mute.',
                    color = discord.Color.green()
                )
                await ctx.send(embed=embed)
                return

            await member.add_roles(role,reason=reason)
            mute = discord.Embed(
            title = f'Muted',
            color = discord.Color.green()
            )
            mute.add_field(name = "User :", value = f'> {member.name}', inline = 'true')
            mute.add_field(name = "Reason :", value = f'> {reason}', inline = 'true')
            mute.add_field(name = "Time :", value = f'> {mute_minutes}m', inline = 'false')
            mute.set_thumbnail(url=member.avatar_url)
            await ctx.send(embed=mute)

            if mute_minutes > 0:
                await asyncio.sleep(mute_minutes * 60)
                await member.remove_roles(role,reason='Times up')
                unmute = discord.Embed(
                    title = f'Unmuted',
                    color = discord.Color.green()
                )
                unmute.add_field(name = "User :", value = f'> {member.name}', inline = 'true')
                unmute.add_field(name = "Reason :", value = f'> Automatic Unmute', inline = 'true')
                unmute.add_field(name = "Time :", value = f'> {mute_minutes}m', inline = 'false')
                unmute.set_thumbnail(url=member.avatar_url)
                await ctx.send(embed=unmute)
        else:
            await ctx.send('A ``Muted`` role was not found in this server. Try Creating one and decline ``Send Message`` permissions to the channels u wish.')
            author = ctx.message.author
            await self.client.create_role(author.guild, name="Muted")

    @commands.command(aliases=['um'])
    @commands.has_permissions(kick_members=True)
    async def unmute(self, ctx, member: discord.Member=None, *,reason=None):
        role = discord.utils.get(ctx.guild.roles, name='Muted')
        if not member:
            await ctx.send('Please specify a member')
            return

        await member.remove_roles(role,reason=reason)
        embed = discord.Embed(
        title = f'Unmuted',

        color = discord.Color.green()
        )
        embed.add_field(name = "User :", value = f'> {member.name}', inline = 'true')
        embed.add_field(name = "Reason :", value = f'> {reason}', inline = 'true')
        embed.set_thumbnail(url=member.avatar_url)
        await ctx.send(embed=embed)

    @commands.command(aliases=['c','clear'])
    @commands.has_permissions(manage_messages=True)
    async def purge(self, ctx, amount=10):
        await ctx.channel.purge(limit=amount)
        embed = discord.Embed(
        title = f'``Logs :`` {ctx.author} deleted {amount} messages',
        color = discord.Color.green()
        )
        await ctx.send(embed=embed,delete_after=3)

def setup(client):
    client.add_cog(Mod(client))


from typing import Set
from pyvalidity.GroupTerm import GroupTerm

import itertools

# The name of this class should probably be "Truncated
# Multiplicatively Closed Set".
class MultiplicativelyClosedSet:
    elements: Set[GroupTerm]
    max_length: int

    # The parameter is_already_closed bypasses the closure procedure, which
    # can make initialisation more efficient in case the input set is already
    # known to be multiplicatively closed.
    def __init__(self,
                 some_set: Set[GroupTerm],
                 max_length: int,
                 is_already_closed=False):
        self.max_length = max_length
        self.elements = some_set.copy()

        if is_already_closed:
            self.unchecked_pairs = []
        else:
            self.unchecked_pairs = itertools.product(self.elements, repeat=2)
            self.close()
        assert self.unchecked_pairs == []

    # seems slow ...
    def close(self):
        while self.unchecked_pairs:
            # sets to avoid duplicate entries
            # but this takes long for > 100 000 unchecked pairs:
            new_elements = {s.times(t) for s, t in self.unchecked_pairs
                            if len(s.times(t)) <= self.max_length
                            and s.times(t) not in self.elements}

            self.elements |= new_elements
            self.unchecked_pairs = []

            # this also takes a while.
            for s in new_elements:
                for t in self.elements:
                    self.unchecked_pairs.append((s, t))
                    if s != t:
                        self.unchecked_pairs.append((t, s))

    def add(self, element: GroupTerm):
        assert self.unchecked_pairs == []
        assert element.__len__() <= self.max_length
        self.unchecked_pairs = [(element, t) for t in self.elements] \
            + [(t, element) for t in self.elements] \
            + [(element, element)]

        self.unchecked_pairs.append((element, element))
        self.elements.add(element)
        self.close()

    def __len__(self):
        return self.elements.__len__()

    def __str__(self):
        return '{' + ', '.join([str(t) for t in self.elements]) + '}'


from .baseCommand import BaseCommand
from .commandCache import CommandCache

from utils import *

from json_minify import json_minify
from mcstatus import MinecraftServer
from nbt import nbt

import json
import os

def first(iterable, condition = lambda x: True):
    for i in iterable:
        if condition(i):
            return i

class KnownLocation:
    """Contains information such as name and 2D bounding box for a known location on the Dugged server."""
    @staticmethod
    def parse(values):
        dimension, location_name, x1, z1, x2, z2 = values

        # Skip locations with incomplete coords
        if x1 != 0 and z1 != 0 and x2 != 0 and z2 != 0:
            min_x = x1 if x1 < x2 else x2
            min_z = z1 if z1 < z2 else z2
            max_x = x2 if x2 > x1 else x1
            max_z = z2 if z2 > z1 else z1

            return KnownLocation(dimension, location_name, min_x, min_z, max_x, max_z)
        return None

    def __init__(self, dimension, location_name, min_x, min_z, max_x, max_z):
        self.dimension = dimension
        self.location_name = location_name
        self.min_x = min_x
        self.min_z = min_z
        self.max_x = max_x
        self.max_z = max_z

    def is_contained(self, x, z):
        # Determine whether the point is contained in the locations 2D bounding box
        return self.min_x <= x <= self.max_x and self.min_z <= z <= self.max_z

class ListCommand(BaseCommand):
    command_text = "!!list"
    dimensions = { -1: 'N', 0: 'O', 1: 'E' }
    known_locations = { -1: [], 0: [], 1: [] }

    def __init__(self, bot, command_cache, minecraft_ip, minecraft_port, current_folder, playerdata_folder):
        super().__init__(bot, command_cache)

        self.minecraft_ip = minecraft_ip
        self.minecraft_port = minecraft_port
        self.current_folder = current_folder
        self.playerdata_folder = playerdata_folder

    def help(self):
        return '`' + self.command_text + '`  **-**  Displays currently online players and their dimension\n'

    async def load_files(self):
        file = os.path.join(self.current_folder,'known_locations.json')

        with open(file, 'r') as f:
            json_data = json.loads(json_minify(f.read(None)))

            for entry in json_data:
                known_location = KnownLocation.parse(entry)

                if known_location != None:
                    ListCommand.known_locations[known_location.dimension].append(known_location)

    async def process(self, message, args):
        try:
            server = MinecraftServer(self.minecraft_ip, self.minecraft_port)
            query = server.query()

            if query.players.online > 0:
                online_list = query.players.names
                text1 = []

                async with CommandCache.semaphore:
                    for item in online_list:
                        nbt_file = nbt.NBTFile(os.path.join(self.playerdata_folder, convert_uuid(self.cache.uuids[self.cache.names.index(item)]) + '.dat'))
                        #nbt_file = nbt.NBTFile(os.path.join(self.playerdata_folder, '33804744-b7a3-4816-8ddc-02ac2d8c80cf.dat'))

                        dimension = nbt_file['Dimension'].value
                        x, y, z = (int(i.value) for i in nbt_file['Pos'])
                        known_location = first(ListCommand.known_locations[dimension], lambda i: i.is_contained(x, z))

                        text1.append('**' + formatNameForEmbed(item) + '** (' + ListCommand.dimensions.get(dimension, '?') + ('' if known_location == None else '@*' + known_location.location_name + '*') + ')')

                if self.bot:
                    await self.bot.send_message(message.channel, 'Players: ' + ', '.join(text1))
                else:
                    print('Players: ' + ', '.join(text1))
            else:
                if self.bot:
                    await self.bot.send_message(message.channel, 'No Player is currently online')
                else:
                    print('No Player is currently online')
        except:
            if self.bot:
                await self.bot.send_message(message.channel, 'An error occured while trying to reach the server')
            else:
                print('An error occured while trying to reach the server')


# -*- coding: utf-8 -*-
"""
Spyder Editor

This is a temporary script file.
"""

import holidayapi

hapi = holidayapi.v1('8172b8cc-45a2-4b5b-b5b3-1dd79030f5de')

parameters = {
	# Required
	'country': 'CO',
	'year':    2017,
	# Optional
	# 'month':    7,
	# 'day':      4,
	# 'previous': True,
	# 'upcoming': True,
	# 'public':   True,
	# 'pretty':   True,
}

holidays2017 = hapi.holidays(parameters)

holidays=holidays2011['holidays'].keys()+holidays2012['holidays'].keys()+holidays2013['holidays'].keys()+holidays2014['holidays'].keys()+holidays2015['holidays'].keys()+holidays2016['holidays'].keys()+holidays2017['holidays'].keys()

import numpy as np

np.savetxt("Holidays.csv", holidays, delimiter=",", fmt='%s')