import customtkinter as ctk
from customtkinter import filedialog
from tkinter import IntVar
from PIL import Image

import hashlib
from pathlib import Path
from datetime import datetime
from time import time, sleep
import requests

# Domain Name for Web Requests
domainName = "https://ransom-detect.cyclic.cloud/"
# domainName = "http://localhost:3000/"

# Frame Foreground Colors
baseFrameFG = "#dfdfdf"
baseFrameChildFG = "#ebebeb"
baseFrameChildBC = "#b7b7b7"
frameLabelFG = "#0078d4"
frameLabelTextColor = "#dce4ee"
frameTextColorPrimary = "grey"
frameTextColorSecondary = "#6f6f6f"

# Setting Appearance Modes
ctk.set_appearance_mode("light")
ctk.set_default_color_theme("blue")

# Initializing App
app = ctk.CTk(fg_color=baseFrameFG)

# Window Geometry & Title
app.title("Ransomware Detection")
app.resizable(False, False)
app.geometry("900x500")

# Custom Fonts
fontFrameLabelBold = ctk.CTkFont("Segoe UI", 13, "bold")
fontFrameLabel = ctk.CTkFont("Segoe UI", 13)
baseFramePadY = (10, 10)
baseFramePadX = (10, 10)

# Base Frames
baseFrameLeft = ctk.CTkFrame(app, corner_radius=12, fg_color=baseFrameFG)
baseFrameLeft.pack(side="left", fill="both", pady=baseFramePadY, padx=baseFramePadX)

baseFrameMid = ctk.CTkFrame(app, width=400, fg_color=baseFrameFG)
baseFrameMid.pack(side="left", fill="both", expand=True, pady=baseFramePadY)

baseFrameRight = ctk.CTkFrame(app, corner_radius=12, fg_color=baseFrameFG)
baseFrameRight.pack(side="left", fill="both", pady=baseFramePadY, padx=baseFramePadX)

# Child Frames of baseFrameLeft
baseFrameLeft1 = ctk.CTkFrame(
    baseFrameLeft,
    corner_radius=12,
    fg_color=baseFrameChildFG,
    border_color=baseFrameChildBC,
    border_width=1,
    height=225,
)
baseFrameLeft1.pack(side="top", fill="both", pady=(0, 5))

baseFrameLeft2 = ctk.CTkFrame(
    baseFrameLeft,
    corner_radius=12,
    fg_color=baseFrameChildFG,
    border_color=baseFrameChildBC,
    border_width=1,
)
baseFrameLeft2.pack(side="top", fill="both", pady=(5, 0))

# Child frames of baseFrameMid
baseFrameMid1 = ctk.CTkFrame(
    baseFrameMid,
    corner_radius=12,
    fg_color=baseFrameChildFG,
    border_color=baseFrameChildBC,
    border_width=1,
    height=185,
    width=400,
)
baseFrameMid1.pack(side="top", fill="x", pady=(0, 5))

baseFrameMid2 = ctk.CTkFrame(
    baseFrameMid,
    corner_radius=12,
    fg_color=baseFrameChildFG,
    border_color=baseFrameChildBC,
    border_width=1,
    height=130,
    width=400,
)
baseFrameMid2.pack(side="top", fill="x", pady=(5, 5))

baseFrameMid3 = ctk.CTkFrame(
    baseFrameMid,
    corner_radius=12,
    fg_color=baseFrameChildFG,
    border_color=baseFrameChildBC,
    border_width=1,
    height=155,
    width=400,
)
baseFrameMid3.pack(side="top", fill="x", pady=(5, 5))

# Input File Details Pane..
frameLabelLeft2 = ctk.CTkLabel(
    baseFrameLeft1,
    text="Selected Files.",
    font=fontFrameLabelBold,
    width=100,
    height=30,
    fg_color="white",
    text_color=frameTextColorPrimary,
    padx=10,
    corner_radius=8,
)
frameLabelLeft2.place(x=70, y=10)

frameLabelLeft1 = ctk.CTkLabel(
    baseFrameLeft1,
    text="Details",
    font=fontFrameLabelBold,
    width=60,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    padx=10,
    corner_radius=8,
)
frameLabelLeft1.place(x=0, y=10)

detailLabelName = ctk.CTkLabel(
    baseFrameLeft1,
    text="Name:",
    font=fontFrameLabelBold,
    width=30,
    height=20,
    text_color=frameTextColorSecondary,
)
detailLabelName.place(x=10, y=50)

detailLabelExten = ctk.CTkLabel(
    baseFrameLeft1,
    text="Extension:",
    font=fontFrameLabelBold,
    width=30,
    height=20,
    text_color=frameTextColorSecondary,
)
detailLabelExten.place(x=10, y=70)

detailLabelAccess = ctk.CTkLabel(
    baseFrameLeft1,
    text="Creation:",
    font=fontFrameLabelBold,
    width=30,
    height=20,
    text_color=frameTextColorSecondary,
)
detailLabelAccess.place(x=10, y=90)

detailLabelSize = ctk.CTkLabel(
    baseFrameLeft1,
    text="Size:",
    font=fontFrameLabelBold,
    width=30,
    height=20,
    text_color=frameTextColorSecondary,
)
detailLabelSize.place(x=10, y=110)

detailLabelSHA = ctk.CTkLabel(
    baseFrameLeft1,
    text="SHA-256:",
    font=fontFrameLabelBold,
    width=30,
    height=20,
    text_color=frameTextColorSecondary,
)
detailLabelSHA.place(x=10, y=130)

# Actual Detail Values (Updated Dynamically)

detailValueName = ctk.CTkLabel(
    baseFrameLeft1,
    text="No File Selected.",
    font=fontFrameLabel,
    height=20,
    text_color=frameTextColorSecondary,
)
detailValueName.place(x=60, y=50)

detailValueExten = ctk.CTkLabel(
    baseFrameLeft1,
    text="No File Selected.",
    font=fontFrameLabel,
    height=20,
    text_color=frameTextColorSecondary,
)
detailValueExten.place(x=80, y=70)

detailValueCreation = ctk.CTkLabel(
    baseFrameLeft1,
    text="No File Selected.",
    font=fontFrameLabel,
    height=20,
    text_color=frameTextColorSecondary,
)
detailValueCreation.place(x=70, y=90)

detailValueSize = ctk.CTkLabel(
    baseFrameLeft1,
    text="No File Selected.",
    font=fontFrameLabel,
    height=20,
    text_color=frameTextColorSecondary,
)
detailValueSize.place(x=45, y=110)

detailValueSHA = ctk.CTkLabel(
    baseFrameLeft1,
    text="No File Selected.",
    font=fontFrameLabel,
    height=20,
    text_color=frameTextColorSecondary,
)
detailValueSHA.place(x=10, y=150)

def getFileStats(path):
    file = Path(path)
    detailValueName.configure(text=file.name)
    detailValueExten.configure(text=file.suffix)
    detailValueCreation.configure(
        text=datetime.strftime(
            datetime.fromtimestamp(file.stat().st_ctime), "%d-%m-%Y, %H:%M"
        )
    )
    detailValueSize.configure(text=file.stat().st_size)
    detailValueSHA.configure(text=hashlib.sha256(file.read_bytes()).hexdigest())

def getPrevFileStats():
    currentFileIndex = inputFiles.index(inputComboSelected.get())
    if currentFileIndex == 0:
        currentFileIndex = len(inputFiles) - 1
    else:
        currentFileIndex -= 1

    inputComboSelected.set(inputFiles[currentFileIndex])
    getFileStats(inputFiles[currentFileIndex])

def getNextFileStats():
    currentFileIndex = inputFiles.index(inputComboSelected.get())
    if currentFileIndex == len(inputFiles) - 1:
        currentFileIndex = 0
    else:
        currentFileIndex += 1

    inputComboSelected.set(inputFiles[currentFileIndex])
    getFileStats(inputFiles[currentFileIndex])

filePrevButton = ctk.CTkButton(
    baseFrameLeft1,
    corner_radius=4,
    text="<",
    width=35,
    height=30,
    fg_color=frameLabelFG,
    anchor="center",
    state="disabled",
    font=ctk.CTkFont("Arial", 15, "bold"),
    command=getPrevFileStats,
)
filePrevButton.place(x=10, y=185)

fileNextButton = ctk.CTkButton(
    baseFrameLeft1,
    corner_radius=4,
    text=">",
    width=35,
    height=30,
    fg_color=frameLabelFG,
    anchor="center",
    state="disabled",
    font=ctk.CTkFont("Arial", 15, "bold"),
    command=getNextFileStats,
)
fileNextButton.place(x=155, y=185)

# Input Frame Content
frameLabelMid2 = ctk.CTkLabel(
    baseFrameMid1,
    text="Select one or more files for input.",
    font=fontFrameLabelBold,
    width=250,
    height=30,
    fg_color="white",
    text_color=frameTextColorPrimary,
    padx=10,
    corner_radius=8,
)
frameLabelMid2.place(x=65, y=10)

frameLabelMid1 = ctk.CTkLabel(
    baseFrameMid1,
    text="Input",
    font=fontFrameLabelBold,
    width=75,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    corner_radius=4,
)
frameLabelMid1.place(x=0, y=10)

def comboBoxHandler(choice):
    getFileStats(choice)

inputFiles = []
inputComboSelected = ctk.StringVar(value="")

# Add Callback later for deleting entries...
inputComboBox = ctk.CTkComboBox(
    baseFrameMid1,
    width=320,
    height=30,
    values=inputFiles,
    state="disabled",
    variable=inputComboSelected,
    font=fontFrameLabel,
    dropdown_font=fontFrameLabel,
    command=comboBoxHandler,
)
inputComboBox.place(x=15, y=55)

def openFile():
    global inputFiles
    inputFiles = list(filedialog.askopenfilenames())
    renderActiveDisplay("scan")
    resultNextButton.configure(state="disabled")

    if len(inputFiles) != 0:
        inputComboBox.configure(
            values=inputFiles, state="normal", button_color=frameLabelFG
        )
        if len(inputFiles) == 1:
            frameLabelLeft2.configure(text="Selected File.")
            fileNextButton.configure(state="disabled")
            filePrevButton.configure(state="disabled")
        else:
            fileNextButton.configure(state="normal")
            filePrevButton.configure(state="normal")

        inputComboSelected.set(inputFiles[0])
        getFileStats(inputComboSelected.get())

chooseButton = ctk.CTkButton(
    baseFrameMid1,
    corner_radius=6,
    text="Select",
    width=70,
    height=30,
    fg_color=frameLabelFG,
    anchor="center",
    font=fontFrameLabelBold,
    command=openFile,
)
chooseButton.place(x=345, y=55)

# Input Frame Scan Options..
frameLabelMid4 = ctk.CTkLabel(
    baseFrameMid1,
    text="Choose scan options.",
    font=fontFrameLabelBold,
    width=150,
    height=30,
    fg_color="white",
    text_color=frameTextColorPrimary,
    corner_radius=8,
    padx=10,
)
frameLabelMid4.place(x=70, y=100)

frameLabelMid3 = ctk.CTkLabel(
    baseFrameMid1,
    text="Options",
    font=fontFrameLabelBold,
    width=75,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    corner_radius=4,
)
frameLabelMid3.place(x=0, y=100)

