#!/usr/bin/python3
from pyfiglet import Figlet
import yaml, random
import sys
import os
import datetime
import time
from multiprocessing import Process, Queue

from SpeechDriver.tts.ttsdefault import speak
from Services import weather, jokes_quote
from Core import textAnimation
from Core.profile import *
from Services import greeting

"""
print("                                            ")
print("  ________  .__               .__           ")
print("  \______ \ |__| ______ _____ |__| ______   ")
print("   |    |  \|  |/  ___//     \|  |/  ___/   ")
print("   |    `   \  |\___ \|  Y Y  \  |\___ \    ")
print("  /_______  /__/____  >__|_|  /__/____  >   ")
print("          \/        \/      \/        \/    ")
print("                                            ")   """

""" Ping google.com """ #Current not using in main instead using in brain.
from urllib.request import urlopen
def internetExamine():
    while True:
        try:
            response = urlopen('https://www.google.com/', timeout=10)
            print('on')
            return True
        except:
            print('false')
            return False

""" Greeting """
def greetMe():
    currentH = int(datetime.datetime.now().hour)
    if currentH >= 0 and currentH < 12:
        speak('Good Morning!')
        print('Good Morning!')
    if currentH >= 12 and currentH < 18:
        speak('Good Afternoon!')
        print('Good Afternoon!')
    if currentH >= 18 and currentH != 0:
        speak('Good Evening!')
        print('Good Evening!')

""" MULTIPROCESSING """
def Aneey_wishMailer(Aneey_wishMailer_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + Aneey_wishMailer_path + " &")
def Aneey_birthdayAlert(AneeyC_BirthdayAlert_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + AneeyC_BirthdayAlert_path + " &")
def Anum_wishMailer(Anum_wishMailer_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + Anum_wishMailer_path + " &")
def AnumBirthdayProtocal(AnumBirthdayAlert_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + AnumBirthdayAlert_path + " &")
def Anisha_wishMailer(Anisha_wishMailer_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + Anisha_wishMailer_path + " &")
def ask_email(ask_abtEmailreminder):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + ask_abtEmailreminder + " &")
def BestfriendBirthdayProtocal(BestfriendBirthdayProtocal_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + BestfriendBirthdayProtocal_path + " &")
def PersonalGmailNotify(PersonalGmailNotify_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + PersonalGmailNotify_path + " &")
def flask_credentials(flask_credentials_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + flask_credentials_path + "&")
def schedule(schedule_path):
    time.sleep(random.randint(1, 3))
    os.system("python3 " + schedule_path + "&")

""" Desktop Notification """
"""
from gi.repository import Notify
# One time initialization of libnotify
Notify.init("Dismis_slave1")
# Create the notification object
title = "Dismis_slave1!"
body = "Meeting at 3PM!"
notification = Notify.Notification.new(
    title, body)
# Actually show on screen
notification.show() """

""" Booting """
def startup():
    """ Start Up """
    textAnimation.load_animation()
    time.sleep(0.30)
    print(' ')

    """ Dismis Banner """
    custom_fig = Figlet(font='graffiti')
    print(custom_fig.renderText('Dismis'))

    """ Greeting """
    greetMe()
    #time.sleep(0.30)

    """ Weather of Default Location """
    currentH = int(datetime.datetime.now().hour)
    if currentH >= 0 and currentH < 12:
        weather.weather_DefaultCity(default_CityLocation, openweatherAPI, accept_path)
        time.sleep(9)

    """ Jokes """
    currentH = int(datetime.datetime.now().hour)
    if currentH >= 0 and currentH < 12:
        jokes_quote.tell_joke(accept_path)
        time.sleep(3)

    """ Quote of The Day """
    currentH = int(datetime.datetime.now().hour)
    if currentH >= 0 and currentH < 12:
        jokes_quote.quote(accept_path)

    """ Final Animation """
    def delay_print(s):
        import time
        import sys
        for c in s:
            sys.stdout.write(c)
            sys.stdout.flush()
            time.sleep(0.1)
    delay_print("Hi, I'm Dismis. Nice to meet you!\n")
    time.sleep(0.20)

""" Running All Main Functions """
#startup()
""" Running Parallel Processes """
Anneybirthday_wisher = Process(target= Aneey_wishMailer(Aneey_wishMailer_path))
Anneybirthday_wisher.start()

Anneybirthday = Process(target= Aneey_birthdayAlert(AneeyC_BirthdayAlert_path))
Anneybirthday.start()

bhaibirthday_wisher = Process(target= Anum_wishMailer(Anum_wishMailer_path))
bhaibirthday_wisher.start()

bhaibirthday = Process(target = AnumBirthdayProtocal(AnumBirthdayAlert_path))
bhaibirthday.start()

Anishabirthday_wisher = Process(target= Anisha_wishMailer(Anisha_wishMailer_path))
Anishabirthday_wisher.start()

reminder =  Process(target=ask_email(ask_abtEmailreminder))
reminder.start()

bffbirthday = Process(target = BestfriendBirthdayProtocal(BestfriendBirthdayProtocal_path))
bffbirthday.start()

chkMail = Process(target = PersonalGmailNotify(PersonalGmailNotify_path))
chkMail.start()

credentials = Process(target = flask_credentials(flask_credentials_path))
credentials.start()

routine = Process(target = schedule(schedule_path))
routine.start()


import numpy as np
import matplotlib.pyplot as plt
# the code works but a bit slow, so open for change

def createSchedule(numOfAgents,agentArchetypes,classrooms, capacity, classAllowedTypeDict=None, modulo=24):
    """
        create a list of schedules for each agents,
        agent_archetype dictates the distribution and types of random values to fill the empty slots after assigning the static portion

        Parameters:
        - numberOfAgents: int, the number of schedules that you want to make
        - agentArchetypes: list or tup, A list that maps the i-th string to the i-th Agent, should store valid archetype string
        - classrooms: List of classroomIds, length = the number of classes
        - capacity: List that contains the capacity that corresponds to the classrooms in the classrooms parameter
        - classAllowedTypeDict: paramter that might be removed
        - modulo: a mod b, this is the value for b, and used to mod time

        Return Value:
        - listOfSchedules: a list where the i-th value is the schedule for the i-th agent
            - A single schedule looks like [[n names or ID], [n names or ID], ... ]
            - n is the number of time slices, so a 24 hour schedule have n = 24
    """
    # the key is the name of the schedule, the value is a list of tuples (duration, starting time, end time),
    # if the duration is less than the total interval, then it will choose a starting and end time inside the interval
    # for now the time is in hours so everything is mod 24.  (24 hours in a day)
    # initialization of parameters
    staticSchedule = {
        "sleep": [(8, 22, 8)],
        "classes": [(2, 10, 12), (2, 12,14), (2,14, 16),(2,16,18)],
        "eating": [(1, 8, 10), (1, 12, 15), (1, 18, 21)]
    }

    # define the parameters relating to choosing classes

    OE_slots = 4 # both odd and even slots are 4
    totalClassSlots = 8
    timeslotsPerDay = 24 # 24 hours in a day
    # define the number and name of unique schedules
    # change to ["mon", "tues", ..." sunday"] to create differnt schedules for a week, this can be expanded
    scheduleType = ["Odd", "Even", "Weekends"]
    # define the priorities of assigning a schedules for odd days, even days, and weekends
    OEW_priority = [["sleep", "eating"], ["sleep", "eating"],  ["sleep", "eating"]]
    # dynamic schedules are types of schedules that will be used to fill empty slots in the schedules
    dynamicSchedule = ["study", "gym", "off_campus"] #dorm, change to "social"
    dynamicProbabilites = {
        "stem" : [0.5, 0.2, 0.3],
        "athletes" : [0.1, 0.5, 0.4],
        "party": [0.1, 0.2, 0.7],
        "other" : [0.3, 0.15, 0.55]
    }
    # dorm is the rest [0.3, 0.15, 0.25]

    listOfScedules = []
    classList = [classrooms[index] for index, val in enumerate(capacity) for _ in range(int(val))]
    masks = createMask(numOfAgents)
    maskSum = np.sum(masks, axis=0, dtype=int)

    maskCurrIndex = np.zeros(maskSum.shape, dtype = int)
    x, y = maskSum.shape
    classes = [[np.random.permutation(classList) for _ in range(y)] for _ in range(x)]
    print(f"creating schedule for {numOfAgents} agents, {len(agentArchetypes)}, {len(classrooms)}, {len(capacity)}")
    for index in range(numOfAgents): # iterate over each agents
        # create a mask that splits the classes into odd and even schedules
        # NoteToSelf: put classPerAgent and totalClassStots inside the loop for randomization
        agentSchedule = [[0 for _ in range(timeslotsPerDay)] for _ in range(len(scheduleType))]
        agentSchedule = assignClasses(agentSchedule, masks[index], classes, maskCurrIndex)

        maskCurrIndex+=masks[index]

        # check if the agent's type is defined or not
        archetype = agentArchetypes[index]
        if archetype not in dynamicProbabilites.keys():
            archetype = "other"

        # assign a schedule for each unique schedule
        for i, routineName in enumerate(scheduleType): # odd, even, weekends
            agentSchedule[i] = chooseStatic(agentSchedule[i], staticSchedule, OEW_priority[i])
            # fill the empty slots with random values with some distribution
            agentSchedule[i] = fillRandomWithCDF(agentSchedule[i], 0, dynamicSchedule, dynamicProbabilites[archetype])

        # add i-th agent's schedule to the schedule list
        listOfScedules.append(agentSchedule)
    return listOfScedules

def chooseStatic(schedule, staticDict, priorityQueue, modulo=24):
    """
        given a scheudule and a priority queue,
        it takes the next highest priority item and checks for conflicts and the event is added to the schedule only if there's no confict
        if the duration of the event is less than the time interval,
        then the event is placed at a random time such that its between the interval and end before or at the end time
        note: this isnt a greedy schedule, it just assign an event if time is available.

        Parameters:
        - schedule: a schedule for a single agent
        - staticDict: a dictionary, keys are the name of the activity or class; Value is a list filled with (durration, starting time, end time) of the activity
        - priorityQueue: a queue that stores the
        - modulo: mod value for time, use 24 for 24 hour schedules

        Return value:
        - a renewed schedule, although pass by reference causes the original to be changed, the renewed one is returned just in case

    """
    # iterate in highest priority to lowest priority
    for priorityItem in priorityQueue:
        staticList = staticDict[priorityItem]
        # go over the time interval(s) for the event
        for index, tup in enumerate(staticList):
            duration, start, end = tup
            # take a slice of the schedule and cut it so its [start, ...., end], since index is time, theres cases where the index wraps around due to modulo
            timeslot = (schedule[start:] + schedule[:end]) if start > end else schedule[start:end]
            # get the possible starting possitions
            availability = getAvailability(timeslot, duration)
            if availability != []:
                # choose a random starting possition and assign the event for the duration of the event
                x = np.random.choice(availability)
                itemName = priorityItem
                for i in range(duration):
                        schedule[(start+x+i)%modulo] = itemName
    return schedule

def fillRandomWithCDF(scheduleList, replaceVal, randomValues, associatedProbabilities = []):
    """
        fill the empty slots with values provided, you can also provide the distribution of those values being selected
        if no probability list is given, then everything will have uniform probability
    """
    # get a random list that is the size of the empty slots
    numRandom = scheduleList.count(replaceVal)
    if associatedProbabilities == [] and len(randomValues) > 0:
        size = len(randomValues)
        associatedProbabilities = [1/size]*size
    distributedSchedule = np.random.choice(randomValues, size= numRandom, p=associatedProbabilities)
    i = 0
    # fill the empty slots with the random value
    for index in range(len(scheduleList)):
        if scheduleList[index] == replaceVal:
            scheduleList[index] = distributedSchedule[i]
            i+=1
    return scheduleList

