# pip3 install -U beautifulsoup4
# pip3 install -U requests

import bs4
import requests

login_info = {
    'id': '',  # 달빛학사 id 입력
    'passwd': ''  # 달빛학사 비밀번호 입력
}

while True:
    song_good_list = list()  # 노래의 좋아요 숫자를 저장하는 리스트
    song_name_list = list()  # 노래의 이름을 저장하는 리스트
    song_is_good_list = list()  # 노래의 좋아요/취소 상태를 저장하는 리스트
    with requests.Session() as s:  # 로그인하는 Session
        first_page = s.get('https://go.sasa.hs.kr/')
        html_login = first_page.text
        soup_login = bs4.BeautifulSoup(html_login, 'html.parser')
        csrf = soup_login.find('input', {'name': 'csrf_test_name'})
        login_info.update({'csrf_test_name': csrf['value']})
        login_req = s.post(
            'https://go.sasa.hs.kr/auth/login/', data=login_info)
        if login_req.status_code != 200:
            raise Exception('로그인이 되지 않았습니다')

        # 달빛학사 음악추천 사이트의 정보를 가져와 BeautifulSoup로 가공
        song = bs4.BeautifulSoup(
            s.get('https://go.sasa.hs.kr/RcmndMusic/musicView').text, 'html.parser')
        song_good = song.select('td.text-center.text-info')  # 노래의 좋아요 숫자를 추출
        for i in song_good:
            # 각 노래의 좋아요 숫자를 현재 순위별로 저장
            song_good_list.append(i.getText().strip())
        song_name = song.select('td')  # td 를 모두 추출
        song_is_good = song.select(
            'td.text-center button')  # 노래의 좋아요/취소 상태를 추출
        for i in song_is_good:  # 각 노래의 좋아요/취소 상태를 현재 순위별로 저장
            song_is_good_list.append(i.getText().strip())
        cnt = -1
        while True:
            cnt += 1
            try:
                for i in song_name[2 + 6 * cnt]:  # td 안에서 노래 이름이 있는 위치에서 노래이름을 추출
                    song_name_list.append(
                        song_name[2 + 6 * cnt].getText().strip())
            except IndexError:
                break
        for i in range(len(song_good_list)):  # 노래이름을 현재 순위대로 저장
            print('{}'.format(i + 1), end=' ')
            print(song_is_good_list[i], song_good_list[i],
                  ': ', song_name_list[i * 3])
        button_info = song.select('button')  # 좋아요/취소와 관련된 정보 추출
        button_like_id = ['0']  # 좋아요를 누를 때 사용할 id 리스트
        button_unlike_id = ['0']  # 취소를 누를 때 사용할 id 리스트
        for i in button_info:
            t = i.get('id')  # 각 노래의 id 추출
            if t == None:  # 맨 첫번째줄 정보 버림
                continue
            # 좋아요의 경우 앞의 4글자를 버림
            button_like_id.append(t[4] + t[5] + t[6] + t[7])
            if t[0] == 'l':
                button_unlike_id.append(t[0])  # indexerror 방지
            elif t[0] == 'u':
                button_unlike_id.append(
                    t[6] + t[7] + t[8] + t[9])  # 싫어요의 경우 앞의 6글자를 버림
        print('좋아요를 누르시려면 1, 취소를 하시려면 2를 눌러주세요')
        print('그 외를 입력하시면 프로그램이 종료됩니다')
        check = input()  # 사용자의 행동 판정
        if check == '1':  # 좋아요를 누르는 경우
            print('좋아요를 누를 노래를 선택하세요')
            print('ex) 3')
            print('"exit"을 입력하면 프로그램이 종료됩니다')
            like = input()
            if like == 'exit':  # 탈출조건
                print('프로그램이 종료됩니다')
                break
            try:  # 입력오류 방지
                like = int(like)
            except ValueError:
                print('잘못된 입력입니다 다시 입력해주세요')
                continue
            if song_is_good_list[like - 1] == '취소':  # 이미 좋아요가 눌려있는 경우
                print('이미 좋아요 상태인 노래입니다')
                continue
            try:  # 좋아요를 누르는 Session
                first_page_1 = s.get(
                    'https://go.sasa.hs.kr/RcmndMusic/musicView')
                html = first_page_1.text
                soup = bs4.BeautifulSoup(html, 'html.parser')
                dict_1 = {'musicIdx': button_like_id[like], 'action': 'insert'}
                like_req = s.post(
                    'https://go.sasa.hs.kr/RcmndMusic/likeControl', data=dict_1)
                print('성공적으로 좋아요가 입력되었습니다')
            except IndexError:  # 잘못된 입력
                print('노래번호를 다시 확인해주세요')
                continue
        elif check == '2':  # 취소의 경우
            print('취소를 누를 노래를 선택하세요')
            print('ex) 3')
            print('"exit"을 입력하면 프로그램이 종료됩니다')
            unlike = input()
            if unlike == 'exit':  # 탈출조건
                print('프로그램이 종료됩니다')
                break
            try:  # 입력오류 방지
                unlike = int(unlike)
            except ValueError:
                print('잘못된 입력입니다 다시 입력해주세요')
                continue
            if song_is_good_list[unlike - 1] == '좋아요':
                print('이미 취소상태인 노래입니다')
                continue
            try:  # 취소를 누르는 Session
                first_page_1 = s.get(
                    'https://go.sasa.hs.kr/RcmndMusic/musicView')
                html = first_page_1.text
                soup = bs4.BeautifulSoup(html, 'html.parser')
                dict_1 = {
                    'musicIdx': button_unlike_id[unlike], 'action': 'delete'}
                unlike_req = s.post(
                    'https://go.sasa.hs.kr/RcmndMusic/likeControl', data=dict_1)
                print('성공적으로 취소가 입력되었습니다')
            except IndexError:  # 잘못된 입력
                print('노래번호를 다시 확인해주세요')
                continue
        else:  # 종료조건
            print('프로그램이 종료됩니다')
            break


from pytube import YouTube

import os
import shutil
import math
import datetime
import pickle
import sys

import matplotlib.pyplot as plt

import cv2

url = input("Video URL: ")
video = YouTube(f"{url}")

count = 0
vids = video.streams.filter(progressive=True, file_extension="mp4")
for vid in vids:
    count += 1
    print(f"\n{count}:\n {vid}\n\n")

res_choice = f"{count + 1}"
try:
    while(int(res_choice) > count):
        res_choice = input(f"Enter stream number (1 to {count})")
    video = vids[int(res_choice) - 1]
except ValueError:
    print("ERROR - cannot convert to int")
    sys.exit()

cwd = os.getcwd()
file_name = "downloadpath.txt"
if not os.path.exists(f"{cwd}\{file_name}"):
    downloadpath = input("Enter the exact path of the folder you would like to download this videos and all future videos into: ")
    with open(file_name, "w") as f:
        f.write(downloadpath)
else:
    with open(file_name, "r") as f:
        downloadpath = f.read()

video.download(downloadpath)
print(f"Video succesfully saved in {downloadpath}")


import json

class JsonExtractor:
    # def __init__(self):
    def extract(self, file_path: str) -> list:
        with open(file_path) as f:
            lines = f.readlines()
        f.close()
        datas = []
        for i in lines:
            datas.append(json.loads(i))
        return datas


from .utils import declare_component

_component_func = declare_component("radio_group")

def radio_group(options, default_value=None, key=None):
    props = {
        "defaultValue": default_value,
        "options": options
    }
    component_value = _component_func(comp="radio_group", props=props, key=key, default=default_value)
    return component_value


from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from boc import views
from django.views.generic import TemplateView
from django.contrib.flatpages import views
from django.contrib.sitemaps.views import sitemap
from boc.sitemap import *
from django.contrib.sitemaps import FlatPageSitemap
from django.conf.urls.i18n import i18n_patterns

sitemaps = {
            'main': MainSitemap,
            'odds_main':OddsMainSitemap,
            'flatpages': FlatPageSitemap,
            'event_odds' : EventOddsSitemap,
            'event_fights' : EventFightsSitemap,
            'articles' : ArticleSitemap,
            'bookmakers' : BookmakerSitemap,
            'predictions' : PredictionSitemap
             }

urlpatterns = [
    url(r'^sitemap\.xml$', sitemap, name='sitemap-xml'),
]

urlpatterns += i18n_patterns('',
    # Examples:
    # url(r'^$', 'boc.views.home', name='home'),
    # url(r'^blog/', include('blog.urls')),

    url(r'^$', 'boc.views.home', name='home'),
    url(r'^login/', 'boc.views.login', name='login'),
    #url(r'^settings/', 'boc.views.settings', name='settings'),
    #url(r'^loginvalidation/', views.loginvalidation, name='loginvalidation'),

    url(r'^join/', 'boc.views.join', name='join'),

    #url(r'^joinvalidation/', views.joinvalidation, name='joinvalidation'),
    url(r'^logout/', 'boc.views.logout', name='logout'),
    #url(r'^joinlogin/', views.joinlogin, name='joinlogin'),

    url(r'^admin/', include(admin.site.urls)),
    url(r'^odds/', include('odds.urls', namespace='odds')),
    url(r'^news/', include('news.urls', namespace='news')),
    url(r'^events/', include('mma.urls', namespace='mma')),
    url(r'^investing/', include('investor.urls', namespace='investor')),
    url(r'^predictions/', include('predictions.urls', namespace='predictions')),
    url(r'^consulting/', include('consulting.urls', namespace='consulting')),
    url(r'^bookmakers/', include('bookmakers.urls', namespace='bookmakers')),
    url(r'^testimonials/', include('testimonial.urls', namespace='testimonial')),
    url(r'^statistics/', include('statistics.urls', namespace='statistics')),
    url(r'^fantasy-mma/', include('fantasy.urls', namespace='fantasy')),
    url(r'^userprofile/', include('userprofile.urls', namespace='userprofile')),
    url(r'^login/', 'boc.views.login', name='login'),

#    (r'^ckeditor/', include('ckeditor.urls')),
    (r'^ckeditor/', include('ckeditor_uploader.urls')),

    url(r'^about/$', 'boc.views.about', name='about'),
    url(r'^about/the-fund/$', 'boc.views.about_fund', name='about_fund'),
    url(r'^about/advisory-board/$', 'boc.views.advisory_board', name='advisory_board'),

    url(r'^contact/$', 'boc.views.contact', name='contact'),
#    url(r'^career/$', views.flatpage, {'url': '/career/'}, name='career'),
    url(r'^career/$', 'boc.views.career', name='career'),
    url(r'^betting-guide/$', 'boc.views.betting_guide', name='betting_guide'),
    url(r'^learn/$', 'boc.views.learn', name='learn'),
    url(r'^learn/become-a-student/$', 'boc.views.become_a_student', name='become_a_student'),
    url(r'^learn/roi/$', 'boc.views.roi', name='roi'),
    url(r'^refer/$', views.flatpage, {'url': '/refer/'}, name='refer'),
    url(r'^affiliates/$', views.flatpage, {'url': '/affiliates/'}, name='affiliates'),
    url(r'^legal/$', 'boc.views.legal', name='legal'),
    url(r'^accounts/', include('allauth.urls')),
    url(r'^refer/$', views.flatpage, {'url': '/refer/'}, name='refer'),

    (r'^comments/', include('django_comments.urls')),

    url(r"^payments/", include("payments.urls")),
    url(r'^sitemap\.xml$', sitemap, {'sitemaps': sitemaps},name='django.contrib.sitemaps.views.sitemap'),
    url(r'^chat/', include('chat.urls', namespace="chat")),
) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)