def scanRadioHandler():
    if scanRadio_var.get() == 2:
        resultNextButton.place(x=160, y=10)
    else:
        resultNextButton.place_forget()

# Scan Option Radio Buttons...
scanRadio_var = IntVar(value=1)
scanRadioSingle = ctk.CTkRadioButton(
    baseFrameMid1,
    text="Single File Scan",
    variable=scanRadio_var,
    value=1,
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    command=scanRadioHandler,
)
scanRadioSingle.place(x=20, y=148)

scanRadioMultiple = ctk.CTkRadioButton(
    baseFrameMid1,
    text="Multiple File Scan",
    variable=scanRadio_var,
    value=2,
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    command=scanRadioHandler,
)
scanRadioMultiple.place(x=170, y=148)

# Progress Bar Rendering...
frameLabelMid22 = ctk.CTkLabel(
    baseFrameMid2,
    text="Awaiting file input.",
    font=fontFrameLabelBold,
    width=150,
    height=30,
    fg_color="white",
    anchor='w',
    text_color=frameTextColorPrimary,
    corner_radius=8,
    padx=10,
)
frameLabelMid22.place(x=70, y=12)

frameLabelMid21 = ctk.CTkLabel(
    baseFrameMid2,
    text="Status",
    font=fontFrameLabelBold,
    width=75,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    corner_radius=4,
)
frameLabelMid21.place(x=0, y=12)

statusProgressDesc = ctk.CTkLabel(
    baseFrameMid2,
    text="Idle",
    width=200,
    anchor='w',
    text_color=frameTextColorSecondary,
    font=fontFrameLabelBold,
)
statusProgressDesc.place(x=15, y=47)

statusProgressBar = ctk.CTkProgressBar(
    baseFrameMid2,
    width=400,
    height=30,
    orientation="horizontal",
    fg_color="white",
    corner_radius=1,
    mode="determinate",
    progress_color="#007edf",
    border_color="grey",
    border_width=1,
)
statusProgressBar.set(0)
statusProgressBar.place(x=15, y=77)

# Backup Option Rendering...
frameLabelMid32 = ctk.CTkLabel(
    baseFrameMid3,
    text="Choose Backup Options.",
    font=fontFrameLabelBold,
    height=30,
    fg_color="white",
    anchor='w',
    text_color=frameTextColorPrimary,
    corner_radius=8,
    padx=10,
)
frameLabelMid32.place(x=70, y=12)

frameLabelMid31 = ctk.CTkLabel(
    baseFrameMid3,
    text="Backup",
    font=fontFrameLabelBold,
    width=75,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    corner_radius=4,
)
frameLabelMid31.place(x=0, y=12)

# Results Rendering...
scanResults = {}
elapseStart = 0
currentActiveDisplay = None

def renderActiveDisplay(display, results=None):
    global elapseEnd
    global currentActiveDisplay
    if display == "scan":
        if currentActiveDisplay == "scan":
            updateServerStats()
            return
        currentActiveDisplay = "scan"
        resultsImage = ctk.CTkImage(
            Image.open(__file__ + "/../../../assets/img/default.png"),
            size=(125, 125),
        )
        resultsImageLabel.configure(image=resultsImage, text="")
        resultsLabel.configure(text="Scan", text_color="#017dcc")
        resultsLabel.pack_forget()
        resultsLabel.place(x=55, y=195)
        resultsSubLabel.configure(text="Results appear here.")
        resultsScanDetailsSubLabel.configure(text="Server Stats.")
        serverStatsLabelStatus.configure(text="Status:")
        serverStatsLabelConn.configure(text="Connection:")
        serverStatsLabelUptime.configure(text="Uptime:")
        serverStatsLabelSamples.configure(text="Samples:")
        serverStatsLabelDomain.configure(text="Domain:")
        serverStatsLabelBackend.configure(text="Backend:")
        updateServerStats()
    elif display == "results":
        currentActiveDisplay = "results"

        resultsScanDetailsSubLabel.configure(text="Scan Details.")
        serverStatsLabelStatus.configure(text="Name:")
        serverStatsLabelConn.configure(text="Status:")
        serverStatsLabelUptime.configure(text="Ransomware:")
        serverStatsLabelSamples.configure(text="Severity:")
        serverStatsLabelDomain.configure(text="Elapsed Time:")
        serverStatsLabelBackend.configure(text="Size:")

        serverStatsStatus.place_forget()
        serverStatsConn.place_forget()
        serverStatsUptime.place_forget()
        serverStatsSamples.place_forget()
        serverStatsDomain.place_forget()
        serverStatsBackend.place_forget()

        if results["isSafe"]:
            resultsImage = ctk.CTkImage(
                Image.open(__file__ + "/../../../assets/img/safe.png"),
                size=(125, 125),
            )
            resultsImageLabel.configure(image=resultsImage, text="")
            resultsLabel.configure(text="Safe", text_color="#017dcc")
            resultsLabel.pack_forget()
            resultsLabel.place(x=55, y=195)
            resultsSubLabel.configure(text="The file looks Safe.")

            elapseEnd = time()
            elapse = elapseEnd - elapseStart

            serverStatsStatus.configure(text=results["fileName"])
            serverStatsConn.configure(text="Safe")
            serverStatsUptime.configure(text="No")
            serverStatsSamples.configure(text="None")
            serverStatsDomain.configure(text=str(round(elapse, 3)) + "s")
            serverStatsBackend.configure(text=str(results["fileSize"]) + " bytes")

        else:
            resultsImage = ctk.CTkImage(
                Image.open(__file__ + "/../../../assets/img/unsafe.png"),
                size=(125, 125),
            )
            resultsImageLabel.configure(image=resultsImage, text="")
            resultsLabel.configure(text="Alert", text_color="#ea510a")
            resultsLabel.pack_forget()
            resultsLabel.place(x=52, y=195)
            resultsSubLabel.configure(text="The file isn't Safe.")

            serverStatsStatus.configure(text=results["fileName"])
            serverStatsConn.configure(text="Malicious")
            serverStatsUptime.configure(text="Yes")
            elapseEnd = time()
            elapse = elapseEnd - elapseStart
            severity = "Medium" if results["size"] < 20000 else "High"
            serverStatsSamples.configure(text=severity)
            serverStatsDomain.configure(text=str(round(elapse, 3)) + "s")
            serverStatsBackend.configure(text=str(results["fileSize"]) + " bytes")

        serverStatsStatus.place(x=56, y=330)
        serverStatsConn.place(x=56, y=350)
        serverStatsUptime.place(x=96, y=370)
        serverStatsSamples.place(x=68, y=390)
        serverStatsBackend.place(x=42, y=410)
        serverStatsDomain.place(x=100, y=430)

def delay(seconds):
    app.update_idletasks()
    sleep(seconds)

progressSteps = 20
progressStepDelay = 0.015

# Computationally Expensive...
def setProgress(value, status=None, label=None):
    if label != None:
        # frameLabelMid22.configure(text="")
        # delay(0.001)
        frameLabelMid22.configure(
            text=label,
            width=150,
            height=30,
            anchor='w'
        )

    if status != None:
        statusProgressDesc.configure(
            text=status,
            width=200,
            anchor='w'
        )

    # Incrementing in 50 steps to get a animation effect...
    incrementValue = (value - statusProgressBar.get()) / progressSteps
    for i in range(progressSteps):
        currentValue = statusProgressBar.get()
        statusProgressBar.set(value=currentValue + incrementValue)
        delay(progressStepDelay)

def scanNowHandler():
    if len(inputFiles) == 0:
        return

    # Calculate elapsed Time
    global elapseStart
    elapseStart = time()

    setProgress(0.2, "Starting Scan..", "Processing..")
    if scanRadio_var.get() == 1:
        # Single File Scan
        inputPath = inputComboSelected.get()
        fileSample = Path(inputPath)
        hashObj = {
            "filename": f"{fileSample.name}",
            "size": fileSample.stat().st_size,
            "hash": f"{hashlib.sha256(fileSample.read_bytes()).hexdigest()}",
        }
        setProgress(0.4, "Scanning File..")
        # hashObj = {
        #     "filename": f'{fileSample.name}',
        #     "size": fileSample.stat().st_size,
        #     "hash": "acd7bea8e0e6e76c5ad0498411812695ea5a9809c8e75fdb5b149d3c3b99190c"
        # }
        singleResponse = requests.post(domainName + "scan", json=hashObj)
        scanResults[inputPath] = singleResponse.json()
        setProgress(0.8, "Rendering Results..")
        renderActiveDisplay("results", scanResults[inputPath])
        setProgress(1, "Done.", "Complete")
    else:
        # Multi File Scan
        if len(inputFiles) == 1:
            scanRadio_var.set(1)
            scanNowHandler()
            scanRadioHandler()
            return

        alertingResults = False
        alertCount = 0

        setProgress(0.4, "Scanning Multiple Files...")
        for inputPath in inputFiles:
            fileSample = Path(inputPath)
            hashObj = {
                "filename": f"{fileSample.name}",
                "size": fileSample.stat().st_size,
                "hash": f"{hashlib.sha256(fileSample.read_bytes()).hexdigest()}",
            }
            singleResponse = requests.post(domainName + "scan", json=hashObj)
            # Don't Update Continually, just store the results...
            scanResults[inputPath] = singleResponse.json()
            if scanResults[inputPath]["isSafe"] == False:
                alertingResults = True
                alertCount += 1

        setProgress(0.8, "Rendering Results..")
        renderActiveDisplay("results", scanResults[inputComboSelected.get()])
        setProgress(1, "Done.", "Complete.")

        if alertingResults:
            resultNextButton.configure(
                state="normal", fg_color="#ea510a", text=str(alertCount)
            )
        else:
            resultNextButton.configure(state="normal")

    delay(3)
    statusProgressBar.set(0)
    statusProgressDesc.configure(text="Idle")
    frameLabelMid22.configure(text="Awaiting file input.")

scanNowButton = ctk.CTkButton(
    baseFrameMid1,
    fg_color=frameLabelFG,
    text="Scan",
    font=fontFrameLabelBold,
    width=90,
    height=35,
    command=scanNowHandler,
)
scanNowButton.place(x=345, y=143)

# Results Frame...

baseFrameRight1 = ctk.CTkFrame(
    baseFrameRight,
    corner_radius=12,
    fg_color=baseFrameChildFG,
    border_color=baseFrameChildBC,
    border_width=1,
)
baseFrameRight1.pack(side="top", expand=True, fill="both")

frameLabelRight1 = ctk.CTkLabel(
    baseFrameRight1,
    text="Results",
    font=fontFrameLabelBold,
    width=80,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    corner_radius=4,
)
frameLabelRight1.place(x=0, y=10)

def getNextResult():
    currentFileIndex = inputFiles.index(inputComboSelected.get())
    if currentFileIndex == len(inputFiles) - 1:
        currentFileIndex = 0
    else:
        currentFileIndex += 1

    inputComboSelected.set(inputFiles[currentFileIndex])
    getFileStats(inputFiles[currentFileIndex])
    renderActiveDisplay("results", scanResults[inputFiles[currentFileIndex]])
    return