def getAvailability(partialInterval, duration):
    """
        returns the starting index of a semi-random scheduler
        if the intervals are | 1 | 2 | 3 |, and the duration is 2,
        then it will return  [0, 1], because they're the corresponding zero based index when the event can start
        if a slot contains a value that doesnt evaluate to False when evaluated as a bool, then that slot is deemed taken
    """
    a=np.zeros(len(partialInterval), dtype=int)
    for i in range(len(a)):
        # the value at that index is not filled
        if not (partialInterval[i] or a[i-1] > duration): # used De Morgans to simplify logic
            # if the value is not yet equal to the required duration
            a[i] = a[i-1]+1
        # else is that the value of a at that index stays 0
    return [time-duration+1 for time, val in enumerate(a) if val >= duration]

def countSchedule(schedule, countParam):
    storingList = [0 for _ in countParam]
    for agent_schedule in schedule:
        for daily_schedule in agent_schedule:
            for activity in daily_schedule:
                if activity in countParam:
                    storingList[countParam.index(activity)]+=1
    return storingList

def createMask(agentNumber, schedulePerAgent = 2):
    """return a 3d object in np"""
    mask = np.concatenate([np.ones(4, dtype=bool), np.zeros(4, dtype=bool)])
    newmask = np.array([np.random.permutation(mask).reshape(2,-1) for _ in range(agentNumber)], dtype=bool)
    return newmask

def assignClasses(agentSchedule, masks, classes, indexMatrix):
    """ assign classes to a schedule"""
    durr = 2
    base = 10
    for index in range(len(masks)): # odd vs even
        chair = classes[index]
        maskRow = indexMatrix[index]
        for i, val in enumerate(masks[index]):
            if val:
                indexVal = maskRow[i]
                for k in range(durr):
                    agentSchedule[index][base+(2*i)+k] = chair[i][indexVal]
    return agentSchedule

def main():
    # test schedule parameters for this file
    agentTypes = ["athletes", "stem", "party", "introverts", "terminators", "aliens", "other"]
    numAgent = 2000
    modTime = 24
    randomizedAgents = np.random.choice(agentTypes, size=numAgent, replace=True)
    # 150 classrooms with 20~50 agent capacity
    classrooms = [a for a in range(0, 150)]
    classCapacity = np.random.choice(range(20, 50), size=len(classrooms), replace=True)
    classEnrollment = np.zeros(shape=np.shape(classCapacity))
    # create schedules

    classDict = dict()
    for key in agentTypes:
        classDict[key] = {classKey: [cap, enrollment] for (classKey, cap, enrollment) in zip(classrooms, classCapacity, classEnrollment)}

    schedule = createSchedule(numAgent, randomizedAgents, classrooms, classCapacity, classDict, modulo=modTime)

    # print the first 5 agent's schedule for n unique days
    print(schedule[:5])

    createMask(numAgent)

if __name__ == "__main__":
    main()


import allure
from playwright.sync_api import Page, expect
import allure
from form_page import FormPage
import os
import inspect

@allure.description("""
Заходим на страницу с веб формой
С помощью css локаторов выбираем формы ввода и отправлем данные
Проверяем, что имя юзернейма именно то, которое мы вводили
""")
def test_css_selectors(page, url) -> None:
    page.goto(url)
    page.locator("input[name=\"username\"]").click(timeout=3000) #пример явного ожидания (playwright использует auto-wait что приближенно можно назвать неявным ожиданием)
    page.locator("input[name=\"username\"]").fill("css_test_username")
    page.locator("css = #HTMLFormElements > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(1) > input:nth-child(2)").click() #пример абсолютного пути css
    page.locator("css = #HTMLFormElements > table:nth-child(1) > tbody:nth-child(1) > tr:nth-child(2) > td:nth-child(1) > input:nth-child(2)").fill("password")
    page.screenshot(path=f"tests/screens/{os.path.basename(__file__)}{inspect.stack()[0][3]}screenshot.png",
                    full_page=True) #пример скриншота
    page.get_by_role("button", name="submit").click()
    expect(page.locator("li[id=\"_valueusername\"]")).to_have_text("css_test_username")
    page.get_by_role("link", name="Go back to the form").click()
@allure.description("""
Заходим на страницу с веб формой
С помощью xpath локаторов выбираем формы, чекбоксы, кнопки и отправлем данные
Проверяем, что имя юзернейма именно то, которое мы вводили
""")
def test_xpath_selectors(page, url) -> None:
    page.goto(url)
    page.locator("xpath = //*[@name=\"username\"]").click()
    page.locator("xpath = //*[@name=\"username\"]").fill("xpath_test_username")
    page.locator("xpath = /html/body/div/div[3]/form/table/tbody/tr[2]/td/input").click() #пример абсолютного пути xpath
    page.locator("xpath = /html/body/div/div[3]/form/table/tbody/tr[2]/td/input").fill("password")
    page.get_by_role("button", name="submit").click()
    expect(page.locator("li[id=\"_valueusername\"]")).to_have_text("xpath_test_username")
    page.get_by_role("link", name="Go back to the form").click()

####иллюстрация page object model

def test_page_object(page, url) -> None:
    form_page = FormPage(page)
    form_page.navigate()
    form_page.fill()


import png

width1, height1, values1, info1 = png.Reader(filename="scrambled1.png").read()
width2, height2, values2, info2 = png.Reader(filename="scrambled2.png").read()

xor = lambda a, b: a ^ b
row = lambda row1, row2: map(xor, row1, row2)
rows = list(map(row, values1, values2))

img = png.Image(rows, info1)
img.save("unscrambled.png")


from fastapi import APIRouter, Depends, HTTPException, Query
from app.db.database import get_db
from sqlalchemy.orm import Session
from datetime import date, timedelta
from app.db.models import Sale, Product
from sqlalchemy import func, and_
from typing import List

router = APIRouter()

@router.get("/revenue/daily/{date}", response_model=dict)
def get_daily_revenue(date: date, db: Session = Depends(get_db)):
    next_day = date + timedelta(days=1)

    # Retrieve daily revenue using SQLAlchemy query
    daily_revenue = (
        db.query(func.sum(Product.price * Sale.quantity))
        .join(Sale, Product.id == Sale.product_id)
        .filter(Sale.date >= date, Sale.date < next_day)
        .scalar() or 0.0
    )

    return {'daily_revenue': daily_revenue}

@router.get("/revenue/weekly/{start_date}", response_model=dict)
def get_weekly_revenue(start_date: date, db: Session = Depends(get_db)):
    end_date = start_date + timedelta(days=7)

    weekly_revenue = (
        db.query(func.sum(Product.price * Sale.quantity))
        .join(Sale, Product.id == Sale.product_id)
        .filter(Sale.date >= start_date, Sale.date < end_date)
        .scalar() or 0.0
    )

    return {'weekly_revenue': weekly_revenue}

@router.get("/revenue/annual/{year}", response_model=dict)
def get_annual_revenue(year: int, db: Session = Depends(get_db)):
    start_date = date(year, 1, 1)
    end_date = date(year, 12, 31) + timedelta(days=1)

    annual_revenue = (
        db.query(func.sum(Product.price * Sale.quantity))
        .join(Sale, Product.id == Sale.product_id)
        .filter(Sale.date >= start_date, Sale.date < end_date)
        .scalar() or 0.0
    )

    return {'annual_revenue': annual_revenue}

@router.get("/revenue/compare", response_model=dict)
def compare_revenue(
    start_date: date,
    end_date: date,
    product_ids: List[int] = Query(None),
    categories: List[str] = Query(None),
    db: Session = Depends(get_db)
):
    filters = [Sale.date >= start_date, Sale.date < end_date]

    if product_ids:
        filters.append(Sale.product_id.in_(product_ids))
    if categories:
        filters.append(Product.category.in_(categories))

    filtered_revenue = (
        db.query(Sale.date, func.sum(Product.price * Sale.quantity))
        .join(Product)
        .filter(and_(*filters))
        .group_by(Sale.date)
        .all()
    )

    result = {str(date): float(revenue) for date, revenue in filtered_revenue}
    return result


import collections

with open('input') as f:
    _input = list(map(lambda x: list(x), f.read().splitlines()))

N, S, W, E = (-1, 0), (1, 0), (0, -1), (0, 1)
action_order = [N, W, E, S]
wall, clear = "#", "."

class Unit:
    Race = str
    Atk = int
    HP = int
    X = int
    Y = int

    def __init__(self, race, x, y):
        self.Atk = 3
        self.HP = 200
        self.Race = race
        self.X = x
        self.Y = y

    def is_enemy_near(self):
        result = False
        for direction in action_order:
            x, y = direction
            if area[self.X + x][self.Y + y] == self.get_opposite_race():
                result = True
        return result

    def attack(self):
        x, y = self.X, self.Y
        enemy_around = list()
        for x2, y2 in ((x - 1, y), (x, y - 1), (x, y + 1), (x + 1, y)):
            if area[x2][y2] == self.get_opposite_race():
                for enemy in filter(lambda x: x.X == x2 and x.Y == y2  , units):
                    enemy_around.append(enemy)

        if len(enemy_around) > 0:
            min_hp = min(enemy_around, key=lambda x: x.HP).HP
            for x2, y2 in ((x - 1, y), (x, y - 1), (x, y + 1), (x + 1, y)):
                enemy = list(filter(lambda x: x.X == x2 and x.Y == y2 and x.HP == min_hp, units))
                if len(enemy) > 0:
                    enemy[0].HP -= 3
                    if enemy[0].HP <= 0:
                        area[enemy[0].X][enemy[0].Y] = clear
                    break

    def move(self):
        squares = list()
        for enemy in filter(lambda x: x.Race == self.get_opposite_race(), units):
            for x, y in ((enemy.X - 1, enemy.Y), (enemy.X, enemy.Y - 1), (enemy.X, enemy.Y + 1), (enemy.X + 1, enemy.Y)):
                if area[x][y] == clear:
                    squares.append([x, y])

        path = self.get_path_to_nearest_enemy(squares)

        if path is not None and len(path) > 0:
            self.X, self.Y = path[1]

    def get_path_to_nearest_enemy(self, goal):
        start = (self.X, self.Y)
        queue = collections.deque([[start]])
        seen = {start}
        while queue:
            path = queue.popleft()
            x, y = path[-1]
            if [x, y] in goal:
                return path

            for x2, y2 in ((x - 1, y), (x, y - 1), (x, y + 1), (x + 1, y)):
                if area[x2][y2] != wall and area[x2][y2] == clear and (x2, y2) not in seen:
                    queue.append(path + [(x2, y2)])
                    seen.add((x2, y2))

    def get_opposite_race(self):
        if self.Race == "G":
            return "E"
        else:
            return "G"

def update_area():
    for i in range(len(area)):
        for j in range(len(area)):
            if area[i][j] == "E" or area[i][j] == "G":
                area[i][j] = clear
    for unit in units:
        if unit.HP <= 0:
            continue
        area[unit.X][unit.Y] = unit.Race

units = list()
area = _input

for i in range(len(area)):
    for j in range(len(area)):
        if area[i][j] == "E" or area[i][j] == "G":
            units.append(Unit(area[i][j], i, j))

for i in range(0, 100):
    units.sort(key=lambda unit: (unit.X, unit.Y))
    for unit in units:
        if unit.HP <= 0:
            units.remove(unit)

    for unit in units:
        if unit.HP <= 0:
            continue

        if unit.is_enemy_near():
            unit.attack()
        else:
            unit.move()
            if unit.is_enemy_near():
                unit.attack()
        update_area()

    print(i)
    for line in area:
        print("".join(line))

    goblis = list(filter(lambda x: x.Race == "G" and x.HP > 0, units))
    elfs = list(filter(lambda x: x.Race == "E" and x.HP > 0, units))

    if len(goblis) == 0 or len(elfs) == 0:
        result = 0
        for u in filter(lambda x: x.HP > 0, units):
            print(u.HP)
            result += u.HP
        print(i, result, result * i)
        break


