import logging
from pathlib import Path
from typing import List

import joblib
import pandas as pd
from sklearn.pipeline import Pipeline

from model import __version__ as _version
from model.config.core import DATASET_DIR, TRAINED_MODEL_DIR, config

logger = logging.getLogger(__name__)

def load_dataset(*, client_file_name: str, price_file_name: str) -> pd.DataFrame:
    # Loading the datasets.
    dataframe_client = pd.read_csv(Path(f"{DATASET_DIR}/{client_file_name}"))
    dataframe_price = pd.read_csv(Path(f"{DATASET_DIR}/{price_file_name}"))

    # Converting to datetime.
    dataframe_client = datetime_conversion_client(df=dataframe_client)
    dataframe_price = datetime_conversion_price(df=dataframe_price)

    # Transforming the price data.
    dataframe_price = price_data_trans(df=dataframe_price)

    # Merging the client and price datasets.
    dataframe = merging_datasets(df=dataframe_client, df_1=dataframe_price)

    # Getting time and consumption features.
    dataframe = time_features(df=dataframe)
    dataframe = consum_features(df=dataframe)

    dataframe = dataframe.drop("Unnamed: 0", axis=1)
    dataframe = dataframe.dropna()

    # Creating the new categorical features.
    dataframe["price_change_energy"] = dataframe[
        "offpeak_diff_dec_january_energy"
    ].apply(price_change)
    dataframe["price_change_power"] = dataframe["offpeak_diff_dec_january_power"].apply(
        price_change
    )

    # Dropping the first feature.
    dataframe = dataframe.drop(
        ["offpeak_diff_dec_january_energy", "offpeak_diff_dec_january_power"], axis=1
    )

    dataframe = dataframe.dropna()

    return dataframe

def save_pipeline(*, pipeline_to_persist: Pipeline) -> None:
    """Persist the pipeline.
    Saves the versioned model, and overwrites any previous
    saved models. This ensures that when the package is
    published, there is only one trained model that can be
    called, and we know exactly how it was built.
    """

    # Prepare versioned save file name
    save_file_name = f"{config.app_config.pipeline_save_file}{_version}.pkl"
    save_path = TRAINED_MODEL_DIR / save_file_name

    remove_old_pipelines(files_to_keep=[save_file_name])
    joblib.dump(pipeline_to_persist, save_path)

def load_pipeline(*, file_name: str) -> Pipeline:
    """Load a persisted pipeline."""

    file_path = TRAINED_MODEL_DIR / file_name
    return joblib.load(filename=file_path)

def remove_old_pipelines(*, files_to_keep: List[str]) -> None:
    """
    Remove old model pipelines.
    This is to ensure there is a simple one-to-one
    mapping between the package version and the model
    version to be imported and used by other applications.
    """
    do_not_delete = files_to_keep + ["__init__.py"]
    for model_file in TRAINED_MODEL_DIR.iterdir():
        if model_file.name not in do_not_delete:
            model_file.unlink()

def datetime_conversion_client(df: pd.DataFrame) -> pd.DataFrame:

    df["date_activ"] = pd.to_datetime(df["date_activ"], format="%Y-%m-%d")
    df["date_end"] = pd.to_datetime(df["date_end"], format="%Y-%m-%d")
    df["date_modif_prod"] = pd.to_datetime(df["date_modif_prod"], format="%Y-%m-%d")
    df["date_renewal"] = pd.to_datetime(df["date_renewal"], format="%Y-%m-%d")

    return df

def datetime_conversion_price(df: pd.DataFrame) -> pd.DataFrame:

    df["price_date"] = pd.to_datetime(df["price_date"], format="%Y-%m-%d")

    return df

def price_data_trans(df: pd.DataFrame) -> pd.DataFrame:

    # Group off-peak prices by companies and month
    monthly_price_by_id = (
        df.groupby(["id", "price_date"])
        .agg({"price_off_peak_var": "mean", "price_off_peak_fix": "mean"})
        .reset_index()
    )

    # Get january and december prices
    jan_prices = monthly_price_by_id.groupby("id").first().reset_index()
    dec_prices = monthly_price_by_id.groupby("id").last().reset_index()

    # Calculate the difference
    diff = pd.merge(
        dec_prices.rename(
            columns={"price_off_peak_var": "dec_1", "price_off_peak_fix": "dec_2"}
        ),
        jan_prices.drop(columns="price_date"),
        on="id",
    )
    diff["offpeak_diff_dec_january_energy"] = diff["dec_1"] - diff["price_off_peak_var"]
    diff["offpeak_diff_dec_january_power"] = diff["dec_2"] - diff["price_off_peak_fix"]
    diff = diff[
        ["id", "offpeak_diff_dec_january_energy", "offpeak_diff_dec_january_power"]
    ]

    return diff

def merging_datasets(df: pd.DataFrame, df_1: pd.DataFrame) -> pd.DataFrame:

    new_df = df.merge(df_1, how="left", left_on="id", right_on="id")

    return new_df

# A function to create the new feature "price_change"
def price_change(x: float) -> str:
    if x > 0:
        return "increase"
    elif x < 0:
        return "decrease"
    else:
        return "stable"

def time_features(df: pd.DataFrame) -> pd.DataFrame:
    # Getting the activation year
    df["activ_year"] = df["date_activ"].dt.year

    # Getting the activation month
    df["activ_month"] = df["date_activ"].dt.month

    # Getting the ending year
    df["end_year"] = df["date_end"].dt.year

    # Getting the ending month
    df["end_month"] = df["date_end"].dt.month

    # Getting the year of the last modification of the product
    df["modif_prod_year"] = df["date_modif_prod"].dt.year

    # Getting the month of the last modification of the product
    df["modif_prod_month"] = df["date_modif_prod"].dt.month

    # Getting the renewal year
    df["renewal_year"] = df["date_renewal"].dt.year

    # Getting the renewal month
    df["renewal_month"] = df["date_renewal"].dt.month

    # Difference between activation and renewal in days
    df["diff_act_renew"] = (df["date_renewal"] - df["date_activ"]).dt.days

    # Difference between activation and end in days
    df["diff_act_end"] = (df["date_end"] - df["date_activ"]).dt.days

    # Difference between activation and production modification.
    df["diff_act_modif"] = (df["date_modif_prod"] - df["date_activ"]).dt.days

    # Difference between end and production modification.
    df["diff_end_modif"] = (df["date_end"] - df["date_modif_prod"]).dt.days

    return df

def consum_features(df: pd.DataFrame) -> pd.DataFrame:

    # Getting the average consumption per month for the past 12 months.
    df["avrg_month_cons"] = df["cons_12m"] / 12

    # Getting the average consumption per month of gaz for the past 12 months.
    df["avrg_month_cons_gaz"] = df["cons_gas_12m"] / 12

    # Getting the average forcasted consumption per month for the next 12 months.
    df["forcast_avrg_month_cons"] = df["forecast_cons_12m"] / 12

    # Getting the ratio of the last month consumption to the last 12m consumption
    df["ratio_last_month_last12m_cons"] = df["cons_last_month"] / df["cons_12m"]

    # Getting the ratio of the last month consumption to the the average consumption per month for the past 12 months.
    df["ratio_last_month_avg_cons"] = df["cons_last_month"] / df["avrg_month_cons"]

    return df


# -*- coding: utf8 -*-
import copy
import hashlib
import coincurve

from py_ecc.secp256k1 import N as secp256k1n

from mythril.laser.ethereum.helper import ALL_BYTES, bytearray_to_int, concrete_int_to_bytes, sha3, zpad

def int_to_32bytes(i):   #used because int can't fit as bytes function's input
    o = [0] * 32
    for x in range(32):
        o[31 - x] = i & 0xff
        i >>= 8
    return bytes(o)

def ecrecover_to_pub(rawhash, v, r, s):
    try:
        pk = coincurve.PublicKey.from_signature_and_message(
            zpad(concrete_int_to_bytes(r), 32) + zpad(concrete_int_to_bytes(s), 32) +
            ALL_BYTES[v - 27],
            rawhash,
            hasher=None,
        )
        pub = pk.format(compressed=False)[1:]
    except BaseException:
        pub = b"\x00" * 64

    return pub

def extract32(data, i):
    if i >= len(data):
        return 0
    o = data[i: min(i + 32, len(data))]
    o.extend(bytearray(32 - len(o)))
    return bytearray_to_int(o)

def ecrecover(data):
    data = bytearray(data)
    message = b''.join(map(lambda x: ALL_BYTES[x], data[0:32]))
    v = extract32(data, 32)
    r = extract32(data, 64)
    s = extract32(data, 96)
    if r >= secp256k1n or s >= secp256k1n or v < 27 or v > 28:
        return []
    try:
        pub = ecrecover_to_pub(message, v, r, s)
    except Exception as e:
        return []
    o = [0] * 12 + [x for x in sha3(pub)[-20:]]
    return o

def sha256(data):
    data = bytes(data)
    return hashlib.sha256(data).digest()

def ripemd160(data):
    data = bytes(data)
    return 12*[0]+[i for i in hashlib.new('ripemd160', data).digest()]

def identity(data):
    return copy.copy(data)

def native_contracts(address, data):
    '''
    takes integer address 1, 2, 3, 4
    '''

    functions = (ecrecover, sha256, ripemd160, identity)
    return functions[address-1](data)


import json
import asyncio

from aiogram import types
from aiogram.dispatcher.storage import FSMContext
from aiogram.types import ReplyKeyboardMarkup
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
from aiogram.types.callback_query import CallbackQuery
from aiogram.utils.callback_data import CallbackData

from keyboards.inline.profile import profile_menu_callback
from keyboards.inline import profile_my_channels_kb, my_ads_posts_kb
from keyboards.inline.my_ads_posts_kb import ad_post_callback
from states.add_ad_post_state import AD_POST
from loader import dp,pgbotdb

admin_post_stats_callback = CallbackData('admin_kb', "act", 'user_id', 'page')
post_statistics_callback = CallbackData('post_stats', "act", 'user_id', 'page', 'reverse')

@dp.callback_query_handler(profile_menu_callback.filter())
async def statistics_ad(call: CallbackQuery):
    # pass
    # posts = pgbotdb.get_posts()
    text, kb = await POST_STATS().post_statistics_kb(call.from_user.id,0)
    if text== "no data": await call.answer("У вас еще нет Статистики рекламы")
    else: await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

@dp.callback_query_handler(post_statistics_callback.filter())
async def choose_category(call: CallbackQuery, callback_data: dict):
    await POST_STATS().process_selection(call, callback_data)

@dp.callback_query_handler(text='my_channels')
async def my_channels(call: CallbackQuery):
    await call.message.edit_text(text='🦋Мои канали🦋', reply_markup=profile_my_channels_kb)

@dp.callback_query_handler(text='my_ads_posts')
async def my_ads_posts(call: CallbackQuery):
    """Назад до Мои рекл. пости"""
    ad_posts = pgbotdb.select_ads_post_for_admin(call.from_user.id,imgs='ALL')
    await call.message.edit_text("Ваши пости для рекламы/Добавить заготовку", reply_markup=my_ads_posts_kb(ad_posts))

@dp.callback_query_handler(text='my_ads_posts',state=[AD_POST.name])
async def my_ads_posts(call: CallbackQuery,state: FSMContext):
    """Назад до Мои рекл. пости FSM"""
    await state.finish()
    ad_posts = pgbotdb.select_ads_post_for_admin(call.from_user.id, imgs='ALL')
    await call.message.edit_text("Ваши пости для рекламы/Добавить заготовку", reply_markup=my_ads_posts_kb(ad_posts))