# Next Button for Multi Results...
resultNextButton = ctk.CTkButton(
    baseFrameRight1,
    corner_radius=4,
    text=">",
    width=35,
    height=30,
    fg_color=frameLabelFG,
    anchor="center",
    state="disabled",
    font=ctk.CTkFont("Arial", 15, "bold"),
    command=getNextResult,
)

resultsImage = ctk.CTkImage(
    Image.open(__file__ + "/../../../assets/img/default.png"), size=(125, 125)
)
resultsImageLabel = ctk.CTkLabel(baseFrameRight1, image=resultsImage, text="")
resultsImageLabel.place(x=35, y=60)

resultsLabel = ctk.CTkLabel(
    baseFrameRight1,
    text="Scan",
    width=100,
    text_color="#017dcc",
    font=ctk.CTkFont("Segoe UI", 34, "bold"),
)
resultsLabel.place(x=55, y=195)

resultsSubLabel = ctk.CTkLabel(
    baseFrameRight1,
    text="Results appear here.",
    text_color=frameTextColorSecondary,
    font=fontFrameLabelBold,
)
resultsSubLabel.place(x=45, y=237)

resultsScanDetailsSubLabel = ctk.CTkLabel(
    baseFrameRight1,
    text="Server Stats",
    font=fontFrameLabelBold,
    width=120,
    height=30,
    fg_color="white",
    text_color=frameTextColorPrimary,
    corner_radius=4,
)
resultsScanDetailsSubLabel.place(x=68, y=290)

resultsScanDetailsLabel = ctk.CTkLabel(
    baseFrameRight1,
    text="Info.",
    font=fontFrameLabelBold,
    width=75,
    height=30,
    fg_color=frameLabelFG,
    text_color=frameLabelTextColor,
    corner_radius=4,
)
resultsScanDetailsLabel.place(x=0, y=290)

# Server Statistics...