"""
Files and Directories
=============================================================================

When you need to save data to a local computer, you have to know how to work
with files and directories.

Example Run
-----------------------------------------------------------------------------
Doing everything in a temp dir so it cleans up automatically ...
Hello, stranger!
Welcome to python-exmaples
...

References
-----------------------------------------------------------------------------
https://docs.python.org/3/tutorial/inputoutput.html#reading-and-writing-files
"""

import os
from pathlib import Path
from tempfile import TemporaryDirectory

def main():
    with TemporaryDirectory() as tmpdir:
        print('Doing everything in a temp dir so it cleans up '
              'automatically after we are done! Temp dir is at', tmpdir)

        # Change to the temp dir
        os.chdir(tmpdir)

        # Let's write a file
        with open('hello.txt', 'w') as fp:
            fp.write('Hello, stranger!\n')
            fp.write('Welcome to python-exmaples')

        # Now, we can read it back.
        with open('hello.txt') as fp:

            # Reads everything
            print(fp.read())

            # Go back to the beginning of the file
            fp.seek(0)

            # Read one line at a time
            for line in fp:
                print(line, end='')  # Line has \n, skip same from print

            # Read a few characters at a time
            fp.seek(0)
            print(fp.read(5))

            # Or just one line
            fp.seek(0)
            print(fp.readline())

        # Using pathlib.Path to create a new dir
        new_dir = Path('.') / 'subdir'
        new_dir.mkdir()

        # Create a new file
        new_file = new_dir / 'world.txt'
        with new_file.open('w') as fp:
            fp.write('Path is pretty cool')

        # Read the file
        with new_file.open() as fp:
            print(fp.read())  # Path is pretty cool

        # Or do it using open works too
        with open(new_file) as fp:
            print(fp.read())

        # List all the files in current dir
        for f in Path.cwd().iterdir():
            print(f)
        """
        /tmp/tmpyjm9e_20/hello.txt
        /tmp/tmpyjm9e_20/subdir
        """

        # Glob for only txt files
        for f in Path.cwd().glob('*.txt'):
            print(f)
        """
        /tmp/tmpyjm9e_20/hello.txt
        """

if __name__ == "__main__":
    main()


import math
import pytest
from metamorphic_test import (
    transformation,
    relation,
    metamorphic,
    system,
    randomized,
)
from metamorphic_test.generators import RandInt
from metamorphic_test.relations import approximately

class MathLibrary():
    __PI = 3.14159265358979323846
    __PRECISION = 15

    @staticmethod
    def sin(x: float) -> float:
        x %= 2 * MathLibrary.__PI

        if x < 0:
            return -MathLibrary.sin(-x)

        if x > MathLibrary.__PI:
            return -MathLibrary.sin(x - MathLibrary.__PI)

        assert x >= 0
        assert x <= MathLibrary.__PI

        for i in range(1, MathLibrary.__PRECISION + 1):
            if i % 2 == 0:
                x += math.pow(x, 2 * i + 1) / MathLibrary.factorial(2 * i + 1)
            else:
                x -= math.pow(x, 2 * i + 1) / MathLibrary.factorial(2 * i + 1)

        return x

    @staticmethod
    def factorial(n: int) -> int:
        fact = 1
        for i in range(1, n + 1):
            fact = fact * i
        return fact

test_two_pi = metamorphic('Plus 2 π', relation=approximately)
test_negate_x = metamorphic('sin(-x)')
test_plus_pi = metamorphic('Plus 1 π')
test_pi_minus_x = metamorphic('sin(π-x)', relation=approximately)

@transformation(test_two_pi)
@randomized('n', RandInt(0, 10))
def two_pi_shift(x: float, n: int) -> float:
    return x + 2 * n * math.pi

@transformation(test_negate_x)
def negate(x: float) -> float:
    return -x

@transformation(test_plus_pi)
def pi_shift(x: float) -> float:
    return x + math.pi

@transformation(test_pi_minus_x)
def pi_shift_minus(x: float) -> float:
    return math.pi - x

@relation(test_negate_x, test_plus_pi)
def approximately_negate(x: float, y: float) -> bool:
    return approximately(-x, y)

@pytest.mark.skip
@pytest.mark.parametrize('x', range(-10, 10))
@system(test_two_pi, test_negate_x, test_plus_pi, test_pi_minus_x)
def test(x: float) -> float:
    return MathLibrary.sin(x)


"""Merge operations that select one version or another."""

from git_theta import metadata
from git_theta.merges import Merge
from git_theta.types import ParamName
from git_theta.utils import TEXT_STYLE, DiffState

class TakeUs(Merge):
    DESCRIPTION = f"Use {TEXT_STYLE.format_who('our')} change to the parameter."
    NAME = "take_us"
    SHORT_CUT = "tu"
    # If only they made a change take "us" doesn't make sense.
    INACTIVE_STATES = frozenset(
        {DiffState.CHANGED_B, DiffState.ADDED_B, DiffState.DELETED_B}
    )

    def merge(
        self,
        param_name: ParamName,
        paramA: metadata.ParamMetadata,
        paramB: metadata.ParamMetadata,
        paramO: metadata.ParamMetadata,
        *args,
        **kwargs,
    ) -> metadata.ParamMetadata:
        """Grab the changes from branch A (current)."""
        return paramA

class TakeThem(Merge):
    DESCRIPTION = f"Use {TEXT_STYLE.format_who('their')} change to the parameter."
    NAME = "take_them"
    SHORT_CUT = "tt"
    # If only we made a change take "them" doesn't make sense.
    INACTIVE_STATES = frozenset(
        {
            DiffState.CHANGED_A,
            DiffState.ADDED_A,
            DiffState.DELETED_A,
        }
    )

    def merge(
        self,
        param_name: ParamName,
        paramA: metadata.ParamMetadata,
        paramB: metadata.ParamMetadata,
        paramO: metadata.ParamMetadata,
        *args,
        **kwargs,
    ) -> metadata.ParamMetadata:
        """Grab the changes from branch B (other)."""
        return paramB

class TakeOriginal(Merge):
    DESCRIPTION = f"Use the {TEXT_STYLE.format_who('original')} parameter."
    NAME = "take_original"
    SHORT_CUT = "to"
    INACTIVE_STATES = frozenset({})

    def merge(
        self,
        param_name: ParamName,
        paramA: metadata.ParamMetadata,
        paramB: metadata.ParamMetadata,
        paramO: metadata.ParamMetadata,
        *args,
        **kwargs,
    ) -> metadata.ParamMetadata:
        """Grab the changes from the ancestor."""
        return paramO


from __future__ import print_function

import os
from glob import glob
from tqdm import trange
from itertools import chain

import torch
from torch import nn
import torch.nn.parallel
import torchvision.utils as vutils
from torch.autograd import Variable

from models import *
from data_loader import get_loader