@dp.callback_query_handler(text='add_new_ad_post')
async def my_ads_posts(call: CallbackQuery):
    """Добавить новый пост в базу"""
    kb = InlineKeyboardMarkup(inline_keyboard=[
    [InlineKeyboardButton('Назад',callback_data='back_to_my_profile')]])
    await call.message.edit_text(text="Введите имя для этого поста, в будущем с помощью него можно будеть найты этот пост среди других", reply_markup=kb)
    await AD_POST.name.set()

@dp.callback_query_handler(text='del_new_ad_post')
async def my_ads_posts(call: CallbackQuery):
    """kb при удалении пост из бази"""
    ad_posts = pgbotdb.select_ads_post_for_admin(admin_id=call.from_user.id, imgs='ALL')
    kb = InlineKeyboardMarkup(row_width=1)
    kb_posts = [InlineKeyboardButton(text=adpost[6], callback_data='del_ad_post:'+str(adpost[0])) for adpost in ad_posts]
    kb.add(*kb_posts)
    kb.add(InlineKeyboardButton('Назад',callback_data='back_to_my_profile'))
    await call.message.edit_text(text="Выберите заготовку для удаления", reply_markup=kb)

@dp.callback_query_handler(text_contains='del_ad_post')
async def my_ads_posts(call: CallbackQuery):
    """Удалить пост из бази"""

    id_post = call.data.replace('del_ad_post:', '')
    pgbotdb.delete_ads_post_for_admin_by_id(post_id=id_post)
    await call.message.answer('Успешно удалено ✅')

@dp.callback_query_handler(ad_post_callback.filter())
async def ad_post_callback(call: CallbackQuery, callback_data: dict):
    """Отправка поста юзеру"""
    post_info = pgbotdb.select_ad_post_from_db_where_increment(callback_data['increment'])
    print('here')
    await send_post(call.from_user.id,post_info[2],post_info[5],post_info[3])
    await asyncio.sleep(1)
    kb = InlineKeyboardMarkup(row_width=1,
        inline_keyboard=[
            [InlineKeyboardButton(text="Назад", callback_data='my_ads_posts')]
            ])
    await call.message.answer(
        text='Назад к постам',
        reply_markup=kb)

@dp.callback_query_handler(text='change_role')
async def my_channels(call: CallbackQuery):
    role = 'Рекламодатель' if pgbotdb.user_is_admin(call.from_user.id) else 'Админ'
    if pgbotdb.user_is_admin(call.from_user.id):
        pgbotdb.upadte_user_role(call.from_user.id,role='advertiser')
    else: pgbotdb.upadte_user_role(call.from_user.id,role='admin')
    print('new_role', role)
    await call.answer(f'Вы сменили свою роль на {role}')

@dp.callback_query_handler(text='zajavka_in_progress')
async def posts_in_progres(call: CallbackQuery):
    print("progess")
    text, kb = await ADMIN_ADS().post_kb(call.from_user.id,0)
    if text== "no data": await call.answer("У вас еще нет Заявок на рекламу в ваших каналу (ах)")
    else: await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

@dp.callback_query_handler(admin_post_stats_callback.filter())
async def choose_category(call: CallbackQuery, callback_data: dict):
    await ADMIN_ADS().kb_logic_change(call, callback_data)

async def send_post(channel_id,text,kb,file_id)->str:
    """Отправка поста"""
    msg = ''
    if file_id == '0':
        if kb=='-':
            msg = await dp.bot.send_message(chat_id=channel_id, text=text)
        else:
            msg = await dp.bot.send_message(chat_id=channel_id, text=text,reply_markup=post_kb(json.loads(kb)))
        return msg
    else:
        if kb=='-':
            msg = await dp.bot.send_photo(channel_id, file_id, text)
        else:
            msg = await dp.bot.send_photo(channel_id, file_id, text,reply_markup=post_kb(json.loads(kb)))
        return msg

def post_kb(kb):
    """Работа с клавиатурой"""
    keb = InlineKeyboardMarkup(row_width=1)
    print(kb)
    if '=' in kb[0]:
        btns = (InlineKeyboardButton(text=k.split('=')[0].strip(' '), url=k.split('=')[1].strip(' ')) for k in kb)
    else:
        btns = (InlineKeyboardButton(text=text, url=url) for text,url in kb)
    keb.add(*btns)
    print(keb)
    return keb

class ADMIN_ADS:

    async def post_kb(
        self,
        user_id,
        page:int=0,
        reverse: str = "False",
    ) -> ReplyKeyboardMarkup:
        up = '▲'
        down = '▼'
        stat_icon = down if reverse=='False' else up
        kb=InlineKeyboardMarkup(row_width=1)
        btns = [
            InlineKeyboardButton(text='По дате'+ stat_icon, callback_data=admin_post_stats_callback.new("SORT",user_id, 1)),
            ]
        kb.row(*btns)
        page, info_text = get_post_info_admin(user_id,page)
        if page=='no data':
            return page, ''
        # info_text = "OST_INFO"

        # берем по статистике ERR канали, отбираем первый 10 x штук и добавляем в клаву

        if page != 0:
            kb.row(InlineKeyboardButton(text='<<<', callback_data=admin_post_stats_callback.new("GO_BACK",user_id, page)),
        InlineKeyboardButton(text='^  ^', callback_data=admin_post_stats_callback.new('OPEN_UP', user_id, page)),
        InlineKeyboardButton(text='>>>', callback_data=admin_post_stats_callback.new("GO_FORTH",user_id, page)),)
        elif page == 0: kb.row(InlineKeyboardButton(text='Назад', callback_data='back_to_my_profile'),
        InlineKeyboardButton(text='^  ^', callback_data=admin_post_stats_callback.new('OPEN_UP', user_id, page)),
        InlineKeyboardButton(text='>>>', callback_data=admin_post_stats_callback.new("GO_FORTH",user_id, page)),)
        return info_text, kb

    async def kb_logic_change(self, call: CallbackQuery, data: CallbackData) -> tuple:
        """
        Process the callback_query. This method generates new kb for channels
        """
        print('IN PORCESSING')

        # user picked a day button, return date
        if data['act'] == "GO_FORTH":

            page = data["page"]
            user_id = data["user_id"]

            text, kb = await self.post_kb(user_id,int(page)+1)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

        if data['act'] == "GO_BACK":

            page = data["page"]
            user_id = data["user_id"]

            text, kb = await self.post_kb(call.from_user.id,int(page)-1)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

        if data['act'] == "SORT":
            await call.answer('Будет скоро...')

        if data['act'] == 'IGNORE':
            await call.answer(cache_time=60)

class POST_STATS:

    async def post_statistics_kb(
        self,
        user_id,
        page:int=0,
        reverse: str = "False",
    ):
        up = '▲'
        down = '▼'
        stat_icon = down if reverse=='False' else up
        kb=InlineKeyboardMarkup(row_width=1)
        btns = [
            InlineKeyboardButton(text='По дате'+ stat_icon, callback_data=post_statistics_callback.new("SORT",user_id, 1, reverse)),
            ]
        kb.row(*btns)
        page, info_text = get_post_info(user_id,page,reverse)
        if page=='no data':
            return page, ''
        # info_text = "OST_INFO"

        # берем по статистике ERR канали, отбираем первый 10 x штук и добавляем в клаву

        if page != 0:
            kb.row(InlineKeyboardButton(text='<<<', callback_data=post_statistics_callback.new("GO_BACK",user_id, page,reverse)),
        InlineKeyboardButton(text='^  ^', callback_data=post_statistics_callback.new('OPEN_UP', user_id, page,reverse)),
        InlineKeyboardButton(text='>>>', callback_data=post_statistics_callback.new("GO_FORTH",user_id, page,reverse)),)
        elif page == 0: kb.row(InlineKeyboardButton(text='Назад', callback_data='back_to_my_profile'),
        InlineKeyboardButton(text='^  ^', callback_data=post_statistics_callback.new('OPEN_UP', user_id, page,reverse)),
        InlineKeyboardButton(text='>>>', callback_data=post_statistics_callback.new("GO_FORTH",user_id, page,reverse)),)
        return info_text, kb

    async def process_selection(self, call: CallbackQuery, data: CallbackData) -> tuple:
        """
        Process the callback_query. This method generates new kb for channels
        """
        print('IN PORCESSING')

        # user picked a day button, return date
        if data['act'] == "GO_FORTH":
            rev = data['reverse']
            page = data["page"]
            user_id = data["user_id"]

            text, kb = await self.post_statistics_kb(user_id,int(page)+1, rev)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

            # if rev=='True':
            #     sorted_list.reverse()
            # await call.message.edit_reply_markup(await self.post_statistics_kb(
            # channel_statistics=sorted_list,
            # sort_by = data['sort_by'],
            # page = int(data['page'])+1,
            # reverse=rev
            # ))

        if data['act'] == "GO_BACK":
            rev = data['reverse']
            page = data["page"]
            user_id = data["user_id"]

            text, kb = await POST_STATS().post_statistics_kb(call.from_user.id,int(page)-1, rev)
            await call.message.edit_text(text, reply_markup=kb, disable_web_page_preview=True)

        """TO DO""" # сортировка по дате
        if data['act'] == "SORT":
            await call.answer('Будет скоро...')

        if data['act'] == 'IGNORE':
            await call.answer(cache_time=60)

from handlers.payments.btc_banker import is_number
def get_post_info(user_id,page,reverse):
    page, post_info = pgbotdb.posts_ads_statistics(user_id,page)
    if page=='no data':
        return page, ''
    print(post_info)
    status = post_info[10]
    msg_id = post_info[11]
    chan_id = post_info[1]
    print(page, 'pageeeeeeeeeeeeeee')
    views_count='-'

    if status == 'False'or status == False:
        cpv = 'Вы не оплатили пост, у вас есть максимум 5 минут чтобы это сделать!'
    elif status == 'PAID':
        cpv = 'Дождытесь окончание поста'
    elif status == 'SUSPANDED':
        cpv = 'Бот постер был удален админом или канал заблокирован, пишите @ad_manager, решим проблему'
    elif status == 'UNKNOWN':
        cpv = 'Незвестрая ошибка, пишите админу, исправим!!! @adx_admin @adx_manager'
    elif status == 'SENT':
        cpv = 'Пост отправлен в канал, просмотри идут'
    elif status == 'DONE':
        views_count = pgbotdb.posts_stats_views(msg_id,chan_id)
        try:
            cpv = post_info[6]/views_count*1000 if is_number(views_count) else 'Дождытесь окончание поста'
        except ZeroDivisionError: cpv = "∞"
    chann_info = pgbotdb.select_channel(chan_id)
    text=f"""Реклама в канале {chann_info[3]}
Дата публикации: {post_info[7]}
Дата завершения: {post_info[8]}
по МСК
Количество просмотров: {views_count}
Цена: {post_info[6]}
CPV(Цена за 1к просм.): {cpv}
Статус: {status}
"""
    return page, text

def get_post_info_admin(user_id,page):
    admin_channels = pgbotdb.select_channels(user_id)
    channels= []
    for i in admin_channels:
        channels.append(i)
    page, post_info = pgbotdb.posts_admin_ads_statistics(channels,page)
    if page=='no data':
        return page, ''
    msg_id = post_info[-2]
    chan_id = post_info[1]

    views_count = "Пост в процессе публикации" if msg_id == None or msg_id == '-' else pgbotdb.posts_stats_views(msg_id,chan_id)
    chann_info = pgbotdb.select_channel(chan_id)

    cpv = post_info[6]/views_count*1000 if is_number(views_count) else 'Дождытесь окончание поста'
    text=f"""Реклама в вашем канале {chann_info[3]}
Дата публикации: {post_info[7]}
Дата завершения: {post_info[8]}
по МСК
Количество просмотров: {views_count}
Получено: {post_info[6]}
CPV(Цена за 1к просм.): {cpv}
"""
    return page, text