serverStatsLabelStatus = ctk.CTkLabel(
    baseFrameRight1,
    text="Status:",
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsLabelStatus.place(x=10, y=330)

serverStatsLabelConn = ctk.CTkLabel(
    baseFrameRight1,
    text="Connection:",
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsLabelConn.place(x=10, y=350)

serverStatsLabelUptime = ctk.CTkLabel(
    baseFrameRight1,
    text="Uptime:",
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsLabelUptime.place(x=10, y=370)

serverStatsLabelSamples = ctk.CTkLabel(
    baseFrameRight1,
    text="Samples:",
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsLabelSamples.place(x=10, y=390)

serverStatsLabelDomain = ctk.CTkLabel(
    baseFrameRight1,
    text="Domain:",
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsLabelDomain.place(x=10, y=430)

serverStatsLabelBackend = ctk.CTkLabel(
    baseFrameRight1,
    text="Backend:",
    font=fontFrameLabelBold,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsLabelBackend.place(x=10, y=410)

# Server Stats Actual Values
serverStatsStatus = ctk.CTkLabel(
    baseFrameRight1,
    text="Pending..",
    font=fontFrameLabel,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsStatus.place(x=60, y=330)

serverStatsConn = ctk.CTkLabel(
    baseFrameRight1,
    text="Pending..",
    font=fontFrameLabel,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsConn.place(x=93, y=350)

serverStatsUptime = ctk.CTkLabel(
    baseFrameRight1,
    text="Pending..",
    font=fontFrameLabel,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsUptime.place(x=68, y=370)

serverStatsSamples = ctk.CTkLabel(
    baseFrameRight1,
    text="Pending..",
    font=fontFrameLabel,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsSamples.place(x=73, y=390)

serverStatsDomain = ctk.CTkLabel(
    baseFrameRight1,
    text="Pending..",
    font=fontFrameLabel,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsDomain.place(x=12, y=450)

serverStatsBackend = ctk.CTkLabel(
    baseFrameRight1,
    text="Pending..",
    font=fontFrameLabel,
    text_color=frameTextColorSecondary,
    height=20,
)
serverStatsBackend.place(x=73, y=410)

def updateServerStats():
    response = {}
    try:
        response = requests.get(domainName + "initial").json()
    except:
        response = {
            "status": "Offline",
            "connection": "Failed",
            "uptime": "Unknown",
            "samples": "Unknown",
            "domain": "Unknown",
            "backend": "Unknown",
        }
    finally:
        serverStatsStatus.place_forget()
        serverStatsConn.place_forget()
        serverStatsUptime.place_forget()
        serverStatsSamples.place_forget()
        serverStatsDomain.place_forget()
        serverStatsBackend.place_forget()
        app.update_idletasks()

        serverStatsStatus.configure(text=response["status"])
        serverStatsConn.configure(text=response["connection"])
        serverStatsUptime.configure(text=response["uptime"])
        serverStatsSamples.configure(text=response["samples"])
        serverStatsDomain.configure(text=response["domain"])
        serverStatsBackend.configure(text=response["backend"])

        serverStatsStatus.place(x=60, y=330)
        serverStatsConn.place(x=93, y=350)
        serverStatsUptime.place(x=68, y=370)
        serverStatsSamples.place(x=73, y=390)
        serverStatsBackend.place(x=73, y=410)
        serverStatsDomain.place(x=10, y=449)

renderActiveDisplay("scan")

app.mainloop()

# Old Code...

# app.option_add("*Font", 'SegoeUIVariable')

# app.geometry("800x500")

# app.wm_attributes('-transparentcolor','black')

# transPath = __file__ + '/../../../assets/img/transparent.png'

# # Images
# transImg = Image.open(transPath)
# resTransImg = transImg.resize((100,100))
# transBg = ImageTk.PhotoImage(resTransImg)
# transBg1 = ctk.CTkImage.

# # Frames
# baseFrameLeft1 = ctk.CTkFrame(
#     app,
#     corner_radius=12,
#     border_color="black",
#     border_width=1,
#     width=200,
#     height=205,
#
# ).grid(row=0, column=0, padx=(10, 12), pady=(10, 5))

# baseFrameLeft2 = ctk.CTkFrame(
#     app,
#     corner_radius=12,
#     border_color="black",
#     border_width=1,
#     width=200,
#     height=205,
#
# ).grid(row=1, column=0, padx=(10, 12), pady=(5, 10))

# baseFrameMid = ctk.CTkFrame(
#     app, corner_radius=16, width=400, height=430, fg_color="#242424"
# ).grid(row=0, column=1, rowspan=2)

# baseFrameRight = ctk.CTkFrame(
#     app, corner_radius=16, border_color="blue", border_width=1, width=200, height=430
# ).grid(row=0, column=2, rowspan=2, padx=(10, 12), pady=(10, 10))

# # BaseFrameLeft Content
# labelInputDetails = ctk.CTkLabel(
#     baseFrameLeft1, text="Input Details", width=90, font=fontFrameLabelBold
# )
# labelInputDetails.place(x=25, y=0)

# labelBackupOptions = ctk.CTkLabel(
#     baseFrameLeft2, text="Backup Options", width=107, font=fontFrameLabelBold
# )
# labelBackupOptions.place(x=25, y=221)

# # BaseFrameMid Content
# bfmFrameTop = ctk.CTkFrame(
#     baseFrameMid,
#     corner_radius=16,
#     border_color="blue",
#     border_width=1,
# )
# bfmFrameTop.place(side="top")


from bs4 import BeautifulSoup as bs
from urllib import request
from functools import lru_cache
from transformers import pipeline
import data
import requests
import unicodedata
import re
import spacy

# Loading spacy
nlp = spacy.load("en_core_web_sm")

# Class representing a recipe
class Recipe:

	# Class representing a node in a step graph
	class Step:

		def __init__(self, sentence=None):

			# Null/empty step
			if sentence is None:
				self.text = ''
				self.actions, self.ingredients, self.tools = [], [], []
				self.new_text = ''
				self.time = None

			# Parsing step
			else:
				self.text = ' '.join([self.convert_fraction(x) for x in sentence.split()])
				self.tokens = nlp(sentence)
				self.actions, self.ingredients, self.tools, self.valid = self.get_info()
				self.new_text = ' '.join([self.convert_fraction(x) for x in sentence.split()])
				self.time = self.get_time()
				self.temp = self.get_temp()
				self.quantities = self.get_quantities()

		def __str__(self):
			return f'(Actions: {self.actions} | Ingredients: {self.ingredients} | Tools: {self.tools} | Time: {self.time})'

		def __repr__(self):
			return str(self)

		# Filling step info
		def get_info(self):

			# Getting items from data, then querying for missing items
			actions, ingredients, tools = self.from_data()

			# QUERYING ITEMS IN THE TEXT
			#actions, ingredients, tools = self.query(actions, ingredients, tools)

			valid = True if (actions and (ingredients or tools)) else False

			return actions, ingredients, tools, valid

		# Grabbing potential data from hardcoded lists
		def from_data(self):
			# Getting actions
			actions = [w.text for w in self.tokens if w.lemma_ in data.cooking_methods]

			# Getting ingredients
			ingredients = [w.text for w in self.tokens if w.lemma_ in data.all_ingredients]

			# Getting tools
			tools = [w.text for w in self.tokens if w.lemma_ in data.tools]

			return actions, ingredients, tools

		# Finding more data with queries
		def query(self, actions, ingredients, tools):

			# Given a question as a string, returns an answer and a confidence in that answer
			def answer(question):
				qdict = {
					'question': question,
					'context': self.text
				}

				return query(qdict)['answer'], query(qdict)['score']

			def append_answer(l, question, threshold=0):
				item, confidence = answer(question)
				if confidence > threshold and len(item.split()) < 4:
					l.append((item, confidence))

			# Querying actions
			if not actions:

				# Querying using ingredients
				for ingredient in ingredients:
					question = f'do what with {ingredient}?'
					append_answer(actions, question)

				# Querying using tools
				for tool in tools:
					question = f'do what with {tool}?'
					append_answer(actions, question)

			# Deleting duplicates
			actions = list(set(actions))

			# Querying ingredients
			if not ingredients:

				for action in actions:
					question = f'{action} what?'
					append_answer(ingredients, question)

			ingredients = list(set(ingredients))

			# Querying tools
			if not tools:

				for action in actions:
					for ingredient in ingredients:
						question = f'{action} {ingredient} in a what?'
						append_answer(tools, question)

			tools = list(set(tools))

			return actions, ingredients, tools

		def convert_fraction(self, word):
			if self.is_vulgar_fraction(word[-1]):

				number = unicodedata.numeric(word[-1])
				if len(word) > 1:
					if word[:-2].isnumeric():
						number += float(word[:-2])
					else:
						return word

				return str(int(number)) if number%1==0 else str(round(float(number), 2))

			elif re.match(r'[0-9]+/[0-9]+', word):
				word = word.split('/')
				return str(round(float(word[0]) / float(word[1]), 2))

			return word

		def is_vulgar_fraction(self, character):
			try:
				unicodedata.numeric(character)
				try:
					float(character)
					return False
				except ValueError:
					return True
			except ValueError:
				return False

		# Returns the time component of the step, or unknown
		def get_time(self):

			seconds = re.compile(r'[1-9]+[0-9]* [sS]econds?')
			minutes = re.compile(r'[1-9]+[0-9]* [mM]inutes?')
			hours = re.compile(r'[1-9]+[0-9]* [hH]ours?')

			result = ''
			for time_pattern in [hours, minutes, seconds]:
				match = time_pattern.search(self.text)
				if match:
					start = match.span()[0]
					end = match.span()[1]

					result += self.text[start:end] + ' '

			return result.strip() if result else 'None Specified'

		def get_temp(self):
			temp = re.compile(r'[1-9]+[0-9]* [dD]egrees? [CF]')
			temp2 = re.compile(r'[rR]oom [tT]emperature')

			for temp_pattern in [temp, temp2]:
				match = temp_pattern.search(self.text)
				if match:
					start = match.span()[0]
					end = match.span()[1]
					return self.text[start:end]

		def get_quantities(self):
			quantities = []
			words = self.text.split()
			found_number = False
			number = -1
			for word in words:
				if not found_number:
					if re.match(r'([0-9]+\.?[0-9]*|\.[0-9]+)', word):
						number = word
						found_number = True
				# last word was a number, check if this is a measurement
				else:
					# number + measurement
					if word in data.measurements:
						full_string = number + ' ' + word
						quantities.append(full_string)
						found_number = False
					else:
						# number + nonmeasurement
						found_number = False
			# this is not done yet
			return quantities

	def __init__(self, url):

		# Store raw HTML
		html_doc = request.urlopen(url)
		self.soup = bs(html_doc, 'html.parser')

		# Getting the Recipe name
		title = self.soup.find('title')
		if ' | Allrecipes' in title.text:
			title = title.text[:-13]

		self.recipe_name = title

		# Getting the ingredients
		self.ingredients = self.get_ingredients()

		# All of the text for a recipe's steps
		self.text = [div.text for div in self.soup.find_all('div', {'class': 'paragraph'})]

		# Parsing the steps
		self.steps = self.get_steps()

		# Getting tools and actions
		self.tools = self.named_tools()
		self.potential_main_actions = self.pmActions()
		self.main_actions = self.mActions()
		self.update_ingredient_indices()
		self.total_time = self.get_total_time()

	def get_total_time(self):
		find_times = self.soup.find_all('div', class_='recipe-meta-item-body')
		times = []
		#This is just a default time, it will never be seen
		total_time = "1 hour"
		for time in find_times:
			if 'mins' in time.text or 'hour' in time.text or 'day' in time.text or 'hr' in time.text:
				times.append(time.text)
		if len(times) == 0:
			total_time = "No Total Time was reported in this recipe"
		else:
			total_time = times[-1]
		return total_time

	def find_tags(self):
		find_tag_list = self.soup.find('script', id='karma-loader')
		find_tag_list = str(find_tag_list).split()
		append_to_list = False
		tags = []
		for item in find_tag_list:
		    item = item.replace('"','')
		    item = item.replace(',','')
		    if append_to_list and '[' not in item:
		        if ']' in item:
		            break
		        tags.append(item)
		    if 'tags:' == item:
		        append_to_list = True
		return tags

	#Identifies Potential Main Actions:
	def pmActions(self):
		first_words = []
		potential_main_actions = []
		for step in self.steps:
			each_step = step.text.split()
			first_words.append(each_step[0])
		for word in first_words:
			if word.lower() in data.cooking_methods:
				potential_main_actions.append(word)
		pma = []
		for i in potential_main_actions:
			if i.lower() not in pma:
				pma.append(i.lower())
		return pma

	def update_ingredient_indices(self):
		self.ingredient_indices = {}
		for i in range(len(self.ingredients)):
			ing = self.ingredients[i]
			self.ingredient_indices[ing['name']] = i

	#Identifies Likely main actions
	def mActions(self):
		action_list = ['cook', 'bake', 'fry', 'roast', 'grill', 'steam', 'poach', 'simmer', 'broil', 'blanch', 'braise', 'stew']
		main_actions = []
		for action in self.potential_main_actions:
			if action.lower() in action_list:
				main_actions.append(action)
		ma = []
		for i in main_actions:
			if i.lower() not in ma:
				ma.append(i.lower())
		return ma

	def named_tools(self):
		tools = []
		for step in self.steps:
			for tool in data.tools:
				if tool in step.text:
					tools.append(tool)
		t = []
		for i in tools:
			if i.lower() not in t:
				t.append(i.lower())
		return t

	@staticmethod
	def clean_split(string, seps):
		result = [string]
		for sep in seps:
			new = result[:]
			result = []
			for phrase in new:
				new_phrase = phrase.split(sep)
				if sep.isalpha() and len(new_phrase) > 1:
					new_phrase = [new_phrase[0]] + [sep + x for x in new_phrase[1:]]
				result += new_phrase

		if result[-1].strip() == 'or':
			result = result[:-1]
		return [x.strip() for x in result if x != ' ']

	# Given a list of words, cleans substeps of phrases that contain those words
	def clean_substeps(self, to_clean):
		# Getting substeps
		cleaned = []
		for i, step in enumerate(self.steps):
			substeps = step.text.split('.')
			for j, substep in enumerate(substeps):
				phrases = self.clean_split(substep, [',', ';', 'until', ', and'])
				for word in to_clean:
					phrases = [phrase for phrase in phrases if word not in phrase]
				substeps[j] = ', '.join(phrases)
				if 'should read' in substeps[j] and 'degree' in substeps[j] and 'thermometer' not in substeps[j]:
					substeps[j] = ''

			step = '. '.join(substeps)
			if len(step.strip()) == 0:
				self.steps[i].new_text = ''
			else:
				cleaned.append(step)
				self.steps[i].new_text = step
		self.steps = [s for s in self.steps if s.new_text != '']
		return cleaned

	# Ingredient Parser
	def get_ingredients(self):

		# Getting all span texts with ingredients-item-name
		ingredient_strings = self.soup.find_all('span', {'class': 'ingredients-item-name'})
		ingredient_strings = [span.text.lower() for span in ingredient_strings]

		# Cleaning the ingredients
		ingredients = []
		unknown = {}
		index = 0
		for string in ingredient_strings:

			# Replacing thin spaces
			string = string.replace('\u2009', '')

			# Ingredient structure
			ingredient = {
				'name': '',
				'type': 'NULL',
				'quantity': .0,
				'measurement': '',
				'descriptors': [],
				'prep': [],
				'text': string
			}

			# Very special case
			if 'to taste' in string:
				string = string.replace('to taste', 'to|taste')

			# Parsing out descriptors in parentheses
			parens = re.search(r'(\([^\)]+\))', string)
			if parens:
				for paren in parens.groups():
					ingredient['descriptors'].append(paren[1:-1])
					string = string.replace(paren, '')
					string = string.replace('  ', ' ')

			# Determining a 'split word'
			split_word = ''
			for word in string.split():
				if word in data.all_ingredients:
					split_word = word
					break

			# Removing commas before the split word
			if split_word:
				string = string.split(split_word)

				# Decomposing into before and after components for processing
				before = string[0].replace(',', ' ') + split_word
				after = ' '.join(string[1:]).replace('  ', ' ')

				# Getting 'in x' as a prep step after the split
				if ' in ' in after:
					after = after.split(' in ')
					after_before = after[0]
					after_after = ' '.join(['in'] + after[1:])
					ingredient['prep'].append(after_after)
					after = after_before

				# Rebuilding string
				string = ' '.join([before, after])

			# Determining if the ingredient has 'prep steps'
			string = string.split(',')
			if len(string) > 1:
				ingredient['prep'] = [' '.join(string[1:]).strip()]

			string = string[0]
			string = string.split(' - ')
			if len(string) > 1:
				ingredient['prep'] = [' '.join(string[1:]).strip()]

			# Determining if the ingredient has an explicit quantity
			string = string[0].split()
			has_quantity = False
			for i, word in enumerate(string):

				# Catching more preparatory steps
				if word.endswith('ed') and word != 'red':
					if i > 0 and isinstance(string[i-1], str) and string[i-1].endswith('ly'):
						ingredient['prep'].append(f'{string[i-1]} {word}')
						string[i-1] = None
					else:
						ingredient['prep'].append(word)
					string[i] = None

				if word.endswith('less'):
					ingredient['descriptors'].append(word)
					string[i] = None

				# Replacing 'A and a' with 1
				if word.lower() == 'a':
					word = '1'
					string[i] = '1'

				# Checking for numbers
				try:
					number = self.convert_fraction(word)
					string[i] = number
					has_quantity = True
				except ValueError:
					continue

			# Removing null words that were added as prep
			string = [x for x in string if x is not None]

			# If the string has an explicit quanity
			if has_quantity:
				if len(string) == 1:
					ingredient['name'] = string[0]
				elif len(string) == 2:
					ingredient['quantity'] = string[0]
					ingredient['measurement'] = 'whole'
					ingredient['name'] = string[1]
				else:
					ingredient['quantity'] = string[0]
					ingredient['measurement'] = string[1]
					ingredient['name'] = ' '.join([str(x) for x in string[2:]])

			# Special cases where there is no quantity
			else:
				ingredient['name'] = ' '.join(string)
				unknown[ingredient['name']] = ingredient

			# This initial parse will properly handle most ingredients, but the validation step is necessary to improve accuracy for the others
			ingredients.append(self.validate(ingredient))
			index += 1

		return ingredients

	# Given a vulgar fraction string, returns a float
	def convert_fraction(self, string_fraction):
		if len(string_fraction) == 1:
			return unicodedata.numeric(string_fraction)
		else:
			return sum([self.convert_fraction(c) for c in string_fraction])

	# Validates the legitimacy/accuracy of an ingredient parse, returning a modified ingredient
	def validate(self, ingredient):

		# Validating measurements
		if ingredient['measurement'] not in data.measurements:
			name = ingredient['measurement'] + ' ' + str(ingredient['name'])
			for word in name.split():
				if word in data.measurements or word[:-1] in data.measurements:
					ingredient['measurement'] = 'to taste' if word == 'to|taste' else word
					ingredient['name'] = name.replace(word, '').replace('  ', ' ').strip()
					break
			else:
				ingredient['name'] = name
				ingredient['measurement'] = 'whole'

		# Validating ingredients, getting type information
		for ingredient_type in data.ingredients_list:
			if ingredient['name'] in data.ingredients_list[ingredient_type]:
				ingredient['type'] = ingredient_type
				break

		# Ingredient type not found, checking individual words
		else:
			for word in reversed(ingredient['name'].split()):
				found = False

				for ingredient_type in data.ingredients_list:
					ingredients = data.ingredients_list[ingredient_type]
					if word in ingredients or word[:-1] in ingredients:
						ingredient['type'] = ingredient_type
						found = True
						break

				if found:
					break

		# Identifying 'descriptors'
		per_word = [token.text for word in ingredient['name'].split() for token in nlp(word) if token.pos_ == 'ADJ']

		per_whole = [token.text for token in nlp(ingredient['name']) if token.pos_ == 'ADJ']

		for word in ingredient['name'].split():
			if word in per_word or word in per_whole:
				ingredient['descriptors'].append(word)

		return ingredient

	# Returns a step graph
	def get_steps(self):

		# Getting all the text corresponding to steps
		steps = [div.text for div in self.soup.find_all('div', {'class': 'paragraph'})]

		# Substeps are sentence level
		substeps = []
		for step in steps:
			substeps += [x.strip() for x in step.split('.')]

		# Creating a step for each sentence
		steps = [self.Step(x) for x in substeps]
		steps = [s for s in steps if len(s.text) > 0]

		# Im not sure
		'''
		for i, step in enumerate(self.steps):
			for ing, _ in enumerate(self.ingredients):
				if ing in step.text and ing not in step.ingredients:
					self.steps[i].ingredients.append(ing)
		'''

		return steps
	def output_steps(self):
		print("Instructions:")
		counter = 1
		for step in self.steps:
			if isinstance(step, str):
				print(str(counter) + '. ' + step)
			else:
				print(str(counter) + '. ' + step.new_text)
			counter += 1

	def output_ingredients(self):
			print("\n--INGREDIENTS--\n")
			for ingredient in self.ingredients:

				ingredient = ingredient['text'].strip().split()
				for i, word in enumerate(ingredient):
					try:
						num = self.convert_fraction(word)
						ingredient[i] = str((int(num) if num % 1 == 0 else round(num, 2)))
					except ValueError:
						continue

				print(' '.join(ingredient))

				'''
				# Cleaning prep steps
				prep= ', '.join(info['prep'])
				if len(prep) > 0:
					prep = ', ' + prep

				# Cleaning descriptors
				descriptors = [d for d in info['descriptors'] if d not in info['name']]
				d = ' ' + ', '.join(descriptors)
				if len(d) == 1:
					d = ''

				# Cleaning quantity, measurement, name
				q = info['quantity']
				q = str((int(q) if q % 1 == 0 else q))

				m = '' if info['measurement'] == 'whole' else info['measurement']

				n = info['name']

				if m:
					print(q + d, m, n + prep)
				else:
					print(q + d, n + prep)
				'''

# Returns a valid recipe url based on an integer input
def get_recipe_url(num=259356):
	response = requests.get(f'https://www.allrecipes.com/recipe/{num}')
	if response.status_code == 200:
		return response.url
	return get_recipe_url(259356)


import io
import logging
import os

import fontTools
import fontTools.ttLib
import ufoLib2

import vttLib

logger = logging.Logger(__name__)

def dump_to_file(font: fontTools.ttLib.TTFont, path: os.PathLike) -> None:
    """Dump relevant VTT data to a file.

    Relevant are:
    - TSI1 holds the assembly code
    - TSI3 holds the VTT code
    - TSI5 holds glyph group information
    - TSIC holds data for the cvar table.
    - cvar holds deltas for the CV table.
    - maxp holds instruction data computed by VTT, among other data

    TSI0 and TSI2 are filled in by fontTools. cvar can be rebuilt from TSIC but
    is dumped for historical reasons.
    """
    tables_to_dump = ["TSI1", "TSI3", "TSI5", "maxp"]

    for table_tag in tables_to_dump:
        if table_tag not in font:
            raise vttLib.VTTLibArgumentError(
                "Table '%s' not found in input font" % table_tag
            )

    if "TSIC" in font:  # Optional.
        tables_to_dump.append("TSIC")
    if "cvar" in font:  # Optional.
        if "cvt " not in font:
            # Fonttools, as of 4.5.0, requires "cvt " to be present before reading
            # "cvar" from XML. Take the easy way out and just dump the CV table.
            control_program = vttLib.get_extra_assembly(font, "cvt")
            vttLib.set_cvt_table(font, control_program)
        tables_to_dump.append("cvt ")
        tables_to_dump.append("cvar")

    vttLib.normalize_vtt_programs(font)
    font.saveXML(path, tables=tables_to_dump)

def merge_from_file(
    font: fontTools.ttLib.TTFont, path: os.PathLike, keep_cvar: bool = False
) -> None:
    """Merge VTT data from TTX dump into TTFont object.

    The 'maxp' table is only partially merged, as we want to import only data
    related to TrueType instructions, so it needs to pre-exist.
    """
    if "maxp" not in font:
        raise vttLib.VTTLibArgumentError("'maxp' table not found in target font.")

    TABLES_TO_MERGE = ("TSI0", "TSI1", "TSI2", "TSI3", "TSI5")
    tables_to_merge_optional = ["TSIC"]
    if keep_cvar:
        tables_to_merge_optional.extend(["cvar", "cvt "])

    ttx_dump = fontTools.ttLib.TTFont()
    ttx_dump.importXML(path)  # Import here so we can selectively merge maxp into font.
    ttx_dump["TSI0"] = fontTools.ttLib.newTable("TSI0")
    ttx_dump["TSI2"] = fontTools.ttLib.newTable("TSI2")

    if keep_cvar and "cvar" not in ttx_dump:
        vttLib.log.warning(
            "The keep_cvar option was specified, but the TTX file did not include a cvar table."
        )

    for tsi_table in TABLES_TO_MERGE:
        font[tsi_table] = ttx_dump[tsi_table]
    for tsi_table in tables_to_merge_optional:
        if tsi_table in ttx_dump:
            font[tsi_table] = ttx_dump[tsi_table]

    for maxp_attr in vttLib.MAXP_ATTRS:
        setattr(font["maxp"], maxp_attr, getattr(ttx_dump["maxp"], maxp_attr))

def copy_from_ufo_data_to_file(ufo: ufoLib2.Font, path: os.PathLike) -> None:
    """Dump VTT data stored in a UFO's data/ structure into a file.

    This is used to convert data from Legacy Projects to The New Way.
    """
    font = fontTools.ttLib.TTFont()

    font["maxp"] = maxp = fontTools.ttLib.newTable("maxp")
    maxp.tableVersion = 0x00010000
    maxp.maxComponentDepth = 0
    maxp.maxComponentElements = max(len(g.components) for g in ufo)
    maxp.maxCompositeContours = 0
    maxp.maxCompositePoints = 0
    maxp.maxContours = 0
    maxp.maxFunctionDefs = 0
    maxp.maxInstructionDefs = 0
    maxp.maxPoints = 0
    maxp.maxSizeOfInstructions = 0
    maxp.maxStackElements = 0
    maxp.maxStorage = 0
    maxp.maxTwilightPoints = 0
    maxp.maxZones = 1
    maxp.numGlyphs = len(ufo)
    if ".notdef" not in ufo:
        maxp.numGlyphs += 1

    for key, data in ufo.data.items():
        if "T_S_I__" in key:
            font.importXML(io.BytesIO(data))
        if "com.daltonmaag.vttLib" in key:
            data_maxp = fontTools.misc.plistlib.loads(data)["maxp"]
            maxp = font["maxp"]
            for name in vttLib.MAXP_ATTRS:
                if name in data_maxp:
                    value = data_maxp[name]
                    setattr(maxp, name, value)

    dump_to_file(font, path)


# -*- coding: UTF-8  -*-

from io import BytesIO
from PIL import Image, ImageDraw, ImageFont

'''Use Python 3.9+'''

TEMPLATE_PATH = '../chat_bot/files/Билет.jpg'
FONT_PATH = '../chat_bot/files/calibri.ttf'
FONT_SIZE = 25
BLACK = (0, 0, 0, 255)
DEPARTURE_CITY_OFFSET = (147, 130)
ARRIVAL_CITY_OFFSET = (120, 182)
DEPARTURE_DATE_OFFSET = (578, 460)
FLIGHT_NUMBER_OFFSET = (600, 234)
NUMBER_OF_SEATS_OFFSET = (250, 234)
NAME_OFFSET = (205, 283)
COMMENT_FIRST_STRING_OFFSET = (188, 333)
COMMENT_FIRST_STRING_LEN = 45
COMMENT_SECOND_STRING_OFFSET = (30, 370)
COMMENT_SECOND_STRING_LEN = 104
COMMENT_THIRD_STRING_OFFSET = (30, 408)
COMMENT_THIRD_STRING_LEN = 163

def generate_ticket(context: dict) -> BytesIO:
    departure_city = context['departure_city']
    arrival_city = context['arrival_city']
    departure_date = context['departure_date']
    flight_number = context['flight_number']
    number_of_seats = str(context['number_of_seats'])
    comment = context['comment']
    name = context['name']
    base = Image.open(TEMPLATE_PATH)
    font = ImageFont.truetype(FONT_PATH, FONT_SIZE)
    draw = ImageDraw.Draw(base)
    draw.text(DEPARTURE_CITY_OFFSET, departure_city, font=font, fill=BLACK)
    draw.text(ARRIVAL_CITY_OFFSET, arrival_city, font=font, fill=BLACK)
    draw.text(DEPARTURE_DATE_OFFSET, departure_date, font=font, fill=BLACK)
    draw.text(FLIGHT_NUMBER_OFFSET, flight_number, font=font, fill=BLACK)
    draw.text(NUMBER_OF_SEATS_OFFSET, number_of_seats, font=font, fill=BLACK)
    draw.text(NAME_OFFSET, name, font=font, fill=BLACK)
    if len(comment) > COMMENT_SECOND_STRING_LEN:
        draw.text(COMMENT_THIRD_STRING_OFFSET, comment[COMMENT_SECOND_STRING_LEN:], font=font, fill=BLACK)
        comment = comment[:COMMENT_SECOND_STRING_LEN]
    if len(comment) > COMMENT_FIRST_STRING_LEN:
        draw.text(COMMENT_SECOND_STRING_OFFSET, comment[COMMENT_FIRST_STRING_LEN:], font=font, fill=BLACK)
        comment = comment[:COMMENT_FIRST_STRING_LEN]
    draw.text(COMMENT_FIRST_STRING_OFFSET, comment, font=font, fill=BLACK)

    temp_file = BytesIO()
    base.save(temp_file, 'png')
    temp_file.seek(0)

    return temp_file


from django.conf import settings
from django.contrib.auth import get_user_model
from django.middleware.csrf import get_token

"""
Middleware to set the default user for the development environment
* In order to prevent dealing with users authentication during frontend development all request will get the default user
"""
class DefaultUserMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        if settings.DEBUG and not request.user.is_authenticated:
            # Get the default user for the development environment
            User = get_user_model()
            default_user, _ = User.objects.get_or_create(username='defaultuser')

            # Set the default user on the request object
            request.user = default_user

        response = self.get_response(request)
        return response

"""
Middleware to set the default user for the development environment
* Neglects CSRF protection during frontend development
"""
class DisableCSRFMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        if settings.DEBUG:
            # Disable CSRF protection for development by adding a valid one to every response
            response['X-CSRFToken'] = get_token(request)
        return response


"""
$ python3 path/to/test_rest.py
"""
import requests

def test_request(console_message, method, uri, request_body={}):
    print(console_message)
    response = requests_dot_method(method, uri, request_body)
    print(response.status_code)
    print(response.headers)
    print(response.json())
    print()
    return response

def requests_dot_method(method, uri, request_body={}):
    if method == 'post':
        return requests.post(uri, json=request_body)
    if method == 'get':
        return requests.get(uri)
    if method == 'put':
        return requests.put(uri, json=request_body)
    if method == 'delete':
        return requests.delete(uri)

response = test_request("Response from testing the creation of an employee: ",
                        'post',
                        "http://annaofalltrades.com/employees",
                        {'name': 'Anna Soto', 'role': 'Software Engineer'})
newest_id = response.json()['id']

test_request("Response from testing the getting of all employees:",
             'get',
             "http://annaofalltrades.com/employees")

test_request("Response from requesting an id that does not exist:",
             'get',
             "http://annaofalltrades.com/employees/999999")

test_request("Response from requesting an id that does exist:",
             'get',
             "http://annaofalltrades.com/employees/{}".format(newest_id))

test_request("Response from testing the replacing of an employee",
             'put',
             "http://annaofalltrades.com/employees/replace/{}".format(newest_id),
             {"name": "Curious the Cat", "role": "Companion"})

test_request("Response from testing the deletion of an employee",
             'delete',
             "http://annaofalltrades.com/employees/{}".format(newest_id))


import datetime

from flask import request, redirect, url_for, render_template, flash, Blueprint, current_app as app
from .forms import RSVPForm, RSVPFreeForm
from .models import RSVP, Invite, db
from .utils import rsvp_notify

rsvp = Blueprint('rsvp', __name__, template_folder='templates', static_folder='static')

@rsvp.route('/form', methods=['GET', 'POST'])
def rsvp_form():
    invite = None

    code = request.args.get('code')
    qr_used = 'qr_used' in request.args

    if code:
        invite = Invite.query.filter_by(rsvp_code=code).first()

    form = RSVPForm() if invite else RSVPFreeForm()

    if form.validate_on_submit():
        name = invite.name if invite else form.name.data

        app.logger.info('RSVP for %s from %s, attending: %s guests: %s' %
            (name, request.remote_addr, form.attending.data, form.guests.data)
        )

        rsvp = RSVP(
                name=name,
                invite_id=invite.id if invite else None,
                email=form.email.data,
                guests=form.guests.data,
                guest_names=form.guest_names.data,
                attending=form.attending.data,
                timestamp=datetime.datetime.utcnow(),
                note=form.note.data,
                code_used='code' in request.args,
                qr_used=qr_used
        )
        db.session.add(rsvp)
        db.session.commit()

        app.logger.info('RSVP by %s', rsvp.name)
        rsvp_notify(rsvp, app.config['MANDRILL_API_KEY'], app.config['NOTIFY_EMAILS'])
        return redirect(url_for('.static', filename='rsvp_success.html'))

    template = 'rsvp_form_prefill.html' if invite else 'rsvp_form.html'
    return render_template(template, invite=invite, form=form,
        header_file=url_for('.static', filename='img/rsvp_banner.png'))


from __future__ import annotations
import argparse
import pathlib

LOGLEVELS = ["HEADLESS", "CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"]
DEFAULT_LOGLEVEL = "INFO"
FILTERS = ["py", "dyn"]

class Options(argparse.Namespace):
    """Class for options for running a conversion like from the command line"""

    def __init__(
        self,
        source: list[pathlib.Path | str] = [],
        loglevel: str = DEFAULT_LOGLEVEL,
        dry_run: bool = False,
        force: bool = False,
        backup: bool = False,
        filter: str = "",
        update: bool = False,
        python_folder: pathlib.Path | str | None = None
    ) -> None:
        """Generate an option object for running it like from the command line

        Args:
            source (list[pathlib.Path  |  str], optional): List of files to run on. Defaults to [].
            loglevel (str, optional): Log level. Defaults to DEFAULT_LOGLEVEL.
            dry_run (bool, optional): Dry run, do not save files. Defaults to False.
            force (bool, optional): Overwrite files, even if they are older. Defaults to False.
            backup (bool, optional): Create backup of modified files. Defaults to False.
            filter (str, optional): 'dyn' or 'py' file filter for running on folders. Defaults to "".
            update (bool, optional): Update mode, like inverse on Dynamo files. Defaults to False.
            python_folder (pathlib.Path | str | None, optional): Path to export python files to, or import from there. Defaults to None.
        """

        self.source = []
        for s in source:
            if isinstance(s, str):
                self.source.append(pathlib.Path(s))
            else:
                self.source.append(s)

        self.loglevel = self.sanitize_option_string("loglevel", loglevel)
        self.dry_run = dry_run
        self.force = force
        self.backup = backup
        self.filter = self.sanitize_option_string("filter", filter)
        self.update = update

        if isinstance(python_folder, str):
            self.python_folder = pathlib.Path(python_folder)
        else:
            self.python_folder = python_folder

    @staticmethod
    def sanitize_option_string(arg: str, value: str) -> str:
        """Sanitize string option values

        Args:
            arg (str): The name of the argument
            value (str): The value

        Raises:
            ValueError: if the value is invalid

        Returns:
            str: The correct string
        """

        if arg == "loglevel":
            if value.upper() in LOGLEVELS:
                sanitized_value = value.upper()
            else:
                raise ValueError("Invalid loglevel!")
        elif arg == "filter":
            if not value or value in FILTERS:
                sanitized_value = value
            else:
                raise ValueError("Invalid filter!")
        else:
            sanitized_value = value

        return sanitized_value

    @classmethod
    def from_kwargs(cls, **kwargs) -> Options:
        """Initialize an Options object from kwargs

        Returns:
            Options: The initialized object
        """
        o = cls()
        for key, value in kwargs.items():
            if isinstance(value, str):
                value = cls.sanitize_option_string(key, value)
            setattr(o, key, value)
        return o


import requests
from take_audio import recognize_wav_file

if __name__ == '__main__':
    default = '/home/hungshing/FastData/ezTalk/users/msn9110/voice_data/uploads/sentence-t2/我不想吃義大利麵/20220525-155634.wav'

    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument('--path', '-p', type=str, default=default, help='''/ooo/.../xxx.wav''')
    args, _ = parser.parse_known_args()
    path = args.path

    *_, res = recognize_wav_file(
        path=path, number=50, user='msn9110')
    rankings = [{k: i for i, (k, _) in enumerate(pl, 1)}
                for pl in res]
    kwargs = {'by_construct': True,
              'topk': 16}
    data = {'data': res,
            'user': 'msn9110',
            'kwargs': kwargs}

    r = requests.post('http://localhost:55555/get_sentences', json=data, timeout=10.0)
    print(r.json())


# Maciej Sikora
# Github id: maciek04786
#
# This is a rain alert app, which tells you whether it will rain today,
# based on coordinates given in weather_parameters, it uses open weather
# map api, registration is free

import requests

weather_parameters = {
    "lat": 50.394050,
    "lon": -3.516020,
    "appid": "Your open weather map api id",
    "exclude": "current,minutely,daily,alerts"
}

response = requests.get(url="https://api.openweathermap.org/data/2.5/onecall", params=weather_parameters)
response.raise_for_status()
weather_data = response.json()
hourly_data = weather_data["hourly"][:12]

will_rain = False

for hour in hourly_data:
    if hour["weather"][0]["id"] < 700:
        will_rain = True
        break

if will_rain:
    print("It will rain today! Don't forget your umbrella â˜‚")
else:
    print("There's no rain in the forecast for today! ðŸ˜Š")


#Imports
from PIL import Image

class RescaleImage(object):
    '''
        Rescales the image to a given size.
    '''

    def __init__(self, output_size):
        '''
            Arguments:
            output_size (tuple or int): Desired output size. If tuple, output is
            matched to output_size. If int, smaller of image edges is matched
            to output_size keeping aspect ratio the same.
        '''
        self.var = output_size
        if(type(output_size) is tuple):
            self.w, self.h = output_size

        else:
            self.x = output_size

    def __call__(self, image):
        '''
            Arguments:
            image (numpy array or PIL image)

            Returns:
            image (numpy array or PIL image)

            Note: You do not need to resize the bounding boxes. ONLY RESIZE THE IMAGE.
        '''
        im1 = Image.open(image)
        h , w = im1.size
        if(type(self.var) is tuple):
            im2 = im1.resize((round(self.w), round(self.h)))

        else:
            if(h<w):
                ratio = w/h
                new_width = int(ratio * self.x)
                im2 = im1.resize((self.x, new_width))
            else:
                ratio = h/w
                new_height = int(ratio * self.x)
                im2 = im1.resize((new_height, self.x))
        return im2


import socket
from http import HTTPStatus

HOST_AND_PORT = ("127.0.0.1", 20000)

def server_start(host_and_port=HOST_AND_PORT):
    with socket.socket() as server_socket:
        server_socket.bind(host_and_port)
        server_socket.listen()
        print(f"Server started on {host_and_port}")
        while True:
            (client_connection, client_address) = server_socket.accept()
            handle_client(client_connection, client_address)
            print(f"Sent data to {client_address}")

def handle_client(connection, address):
    data = ''
    with connection:
        while True:
            data_part = connection.recv(1024)
            if not data_part:
                break
            data += data_part.decode()
            if '\r\n\r\n' in data:
                break
        response = generate_response(data, address)
        connection.send(response)

def generate_response(data, address):
    lines = data.splitlines()
    method, path, version = lines[0].split()
    status = generate_status(path)
    headers = ""
    for line in lines[1:-1]:
        key, value = line.split(": ", 1)
        headers += f"\r\n{key}: {value}"
    body = f"Request Method: {method}" \
           f"\r\nRequest Source: {address}" \
           f"\r\nResponse Status: {status}" \
           f"{headers}"
    response = f"{version} {status}" \
               f"{headers}" \
               f"\r\n\r\n" \
               f"{body}"
    return response.encode()

def generate_status(path):
    if "status" not in path:
        return "200 OK"
    try:
        index = path.find("=")
        status = int(path[index + 1:])
    except ValueError:
        return "200 OK"
    if status not in [s.value for s in HTTPStatus]:
        return "200 OK"
    return f"{status} {HTTPStatus(status).name}"

if __name__ == "__main__":
    server_start()


import jax
import jax.numpy as jnp

def autocorr(chains, n_lags=20):
    # estimate mean of each param
    means = chains.mean(axis=[0, 1])

    # single param, single lag
    def acf(y, mean, lag):
        a = y[n_lags:]
        b = jnp.roll(y, lag)[n_lags:]
        cov_ab = ((a - mean) * (b - mean)).mean()
        cov_a = ((a - mean)**2).mean()
        cov_b = ((b - mean)**2).mean()
        cor = cov_ab / jnp.sqrt(cov_a * cov_b)
        return cor

    # vmap over params
    acf = jax.vmap(acf, in_axes=(1, 0, None))

    # vmap over chains
    acf = jax.vmap(acf, in_axes=(0, None, None))

    # for each lag, average autocor across all chains and params
    def step(i, r):
        cor = acf(chains, means, i).mean()
        r = r.at[i].set(cor)
        return r
    r = jnp.ones([n_lags])
    r = jax.lax.fori_loop(1, n_lags, step, r)

    return r

def r_hat(y):
    """
    Based on the paper "What Are Bayesian Neural Network Posteriors Really Like? - Appendix B"
    y.shape: [num_chains (M) x num_steps (N) x num_features (B)]
    """
    M, N, B = y.shape

    # compute variances
    b = (N/(M-1)) * ((y.mean(axis=1, keepdims=True) - y.mean(axis=(0, 1), keepdims=True))**2).sum(axis=(0, 1))
    w = 1/(M*(N-1)) * ((y - y.mean(axis=1, keepdims=True))**2).sum(axis=(0, 1))
    r_hat = jnp.sqrt((((N-1)/N)*w + (1/N)*b)/w)

    return r_hat

def r_hat(chains, f):
    """
    Based on the paper "What Are Bayesian Neural Network Posteriors Really Like? - Appendix B"
    chains: [num_chains x num_steps x num_params]
    f: f(chain_node) -> [B]: transforms params to a 1D quantity of interest, eg predictive distribution
    """

    # compute function of interest for each chain node
    # - y.shape: [num_chains (M) x num_steps (N) x num_features (B)]
    y = jax.vmap(jax.vmap(f))(chains)
    M, N, B = y.shape

    # compute variances
    b = (N/(M-1)) * ((y.mean(axis=1, keepdims=True) - y.mean(axis=[0, 1], keepdims=True))**2).sum(axis=[0, 1])
    w = 1/(M*(N-1)) * ((y - y.mean(axis=1, keepdims=True))**2).sum(axis=[0, 1])
    r_hat = jnp.sqrt((((N-1)/N)*w + (1/N)*b)/w)

    return r_hat


"""
A module to compute bigwig coverage over a set of regions (bed).
"""

import os
import sys
from itertools import repeat
from tempfile import NamedTemporaryFile

import billiard as multiprocessing
import numpy as np
import pyBigWig
from pybedtools import BedTool

import pygtftk
from pygtftk.utils import GTFtkError
from pygtftk.utils import add_prefix_to_file
from pygtftk.utils import close_properly
from pygtftk.utils import flatten_list
from pygtftk.utils import intervals
from pygtftk.utils import make_tmp_file
from pygtftk.utils import message

# -------------------------------------------------------------------------
# TMP_FILE_POOL_MANAGER stores temporary file name
# make_tmp_file_pool is function that add temporary files to TMP_FILE_POOL_MANAGER
# TMP_FILE_POOL_MANAGER will be updated by workers (in contrast to a global
# variable)
# -------------------------------------------------------------------------

TMP_FILE_POOL_MANAGER = multiprocessing.Manager().list()

def make_tmp_file_pool(prefix='tmp',
                       suffix='',
                       store=True,
                       dir=None):
    """
    This

    :Example:

    >>> from pygtftk.utils import make_tmp_file_pool
    >>> tmp_file = make_tmp_file_pool()
    >>> assert os.path.exists(tmp_file.name)
    >>> tmp_file = make_tmp_file_pool(prefix="pref")
    >>> assert os.path.exists(tmp_file.name)
    >>> tmp_file = make_tmp_file_pool(suffix="suf")
    >>> assert os.path.exists(tmp_file.name)

    """

    dir_target = None

    if dir is None:
        if pygtftk.utils.TMP_DIR is not None:
            if not os.path.exists(pygtftk.utils.TMP_DIR):
                msg = "Creating directory {d}."
                message(msg.format(d=pygtftk.utils.TMP_DIR), type="INFO")
                os.mkdir(pygtftk.utils.TMP_DIR)
                dir_target = pygtftk.utils.TMP_DIR

    else:
        if not os.path.exists(dir):
            msg = "Creating directory {d}."
            message(msg.format(d=dir), type="INFO")
            os.mkdir(dir)
            dir_target = dir

    tmp_file = NamedTemporaryFile(delete=False,
                                  mode='w',
                                  prefix=prefix + "_pygtftk_",
                                  suffix=suffix,
                                  dir=dir_target)

    if store:
        TMP_FILE_POOL_MANAGER.append(tmp_file.name)

    return tmp_file

# -------------------------------------------------------------------------
# Define the job of a single worker whose job is to compute mean coverage
# or coverage profile using a bed and a bigwig as input.
# -------------------------------------------------------------------------

def _big_wig_coverage_worker(input_values):
    """
    This function compute bigwig coverage. The input_values arguments is a
    tuple that contains various input parameters. 'span' is a tuple that
    correspond to a fraction (from, to) of the bedfile to be processed. Each
    worker will process all bigwig filesbut it will only process a fraction
    (span) of the bed file regions

    :param span: the fraction (lines) of the bed file [from, to] to be processed.
    :param bw_list: the list of bigWig files to be processed.
    :param region_bed_file_name: the bed file containing the region for which coverage is to be computed.
    :param bin_nb: The number of bin into which the region should be splitted.
    If the number of nucleotides is < nbBin a warning is printed.
    :param pseudo_count: A value for the pseudo_count.
    :param n_highest: compute the score based on the n highest values in the bins.
    :param profile: compute coverage profile not a single coverage value (mean).
    :param stranded: controls whether the profile should be ordered based on
    strand.
    :param type: This string will be added to the output to indicate the type
    of region (e.g tss, promoter...).
    :param label: Bigwig labels (i.e short name version)
    :param zero_to_na: Use NA not zero when region is undefined in bigwig.
    :param stat: mean (default) or sum.
    :param verbose: run in verbose mode.

    """

    (span, bw_list,
     region_bed_file_name,
     bin_nb, pseudo_count,
     n_highest, profile,
     stranded, type, label, zero_to_na, stat, _) = input_values

    pc = pseudo_count

    if not profile:
        if n_highest is None:
            n_highest = bin_nb
        results = list()
    else:
        if bin_nb < 1:
            bin_nb = 1
        matrix_file = make_tmp_file_pool(prefix="worker_coverage_", suffix=".txt")

    for cpt, big_wig in enumerate(bw_list):

        try:
            bigwig = pyBigWig.open(big_wig)
            if not bigwig.isBigWig():
                message("Not a bigwig file :" + big_wig, type="ERROR")
        except:
            message("Not a bigwig file :" + big_wig, type="ERROR")

        mesg = "Computing coverage for %s (chunks : #%s , type : %s, lab : %s)."
        mesg = mesg % (os.path.basename(big_wig), str(span[1] - span[0]), type, label[cpt])
        message(mesg, type="INFO")

        # Load the regions for which the coverage is to be processed.

        tx_bed = BedTool(region_bed_file_name)

        # The fraction of bed file
        # to be processed
        (from_here, to_here) = span

        nb = 0
        nb_to_do = to_here - from_here

        for i in tx_bed[slice(from_here, to_here)]:

            nb += 1

            if nb == nb_to_do:
                p_name = str(multiprocessing.current_process().name)
                message(p_name + " has processed " + str(nb) + " regions")

            if (i.end - i.start) < bin_nb:

                if pygtftk.utils.WARN_REGION_SIZE:
                    pygtftk.utils.WARN_REGION_SIZE = False
                    message("Encountered regions shorter than bin number.",
                            type="WARNING")
                    message(i.name +
                            " has length : " +
                            str(i.end -
                                i.start), type="WARNING")
                    message("They will be set to NA or --pseudo-count depending on --zero-to-na.",
                            type="WARNING")
                    message("Filter them out please.",
                            type="WARNING")

                if zero_to_na:
                    out = ['NA'] * bin_nb
                else:
                    out = [pc] * bin_nb

            else:

                try:
                    """
                    bw_cov = bigwig.stats(i.chrom,
                                          i.start,
                                          i.end,
                                          nBins=bin_nb)
                    """

                    bw_cov = bigwig.values(i.chrom,
                                           i.start,
                                           i.end)

                    out = []
                    size = i.end - i.start

                    for range_curr in intervals(list(range(size)), bin_nb, silent=True):

                        interval_cur = bw_cov[range_curr[0]:range_curr[1]]

                        if not zero_to_na:
                            interval_cur = [k if not np.isnan(k) else 0 for k in interval_cur]

                        if stat == 'mean':
                            out += [round(sum(interval_cur) / (range_curr[1] - range_curr[0]), 6)]
                        elif stat == 'sum':
                            out += [round(sum(interval_cur), 6)]
                        else:
                            raise GTFtkError("Stat should be 'sum' or 'mean'.")

                    if zero_to_na:
                        out = ['NA' if np.isnan(k) else k + pc for k in out]

                    else:
                        out = [pc if np.isnan(k) else k + pc for k in out]

                except:
                    if pygtftk.utils.WARN_UNDEF:
                        pygtftk.utils.WARN_UNDEF = False

                        mesg = "Encountered regions undefined in bigWig file."
                        message(mesg, type="WARNING")
                        mesg = '%s:%s-%s' % (i.chrom, str(i.start), str(i.end))
                        message(mesg)

                    if zero_to_na:
                        out = ['NA'] * bin_nb
                    else:
                        out = [pc] * bin_nb

            # Prepare output
            if i.name in ["", "."]:
                name = "|".join([i.chrom,
                                 str(i.start),
                                 str(i.end)])
            else:
                name = i.name

            if i.strand == "":
                strand = "."
            else:
                strand = i.strand

            # Print profiles
            if profile:

                # Data should be oriented in 5' -> 3'
                if stranded:

                    if i.strand == '-':
                        out = out[::-1]

                out = [str(x) for x in out]

                out_text = [label[cpt],
                            i.chrom,
                            str(i.start),
                            str(i.end),
                            str(i.name),
                            i.strand]
                out_text = out_text + out
                out_text = "\t".join(out_text)
                matrix_file.write(out_text + "\n")

            else:

                out = sorted(out, reverse=True)
                out = out[0:n_highest]

                if 'NA' not in out:
                    out = sum(out) / len(out)
                else:
                    out = 'NA'

                results.append("\t".join([i.chrom,
                                          str(i.start),
                                          str(i.end),
                                          label[cpt] + "|" + name,
                                          str(out),
                                          strand]) + "\n")

    if profile:
        matrix_file.close()
        return matrix_file.name

    else:
        return results

def bw_cov_mp(bw_list=None,
              region_file=None,
              labels=None,
              bin_nb=None,
              nb_proc=None,
              n_highest=None,
              zero_to_na=False,
              pseudo_count=None,
              stat='mean',
              verbose=False):
    """
    Compute bigwig coverage (multi-processed) for a set of regions.

    :param bw_list: the list of bigWig files to be processed.
    :param region_file: the bed file containing the region for which coverage is to be computed.
    :param labels: shortname for bigwigs.
    :param bin_nb: The number of bin into which the region should be splitted.
    :param nb_proc: Number of threads to be used.
    :param n_highest: compute the mean coverage based on the n highest values in the bins.
    :param pseudo_count: The value for a pseudo-count.
    :param verbose: run in verbose mode.
    :param stat: mean (default) or sum.
    :param zero_to_na: Convert missing values to NA, not zero.

    Returns a file.

    """

    n_region_to_proceed = len(BedTool(region_file.name))

    message("Received " +
            str(n_region_to_proceed) +
            " regions to proceed for each bigwig")

    tokens = intervals(list(range(n_region_to_proceed)), nb_proc)

    pool = multiprocessing.Pool(nb_proc)
    coverage_list = pool.map_async(_big_wig_coverage_worker,
                                   list(zip(tokens,
                                            repeat(bw_list),
                                            repeat(region_file.name),
                                            repeat(bin_nb),
                                            repeat(pseudo_count),
                                            repeat(n_highest),
                                            repeat(False),
                                            repeat(False),
                                            repeat(None),
                                            repeat(labels),
                                            repeat(zero_to_na),
                                            repeat(stat),
                                            repeat(verbose)))).get(9999999)

    if False in coverage_list:
        sys.stderr.write("Aborting...")
        sys.exit()

    # Unlist the list of list

    coverage_list = [item for sublist in coverage_list for item in sublist]

    tmp_file = make_tmp_file(prefix="region_coverage",
                             suffix=".bed")
    for i in coverage_list:
        tmp_file.write(i)

    tmp_file.close()

    return open(tmp_file.name)

def bw_profile_mp(in_bed_file=None,
                  nb_proc=None,
                  big_wig=None,
                  bin_nb=None,
                  pseudo_count=0,
                  stranded=True,
                  type=None,
                  labels=None,
                  outputfile=None,
                  zero_to_na=False,
                  bed_format=False,
                  add_score=False,
                  stat='mean',
                  verbose=False):
    """
    Compute bigwig profile for a set of regions.

    :param in_bed_file: the bed file containing the region for which coverage is to be computed.
    :param nb_proc: Number of threads to be used.
    :param big_wig: The bigWig files to be processed.
    :param bin_nb: The number of bin into which the region should be splitted.
    :param pseudo_count: The value for a pseudo-count.
    :param stranded: controls whether the profile should be ordered based on strand.
    :param type: This string will be added to the output to indicate the type of region (e.g tss, promoter...).
    :param labels: shortname for bigwigs.
    :param outputfile: output file name.
    :param zero_to_na: Convert missing values to NA, not zero.
    :param bed_format: Force Bed format. Default is to write columns in the following way: bwig, chrom, start, end, gene/feature, strand...
    :param add_score: add a 'score' column ("."). Just for downstream compatibility).
    :param stat: mean (default) or sum.
    :param verbose: run in verbose mode.

    Returns a file.

    """

    outputfile = add_prefix_to_file(infile=outputfile,
                                    prefix=type + "_")

    outputfile = open(outputfile, "w")

    n_region_to_proceed = len(BedTool(in_bed_file))

    message("Received " +
            str(n_region_to_proceed) +
            " regions to proceed for each bigwig")

    # 'Split' the file into multiple fragment
    tokens = intervals(list(range(n_region_to_proceed)), nb_proc)

    # Computing coverage of features.
    # Each worker will return a file
    pool = multiprocessing.Pool(processes=nb_proc)

    # Write a header
    if bed_format:
        prefix = []
    else:
        prefix = ["bwig"]

    suffix = [type + "_" + str(x + 1) for x in range(bin_nb)]

    if add_score:
        score_h = ["score"]
        score = ["."]
    else:
        score_h = []
        score = []

    outputfile.write("\t".join(prefix + ["chrom",
                                         "start",
                                         "end",
                                         "gene"] + score_h + ["strand"] + suffix) + "\n")

    if nb_proc > 1:
        argss = list(zip(tokens,
                         repeat(big_wig),
                         repeat(in_bed_file),
                         repeat(bin_nb),
                         repeat(pseudo_count),
                         repeat(None),
                         repeat(True),
                         repeat(stranded),
                         repeat(type),
                         repeat(labels),
                         repeat(zero_to_na),
                         repeat(stat),
                         repeat(verbose)))

        for res_file_list in pool.map_async(_big_wig_coverage_worker,
                                            argss).get(999999):

            for cur_file in flatten_list([res_file_list], outlist=[]):

                with open(cur_file) as infile:
                    for i in infile:
                        if bed_format:
                            i = i.split("\t")
                            outputfile.write("\t".join(i[1:4] + [i[0] + "|" + i[4]] + score + i[5:]))
                        else:
                            outputfile.write(i)
    # Don't use pool.
    else:
        for tok in tokens:
            res_file = _big_wig_coverage_worker((tok,
                                                 big_wig,
                                                 in_bed_file,
                                                 bin_nb,
                                                 pseudo_count,
                                                 None,
                                                 True,
                                                 stranded,
                                                 type,
                                                 labels,
                                                 zero_to_na,
                                                 stat,
                                                 verbose))

            with open(res_file) as infile:
                for i in infile:
                    if bed_format:
                        i = i.split("\t")
                        outputfile.write("\t".join(i[1:4] + [i[0] + "|" + i[4]] + ["."] + i[5:]))
                    else:
                        outputfile.write(i)

    close_properly(outputfile)

    return outputfile


#!/usr/bin/env python3
""""
scraper_monthly_rendextracts.py
  Organizes the month range and then calls extractFromWithinAFundoReport.py month to month
"""
import datetime
from dateutil.relativedelta import relativedelta
import settings as sett
# from models.extractFromWithinAFundoReport import WithinFundoExtractScraper
import models.extractFromWithinAFundoReport as extScr
import fs.texts.texts_scrapehelper as scrapehelper
YEARMONTH_INI = datetime.date(year=2022, month=8, day=1)
YEARMONTH_FIM = datetime.date(year=2023, month=8, day=1)

class MonthlyRoller:

  def __init__(self, yearmonth_ini=None, yearmonth_fim=None):
    self.fundo_result_records = []
    self.yearmonth_ini = yearmonth_ini
    self.yearmonth_fim = yearmonth_fim
    self.treat_param_dates()

  def treat_param_dates(self):
    if self.yearmonth_ini is None or not type(self.yearmonth_ini) == datetime.date:
      self.yearmonth_ini = YEARMONTH_INI
    if self.yearmonth_fim is None or not type(self.yearmonth_fim) == datetime.date:
      self.yearmonth_fim = YEARMONTH_FIM

  def scrape_sliced_fundos_for_refmonth(self, current_yearmonth):
    fundofilepath = sett.get_bb_fi_extract_filepath_by_year_month(current_yearmonth.year, current_yearmonth.month)
    scrapetexts = scrapehelper.slice_fundofile_into_fundoscrapetexts(fundofilepath)
    for scrapetext in scrapetexts:
      fundoresult = extScr.WithinFundoExtractScraper(scrapetext, refmonthdate=current_yearmonth)
      fundoresult.process()
      # str_curr_yearmonth = '{year}-{month:02d}'.format(
      #  year=current_yearmonth.year, month=current_yearmonth.month
      # )
      # print(str_curr_yearmonth)
      # print('=' * 40)
      # print(fundoresult)
      self.fundo_result_records.append(fundoresult.datadict)

  def roll_months(self):
    current_yearmonth = self.yearmonth_ini
    while current_yearmonth <= self.yearmonth_fim:
      # print('-*-=-*-'*10)
      self.scrape_sliced_fundos_for_refmonth(current_yearmonth)
      current_yearmonth = current_yearmonth + relativedelta(months=1)

  def process(self):
    self.roll_months()

  def __str__(self):
    dateini = self.yearmonth_ini
    datefim = self.yearmonth_fim
    outstr = "<MonthlyRoller dateini={dateini} datefim={datefim}>".format(dateini=dateini, datefim=datefim)
    return outstr

if __name__ == '__main__':
  roller = MonthlyRoller()
  roller.process()


from os import path, getcwd
import json

from . import env
from . import backend_config

from .component import is_workspaced
from .component import resolve_variable_opts
from .dependencies import get_cascades

from ...commands.types.target import parse_target

commands_no_vars = [
    'output',
    'fmt',
    'validate',
    'state',
    'taint',
    'untaint',
    '0.13upgrade',
    'providers',
]
commands_with_vars = [
    'plan',
    'show',
    'apply',
    'destroy',
    'refresh',
    'import',
    'console',
]
unlock_script = 'terraform/bin/clear-state-lock.sh'

class TerraformDriver(object):
    def __init__(self, settings, target, flags=[]):
        self.settings = settings
        self.component = target.component
        self.workspace = target.workspace
        self.flags = flags

        # Assume the component's path is under the top-level terraform/ sub-folder
        _component = self.component.rstrip("/").replace(getcwd(), '').replace('/terraform/', '')  # Normalize
        self.component_dir = path.join(getcwd(), 'terraform', _component)

        self.env_ctx = env(self.settings, self.workspace)

        self.runner = self.settings.runner(cwd=self.component_dir, env_ctx=self.env_ctx)
        self._exec = self.runner.exec
        self._output_list = self.runner.output_list
        self._output_string = self.runner.output_string

    def _state_prep(self, flags=[]):
        # Odd, but you init, then workspace (first time only)
        # otherwise you workspace then init
        terrform_state = path.join(self.component_dir, '.terraform', 'terraform.tfstate')
        first_time = not path.exists(terrform_state)

        # First time (locally) - components must be init'd first before setting the workspace
        if first_time:
            self._exec(['terraform', 'init'] + list(flags) + ['-upgrade', f'-backend-config={backend_config}'], check=False)

        # If applicable: create workspace where neded before selecting it
        if is_workspaced(self.component_dir):
                workspaces = self._output_list(['terraform', 'workspace', 'list'], check=False)
                if workspaces == None:
                    self._exec(['terraform', 'init'] + list(flags) + ['-upgrade', f'-backend-config={backend_config}'], check=False)
                else:
                    if not f'* {self.workspace}' in workspaces:
                        if not self.workspace in workspaces:
                            self._exec(['terraform', 'workspace', 'new', self.workspace])
                        else:
                            self._exec(['terraform', 'workspace', 'select', self.workspace])

        # Safe to init here for non-first time workspaced states
        if not first_time:
            self._exec(['terraform', 'init'] + list(flags) + ['-upgrade', f'-backend-config={backend_config}'])

    def init(self):
        # "state prep" will handle this completely
        self._state_prep(self.flags)

    def unlock(self):
        self._exec([unlock_script, f'{self.component}:{self.workspace}'], cwd=getcwd())

    def outputs(self):
        self._state_prep()  # "state prep" the state first, then proceed
        json_stdout = self._output_string(['terraform', 'output', '-json'])
        return json.loads(json_stdout)

    def output_value(self, key):
        return self.outputs()[key]['value']

    def has_resources(self):
        if not path.exists(self.component_dir):
            return False
        if not path.exists(path.join(self.component_dir, 'terraform.tf')):
            return False
        self._state_prep()  # "state prep" the state first, then proceed
        return len(self._output_list(['terraform', 'state', 'list'])) != 0

    def _run_command_without_vars(self, command):
        self._state_prep()  # "state prep" the state first, then proceed
        self._exec(['terraform', command] + list(self.flags))
        self._run_cascades(command)

    def _run_command_with_vars(self, command):
        self._state_prep()  # "state prep" the state first, then proceed
        var_opts = resolve_variable_opts(self.component_dir, self.workspace)
        self._exec(['terraform', command] + var_opts + list(self.flags))
        self._run_cascades(command)

    def _run_cascades(self, command):
        # Handle cascades
        for cascade in get_cascades(self.component):
            if command in cascade['ops']:
                _command = cascade['type']
                target = parse_target(cascade['target'])
                flags = [cascade['opts']]
                driver = TerraformDriver(self.settings, target, flags)
                getattr(driver, _command)()

    def __getattr__(self, name):
        if name in commands_no_vars:
            def _func():
                self._run_command_without_vars(name)
            return _func
        if name in commands_with_vars:
            def _func():
                self._run_command_with_vars(name)
            return _func