def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        m.weight.data.normal_(0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        m.weight.data.normal_(1.0, 0.02)
        m.bias.data.fill_(0)

class Trainer(object):
    def __init__(self, config, a_data_loader, b_data_loader):
        self.config = config

        self.a_data_loader = a_data_loader
        self.b_data_loader = b_data_loader

        self.num_gpu = config.num_gpu
        self.dataset = config.dataset

        self.loss = config.loss
        self.lr = config.lr
        self.beta1 = config.beta1
        self.beta2 = config.beta2
        self.optimizer = config.optimizer
        self.batch_size = config.batch_size
        self.weight_decay = config.weight_decay
        self.cnn_type = config.cnn_type

        self.model_dir = config.model_dir
        self.load_path = config.load_path

        self.start_step = 0
        self.log_step = config.log_step
        self.max_step = config.max_step
        self.save_step = config.save_step

        self.build_model()

        if self.num_gpu == 1:
            self.G_AB.cuda()
            self.G_BA.cuda()
            self.D_A.cuda()
            self.D_B.cuda()

        elif self.num_gpu > 1:
            self.G_AB = nn.DataParallel(self.G_AB.cuda(),device_ids=range(self.num_gpu))
            self.G_BA = nn.DataParallel(self.G_BA.cuda(),device_ids=range(self.num_gpu))
            self.D_A = nn.DataParallel(self.D_A.cuda(),device_ids=range(self.num_gpu))
            self.D_B = nn.DataParallel(self.D_B.cuda(),device_ids=range(self.num_gpu))

        if self.load_path:
            self.load_model()

    def build_model(self):
        if self.dataset == 'toy':
            self.G_AB = GeneratorFC(2, 2, [config.fc_hidden_dim] * config.g_num_layer)
            self.G_BA = GeneratorFC(2, 2, [config.fc_hidden_dim] * config.g_num_layer)

            self.D_A = DiscriminatorFC(2, 1, [config.fc_hidden_dim] * config.d_num_layer)
            self.D_B = DiscriminatorFC(2, 1, [config.fc_hidden_dim] * config.d_num_layer)
        else:
            a_height, a_width, a_channel = self.a_data_loader.shape
            b_height, b_width, b_channel = self.b_data_loader.shape

            if self.cnn_type == 0:
                #conv_dims, deconv_dims = [64, 128, 256, 512], [512, 256, 128, 64]
                conv_dims, deconv_dims = [64, 128, 256, 512], [256, 128, 64]
            elif self.cnn_type == 1:
                #conv_dims, deconv_dims = [32, 64, 128, 256], [256, 128, 64, 32]
                conv_dims, deconv_dims = [32, 64, 128, 256], [128, 64, 32]
            else:
                raise Exception("[!] cnn_type {} is not defined".format(self.cnn_type))

            self.G_AB = GeneratorCNN(
                    a_channel, b_channel, conv_dims, deconv_dims, self.num_gpu)
            self.G_BA = GeneratorCNN(
                    b_channel, a_channel, conv_dims, deconv_dims, self.num_gpu)

            self.D_A = DiscriminatorCNN(
                    a_channel, 1, conv_dims, self.num_gpu)
            self.D_B = DiscriminatorCNN(
                    b_channel, 1, conv_dims, self.num_gpu)

            self.G_AB.apply(weights_init)
            self.G_BA.apply(weights_init)

            self.D_A.apply(weights_init)
            self.D_B.apply(weights_init)

    def load_model(self):
        print("[*] Load models from {}...".format(self.load_path))

        paths = glob(os.path.join(self.load_path, 'G_AB_*.pth'))
        paths.sort()

        if len(paths) == 0:
            print("[!] No checkpoint found in {}...".format(self.load_path))
            return

        idxes = [int(os.path.basename(path.split('.')[0].split('_')[-1])) for path in paths]
        self.start_step = max(idxes)

        if self.num_gpu == 0:
            map_location = lambda storage, loc: storage
        else:
            map_location = None

        G_AB_filename = '{}/G_AB_{}.pth'.format(self.load_path, self.start_step)
        self.G_AB.load_state_dict(torch.load(G_AB_filename, map_location=map_location))
        self.G_BA.load_state_dict(
            torch.load('{}/G_BA_{}.pth'.format(self.load_path, self.start_step), map_location=map_location))

        self.D_A.load_state_dict(
            torch.load('{}/D_A_{}.pth'.format(self.load_path, self.start_step), map_location=map_location))
        self.D_B.load_state_dict(
            torch.load('{}/D_B_{}.pth'.format(self.load_path, self.start_step), map_location=map_location))

        print("[*] Model loaded: {}".format(G_AB_filename))

    def train(self):
        d = nn.MSELoss()
        bce = nn.BCELoss()

        real_label = 1
        fake_label = 0

        real_tensor = Variable(torch.FloatTensor(self.batch_size))
        _ = real_tensor.data.fill_(real_label)

        fake_tensor = Variable(torch.FloatTensor(self.batch_size))
        _ = fake_tensor.data.fill_(fake_label)

        if self.num_gpu > 0:
            d.cuda()
            bce.cuda()

            real_tensor = real_tensor.cuda()
            fake_tensor = fake_tensor.cuda()

        if self.optimizer == 'adam':
            optimizer = torch.optim.Adam
        else:
            raise Exception("[!] Caution! Paper didn't use {} opimizer other than Adam".format(config.optimizer))

        optimizer_d = optimizer(
            chain(self.D_A.parameters(), self.D_B.parameters()),
            lr=self.lr, betas=(self.beta1, self.beta2), weight_decay=self.weight_decay)
        optimizer_g = optimizer(
            chain(self.G_AB.parameters(), self.G_BA.parameters()),
            lr=self.lr, betas=(self.beta1, self.beta2))

        A_loader, B_loader = iter(self.a_data_loader), iter(self.b_data_loader)
        valid_x_A, valid_x_B = self._get_variable(A_loader.next()), self._get_variable(B_loader.next())

        vutils.save_image(valid_x_A.data, '{}/valid_x_A.png'.format(self.model_dir))
        vutils.save_image(valid_x_B.data, '{}/valid_x_B.png'.format(self.model_dir))

        for step in trange(self.start_step, self.max_step):
            try:
                x_A, x_B = A_loader.next(), B_loader.next()
            except StopIteration:
                A_loader, B_loader = iter(self.a_data_loader), iter(self.b_data_loader)
                x_A, x_B = A_loader.next(), B_loader.next()
            if x_A.size(0) != x_B.size(0):
                print("[!] Sampled dataset from A and B have different # of data. Try resampling...")
                continue

            x_A, x_B = self._get_variable(x_A), self._get_variable(x_B)

            batch_size = x_A.size(0)
            real_tensor.data.resize_(batch_size).fill_(real_label)
            fake_tensor.data.resize_(batch_size).fill_(fake_label)

            # update D network
            self.D_A.zero_grad()
            self.D_B.zero_grad()

            x_AB = self.G_AB(x_A).detach()
            x_BA = self.G_BA(x_B).detach()

            x_ABA = self.G_BA(x_AB).detach()
            x_BAB = self.G_AB(x_BA).detach()

            if self.loss == "log_prob":
                l_d_A_real, l_d_A_fake = bce(self.D_A(x_A), real_tensor), bce(self.D_A(x_BA), fake_tensor)
                l_d_B_real, l_d_B_fake = bce(self.D_B(x_B), real_tensor), bce(self.D_B(x_AB), fake_tensor)
            elif self.loss == "least_square":
                l_d_A_real, l_d_A_fake = \
                    0.5 * torch.mean((self.D_A(x_A) - 1)**2), 0.5 * torch.mean((self.D_A(x_BA))**2)
                l_d_B_real, l_d_B_fake = \
                    0.5 * torch.mean((self.D_B(x_B) - 1)**2), 0.5 * torch.mean((self.D_B(x_AB))**2)
            else:
                raise Exception("[!] Unkown loss type: {}".format(self.loss))

            l_d_A = l_d_A_real + l_d_A_fake
            l_d_B = l_d_B_real + l_d_B_fake

            l_d = l_d_A + l_d_B

            l_d.backward()
            optimizer_d.step()

            # update G network
            self.G_AB.zero_grad()
            self.G_BA.zero_grad()

            x_AB = self.G_AB(x_A)
            x_BA = self.G_BA(x_B)

            x_ABA = self.G_BA(x_AB)
            x_BAB = self.G_AB(x_BA)

            l_const_A = d(x_ABA, x_A)
            l_const_B = d(x_BAB, x_B)

            if self.loss == "log_prob":
                l_gan_A = bce(self.D_A(x_BA), real_tensor)
                l_gan_B = bce(self.D_B(x_AB), real_tensor)
            elif self.loss == "least_square":
                l_gan_A = 0.5 * torch.mean((self.D_A(x_BA) - 1)**2)
                l_gan_B = 0.5 * torch.mean((self.D_B(x_AB) - 1)**2)
            else:
                raise Exception("[!] Unkown loss type: {}".format(self.loss))

            l_g = l_gan_A + l_gan_B + l_const_A + l_const_B

            l_g.backward()
            optimizer_g.step()

            if step % self.log_step == 0:
                print("[{}/{}] Loss_D: {:.4f} Loss_G: {:.4f}". \
                      format(step, self.max_step, l_d.data[0], l_g.data[0]))

                print("[{}/{}] l_d_A_real: {:.4f} l_d_A_fake: {:.4f}, l_d_B_real: {:.4f}, l_d_B_fake: {:.4f}". \
                      format(step, self.max_step, l_d_A_real.data[0], l_d_A_fake.data[0],
                             l_d_B_real.data[0], l_d_B_fake.data[0]))

                print("[{}/{}] l_const_A: {:.4f} l_const_B: {:.4f}, l_gan_A: {:.4f}, l_gan_B: {:.4f}". \
                      format(step, self.max_step, l_const_A.data[0], l_const_B.data[0],
                             l_gan_A.data[0], l_gan_B.data[0]))

                self.generate_with_A(valid_x_A, self.model_dir, idx=step)
                self.generate_with_B(valid_x_B, self.model_dir, idx=step)

            if step % self.save_step == self.save_step - 1:
                print("[*] Save models to {}...".format(self.model_dir))

                torch.save(self.G_AB.state_dict(), '{}/G_AB_{}.pth'.format(self.model_dir, step))
                torch.save(self.G_BA.state_dict(), '{}/G_BA_{}.pth'.format(self.model_dir, step))

                torch.save(self.D_A.state_dict(), '{}/D_A_{}.pth'.format(self.model_dir, step))
                torch.save(self.D_B.state_dict(), '{}/D_B_{}.pth'.format(self.model_dir, step))

    def generate_with_A(self, inputs, path, idx=None):
        x_AB = self.G_AB(inputs)
        x_ABA = self.G_BA(x_AB)

        x_AB_path = '{}/{}_x_AB.png'.format(path, idx)
        x_ABA_path = '{}/{}_x_ABA.png'.format(path, idx)

        vutils.save_image(x_AB.data, x_AB_path)
        print("[*] Samples saved: {}".format(x_AB_path))

        vutils.save_image(x_ABA.data, x_ABA_path)
        print("[*] Samples saved: {}".format(x_ABA_path))

    def generate_with_B(self, inputs, path, idx=None):
        x_BA = self.G_BA(inputs)
        x_BAB = self.G_AB(x_BA)

        x_BA_path = '{}/{}_x_BA.png'.format(path, idx)
        x_BAB_path = '{}/{}_x_BAB.png'.format(path, idx)

        vutils.save_image(x_BA.data, x_BA_path)
        print("[*] Samples saved: {}".format(x_BA_path))

        vutils.save_image(x_BAB.data, x_BAB_path)
        print("[*] Samples saved: {}".format(x_BAB_path))

    def generate_infinitely(self, inputs, path, input_type, count=10, nrow=2, idx=None):
        if input_type.lower() == "a":
            iterator = [self.G_AB, self.G_BA] * count
        elif input_type.lower() == "b":
            iterator = [self.G_BA, self.G_AB] * count

        out = inputs
        for step, model in enumerate(iterator):
            out = model(out)

            out_path = '{}/{}_x_{}_#{}.png'.format(path, idx, input_type, step)
            vutils.save_image(out.data, out_path, nrow=nrow)
            print("[*] Samples saved: {}".format(out_path))

    def test(self):
        batch_size = self.config.sample_per_image
        A_loader, B_loader = iter(self.a_data_loader), iter(self.b_data_loader)

        test_dir = os.path.join(self.model_dir, 'test')
        if not os.path.exists(test_dir):
            os.makedirs(test_dir)

        step = 0
        while True:
            try:
                x_A, x_B = self._get_variable(A_loader.next()), self._get_variable(B_loader.next())
            except StopIteration:
                print("[!] Test sample generation finished. Samples are in {}".format(test_dir))
                break

            vutils.save_image(x_A.data, '{}/{}_x_A.png'.format(test_dir, step))
            vutils.save_image(x_B.data, '{}/{}_x_B.png'.format(test_dir, step))

            self.generate_with_A(x_A, test_dir, idx=step)
            self.generate_with_B(x_B, test_dir, idx=step)

            self.generate_infinitely(x_A, test_dir, input_type="A", count=10, nrow=4, idx=step)
            self.generate_infinitely(x_B, test_dir, input_type="B", count=10, nrow=4, idx=step)

            step += 1

    def _get_variable(self, inputs):
        if self.num_gpu > 0:
            out = Variable(inputs.cuda())
        else:
            out = Variable(inputs)
        return out


from typing import List, Optional

# External Dependencies:
from aws_cdk import CfnOutput, CfnParameter, Duration, RemovalPolicy, Stack
from aws_cdk.aws_iam import (
    ManagedPolicy,
    PolicyDocument,
)
import aws_cdk.aws_s3 as s3
import aws_cdk.aws_ssm as ssm
from constructs import Construct

# Local Dependencies:
from annotation import AnnotationInfra
from pipeline import ProcessingPipeline
from pipeline.iam_utils import (
    S3Statement,
    SsmParameterReadStatement,
    StateMachineExecuteStatement,
)

class PipelineDemoStack(Stack):
    """Deployable CDK stack bundling the core OCR pipeline construct with supporting demo resources

    This stack bundles the core ProcessingPipeline construct with the additional resources required
    to deploy and use it for the demo: Such as the project ID SSM parameter, input data bucket,
    SageMaker permissions policy, and the infrastructure for custom annotation UIs in SageMaker
    Ground Truth.

    It also creates several CloudFormation stack outputs to help users find important created
    resources.
    """

    def __init__(
        self,
        scope: Construct,
        construct_id: str,
        default_project_id: str,
        use_thumbnails: bool,
        enable_sagemaker_autoscaling: bool = False,
        build_sagemaker_ocrs: List[str] = [],
        deploy_sagemaker_ocrs: List[str] = [],
        use_sagemaker_ocr: Optional[str] = None,
        **kwargs,
    ) -> None:
        """Create a PipelineDemoStack

        Parameters
        ----------
        scope :
            As per aws_cdk.Stack
        construct_id :
            As per aws_cdk.Stack
        default_project_id :
            The `ProjectId` is a CFn stack parameter that prefixes created SSM parameters and
            allows SageMaker notebooks to look up the parameters for the deployed stack. If you're
            deploying straight from `cdk deploy`, then the value you specify here will be used. If
            you're `cdk synth`ing a CloudFormation template, then this will be the default value
            for the ProjectId parameter.
        use_thumbnails :
            Set `True` to build the stack with support for visual (page thumbnail image) model
            input features, or `False` to omit the thumbnailing step. Pipelines deployed with
            `use_thumbnails=True` will fail if a thumbnailer endpoint is not set up (see SageMaker
            notebooks). Pipelines deployed with `use_thumbnails=False` cannot fully utilize model
            architectures that use page images for inference (such as LayoutLMv2+, etc).
        enable_sagemaker_autoscaling :
            Set True to enable auto-scale-to-zero on any SageMaker endpoints created by the stack.
            Turning this on should improve cost-efficiency for workloads which are often idle, but
            will introduce cold-start delays to affected stages of the pipeline so may not be ideal
            during development. This setting does not affect endpoints created *outside* the stack
            and later plumbed in to the pipeline (i.e. endpoints deployed from notebooks).
        build_sagemaker_ocrs :
            List of alternative (SageMaker-based) OCR engine names to build container images and
            SageMaker Models for in the deployed stack. By default ([]), none will be included. See
            `CUSTOM_OCR_ENGINES` in pipeline/ocr/sagemaker_ocr.py for supported engines.
        deploy_sagemaker_ocrs :
            List of alternative OCR engine names to deploy SageMaker endpoints for in the stack. Any
            names in here must also be included in `build_sagemaker_ocrs`. Default []: Support
            Amazon Textract OCR only.
        use_sagemaker_ocr :
            Optional alternative OCR engine name to use in the deployed document pipeline. If set
            and not empty, this must also be present in `build_sagemaker_ocrs` and
            `deploy_sagemaker_ocrs`. Default None: Use Amazon Textract for initial document OCR.
        **kwargs :
            As per aws_cdk.Stack
        """
        super().__init__(scope, construct_id, **kwargs)

        # Could consider just directly using the stack ID for this, but then if you were to vend
        # the stack through e.g. AWS Service Catalog you may not have control over setting a nice
        # readable stack ID:
        self.project_id_param = CfnParameter(
            self,
            "ProjectId",
            allowed_pattern=r"[a-zA-Z0-9]+(\-[a-zA-Z0-9]+)*",
            constraint_description="Alphanumeric with internal hyphens allowed",
            default=default_project_id,
            description=(
                "ID to look up this stack's resources from SageMaker notebooks, used in folder "
                "prefixes for SSM parameters."
            ),
            max_length=25,
            min_length=3,
        )

        self.annotation_infra = AnnotationInfra(self, "AnnotationInfra")

        self.input_bucket = s3.Bucket(
            self,
            "PipelineInputBucket",
            auto_delete_objects=True,
            block_public_access=s3.BlockPublicAccess(
                block_public_acls=True,
                block_public_policy=True,
                ignore_public_acls=True,
                restrict_public_buckets=True,
            ),
            encryption=s3.BucketEncryption.S3_MANAGED,
            enforce_ssl=True,
            lifecycle_rules=[
                s3.LifecycleRule(enabled=True, expiration=Duration.days(7)),
            ],
            removal_policy=RemovalPolicy.DESTROY,
            cors=[
                # CORS permissions are required for the A2I human review UI to retrieve objects:
                s3.CorsRule(
                    allowed_headers=["*"],
                    allowed_methods=[s3.HttpMethods.GET],
                    allowed_origins=[
                        "https://mturk-console-template-preview-hooks.s3.amazonaws.com",
                    ],
                ),
            ],
        )
        self.pipeline = ProcessingPipeline(
            self,
            "ProcessingPipeline",
            input_bucket=self.input_bucket,
            ssm_param_prefix=f"/{self.project_id_param.value_as_string}/config/",
            use_thumbnails=use_thumbnails,
            enable_sagemaker_autoscaling=enable_sagemaker_autoscaling,
            build_sagemaker_ocrs=build_sagemaker_ocrs,
            deploy_sagemaker_ocrs=deploy_sagemaker_ocrs,
            use_sagemaker_ocr=use_sagemaker_ocr,
        )
        self.data_science_policy = ManagedPolicy(
            self,
            "PipelineDataSciencePolicy",
            document=PolicyDocument(
                statements=[
                    S3Statement(
                        grant_write=True,
                        resources=[self.input_bucket],
                        sid="ReadWritePipelineInputBucket",
                    ),
                    StateMachineExecuteStatement(
                        resources=[self.pipeline.plain_textract_state_machine],
                        sid="RunPlainTextractStateMachine",
                    ),
                ]
                + self.annotation_infra.get_data_science_policy_statements()
                + self.pipeline.config_read_write_statements()
                # In the notebooks we'll use the same execution role for the trained model/endpoint
                # as the notebook itself runs with - so need to grant the role the required perms
                # for reading/writing relevant S3 buckets and publishing to SNS in the pipeline:
                + self.pipeline.sagemaker_model_statements(),
            ),
        )

        # We'd like the stack to push some useful outputs to CloudFormation, and defining them here
        # rather than in the lower-level constructs will keep the constructs flexible/reusable.
        #
        # We override the auto-generated logical IDs to make the names simple to find in console.
        self.data_science_policy_output = CfnOutput(
            self,
            "DataSciencePolicyName",
            description=(
                "Name of the IAM policy with permissions needed for the SageMaker notebooks to "
                "access this stack's resources. Add this policy to your SageMaker execution role."
            ),
            value=self.data_science_policy.managed_policy_name,
        )
        self.data_science_policy_output.override_logical_id("DataSciencePolicyName")
        self.input_bucket_name_output = CfnOutput(
            self,
            "InputBucketName",
            description="Name of the S3 bucket to which input documents should be uploaded",
            value=self.pipeline.input_bucket.bucket_name,
        )
        self.input_bucket_name_output.override_logical_id("InputBucketName")
        self.pipeline_statemachine_output = CfnOutput(
            self,
            "PipelineStateMachine",
            description="ARN of the State Machine for the end-to-end OCR pipeline",
            value=self.pipeline.state_machine.state_machine_arn,
        )
        self.pipeline_statemachine_output.override_logical_id("PipelineStateMachine")
        self.textract_statemachine_output = CfnOutput(
            self,
            "PlainTextractStateMachine",
            description="ARN of the State Machine for *only* running Textract (no enrichments)",
            value=self.pipeline.plain_textract_state_machine.state_machine_arn,
        )
        self.textract_statemachine_output.override_logical_id("PlainTextractStateMachine")
        self.model_param_output = CfnOutput(
            self,
            "SageMakerEndpointParamName",
            description="SSM parameter to configure the pipeline's SageMaker endpoint name",
            value=self.pipeline.sagemaker_endpoint_param.parameter_name,
        )
        self.model_param_output.override_logical_id("SageMakerEndpointParamName")
        self.thumbnail_param_output = CfnOutput(
            self,
            "ThumbnailEndpointParamName",
            description=(
                "SSM parameter to configure the pipeline's Thumbnail generation endpoint name"
            ),
            value="undefined"
            if self.pipeline.thumbnail_endpoint_param is None
            else self.pipeline.thumbnail_endpoint_param.parameter_name,
        )
        self.thumbnail_param_output.override_logical_id("ThumbnailEndpointParamName")
        self.entity_config_param_output = CfnOutput(
            self,
            "EntityConfigParamName",
            description=(
                "JSON configuration describing the field types to be extracted by the pipeline"
            ),
            value=self.pipeline.entity_config_param.parameter_name,
        )
        self.entity_config_param_output.override_logical_id("EntityConfigParamName")
        self.a2i_role_arn_output = CfnOutput(
            self,
            "A2IHumanReviewExecutionRoleArn",
            description="ARN of the execution Role to use for Amazon A2I human review workflows",
            value=self.pipeline.review_a2i_role.role_arn,
        )
        self.a2i_role_arn_output.override_logical_id("A2IHumanReviewExecutionRoleArn")
        self.workflow_param_output = CfnOutput(
            self,
            "A2IHumanReviewFlowParamName",
            description="SSM parameter to configure the pipeline's A2I review workflow ARN",
            value=self.pipeline.review_workflow_param.parameter_name,
        )
        self.workflow_param_output.override_logical_id("A2IHumanReviewFlowParamName")
        self.reviews_bucket_name_output = CfnOutput(
            self,
            "A2IHumanReviewBucketName",
            description="Name of the S3 bucket to which A2I reviews should be stored",
            value=self.pipeline.human_reviews_bucket.bucket_name,
        )
        self.reviews_bucket_name_output.override_logical_id("A2IHumanReviewBucketName")

        # While these CloudFormation outputs are nice for the CFn console, we'd also like to be
        # able to automatically look up project resources from SageMaker notebooks. To support
        # this, we'll create additional SSM params used just to *retrieve* static attributes of the
        # stack - rather than configuration points like the ProcessingPipeline construct's params.
        static_param_prefix = f"/{self.project_id_param.value_as_string}/static"
        self.sm_image_build_role_ssm_param = ssm.StringParameter(
            self,
            "SMImageBuildRoleSSMParam",
            string_value=self.annotation_infra.sm_image_build_role.role_name,
            description=(
                "Name of the CodeBuild execution role to use in SMStudio Image Build commands"
            ),
            parameter_name=f"{static_param_prefix}/SMDockerBuildRole",
            simple_name=False,
        )
        self.preproc_image_param = ssm.StringParameter(
            self,
            "PreprocImageSSMParam",
            description="URI of the thumbnail generator container image pre-created by the stack",
            parameter_name=f"{static_param_prefix}/PreprocImageURI",
            simple_name=False,
            string_value=self.pipeline.preproc_image.image_uri,
        )
        self.input_bucket_ssm_param = ssm.StringParameter(
            self,
            "InputBucketNameSSMParam",
            string_value=self.input_bucket.bucket_name,
            description="Name of the S3 bucket to which input documents should be uploaded",
            parameter_name=f"{static_param_prefix}/InputBucket",
            simple_name=False,
        )
        self.reviews_bucket_ssm_param = ssm.StringParameter(
            self,
            "ReviewsBucketNameSSMParam",
            string_value=self.pipeline.human_reviews_bucket.bucket_name,
            description="Name of the S3 bucket to which human reviews should be stored",
            parameter_name=f"{static_param_prefix}/ReviewsBucket",
            simple_name=False,
        )
        self.pipeline_statemachine_ssm_param = ssm.StringParameter(
            self,
            "PipelineStateMachineSSMParam",
            string_value=self.pipeline.state_machine.state_machine_arn,
            description="ARN of the State Machine for the end-to-end OCR pipeline",
            parameter_name=f"{static_param_prefix}/PipelineStateMachine",
            simple_name=False,
        )
        self.textract_statemachine_ssm_param = ssm.StringParameter(
            self,
            "PlainTextractStateMachineSSMParam",
            string_value=self.pipeline.plain_textract_state_machine.state_machine_arn,
            description="ARN of the State Machine for *only* running Textract (no enrichments)",
            parameter_name=(
                f"/{self.project_id_param.value_as_string}/static/PlainTextractStateMachine"
            ),
            simple_name=False,
        )
        self.enrichment_results_bucket_ssm_param = ssm.StringParameter(
            self,
            "EnrichmentModelResultsBucketSSMParam",
            description=(
                "Name of the S3 bucket to which SageMaker (async) model results should be stored"
            ),
            parameter_name=f"{static_param_prefix}/ModelResultsBucket",
            simple_name=False,
            string_value=self.pipeline.enriched_results_bucket.bucket_name,
        )
        self.thumbnails_callback_topic_ssm_param = ssm.StringParameter(
            self,
            "ThumbnailsCallbackTopicSSMParam",
            description="ARN of the SNS Topic to use for thumbnail images generation callback",
            parameter_name=f"{static_param_prefix}/ThumbnailsCallbackTopicArn",
            simple_name=False,
            string_value=(
                self.pipeline.thumbnail_sns_topic.topic_arn
                if self.pipeline.thumbnail_sns_topic
                else "undefined"
            ),
        )
        self.enrichment_callback_topic_ssm_param = ssm.StringParameter(
            self,
            "EnrichmentModelCallbackTopicSSMParam",
            description="ARN of the SNS Topic to use for callback in SageMaker Async Inference",
            parameter_name=f"{static_param_prefix}/ModelCallbackTopicArn",
            simple_name=False,
            string_value=(
                self.pipeline.sagemaker_sns_topic.topic_arn
                if self.pipeline.sagemaker_sns_topic
                else "undefined"
            ),
        )
        self.a2i_role_arn_param = ssm.StringParameter(
            self,
            "A2IExecutionRoleArnParam",
            string_value=self.pipeline.review_a2i_role.role_arn,
            description="ARN of the execution role which A2I human review workflows should use",
            parameter_name=f"{static_param_prefix}/A2IExecutionRoleArn",
            simple_name=False,
        )

        self.data_science_policy.add_statements(
            SsmParameterReadStatement(
                resources=[
                    self.sm_image_build_role_ssm_param,
                    self.input_bucket_ssm_param,
                    self.reviews_bucket_ssm_param,
                    self.pipeline_statemachine_ssm_param,
                    self.preproc_image_param,
                    self.textract_statemachine_ssm_param,
                    self.thumbnails_callback_topic_ssm_param,
                    self.enrichment_callback_topic_ssm_param,
                    self.enrichment_results_bucket_ssm_param,
                    self.a2i_role_arn_param,
                ],
                sid="ReadStaticPipelineParams",
            ),
        )


from filters.filters import toDiscreteFrequency
from multiprocessing import Lock

# this class stores user-interface settings
class filterSettings:
    frequencies = []
    deltas = []
    amplitudes = []
    rate = 22050 # sampling rate

    def update(self, f, d, a, rate):

        if( len(f) != len(d) ):
            print("frequencies and delta vectors should have same length.")

        if ( len(a)!= len(f) / 2 + 1 ):
            print("amplitudes vector should have 'len(freqs) / 2 + 1' length.")

        self.frequencies = toDiscreteFrequency(f, rate)
        self.deltas = d
        self.amplitudes = a
        self.rate = rate

    def __init__(self, f, d, a, rate):
        self.update(f, d, a, rate)

# this class stores specific gui settings
# for multi band filters with handles
class guiSettings:
    filterSelected = 0
    resolutionSelected = 0
    rateSelected = 0
    handleSelected = 0
    handleValue = 0

    def __init__(self, *args, **kwargs):
        if 'initValues' in kwargs:
            init = kwargs['initValues']
            self.filterSelected = init[0]
            self.resolutionSelected = init[1]
            self.rateSelected = init[2]
            self.handleSelected = init[3]
            self.handleValue = init[4]

    def printSettings(self):
        print("filType:", self.filterSelected, end=", ")
        print("res:", self.resolutionSelected, end=", ")
        print("rate:", self.rateSelected, end=", ")
        print("handle: [", self.handleSelected, end=", ")
        print(self.handleValue, "]")

    # checks if something changed
    def settingsChanged(self, oldSettings):
        a = self.filterSelected != oldSettings.filterSelected
        b = self.resolutionSelected != oldSettings.resolutionSelected
        c = self.rateSelected != oldSettings.rateSelected
        return a or b or c


from ModelHelper.Common.CommonUtils import get, get_valid
from ModelHelper.Common.CommonModels.ModelFactory import BackboneFactory
from torch.autograd import Variable
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

class AbstractClassifyModel(nn.Module):
    def __init__(self, **kwargs):
        super(AbstractClassifyModel, self).__init__()
        backbone = get_valid('backbone', kwargs)
        backbone_factory = BackboneFactory()
        kwargs['model_name'] = backbone
        self.backbone = backbone_factory.get_model(**kwargs)

    def forward(self, **kwargs):
        pass

# NTS Net
class ProposalNet(nn.Module):
    def __init__(self, **kwargs):
        super(ProposalNet, self).__init__()
        self.nts_fc_ratio = get_valid("nts_fc_ratio", kwargs)
        self.down1 = nn.Conv2d(512*self.nts_fc_ratio, 128, 3, 1, 1)
        self.down2 = nn.Conv2d(128, 128, 3, 2, 1)
        self.down3 = nn.Conv2d(128, 128, 3, 2, 1)
        self.ReLU = nn.ReLU()
        self.tidy1 = nn.Conv2d(128, 6, 1, 1, 0)
        self.tidy2 = nn.Conv2d(128, 6, 1, 1, 0)
        self.tidy3 = nn.Conv2d(128, 9, 1, 1, 0)

    def forward(self, x):
        batch_size = x.size(0)
        d1 = self.ReLU(self.down1(x))
        d2 = self.ReLU(self.down2(d1))
        d3 = self.ReLU(self.down3(d2))
        t1 = self.tidy1(d1).view(batch_size, -1)
        t2 = self.tidy2(d2).view(batch_size, -1)
        t3 = self.tidy3(d3).view(batch_size, -1)
        return torch.cat((t1, t2, t3), dim=1)

class NTSClassifyModel(AbstractClassifyModel):
    def __init__(self, **kwargs):
        kwargs['get_layer4_feature'] = True
        kwargs['get_fc_feature'] = True
        kwargs['get_fc_output'] = True
        self.nts_fc_ratio = get_valid("nts_fc_ratio", kwargs)
        self.use_gpu = get('use_gpu', kwargs, True)
        self.class_num = get_valid('class_num', kwargs)
        super(NTSClassifyModel, self).__init__(**kwargs)

        self.top_n = get('top_n', kwargs, 6)
        self.crop_size = get('crop_size', kwargs, (448, 448))
        self.cat_num = get('cat_num', kwargs, 4)
        self._default_anchors_setting = (
            dict(layer='p3', stride=32, size=48, scale=[2 ** (1. / 3.), 2 ** (2. / 3.)], aspect_ratio=[0.667, 1, 1.5]),
            dict(layer='p4', stride=64, size=96, scale=[2 ** (1. / 3.), 2 ** (2. / 3.)], aspect_ratio=[0.667, 1, 1.5]),
            dict(layer='p5', stride=128, size=192, scale=[1, 2 ** (1. / 3.), 2 ** (2. / 3.)],
                 aspect_ratio=[0.667, 1, 1.5]),
        )

        self.backbone.avgpool = nn.AdaptiveAvgPool2d(1)
        self.backbone.fc = nn.Linear(512*self.nts_fc_ratio, self.class_num)
        self.proposal_net = ProposalNet(**kwargs)
        self.concat_net = nn.Linear(512*self.nts_fc_ratio * (self.cat_num + 1), self.class_num)
        self.partcls_net = nn.Linear(512 * self.nts_fc_ratio, self.class_num)
        _, edge_anchors, _ = self.generate_default_anchor_maps()
        self.pad_side = 224
        self.edge_anchors = (edge_anchors + 224).astype(np.int)

    def generate_default_anchor_maps(self, anchors_setting=None):
        """
        generate default anchor

        :param anchors_setting: all informations of anchors
        :param input_shape: shape of input images, e.g. (h, w)
        :return: center_anchors: # anchors * 4 (oy, ox, h, w)
                 edge_anchors: # anchors * 4 (y0, x0, y1, x1)
                 anchor_area: # anchors * 1 (area)
        """
        input_shape = self.crop_size
        if anchors_setting is None:
            anchors_setting = self._default_anchors_setting

        center_anchors = np.zeros((0, 4), dtype=np.float32)
        edge_anchors = np.zeros((0, 4), dtype=np.float32)
        anchor_areas = np.zeros((0,), dtype=np.float32)
        input_shape = np.array(input_shape, dtype=int)

        for anchor_info in anchors_setting:

            stride = anchor_info['stride']
            size = anchor_info['size']
            scales = anchor_info['scale']
            aspect_ratios = anchor_info['aspect_ratio']

            output_map_shape = np.ceil(input_shape.astype(np.float32) / stride)
            output_map_shape = output_map_shape.astype(np.int)
            output_shape = tuple(output_map_shape) + (4,)
            ostart = stride / 2.
            oy = np.arange(ostart, ostart + stride * output_shape[0], stride)
            oy = oy.reshape(output_shape[0], 1)
            ox = np.arange(ostart, ostart + stride * output_shape[1], stride)
            ox = ox.reshape(1, output_shape[1])
            center_anchor_map_template = np.zeros(output_shape, dtype=np.float32)
            center_anchor_map_template[:, :, 0] = oy
            center_anchor_map_template[:, :, 1] = ox
            for scale in scales:
                for aspect_ratio in aspect_ratios:
                    center_anchor_map = center_anchor_map_template.copy()
                    center_anchor_map[:, :, 2] = size * scale / float(aspect_ratio) ** 0.5
                    center_anchor_map[:, :, 3] = size * scale * float(aspect_ratio) ** 0.5

                    edge_anchor_map = np.concatenate((center_anchor_map[..., :2] - center_anchor_map[..., 2:4] / 2.,
                                                      center_anchor_map[..., :2] + center_anchor_map[..., 2:4] / 2.),
                                                     axis=-1)
                    anchor_area_map = center_anchor_map[..., 2] * center_anchor_map[..., 3]
                    center_anchors = np.concatenate((center_anchors, center_anchor_map.reshape(-1, 4)))
                    edge_anchors = np.concatenate((edge_anchors, edge_anchor_map.reshape(-1, 4)))
                    anchor_areas = np.concatenate((anchor_areas, anchor_area_map.reshape(-1)))

        return center_anchors, edge_anchors, anchor_areas

    @staticmethod
    def hard_nms(cdds, topn=10, iou_thresh=0.25):
        if not (type(cdds).__module__ == 'numpy' and len(cdds.shape) == 2 and cdds.shape[1] >= 5):
            raise TypeError('edge_box_map should be N * 5+ ndarray')

        cdds = cdds.copy()
        indices = np.argsort(cdds[:, 0])
        cdds = cdds[indices]
        cdd_results = []

        res = cdds

        while res.any():
            cdd = res[-1]
            cdd_results.append(cdd)
            if len(cdd_results) == topn:
                return np.array(cdd_results)
            res = res[:-1]

            start_max = np.maximum(res[:, 1:3], cdd[1:3])
            end_min = np.minimum(res[:, 3:5], cdd[3:5])
            lengths = end_min - start_max
            intersec_map = lengths[:, 0] * lengths[:, 1]
            intersec_map[np.logical_or(lengths[:, 0] < 0, lengths[:, 1] < 0)] = 0
            iou_map_cur = intersec_map / ((res[:, 3] - res[:, 1]) * (res[:, 4] - res[:, 2]) + (cdd[3] - cdd[1]) * (
                    cdd[4] - cdd[2]) - intersec_map)
            res = res[iou_map_cur < iou_thresh]

        return np.array(cdd_results)

    def forward(self, **kwargs):
        super(NTSClassifyModel, self).forward(**kwargs)
        image = get_valid('image', kwargs)

        feature_list = self.backbone(image)
        resnet_out = feature_list[2]
        rpn_feature = feature_list[0]
        feature = feature_list[1]

        x_pad = F.pad(image, (self.pad_side, self.pad_side, self.pad_side, self.pad_side), mode='constant', value=0)
        batch = image.size(0)
        # we will reshape rpn to shape: batch * nb_anchor
        rpn_score = self.proposal_net(rpn_feature.detach())
        all_cdds = [
            np.concatenate((x.reshape(-1, 1), self.edge_anchors.copy(), np.arange(0, len(x)).reshape(-1, 1)), axis=1)
            for x in rpn_score.data.cpu().numpy()]
        top_n_cdds = [self.hard_nms(x, topn=self.top_n, iou_thresh=0.25) for x in all_cdds]
        top_n_cdds = np.array(top_n_cdds)
        top_n_index = top_n_cdds[:, :, -1].astype(np.int)
        if self.use_gpu:
            top_n_index = torch.from_numpy(top_n_index).cuda()
        else:
            top_n_index = torch.from_numpy(top_n_index)
        top_n_prob = torch.gather(rpn_score, dim=1, index=top_n_index.long())
        if self.use_gpu:
            part_imgs = torch.zeros([batch, self.top_n, 3, 224, 224]).cuda()
        else:
            part_imgs = torch.zeros([batch, self.top_n, 3, 224, 224])
        for i in range(batch):
            for j in range(self.top_n):
                [y0, x0, y1, x1] = top_n_cdds[i][j, 1:5].astype(np.int)
                part_imgs[i:i + 1, j] = F.interpolate(x_pad[i:i + 1, :, y0:y1, x0:x1], size=(224, 224), mode='bilinear',
                                                      align_corners=True)
        part_imgs = part_imgs.view(batch * self.top_n, 3, 224, 224)
        feature_list2 = self.backbone(part_imgs.detach())
        part_features = feature_list2[1]
        part_feature = part_features.view(batch, self.top_n, -1)
        part_feature = part_feature[:, :self.cat_num, ...].contiguous()
        part_feature = part_feature.view(batch, -1)
        concat_out = torch.cat([part_feature, feature], dim=1)
        concat_logits = self.concat_net(concat_out)
        raw_logits = resnet_out
        part_logits = self.partcls_net(part_features).view(batch, self.top_n, -1)
        return [raw_logits, concat_logits, part_logits, top_n_index, top_n_prob]

    @staticmethod
    def list_loss(logits, targets):
        temp = F.log_softmax(logits, -1)
        loss = [-temp[i][targets[i].item()] for i in range(logits.size(0))]
        return torch.stack(loss)

    @staticmethod
    def ranking_loss(score, targets, proposal_num, **kwargs):
        use_gpu = get('use_gpu', kwargs, True)
        if use_gpu:
            loss = Variable(torch.zeros(1).cuda())
        else:
            loss = Variable(torch.zeros(1))
        batch_size = score.size(0)
        for i in range(proposal_num):
            if use_gpu:
                targets_p = (targets > targets[:, i].unsqueeze(1)).type(torch.cuda.FloatTensor)
            else:
                targets_p = (targets > targets[:, i].unsqueeze(1)).type(torch.FloatTensor)
            pivot = score[:, i].unsqueeze(1)
            loss_p = (1 - pivot + score) * targets_p
            loss_p = torch.sum(F.relu(loss_p))
            loss += loss_p
        return loss / batch_size

class ResnetClassifyModel(AbstractClassifyModel):
    def __init__(self, **kwargs):
        kwargs['get_fc_feature'] = True
        super(ResnetClassifyModel, self).__init__(**kwargs)
        self.class_num = get_valid('class_num', kwargs)
        fc_input_num = get_valid('fc_input_num', kwargs)
        self.fc = nn.Linear(fc_input_num, self.class_num)

    def forward(self, **kwargs):
        super(ResnetClassifyModel, self).forward(**kwargs)
        image = get_valid('image', kwargs)
        feature_list = self.backbone(image)
        output = self.fc(feature_list[0])
        return output


# 破解ctf比赛中的md5截断认证, 如: substr(md5($str), 0, 6) === "3322cf"
from hashlib import md5
from string import ascii_letters, digits
from itertools import permutations

all_string = ascii_letters + digits + '.,:;-_'

def fuzz_md5(value):
    value = value.lower()     # 转换为小写
    for i in range(10):
        print('.', end = '')
        for item in permutations(all_string, i):
            item = ''.join(item)
            if md5(item.encode()).hexdigest()[0:len(value)] == value:
                print('\nSuccess: ' + value + ' ==> ' + item)
                return

md5_value = '3322cf'
fuzz_md5(md5_value)


int_encode = b'2'
float_encode = b'42.3'
string1 = "Hello!"
string1_encode = string1.encode()
int1 = 5
int1_encode = b'%d' %int1
import serial
ser = serial.Serial('/dev/ttyUSB0', 9600)
ser.write(b'3')
ser.write(b'5')
ser.write(b'7')


"""video_clip table

Revision ID: b249d75760e9
Revises: 2d1f40a09897
Create Date: 2023-08-17 11:47:59.172458

"""
from alembic import op
import sqlalchemy as sa

# revision identifiers, used by Alembic.
revision = 'b249d75760e9'
down_revision = '2d1f40a09897'
branch_labels = None
depends_on = None

def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table('video_clips',
    sa.Column('id', sa.Integer(), nullable=False),
    sa.Column('name', sa.String(length=50), nullable=True),
    sa.PrimaryKeyConstraint('id')
    )
    op.create_index(op.f('ix_video_clips_name'), 'video_clips', ['name'], unique=False)
    op.add_column('render_hosts', sa.Column('record_date', sa.DateTime(), nullable=False))
    # ### end Alembic commands ###

def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column('render_hosts', 'record_date')
    op.drop_index(op.f('ix_video_clips_name'), table_name='video_clips')
    op.drop_table('video_clips')
    # ### end Alembic commands ###


from scipy.sparse import csr_matrix
import numpy as np

from xclimf import compute_mrr

def test_compute_mrr():
  row = np.array([0,   0,   1,   1])
  col = np.array([0,   1,   0,   1])
  val = np.array([1.0, 2.0, 2.0, 1.0])
  data = csr_matrix((val, (row, col)), shape=(2, 2))

  U = np.array([
    [1.0, 2.0],
    [2.0, 1.0]
  ])
  V = np.array([
    [2.0, 1.0],
    [1.0, 2.0]
  ])

  mrr = compute_mrr(data, U, V)

  assert mrr == 1

def test_compute_mrr_when_dont_predicted_for_user():
  row = np.array([0,   0,   1])
  col = np.array([0,   1,   2])
  val = np.array([1.0, 2.0, 2.0])
  data = csr_matrix((val, (row, col)), shape=(2, 3))

  U = np.array([
    [1.0, 2.0],
    [2.0, 1.0]
  ])
  V = np.array([
    [2.0, 1.0],
    [1.0, 2.0],
    [0.0, 0.0],
  ])

  mrr = compute_mrr(data, U, V)

  assert mrr == (1.0 + 1.0/3.0)/2.0


import datetime
import uuid

from eventsourcing.domain import Aggregate, event

from goods_model import Goods

class PaymentProcessed(Aggregate.Event):
    is_paid: bool

class OrderDispatched(Aggregate.Event):
    is_dispatched: bool

class OrderDelivered(Aggregate.Event):
    is_delivered: bool

class OrderPlaced(Aggregate.Event):
    order_id: int
    buyer_id: int
    goods_id: int
    is_active: bool

class OrderCancelled(Aggregate.Event):
    order_id: int

class Order(Aggregate):
    def __init__(self):
        self.order_id = None
        self.buyer_id = None
        self.goods_id = None
        self.is_active = False
        self.is_paid = False
        self.is_dispatched = False
        self.is_delivered = False
        self.message = ''

    def get_order(self, order_id):
        if self.order_id == order_id:
            return self
        raise ValueError('not found')

    @event(OrderPlaced)
    def place_order(self, buyer_id, goods_id):
        item = Goods().find_one_item(goods_id)
        item.change_is_ordered(True)
        self.order_id = str(uuid.uuid4())
        self.buyer_id = buyer_id
        self.goods_id = goods_id
        self.is_active = True
        self.is_paid = False
        self.is_dispatched = False
        self.is_delivered = False

    @event(OrderCancelled)
    def cancel_order(self, order_id):
        if not self.is_active:
            raise ValueError("Order is not active and cannot be cancelled.")
        if self.order_id != order_id:
            raise ValueError("Order ID does not match.")
        self.is_active = False

    @event(PaymentProcessed)
    def payment_process(self):
        self.is_paid = True

    @event(OrderDelivered)
    def order_delivered(self):
        self.is_delivered = True

    @event(OrderDispatched)
    def dispatch_order(self):
        if not self.is_paid:
            raise ValueError("Cannot dispatch an order that hasn't been paid for.")
        self.is_dispatched = True


from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from database_setup import Genre, Base, Movie, User

engine = create_engine('sqlite:///Movie_Genre.db')

Base.metadata.bind = engine

DBSession = sessionmaker(bind=engine)
session = DBSession()

# Create dummy user
User1 = User(name="John Doe", email="johndoe@gmail.com",
             picture='https://nicolealvares.files.'
                     'wordpress.com/2013/10/making-of-the'
                     '-worlds-first-camera-thumb.jpg')

session.add(User1)
session.commit()

# Movie comedy
movies = Genre(user_id=1, name="Comedy")

session.add(movies)
session.commit()

Item2 = Movie(user_id=1, name="Smallfoot",
              description="Mythical Creatures, Unlikely Friendships",
              price="$9.99", ratings="PG", genre=movies)

session.add(Item2)
session.commit()

Item1 = Movie(user_id=1, name="Teen Titans Go! To the Movies",
              description="Big Break, Heroic Mission, Unlikely Heroes",
              price="$9.99", ratings="PG", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="The House with a Clock in Its Walls",
              description="Curses and Spells, Wizards and Magicians",
              price="$5.50", ratings="PG", genre=movies)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="Ferdinand",
              description="Coming Home, Non-Traditional Families",
              price="$3.99", ratings="PG", genre=movies)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="Spaceballs",
              description="Space Wars, Robots and Androids, Unlikely Heroes",
              price="$3.99", ratings="PG", genre=movies)