from database import insert_text, create_db
from regexes import REGEX_DETECTION_LIST
from pydantic import BaseModel
from fastapi import FastAPI
from shutil import copyfile
from pathlib import Path
from PIL import Image
import numpy as np
import uvicorn
import easyocr
import sqlite3
import sys
import os
import re

class Message(BaseModel):
    msg: str

def create_folder(path):
    Path(os.path.join(path)).mkdir(parents=True, exist_ok=True)

def resized_img(img_path):
    with Image.open(img_path) as im:
        im = im.convert('RGB')
        max_size = 1000, 1000
        # Resizes image
        im.thumbnail(max_size, Image.ANTIALIAS)
        return im.copy()

def detect_text(img_path):
    img = np.asarray(resized_img(img_path))
    result = reader.readtext(img)
    detected_text = ""
    # Get detected text separated by newline
    for res in result:
        text = res[1]
        detected_text += text + '\n'
    return detected_text

def copy_img(img_source_path, img_destination_path):
    # Check if the source exists so it can copy over
    if os.path.exists(img_source_path):
        # If exists, then check if it wasn't already copied to destination folder.
        if not os.path.exists(img_destination_path):
            copyfile(img_source_path, img_destination_path)

def classify_text(img_name, img_text):
    # Runs some regexes to classify possible important texts in the image detected text.
    # All detections types and regexes are withing regexes.py

    # Keep track of all detections for that image:
    img_detections = []

    for detection, regexes in REGEX_DETECTION_LIST.items():
        # Create a folder for that category of detections (ex: emails, crypto stuff, etc..)
        detection_path = os.path.join(FILES_PATH, 'detections', detection)
        create_folder(detection_path)
        for regex in regexes:
            re_exp = re.compile(regex, re.IGNORECASE)
            if re_exp.search(img_text):
                img_source_path = os.path.join(IMAGES_PATH, img_name)
                img_destination_path = os.path.join(detection_path, img_name)
                # Create a copy of the image in the DETECTIONS folder.
                copy_img(img_source_path, img_destination_path)
                # Add detection to img_detection list
                img_detections.append(detection)
                # Breaks out of inner loop, because already found that image to be of certain DETECTION type
                break

    if len(img_detections) >= 1:
        print(f'>> Found image {img_name} to contain text about: {img_detections}')

app = FastAPI()

@app.get('/ready')
def api_ready():
    return {'Connected': 'API is online'}

@app.post('/')
async def imgur_scraper(msg: Message):
    # Get image name
    img_name = msg.msg

    HERE = os.path.dirname(sys.argv[0])

    # Image/File info
    img_identifier = img_name.split('.')[0]
    img_extension = img_name.split('.')[1]
    img_path = os.path.join(HERE, '..', 'imgs', img_name)

    # Conect do Database
    try:
        conn = sqlite3.connect(os.path.join(HERE, '..', 'files', 'detected_text.db'))
        c = conn.cursor()
    except Exception as e:
        print('Error coneccting to DB', e)
        return {'msg': 'Error coneccting to DB'}

    try:
        # Detects text, reader object started in main execution
        print(f'Detecting text on: {img_identifier}')
        text = detect_text(img_path)
        print(f'Detected text on: {img_identifier}')
    except Exception as e:
        print('Error recognizing image', e)
        return {'msg': 'Error recognizing image'}

    # Add text to DB
    insert_text(conn, c, img_identifier, img_extension, text)

    # Closes db connection
    conn.close()

    # Try to detect important info on text
    classify_text(img_name, text)

    return {'msg': 'OK'}

HERE = os.path.dirname(sys.argv[0])
# Confirms that the output folder exists
create_folder(os.path.join(HERE, '..', 'files'))
FILES_PATH = os.path.join(HERE, '..', 'files')
IMAGES_PATH = os.path.join(HERE, '..', 'imgs')
### Create the parent folder where the detected images will be
create_folder(os.path.join(FILES_PATH, 'detections'))

print('Starting database...')
create_db()
print('Starting OCR reader...')
reader = easyocr.Reader(['en'], gpu=False)

if __name__ == '__main__':
    print('Running server...')
    uvicorn.run(app, host="0.0.0.0", port=8001)

    # POST with cURL
    # curl -d '{"msg":"test_msg"}' -X POST localhost:8001
    # curl -d '{"msg":"El2dyRE.png"}' -X POST localhost:8001


"SCRIPT PRINCIPAL PARA CRIAÇÃO DA TABELA Q"
import os
import numpy as np
from colordict import ColorDict
cor = ColorDict()
#Definindo entidades do mapa
parede   = 1
livre    = 0
objetivo = 2
parado   = 9
#Motivos de interface com usuario
AcaoR = {
        1 : "Direita",
        0 : "Frente" ,
        3 : "Esquerda",
        2 : "Atrás",
        4 : "Parado"
        }
#------------------------------------------------------------------------------
class ambiente:
     def __init__(self,path : str):
        self.mapa    = np.load("./Mapas/"+path)
        self.linhas  = int(len(self.mapa))
        self.ultimo    = (0,0,0)            #(linha,coluna,entidade)
        self.colunas = int(len(self.mapa[:]))
        self.recompensa = { livre:   -1, parede:  -100, objetivo: 100,
                            parado:   -1  }
        self.cores      = { livre:    cor['white'], parede: cor['red'],
                            objetivo: cor['green'], parado: cor['white']}
     def att_mapa(self,linha,coluna,entidade):
        self.ultimo = (linha,coluna,entidade)
        self.mapa[linha][coluna] = entidade
#------------------------------------------------------------------------------
class agente:
    #Função que determina a posição inicial e o objetivo do agente
    def Fambient(self, mapa : list, linhas :int , colunas: int) -> [int,int] :
        inicio = fim = (0,0)
        for linha in range(0,linhas):
            for coluna in range(0,colunas):
                if mapa[linha][coluna] == objetivo: fim    = (linha,coluna)
                if mapa[linha][coluna] == parado:   inicio = (linha,coluna)
        return inicio,fim

    #Função que reinicia as variaveis para condição inicial
    def restart(self) -> None:
        self.Nestado = self.estado = self.inicio
        self.condicoes["impacto"]  = False
        self.condicoes["objetivo"] = False
        self.timeon                = 0

    #Função que decai o epsilon:
    def decair(self) -> None:
        epsilon = self.hp["epsilon"]
        epsilon[0] = epsilon[0] - epsilon[2]
    #Função que verifica o estado no mapa
    def verificar(self,ambiente : object ) -> None:
        Maxrow = ambiente.linhas
        Maxcol = ambiente.colunas
        if self.Nestado[0] > Maxrow-1 or self.Nestado[0] < 0:
            self.condicoes["impacto"] = True
            return
        if self.Nestado[1] > Maxcol-1 or self.Nestado[1] < 0:
            self.condicoes["impacto"] = True
            return
        if ambiente.mapa[self.Nestado[0]][self.Nestado[1]] == parede:
            self.condicoes["impacto"] = True
        if ambiente.mapa[self.estado[0]][self.estado[1]] == objetivo:
            self.condicoes["objetivo"] = True

    #Função que atualiza o estado
    def att_Estado(self , e :tuple):
        meta = list(e)
        for i in range(0,2):
         meta[i] = meta[i] + self.acoes[self.acao][i]
        return tuple(meta)

    #Função que atualiza a tabela Q
    def att_TabelaQ(self,ambiente : object) -> None :
        Q,s,a = self.TabelaQ,self.estado,self.acao
        alpha = self.hp["alpha"]
        gamma = self.hp["gamma"]
        ns =    self.Nestado
        check = False
        if ns[0] > ambiente.linhas - 1 or ns[1] > ambiente.colunas - 1:
            R = ambiente.recompensa[parede]
            check = True
        if ns[0] < 0 or ns[1] < 0 :
            check = True
            R = ambiente.recompensa[parede]
        if check == False:
            R = ambiente.recompensa[ambiente.mapa[ns]]

        if self.condicoes["impacto"] == True or self.condicoes["objetivo"] == True:
            Q [ s ][ a ] = (1 - alpha ) * Q [ s ][ a ] + alpha * R
        else:
            Q [ s ][ a ] = (1-alpha)*Q[s][a] + alpha*(R+gamma*np.max( Q[ns]))

    #Politica do agente
    def politica(self) -> None:
        #Exploitation
        if np.random.uniform(0,1) > self.hp["epsilon"][0]:
            self.acao  = np.argmax(self.TabelaQ[self.estado])
        #Exploration
        else:
           self.acao = np.math.ceil(np.random.randint(0,self.Nacoes))

    def __init__(self,ambiente : object,alpha : float ,gamma : float):
        self.acoes = [
        [1, 0],     #Frente
        [0, 1],     #Direita
        [-1,0],     #Atrás
        [0,-1],     #Esquerda
        [0, 0]      #parado
        ]
        self.Nacoes = len(self.acoes)
        self.episodios = (ambiente.linhas * ambiente.colunas*10)*self.Nacoes
        self.hp = {      #hiperparametros
        "alpha": alpha,  #Taxa de aprendizagem
        "gamma": gamma,  #Taxa de desconto
        "epsilon": [1,(1/self.episodios),(1/self.episodios)] #inicio,min,decay
        }
        self.inicio,self.objetivo = self.Fambient(ambiente.mapa,ambiente.linhas,ambiente.colunas)
        ambiente.att_mapa(self.inicio[0],self.inicio[1],0)
        self.estado = self.inicio
        self.timeon = 0
        self.Nestado = self.inicio
        self.TabelaQ = np.zeros((ambiente.linhas,ambiente.colunas,self.Nacoes),float)
        self.condicoes = {
            "impacto" : False,
            "objetivo": False
            }
        self.acao = 4