import tensorflow as tf

weights = tf.constant([[1.0, 2.0], [-3.0, 4.0]], name="const_mat", dtype=tf.float32)

# 返回一个函数，这个函数可以计算一个给定权重的L1正则化项的值
regularizer = tf.contrib.layers.l1_regularizer(0.5)

# 计算权重为0.5时L1正则化的取值
y = regularizer(weights)

with tf.Session() as sess:
    print(sess.run(y))


import turtle
import pandas as pd

#setting up the screen
screen = turtle.Screen()
screen.setup(width=655, height=712)
screen.title("Animal Guessing Game")
image = "animals.gif"
screen.addshape(image)
turtle.shape(image)

#reading the csv file that contains the animals name and x,y coordinate
data = pd.read_csv("animals_name.csv")
animal_name = data.name.to_list()

#the game
guessed_animals = []
while len(guessed_animals) < 46:
    answer_animal = screen.textinput(title=f"{len(guessed_animals)}/46 Correct Answer",
                                     prompt="What's another animal's name?").title()
    if answer_animal == "Exit":
        break
    if answer_animal in animal_name:
        guessed_animals.append(answer_animal)
        t = turtle.Turtle()
        t.hideturtle()
        t.penup()
        animal_data = data[data.name == answer_animal]
        t.goto(int(animal_data.x), int(animal_data.y))
        t.pencolor("grey")
        t.write(answer_animal, font=("Arial", 14, "normal"))


# Didn't end up using this but considered saving a logfile of the fits that are already complete

import os
import sys
import numpy as np
from spectrum import Spectrum

def read_last_line(filename):
    if os.path.isfile(filename):
        with open(filename, 'r') as f:
            lines = f.read().splitlines()
            last_line = lines[-1]
        return int(last_line) +1
    else:
        return 0

def add_i_to_file(filename, i):
    with open(filename, 'a') as f:
        f.write(f'{i}\n')

if __name__ == '__main__':

    model = sys.argv[1]
    wind = sys.argv[2]
    snap = sys.argv[3]

    logfile = f'/disk04/sapple/data/normal/{model}_{wind}_{snap}/logfile.txt'
    i = read_last_line(logfile)
    add_i_to_file(logfile, i)

    vel_range = 600.
    chisq_asym_thresh = -3.

    spec_dir = f'/disk04/sapple/data/normal/{model}_{wind}_{snap}/'
    try:
        spec_file = sorted(os.listdir(spec_dir))[1:][i]
    except IndexError:
        os.mknod(f'{spec_dir}done.txt')
        sys.exit()

    print(spec_file)

    spec = Spectrum(f'{spec_dir}{spec_file}')
    if hasattr(spec, 'line_list'):
        sys.exit()
    else:
        spec.main(vel_range=vel_range, chisq_asym_thresh=-3., write_lines=True)


import numpy as np
from braphy.utility.stat_functions import StatFunctions as stat
from braphy.utility.helper_functions import get_subject_class
from braphy.utility.permutation import Permutation
import statistics

class Group:
    def __init__(self, subject_class, subjects = None, name = 'Group', description = '-'):
        self.name = name
        self.subject_class = subject_class
        self.description = description
        if not subjects:
            subjects = []
        self.subjects = subjects

    def to_dict(self, subjects):
        d = {}
        d['name'] = self.name
        d['subject_class'] = self.subject_class.__name__
        d['description'] = self.description
        d['subjects'] = []
        for subject in self.subjects:
            match = -1
            for i, other_subject in enumerate(subjects):
                if subject.equals(other_subject):
                    match = i
                    break
            assert match >= 0, 'Subject {} in group {} is not matched in subject list.'.format(subject.id, self.name)
            d['subjects'].append(match)
        return d

    def from_dict(d, subjects):
        subject_class = get_subject_class(d['subject_class'])
        subjects_in_group = [subjects[index] for index in d['subjects']]
        return Group(subject_class, subjects=subjects_in_group, name=d['name'], description=d['description'])

    def add_subject(self, subject):
        self.subjects.append(subject)

    def add_subjects(self, subjects):
        self.subjects.extend(subjects)

    def remove_subject(self, subjects):
        self.subjects.remove(subjects)

    def remove_subjects(self, subjects):
        for subject in subjects:
            self.subjects.remove(subject)

    def averages(self):
        values = []
        for subject in self.subjects:
            values.append(subject.data_dict['data'].value)
        values = np.array(values)
        if values.size == 0:
            return []
        return values.mean(0)

    def standard_deviations(self):
        values = []
        for subject in self.subjects:
            values.append(subject.data_dict['data'].value)
        values = np.array(values)
        if values.size == 0:
            return []
        return values.std(0)

    def data_comparison(self, other, permutations = 1000):
        assert isinstance(other, Group), "{} is not a group".format(other.__class__.__name__)
        assert self.subject_class == other.subject_class, "{} and {} are different subject classes".format(self.subject_class.__name__, other.subject_class.__name__)
        assert len(self.subjects) > 0, "{} has no subjects".format(self.name)
        assert len(other.subjects) > 0, "{} has no subjects".format(other.name)

        values_self = np.array([subject.data_dict['data'].value for subject in self.subjects])
        values_other = np.array([subject.data_dict['data'].value for subject in other.subjects])

        subject_length_self = np.shape(values_self)[0]
        data_length_self = np.shape(values_self)[1]
        data_length_other = np.shape(values_other)[1]
        assert data_length_self == data_length_other, "Subject data length does not match: {} and {}".format(data_length_self, data_length_other)

        std_self = values_self.std(0)
        std_other = values_other.std(0)
        mean_self = values_self.mean(0)
        mean_other = values_other.mean(0)

        values_all = np.concatenate((values_self, values_other))

        diffs = []
        for _ in range(permutations):
            p1, p2 = Permutation.permute(values_self, values_other, False)
            diffs.append(p1.mean(0) - p2.mean(0))
        diffs = np.array(diffs)
        p_value_single = stat.p_value(mean_self, diffs, True)
        p_value_double = stat.p_value(mean_self, diffs, False)

        averages = [mean_self, mean_other]
        stds = [std_self, std_other]
        p_values = [p_value_single, p_value_double]

        return averages, stds, p_values

    def correlation(self, correlation_type):
        return self.subject_class.correlation(self.subjects, correlation_type)


'''
  Write a function can_construct(target, strs) that accepts a target string and an array of strings.
  The function should return a boolean indicating whether or not the target can be
  constructed by concatenating elements of the strs array.
  You may use elements in strs as many times as needed.
  Example:
    strs = ["ab", "abc", "cd", "def", "abcd"], target = "abcdef" -> true "abcde" can be constructed by "abc" + "def"
    strs = ["ab", "abc", "cd", "def", "abcd"] target = "abcde" -> false
'''
def solution1(target, strs):
  if target == '': return True
  for s in strs:
    if target.startswith(s):
      sub = target[len(s):]
      if solution1(sub, strs): return True
  return False

def solution2(target, strs, memo = None):
  if memo is None: memo = {}
  if target in memo: return memo[target]
  if target == '': return True
  for s in strs:
    if target.startswith(s):
      sub = target[len(s):]
      if solution2(sub, strs, memo):
        memo[target] = True
        return True
  memo[target] = False
  return False

def solution3(target, strs):
  test_arr = [False] * (len(target) + 1)
  test_arr[0] = True

  for i in range(len(target)):
    if test_arr[i]:
      for s in strs:
        if target[i:i+len(s)] == s and i + len(s) <= len(target):
          test_arr[i+len(s)] = True
  return test_arr[len(target)]


# SW Expert Academy
# 5248. [파이썬 S/W 문제해결 구현] 6일차 - 그룹 나누기 D3
# https://swexpertacademy.com/main/learn/course/subjectDetail.do?courseId=AVuPDYSqAAbw5UW6&subjectId=AWUYG3y62EcDFAVT

# Union Find 문제

import sys
sys.stdin = open("../sample_input.txt", "r")

def get_parent(x):
    if parent[x] != x: parent[x] = get_parent(parent[x])
    return parent[x] # 부모 노드 넣기

def union_parent(x, y):
    a = get_parent(x)
    b = get_parent(y)
    if a > b: parent[a] = b
    else: parent[b] = a

for test_case in range(1, int(input()) + 1):
    N,M =  map(int, input().split())
    parent = [i for i in range(N+1)]
    group = list(map(int, input().split()))
    for i in range(0, M*2, 2):
        union_parent(group[i],group[i+1])
    answer = set()
    for i in parent:
        answer.add(get_parent(i))
    print("#"+str(test_case),len(answer)-1)


# Определить индексы элементов массива (списка), значения
# которых принадлежат заданному диапазону (т.е. не меньше
# заданного минимума и не больше заданного максимума)

a = list(map(int, input().split())) # =[int(i) for i in input().split()]
min = int(input())
max = int(input())
b = []
for i in range(len(a)):
    if a[i] >= min and a[i] <= max:
        b.append(i)
print(b)

# print([ind for ind, val in enumerate(a) if min <= val <= max])
# enumerate собирает кортежи из элементов(val) и их индексов(ind).
# Мы выводим только индексы(ind) элементов, соответствующих условию.


import math

num_list = [True] * (123456 * 2 + 2)
for i in range(1, 123456 * 2 + 1):
    tmp = 0
    if i > 1:
        for j in range(2, int(math.sqrt(i)) + 1):
            if i % j == 0:
                num_list[i] = False
                break

while True:
    N = int(input())

    if N == 0:
        break

    print(sum(num_list[N + 1:2*N +1]))


# setup two lists to store the 'name' and 'birthday' from the txt file.
name = []
birthday = []

# open the file using 'with/as'
with open("./DOB.txt", "r") as f:

    # loop through every lines from the file
    for line in f:
        # trim all newline to make the result tidy and split the line into each words
        person = line.replace('\n', '').split(" ")

        # join the first two words (the full name), and append it into 'name'
        name.append(" ".join(person[0:2]))

        # join the last three words (the birthday), and append it into 'birthday'
        birthday.append(" ".join(person[2:5]))

    # loop through 'name', and print the contents
    print("Name")
    for i in range(0, len(name)):
        print(name[i])

    # loop through 'birthday', and print the contents
    print("Birthday")
    for i in range(0, len(birthday)):
        print(birthday[i])


from sqlite3 import connect

class Yui_Database():
    """
    Database of Yui Chat bot
        This database won't be used if you're hosting this on heroku
    """

    def __init__(self) -> None:
        self.yui_db = connect("yui-x.db")
        self.curs = self.yui_db.cursor()
        # Creates a table for storing ai engine details
        self.curs.execute(
            """
            CREATE TABLE
            IF NOT EXISTS
            engine (engine_name text)
            """
        )

    async def set_engine(self, engine_name):
        is_exists = await self.get_engine()
        if not is_exists:
            self.curs.execute(
                """
                INSERT INTO engine(engine_name)
                VALUES (?)
                """, (engine_name,)
            )
        else:
            self.curs.execute(
                """
                UPDATE engine
                SET engine_name=?
                """, (engine_name,)
            )
        return self.yui_db.commit()

    async def get_engine(self):
        selct = self.curs.execute(
            """
            SELECT engine_name
            FROM engine
            """
        ).fetchone()
        if selct:
            return selct[0]
        else:
            return None