session.add(Item4)
session.commit()

Item5 = Movie(user_id=1, name="Space Jam",
              description="Basketball Players, Evil Aliens, Heroic Mission",
              price="$12.99", ratings="PG", genre=movies)

session.add(Item5)
session.commit()

Item6 = Movie(user_id=1, name="Shrek the Third",
              description="Fantasy Lands, Talking Animals, Crowned Heads, "
                          "Curses and Spells, Fish Out of Water, "
                          "Mythical Creatures",
              price="$.99", ratings="NR", genre=movies)

session.add(Item6)
session.commit()

Item7 = Movie(user_id=1, name="Dr. Seuss' The Grinch",
              description="Conspiracies, Crime Sprees, Metamorphosis, "
                          "Unlikely Friendships, Unlikely Heroes",
              price="$13.49", ratings="PG", genre=movies)

session.add(Item7)
session.commit()

# Action movies
movies2 = Genre(user_id=1, name="Action")

session.add(movies2)
session.commit()

Item1 = Movie(user_id=1, name="Incredibles 2",
              description="Heroic Mission, Unlikely Heroes",
              price="$19.99", ratings="PG", genre=movies2)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="Christopher Robin",
              description="Starting Over, Toys Come to Life, "
                          "Unlikely Friendships",
              price="$25", ratings="PG", genre=movies2)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="Moana",
              description="Chosen One, Curses and Spells, "
                          "Heroic Mission, Lost Worlds, "
                          "Priceless Artifacts and Prized Objects",
              price="$15", ratings="PG", genre=movies2)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="Sherlock Gnomes",
              description="Amateur Sleuths, Kidnapping, Star Detectives, "
                          "Toys Come to Life",
              price="$12", ratings="PG", genre=movies2)