#------------------------------------------------------------------------------
#inicializando as variaveis
ARQUIVO = input("Nome do mapa: ")
ambiente = ambiente(ARQUIVO+".npy")
agente   = agente(ambiente,0.95,0.95)

#Treinamento
RUN = True
for episodio in range(1,agente.episodios+1):
    while(RUN == True):
        agente.politica()
        #print(AcaoR[agente.acao])
        #Atualizando o Novo estado
        agente.Nestado = agente.att_Estado(agente.Nestado)
        agente.verificar(ambiente)
        agente.att_TabelaQ(ambiente)
        if agente.condicoes["impacto"] == True:
            agente.restart()
            break
        if agente.condicoes["objetivo"] == True:
            agente.restart()
            break
        agente.estado = agente.Nestado
    agente.decair()

#Solução para o erro de estruturaão do TabelaQ
agente.TabelaQ[agente.objetivo[0]][agente.objetivo[1]][4] = ambiente.recompensa[objetivo]

print("TREINAMENTO CONCLUIDO, SALVAR TABELA Q?")
opt = input(" Y - sim / N - nao : ").upper()
if opt  == "Y":
    try:
        os.mkdir("./TabelasQ")
    except:
        pass
    np.save("./TabelasQ/" + ARQUIVO +"Qtable.npy",agente.TabelaQ)
    print("ARQUIVO SALVO")


from dotenv import load_dotenv
import os
from ..middlewares import mailjet

load_dotenv()
SENDER_EMAIL = os.environ.get("SENDER_EMAIL")

async def send_email(message_data: dict):
    data = {
        "Messages": [
            {
                "From": {
                    "Email": SENDER_EMAIL,
                    "Name": "No Budget Shop"
                },
                "To": message_data["to"],
                "Subject": message_data["subject"],
                "TextPart": message_data["TextPart"],
                "HTMLPart": message_data["HTMLPart"]
            }
        ]
    }

    result = mailjet.client.send.create(data=data)
    return result.status_code


import os
import ffmpeg
from tqdm import tqdm
from os import listdir, path
import argparse

def convert_avi_to_mp4(avi_file_path, output_name):
    os.popen("ffmpeg -i '{input}' -ac 2 -b:v 2000k -c:a aac -c:v libx264 -b:a 160k -vprofile high -bf 0 -strict "
             "experimental -f mp4 '{output}.mp4'".format(input = avi_file_path, output = output_name))
    return True

if __name__ == '__main__':
    ap = argparse.ArgumentParser()
    ap.add_argument("-i", "--input", required=True, help="path to input episode files for encodings")
    ap.add_argument("-o", "--output", required=True, help="path to output episode files for encodings")
    args = vars(ap.parse_args())

    vid_dir_path = args['input']
    vid_out_dir_path = args['output']

    for video_path in tqdm(listdir(vid_dir_path)):
        if video_path.lower().endswith('.avi'):
            convert_avi_to_mp4(os.path.join(vid_dir_path, video_path),
                               os.path.join(vid_dir_path, video_path[:-4]))

    for video_path in tqdm(listdir(vid_dir_path)):
        if not video_path.startswith('episode'):
            print('skip ' + video_path)
            continue

        if video_path.endswith('.mp4'):
            vidstream = ffmpeg.input(path.join(vid_dir_path, video_path)).video
            ffmpeg.output(vidstream, path.join(vid_out_dir_path, video_path), vcodec='libx265', crf=30).overwrite_output().run()


from typing import Dict, List

from sbmlsim.data import DataSet, load_pkdb_dataframes_by_substance
from sbmlsim.fit import FitData, FitMapping
from sbmlsim.plot import Axis, Figure
from sbmlsim.simulation import Timecourse, TimecourseSim

from . import MidazolamSimulationExperiment

class Mandema1992(MidazolamSimulationExperiment):
    """Mandema1992."""

    def datasets(self) -> Dict[str, DataSet]:
        dsets = {}
        for fig_id in ["Fig1A", "Fig2A", "Fig3A"]:
            dframes = load_pkdb_dataframes_by_substance(
                f"{self.sid}_{fig_id}", data_path=self.data_path
            )

            for substance, df in dframes.items():
                dset = DataSet.from_df(df, self.ureg)
                if substance == "midazolam":
                    dset.unit_conversion("mean", 1 / self.Mr.mid)
                elif substance == "1-hydroxymidazolam":
                    dset.unit_conversion("mean", 1 / self.Mr.mid1oh)
                else:
                    raise ValueError

                dsets[f"{fig_id}_{substance}"] = dset
        return dsets

    def simulations(self) -> Dict[str, TimecourseSim]:
        return {**self.simulation_mid()}

    def simulation_mid(self) -> Dict[str, TimecourseSim]:
        """Mandema1992

        - midazolam, iv, 0.1 [mg/kg] (infusion over 15 min)
        - 1-hydroxy midazolam, iv, 0.15 [mg/kg] (infusion over 15 min)
        - midazolam, po, 7.5 [mg]
        """
        Q_ = self.Q_

        bodyweight = Q_(69, "kg")  # avg. bodyweight of 8 individuals (sd=6kg)
        # mid_iv = Q_(0.1, 'mg/kg') * bodyweight
        mid_Ri = Q_(0.1, "mg/kg") * bodyweight / Q_(15, "min")

        # if injected in 1 min use the IVDOSE_mid1oh parameter
        # mid1oh_iv = Q_(0.15, 'mg/kg') * bodyweight
        # but infused in 15 [min]
        mid1oh_Ri = Q_(0.15, "mg/kg") * bodyweight / Q_(15, "min")

        mid_po = Q_(7.5, "mg")

        tcsims = {}
        tcsims["mid_iv"] = TimecourseSim(
            [
                Timecourse(
                    start=0,
                    end=15,
                    steps=600,
                    changes={
                        "BW": bodyweight,
                        "Ri_mid": mid_Ri,
                    },
                ),
                Timecourse(
                    start=0,
                    end=300,
                    steps=600,
                    changes={
                        "Ri_mid": Q_(0, "mg_per_min"),
                    },
                ),
            ]
        )
        tcsims["mid1oh_iv"] = TimecourseSim(
            [
                Timecourse(
                    start=0,
                    end=15,
                    steps=100,
                    changes={
                        "BW": bodyweight,
                        # 'IVDOSE_mid1oh': mid1oh_iv
                        "Ri_mid1oh": mid1oh_Ri,
                    },
                ),
                Timecourse(
                    start=0,
                    end=300,
                    steps=600,
                    changes={
                        "Ri_mid1oh": Q_(0, "mg_per_min"),
                    },
                ),
            ]
        )
        tcsims["mid_po"] = TimecourseSim(
            [
                Timecourse(
                    start=0,
                    end=315,
                    steps=700,
                    changes={
                        "BW": bodyweight,
                        "PODOSE_mid": mid_po,
                    },
                )
            ]
        )

        return tcsims

    def fit_mappings(self) -> Dict[str, FitMapping]:
        # fit mapping: which data maps on which simulation
        fit_dict = {
            "fm1": {"ref": "Fig1A_midazolam", "obs": "task_mid_iv", "yid": "[Cve_mid]"},
            "fm2": {"ref": "Fig3A_midazolam", "obs": "task_mid_po", "yid": "[Cve_mid]"},
            "fm3": {
                "ref": "Fig1A_1-hydroxymidazolam",
                "obs": "task_mid_iv",
                "yid": "[Cve_mid1oh]",
            },
            "fm4": {
                "ref": "Fig2A_1-hydroxymidazolam",
                "obs": "task_mid1oh_iv",
                "yid": "[Cve_mid1oh]",
            },
            "fm5": {
                "ref": "Fig3A_1-hydroxymidazolam",
                "obs": "task_mid_po",
                "yid": "[Cve_mid1oh]",
            },
        }

        mappings = {}
        for key, values in fit_dict.items():
            mappings[key] = FitMapping(
                self,
                reference=FitData(
                    self,
                    dataset=values["ref"],
                    xid="time",
                    yid="mean",
                    yid_sd="mean_sd",
                ),
                observable=FitData(
                    self, task=values["obs"], xid="time", yid=values["yid"]
                ),
            )
        return mappings

    def figures(self) -> Dict[str, Figure]:
        return {
            **self.figure_mid(),
        }

    def figure_mid(self):
        unit_time = "min"
        unit_mid = "nmol/ml"
        unit_mid1oh = "nmol/ml"

        self.add_selections_data(selections=["time", "[Cve_mid]", "[Cve_mid1oh]"])

        fig = Figure(self, sid="Fig1", num_rows=2, num_cols=3, name=self.sid)
        plots = fig.create_plots(Axis("time", unit=unit_time), legend=True)

        # simulation
        plots[0].set_title("midazolam iv, 0.1 [mg/kg]")
        plots[1].set_title("1-hydroxymidazolam iv, 0.15 [mg/kg]")
        plots[2].set_title("midazolam po, 7.5 [mg]")
        for k in (0, 1, 2):
            plots[k].set_yaxis("midazolam", unit_mid)
            plots[k].xaxis.label_visible = False
        for k in (3, 4, 5):
            plots[k].set_yaxis("1-hydroxymidazolam", unit_mid1oh)

        plot_dict = {
            "mid_iv": (plots[0], plots[3]),
            "mid1oh_iv": (plots[1], plots[4]),
            "mid_po": (plots[2], plots[5]),
        }

        for key, plot in plot_dict.items():
            # plot midazolam
            p = plot[0]
            p.add_data(
                task=f"task_{key}",
                xid="time",
                yid="[Cve_mid]",
                label="mid (ve blood)",
                color="black",
            )
            # plot 1-hydroxymidazolam
            p = plot[1]
            p.add_data(
                task=f"task_{key}",
                xid="time",
                yid="[Cve_mid1oh]",
                label="mid1oh (ve blood)",
                color="black",
            )

        # plot data
        data_def = {
            "Fig1A_midazolam": {"plot": plots[0], "key": "mid"},
            "Fig1A_1-hydroxymidazolam": {"plot": plots[3], "key": "mid1oh"},
            "Fig2A_1-hydroxymidazolam": {"plot": plots[4], "key": "mid1oh"},
            "Fig3A_midazolam": {"plot": plots[2], "key": "mid"},
            "Fig3A_1-hydroxymidazolam": {"plot": plots[5], "key": "mid1oh"},
        }
        for dset_key, dset_info in data_def.items():
            p = dset_info["plot"]
            p.add_data(
                dataset=dset_key,
                xid="time",
                yid="mean",
                yid_sd="mean_sd",
                count=None,
                color="black",
                label=dset_info["key"],
            )

        return {"fig1": fig}