def main(n):

    global finishers
    global zones

    total = 21
    finishers = [0] * 21
    zones = [0] * 62
    for i in range(20):
        finishers[i] = (i+1)*2
    finishers[-1] = 50

    for i in range(3):
        for j in range(20):
            zones[i*20 + j] = (j + 1) * (i + 1)
    zones[-2], zones[-1] = 25, 50

    total += two_darts(n)
    total += three_darts(n)

    return total

def two_darts(n):
    t = 0
    for i in range(62):
        for f in range(21):
            check = zones[i] + finishers[f]
            if check < n:
                t += 1
    return t

def three_darts(n):
    t = 0
    for i in range(62):
        for j in range(i, 62):
            for f in range(21):
                check = zones[i] + zones[j] + finishers[f]
                if check < n:
                    t += 1
    return t

if __name__ == '__main__':
    print(main(100))

'''
possible_throws = sorted(range(20+1) + [2*n for n in range(1, 20+1)] + [3*n for n in range(1, 20+1)] + [25, 50])
count = 0
possible_doubles = [2*n for n in range(1, 20+1)] + [50]
for i in range(len(possible_throws)):
    for j in range(i+1):
        for dart3 in possible_doubles:
            if possible_throws[i] + possible_throws[j] + dart3 < 100:
                count += 1

print count
'''


from collections import defaultdict

def solution(s):
    temp = [a.split(',') for a in s[2:-2].split("},{")]
    elements = defaultdict(int)
    for t in temp:
        for x in t:
            elements[x] += 1
    return list(map(int, sorted(elements, key=lambda k: elements[k], reverse=True)))


# 比较相邻的元素。如果第一个比第二个大，就交换它们两个；
# 对每一对相邻元素作同样的工作，从开始第一对到结尾的最后一对，这样在最后的元素应该会是最大的数；
# 针对所有的元素重复以上的步骤，除了最后一个；
# 重复步骤1~3，直到排序完成

def BubbleSort(lst):

    if len(lst) <= 1:
        return lst

    for i in range(len(lst)):
        for j in range(len(lst) - 1):
            if lst[j] > lst[j + 1]:
                (lst[j], lst[j + 1]) = (lst[j + 1], lst[j])  # 其实就是一轮轮把最大的元素排到最后

    return lst

input = [2, 1, 3, 4, 1]
output = BubbleSort(input)

print(output)


import os
from unittest import TestCase
from gwent import *
from gwent.data.card_db import CardDB
from gwent.cards import *

class TestScorchUnitCard(TestCase):

    def setUp(self):
        self.unitCard1 = UnitCard("1", "dummy1", "Monster", 0, 11, False, False)
        self.unitHero = UnitCard("2", "dummy2", "Monster", 0, 10, True, True)
        self.unitCard2 = UnitCard("5", "dummy5", "Monster", 0, 7, False, False)
        self.med1 = MedicCard("3", "med1", "Northern", 0, 4, False, False)
        self.scorchio = ScorchUnitCard("4", "dummy4", "Monster", 0, 10, False, False)
        self.unitCard3 = UnitCard("6", "dummy6", "Monster", 0, 11, False, False)

        test_decks = "data/test_decks.csv"
        parent_dir = os.path.dirname(__file__)
        file_name = "./../../../../gwent/data/card_data.csv"
        faction = "Nilfgaardian"
        size = 22
        seed = 123

        self.card_db = CardDB(os.path.join(parent_dir, file_name))
        self.deck = Deck(self.card_db, faction, size, seed)
        monster, nilf, northern, scoiatael = self.deck.create_deck_using_list(test_decks)
        self.player1 = Player("p1", "Nilfgaardian", nilf)
        self.board1 = Board(self.player1)
        self.player2 = Player("p2", "Monster", monster)
        self.board2 = Board(self.player2)

    def test_battlecry(self):
        self.board2.rows[0].append(self.unitCard1)
        self.board2.rows[0].append(self.unitCard2)
        self.assertTrue(len(self.board2.rows[0]), 2)

        self.scorchio.place_card(self.board1, self.board2, self.scorchio.row, None)
        self.assertTrue(len(self.board1.rows[0]), 1)
        self.assertTrue(len(self.board2.rows[0]), 1)
        self.assertTrue(self.player2.graveyard.__contains__(self.unitCard1))
        self.assertFalse(self.player2.graveyard.__contains__(self.unitCard2))

    def test_against_hero(self):
        self.board2.rows[0].append(self.unitHero)
        self.board2.rows[0].append(self.unitCard2)
        self.assertTrue(len(self.board2.rows[0]), 2)

        self.scorchio.place_card(self.board1, self.board2, self.scorchio.row, None)
        self.assertTrue(len(self.board1.rows[0]), 1)
        self.assertTrue(len(self.board2.rows[0]), 1)
        self.assertTrue(self.player2.graveyard.__contains__(self.unitCard2))
        self.assertFalse(self.player2.graveyard.__contains__(self.unitHero))
        self.assertTrue(self.board2.rows[0].__contains__(self.unitHero))

    def test_medic(self):
        self.board2.rows[0].append(self.unitCard1)
        self.board2.rows[0].append(self.unitCard2)
        self.board2.rows[0].append(self.unitCard3)
        self.assertTrue(len(self.board2.rows[0]), 3)

        self.player1.graveyard.append(self.scorchio)
        self.med1.place_card(self.board1, self.board2, self.med1.row, self.scorchio)
        self.assertTrue(len(self.board1.rows[0]), 2)
        self.assertTrue(len(self.board2.rows[0]), 1)
        self.assertTrue(self.player2.graveyard.__contains__(self.unitCard1))
        self.assertTrue(self.player2.graveyard.__contains__(self.unitCard3))
        self.assertFalse(self.player2.graveyard.__contains__(self.unitCard2))

        self.assertTrue(self.board2.rows[0].__contains__(self.unitCard2))

    def test_scorch_row(self):
        self.unitCard3 = UnitCard("5", "dummy5", "Monster", 0, 11, False, False)

        self.board2.rows[0].append(self.unitCard1)
        self.board2.rows[0].append(self.unitCard2)
        self.board2.rows[0].append(self.unitCard3)

        self.scorchio.scorch_row(self.board2, self.board2.rows[0], 11)

        self.assertEqual(len(self.board2.rows[0]), 1)
        self.assertTrue(self.board2.rows[0].__contains__(self.unitCard2))
        self.assertFalse(self.board2.rows[0].__contains__(self.unitCard1))
        self.assertFalse(self.board2.rows[0].__contains__(self.unitCard3))
        self.assertTrue(self.player2.graveyard.__contains__(self.unitCard1))
        self.assertTrue(self.player2.graveyard.__contains__(self.unitCard3))

    def test_weather_combo(self):
        frost = WeatherCard("11", "dummyWeather", "Neutral", 0, -1, False, False)

        self.board2.rows[0].append(self.unitHero)
        self.board2.rows[0].append(self.unitCard1)
        self.board2.rows[0].append(self.unitCard2)
        self.board2.rows[0].append(self.unitCard3)

        self.assertTrue(self.board2.rows[0].__contains__(self.unitCard2))

        frost.place_card(self.board1, self.board2, 0, None)

        self.scorchio.place_card(self.board1, self.board2, self.scorchio.row, None)

        self.assertTrue(self.board2.rows[0].__contains__(self.unitHero))
        self.assertFalse(self.board2.rows[0].__contains__(self.unitCard1))
        self.assertFalse(self.board2.rows[0].__contains__(self.unitCard2))
        self.assertFalse(self.board2.rows[0].__contains__(self.unitCard3))


import pygame,pygame.mixer
from copy import deepcopy
from game_edit import edit,get_tuple,get_bool
from random import random
from math import sqrt,hypot,pi