session.add(Item4)
session.commit()

Item5 = Movie(user_id=1, name="Johnny English Strikes Again",
              description="Betrayal, Computer Paranoia, "
                          "Hijackings, Virtual Reality.",
              price="$14", ratings="PG", genre=movies2)

session.add(Item5)
session.commit()

Item6 = Movie(user_id=1, name="The Jungle Book ",
              description="	Monkeys, Survival in the Wilderness, "
                          "Talking Animals.",
              price="$12", ratings="PG", genre=movies2)

session.add(Item6)
session.commit()

# Thriller movies
movies = Genre(user_id=1, name="Thriller")

session.add(movies)
session.commit()

Item1 = Movie(user_id=1, name="Ocean's 8", description="Jewel Theft.",
                     price="$8.99", ratings="PG13", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="The Darkest Minds",
              description="Escape From Prison, Future Dystopias, "
                          "On the Run, Redemption.",
              price="$6.99", ratings="PG13", genre=movies)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="The Post",
              description="	Conspiracies, Fighting the System, "
                          "Office Politics, Scandals and Cover-Up",
              price="$9.95", ratings="PG13", genre=movies)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="Non-Stop",
              description="	Air Disasters, Hijackings, "
                          "Race Against Time, Trapped or Confined.",
              price="$6.99", ratings="PG13", genre=movies)