class BluewError(Exception):
    """For those times when the Engine blows."""

    DEVICE_NOT_AVAILABLE = 'Bluew can not find bluetooth device.'
    NO_CONTROLLERS = 'Bluew could not find any bluetooth controllers.'
    CONTROLLER_NOT_AVAILABLE = 'Bluew could not find the controller specified.'
    CONTROLLER_NOT_READY = 'The controller might be blocked/disabled.'
    COULD_NOT_PAIR = 'Bluew could not pair with device.'
    READ_WRITE_FAILED = 'Bluew could not read/write this attribute.'
    INVALID_ARGS = 'Invalid args.'
    UNEXPECTED_ERROR = 'An unexpected error happened.'

    def __init__(self, reason, long_reason='', name='', version=''):
        super().__init__()
        self.engine_name = name
        self.version = version
        self.reason = reason
        self.long_reason = long_reason

    def __str__(self):
        msg_tail = ' using: ' + self.engine_name + ' ver: ' + self.version
        if self.long_reason:
            msg = self.long_reason + msg_tail
            return msg
        msg = self.reason + msg_tail
        return msg

class DeviceNotAvailable(BluewError):
    """
    This error is raised by when the bluetooth devices specified
    is not found.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.DEVICE_NOT_AVAILABLE, *args, **kwargs)

class PairError(BluewError):
    """
    This error is raised when a pairing error that can't be handled is raised.
    """

    AUTHENTICATION_ERROR = 'Failed to authenticate'

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.COULD_NOT_PAIR, *args, **kwargs)

class ReadWriteNotifyError(BluewError):
    """
    This error is raised when a read/write fail that can't be handled happens.
    """

    NOT_PERMITTED = '''Action not allowed on this attribute.'''
    NOT_SUPPORTED = '''This attribute doesn't support this action'''
    NOT_AUTHORIZED = '''Client not trusted by device.'''
    IN_PROGRESS = '''There's already a read/write op on this attribute.'''

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.READ_WRITE_FAILED, *args, **kwargs)

class InvalidArgumentsError(BluewError):
    """
    This error is rasied when arguemnts passed are.. well.. invalid.
    """

    INVALID_LEN = '''Length of passed argument isn't valid.'''
    INVALID_ARGS = '''The arguments passed are incorrect.'''

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.INVALID_ARGS, *args, **kwargs)

class NoControllerAvailable(BluewError):
    """
    This error is raised when no bluetooth controllers
    are found.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.NO_CONTROLLERS, *args, **kwargs)

class ControllerSpecifiedNotFound(BluewError):
    """
    This error is raised when the controller specified
    is not found by bluew.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.CONTROLLER_NOT_AVAILABLE, *args, **kwargs)

class ControllerNotReady(BluewError):
    """
    This error is raised when the controller specified or picked by default
    is not ready.
    """

    def __init__(self, *args, **kwargs):
        super().__init__(BluewError.CONTROLLER_NOT_READY, *args, **kwargs)


# -*- coding: utf-8 -*-
"""
Created on Mon Aug 27 12:34:51 2018

@author: jdshin
"""

#Specify tetrode metadata - remember 0 indexing
#JS15 metadata
ca1tet = [4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 20, 21, 22, 23, 24, 25]
pfctet =[0, 1, 2, 3, 14, 15, 16, 17, 18, 19, 27, 28, 29, 30, 31]

#riptet = []

#Split into separate dics
ca1_spikes = []
for tet in ca1tet:
    for t, cell in enumerate(spikes):
        if cell['Tetrode'] == tet:
            cell['Area'] = 'CA1'
            if cell['meanrate'] > 7:
                cell['tag'] = 'CA1int'
                ca1_spikes.append({})
                ca1_spikes[-1] = cell
                spikes[t]['tag'] = 'CA1int'
                spikes[t]['Area'] = 'CA1'
            else:
                cell['tag'] = 'CA1pyr'
                ca1_spikes.append({})
                ca1_spikes[-1] = cell
                spikes[t]['tag'] = 'CA1pyr'
                spikes[t]['Area'] = 'CA1'
        else:
            continue

pfc_spikes = []
for tet2 in pfctet:
    for t, cell in enumerate(spikes):
        if cell['Tetrode'] == tet2:
            cell['Area'] = 'PFC'
            pfc_spikes.append({})
            pfc_spikes[-1] = cell
            spikes[t]['Area'] = 'PFC'
        else:
            continue


import cv2
import colorsys
from random import randrange

colorHues = [0, 30, 60, 120, 160, 200, 240, 270, 300, 360]

def getClosestHueIndex(hue: int) -> int:
    if hue < 0 or hue > 360: return 0
    for index, colorHue in enumerate(colorHues):
        if (hue == colorHue): return index
        if (hue > colorHue): continue
        if ((colorHue - hue) < (hue - colorHues[index - 1])):
            return index if (index < len(colorHues) - 1) else 0
        else: return index - 1

def hueToHex(hue):
    (r, g, b) = colorsys.hsv_to_rgb(hue/360, 1, 1)
    (r, g, b) = (int(r * 255), int(g * 255), int(b * 255))
    return '#%02x%02x%02x' % (r, g, b)

def createColorList(debug=True):
    print("  Collecting colors: ", end="")
    colorList = [0] * (len(colorHues) - 1)
    img = cv2.imread('img/cover_small.png')
    img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
    (h, s, v) = cv2.split(img_hsv)
    for iy, y in enumerate(h):
        for ix, x in enumerate(y):
            closestIndex = getClosestHueIndex(x*2)
            h[iy][ix] = colorHues[closestIndex]*0.5
            weight = int(0.0039 * v[iy][ix] * (s[iy][ix] if s[iy][ix] > 32 else 0))
            colorList[closestIndex] += weight
            if debug:
                v[iy][ix] = weight
                s[iy][ix] = weight
    if debug:
        img_hsv = cv2.merge([h, s, v])
        img = cv2.cvtColor(img_hsv, cv2.COLOR_HSV2BGR)
        cv2.imwrite('img/cover_small_color.png', img)
    print(colorList)
    return colorList

def getColors():
    colHues = colorHues.copy()[:-1]
    COLCOUNT = len(colHues)
    colList = createColorList()

    col1id = 0
    if sum(colList) > 0: # test if color left
        print("  1 Searching primary color")
        for idx, col in enumerate(colList):
            if col > colList[col1id]:
                col1id = idx
        colList[col1id] = 0
    else:
        print("  1 Random primary color")
        col1id = randrange(0,COLCOUNT - 1)

    col2id = 0
    col2prob = 0
    if sum(colList) > 0: # test if color left
        print("  2 Searching secondary color")
        for idx, col in enumerate(colList):
            if col > colList[col2id]:
                col2id= idx
        col2prob = colList[col2id]
        colList[col2id] = 0
        # remove primary neightbors
        colList[(col1id+1)%COLCOUNT] = 0
        colList[(col1id+COLCOUNT-1)%COLCOUNT] = 0
    else:
        print("  2 Random secondary color")
        mod = randrange(2,COLCOUNT - 2)
        col2id = (col1id + mod) % COLCOUNT

    if sum(colList) > 0 and closeColors(col1id, col2id):
        col3id = 0
        print("    Searching for alternative color")
        for idx, col in enumerate(colList):
            if col > colList[col3id]:
                col3id= idx
        if colList[col3id] * 3 >= col2prob:
            print("    Changing to alternative color")
            col2id = col3id

    col1hue = colHues[col1id]
    col2hue = colHues[col2id]
    col1hex = hueToHex(col1hue)
    col2hex = hueToHex(col2hue)

    return [[col1id, col1hue, col1hex], [col2id, col2hue, col2hex]]

def closeColors(col1id, col2id) -> bool:
    LASTHUE = len(colorHues) - 2
    if col1id == 0:
        if col2id == 1: return True
        if col2id == LASTHUE: return True
    if col2id == 0:
        if col1id == 1: return True
        if col1id == LASTHUE: return True
    if abs(col1id - col2id) <= 1:
        return True
    return False


import machine
import time
import random
import ustruct

from machine import Pin, ADC
#customize behavior below
#=====================1====2====3====4====5====6====7====8====9===10#
midiOut1Note     = [ 48,  49,  50,  51,  52,  53,  54,  55,  56,  57] #MidiNote for each digital input. If you put 128 then it is ignored
midiOut2Note     = [ 48,  49,  50,  51,  52,  53,  54,  55,  56,  57] #MidiNote for each digital input
midiOut1Channel  = [ 10,  10,  10,  10,  10,  10,  10,  10,  10,  10]
midiOut2Channel  = [ 10,  10,  10,  10,  10,  10,  10,  10,  10,  10]
midiOut1Velocity = [127, 127, 127, 127, 127, 127, 127, 127, 127, 127]
midiOut2Velocity = [127, 127, 127, 127, 127, 127, 127, 127, 127, 127]
#=====================================================================

midiStart    = 0xFA
midiStop     = 0xFC
midiContinue = 0xFB
midiProg     = 0xC0
uart0 = machine.UART(0,31250)
uart1 = machine.UART(1,31250)

#digital pin debounce period in ms
debounceDigital = 20

# array that holds the last time a note was sent for each part. Used for debounce
lastNoteSent = [ 0,0,0,0,0,0,0,0,0,0]
lastStartStopSent = 0

parts = [Pin(22, Pin.IN), Pin(1, Pin.IN), Pin(2, Pin.IN), Pin(3, Pin.IN), Pin(16, Pin.IN),
         Pin(7, Pin.IN), Pin(8, Pin.IN), Pin(9, Pin.IN), Pin(10, Pin.IN), Pin(11, Pin.IN)]

startStop = Pin(12, Pin.IN)
SW1 = Pin(13, Pin.IN)
SW1Value = 0;
SW2 = Pin(14, Pin.IN)
SW2Value = 0;
isPlaying = 0
knob = machine.ADC(26)
aIn = machine.ADC(27)

def sendNoteOnMidi1(channel, note, vol):
    uart0.write(ustruct.pack("bbb",(0x90 | channel),note,vol))
def sendNoteOnMidi2(channel, note, vol):
    uart1.write(ustruct.pack("bbb",(0x90 | channel),note,vol))
def sendNoteOffMidi1(channel, note, vol):
    uart0.write(ustruct.pack("bbb",(0x80 | channel),note,vol))
def sendNoteOffMidi2(channel, note, vol):
    uart1.write(ustruct.pack("bbb",(0x90 | channel),note,vol))
def sendStart():
    uart0.write(ustruct.pack("b",midiStart))
    uart1.write(ustruct.pack("b",midiStart))
def sendStop():
    uart0.write(ustruct.pack("b",midiStop))
    uart1.write(ustruct.pack("b",midiStop))
def sendContinue():
    uart0.write(ustruct.pack("b",midiContinue))
    uart1.write(ustruct.pack("b",midiContinue))
def sendChange():
    uart0.write(ustruct.pack("bb",midiProg,0x00))
    uart1.write(ustruct.pack("bb",midiProg,0x00))
def sendCC(channel, cc, aValue):
    uart0.write(ustruct.pack("bbb",0xB0 + channel, cc, aValue))
    uart1.write(ustruct.pack("bbb",0xB0 + channel, cc, aValue))

loopCounter = 0
aNote = 0
currentTime = 0
def processDigitalInputs():
    global isPlaying
    global parts
    global lastNoteSent
    global lastStartStopSent
    for a in range(10):
        currentTime = time.ticks_ms()
        if parts[a].value() == 0 and (currentTime-lastNoteSent[a] > debounceDigital):
            lastNoteSent[a] = currentTime
            if midiOut1Note[a] != 128:
                sendNoteOnMidi1 (midiOut1Channel[a]-1,midiOut1Note[a],midiOut1Velocity[a])
                sendNoteOffMidi1(midiOut1Channel[a]-1,midiOut1Note[a],midiOut1Velocity[a])
            if midiOut2Note[a] != 128:
                sendNoteOnMidi2 (midiOut2Channel[a]-1,midiOut2Note[a],midiOut2Velocity[a])
                sendNoteOffMidi2(midiOut2Channel[a]-1,midiOut2Note[a],midiOut2Velocity[a])

    if startStop.value() == 0 and (currentTime-lastStartStopSent > (debounceDigital*3)):
        lastStartStopSent = currentTime
        if isPlaying == 1:
            sendStop()
            isPlaying = 0
        else:
            if SW2Value == 1:
                sendStart()
            else:
                sendContinue()
            isPlaying = 1

def processAnalogInputs():
    reading = knob.read_u16()
    reading2 = aIn.read_u16()
    #print("ADC: ",reading2)
    SW1Value = SW1.value()
    SW2Value = SW2.value()

### MAIN LOOP
while True:
    loopCounter +=1
    processDigitalInputs()
    if loopCounter > 1000:
       processAnalogInputs()
       loopCounter = 0


""" A module for working with q parameters which describe Gaussian laser beams