def load_config():
    """ Permite obtener datos de el archivo config
    """
    global Width,Height,color_palabras,color_particulas1,color_particulas2,FPS,dt,color_fondo,Kv_max,Kv_min,Kr_max,Kr_min,Kd,n,merge,bound,color_medio,Max_Points
    Width = get_tuple("Data\config.txt",0)[0]
    Height = get_tuple("Data\config.txt",0)[1]
    FPS = get_tuple("Data\config.txt",1)[0]
    dt = get_tuple("Data\config.txt",2)[0]
    color_particulas1 = get_tuple("Data\config.txt",3)
    color_particulas2 = get_tuple("Data\config.txt",4)
    color_palabras = get_tuple("Data\config.txt",5)
    color_fondo = get_tuple("Data\config.txt",6)
    Kv_max = get_tuple("Data\config.txt",7)[0]
    Kv_min = get_tuple("Data\config.txt",8)[0]
    Kr_max = get_tuple("Data\config.txt",9)[0]
    Kr_min = get_tuple("Data\config.txt",10)[0]
    Kd = get_tuple("Data\config.txt",11)[0]
    n = get_tuple("Data\config.txt",12)[0]
    merge = get_bool("Data\config.txt",13)
    bound = get_bool("Data\config.txt",14)
    Max_Points = get_tuple("Data\config.txt",15)[0]
    u = []
    for i in range(3):
        u.append(int((color_particulas1[i]+color_particulas2[i])//2))
    color_medio = tuple(u)
    print("\n**Datos cargados exitosamente**")

def out(pos,r):
    """ Recibe una posicion
    y un radio para determinar si un circulo esta afuera del cuadrado
    """
    if pos[0]-r > L or pos[1]-r > L:
        return True
    if pos[0]+r < 0 or pos[1]+r < 0:
        return True
    return False

def to_display(pos,L):
    """ Recibe una posicion y el largo de un cuadrado(logico) y retorna la posicion en donde se tiene que renderizar
    """
    return [int(Margen_x +(pos[0]*Largo_caja/L)),int(Margen_y+(pos[1]*Largo_caja/L))]
def to_logic(pos,L):
    """ Recive una posicion de el display y lo pasa a un punto del cuadrado(logico)
    """
    return [((L*(pos[0]-Margen_x))/Largo_caja),((L*(pos[1]-Margen_y))/Largo_caja)]

def norm_coef(coef):
    """ Acota el coeficiente de restitucion, para que no sean colisiones super elasticas
    """
    if coef > 1:
        return 1
    elif coef < 0:
        return 0
    return coef

def general_d(pos1,pos2):
    """ Distancia entre dos puntos
    """
    u = 0
    for i in range(2):
        u+= (pos1[i] - pos2[i])**2
    return sqrt(u)

def d(P1,P2):
    """ Distancia entre dos particulas
    """
    return general_d(P1.pos,P2.pos)

def atraccion(P1,P2,G):
    """ Atraccion gravitatoria entre dos particulas con una constante G
    retorna aceleracion que experimenta P1
    """
    for i in range(2):
        delta = P2.pos[i] - P1.pos[i]
        P1.a[i] += (P2.m*G*delta)/(r**3)# r = d(P1,P2), ya calculado (para reducir cantidad de calculos)

def merge_particulas(P1,P2,particulas):
    """ Permite que dos particulas se unan en una
    """
    P3_color = []
    for i in range(3):
        P3_col = (P1.color[i] + P2.color[i])//2
        P3_color.append(int(P3_col))
    if P1.m**2/P1.r < P2.m**2/P2.r:
        P1,P2 = P2,P1
    P1d = ((P1.m/P1.r))
    P1.coef = (P1.coef + P2.coef)/2
    for i in range(2):
        P1.v[i] = ((P1.v[i]*P1.m) + (P2.v[i]*P2.m)) / (P1.m + P2.m)
    P1.m += P2.m
    P1.r = P1.m/P1d
    P1.color = P3_color
    particulas.remove(P2)

def gravedad_caja(P,g):
    """ Gravedad de la caja sobre una particula
    """
    for i in range(2):
        P.a[i] += g[i]

def colisionar(P1,P2):
    """ Colision entre dos particulas, puede ser inelastica o elastica
    y ademas evita que las particulas se queden pegadas
    """
    C = (P1.coef + P2.coef) / 2
    E = r-(P1.r + P2.r)# r = d(P1,P2), ya calculado
    for i in range(2):
        vf1 = (C*P2.m*(P2.v[i]-P1.v[i]) + P1.m * P1.v[i] + P2.m * P2.v[i]) / (P1.m + P2.m)
        P2.v[i] += (P1.m/P2.m) * (P1.v[i]-vf1)
        P1.v[i] = vf1
        collision_pos = ((P1.pos[i] * P2.r) + (P2.pos[i] * P1.r)) / (P2.r + P1.r)
        error = E * (P1.pos[i] - collision_pos)/(P1.r) # para evitar que queden atrapados juntos
        if P1.m < P2.m:
            P1.pos[i] -= 2*error
        else:
            P2.pos[i] += 2*error

def toggle(boolean):
    """ invierte boleano
    """
    if boolean:
        return False
    else:
        return True

def neg():
    """ permite que la direccion sea aleatoria
    """
    u = random()
    if u < 0.5:
        return -1
    return 1

def message_to_screen(msg,color,x,y):
    """ Recibe un mensaje con un color y lo pone en la pantalla en una posicion indicada
    """
    screen_text = font.render(msg, True, color)
    display.blit(screen_text, (x,y))

def pause():
    """ Pausa simulacion
    Sonido Incluido
    """
    message_to_screen("Pausa :3",color_palabras,Width//2,int((Height//2)+Ly))
    pygame.display.update()
    sound = pygame.mixer.Sound("Data\smb_pause.wav")
    pygame.mixer.music.pause()
    pygame.mixer.Sound.play(sound)
    pause = True
    while pause:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                simulacion = False
                pygame.quit()
                quit()
            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_p:
                    pause = False
                    pygame.mixer.music.unpause()

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

class Particula:
    """ Particula que tiene como atributos
    posicion, Velocidad, Aceleracion, Masa, Radio, Color, Coeficiente de restitucion y
    Cola

    Se puede saber si una posicion esta dentro de ella,
    Puede rebotar si se le da una pared,
    Se desplaza y acelera
    Puede colisionar y unirse con otra particula
    Es atraida a todas las particulas
    y por ultimo puede tener una propia cola
    """
    def __init__(self,pos,velocidad,masa,radio,coef,color):
        self.pos = pos
        self.v = velocidad
        self.m = masa
        self.r = abs(radio)
        self.coef = norm_coef(coef)
        self.color = color
        self.cola = [to_display(deepcopy(self.pos),L)]

    def adentro(self,pos):
        """ Determina si una posicion esta adentro
        """
        u = 0
        for i in range(2):
            u += (self.pos[i] - pos[i])**2
        if u <= self.r**2:
            return True
        return False

    def get_cola(self):
        """ Obtiene cola, con un numero maximo de puntos
        """
        nueva_parte = to_display(deepcopy(self.pos),L)
        self.cola.insert(0,nueva_parte)
        while len(self.cola) > Max_Points:
            last = self.cola[-1]
            self.cola.remove(last)

    def desplazar(self,dt):
        """ Se desplaza en intervalos dt
        """
        for i in range(2):
            self.pos[i] += self.v[i]*dt

    def acelerar(self,dt):
        """ Acelera en intervalos dt
        """
        for i in range(2):
            self.v[i] += self.a[i]*dt

    def rebotar_pared(self,L):
        """ Rebota en una pared de largo L
        y ademas devuelve la particula si se encuentra
        fuera de el contenedor, cada colision es ponderada por
        el coeficiente de restitucion
        """
        for i in range(2):
            if self.pos[i]-self.r <= 0:
                self.v[i] *= -self.coef
                d = -self.pos[i]+self.r
                self.pos[i] += d
            elif self.pos[i]+self.r >= L:
                self.v[i] *= -self.coef
                d = L-(self.pos[i]+self.r)
                self.pos[i] += d

    def comprobar_colision(self,particulas,merge):
        """ Comprueba si la particula self, colisiono con alguna
        particula diferente de ella en una lista, y dependiendo de la configuracion
        puede colisionar o unirse
        """
        for particula in particulas:
            if particula is not self:
                global r
                r = d(self,particula)
                if r <= self.r + particula.r:
                    if not merge:
                        colisionar(self,particula)
                    else:
                        merge_particulas(self,particula,particulas)

    def atraccion_neta(self,particulas,G,g):
        """ Asigna una aceleracion neta a la particula
        que depende de la atraccion gravitatoria y la gravedad de la caja
        """
        self.a = [0,0]
        gravedad_caja(self,g)
        if G != 0:
            for particula in particulas:
                if particula is not self:
                    global r
                    r = d(self,particula)
                    if r > self.r*2 + particula.r*2:
                        atraccion(self,particula,G)

class Caja:
    """ Caja que contiene consigo objetos particulas que interaccionan entre ellas y la caja.
    Tiene como atributos ademas: un largo, una aceleracion de gravedad como si se tratase de la tierra
    una constante gravitatoria, y puede controlar cosas como los tipos de colisiones, puede permitir
    que hayan colas y campos vectoriales, o que existan o no limites en la caja.

    """
    def __init__(self,L,g = [0,0],G = 0,particulas = [], merge= True, bound = False,colas = False):
        self.L = L
        self.g = g
        self.G = G
        self.particulas = particulas
        self.vector_field = False
        self.merge = merge
        self.bound = bound
        self.colas = colas

    def generar(self, n, velocidad, radio, densidad, coef):
        """ Genera n particulas de manera aleaotira dependiendo de los limites que se le dan
        """
        v_max = velocidad[0]
        v_min = velocidad[1]
        r_max = radio[0]
        r_min = radio[1]
        for i in range(n):
            pos = []
            v = []
            r = random()*(r_max-r_min)+ r_min
            m = r * densidad
            for k in range(2):
                v_eje = ((random()*(v_max-v_min))+v_min)*neg()
                pos_eje = random()*self.L
                v.append(v_eje)
                pos.append(pos_eje)
            u = Particula(pos,v,m,r,coef,color_particulas1)
            self.particulas.append(u)

    def agregar(self,pos,velocidad,masa,radio,coef,color):
        """ Agrega una particula con determinadas caracteristicas
        """
        p = Particula(pos,velocidad,masa,radio,coef,color)
        self.particulas.append(p)

    def update_logic(self,dt):
        """ Actualiza todos los parametros logicos de las particulas dentro de la caja
        """
        for particula in self.particulas:
            particula.comprobar_colision(self.particulas,self.merge)
            if self.bound:
                particula.rebotar_pared(self.L)
            particula.desplazar(dt)
            particula.atraccion_neta(self.particulas,self.G,self.g)
            particula.acelerar(dt)

    def select(self,pos):
        """ Retorna una particula selecionada en una posicion
        """
        for particula in self.particulas:
            if particula.adentro(pos) == True:
                return particula

    def agarrar(self,pos,dv):
        """ Permite acelerar particulas de una posicion pos con un dv
        """
        for particula in self.particulas:
            if particula.adentro(pos) == True:
                for i in range(2):
                    particula.v[i] += dv[i]

    def detener(self,pos):
        """ Detiene una particula de una posicion determinada
        """
        for particula in self.particulas:
            if particula.adentro(pos) == True:
                for i in range(2):
                    particula.v[i] = 0

    def eliminar(self,pos):
        """ Elimina una particula en una determinada posicion
        """
        for particula in self.particulas:
            if particula.adentro(pos) == True:
                self.particulas.remove(particula)

    def probe(self,pos):
        """ Permite calcular como una particula de prueba aceleraria en un punto determinado
        """
        a = [0,0]
        try:
            for particula in self.particulas:
                r = general_d(pos,particula.pos)
                for i in range(2):
                    delta = particula.pos[i] - pos[i]
                    a[i] += (particula.m*self.G*delta)/(r**3) + self.g[i]
            return a
        except ZeroDivisionError:
            return "infinte","infinte"

    def cambiar_coef(self,d):
        """ Permite cambiar el coeficiente de restitucion de todas las particulas
        """
        for particula in self.particulas:
            particula.coef += d
            if particula.coef > 1:
                particula.coef = 1
            elif particula.coef < 0:
                particula.coef = 0

    def rapidez_promedio(self):
        """ Retorna rapidez promedio
        """
        v = 0
        i = 0
        for particula in self.particulas:
            u = 0
            for i in range(2):
                u += particula.v[i]**2
            v+= sqrt(u)
            i += 1
        try:
            return v/i
        except:
            return 0

    def get_info(self,particula):
        """ Muestra en pantalla algunos parametros de una particula selecionada
        """
        x1 = Margen_x
        try:
            pos_plot ="Pos:"+"(" + str("{:.1e}".format(round(particula.pos[0]))) + "," + str("{:.2e}".format(particula.pos[1]))+")"
            v_plot = "V:"+"(" + str("{:.1e}".format(round(particula.v[0]))) + "," + str("{:.2e}".format(particula.v[1]))+")"
            a_plot = "A:"+"("+str("{:.1e}".format(round(particula.a[0]))) + "," + str("{:.2e}".format(particula.a[1]))+")"
            m_plot = "M:"+str("{:.1e}".format(round(particula.m)))
            r_plot = "R:"+str("{:.1e}".format(round(particula.r)))
            x2 = x1 + len(pos_plot)*Kx
            x3 = x2 + len(v_plot)*Kx
            x4 = x3 + len(a_plot)*Kx
            x5 = x4 + len(m_plot)*Kx
            message_to_screen(pos_plot,color_palabras,x1,Margen_y//10)
            message_to_screen(v_plot,color_palabras,x2,Margen_y//10)
            message_to_screen(a_plot,color_palabras,x3,Margen_y//10)
            message_to_screen(m_plot,color_palabras,x4,Margen_y//10)
            message_to_screen(r_plot,color_palabras,x5,Margen_y//10)
            p = to_display((particula.pos[0],particula.pos[1]),L)
            r = int(particula.r*Largo_caja/(self.L*4))
            pygame.draw.circle(display,color_particulas2,p,r,r//2)

        except:
            pos_plot = "Pos: --"
            v_plot = "V: --"
            a_plot = "A: --"
            m_plot = "M: --"
            r_plot = "R: --"
            x2 = x1 + len(pos_plot)*Kx
            x3 = x2 + len(v_plot)*Kx
            x4 = x3 + len(a_plot)*Kx
            x5 = x4 + len(m_plot)*Kx
            message_to_screen(pos_plot,color_palabras,x1,Margen_y//10)
            message_to_screen(v_plot,color_palabras,x2,Margen_y//10)
            message_to_screen(a_plot,color_palabras,x3,Margen_y//10)
            message_to_screen(m_plot,color_palabras,x4,Margen_y//10)
            message_to_screen(r_plot,color_palabras,x5,Margen_y//10)

    def get_vectorPoints(self):
        """ Muestra en pantalla un campo vectorial de la suma neta de las fuerzas
        """
        if (self.G != 0 and self.vector_field) or (self.g !=[0,0] and self.vector_field):
            r = self.L/(n*2)
            u = 4
            v = 2
            for i in range(n):
                for k in range(n):
                    point = (int(r+(2*r*i)),int(r+(2*r*k)))
                    a = self.probe(point)
                    norm_a = sqrt(a[0]**2 + a[1]**2)
                    k = norm_a/Largo_caja*10
                    if k>=0.6:
                        k=0.6
                    try:
                        delta_x = k*a[0]*r/norm_a
                        delta_y = k*a[1]*r/norm_a
                        end_point = to_display((point[0]+delta_x,point[1]+delta_y),self.L)
                        beg_point = to_display((point[0]-delta_x,point[1]-delta_y),self.L)
                        x1 = (-a[0]*u - a[1]*v*k)/norm_a + end_point[0]
                        y1 = (-a[1]*u + a[0]*v*k)/norm_a + end_point[1]
                        x2 = (-a[0]*u + a[1]*v*k)/norm_a + end_point[0]
                        y2 = (-a[1]*u - a[0]*v*k)/norm_a + end_point[1]
                        p1 = (x1,y1)
                        p2 = (x2,y2)
                        pygame.draw.line(display,(0,0,0),end_point,beg_point)
                        pygame.draw.polygon(display, (0,0,0),(end_point,p1,p2))
                    except:
                        pass

    def sim_start(self,dt,t = -1):
        """ Parte simulacion dentro de la caja
        """
        dt_inicial = deepcopy(dt)
        G_inicial = deepcopy(self.G)
        selected_particula = self.particulas[0]
        probe_pos = [self.L/2,self.L/2]

        #Parametros Default iniciales
        troll = False
        press = False
        Up = False
        Down = False
        Right = False
        Left = False
        G_up = False
        G_down = False
        coef_up = False
        coef_down = False
        dt_up = False
        dt_down = False
        rojos = 0

        while t != 0:
            FpsA = clock.get_fps()
            if FpsA == 0:
                FpsA = 0.01
            N = len(self.particulas)
            if N == 0:
                coefEn = 1
            else:
                coefEn = self.particulas[0].coef
#------------------------------------------------------------------------------------------------------// Manejo Eventos
            for event in pygame.event.get():
                posicion_mouse = pygame.mouse.get_pos()
                pos = to_logic(posicion_mouse,self.L) #Posicion normalizada acuerdo a la caja
                rel = pygame.mouse.get_rel()
                if event.type == pygame.QUIT:
                    pygame.quit()
                    quit()

                if event.type == pygame.MOUSEBUTTONDOWN:
                    key = pygame.mouse.get_pressed()
                    if key == (1,0,0):
                        selected_particula = self.select(pos)
                        Vx = 0
                        Vy = 0
                        press = True
                    if key == (0,0,1) or key == (1,0,1):
                        self.detener(pos)
                if press == True:
                    Vx = rel[0]
                    Vy = rel[1]
                    v = [Vx,Vy]
                    self.agarrar(pos,v)

                if event.type == pygame.MOUSEBUTTONUP:
                    press = False

                if event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_k:
                        m = []
                        r = []
                        for particula in self.particulas:
                            m.append(particula.m)
                            r.append(particula.r)
                        try:
                            self.agregar(pos,[0,0],16*max(m), min(r),coefEn,color_particulas2)
                            rojos += 1
                        except:
                            pass
                    if event.key == pygame.K_p:
                        pause()
                    if event.key == pygame.K_UP:
                        Up = True
                    if event.key == pygame.K_DOWN:
                        Down = True
                    if event.key == pygame.K_RIGHT:
                        Right = True
                    if event.key == pygame.K_LEFT:
                        Left = True
                    if event.key == pygame.K_w:
                        coef_up = True
                    if event.key == pygame.K_q:
                        coef_down = True
                    if event.key == pygame.K_x:
                        dt_up = True
                    elif event.key == pygame.K_z:
                        dt_down = True
                    if event.key == pygame.K_s:
                        G_up = True
                    elif event.key == pygame.K_a:
                        G_down = True
                    if event.key == pygame.K_r:
                        self.g = [0,0]
                        dt = dt_inicial
                        self.G = G_inicial
                        for particula in self.particulas:
                            particula.coef = 1
                    if event.key == pygame.K_y:
                        self.G = 0
                    if event.key == pygame.K_t:
                        self.generar(1,((self.L*Kv_max),(self.L*Kv_min)),((self.L*Kr_max),(self.L*Kr_min)),Kd,coefEn)
                    if event.key == pygame.K_BACKSPACE:
                        self.eliminar(pos)
                    if event.key == pygame.K_h:
                        probe_pos = pos
                    if event.key == pygame.K_v:
                        self.vector_field = toggle(self.vector_field)
                    if event.key == pygame.K_m:
                        self.merge = toggle(self.merge)
                    if event.key == pygame.K_b:
                        self.bound = toggle(self.bound)
                    if event.key == pygame.K_c:
                        self.colas = toggle(self.colas)
                    if event.key == pygame.K_f:
                        dt = 0

                if event.type == pygame.KEYUP:
                    if event.key == pygame.K_UP:
                        Up = False
                    elif event.key == pygame.K_DOWN:
                        Down = False
                    if event.key == pygame.K_RIGHT:
                        Right = False
                    elif event.key == pygame.K_LEFT:
                        Left = False

                    if event.key == pygame.K_w:
                        coef_up = False
                    elif event.key == pygame.K_q:
                        coef_down = False

                    if event.key == pygame.K_x:
                        dt_up = False
                    elif event.key == pygame.K_z:
                        dt_down = False

                    if event.key == pygame.K_s:
                        G_up = False
                    elif event.key == pygame.K_a:
                        G_down = False

            if Up:
                self.g[1] -=5/FpsA
            if Down:
                self.g[1] +=5/FpsA
            if Right:
                self.g[0] +=5/FpsA
            if Left:
                self.g[0] -=5/FpsA

            if coef_up and coefEn<0.9999:
                self.cambiar_coef(0.45/FpsA)
            if coef_down and coefEn>0.0001:
                self.cambiar_coef(-0.45/FpsA)

            if dt_up:
                dt += 0.002/FpsA
            if dt_down:
                dt -= 0.002/FpsA
                if dt <= 0:
                    dt = 0
            if G_up:
                self.G += 50/FpsA
            if G_down:
                self.G -= 50/FpsA
#------------------------------------------------------------------------------------------------------// Manejo Eventos
#------------------------------------------------------------------------------------------------------// Display
            display.fill(color_particulas1)
            pygame.draw.rect(display,color_fondo,(Margen_x,Margen_y,Largo_caja,Largo_caja))
            for particula in self.particulas:
                if not out(particula.pos,particula.r):
                    p = to_display((particula.pos[0],particula.pos[1]),self.L)
                    r = int(particula.r*Largo_caja/self.L)
                    pygame.draw.circle(display,particula.color,p,r)
                    if self.colas:
                        particula.get_cola()
                        pygame.draw.lines(display,color_medio,False,particula.cola)
                    else:
                        particula.cola = [deepcopy(to_display(particula.pos,L))]

            try:
                v_promedio = self.rapidez_promedio()
                g_in_display = "g :("+str(round(self.g[0]*10)/10)+", "+str(round(self.g[1]*10)/10)+")"
                N_in_display = "NºBolas :"+str(N)
                V_in_display = "Rapidez media:" +str(round(v_promedio))
                Largo_in_display = "Largo caja:" +str(self.L)
                FPS_in_display = "FPS: "+str(round(FpsA))
                dt_in_display ="dt: " + str("{:.2e}".format(dt))
                G_in_display = "G: "+str("{:.2e}".format(self.G))
                probe_acceleration = self.probe(probe_pos)
                Probe_in_display ="Probe A:"+"(" + str("{:.2e}".format(round(probe_acceleration[0]))) + ", " + str("{:.2e}".format(probe_acceleration[0]))+")"
                coef_in_display = "coef :"+str(round(self.particulas[0].coef*100)/100)
            except:
                pass
            try:
                message_to_screen(g_in_display,color_palabras,Margen_x//10,Margen_y)
                message_to_screen(N_in_display,color_palabras,Largo_caja+((11*Margen_x)//10),Margen_y)
                message_to_screen(V_in_display,color_palabras,Margen_x//10,Height - Margen_y + Ly)
                message_to_screen(Largo_in_display,color_palabras,Largo_caja+((11*Margen_x)//10),Height - Margen_y + Ly)
                message_to_screen(FPS_in_display,color_palabras,Largo_caja+((11*Margen_x)//10),Height - Margen_y)
                message_to_screen(dt_in_display,color_palabras,Largo_caja+((11*Margen_x)//10),Margen_y + Ly)
                message_to_screen(G_in_display,color_palabras,Largo_caja+((11*Margen_x)//10),Margen_y + 2*Ly)
                message_to_screen(coef_in_display,color_palabras,Margen_x//10,Margen_y + Ly)
                message_to_screen(Probe_in_display,color_palabras,(Largo_caja/2)+Margen_x-(len(Probe_in_display)-10)*Kx,Height - (Margen_y/2))
                if self.merge:
                    m = "Merge"
                else:
                    m = "Collide"
                message_to_screen(m,color_palabras,Margen_x//10,Margen_y + Ly*4)
                if self.bound:
                    b = "Con Limte"
                else:
                    b = "Sin Limite"
                message_to_screen(b,color_palabras,Margen_x//10,Margen_y + Ly*5)
            except:
                pass
#------------------------------------------------------------------------------------------------------// Display

            self.update_logic(dt)
            self.get_info(selected_particula)
            self.get_vectorPoints()

            if (rojos >= 4) and not troll:
                troll = True
                pygame.mixer.music.play(-1)
            if troll:
                message_to_screen(":D",color_particulas2,Width//2,Height//2)

            pygame.display.update()
            clock.tick(FPS)
            t -= 1

        print("\n**Simulacion Terminada**")
        pygame.quit()
#---------------------------------------------------------------------------------------------------------------------------------------------------

print("*Simulacion de Colisiones*\n")
Q = input("(R->Cambiar config) / (Enter->continuar): ")
if Q.upper() == "R":
    config_loop = True
    while config_loop:
        print("\n(1)Resolucion\n(2)FPS_lock\n(3)dt\n(4)Colores\n(5)Parametros de particulas ingresadas despues de partir\n(6)Numero de puntos de prueba para el campo vectorial\n(7)Permitir Merge\n(8)Rebote con las paredes\n(9)Numero de puntos en las colas")
        Q = input("\nIngrese configuracion a cambiar, exit para salir: ")
        if Q == "":
            break
        if Q.upper() == "EXIT":
            break
        elif int(Q) == 1:
            print("->Resolucion Actual:",get_tuple("Data\config.txt",0))
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese resolucion de la forma (A,B): ")
                edit("Data\config.txt",0,X)
        elif int(Q) == 2:
            print("->FPS Actual:",get_tuple("Data\config.txt",1)[0])
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese FPS_lock: ")
                edit("Data\config.txt",1,X)
        elif int(Q) == 3:
            print("->dt Actual:",get_tuple("Data\config.txt",2)[0])
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese dt: ")
                edit("Data\config.txt",2,X)
        elif int(Q) == 4:
            print("\nColores(RGB):\n(1)Particula1:",get_tuple("Data\config.txt",3),"\n(2)Particula2:",get_tuple("Data\config.txt",4),"\n(3)Palabras:",get_tuple("Data\config.txt",5),"\n(4)Fondo:",get_tuple("Data\config.txt",6))
            I = input("\nNºColor a cambiar(Enter para pasar): ")
            if I == "":
                pass
            elif int(I) in range(1,5):
                col = input("->Ingrese color forma(R,G,B): ")
                edit("Data\config.txt",int(I)+2,col)
        elif int(Q) == 5:
            print("\nParametros:\n(1)Velocidad_max/Largo_caja:",get_tuple("Data\config.txt",7)[0],"\n(2)Velocidad_min/Largo_caja:",get_tuple("Data\config.txt",8)[0])
            print("(3)Radio_max/Largo_caja:",get_tuple("Data\config.txt",9)[0],"\n(4)Radio_min/Largo_caja:",get_tuple("Data\config.txt",10)[0],"\n(5)Densidad/Largo_caja:",get_tuple("Data\config.txt",11)[0])
            I = input("\nNºParametro a cambiar(Enter para pasar): ")
            if I == "":
                pass
            elif int(I) in range(1,6):
                col = input("->Ingrese Nuevo Parametro: ")
                edit("Data\config.txt",int(I)+6,col)
        elif int(Q) == 6:
            print("->n Actual:",get_tuple("Data\config.txt",12)[0])
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese n: ")
                edit("Data\config.txt",12,X)
        elif int(Q) == 7:
            print("->Merge:",get_bool("Data\config.txt",13))
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese 1 para True o 0 para False: ")
                edit("Data\config.txt",13,X)
        elif int(Q) == 8:
            print("->Bound:",get_bool("Data\config.txt",14))
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese 1 para True o 0 para False: ")
                edit("Data\config.txt",14,X)
        elif int(Q) == 9:
            print("->Puntos maximos:",get_tuple("Data\config.txt",15)[0])
            I = input("Cambiar? ")
            if I.upper() == "SI":
                X = input("->Ingrese nuevo valor: ")
                edit("Data\config.txt",15,X)
        else:
            pass

load_config()
fontl = min(Width,Height)//30
maxLen = 16
Largo_caja = min(Width,Height)//1.1
if (Width-Largo_caja)/2 < maxLen * fontl/2.5:
    fontl = int((2.5*(Width - Largo_caja))/(2*maxLen))
Kx = fontl/2.5
Ky = fontl/1.818
Ly = Ky * 1.6
Margen_x = (Width - Largo_caja)//2
Margen_y = (Height - Largo_caja)//2
pygame.init()
pygame.mixer.init()

pregunta = True
while pregunta:
    pregunta = False
    Q = input("\nQuieres ocupar los parametros default?(recomendado): ")
    if Q.upper() == "NO":
        try:
            L = float(input("\n->Ingrese el largo de el contenedor cuadrado: "))
            N = int(input("->Ingrese la cantidad de pelotas a generar: "))
            gy = float(input("->Ingrese la gravedad en el eje vertical: "))
            gx = float(input("->Ingrese la gravedad en el eje horizontal: "))
            G = float(input("->Ingrese Constante atraccion gravitatoria(0 si no hay): "))
            Vmax = float(input("->Ingrese la velocidad maxima permitida: "))
            Vmin = float(input("->Ingrese la velocidad minima permitida: "))
            fit = False
            while not fit:
                fit = True
                Rmax = float(input("->Ingrese el radio maximo permitido: "))
                if 4*Rmax >= L:
                    print("\nEso definitivamente no cabe en el contenedor de largo",L)
                    fit = False
                elif N*pi*Rmax**2 > 1.5*L**2:
                    print("\nPuede que se vea muy apretada la cosa")
                    Q = input("Continuar? ")
                    if Q.upper() == "NO":
                        fit = False
            Rmin = float(input("->Ingrese el radio minimo permitido: "))
            Densidad = float(input("->Ingrese la densidad de cada pelota: "))
            c = float(input("->Ingrese el coeficiente de restitucion : "))
            t = int(input("->Ingrese el tiempo que dura la simulacion(-1 si es infinito): "))
            V = (Vmax,Vmin)
            r = (Rmax,Rmin)
            g = [gx,gy]
        except:
            print("\nError")
            pregunta = True
    else:
        N = 6
        V = (50,30)
        r = (8,2)
        L = 500
        Densidad = 1
        c = 0.98
        t = -1
        g = [0,0]
        G = 15000
    if pregunta == False:
        print("**------------------------------------**")
        print("Entonces tendriamos:\n")
        print("El largo como",L)
        print("\nEl numero de particulas como",N)
        print("\nLa constante gravitatoria como",G)
        print("\nLa velocidad como",V)
        print("\nEl radio como",r)
        print("\nLa densidad como",Densidad)
        print("\nEl coeficientes de restitucion como",c)
        print("\nEl tiempo de simulacion es",t)
        print("\ndt :",dt)
        Q = input("\nEstas bien con esta configuracion? ")
        if Q.upper() == "NO":
            pregunta = True
            print("\nOkay, te preguntaremos de nuevo")
            input("\n(Enter para continuar)")
        else:
            input("\n(Enter para empezar simulacion)")
            print("\n******************************\n")
            font = pygame.font.SysFont(None, fontl)
            pygame.display.set_caption("Colisiones")
            clock = pygame.time.Clock()
            pygame.mixer.music.load("Data\Troll.mp3")
            display = pygame.display.set_mode((Width,Height))
            contenedor = Caja(L,g,G,[],merge,bound)
            contenedor.generar(N,V,r,Densidad,c)
            contenedor.sim_start(dt,t)


# File:        proj1.py
# Written by:  Kyle Fritz
# Date:        11/14/2013
# Lab Section: 10
# UMBC email:  fritzk1@umbc.edu
# Description: This is an autofill program.
############### USE WITH PYTHON 3 ###########
# scl enable python33 bash

def printGreeting():
    print("This is the autofill program!")

# input: The contents of the file, the coordinates, and the maximum values that
#    X and Y can be.
# output: The contents of the file after autofill.
def autoFill(columnList, x, y, maxX, maxY):
    if columnList[y][x] == ".":
        columnList[y][x] = "x"
        if y - 1 >= 0:
            autoFill(columnList, x, y - 1, maxX, maxY)
        if y + 1 <= maxY:
            autoFill(columnList, x, y + 1, maxX, maxY)
        if x - 1 >= 0:
            autoFill(columnList, x - 1, y, maxX, maxY)
        if x + 1 <= maxX:
            autoFill(columnList, x + 1, y, maxX, maxY)
    else:
        return columnList
    return columnList

# input: The contents of the file and a count of 0.
# output: Displays the contents of the file after autofill.
def displayAutofill(columnList, count):
    for i in columnList:
        for j in i:
            count += 1
            print(j, end = "")
            try:
                i[count]
            except IndexError:
                count = 0
                print()

# input: A non-valid X coordinate and the maximum value that X can be.
# output: A valid X coordinate.
def getXCoordinates(x, maxX):
    while x < 0 or x > maxX:
        try:
            x = int(input("Type in a valid X coordinate: "))
        except ValueError:
            x = -1
    return x

# input: A non-valid Y coordinate and the maximum value that Y can be.
# output: A valid Y coordinate.
def getYCoordinates(y, maxY):
   while  y < 0 or y > maxY:
       try:
           y = int(input("Type in a valid Y coordinate: "))
       except ValueError:
           y = -1
   return y

# input: The file that was read.
# output: The contents of the file and a list of lists of the contents.
def readFile(theFile):
    columnList = []
    for i in theFile:
        print(i)
        # k makes a list of the X values.
        k = list(i)
        for j in k:
            if j == "\n":
                k.remove(j)
        columnList.append(k)
    return columnList

# input: List of contents in the file.
# output: Maximum values for X and Y coordinates.
def maxCoordinates(columnList):
    maxX = 0
    maxY = 0
    for i in columnList:
        maxY += 1
        for j in i:
            maxX += 1
    # The number of js divided by the number of lines in the file will
    #   give number of characters in each line.
    maxX = maxX/maxY
    # Length - 1 will give actual maximum values.
    maxX, maxY = maxX - 1, maxY - 1
    return maxX, maxY

def main():
    printGreeting()
    # Set filename
    fileName = "a"
    # Maximum coordinates
    maxX = 0
    maxY = 0
    while fileName.lower() != "quit" and fileName.lower() != "q":
        # List for contents of the file
        columnList = []
        # Non-valid X and Y values
        x = -1
        y = -1
        try:
            print("Type in the name of the file that you would")
            fileName = input("like to open up or type 'quit' to exit: ")
            if fileName.lower() != "quit" and fileName.lower() != "q":
                theFile = open(fileName, "r")
                print("\nThis is what your file looks like before autofill:")
                # Get contents of the file to columnList and display what is
                #   on the file before autofill
                columnList = readFile(theFile)
                maxX, maxY = maxCoordinates(columnList)
                x = getXCoordinates(x, maxX)
                y = getYCoordinates(y, maxY)
                columnList = autoFill(columnList, x, y, maxX, maxY)
                count = 0
                print("This is what the file looks like after autofill")
                displayAutofill(columnList, count)
                theFile.close()
        except FileNotFoundError:
            print("Please type in a valid file name.")
            #  Change fileName to something that isn't 'quit' or a .txt file
            fileName = "a"
main()


from bs4 import BeautifulSoup
import requests

URL = "https://www.empireonline.com/movies/features/best-movies-2/"
response = requests.get(URL)
empire_web = response.text
soup = BeautifulSoup(empire_web, "html.parser")
all_tags = soup.find_all(name="img", class_="jsx-952983560")
titles = []
for tag in all_tags:
    title = tag.get("alt")
    titles.append(title)
cut_list_titles = titles[12:]
movies = cut_list_titles[::-1]

with open("movies.txt", mode="w") as file:
    for movie in movies:
        number = movies.index(movie) + 1
        file.write(f"{number}) {movie}\n")


import logic
import random
import time
queue = logic.Queue()

AI_Names = ["Jacob","One time Mike","Tucker"]
print("Welcome to UNO in Python")

def start_game():
    player_name = str(input("Please input a name to start:  ")).lower()
    if player_name == "":
        return start_game()
    else:
        #player
        new_unit = logic.Unit_Generator(str(player_name),True)
        #AI
        new_ai_unit = logic.Unit_Generator(AI_Names[0])

        queue.add_unit(new_unit)
        queue.add_unit(new_ai_unit)

        for unit in queue.units:
            unit.generate_new_hand(7)

def Specials(card,color = None):
    if card["sign"] == "wild":
        logic.wild_color = color
        print("A wild was played new color is "+color)
    elif card["sign"] == "draw4":
        logic.wild_color = color
        logic.draw_rate+=4
        print("draw4 was played new color is "+color)
        return
    elif card["sign"] == "skip":
        queue.move_unit_back()
        print(queue.get_current_unit().name+" has been skipped")

    elif card["sign"] == "reverse":
        queue.reverse_queue()
        print("Game has been reverse")
    elif card["sign"] == "draw2":
        logic.draw_rate+=2
        print("draw2 was played")
        return
    if logic.draw_rate > 0:
        print(queue.get_current_unit().name+" has drawn "+str(logic.draw_rate)+" cards")
        while logic.draw_rate > 0:
            queue.get_current_unit().hand.append(logic.rand_card_generator())
            logic.draw_rate-=1

        logic.wild_color = color

def color_queue():
    choice = str(input("Pick a color: ")).lower()
    if choice == "red" or choice == "yellow" or choice == "blue" or choice == "green":
       print("Color has been changed")
       return choice
    else:
        print(choice)
        color_queue()

def game_loop():
    print("Current faced up card is "+logic.pile_top["name"]+"\n")
    # get's list for playable cards
    playable_hand = []
    for card in queue.get_current_unit().hand:
        playable_card = logic.playableChecker(card,logic.pile_top,logic.wild_color)
        if playable_card != None:
            playable_hand.append(card)
    # Player actions
    if queue.get_current_unit().is_player == True:
        # Displays player's card names
        hand_names =[]
        for card in queue.get_current_unit().hand:
            hand_names.append(card["name"])
        # Display play's hand
        print("Your cards")
        print(hand_names)
        # Ask for input
        time.sleep(1.5)
        choice = str(input("Please choose a card or type 'draw' to draw a new card or type 'uno' to call uno on someone: ")).lower()

        for card in playable_hand:
            if card["name"] == choice:
                queue.get_current_unit().remove_card(card)
                logic.pile_top = card
                print("\n"+card["name"]+" was played\n")
                print(queue.get_current_unit().name+" has "+str(len(queue.get_current_unit().hand))+" cards left")

    # check to see if need to ask for a color
                if card["sign"] == "wild" or card["sign"] == "draw4":
                    Specials(card,color_queue())
                else:
                    Specials(card)
                    queue.move_unit_back()
                break
    #Draw's a card
        if choice == "draw" or choice == "d":
            print("A card was drawn")
            queue.get_current_unit().draw_card()
        elif choice == "uno":
            logic.uno_called(queue)
    # invalid input was given
        elif choice not in hand_names:
            print("\nThat cannot be played!\n")

    # action for AI turn
    elif  queue.get_current_unit().is_player == False :
        if len(playable_hand) > 0:
            time.sleep(1.5)
            # randon card picker
            ran_card = random.randint(0,len(playable_hand)-1)
            # removes the card being played
            queue.get_current_unit().remove_card(playable_hand[ran_card])
            print(queue.get_current_unit().name+" played "+playable_hand[ran_card]["name"])
            # sets playing card as new pile card
            logic.pile_top = playable_hand[ran_card]
            Specials(playable_hand[ran_card],logic.wildCard())
            if random.randint(0,10) > 5:
                logic.uno_called(queue)
            print(queue.get_current_unit().name+" has "+str(len(queue.get_current_unit().hand))+" cards left")
            queue.move_unit_back()

        else:
            time.sleep(1.5)
            queue.get_current_unit().draw_card()
            print(queue.get_current_unit().name+" drawn a card.")

    else:
        return print("Somthing went wrong...")

    # If any player has 0 cards then end the loop else keep looping
    for unit in queue.units:
        if len(unit.hand) == 0:
            print(unit.name+" has won!")
            break
        elif len(unit.hand) > 0:
            game_loop()
            break

start_game()
game_loop()


from flask import Flask, request, jsonify
from helpers import *
import pigpio
app = Flask(__name__)

#rgb 22, 27, 17
#base teal 40 97 15

pi = pigpio.pi()

@app.route("/")
def home():
    return "Hello World!"

@app.route("/color")
def set_color():
    args = request.args.to_dict()
    r = to_PWM_dutycycle(args['r'])
    g = to_PWM_dutycycle(args['g'])
    b = to_PWM_dutycycle(args['b'])
    pi.set_PWM_dutycycle(GPIO_RED, r)
    pi.set_PWM_dutycycle(GPIO_GREEN, g)
    pi.set_PWM_dutycycle(GPIO_BLUE, b)
    return str(r) + ' ' + str(g) + ' ' + str(b)

@app.route("/get_color")
def get_color():
    color = get_current_color(pi)
    return jsonify(color)

if __name__ == "__main__":
    #app.run(debug=True)
    app.run(host='0.0.0.0', port=80, debug=True)


import time
from machine import SoftSPI, Pin
from hid_services import Keyboard

class Device:
    def __init__(self):
        # Define state
        self.key0 = 0x00
        self.key1 = 0x00
        self.key2 = 0x00
        self.key3 = 0x00

        # Define buttons
        self.pin_forward = Pin(5, Pin.IN)
        self.pin_reverse = Pin(23, Pin.IN)
        self.pin_right = Pin(19, Pin.IN)
        self.pin_left = Pin(18, Pin.IN)

        # Create our device
        self.keyboard = Keyboard("Keyboard")
        # Set a callback function to catch changes of device state
        self.keyboard.set_state_change_callback(self.keyboard_state_callback)
        # Start our device
        self.keyboard.start()

    # Function that catches device status events
    def keyboard_state_callback(self):
        if self.keyboard.get_state() is Keyboard.DEVICE_IDLE:
            return
        elif self.keyboard.get_state() is Keyboard.DEVICE_ADVERTISING:
            return
        elif self.keyboard.get_state() is Keyboard.DEVICE_CONNECTED:
            return
        else:
            return

    def keyboard_event_callback(self, bytes):
        print("Keyboard state callback with bytes: ", bytes)

    def advertise(self):
        self.keyboard.start_advertising()

    def stop_advertise(self):
        self.keyboard.stop_advertising()

    # Main loop
    def start(self):
        while True:
            # Read pin values and update variables
            if self.pin_forward.value():
                self.key0 = 0x1A  # W
            else:
                self.key0 = 0x00

            if self.pin_left.value():
                self.key1 = 0x04  # A
            else:
                self.key1 = 0x00

            if self.pin_reverse.value():
                self.key2 = 0x16  # S
            else:
                self.key2 = 0x00

            if self.pin_right.value():
                self.key3 = 0x07  # D
            else:
                self.key3 = 0x00

            # If the variables changed do something depending on the device state
            if (self.key0 != 0x00) or (self.key1 != 0x00) or (self.key2 != 0x00) or (self.key3 != 0x00):
                # If connected set keys and notify
                # If idle start advertising for 30s or until connected
                if self.keyboard.get_state() is Keyboard.DEVICE_CONNECTED:
                    self.keyboard.set_keys(self.key0, self.key1, self.key2, self.key3)
                    self.keyboard.notify_hid_report()
                elif self.keyboard.get_state() is Keyboard.DEVICE_IDLE:
                    self.keyboard.start_advertising()
                    i = 10
                    while i > 0 and self.keyboard.get_state() is Keyboard.DEVICE_ADVERTISING:
                        time.sleep(3)
                        i -= 1
                    if self.keyboard.get_state() is Keyboard.DEVICE_ADVERTISING:
                        self.keyboard.stop_advertising()

            if self.keyboard.get_state() is Keyboard.DEVICE_CONNECTED:
                time.sleep_ms(20)
            else:
                time.sleep(2)

    def send_char(self, char):
        if char == " ":
            mod = 0
            code = 0x2C
        elif ord("a") <= ord(char) <= ord("z"):
            mod = 0
            code = 0x04 + ord(char) - ord("a")
        elif ord("A") <= ord(char) <= ord("Z"):
            mod = 1
            code = 0x04 + ord(char) - ord("A")
        else:
            assert 0

        self.keyboard.set_keys(code)
        self.keyboard.set_modifiers(left_shift=mod)
        self.keyboard.notify_hid_report()
        time.sleep_ms(2)

        self.keyboard.set_keys()
        self.keyboard.set_modifiers()
        self.keyboard.notify_hid_report()
        time.sleep_ms(2)

    def send_string(self, st):
        for c in st:
            self.send_char(c)

    # Only for test
    def stop(self):
        self.keyboard.stop()

    # Test routine
    def test(self):
        time.sleep(5)
        self.keyboard.set_battery_level(50)
        self.keyboard.notify_battery_level()
        time.sleep_ms(2)

        # Press Shift+W
        self.keyboard.set_keys(0x1A)
        self.keyboard.set_modifiers(right_shift=1)
        self.keyboard.notify_hid_report()

        # release
        self.keyboard.set_keys()
        self.keyboard.set_modifiers()
        self.keyboard.notify_hid_report()
        time.sleep_ms(500)

        # Press a
        self.keyboard.set_keys(0x04)
        self.keyboard.notify_hid_report()

        # release
        self.keyboard.set_keys()
        self.keyboard.notify_hid_report()
        time.sleep_ms(500)

        # Press s
        self.keyboard.set_keys(0x16)
        self.keyboard.notify_hid_report()

        # release
        self.keyboard.set_keys()
        self.keyboard.notify_hid_report()
        time.sleep_ms(500)

        # Press d
        self.keyboard.set_keys(0x07)
        self.keyboard.notify_hid_report()

        # release
        self.keyboard.set_keys()
        self.keyboard.notify_hid_report()
        time.sleep_ms(500)

        self.send_string(" Hello World")

        self.keyboard.set_battery_level(100)
        self.keyboard.notify_battery_level()

if __name__ == "__main__":
    d = Device()
    d.start()


import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

def calculate_birds_eye_view_parameters(x_bounds, y_bounds, z_bounds):
    """
    Parameters
    ----------
        x_bounds: Forward direction in the ego-car.
        y_bounds: Sides
        z_bounds: Height

    Returns
    -------
        bev_resolution: Bird's-eye view bev_resolution
        bev_start_position Bird's-eye view first element
        bev_dimension Bird's-eye view tensor spatial dimension
    """
    bev_resolution = torch.tensor(
        [row[2] for row in [x_bounds, y_bounds, z_bounds]])
    bev_start_position = torch.tensor(
        [row[0] + row[2] / 2.0 for row in [x_bounds, y_bounds, z_bounds]])
    bev_dimension = torch.tensor([(row[1] - row[0]) / row[2]
                                 for row in [x_bounds, y_bounds, z_bounds]], dtype=torch.long)

    return bev_resolution, bev_start_position, bev_dimension

def gen_dx_bx(xbound, ybound, zbound):
    dx = torch.Tensor([row[2] for row in [xbound, ybound, zbound]])
    bx = torch.Tensor([row[0] + row[2]/2.0 for row in [xbound, ybound, zbound]])
    nx = torch.LongTensor([(row[1] - row[0]) / row[2] for row in [xbound, ybound, zbound]])

    return dx, bx, nx

# Instance utils
def update_instance_ids(instance_seg, old_ids, new_ids):
    """
    Parameters
    ----------
        instance_seg: torch.Tensor arbitrary shape
        old_ids: 1D tensor containing the list of old ids, must be all present in instance_seg.
        new_ids: 1D tensor with the new ids, aligned with old_ids

    Returns
        new_instance_seg: torch.Tensor same shape as instance_seg with new ids
    """
    indices = torch.arange(old_ids.max() + 1, device=instance_seg.device)
    for old_id, new_id in zip(old_ids, new_ids):
        indices[old_id] = new_id

    return indices[instance_seg].long()

def make_instance_seg_consecutive(instance_seg):
    # Make the indices of instance_seg consecutive
    unique_ids = torch.unique(instance_seg)  # include background
    new_ids = torch.arange(len(unique_ids), device=instance_seg.device)
    instance_seg = update_instance_ids(instance_seg, unique_ids, new_ids)
    return instance_seg

def predict_instance_segmentation_and_trajectories(
                                    foreground_masks,
                                    ins_sigmoid,
                                    vehicles_id=1,
                                    ):
    if foreground_masks.dim() == 5 and foreground_masks.shape[2] == 1:
        foreground_masks = foreground_masks.squeeze(2)  # [b, t, h, w]
    foreground_masks = foreground_masks == vehicles_id  # [b, t, h, w]  Only these places have foreground id

    argmax_ins = ins_sigmoid.argmax(dim=1)  # long, [b, t, h, w], ins_id starts from 0
    argmax_ins = argmax_ins + 1 # [b, t, h, w], ins_id starts from 1
    instance_seg = (argmax_ins * foreground_masks.float()).long()  # bg is 0, fg starts with 1

    # Make the indices of instance_seg consecutive
    instance_seg = make_instance_seg_consecutive(instance_seg).long()

    return instance_seg