session.add(Item4)
session.commit()

# Horror Movies
movies = Genre(user_id=1, name="Horror")

session.add(movies)
session.commit()

Item1 = Movie(user_id=1, name="Halloween",
              description="Crime Sprees, Escape From Prison, "
                          "Haunted By the Pas.",
              price="$2.99", ratings="R", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="The Nun",
              description="Demonic Possession, "
                          "Members of the Clergy, Suicide",
              price="$5.99", ratings="R", genre=movies)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="Hotel Artemis",
              description="Future Dystopias, Secret Organizations",
              price="$4.50", ratings="R", genre=movies)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="Bad Times at the El Royale",
              description="Crime Sprees, Keeping a Secret",
              price="$6.95", ratings="R", genre=movies)

session.add(Item4)
session.commit()

Item5 = Movie(user_id=1, name="Red Sparrow",
              description="Femmes Fatales, Switching Sides, "
                          "Traitorous Spies",
              price="$7.95", ratings="R", genre=movies)

session.add(Item5)
session.commit()

# War movie
movies = Genre(user_id=1, name="War")

session.add(movies)
session.commit()

Item1 = Movie(user_id=1, name="Fury",
              description="	Heroic Mission, Military Life",
              price="$13.95", ratings="R", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="	Zero Dark Thirty",
              description="Assassination Plots, Heroic Mission, Terrorism",
              price="$4.95", ratings="R", genre=movies)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="Hacksaw Ridge",
              description="Great Battles, Message From God, Military Life",
              price="$6.95", ratings="R", genre=movies)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="	Dunkirk",
              description="Great Battles, Heroic Mission, "
                          "War At Sea, War in the Sky",
              price="$13.95", ratings="PG13", genre=movies)