The q parameter is a complex number which specifies the properties of a Gaussian laser beam at a
particular point.  For an ideal Gaussian laser beam in the fundamental mode, the q parameter
contains enough information to characterize the beam at every point in space.
"""

import warnings
import numpy as np

__author__ = "Chris Mueller"
__status__ = "Development"

###################################################################################################
# qParameter Class
###################################################################################################
class qParameter(object):
    """ Used to define the complex q parameter of Gaussian beams.

    This class is designed for working with the complex-valued q parameter of Gaussian beams.
    The root object contains the complex-valued q parameter and the wavelength of the radiation
    described by that q parameter.

    :param q: The complex-valued q parameter
    :param wvlnt: The wavelength of the radiation
    :type q: complex
    :type wvlnt: float
    """
    def __init__(self, q=None, wvlnt=None):
        """ Returns an object in the qParameter class

        It is possible to initialize an empty object so that the set routines (defined below) can
        be used to more easily define the q parameter.  It is important that the units of the q
        parameter and the wavelength match, and it is generally best if both are expressed in
        meters.

        :param q: The complex-valued q parameter
        :param wvlnt: The wavelength of the radiation
        :type q: complex
        :type wvlnt: float
        :return: An instance of the qParameter class
        :rtype: qParameter
        """
        # Check types
        if q is not None:
            if not np.iscomplex(q):
                raise TypeError('q parameter should be complex')
            elif q.imag < 0:
                raise TypeError('q parameter should have a positive imaginary value')
        if wvlnt is not None:
            if wvlnt < 0:
                raise TypeError('wvlnt should be positive')
            elif wvlnt > 10e-6:
                warnings.warn('wvlnt is greater than 10e-6, double check the units')
        self.q = q
        self.wvlnt = wvlnt

    def copy(self):
        """ Returns a copy of the qParameter instance

        :return: fresh copy of qParameter
        :rtype: qParameter
        """
        other = qParameter(q=self.q, wvlnt=self.wvlnt)
        return other

    def __str__(self):
        """ Defines the string representation of the q parameter

        :return: string representation of the q parameter
        :rtype: str
        """
        q = self.get_q()
        if abs(q.real) < 1e-10:
            real_str = '{0:6.3f}'.format(q.real)
        elif -3 < np.log10(abs(q.real)) < 3:
            real_str = '{0:6.3f}'.format(q.real)
        else:
            real_str = '{0:4.3e}'.format(q.real)
        if abs(q.imag) < 1e-10:
            imag_str = '{0:6.3f}'.format(q.imag)
        elif -3 < np.log10(abs(q.imag)) < 3:
            imag_str = '{0:6.3f}'.format(q.imag)
        else:
            imag_str = '{0:4.3e}'.format(q.imag)
        return '{0} + {1}i'.format(real_str, imag_str)

    def __repr__(self):
        """ Defines the representation of the q parameter when called at the command line

        :return: string representation of the q parameter
        :rtype: str
        """
        return self.__str__()

    def print_q(self):
        """ Prints the q parameter to the screen
        """
        print(self.__str__())

    ###############################################################################################
    # get/set methods
    ###############################################################################################
    def get_q(self):
        """ Returns the q value

        :return: q parameter
        :rtype: complex
        """
        if self.q is None:
            raise AttributeError('q value has not yet been defined')
        return self.q

    def get_wvlnt(self):
        """ Returns the wvlnt

        :return: wavelength of q parameter
        :rtype: float
        """
        if self.wvlnt is None:
            raise AttributeError('wvlnt value has not yet been defined')
        return self.wvlnt

    def set_q(self, q=None, position=None, beamsize=None, rayleigh=None):
        """ Sets the q parameter

        This method is designed to be a flexible way to set the q parameter from more common
        variables.  It is important that the keword arguments be used otherwise the function will
        always assume that the first value is the q parameter.

        The function is hierarchical in the following ways
          - **q:** If q is not None, then all other arguments are ignored.  Note also that python
            will assume that the first argument is :code:`q` if keyword arguments are not used.
          - **position:** This is the position with respect to the waist.  If it is left as None,
            then it is assumed to be zero.
          - **beamsize:** This is the beam size expressed as the 1/e**2 *radius* (i.e. :math:`1w`,
            not :math:`2w`). If this is not None, then the rayleigh length is ignored.
          - **rayleigh:** This is the rayleigh range of the beam and is only used if
            :code:`beamsize` is None.

        :param q: The q parameter
        :param position: The postion with respect to the waist
        :param beamsize: The 1/e**2 beam radius
        :param rayleigh: The rayleight range of the beam
        :type q: complex
        :type position: float
        :type beamsize: float
        :type rayleigh: float
        :return: A modified qParameter instance with q redefined
        :rtype: qParameter
        """
        if q is None:
            if position is None:
                z = 0
            else:
                try:
                    z = float(position)
                except ValueError:
                    raise TypeError('position should be a float or int')
            if beamsize is None:
                if rayleigh is None:
                    raise TypeError('either beamsize or rayleigh must be specified if q is None')
                elif rayleigh <= 0:
                    raise TypeError('rayleigh should be positive')
                else:
                    zr = rayleigh
            elif beamsize <= 0:
                raise TypeError('beamsize should be positive')
            else:
                wvlnt = self.get_wvlnt()
                zr = np.pi * beamsize**2/wvlnt
            q = z + 1j*zr
        if not np.iscomplex(q):
            raise TypeError('q parameter should be complex')
        elif q.imag < 0:
            raise TypeError('q parameter should have a positive imaginary value')
        self.q = q
        return self

    def set_wvlnt(self, wvlnt):
        """ Sets the wavelength of the radiation described by the q parameter.

        :param wvlnt: The wavelength of the radiation
        :type wvlnt: float
        :return: modified instance of the qParameter class
        :rtype: qParameter
        """
        if wvlnt < 0:
            raise TypeError('wvlnt should be positive')
        elif wvlnt > 10e-6:
            warnings.warn('wvlnt is greater than 10e-6, double check the units')
        self.wvlnt = wvlnt

    ###############################################################################################
    # Add/Subtract Methods
    ###############################################################################################
    def __add__(self, q2):
        copy = self.copy()
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            copy.q = self.get_q() + q2.get_q()
        else:
            copy.q = self.get_q() + q2
        return copy

    def __radd__(self, q2):
        copy = self.copy()
        copy.q = q2 + self.get_q()
        return copy

    def __iadd__(self, q2):
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            self.q = self.get_q() + q2.get_q()
        else:
            self.q = self.get_q() + q2
        return self

    def __sub__(self, q2):
        copy = self.copy()
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            copy.q = self.get_q() - q2.get_q()
        else:
            copy.q = self.get_q() - q2
        return copy

    def __rsub__(self, q2):
        copy = self.copy()
        copy.q = q2 - self.get_q()
        return copy

    def __isub__(self, q2):
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            self.q = self.get_q() - q2.get_q()
        else:
            self.q = self.get_q() - q2
        return self

    def __truediv__(self, q2):
        copy = self.copy()
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            copy.q = self.get_q() / q2.get_q()
        else:
            copy.q = self.get_q() / q2
        return copy

    def __rtruediv__(self, q2):
        copy = self.copy()
        copy.q = q2 / self.get_q()
        return copy

    def __itruediv__(self, q2):
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            self.q = self.get_q() / q2.get_q()
        else:
            self.q = self.get_q() / q2
        return self

    def __mul__(self, q2):
        copy = self.copy()
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            copy.q = self.get_q() * q2.get_q()
        else:
            copy.q = self.get_q() * q2
        return copy

    def __rmul__(self, q2):
        copy = self.copy()
        copy.q = q2 * self.get_q()
        return copy

    def __imul__(self, q2):
        if type(q2) is qParameter:
            if not self.get_wvlnt() == q2.get_wvlnt():
                warnings.warn('wvlnt of two qParameter instances is not equal')
            self.q = self.get_q() * q2.get_q()
        else:
            self.q = self.get_q() * q2
        return self

    ###############################################################################################
    # Beam Property Calculations
    ###############################################################################################
    def w(self, m2=1):
        """ Calculates the beam size (not necessarily at the waist) of the q parameter.

        An M**2 value other than 1 can be taken into account.  The beam size returned is the
        1/e**2 beam size usually denoted as w.  Note that M**2 is used, not M.

        :param m2: The M**2 value of the beam
        :type m2: float > 1
        :return: The 1/e**2 beam size
        :rtype: float
        """
        # Check m2
        if m2 < 1:
            raise ValueError('m2 should be greater than or equal to 1')
        wvlnt = self.get_wvlnt()
        q = self.get_q()
        return (wvlnt/np.pi * q.imag * (1 + m2**2 * (q.real/q.imag)**2))**(1/2)

    def r(self):
        """ Calculates the radius of curvature of the field's phasefronts.
        """
        q = self.get_q()
        return q.imag * (q.real/q.imag + q.imag/q.real)

    def w0(self):
        """ Calculates the *waist size* of the beam.
        """
        wvlnt = self.get_wvlnt()
        q = self.get_q()
        return (wvlnt/np.pi * q.imag)**(1/2)

    def z(self):
        """ Calculates the position of the beam relative to the waist

         I.E. the real part of the q parameter.

        :return: position relative to waist
        :rtype: float
        """
        q = self.get_q()
        return q.real

    def zr(self):
        """ Calculates the Rayleigh range of the beam

        I.E. the imaginary part of the q parameter.

        :return: Rayleigh length of the beam
        :rtype: float
        """
        q = self.get_q()
        return q.imag

    def gouy(self):
        """ Calculates the Gouy phase, relative to the waist.
        """
        q = self.get_q()
        return np.arctan(q.real/q.imag)

    def divang(self):
        """ Calculates the divergence angle of the beam given the q parameter.

        This is the diffraction limited (M^2=1) beam divergence.
        """
        wvlnt = self.get_wvlnt()
        return wvlnt/(np.pi * self.w0())

    def reverse(self):
        """ Returns a q parameter which is identical but facing the other direction.

        This is accomplished by switching the sign of the real component.

        :return: reversed q parameter
        :rtype: qParameter
        """
        q = self.get_q()
        self.q = q - 2 * q.real
        return self

    def ovlp_field(self, q2):
        """ Calculates the field overlap coefficient with q2.

        This method calculates the overlap coefficient of two Gaussian beams defined by the two
        q parameters.  This is a complex coefficient which describes how much of one field
        overlaps with the other in the orthogonal basis.

        :param q2: The q parameter against which the overlap will be calculated
        :type q2: qParameter
        :return: complex-valued overlap coefficient
        :rtype: complex
        """
        # Check type of q2
        if type(q2) is not qParameter:
            raise TypeError('q2 should be an instance of the qParameter class')
        q1_val = self.get_q()
        q2_val = q2.get_q()
        return (4 * q1_val.imag * q2_val.imag)/abs(q1_val.conjugate() - q2_val)**2

    def ovlp_pow(self, q2):
        """ Calculates the power overlap coefficient with q2.

        This method calculates the overlap coefficient of two Gaussian beams defined by the two
        q parameters.  This is a real-valued coefficient which describes how much of the power in
        one field overlaps with the other.

        :param q2: The q parameter against which the overlap will be calculated
        :type q2: qParameter
        :return: real-valued overlap coefficient
        :rtype: float
        """
        return abs(self.ovlp_field(q2))**2

###############################################################################
# Beam Class
###############################################################################
class Beam(qParameter):
    """ Extension of the qParameter class which includes a position and label

    """
    def __init__(self, position, label, q=None, wvlnt=None):
        """ Returns an object in the qParameter class

        It is possible to initialize an empty object so that the set routines (defined below) can
        be used to more easily define the q parameter.  It is important that the units of the q
        parameter and the wavelength match, and it is generally best if both are expressed in
        meters.

        :param z: The position of the q parameter
        :param label: A string identifier of the q parameter
        :param q: The complex-valued q parameter
        :param wvlnt: The wavelength of the radiation
        :type z: float
        :type label: str
        :type q: complex or qParameter
        :type wvlnt: float or None
        :return: An instance of the Beam class
        :rtype: Beam
        """
        assert type(label) is str
        try:
            position = float(position)
        except ValueError:
            raise ValueError("z should be a float")
        if type(q) is qParameter:
            super(Beam, self).__init__(q=q.get_q(), wvlnt=q.get_wvlnt())
        else:
            super(Beam, self).__init__(q=q, wvlnt=wvlnt)
        self.position = position
        self.label = label

    def __repr__(self):
        """ Defines the representation of the Beam instance
        """
        out = "{0}: {1} @ {2:0.2g}, wvlnt={3:0.3g}".format(self.label,
                                                           super(Beam, self).__repr__(),
                                                           self.position,
                                                           self.wvlnt)
        return out


import matplotlib.pyplot as plt
import os

import numpy as np
from models.skip import skip
from models import get_net

import torch
import torch.optim
import torchvision
from skimage.measure import compare_psnr

from PIL import Image
import PIL.ImageDraw as ImageDraw
import PIL.ImageFont as ImageFont

def get_bernoulli_mask(for_image, zero_fraction=0.95):
    img_mask_np=(np.random.random_sample(size=for_image.shape) > zero_fraction).astype(int)
    img_mask = img_mask_np

    return img_mask

def fill_noise(x, noise_type):
    """Fills tensor `x` with noise of type `noise_type`."""
    if noise_type == 'u':
        x.uniform_()
    elif noise_type == 'n':
        x.normal_()
    else:
        assert False

def get_noise(input_depth, method, spatial_size, noise_type='u', var=1./10):
    if isinstance(spatial_size, int):
        spatial_size = (spatial_size, spatial_size)
    if method == 'noise':
        shape = [1, input_depth, spatial_size[0], spatial_size[1]]
        net_input = torch.zeros(shape)

        fill_noise(net_input, noise_type)
        net_input *= var
    elif method == 'meshgrid':
        assert input_depth == 2
        X, Y = np.meshgrid(np.arange(0, spatial_size[1])/float(spatial_size[1]-1), np.arange(0, spatial_size[0])/float(spatial_size[0]-1))
        meshgrid = np.concatenate([X[None,:], Y[None,:]])
        net_input=  np_to_torch(meshgrid)
    else:
        assert False

    return net_input

def get_params(opt_over, net, net_input, downsampler=None):
    opt_over_list = opt_over.split(',')
    params = []

    for opt in opt_over_list:

        if opt == 'net':
            params += [x for x in net.parameters() ]
        elif  opt=='down':
            assert downsampler is not None
            params = [x for x in downsampler.parameters()]
        elif opt == 'input':
            net_input.requires_grad = True
            params += [net_input]
        else:
            assert False, 'what is it?'

    return params

def optimize(optimizer_type, parameters, closure, LR, num_iter):
    """Runs optimization loop.
    Args:
        optimizer_type: 'LBFGS' of 'adam'
        parameters: list of Tensors to optimize over
        closure: function, that returns loss variable
        LR: learning rate
        num_iter: number of iterations
    """
    if optimizer_type == 'LBFGS':
        # Do several steps with adam first
        optimizer = torch.optim.Adam(parameters, lr=0.001)
        for j in range(100):
            optimizer.zero_grad()
            closure()
            optimizer.step()

        print('Starting optimization with LBFGS')
        def closure2():
            optimizer.zero_grad()
            return closure()
        optimizer = torch.optim.LBFGS(parameters, max_iter=num_iter, lr=LR, tolerance_grad=-1, tolerance_change=-1)
        optimizer.step(closure2)

    elif optimizer_type == 'adam':
        print('Starting optimization with ADAM')
        optimizer = torch.optim.Adam(parameters, lr=LR)

        for j in range(num_iter):
            optimizer.zero_grad()
            closure()
            optimizer.step()
    else:
        assert False

def get_image_grid(images_np, nrow=8):
    '''Creates a grid from a list of images by concatenating them.'''
    images_torch = [torch.from_numpy(x) for x in images_np]
    torch_grid = torchvision.utils.make_grid(images_torch, nrow)

    return torch_grid.numpy()

def plot_image_grid(images_np, nrow =8, factor=1, interpolation='lanczos'):
    """Draws images in a grid

    Args:
        images_np: list of images, each image is np.array of size 3xHxW of 1xHxW
        nrow: how many images will be in one row
        factor: size if the plt.figure
        interpolation: interpolation used in plt.imshow
    """
    n_channels = max(x.shape[0] for x in images_np)
    assert (n_channels == 3) or (n_channels == 1), "images should have 1 or 3 channels"

    images_np = [x if (x.shape[0] == n_channels) else np.concatenate([x, x, x], axis=0) for x in images_np]

    grid = get_image_grid(images_np, nrow)

    plt.figure(figsize=(len(images_np) + factor, 12 + factor))

    if images_np[0].shape[0] == 1:
        plt.imshow(grid[0], cmap='gray', interpolation=interpolation)
    else:
        plt.imshow(grid.transpose(1, 2, 0), interpolation=interpolation)

    plt.show()

    return grid


# https://leetcode.com/problems/find-all-duplicates-in-an-array/
# 我们发现对数组的所有元素均有1\leq a[i]\leq n1≤a[i]≤n，也就是说对于所有a[a[i]-1]均为合法下标。由此引出如下做法：
# 对于每个a[i]，我们将其对应的a[a[i]-1]取相反数，如果已经为负数则将a[i]加入答案中。

# T: O(n)
# S: O(n)

class Solution:
    def findDuplicates(self, nums: List[int]) -> List[int]:
        if not nums:
            return []

        result = []
        for i in range(len(nums)):
            idx = nums[i]
            if idx < 0:
                idx = -idx
            if nums[idx - 1] > 0:
                nums[idx - 1] = -nums[idx - 1]
            else:
                result.append(idx)
        return result


subject_codes = {
    "AANB",
    "ACAM",
    "ADHE",
    "AFST",
    "AGEC",
    "ANAT",
    "ANTH",
    "APBI",
    "APPP",
    "APSC",
    "AQUA",
    "ARBC",
    "ARC",
    "ARCH",
    "ARCL",
    "ARST",
    "ARTC",
    "ARTH",
    "ARTS",
    "ASIA",
    "ASIC",
    "ASLA",
    "ASTR",
    "ASTU",
    "ATSC",
    "AUDI",
    "BA",
    "BAAC",
    "BABS",
    "BAEN",
    "BAFI",
    "BAHC",
    "BAHR",
    "BAIM",
    "BAIT",
    "BALA",
    "BAMA",
    "BAMS",
    "BAPA",
    "BASC",
    "BASD",
    "BASM",
    "BATL",
    "BAUL",
    "BEST",
    "BIOC",
    "BIOF",
    "BIOL",
    "BIOT",
    "BMEG",
    "BOTA",
    "BRDG",
    "BUSI",
    "CAPS",
    "CCFI",
    "CCST",
    "CDST",
    "CEEN",
    "CELL",
    "CENS",
    "CHBE",
    "CHEM",
    "CHIL",
    "CHIN",
    "CIVL",
    "CLCH",
    "CLST",
    "CNPS",
    "CNRS",
    "CNTO",
    "COEC",
    "COGS",
    "COHR",
    "COLX",
    "COMM",
    "COMR",
    "CONS",
    "CPEN",
    "CPSC",
    "CRWR",
    "CSIS",
    "CSPW",
    "CTLN",
    "DANI",
    "DENT",
    "DES",
    "DHYG",
    "DMED",
    "DSCI",
    "ECED",
    "ECON",
    "ECPS",
    "EDCP",
    "EDST",
    "EDUC",
    "EECE",
    "ELEC",
    "ELI",
    "EMBA",
    "ENDS",
    "ENGL",
    "ENPH",
    "ENPP",
    "ENVR",
    "EOSC",
    "EPSE",
    "ETEC",
    "EXCH",
    "EXGR",
    "FACT",
    "FCOR",
    "FEBC",
    "FHIS",
    "FIPR",
    "FISH",
    "FIST",
    "FMPR",
    "FMST",
    "FNEL",
    "FNH",
    "FNIS",
    "FOOD",
    "FOPR",
    "FRE",
    "FREN",
    "FRSI",
    "FRST",
    "FSCT",
    "GBPR",
    "GEM",
    "GENE",
    "GEOB",
    "GEOG",
    "GERM",
    "GPP",
    "GREK",
    "GRS",
    "GRSJ",
    "GSAT",
    "HEBR",
    "HESO",
    "HGSE",
    "HINU",
    "HIST",
    "HPB",
    "HUNU",
    "IAR",
    "IEST",
    "IGEN",
    "INDE",
    "INDO",
    "INDS",
    "INFO",
    "INLB",
    "ISCI",
    "ITAL",
    "ITST",
    "IWME",
    "JAPN",
    "JRNL",
    "KIN",
    "KORN",
    "LAIS",
    "LARC",
    "LASO",
    "LAST",
    "LATN",
    "LAW",
    "LFS",
    "LIBE",
    "LIBR",
    "LING",
    "LLED",
    "LWS",
    "MANU",
    "MATH",
    "MDVL",
    "MECH",
    "MEDD",
    "MEDG",
    "MEDI",
    "MGMT",
    "MICB",
    "MIDW",
    "MINE",
    "MRNE",
    "MTRL",
    "MUSC",
    "NAME",
    "NEST",
    "NEUR",
    "NRSC",
    "NURS",
    "OBMS",
    "OBST",
    "OHS",
    "ONCO",
    "ORNT",
    "ORPA",
    "OSOT",
    "PATH",
    "PCTH",
    "PERS",
    "PHAR",
    "PHIL",
    "PHRM",
    "PHTH",
    "PHYL",
    "PHYS",
    "PLAN",
    "PLAS",
    "PLNT",
    "POLI",
    "POLS",
    "PORT",
    "PSYC",
    "PSYT",
    "PUNJ",
    "RADI",
    "RELG",
    "RES",
    "RGLA",
    "RHSC",
    "RMST",
    "RUSS",
    "SANS",
    "SCAN",
    "SCIE",
    "SEAL",
    "SGES",
    "SLAV",
    "SOAL",
    "SOCI",
    "SOIL",
    "SOWK",
    "SPAN",
    "SPE",
    "SPHA",
    "SPPH",
    "STAT",
    "STS",
    "SURG",
    "SWED",
    "TEST",
    "THTR",
    "TIBT",
    "TRSC",
    "UDES",
    "UFOR",
    "UKRN",
    "URO",
    "UROL",
    "URST",
    "URSY",
    "VANT",
    "VGRD",
    "VISA",
    "VRHC",
    "VURS",
    "WOOD",
    "WRDS",
    "WRIT",
    "ZOOL"
}


import json

from typing import Dict, Set

from archives.wadarchive import WADArchive
from idgames.game import Game
from utils.config import Config
from utils.logger import Logger

iwads = {
    Game.DOOM2: 'doom2.wad',
    Game.DOOM: 'doom.wad',
    Game.HERETIC: 'heretic.wad',
    Game.HEXEN: 'hexen.wad',
    Game.TNT: 'tnt.wad',
    Game.PLUTONIA: 'plutonia.wad',
    Game.STRIFE: 'strife0.wad',
    Game.HACX: 'hacx.wad'
}

iwad_factors = {
    Game.DOOM2: 1.0,
    Game.DOOM: 1.0,
    Game.HERETIC: 1.0,
    Game.HEXEN: 1.0,
    Game.TNT: 0.75,
    Game.PLUTONIA: 0.75,
    Game.STRIFE: 1.0,
    Game.HACX: 0.5,
}

ignore = {
    'THINGS', 'LINEDEFS', 'SIDEDEFS', 'VERTEXES', 'SEGS', 'SSECTORS', 'NODES', 'SECTORS', 'REJECT', 'BLOCKMAP',
    'SS_START', 'S_START', 'SS_END', 'S_END',
    'FF_START', 'F_START', 'FF_END', 'F_END',
    'PP_START', 'P_START', 'PP_END', 'P_END',
    'PLAYPAL', 'COLORMAP'
}

config = Config()
logger = Logger(config.get('paths.logs'))

lump_list: Dict[str, Set[Game]] = {}
for game, iwad_file in iwads.items():
    iwad_path = '{}/{}'.format(config.get('paths.iwads'), iwad_file)
    iwad_path = str(iwad_path).replace('\\', '/')

    with open(iwad_path, 'rb') as f:
        archive = WADArchive(iwad_path, f, logger)

    for file in archive.files:
        if file.name in ignore:
            continue

        if file.name in lump_list:
            lump_list[file.name].add(game)
        else:
            lump_list[file.name] = {game}

lump_scores: Dict[str, Dict[str, float]] = {}
for lump_name, lump_presence in lump_list.items():

    # Skip lumps that are present in every IWAD.
    if len(lump_presence) == len(iwads):
        continue

    # Score by how many IWADs a lump appears in and the factor per IWAD.
    average = 1 / len(lump_presence)
    presence_scores: Dict[str, float] = {}
    for game in lump_presence:
        presence_scores[game.value] = average * iwad_factors[game]

    lump_scores[lump_name] = presence_scores

with open(config.get('extractors.game.lump_score_table'), 'w') as f:
    json.dump(lump_scores, f, indent=4)


from Google import Create_Service
import pandas
import os, shutil
import pickle
import requests
import ast
from os import path

class GooglePhotos:

    API_NAME = 'photoslibrary'
    API_Version = 'v1'
    CLIENT_SECRET_FILE = 'client_secret_storein.com.json'
    SCOPES = ['https://www.googleapis.com/auth/photoslibrary',
              'https://www.googleapis.com/auth/photoslibrary.sharing']
    REQUEST_BODY = {'album': {'title': 'StoreIN',
                              'coverPhotoBaseUrl': 'https://cdn.pixabay.com/photo/2012/04/12/20/12/x-30465_960_720.png=w2048-h1024'}}

    def __init__(self):
        self.init_service()
        self.check_folders()

    def init_service(self):
        self.service = Create_Service(GooglePhotos.CLIENT_SECRET_FILE,
                                      GooglePhotos.API_NAME,
                                      GooglePhotos.API_Version,
                                      GooglePhotos.SCOPES)

    def list_api_folders(self):
        self.albums = self.service.albums().list(pageSize = 50,
                                         excludeNonAppCreatedData = True
                                         ).execute()
        albums_list = self.albums.get('albums')
        nextPageToken = self.albums.get('nextPageToken')
        while nextPageToken:
            self.albums = self.service.albums.list(pageSize=50,
                                   excludeNonAppCreatedData=True,
                                   pageToken=nextPageToken)
            albums_list.append(self.albums.get('ablums'))
            nextPageToken = self.albums.get('nextPageToken')
        df_albums = pandas.DataFrame(albums_list)
        return df_albums

    def check_folders(self):
        temp_list = self.list_api_folders()
        if not temp_list.empty:
            if temp_list['title'].any() == 'StoreIN':
                self.temp_copy = temp_list
                self.my_album_id = temp_list[temp_list['title']=='StoreIN']['id'][0]
            else:
                temp_body = self.service.albums().create(body = GooglePhotos.REQUEST_BODY).execute()
                enrichement_body = {'newEnrichmentItem':
                                    {'textEnrichment':
                                        {'text': 'This album contains the photos of valid receipts stored thanks to StoreIN app, please make sure to not change or delete anything while not using the app itself. Such behavior may create some issues while using the app. Thank you for cooperation.'}},
                                    'albumPosition': {'position': 'LAST_IN_ALBUM'}}
                self.service.albums().addEnrichment(albumId=temp_body.get('id'),
                                        body=enrichement_body).execute()
                self.my_album_id = temp_body.get('id')
        else:
            temp_body = self.service.albums().create(body = GooglePhotos.REQUEST_BODY).execute()
            enrichement_body = {'newEnrichmentItem':
                                {'textEnrichment':
                                    {'text': 'This album contains the photos of valid receipts stored thanks to StoreIN app. Please make sure to not change or delete anything while not using the app itself, such behavior may create some issues while using the app. Thank you for cooperation.'}},
                                'albumPosition': {'position': 'LAST_IN_ALBUM'}}
            self.service.albums().addEnrichment(albumId=temp_body.get('id'),
                                        body=enrichement_body).execute()
            self.my_album_id = temp_body.get('id')

    def delete_empty_lines(self, my_file = "location_enrichments.txt"):
        with open(my_file,'r+') as file:
            for line in file:
                if not line.isspace():
                    file.write(line)

    def check_enrichments(self):
        dictionary = {}
        i = 1
        if not os.path.exists('location_enrichments.txt'):
            with open('location_enrichments.txt', 'w') as file:
                pass
            file.close()
        #self.delete_empty_lines()
        with open("location_enrichments.txt", "r") as file:
            lines = [line.rstrip() for line in file]
        file.close()
        for x in lines:
            temp = {}
            temp.update(ast.literal_eval(x))
            dictionary[i] = temp
            i = i+1
        return dictionary

    def check_if_location_already_exists(self, loc_name = " "):
        #self.delete_empty_lines()
        for line_number,line_content in self.check_enrichments().items():
            for key in line_content:
                if loc_name in line_content[key]:
                    if loc_name == line_content[key]:
                        return line_content["ID"]
                    else:
                        return False

    def add_location(self, lat = 0, lon = 0, loc_name = "None", **kwargs):
        temp = self.check_if_location_already_exists(loc_name = loc_name)
        print(temp)
        if temp is not None:
            return temp
        else:
            self.latitude = lat
            self.longitude = lon
            self.location_name = loc_name
            enrichement_body ={"newEnrichmentItem": {
                                "locationEnrichment": {
                                  "location": {
                                    "latlng": {
                                      "latitude": self.latitude,
                                      "longitude": self.longitude
                                    },
                                    "locationName": self.location_name
                                  }
                                }
                              },
                              "albumPosition": {
                                "position": "LAST_IN_ALBUM"
                              }
                            }
            response = self.service.albums().addEnrichment(albumId=self.my_album_id,
                                                body=enrichement_body).execute()
            f=open("location_enrichments.txt", "a+")
            f.write("{{'Name': '{}', 'latitude': '{}', 'longitude': '{}', 'ID': '{}'}}\r".format(self.location_name, self.latitude, self.longitude, response['enrichmentItem']['id']))
            f.close
            #self.delete_empty_lines()
            return response['enrichmentItem']['id']

    # create method in mainy.py that will check if user wants to add the location or not
    #atm method takes values for only created enrichment
    def upload_image(self, file_name = 'dummy2.png', relativeEnrichmentItemId = None, posistion = "LAST_IN_ALBUM", uploaded_file_name = " ", **kwargs):
        #relativeEnrichmentItemId = self.check_enrichment()['ID']
        posistion = "AFTER_ENRICHMENT_ITEM"
        image_dir = os.path.join(os.getcwd(), 'imagestoupload')
        token = pickle.load(open('token_photoslibrary_v1.pickle', 'rb'))
        upload_url = 'https://photoslibrary.googleapis.com/v1/uploads'
        headers = {'Authorization': 'Bearer ' + token.token,
                   'Content-type': 'application/octet-stream',
                   'X-Goog-Upload-Protocol': 'raw',
                   'X-Goog-File-Name': file_name
                   }
        image_file = os.path.join(image_dir, file_name)
        img = open(image_file, 'rb').read()
        response = requests.post(upload_url, data=img, headers=headers)
        request_body = {"newMediaItems": [{"description": 'Name|Date|Years to expire',
                                           "simpleMediaItem":
                                               {"fileName": uploaded_file_name,
                                                "uploadToken": response.content.decode('utf-8')
                                                }
                                               }
                                               ],
                                                "albumId": self.my_album_id,
                                                "albumPosition":
                                                    {"relativeEnrichmentItemId": relativeEnrichmentItemId,
                                                     "position": posistion
                                                     }
                                                    }

        self.service.mediaItems().batchCreate(body=request_body).execute()
        #print('\nUpload token: {0}'.format(response.content.decode('utf-8')))
        #return response

    def get_enrichment_id(self): #method to find enrichement ID for response body below
        print(self.add_location())

    def list_thumbnails(self):
        thumbnails = {}
        i = 0
        media_files = self.service.mediaItems().search(body={'albumId': self.my_album_id}).execute()['mediaItems']
        #print(media_files)
        for media_file in media_files:
            temp = {}
            temp.update({"file_name": media_file["filename"]})
            temp.update({"download_url": media_file["baseUrl"] + '=w128-h128'})
            thumbnails[i] = temp
            i = i+1
        return thumbnails
        #image = Image.open('image.jpg')
        #image.show()

    def download_file(self, url: str, file_name: str):
        response = requests.get(url)
        if response.status_code == 200:
            temp_name = file_name.split('|')[0] + '.png'
            if not os.path.exists(file_name + 'png'):
                # with open('tmp/' + temp_name, 'w') as file:
                #     file.close()
            #remember to add smth taht changes name if already exists
                with open('tmp/' + temp_name, 'wb') as file:
                    content = response.content
                    file.write(content)
                    file.close()
        else:
            return response.status_code

        #method for creating one pic
    def create_im(self):
        file_name = 'pieknyja|2020-05-01|2'
        url = self.list_thumbnails().get(10,'').get('download_url','')
        content = self.download_file(url, file_name)
        #print(content)
        temp_name = file_name.split('|')[0] + '.png'
        if not os.path.exists(file_name + 'png'):
            with open('tmp/' + temp_name, 'w') as file:
                file.close()
            with open('tmp/' + temp_name, 'wb') as file:
                file.write(content)
                file.close()
        else:
            pass

    def download_thumbnails(self, number_of_items_to_download: int):
        if number_of_items_to_download == -1:
            sliced_part = self.list_thumbnails()
        else:
            sliced_part = {k: self.list_thumbnails()[k] for k in sorted(self.list_thumbnails().keys())[:number_of_items_to_download]}
        for key in sliced_part:
            #print(key)
            url = sliced_part.get(key,'').get('download_url','')
            file_name = sliced_part.get(key,'').get('file_name','')
            if path.exists('tmp/' + file_name + '.png'):
                pass
            else:
                self.download_file(url, file_name)
        return number_of_items_to_download

    def delete_thumbnails(self):
        folder = 'tmp/'
        for filename in os.listdir(folder):
            file_path = os.path.join(folder, filename)
            try:
                if os.path.isfile(file_path) or os.path.islink(file_path):
                    os.unlink(file_path)
                elif os.path.isdir(file_path):
                    shutil.rmtree(file_path)
            except Exception as e:
                print('Failed to delete %s. Reason: %s' % (file_path, e))

if __name__ == '__main__':
    gp = GooglePhotos()
    #gp.download_thumbnails(5)
    gp.delete_thumbnails()
    #gp.create_im()
    #url = 'https://lh3.googleusercontent.com/lr/AFBm1_ZtG5b1WX30UZazbuVueigjgx-HiR-a5pzwP4rixJ16fWKE5dKKLPFX9Vlq8ev8tfqnYYHZJqOirRq_Eo0bzm6rIcmZHCFngvW9GEx0ODh9_IyTvS1JQO9xYn187CJIjqa63-6gXOl_dXYoOdQCZ57qsPnwjYaei9J8M1C27YCo-7bRkDl6dMKoJ8RPX4hKK0ArbQo0GrhgpAt2avOzEOOHpGwF1doi46OOlBXzfZWSfgZdHjyWgkWviRMeIQyLJqJpW7UyKQwoFjLhrqDsc8YKVA5cPCm1z1WOONCYO2KARU2Z61FEkZrSEQn0BRSb8Mz20an9NRnRK61LbE1NVzpSHKGkpLbbBhZDqwf27wvyN2vqG-p4usXHmpfX15-SOCK3e8fP0eAh9O9h2Q39182yGeJ0PpwVO83oxeJlWezd1s-fiLyz1mUMw-AgQWDlpZG87fyIgQDbecH5W1zSfyB3H2qj2qBsrdXIxg9q0cbiuuvJrd6utX2jKhm9o6AqmpTm2Esyl3lleeK1ztwwqofso8wtjdP-Rsk12JQMbgIapY15Dt-agqtnY58RyUi4SHs2BvAY70dyB1Q8ImKlVmNW9p8Qsj2lBN4mbuyIWh2YxMHSiaEPEzrWZY4Bu86TYtaluW9c5nTHkL85EUvXoI0nGqbqzc7xOh_84U7upUWuKoxfrsuxE-dkWqtQX00kZAMiJZ5-UJ6zn7Y2MjJUmK-Oy9CTJBPxYJ59oThH_VJFtvAQP17tiFgI17wr719btCrMEiZoNwwqQlYu70eMebBORGuPSF7fh6kyy6Kf9o03-sAJJ1LOSHvhrGsd=w128-h128'
    #file_name = 'pieknyja|2020-05-01|2'
    #gp.[10]['download_url']
    #print(gp.list_thumbnails())
    #print(gp.add_location(loc_name = "Makowa"))
    #print(gp.load_enrichmentID(loc_name = "Makowa"))
    #gp.upload_image()
    #print(gp.check_enrichments())
    #gp.get_enrichment_id()
    #
    #
    #print(gp.list_api_folders()['title'])
    #print(gp.check_folders())