session.add(Item4)
session.commit()

Item5 = Movie(user_id=1, name="Behind Enemy Lines",
              description="Behind Enemy Lines, Daring Rescues, "
                          "Heroic Mission",
              price="$7.95", ratings="PG13", genre=movies)

session.add(Item5)
session.commit()

# SciFi movie
movies = Genre(user_id=1, name="SciFi")

session.add(movies)
session.commit()

Item1 = Movie(user_id=1, name="The Predator",
              description="Evil Aliens",
              price="$9.95", ratings="R", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="Ready Player One",
              description="Chicken cooked in Marsala "
                          "wine sauce with mushrooms",
              price="$7.95", ratings="PG", genre=movies)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="Potstickers",
              description="Bounty Hunters, Computer Paranoia, "
                          "Future Dystopias, Priceless.",
              price="$6.50", ratings="PG13", genre=movies)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="Sorry to Bother You",
              description="Assumed Identities, Big Break, "
                          "Double Life, Schemes and Ruses",
              price="$16.75", ratings="PG13", genre=movies)

session.add(Item4)
session.commit()

# Documentary
movies = Genre(user_id=1, name="Documentary")

session.add(movies)
session.commit()

Item9 = Movie(user_id=1, name="WHAT'S THE MATTER WITH KANSAS",
              description="Down on Their Luck, Religious "
                          "Zealotry, Underdogs",
              price="$8.99", ratings="NR", genre=movies)

session.add(Item9)
session.commit()

Item1 = Movie(user_id=1, name="Woodstock",
              description="Generation Gap, Bohemian Life",
              price="$2.99", ratings="R", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="Undefeated",
              description="Documentary",
              price="$10.95", ratings="PG13", genre=movies)

session.add(Item2)
session.commit()

Item3 = Movie(user_id=1, name="Get Thrashed: The Story of Thrash Metal",
              description="Explore the blistering rise, brutal fall, and "
                          "lasting impact of thrash metal",
              price="$7.50", ratings="PG13", genre=movies)

session.add(Item3)
session.commit()

Item4 = Movie(user_id=1, name="Where Was God?",
              description="This inspirational documentary follows the "
                          "aftermath of the May 20, 2013 tornado "
                          "that devastated Moore, Oklahoma",
              price="$8.95", ratings="PG", genre=movies)

session.add(Item4)
session.commit()

session.add(Item2)
session.commit()

Item10 = Movie(user_id=1, name="MANNY",
               description="In Training, Boxers",
               price="$1.99", ratings="PG13", genre=movies)

session.add(Item10)
session.commit()

# Anime Movie
movies = Genre(user_id=1, name="Anime")

session.add(movies)
session.commit()

Item1 = Movie(user_id=1, name="Spirited Away ",
              description="Journey of Self-Discovery, Mythical Creatures, "
                          "Fantasy Lands",
              price="$5.95", ratings="PG", genre=movies)

session.add(Item1)
session.commit()

Item2 = Movie(user_id=1, name="Princess Mononoke",
              description="Heroic Mission, Mythical "
                          "Creatures, Righting the Wronged.",
              price="$17.99", ratings="PG", genre=movies)

session.add(Item2)
session.commit()

movies = Genre(user_id=1, name="Fantasy")
session.add(movies)
session.commit()

Item1 = Movie(user_id=1, name="Dog Days",
              description="Expecting a Baby, Intersecting Lives, "
                          "Looking For Love, Man's Best Friend",
              price="$5.95", ratings="PG", genre=movies)

session.add(Item1)
session.commit

Item1 = Movie(user_id=1, name="The Shape of Water",
              description="Living With Disability, "
                          "Mutants, On the Run, Workplace Romance",
              price="$16.95", ratings="R", genre=movies)

session.add(Item1)
session.commit()

Item1 = Movie(user_id=1, name="	Isle of Dogs",
              description="Conspiracies, Daring Rescues, "
                          "Future Dystopias, Talking Animals",
              price="$14.25", ratings="R", genre=movies)

session.add(Item1)
session.commit()

print("added menu items!")


import datetime
from textwrap import dedent

from vdirsyncer.storage.base import Item
from khal.controllers import get_agenda, import_ics

from .aux import _get_text
from . import aux

today = datetime.date.today()
yesterday = today - datetime.timedelta(days=1)
tomorrow = today + datetime.timedelta(days=1)

event_allday_template = u"""BEGIN:VEVENT
SEQUENCE:0
UID:uid3@host1.com
DTSTART;VALUE=DATE:{}
DTEND;VALUE=DATE:{}
SUMMARY:a meeting
DESCRIPTION:short description
LOCATION:LDB Lobby
END:VEVENT"""

event_today = event_allday_template.format(today.strftime('%Y%m%d'),
                                           tomorrow.strftime('%Y%m%d'))
item_today = Item(event_today)

class TestGetAgenda(object):
    def test_new_event(self, coll_vdirs):
        coll, vdirs = coll_vdirs
        event = coll.new_event(event_today, aux.cal1)
        coll.new(event)
        assert ['\x1b[1mToday:\x1b[0m', '\x1b[34ma meeting\x1b[0m'] == get_agenda(coll, aux.locale)

    def test_empty_recurrence(self, coll_vdirs):
        coll, vidrs = coll_vdirs
        coll.new(coll.new_event(dedent(
            'BEGIN:VEVENT\r\n'
            'UID:no_recurrences\r\n'
            'SUMMARY:No recurrences\r\n'
            'RRULE:FREQ=DAILY;COUNT=2;INTERVAL=1\r\n'
            'EXDATE:20110908T130000\r\n'
            'EXDATE:20110909T130000\r\n'
            'DTSTART:20110908T130000\r\n'
            'DTEND:20110908T170000\r\n'
            'END:VEVENT\r\n'
        ), aux.cal1))
        assert 'no events' in '\n'.join(get_agenda(
            coll, aux.locale,
            dates=[datetime.date(2011, 9, 8),
                   datetime.date(2011, 9, 9)]
        )).lower()

class TestImport(object):
    def test_import(self, coll_vdirs):
        coll, vdirs = coll_vdirs
        import_ics(coll, {'locale': aux.locale}, _get_text('event_rrule_recuid'),
                   batch=True)
        start_date = aux.BERLIN.localize(datetime.datetime(2014, 4, 30))
        end_date = aux.BERLIN.localize(datetime.datetime(2014, 9, 26))
        events = list(coll.get_localized(start_date, end_date))
        assert len(events) == 6
        events = sorted(events)
        assert events[1].start_local == aux.BERLIN.localize(datetime.datetime(2014, 7, 7, 9, 0))
        assert aux.BERLIN.localize(datetime.datetime(2014, 7, 14, 7, 0)) in \
            [ev.start for ev in events]

        import_ics(coll, {'locale': aux.locale}, _get_text('event_rrule_recuid_update'),
                   batch=True)
        events = list(coll.get_localized(start_date, end_date))
        for ev in events:
            print(ev.start)
        assert len(events) == 5
        assert aux.BERLIN.localize(datetime.datetime(2014, 7, 14, 7, 0)) not in \
            [ev.start_local for ev in events]

    def test_mix_datetime_types(self, coll_vdirs):
        """
        Test importing events with mixed tz-aware and tz-naive datetimes.
        """
        coll, vdirs = coll_vdirs
        import_ics(
            coll,
            {'locale': aux.locale},
            _get_text('event_dt_mixed_awareness'),
            batch=True
        )
        start_date = aux.BERLIN.localize(datetime.datetime(2015, 5, 29))
        end_date = aux.BERLIN.localize(datetime.datetime(2015, 6, 3))
        events = list(coll.get_localized(start_date, end_date))
        assert len(events) == 2
        events = sorted(events)
        assert events[0].start_local == \
            aux.BERLIN.localize(datetime.datetime(2015, 5, 30, 12, 0))
        assert events[0].end_local == \
            aux.BERLIN.localize(datetime.datetime(2015, 5, 30, 16, 0))
        assert events[1].start_local == \
            aux.BERLIN.localize(datetime.datetime(2015, 6, 2, 12, 0))
        assert events[1].end_local == \
            aux.BERLIN.localize(datetime.datetime(2015, 6, 2, 16, 0))


import json
import time
import random
import numpy
from typing import Union
from cv2 import absdiff
from neopixel import NeoPixels, Color
# from communication.esp_serial import esp
from webcam import Webcam, read_im, store_im
from config import storage, NUM_PIXELS, NUM_SNAP_FRAMES

np = NeoPixels(NUM_PIXELS)

class Snapper:
    """
    Snapper is used to create all images needed to measure 1 side of the tree.
    Snapper.run() is used to create a series of images.
    First each pixel in the tree is assigned a unique number. This number is translated to a binary number.
    Next for each bit the pixel is turned on in case the bit is high, turned off if the bit is low.
    When making images, first a base image is created with all leds off. The sequence of images representing the bit
    is shot after, and from each image the base image is subtracted to make maximum contrast betwwen led on and led off.
    """
    def __init__(self, series_name: str, snap_color: Union[Color, str] = 'white', num_pixels=NUM_PIXELS):
        self.series_name = series_name
        self.snap_color = snap_color
        self.num_pixels = num_pixels

        (storage / series_name).mkdir(exist_ok=True, parents=True)

    def snap_monochrome(self, name: str, color: Union[Color, str]):
        """
        Create an image in which all the pixels have the same color.
        :param name: Name of the image
        :param color: 1 Color to use for all the pixels in the tree.
        :return:
        """
        for pixel in NeoPixels.pixels:
            pixel.color = color

        # esp.write()
        NeoPixels.send_by_wifi()
        # Hardcoded sleep for now. This sleep could be replaced by some code calculating
        # and waiting the exact time it takes to render the frame.
        time.sleep(0.2)
        store_im(f'{self.series_name}/{name}', Webcam.snap())

    def snap_ground(self):
        """Snap a pixture with all leds off."""
        self.snap_monochrome('ground', 'black')

    def snap_full_on(self):
        """Snap a pixture with all leds on"""
        self.snap_monochrome('all', Color(100, 100, 100))

    def snap_leds(self):
        """Snap N pixtures. In each pixture all leds turn either on or off, depending on their "foto number".
        The foto number is a "random" number each led is assigned.
        With the N pixures, each led will flash a binary pattern.
        This pattern is the binary representation of the foto number of the led.
        :return:
        """
        for n in range(NUM_SNAP_FRAMES):
            for pixel in NeoPixels.pixels:
                if pixel.bit_n_set(n):
                    pixel.color = self.snap_color
                else:
                    pixel.color = 'black'
            # esp.write()
            NeoPixels.send_by_wifi()
            NeoPixels.send_by_wifi()
            time.sleep(0.2)
            store_im(f'{self.series_name}/{n}', Webcam.snap())

    def make_example(self):
        """
        Create an example image showing all snapped foto's in 1 images
        :return:
        """
        ground = read_im(f'{self.series_name}/ground')
        images = [absdiff(read_im(f'{self.series_name}/{n}'), ground) for n in range(8)]

        store_im(f'{self.series_name}/example', numpy.concatenate(
            (numpy.concatenate((images[0], images[1], images[2], images[3]), axis=1),
             numpy.concatenate((images[4], images[5], images[6], images[7]), axis=1)), axis=0))

    def assign_pixel_id(self):
        """
        Assign each pixel a unique random number
        """
        pixel = 0
        id_dict = {}
        for num in random.sample(range(16, 2**NUM_SNAP_FRAMES), self.num_pixels):
            NeoPixels.pixels[pixel].foto_number = num
            id_dict[num] = pixel
            pixel += 1

        with (storage / self.series_name / 'numbers.txt').open('w') as number_file:
            number_file.write(json.dumps(id_dict))

    def run(self):
        """
        Run the sequence needed to measure 1 side of the tree
        """
        self.assign_pixel_id()
        self.snap_ground()
        self.snap_full_on()
        self.snap_leds()
        self.make_example()

if __name__ == "__main__":
    Snapper('slaapkamer5').run()