from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from rest_framework.generics import get_object_or_404
from rest_framework.permissions import IsAuthenticatedOrReadOnly
from rest_framework.pagination import PageNumberPagination
from .models import UserPost
from .serializers import UserPostSerializer, CreateUserSerializer
from .permissions import IsOwnerOrReadOnly
from .utils import patch_error_handler

# i'm not completely following API design here, so it is 1:1 matching between HTTP and CRUD
class ListCreateUserPosts(APIView, PageNumberPagination):
    page_size = 5
    serializer_class = UserPostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly,]

    def get(self, request): # all can read
        posts = UserPost.objects.all()
        # pagination works, but doesn't show pagination buttons when using Test page in the Browser
        result_page = self.paginate_queryset(posts, request)
        seralizered_data = self.serializer_class(result_page, many=True)
        response = self.get_paginated_response(seralizered_data.data)
        response.data['posts'] = response.data.pop('results')
        return response

    def post(self, request): # only authenticated user can create
        seraialized_data = self.serializer_class(
            data=request.data,
            context={'request': request}
        )
        seraialized_data.is_valid(raise_exception=True)
        res = seraialized_data.save()
        headers = {'Location': f'http://{request.get_host()}/api/v1/userpost/{res.id}/'}
        return Response({'post': seraialized_data.data}, status=status.HTTP_201_CREATED, headers=headers)

class GetUpdateDeleteUserPost(APIView):
    serializer_class = UserPostSerializer
    permission_classes = [IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,]

    def get(self, request, pk=None): # for all, because maybe someone wants to check a specific post
        post = get_object_or_404(UserPost, pk=pk)
        seralizered_data = self.serializer_class(post)
        return Response({'post': seralizered_data.data})

    def put(self, request, **kwargs): # only authenticated user and an author of the post
        partial = kwargs.pop('partial', False)
        old_instance = get_object_or_404(UserPost, pk=kwargs.get('pk'))
        self.check_object_permissions(request, old_instance)
        seraialized_data = self.serializer_class(
            data=request.data,
            instance=old_instance,
            context={'request': request},
            partial=partial
        )
        seraialized_data.is_valid(raise_exception=True)
        seraialized_data.save()
        return Response(seraialized_data.data)

    @patch_error_handler
    def patch(self, request, **kwargs): # only authenticated user and an author of the post
        # it seems like generic patch doesn't provide any error handling. It returns
        # 200 + an old data per every invalid request that i tried. At leats it doesn't
        # apply changes
        kwargs['partial'] = True
        return self.put(request, **kwargs)

    def delete(self, request, pk): # only authenticated user and an author of the post
        instance = get_object_or_404(UserPost, pk=pk)
        self.check_object_permissions(request, instance)
        instance.delete()
        return Response(status=status.HTTP_204_NO_CONTENT)

class CreateUser(APIView):
    serializer_class = CreateUserSerializer

    def post(self, request):
        serialized_data = CreateUserSerializer(data=request.data)
        serialized_data.is_valid(raise_exception=True)
        serialized_data.save()
        return Response({'new_user': serialized_data.data}, status=status.HTTP_201_CREATED)


import time
import numpy as np
from sklearn.metrics import roc_auc_score
from collections import defaultdict
import math

def print_list(in_list):
    line=''
    for item in in_list:
        line=line+str(item)+' '
    return line[0:-1]

def compute_det_curve(target_scores, nontarget_scores):
    n_scores = len(target_scores) + len(nontarget_scores)
    all_scores = np.concatenate((target_scores, nontarget_scores))
    labels = np.concatenate((np.ones(len(target_scores)), np.zeros(len(nontarget_scores))))
    # Sort labels based on scores
    indices = np.argsort(all_scores, kind='mergesort')
    labels = labels[indices]
    # Compute false rejection and false acceptance rates
    tar_trial_sums = np.cumsum(labels)
    nontarget_trial_sums = len(nontarget_scores) - (np.arange(1, n_scores + 1) - tar_trial_sums)
    # value <=thresholds，missing report
    frr = np.concatenate((np.atleast_1d(0), tar_trial_sums / len(target_scores)))  # false rejection rates
    far = np.concatenate((np.atleast_1d(1), nontarget_trial_sums / len(nontarget_scores)))  # false acceptance rates
    thresholds = np.concatenate((np.atleast_1d(all_scores[indices[0]] - 0.001), all_scores[indices]))  # Thresholds are the sorted scores
    return frr, far, thresholds

#target_scores,positive sample
def compute_eer(target_scores, nontarget_scores):
    """ Returns equal error rate (EER) and the corresponding threshold. """
    frr, far, thresholds = compute_det_curve(target_scores, nontarget_scores)
    #draw table Far
    # eer
    abs_diffs = np.abs(frr - far)
    min_index = np.argmin(abs_diffs)
    eer = np.mean((frr[min_index], far[min_index]))
    # FRR@FAR10% (%) approximate value
    abs_diffs_10 = np.abs(far - 0.1)
    min_index_10 = np.argmin(abs_diffs_10)
    frr_10 = round(np.mean(frr[min_index_10]),2)
    return eer, thresholds[min_index],frr_10,far, thresholds

# draw distribute table params, x num label,max_x
graph_group_num = 20
margin = 1
def compute_far(far_list,thresholds_list,group_num):
    # compute_far for FAR curse
    step=round(margin/group_num,4)
    sample_far_list=[]
    sample_thresholds_list=[]
    for i in range(0,group_num):
        # approximate value
        x=step/2+i*step
        temp_list=[abs(k-x) for k in thresholds_list]
        min_index = np.argmin(temp_list)
        sample_thresholds_list.append(round(x,4))
        sample_far_list.append(round(far_list[min_index],4))
    return sample_thresholds_list,sample_far_list

def compute_auc(real_distance_list,fake_distance_list):
    real_length = len(real_distance_list)
    fake_length = len(fake_distance_list)
    # print("real_len", real_length)
    # print("fake_len", fake_length)
    all_distances = np.concatenate((real_distance_list, fake_distance_list))
    labels = np.concatenate((np.ones(real_length), np.zeros(fake_length)))
    auc = roc_auc_score(labels, all_distances)
    return auc

#0-1化处理,阈值也要进行01化处理
def compute_distribution(real_distance_list,fake_distance_list,group_num):
    #分成
    step=round(margin/group_num,2)
    real_group_list=[0]*group_num
    fake_group_list=[0]*group_num
    for item in real_distance_list:
        i=int(item//step)
        real_group_list[i]=real_group_list[i]+1
    for item in fake_distance_list:
        i = int(item // step)
        fake_group_list[i]=fake_group_list[i]+1
    real_group_list=[round(i/len(real_distance_list),2) for i in real_group_list]
    fake_group_list=[round(i/len(fake_distance_list),2) for i in fake_group_list]
    return real_group_list,fake_group_list

def calculate_metrics(real_clip_distance_map,fake_clip_distance_map,label,iteration,evaluate_path):
    real_clip_distance_list = []
    fake_clip_distance_list = []
    # mean
    real_video_distance_list = []
    fake_video_distance_list = []
    # video metrics
    real_video_distance_map = defaultdict(lambda: [])
    fake_video_distance_map = defaultdict(lambda: [])
    for key, value in real_clip_distance_map.items():
        value=1.0/(np.mean(value)+0.1)
        real_clip_distance_list.append(value)
        # real_fadg0_sa1_SH_num
        part_list=key.split('_')
        video_key=part_list[0]+'_'+part_list[1]+'_'+part_list[2]
        if video_key in real_video_distance_map.keys():
            real_video_distance_map[video_key].append(value)
        else:
            real_video_distance_map[video_key]=[value]

    for key, value in fake_clip_distance_map.items():
        value=1.0/(np.mean(value)+0.1)
        fake_clip_distance_list.append(value)
        part_list = key.split('_')
        video_key = part_list[0] + '_' + part_list[1] + '_' + part_list[2]
        if video_key in fake_video_distance_map.keys():
            fake_video_distance_map[video_key].append(value)
        else:
            fake_video_distance_map[video_key] = [value]

    for _,value in real_video_distance_map.items():
        real_video_distance_list.append(np.mean(value))
    for _, value in fake_video_distance_map.items():
        fake_video_distance_list.append(np.mean(value))
    # standardization
    real_clip_distance_list = [math.log(i) for i in real_clip_distance_list]
    fake_clip_distance_list = [math.log(i) for i in fake_clip_distance_list]
    real_video_distance_list = [math.log(i)  for i in real_video_distance_list]
    fake_video_distance_list = [math.log(i) for i in fake_video_distance_list]
    clip_min_point=min(min(real_clip_distance_list),min(fake_clip_distance_list))
    clip_max_point=max(max(real_clip_distance_list),max(fake_clip_distance_list))+0.0001
    video_min_point = min(min(real_video_distance_list), min(fake_video_distance_list))
    video_max_point = max(max(real_video_distance_list), max(fake_video_distance_list))+0.0001

    real_clip_distance_list=[margin*(i-clip_min_point)/(clip_max_point-clip_min_point) for i in real_clip_distance_list]
    fake_clip_distance_list=[margin*(i-clip_min_point)/(clip_max_point-clip_min_point) for i in fake_clip_distance_list]
    real_video_distance_list = [margin*(i - video_min_point) / (video_max_point - video_min_point) for i in real_video_distance_list]
    fake_video_distance_list = [margin*(i - video_min_point) / (video_max_point - video_min_point) for i in fake_video_distance_list]

    # ROC video
    clip_auc  = compute_auc(real_clip_distance_list, fake_clip_distance_list)
    clip_eer, clip_thresholds,clip_frr_10,clip_far_list, clip_thresholds_list = compute_eer(real_clip_distance_list, fake_clip_distance_list)
    sample_clip_thresholds_list, sample_clip_far_list = compute_far(clip_far_list, clip_thresholds_list, graph_group_num)
    real_clip_group_list,fake_clip_group_list=compute_distribution(real_clip_distance_list, fake_clip_distance_list,graph_group_num)
    video_auc  = compute_auc(real_video_distance_list, fake_video_distance_list)
    video_eer, video_thresholds,video_frr_10,video_far_list, video_thresholds_list = compute_eer(real_video_distance_list, fake_video_distance_list)
    sample_video_thresholds_list, sample_video_far_list = compute_far(video_far_list, video_thresholds_list, graph_group_num)
    real_video_group_list, fake_video_group_list = compute_distribution(real_video_distance_list, fake_video_distance_list, graph_group_num)
    print('clip_AUC:{},clip_eer:{},clip_thresholds:{},clip_frr_10:{}'.format(clip_auc,clip_eer,clip_thresholds,clip_frr_10))
    print('video_AUC:{},video_eer:{},video_thresholds:{},video_frr_10:{}'.format(video_auc,video_eer,video_thresholds,video_frr_10))
    real_clip_group_list_line='real_clip_group_list:'+print_list(real_clip_group_list)
    fake_clip_group_list_line='fake_clip_group_list:'+print_list(fake_clip_group_list)
    real_video_group_list_line='real_video_group_list:'+print_list(real_video_group_list)
    fake_video_group_list_line='fake_video_group_list:'+print_list(fake_video_group_list)

    sample_clip_thresholds_list_line='sample_clip_thresholds_list:'+print_list(sample_clip_thresholds_list)
    sample_clip_far_list_line='sample_clip_far_list :'+print_list(sample_clip_far_list)
    sample_video_thresholds_list_line='sample_video_thresholds_list :'+print_list(sample_video_thresholds_list)
    sample_video_far_list_line='sample_video_far_list :'+print_list(sample_video_far_list)

    print(real_clip_group_list_line)
    print(fake_clip_group_list_line)
    print(real_video_group_list_line)
    print(fake_video_group_list_line)
    print(sample_clip_thresholds_list_line)
    print(sample_clip_far_list_line)
    print(sample_video_thresholds_list_line)
    print(sample_video_far_list_line)

    # record train and infer line
    with open(evaluate_path, 'a') as model_f:
        line0 = '\n'+label+':'+time.strftime('%Y-%m-%d %H:%M:%S',time.localtime(time.time()))+' model_'+str(iteration)+ '\n'
        line1 = 'clip_auc:' + ' ' + str(clip_auc) + '\n'
        line2 = 'clip_eer:' + ' ' + str(clip_eer) + '  '+'clip_thresholds:'+str(clip_thresholds)+'  '+'clip_frr_10:'+str(clip_frr_10)+ '\n'
        line3 = 'video_auc:' + ' ' + str(video_auc) + '\n'
        line4 = 'video_eer:' + ' ' + str(video_eer) + '  ' + 'video_thresholds:' + str(video_thresholds)+'  '+'video_frr_10:'+str(video_frr_10)+ '\n'
        model_f.write(line0)
        model_f.write(line1)
        model_f.write(line2)
        model_f.write(line3)
        model_f.write(line4)
        model_f.write(real_clip_group_list_line+'\n')
        model_f.write(fake_clip_group_list_line+'\n')
        model_f.write(real_video_group_list_line+'\n')
        model_f.write(fake_video_group_list_line+'\n')

        model_f.write(sample_clip_thresholds_list_line + '\n')
        model_f.write(sample_clip_far_list_line + '\n')
        model_f.write(sample_video_thresholds_list_line + '\n')
        model_f.write(sample_video_far_list_line + '\n')
        meta={
            'clip_auc':clip_auc,
            'clip_eer':clip_eer,
            'clip_thresholds':clip_thresholds,
            'clip_frr_10':clip_frr_10,
            'video_auc': video_auc,
            'video_eer': video_eer,
            'video_thresholds': video_thresholds,
            'video_frr_10': video_frr_10,
        }
    return meta

def test_auc():
    real_clip_distance_map = {
        'real_fadg0_sa1_0':1,
        'real_fadg0_sa2_1':1,
        'real_fadg0_sa3_2':3,
        'real_fadg0_sa1_5':4,
    }
    fake_clip_distance_map = {
        'fake_fadg0_sa4_0': 11,
        'fake_fmdg0_sa1_1': 7,
        'fake_fkdg0_sa2_1': 32,
        'fake_fkdu0_sa8_1': 9,
    }
    calculate_metrics(real_clip_distance_map,fake_clip_distance_map,'real',9,'path')

# def test_table():
#     real_distance_list=[1,2,3,4,5]
#     fake_distance_list=[6,7,8,9,10]
#     a,b=compute_table(real_distance_list, fake_distance_list,10)
#     print('hello')
#test_auc()


import functools
import json
import logging
import os
import socket
import threading
import urllib.request
from http.client import RemoteDisconnected
from urllib.error import HTTPError
from urllib.error import URLError

import backoff
import pysm

from upparat.config import settings
from upparat.events import DOWNLOAD_COMPLETED
from upparat.events import DOWNLOAD_INTERRUPTED
from upparat.events import HOOK
from upparat.events import HOOK_COMMAND
from upparat.events import HOOK_MESSAGE
from upparat.events import HOOK_STATUS
from upparat.events import HOOK_STATUS_COMPLETED
from upparat.events import HOOK_STATUS_FAILED
from upparat.events import HOOK_STATUS_TIMED_OUT
from upparat.events import JOB
from upparat.hooks import run_hook
from upparat.jobs import JobFailedStatus
from upparat.jobs import JobProgressStatus
from upparat.statemachine import JobProcessingState

logger = logging.getLogger(__name__)

READ_CHUNK_SIZE_BYTES = 1024 * 100  # 100 kib
REQUEST_TIMEOUT_SEC = 30
BACKOFF_EXPO_MAX_SEC = 2 ** 6  # 64

RETRYABLE_EXCEPTIONS = (
    URLError,
    HTTPError,
    RemoteDisconnected,
    socket.timeout,
    ConnectionResetError,
)

@backoff.on_exception(
    functools.partial(backoff.expo, max_value=BACKOFF_EXPO_MAX_SEC),
    RETRYABLE_EXCEPTIONS,
    jitter=backoff.full_jitter,
)
def download(job, stop_download, publish, update_job_progress):
    """
    See https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
    for more information regarding the backoff behaviour (i.e. jitter).
    """
    start_position_bytes = 0

    if os.path.exists(job.filepath):
        start_position_bytes = os.path.getsize(job.filepath)
        logger.info(f"Partial download of {start_position_bytes} bytes found.")

    if stop_download.is_set():
        logger.info(f"Download interrupted [stop event set].")

    request = urllib.request.Request(job.file_url)
    request.add_header("Range", f"bytes={start_position_bytes}-")

    logger.info(f"Downloading job to {job.filepath}.")

    try:
        with urllib.request.urlopen(
            request, timeout=REQUEST_TIMEOUT_SEC
        ) as source, open(job.filepath, "ab") as destination:

            done = False

            while not done and not stop_download.is_set():
                data = source.read(READ_CHUNK_SIZE_BYTES)

                if data:
                    destination.write(data)
                    # make sure everything is written to disk now
                    # https://docs.python.org/3/library/os.html#os.fsync
                    destination.flush()
                    os.fsync(destination)

                    downloaded_bytes = os.fstat(destination.fileno()).st_size
                    logger.debug(f"Downloaded {downloaded_bytes} bytes.")

                    update_job_progress(
                        JobProgressStatus.DOWNLOAD_PROGRESS.value,
                        message=json.dumps({"downloaded_bytes": downloaded_bytes}),
                    )
                else:
                    done = True

        if stop_download.is_set():
            logger.info(f"Download stopped. Removing {job.filepath}.")
            os.remove(job.filepath)

        if done:
            logger.info(f"Download completed.")
            publish(pysm.Event(DOWNLOAD_COMPLETED, **{JOB: job}))

    except HTTPError as http_error:
        if http_error.status == 416:
            publish(pysm.Event(DOWNLOAD_COMPLETED, **{JOB: job}))
        elif http_error.status == 403:
            logger.warning("URL has expired. Starting over.")
            update_job_progress(JobProgressStatus.DOWNLOAD_INTERRUPT.value)
            publish(pysm.Event(DOWNLOAD_INTERRUPTED))
        else:
            logger.error(f"HTTPError {http_error.status}: {http_error.reason}.")
            raise http_error
    except Exception as exception:
        if type(exception) not in RETRYABLE_EXCEPTIONS:
            # see issue #19, instead of hanging on unhanded exception,
            # we try to improve the situation by just starting over
            # since we don't have any better error recovery strategy.
            logger.exception("Unhandled failure. Starting over.")
            update_job_progress(JobProgressStatus.DOWNLOAD_INTERRUPT.value)
            publish(pysm.Event(DOWNLOAD_INTERRUPTED))
        else:
            # let backoff.on_exception handle retry
            # for this exception
            raise exception

class DownloadState(JobProcessingState):
    """ State that handles the actual download. """

    name = "download"
    job = None

    def __init__(self):
        self.stop_download_hook = threading.Event()
        self.stop_download = threading.Event()
        super().__init__()

    def clean_previous_downloads(self):
        for download_file in os.listdir(settings.service.download_location):
            download_file_path = settings.service.download_location / download_file
            if not self.job.filepath == download_file_path:
                logger.info(f"Deleting previous download artifact {download_file_path}")
                os.remove(download_file_path)

    def start_download_thread(self):
        # event could still be set from the previous job
        # that has been cancelled or deleted, so clear it.
        self.stop_download.clear()
        self.clean_previous_downloads()

        logger.debug(f"Start download for job {self.job.id_}.")
        self.job_progress(JobProgressStatus.DOWNLOAD_START.value)

        threading.Thread(
            daemon=True,
            target=download,
            kwargs={
                "job": self.job,
                "stop_download": self.stop_download,
                "publish": self.publish,
                "update_job_progress": self.job_progress,
            },
        ).start()

    def on_enter(self, state, event):
        hook = settings.hooks.download
        force = self.job.force

        if hook and not force:
            self.stop_download_hook = run_hook(
                hook, self.root_machine.inbox, args=[self.job.meta]
            )
        else:
            logger.info(
                f"Skip download hook: Hook={hook if hook else 'no-hook'}, force={force}."
            )
            self.start_download_thread()

    def stop_hooks(self):
        self.stop_download_hook.set()

    def on_exit(self, state, event):
        self.stop_hooks()
        self.stop_download.set()

    def event_handlers(self):
        return {HOOK: self.on_handle_hooks}

    def on_handle_hooks(self, _, event):
        if event.cargo[HOOK_COMMAND] != settings.hooks.download:
            return

        status = event.cargo[HOOK_STATUS]

        if status == HOOK_STATUS_COMPLETED:
            logger.info("Hook successfully completed. Download now allowed.")
            self.start_download_thread()

        elif status in (HOOK_STATUS_FAILED, HOOK_STATUS_TIMED_OUT):
            error_message = event.cargo[HOOK_MESSAGE]
            logger.error(f"Version hook failed: {error_message}")

            self.job_failed(
                JobFailedStatus.DOWNLOAD_HOOK_FAILED.value, message=error_message
            )
            self.publish(pysm.Event(DOWNLOAD_INTERRUPTED))

    def on_job_cancelled(self, state, event):
        self.stop_hooks()
        self.stop_download.set()
        self.publish(pysm.Event(DOWNLOAD_INTERRUPTED))


from queue import Queue
from tqdm import tqdm

class Monkey:
    def __init__(self, id, items, operation, test, false_cond, true_cond):
        self.item_list = Queue()
        for item in items:
            self.item_list.put(item)
        self.operation = operation
        self.test = test
        self.false_cond = false_cond
        self.true_cond = true_cond
        self.id = id
        self.total_inspected_item = 0

    def inspect(self, monkey_list):
        while self.item_list.qsize() > 0:
            self.total_inspected_item += 1
            item = self.item_list.get()
            new_item = eval(str(item) + self.operation)
            # new_item = new_item // 3 # for part 1
            new_item = new_item % part2_relief # for part 2
            if new_item % self.test == 0:
                self.throw(new_item, self.true_cond, monkey_list)
            else:
                self.throw(new_item, self.false_cond, monkey_list)

    def throw(self, item, receiver_id, monkey_list):
        for idx in range(len(monkey_list)):
            if idx == receiver_id:
                monkey_list[idx].item_list.put(item)
                break

    def __repr__(self):
        print(f"Monkey {self.id} - Item list: {list(self.item_list.queue)} - Operation {self.operation} - Test {self.test}")

    def __str__(self):
        return f"Monkey {self.id} - Item list: {list(self.item_list.queue)} - Operation {self.operation} - Test {self.test}"

def decrypt_operation(operation):
    """
    operation: new = old * 7 or new = old + old
    """
    ans = None
    assert operation[0] == "new", "Wrong output"
    assert operation[1] == "=", "Wrong equal sign"
    try:
        second = int(operation[-1])
        ans = operation[-2] + operation[-1]
    except:
        # failed to convert to int
        if operation[-2] == "*":
            ans = "**2"
        elif operation[-2] == "+":
            ans = "*2"

    return ans

if __name__ == '__main__':
    monkey_list = []
    part2_relief = 1
    while True:
        try:
            line = list(input().replace(":", "").split())
            if "Monkey" in line:
                # create a new monkey
                temp_id = int(line[-1])
                count = 0
                temp_list = []
                operation = ""
                div = 0
                false_id = 0
                true_id = 0
                while count < 5:
                    line = list(input().replace(",","").split())
                    count += 1
                    if "Starting" in line:
                        for item in line[2:]:
                            temp_list.append(int(item))
                    elif "Operation:" in line:
                        operation = decrypt_operation(line[1:])
                    elif "Test:" in line:
                        div = int(line[-1])
                        part2_relief *= div
                    elif "true:" in line:
                        true_id = int(line[-1])
                    elif "false:" in line:
                        false_id = int(line[-1])
                    else:
                        print("Empty line")
                monkey = Monkey(temp_id, temp_list, operation, div, false_id, true_id)
                print(monkey)
                monkey_list.append(monkey)

        except Exception as e:
            print(e.args)
            break

    for round in tqdm(range(10000)): # for part 2
        for monkey in monkey_list:
            monkey.inspect(monkey_list)

    result = []
    for idx, monkey in enumerate(monkey_list):
        print("Monkey: ", idx)
        print(monkey.total_inspected_item)
        result.append(monkey.total_inspected_item)
        #print(list(monkey.item_list.queue))

    result = sorted(result)
    print(result[-2] * result[-1])


from functools import update_wrapper
import subprocess

from PySimpleGUI.PySimpleGUI import Button
from GPUprotection import get_gpu_max_temp
import PySimpleGUI as sg
import serial
from pystemd.systemd1 import Unit

arduino_port = "/dev/ttyUSB0"

def main():
    fcg = FanControlGui()
    fcg.start_loop()

class FanControlGui():

    #window config
    title="FanControlGui"
    x=80
    y=70
    down_text = "    v     "
    up_text = "    ^     "
    turn_on_service = "Turn On Service"
    stop_service = "Stop Service"
    connect_arduino = "Connect To Serial Port @Arduino"
    disconnect_arduino = "Disconnect"
    get_speed = "Get Fan Speed"
    update_speed = "Update Speed"

    def __init__(self):

        self.speed_value_text = sg.Text("-1",size=(5,1))
        self.arduino_connection_button = sg.Button(self.connect_arduino)
        self.arduino = None
        self.arduino_connected = False
        self.new_power = 255
        self.down_button = sg.Button(self.down_text,disabled=True)
        self.up_button = sg.Button(self.up_text,disabled=True)
        self.speed_button = sg.Button(self.get_speed)

        self.set_new_speed_input = sg.Input(size=(6,1))
        status = get_GPUprotection_status()
        print("-D- gpu-proteciton status: " + str(status))
        if status:
            self.GPUprotection_status_colored = sg.Text("O",background_color="green")
            self.GPUprotection_status_text = sg.Text("Running")
            self.GPUprotection_button = sg.Button(self.stop_service)
        else:
            self.GPUprotection_status_colored = sg.Text("X",background_color="red")
            self.GPUprotection_status_text = sg.Text("Not Running" )
            self.GPUprotection_button = sg.Button(self.turn_on_service)

        self.gpu_temp_value = sg.Text(str(get_gpu_max_temp()))

        layout = [
            [
                sg.Text("GPU Temp:"),
                self.gpu_temp_value
            ],
            [
                sg.Text("GPUprotection service\nstatus:"),
                self.GPUprotection_status_colored,
                self.GPUprotection_status_text,
                self.GPUprotection_button
            ],
            [
                self.arduino_connection_button
            ],
            [
                sg.Text("Current Speed(0-255):"),
                self.speed_value_text,
                self.speed_button
            ],
            [
                self.up_button,
                sg.Text("Up")
            ],
            [
                self.down_button,
                sg.Text("Down")
            ],
            [
                sg.Text("Set Speed:"),
                self.set_new_speed_input,
                sg.Button(self.update_speed)
            ]
            ]

        self.window = sg.Window(title=self.title, layout=layout, margins=(self.x, self.y))

    def start_loop(self):
        unit = Unit(b'gpu-protection.service')
        while True:
            event, value = self.window.read(timeout=1000)
            if event in (None,'Quit'):
                break
            self.update_GPUprotection()
            self.update_gpu_temp()
            if self.arduino_connected:
                try:
                    self.arduino.write(str(self.new_power).encode())
                except Exception as e:
                    print(e)
                    if not get_GPUprotection_status():
                        unit.load()
                        unit.Start(b'replace')

            if event == self.up_text or event == self.down_text:
                self.change_fan_power(event)
            elif event == self.turn_on_service:
                unit.load()
                try:
                    unit.Start(b'replace')
                except Exception as e:
                    print(e)
                    sg.popup_error('-E- Unable to Start Service! (do you have permission?)')
            elif event == self.stop_service:
                unit.load()
                try:
                    unit.Stop(b'replace')
                except Exception as e:
                    print(e)
                    sg.popup_error('-E- Unable to Stop Service! (do you have permission?)')
            elif event == self.connect_arduino or event == self.disconnect_arduino:
                self.invoke_arduino_connection(event)
            elif event == self.get_speed:
                self.update_fan_speed()
            elif event == self.update_speed:
                self.change_fan_power(new_power=int(self.set_new_speed_input.get()))

    def change_fan_power(self,direction=None,new_power=None):
        try:
            if not new_power:
                fan_speed = int(self.speed_value_text.get())
                print("Fan Speed: " + str(fan_speed))
        except Exception as e:
            print(e)
            self.arduino_disconnected()
            sg.popup_error("Failed to get current speed!\nCant update speed without beeing connected to arduino")
            return
        try:
            if direction == self.up_text:
                new_power = fan_speed + 1
                if new_power > 255:
                    new_power = 255
            elif direction == self.down_text:
                new_power = fan_speed - 1
                if new_power < 76:
                    new_power = 76
            self.new_power = str(new_power).rjust(3,'0')+"-"
            print("-D- Writting to arduino: " + self.new_power)
            self.arduino.write(self.new_power.encode())
            respons = self.arduino.read_until()
            print("Arduino responded with: " + str(respons))
        except Exception as e:
            print(e)
            self.arduino_disconnected()
            sg.popup_error("Failed to write to arduino!")

    def invoke_arduino_connection(self,action):
        if action == self.connect_arduino:
            try:
                self.serial_connect_arduino()
                self.arduino_connected = True
                self.arduino_connection_button.update(self.disconnect_arduino)
                self.up_button.update(disabled=False)
                self.down_button.update(disabled=False)
            except Exception as e:
                self.arduino_connected = False
                print(e)
                sg.popup_error("Failed to connect to arduino")
        elif action == self.disconnect_arduino:
            print("Disconnecting")
            try:
                self.arduino_disconnected()
                self.arduino
            except Exception as e:
                print(e)
                self.arduino_connected = False
                sg.popup_error("Failed to close connection to arduino")

    def arduino_disconnected(self):
        self.arduino_connected = False
        self.arduino_connection_button.update(self.connect_arduino)
        self.up_button.update(disabled=True)
        self.down_button.update(disabled=True)

    def update_GPUprotection(self):
        status = get_GPUprotection_status()
        if status:
            self.GPUprotection_status_colored.update("O",background_color="green")
            self.GPUprotection_status_text.update("Running")
            self.GPUprotection_button.update("Stop Service")
        else:
            self.GPUprotection_status_colored.update("X",background_color="red")
            self.GPUprotection_status_text.update("Stopped")
            self.GPUprotection_button.update("Turn On Service")

    def update_gpu_temp(self):
        self.gpu_temp_value.update(str(get_gpu_max_temp()))
        pass

    def update_fan_speed(self):
        self.new_power = get_fan_speed(self.arduino)
        self.speed_value_text.update(str(self.new_power))

    def serial_connect_arduino(self):
        self.arduino = serial.Serial(arduino_port, 9600,timeout=1)

def get_fan_speed(arduino):
    try:
        fan_speed = arduino.read_until()
        print("Got fan_speed from arduino: ",end='')
        print(fan_speed)
        fan_speed.decode("utf-8")
    except Exception as e:
        print(e)
        fan_speed = -1
    if not fan_speed:
        fan_speed = -1
    return int(fan_speed)

def get_GPUprotection_status():
    unit = Unit(b'gpu-protection.service')
    unit.load()
    if unit.ActiveState == b"active":
        return True
    return False

main()


import streamlit as st
import openai

# Set your OpenAI API key
openai.api_key = "YOUR_API_KEY"

def chat_with_gpt(prompt):
    response = openai.Completion.create(
        engine="text-davinci-003",
        prompt=prompt,
        max_tokens=50
    )
    return response.choices[0].text.strip()

def main():
    st.title("Chat with GPT-3")
    st.write("Enter a prompt to chat with the AI:")

    user_input = st.text_input("You:", value="", max_chars=200)

    if st.button("Send"):
        st.text("AI:")
        response = chat_with_gpt(user_input)
        st.write(response)

if __name__ == "__main__":
    main()


import argparse
import os
import shutil
import sys
import threading
import zipfile
from datetime import datetime
from os.path import basename

import patoolib
from PIL import Image
from pdf2jpg import pdf2jpg

Image.MAX_IMAGE_PIXELS = None   # disables the warning
from shutil import copyfile

defaultOutputFolder = datetime.now().strftime("%Y%m%d_%H%M%S")
fullOutputPath = os.path.join(os.path.dirname(os.path.realpath(__file__)), defaultOutputFolder)

def parser():
    """
    @brief    Parser function to get all the arguments
    """
    description = ""

    # Construct the argument parse and parse the arguments
    parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=description)
    parser.add_argument("-i", "--input", nargs='+', default=None,
                        help="path(s) to comics cbr, cbz, pdf or folder with images")
    parser.add_argument("-o", "--output", type=str, default="output",
                        help="path to output")
    parser.add_argument("--dontrename", action='store_true',
                        help="Name image files with included digits and added zeroes")
    parser.add_argument("--onlyextract", action='store_true',
                        help="Only extract files from input")
    parser.add_argument("--silence", action='store_true',
                        help="Don't do print outs.")
    parser.add_argument("--subfolders", action='store_true',
                        help="Instead of using multiple inputs just use all sub folders in folder")
    #parser.add_argument("--subfiles", action='store_true',
    #                    help="Instead of using multiple inputs just use all sub files in folder")
    print('\n' + str(parser.parse_args()) + '\n')

    return parser.parse_args()

def extract_cbr(filename, tmpdirname):
    patoolib.extract_archive(filename, outdir=tmpdirname)

def extract_cbz(filename, tmpdirname):
    zip_file = zipfile.ZipFile(filename, "r")
    zip_file.extractall(tmpdirname)
    zip_file.close()

def extract_pdf(filename, tmpdirname):
    result = pdf2jpg.convert_pdf2jpg(filename, tmpdirname, dpi=300, pages="ALL")
    if not result:
        print("WIN ERROR 2 ? - You will probably have to install JAVA and restart terminal or system")

def create_cbz(arguments, input, file_list, index):

    """
    @brief    Create the cbz file output
    """

    # Print
    print("Creating a new CBZ file:")

    # Output file name and print
    out_file_name = os.path.splitext(input.split(os.sep)[-1])[0]
    input_file_path = input.replace(input.split(os.sep)[-1], "")
    print(input_file_path)
    print("    %s.cbz" % os.path.join(input_file_path, out_file_name))

    # Create a zip object
    zipObj = zipfile.ZipFile(os.path.join(input_file_path, out_file_name)+".cbz", "w")

    # Zip all files
    for file in file_list:
        zipObj.write(file, basename(file))
    zipObj.close()

    # Delete the output folder
    if os.path.exists(arguments.output+str(index)):
        print(f"Clean up ({arguments.output + str(index)})")
        try:
            shutil.rmtree(arguments.output+str(index))
        except:
            print("Failed to remove %s" % arguments.output+str(index))

def fix_files(arguments, input, index):

    outputFolder = defaultOutputFolder+"_"+str(index)

    if os.path.splitext(input)[-1] == '.cbr':
        print("Extracting CBR...")
        if not os.path.exists(outputFolder):
            os.makedirs(outputFolder)
        if arguments.onlyextract:
            extract_cbr(input, os.path.splitext(input)[0])
            return
        else:
            extract_cbr(input, outputFolder)
    elif os.path.splitext(input)[-1] == '.cbz':
        print("Extracting CBZ...")
        if not os.path.exists(outputFolder):
            os.makedirs(outputFolder)
        if arguments.onlyextract:
            extract_pdf(input, os.path.splitext(input)[0])
            return
        else:
            extract_cbz(input, outputFolder)
    elif os.path.splitext(input)[-1] == '.pdf':
        print("Extracting PDF...")
        if arguments.onlyextract:
            extract_pdf(input, os.path.splitext(input)[0])
            return
        else:
            extract_pdf(input, outputFolder)
    elif os.path.isdir(input):
        print("Processing folder")
        shutil.copytree(input, outputFolder)
    else:
        print("Invalid input")
        return

    # Init a file list
    file_list = []

    # Fix all files in one folder with sub-folder names in each file.

    if not os.path.exists(arguments.output+str(index)):
        os.makedirs(arguments.output+str(index))

    # Go through all images
    for root, dirs, files in os.walk(outputFolder, topdown=False):
        for file in sorted(files):

            # Get directory name of last folder in tree unless defaultOutputFolder
            if root.split(os.sep)[-1] == outputFolder:
                last_dir = None
            else:
                last_dir = root.split(os.sep)[-1].replace(" ", "_")

            # Get file extension
            file_ext = os.path.splitext(file)[-1]

            # Rename images is chosen (cbz don't like 1.jpg and 10.jpg so zeroes should be added)
            if arguments.dontrename:
                file_name = os.path.splitext(file)[0]
            else:
                if last_dir == None:
                    file_name = "%04d" % (int(''.join(filter(str.isdigit, file))))
                else:
                    file_name = "%03d%04d" % (int(''.join(filter(str.isdigit, last_dir))), int(''.join(filter(str.isdigit, file))))

            # Determine new file name and print it out to see the progress
            new_file_name = file_name + file_ext
            if not arguments.silence:
                print(f"Creating {new_file_name}")

            # Determine source path for upcoming copy
            source = os.path.join(root, file)

            # Determine destination for upcoming copy
            destination = os.path.join(arguments.output+str(index), new_file_name)

            # Copy file and check if file already exist, and exit if duplicate, because we will miss a file if we overwrite.
            copyfile(source, destination)
            if destination in file_list:
                print("ERROR: Duplicate filename")
                sys.exit()

            # Add the file to a list of files to return
            file_list.append(destination)

            # Convert file to RGB (readers don't like files with alpha channels or something)
            im = Image.open(destination).convert('RGB')
            im.save(destination)
            im.close()

    # Remove folders
    """
    if os.path.exists(fullOutputPath):
        print("Clean up (%s)" % fullOutputPath)
        try:
            shutil.rmtree(fullOutputPath)
        except:
            print("Failed to remove %s" % fullOutputPath)
    """
    if os.path.exists(outputFolder):
        print("Clean up (%s)" % outputFolder)
        try:
            shutil.rmtree(outputFolder)
        except:
            print("Failed to remove %s" % outputFolder)

    if not args.onlyextract:
        create_cbz(args, input, file_list, index)

if __name__ == "__main__":

    """
    @brief    Main entry point
    """

    args = parser()

    threads = []

    # If sub folders are used, save all sub folders in the args.input
    if args.subfolders:
        sub_folders = []
        for sub_folder, _, _ in os.walk(args.input[0]):
            if sub_folder is not args.input[0]:
                print(sub_folder)
                sub_folders.append(sub_folder)
        args.input = sub_folders

    for item in args.input:
        thread = threading.Thread(target=fix_files, args=(args, item, args.input.index(item)))
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

        #files = fix_files(args, item)
        #if not args.onlyextract:
        #    create_cbz(args, item, files)


from collections import deque

name_of_player = input().split(' ')
step_of_hot_potato = int(input())

players_deque = deque(name_of_player)
counter = 0

while len(players_deque) > 1:
    counter += 1
    current_name_of_player = players_deque.popleft()

    if counter == step_of_hot_potato:
        print(f'Removed {current_name_of_player}')
        counter = 0
    else:
        players_deque.append(current_name_of_player)

print(f'Last is {players_deque[0]}')

# names = input().split()
# n = int(input())
# queue = []
#
# for name in names:
#     queue.append(name)
#
# count = 0
#
# while len(queue) > CNN by Keras:
#     name = queue.pop(0)
#     if count == n - CNN by Keras:
#         print("Removed " + name)
#         count = 0
#     else:
#         queue.append(name)
#         count += CNN by Keras
#
# print("Last is " + queue[0])

1.Lab/01.Lists as Stacks and Queues - Lab/05. Hot Potato.py


from typing import Optional, Tuple
from compute_graph.IR.symbols import  IR_LooseFunction,  IR_SingleAssign, IR_Symbol, IR_Variable
from compute_graph.IR.transform.base import IR_Transfrom

class RemoveForwardAlias(IR_Transfrom):
    def __init__(self):
        super().__init__()

    def tf(self, in_IR: IR_Symbol)->IR_Symbol:
        working_ir = self.assert_and_cast_symbol_type(in_IR, IR_LooseFunction)

        line_var_cache = [l.get_instances(IR_Variable) for l in working_ir.body]
        next_alias = self._get_next_alias(working_ir, 0)
        while next_alias is not None:
            alias, var, line_idx = next_alias
            working_ir.body = working_ir.body[:line_idx] + working_ir.body[line_idx+1:]
            line_var_cache = line_var_cache[:line_idx] + line_var_cache[line_idx+1:]

            for i in  range(line_idx, len(working_ir.body)):
                if alias in line_var_cache[i]:
                    working_ir.body[i].replace(alias, var)
                    line_var_cache[i].remove(alias)
                    line_var_cache[i].add(var)

            next_alias = self._get_next_alias(working_ir, line_idx)

        return working_ir

    def _get_next_alias(self, func: IR_LooseFunction, start_line: int)->Optional[Tuple[IR_Variable, IR_Variable, int]]:
        for idx in range(start_line, len(func.body)):
            l = func.body[idx]
            if not isinstance(l, IR_SingleAssign): continue

            left = l.assign_var
            right = l.expr

            if not isinstance(right, IR_Variable): continue
            if left in func.out_var: continue

            return (left, right, idx)

        return None

class RemoveBackwardsAlias(IR_Transfrom):
    def __init__(self):
        super().__init__()

    def tf(self, in_IR: IR_Symbol)->IR_Symbol:
        working_ir = self.assert_and_cast_symbol_type(in_IR, IR_LooseFunction)

        next_alias = self._get_next_alias(working_ir)
        while next_alias is not None:
            var, alias, line_idx = next_alias
            working_ir.body = working_ir.body[:line_idx] + working_ir.body[line_idx+1:]
            working_ir.replace(alias, var)

            next_alias = self._get_next_alias(working_ir)

        return working_ir

    def _get_next_alias(self, func: IR_LooseFunction)->Optional[Tuple[IR_Variable, IR_Variable, int]]:
        for idx, l in reversed(list(enumerate(func.body))):
            if not isinstance(l, IR_SingleAssign): continue

            left = l.assign_var
            right = l.expr

            if not isinstance(right, IR_Variable): continue
            if left not in func.out_var: continue
            if right in func.in_var: continue

            return (left, right, idx)

        return None


import datetime as dt

from absl.testing import absltest
from absl.testing import parameterized
from balloon_learning_environment.utils import units
import numpy as np

# Disabling linter to explicitly test comparison operators operators.
# pylint: disable=g-generic-assert
class DistanceTest(parameterized.TestCase):

  def test_distance_adds_all_distances_in_constructor(self):
    d = units.Distance(m=1000.0, meters=1100.0, km=1.2, kilometers=1.3)

    self.assertEqual(d.m, 4_600.0)

  def test_distance_converts_meters_to_kilometers(self):
    d = units.Distance(m=1525.0)

    self.assertEqual(d.km, 1.525)
    self.assertEqual(d.kilometers, 1.525)

  def test_distance_converts_kilometers_to_meters(self):
    d = units.Distance(km=3.876)

    self.assertEqual(d.m, 3_876)
    self.assertEqual(d.meters, 3_876)

  def test_distance_converts_between_meters_and_feet(self):
    d = units.Distance(feet=1.0)
    d2 = units.Distance(meters=d.meters)

    self.assertEqual(d.meters, 0.3048)
    self.assertEqual(d2.feet, 1.0)

  def test_distance_divided_by_time_gives_velocity(self):
    d = units.Distance(m=10.0)
    t = dt.timedelta(seconds=5.0)

    vel = d / t
    self.assertIsInstance(vel, units.Velocity)
    self.assertEqual(vel.mps, 2.0)

  def test_distance_divided_by_float_gives_distance(self):
    d = units.Distance(m=10.0)

    d = d / 2.0
    self.assertIsInstance(d, units.Distance)
    self.assertEqual(d.m, 5.0)

  def test_distance_divided_by_distance_gives_float(self):
    d1 = units.Distance(m=10.0)
    d2 = units.Distance(m=5.0)

    result = d1 / d2

    self.assertIsInstance(result, float)
    self.assertEqual(result, 2.0)

  @parameterized.named_parameters(
      dict(testcase_name='lhs', lhs=True), dict(testcase_name='rhs', lhs=False))
  def test_distance_multiplied_by_float_gives_distance(self, lhs):
    d = units.Distance(m=2.5)

    if lhs:
      d = d * 4
    else:
      d = 4 * d

    self.assertEqual(d.m, 10.0)

  def test_distances_are_equal(self):
    d1 = units.Distance(km=1.5)
    d2 = units.Distance(m=1_500.0)

    self.assertEqual(d1, d2)

  def test_distance_are_not_equal(self):
    d1 = units.Distance(m=1.1)
    d2 = units.Distance(m=1.10001)

    self.assertNotEqual(d1, d2)

  def test_distance_less_than(self):
    d1 = units.Distance(m=1.0)
    d2 = units.Distance(m=1.0)
    d3 = units.Distance(m=1.1)

    self.assertFalse(d1 < d2)
    self.assertTrue(d1 < d3)

  def test_distance_less_than_or_equal(self):
    d1 = units.Distance(m=1.0)
    d2 = units.Distance(m=1.0)
    d3 = units.Distance(m=1.1)

    self.assertTrue(d1 <= d2)
    self.assertTrue(d1 <= d3)
    self.assertFalse(d3 <= d1)

  def test_distance_greater_than(self):
    d1 = units.Distance(m=1.0)
    d2 = units.Distance(m=1.0)
    d3 = units.Distance(m=1.1)

    self.assertFalse(d1 > d2)
    self.assertTrue(d3 > d1)

  def test_distance_greater_than_or_equal(self):
    d1 = units.Distance(m=1.0)
    d2 = units.Distance(m=1.0)
    d3 = units.Distance(m=1.1)

    self.assertTrue(d1 >= d2)
    self.assertTrue(d3 >= d1)
    self.assertFalse(d1 >= d3)

class VelocityTest(parameterized.TestCase):

  def test_velocity_adds_all_velocities_in_constructor(self):
    v = units.Velocity(
        mps=3, meters_per_second=4, kmph=3.6, kilometers_per_hour=7.2)

    self.assertEqual(v.mps, 10.0)

  def test_velocity_converts_mps_to_kmph(self):
    v = units.Velocity(mps=1.0)

    self.assertEqual(v.kmph, 3.6)
    self.assertEqual(v.kilometers_per_hour, 3.6)

  def test_velocity_converts_kmph_to_mps(self):
    v = units.Velocity(kmph=3.6)

    self.assertEqual(v.mps, 1.0)
    self.assertEqual(v.meters_per_second, 1.0)

  def test_velocities_add(self):
    v1 = units.Velocity(mps=3.5)
    v2 = units.Velocity(mps=9.3)

    v = v1 + v2
    self.assertEqual(v.mps, 3.5 + 9.3)

  def test_velocities_subtract(self):
    v1 = units.Velocity(mps=3.5)
    v2 = units.Velocity(mps=9.3)

    v = v1 - v2
    self.assertEqual(v.mps, 3.5 - 9.3)

  def test_equality_of_velocities(self):
    v1 = units.Velocity(mps=2.5)
    v2 = units.Velocity(mps=2.5)
    v3 = units.Velocity(mps=2.51)

    self.assertTrue(v1 == v2)
    self.assertFalse(v1 == v3)

  @parameterized.named_parameters(
      dict(testcase_name='lhs', lhs=True), dict(testcase_name='rhs', lhs=False))
  def test_velocity_multiplies_with_timedelta(self, lhs: bool):
    v = units.Velocity(mps=10.0)
    change_in_time = dt.timedelta(seconds=20.0)

    if lhs:
      d = v * change_in_time
    else:
      d = change_in_time * v

    self.assertIsInstance(d, units.Distance)
    self.assertAlmostEqual(d.m, 200.0)

  def test_velocity_divided_by_float_gives_velocity(self):
    v = units.Velocity(mps=10.0)

    v = v / 2.0
    self.assertIsInstance(v, units.Velocity)
    self.assertEqual(v.mps, 5.0)

class EnergyTest(absltest.TestCase):

  def test_energies_add(self):
    e1 = units.Energy(watt_hours=1.5)
    e2 = units.Energy(watt_hours=4.5)

    self.assertEqual((e1 + e2).watt_hours, 6.0)

  def test_energies_subtract(self):
    e1 = units.Energy(watt_hours=4.0)
    e2 = units.Energy(watt_hours=1.5)

    self.assertEqual((e1 - e2).watt_hours, 2.5)

  def test_energies_divide(self):
    e1 = units.Energy(watt_hours=9.0)
    e2 = units.Energy(watt_hours=3.0)

    # Dividing energies gives a ratio, which doesn't have a unit.
    self.assertEqual(e1 / e2, 3.0)

  def test_energies_multiply_with_float(self):
    e = units.Energy(watt_hours=2.0)
    c = 5.0

    # We should be able to left- and right-multiply.
    e = e * c
    e = c * e

    self.assertEqual(e.watt_hours, 50.0)

  def test_energy_greater_than(self):
    e1 = units.Energy(watt_hours=5.0)
    e2 = units.Energy(watt_hours=5.5)
    e3 = units.Energy(watt_hours=6.0)

    self.assertTrue(e2 > e1)
    self.assertFalse(e1 > e3)

  def test_energy_greater_than_or_equal_to(self):
    e1 = units.Energy(watt_hours=5.0)
    e2 = units.Energy(watt_hours=5.0)
    e3 = units.Energy(watt_hours=5.01)

    self.assertTrue(e2 >= e1)
    self.assertTrue(e3 >= e1)
    self.assertFalse(e1 >= e3)

  def test_equality_of_energies(self):
    e1 = units.Energy(watt_hours=3.0)
    e2 = units.Energy(watt_hours=3.0)
    e3 = units.Energy(watt_hours=3.001)

    self.assertTrue(e1 == e2)
    self.assertFalse(e1 == e3)

class PowerTest(absltest.TestCase):

  def test_powers_add(self):
    p1 = units.Power(watts=3.5)
    p2 = units.Power(watts=4.5)

    self.assertEqual((p1 + p2).watts, 8.0)

  def test_powers_subtract(self):
    p1 = units.Power(watts=5.5)
    p2 = units.Power(watts=3.0)

    self.assertEqual((p1 - p2).watts, 2.5)

  def test_powers_multiply_with_time(self):
    # Energy = power * time
    p = units.Power(watts=5.0)
    delta_time = dt.timedelta(hours=2.0)

    energy = p * delta_time

    self.assertIsInstance(energy, units.Energy)
    self.assertEqual(energy.watt_hours, 10.0)

  def test_power_greather_than(self):
    p1 = units.Power(watts=5.0)
    p2 = units.Power(watts=5.5)
    p3 = units.Power(watts=6.0)

    self.assertTrue(p2 > p1)
    self.assertFalse(p1 > p3)

  def test_equality_of_powers(self):
    p1 = units.Power(watts=3.0)
    p2 = units.Power(watts=3.0)
    p3 = units.Power(watts=3.001)

    self.assertTrue(p1 == p2)
    self.assertFalse(p1 == p3)

# pylint: enable=g-generic-assert

class UnitsTest(absltest.TestCase):

  def test_111_km_should_be_one_degree(self):
    degrees = units.distance_to_degrees(units.Distance(km=111.0))

    self.assertEqual(degrees, 1.0)

  def test_relative_distance(self):
    # Distance objects consider very close distances as equal.
    self.assertEqual(
        units.relative_distance(units.Distance(m=1.0), units.Distance(m=1.0)),
        units.Distance(m=np.sqrt(2)))

  def test_seconds_to_hours(self):
    self.assertEqual(units.seconds_to_hours(7200.0), 2.0)

  def test_timedelta_to_hours(self):
    hours = 6.39
    delta = dt.timedelta(hours=hours)
    self.assertEqual(units.timedelta_to_hours(delta), hours)

if __name__ == '__main__':
  absltest.main()


import json
import os

import pandas as pd

from autumn.settings import Region

base_dir = os.path.dirname(os.path.abspath(os.curdir))
HOSP_DATA_FILE = os.path.join(
    base_dir, "data", "inputs", "hospitalisation_data", "european_data.csv"
)

COUNTRY_TARGETS = {
    Region.BELGIUM: ["bel_hosp_adm_per_100K", "new_hospital_admissions"],
    Region.FRANCE: ["fra_hosp_adm_per_100K", "new_hospital_admissions"],
    Region.ITALY: ["ita_hosp_adm_per_100K", "new_hospital_admissions"],
    Region.SPAIN: ["spa_hosp_adm_per_100K", "new_hospital_admissions"],
    Region.SWEDEN: ["swe_icu_adm_per_100K", "new_icu_admissions"],
    Region.UNITED_KINGDOM: ["uk_hosp_adm", "new_hospital_admissions"],
}

POPULATION = {
    Region.BELGIUM: 11589623,
    Region.FRANCE: 65273511,
    Region.ITALY: 60461826,
    Region.SPAIN: 46754778,
    Region.SWEDEN: 10099265,
    Region.UNITED_KINGDOM: 67886011,  # UK not required
}

def convert_year_week_to_day(year_week):
    """
    return a day representing the 4th week defined by year_week
    :param year_week: string such as "2020-W12"
    :param reference_date: date corresponding to time t=0
    :return: integer representing the 4th day of the week
    """
    year = year_week.split("-")[0]
    week = year_week.split("W")[1]

    # work out out date_int, which is the integer representing the first day of the selected week
    ref_integer = -8 if year == "2020" else 363
    date_int = ref_integer + 7 * float(week) + 3

    return date_int

def read_hospital_data(country, data_start_time=0, data_end_time=365):
    path = HOSP_DATA_FILE
    data = pd.read_csv(path)

    mask = pd.notnull(data[COUNTRY_TARGETS[country][0]])
    year_weeks = list(data[mask]["year_week"])
    values = [float(v) for v in data[mask][COUNTRY_TARGETS[country][0]]]

    times = [convert_year_week_to_day(y_w) for y_w in year_weeks]

    min_hosp_time = min(times)
    data_start_time = max(min_hosp_time, data_start_time)
    start_index = next(x[0] for x in enumerate(times) if x[1] >= data_start_time)

    max_hosp_time = max(times)
    data_end_time = min(max_hosp_time, data_end_time)
    end_index = next(x[0] for x in enumerate(times) if x[1] >= data_end_time) - 1

    times = times[start_index : end_index + 1]
    values = values[start_index : end_index + 1]

    return times, values

def write_hospital_data_to_timeseries(country, data_start_time=0, data_end_time=365):
    region = country if country != Region.UNITED_KINGDOM else "united_kingdom"
    target_path = os.path.join(
        "../autumn",
        "projects",
        "covid_19",
        "mixing_optimisation",
        "regions",
        region,
        "timeseries.json",
    )

    times, values = read_hospital_data(country, data_start_time, data_end_time)

    # UK's data represents the average daily number of new hospitalisation for each week.
    # For the other countries, this is total number of new hospitalisations for each week, expressed per 100k people
    if country != Region.UNITED_KINGDOM:
        population = POPULATION[country]
        values = [v / 7 * population / 1.0e5 for v in values]

    with open(target_path, mode="r") as f:
        targets = json.load(f)
        targets[COUNTRY_TARGETS[country][1]]["times"] = times
        targets[COUNTRY_TARGETS[country][1]]["values"] = values
    with open(target_path, "w") as f:
        json.dump(targets, f, indent=2)

if __name__ == "__main__":
    for country in Region.MIXING_OPTI_REGIONS:
        write_hospital_data_to_timeseries(country, data_end_time=365)


import os
import random
import re
import sqlite3
import time
import importlib
import sys
import asyncio
import demoji

import discord
from discord.ext import tasks, commands

import checks
import texts
from common import clamp, idPattern, emojiPattern, customEmojiPattern, send_message, print_error, escuchameUrl, pokeballUrl, TYPE_COLORS, DBL_BREAK, EMOJI, ICON_CLOSE, enquote
from trainers import ign_as_text, fc_as_text

from .config import RAID_EMOJI, LOCKED_EMOJI, CLOSED_EMOJI, \
                    DEFAULT, LOCKED, CLOSED, \
                    BALANCED, FFA, QUEUE

from .raid import Raid, RaidRecord

class Cog(commands.Cog):
    def __init__(self, bot):
        # self.db = db
        self.bot = bot
        self.cache = {}
        self.db = sqlite3.connect('raid.db')

        self.category = None
        self.archive = None
        self.guild = None
        self.listing_channel = None
        self.thanks_channel = None
        self.log_channel = None
        self.announce_channels = []
        self.admin_roles = []
        self.creating_enabled = True
        self.loaded = False
        self.max_raids = 10

        self.raids = {}

        # debug
        self.task_n = 0

        if self.bot.is_ready():
            self.bot.loop.create_task(self.on_load())

    def cog_unload(self):
        print('[Unload] Raid Cog Teardown')
        self.bot.loop.create_task(self.save_raids_to_db())

    async def on_load(self):
        self.loaded = False

        configured = self.configure()
        while not configured:
            await asyncio.sleep(5)
            configured = self.configure()

        print('[DB] Init')
        self.make_db()
        await self.load_raids_from_db()
        try:
            self.save_interval.start()
        except Exception as e:
            print('Attempting to restart, save Interval Exception: {type(e).__name__}, {e}')
            await self.bot.misc.execute_restart(by_command=False)
        self.loaded = True

    def reload_deps(self):
        if self.bot.config.get('disable_hot_reload', False):
            return

        importlib.reload(sys.modules['raid.pool'])
        importlib.reload(sys.modules['raid.raid'])

    def make_db(self):
        c = self.db.cursor()
        c.execute('''create table if not exists raids (
            host_id integer not null,
            guild_id integer not null,
            raid_name text not null,
            mode integer not null,
            private integer not null,
            allow_mb integer not null,
            channel_id integer not null,
            raid_emoji text,
            desc text,
            listing_msg_id integer not null,
            last_round_msg_id integer,
            round integer not null,
            max_joins integer not null,
            pool text,
            ups text,
            start_time integer not null,
            time_saved integer,
            code text,
            locked integer not null,
            closed integer not null,
            primary key(host_id)
        )''')
        self.db.commit()
        c.close()

    def remake_db(self):
        self.db.close()
        try:
            os.remove('raid.db')
        except:
            pass
        self.db = sqlite3.connect('raid.db')
        self.make_db()

    async def load_raids_from_db(self):
        # print(f'[DB] Attempting to load raids...')
        if self.raids:
            print('[DB ERROR] `self.raids` already exists, should be empty')
            await self.bot.misc.execute_restart(by_command=False)

        t = time.time()
        c = self.db.cursor()
        c.execute('select host_id, guild_id, raid_name, mode, private, allow_mb, channel_id, raid_emoji, desc, listing_msg_id, last_round_msg_id, round, max_joins, pool, ups, start_time, time_saved, code, locked, closed from raids')
        for r in map(RaidRecord._make, c.fetchall()):
            raid = Raid(self.bot, self, self.guild, r.host_id)
            loaded, err = await raid.load_from_record(r)
            print('raid:', raid)
            print(await raid.as_record())
            if not loaded:
                print(f'Did not succesfully load {r.raid_name}, error: {err}')
                if raid.channel and not raid.closed:
                    await raid.channel.send(f'❌ During a botte restart, this raid failed to load and will have to be remade. This is likely a Discord issue, but let <@232650437617123328> know. The following error occured:\n\n```{err}```')
            else:
                self.raids[raid.host_id] = raid
                print(f'Succesfully loaded {r.raid_name}')
                if raid.closed:
                    await raid.archive()
                    print('...and immediately archived it!')
                else:
                    t = int(time.time())
                    delta = t - raid.time_saved
                    if delta < 60:
                        ago = f'{delta} second(s)'
                    else:
                        ago = f'{delta//60} minute(s)'

                    await raid.channel.send(f'🔄 Botte Reloaded! Restored raid data from ~{ago} ago.')

        c.close()
        print(f'[DB] Loaded {len(self.raids)} raids from database. Took {time.time() - t:.3f} seconds')

    async def save_raids_to_db(self):
        # print(f'[DB] Attempting to save raids...')
        # t = time.time()
        print('[DB] Starting saving raids to DB.')
        c = self.db.cursor()
        c.execute('delete from raids')
        for raid in self.raids.values():
            try:
                record, can_save = await raid.as_record()
            except Exception as e:
                print(f'[Raid Serialize] Could not serialize raid {raid.raid_name}; this will be lost! Error: {type(e).__name__}, {e}')
                continue

            if not can_save:
                print(f'[DB] Skipped saving record {record}')
                continue

            print(f'[DB] Saving record {record} to database')
            try:
                c.execute('insert into raids values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', record)
            except sqlite3.Error as e:
                print(f'[SQL Insert Error] Could not insert raid {record.raid_name}! Error: {type(e).__name__}, {e}')

        self.db.commit()
        c.close()
        print('[DB] Save complete.')

    @commands.Cog.listener()
    async def on_ready(self):
        await self.on_load()

    @tasks.loop(minutes=3.0)
    async def save_interval(self):
        print('[Task] Periodic Save')
        await self.save_raids_to_db()

    # @commands.Cog.listener()
    # async def on_reaction_add(self, reaction, user):
    #     await self.reaction_action('add', reaction, user)
    #
    # @commands.Cog.listener()
    # async def on_reaction_remove(self, reaction, user):
    #     await self.reaction_action('remove', reaction, user)

    @commands.Cog.listener()
    async def on_raw_reaction_add(self, payload):
        if self.loaded:
            await self.reaction_action('add', payload)

    @commands.Cog.listener()
    async def on_raw_reaction_remove(self, payload):
        if self.loaded:
            await self.reaction_action('remove', payload)

    async def reaction_action(self, action, payload):

        if not self.listing_channel:
            return

        if payload.user_id == self.bot.user.id:
            return

        if payload.channel_id != self.listing_channel.id:
            return

        user = self.bot.get_user(payload.user_id)
        if not user:
            return

        if str(payload.emoji) not in [EMOJI['pokeball'], EMOJI['masterball']]:
            await self.bot.misc.remove_raw_reaction(payload, user)
            return

        target_raid = None
        for host_id, raid in self.raids.items():
            if raid and raid.listing_message and raid.listing_message.id == payload.message_id:
                target_raid = raid
                break

        if not target_raid:
            return

        if not target_raid.allow_mb and str(payload.emoji) == EMOJI['masterball']:
            return

        if action == 'add':
            join_type = 'mb' if str(payload.emoji) == EMOJI['masterball'] else 'pb'
            return await target_raid.user_join(user, join_type, payload)

        if action == 'remove':
            return await target_raid.user_leave(user)

    # @commands.group(invoke_without_command=True)
    # async def raid(self, ctx, arg=None):
    #     pass
    #     # message = await send_message(ctx, 'Top Level Raid')

    '''
    host
    '''

    # @commands.has_role('raider')
    @commands.command()
    async def host(self, ctx, *, arg=None):

        if not self.creating_enabled and ctx.author.id not in self.bot.config['creator_ids']:
            return await send_message(ctx, 'Creating new raids is temporarily paused (probably pending a botte update, check the botte status channel).', error=True)

        if arg is None or 'help' in arg:
            msg = f'''{texts.CREATE_HELP}

_Managing a Raid_
{texts.HOST_COMMANDS}
'''
            return await send_message(ctx, msg)

        if len(self.raids) >= self.max_raids:
            return await send_message(ctx,
                                      f'Unfortunately, we already have the maximum number of **{self.max_raids}** raids being hosted.',
                                      error=True)

        uid = ctx.author.id
        if uid in self.raids:
            if self.raids[uid].confirmed:
                return await send_message(ctx, 'You are already configuring or hosting a raid.', error=True)
            self.raids[uid].clear_wait_fors()
            del self.raids[uid]

        can_host, err = await self.bot.trainers.can_host(ctx.author, ctx)
        if not can_host:
            msg = texts.make_error_msg(err, uid)
            return await send_message(ctx, msg, error=True)

        # todo BALANCED
        options = {
            'mode': FFA,
            'max_joins': 30,
            'private': False,
            'locked': False,
            'emoji': None
        }

        # try:
        #     mode_arg, arg = arg.split(' ', 1)
        #     try:
        #         mode = {
        #             # 'balanced': BALANCED,
        #             'ffa': FFA,
        #             'queue': QUEUE,
        #         }[mode_arg.lower()]
        #     except KeyError:
        #         pass
        # except ValueError:
        #     pass

        # m = re.search(customEmojiPattern, arg)

        m = re.search(r' max[\s=](\d*)', arg)
        if m:
            try:
                options['max_joins'] = int(m.group(1))
            except ValueError:
                return await send_message(ctx, 'Invalid value for max raiders. Include `max n` or leave it out for `max 30`', error=True)
            arg = arg.replace(m.group(0), '')

        m = re.search(r'[\"“‟”’❝❞＂‘‛❛❜\'](.*)[\"“‟”’❝❞＂‘‛❛❜\']', arg, flags=re.DOTALL)
        if m:
            options['desc'] = m.group(1)
            arg = arg.replace(m.group(0), '')

        # if 'private' in arg:
        #     options['private'] = True
        #     arg = arg.replace('private', '')
        #
        # if 'locked' in arg:
        #     options['locked'] = True
        #     arg = arg.replace('locked', '')

        m = re.search(customEmojiPattern, arg)
        if m:
            return await send_message(ctx, 'You cannot put images (custom emoji) into channel names on Discord.', error=True)

        options['raid_name'] = arg.strip()
        m = re.match(emojiPattern, options['raid_name'])
        if m:
            options['emoji'] = options['raid_name'][0]
            options['raid_name'] = options['raid_name'][1:].strip()
            if options['emoji'] in [LOCKED_EMOJI, CLOSED_EMOJI]:
                options['emoji'] = None

        if 'ffa' in options['raid_name'].lower():
            options['mode'] = FFA
        # elif options['mode'] == FFA:
        #     options['name'] = f'''ffa {options['raid_name']}'''

        # channel_name = f"{RAID_EMOJI}┊{options['raid_name'].replace(' ', '-')}"[:100]
        # options['channel_name'] = re.sub('[<>]', '', channel_name)

        self.raids[uid] = Raid(self.bot, self, self.guild, ctx.author.id)
        print('[Raid Creation]', self.raids)

        await self.raids[uid].send_confirm_prompt(ctx, options)

    @host.error
    async def host_error(self, ctx, error):
        if isinstance(error, commands.CheckFailure):
            await ctx.send('oopsie poopsie')
        else:
            print_error(ctx, error)

    async def cancel_before_confirm(self, raid, channel, msg=''):
        print(f'[Raid Cancel] {self} was cancelled before it was confirmed.')
        del self.raids[raid.host_id]
        raid.clear_wait_fors()
        await channel.send(f"<@{raid.host_id}> {msg}Your raid was cancelled. {EMOJI['flop']}")

    @commands.command()
    async def round(self, ctx, *, arg=None):
        uid = ctx.author.id
        if uid in self.raids:
            return await self.raids[uid].round_command(ctx, arg)

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    @commands.command(aliases=['end'])
    async def close(self, ctx, arg=''):
        uid = ctx.author.id
        is_bot_admin = checks.check_bot_admin(ctx.author, ctx.bot)
        if uid in self.raids:
            if self.raids[uid].channel == ctx.channel:
                return await self.raids[uid].end(ctx, is_bot_admin and ('immediately' in arg or 'now' in arg))

        if is_bot_admin:
            for host_id, raid in self.raids.items():
                if raid and raid.channel == ctx.channel:
                    return await raid.end(ctx, 'immediately' in arg or 'now' in arg)

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    @commands.command()
    async def up(self, ctx, *, arg=None):
        target_raid = None
        is_host = False
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                is_host = ctx.author.id == host_id
                target_raid = raid
                break

        if not target_raid:
            return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

        if not is_host:
            if len(target_raid.ups) == 0:
                msg = f'''The host hasn't told me what the current Pokémon is! They can use `.up pokémon` to tell me!'''
            else:
                msg = f'''The current Pokémon is **{target_raid.ups[-1]}**! Here's a list of all so far: {target_raid.ups}'''
            return await ctx.send(msg)

        await target_raid.up_command(ctx, arg)

    # todo
    @commands.command()
    async def remake(self, ctx, *, arg=None):
        pass

    '''
    host moderation
    '''

    @commands.command(name='group')
    async def group_message(self, ctx, *, arg=None):
        uid = ctx.author.id
        if uid not in self.raids:
            return

        if not arg:
            return await send_message(ctx, f'Type a message that you\'d like to relay to the current round\'s group.',
                                      error=True)

        raid = self.raids[uid]
        if not raid.pool.group:
            return await send_message(ctx, 'No active group! Type `.round` to start one.', error=True)

        mentions = []
        for i, raider in enumerate(raid.pool.group):
            join_type, raider_id = raider
            mention = f'<@{raider_id}>'
            mentions.append(mention)

        msg = f"{' '.join(mentions)} 📣 Message from host:\n{enquote(arg)}"
        await ctx.send(msg)

    @commands.command()
    async def skip(self, ctx, *, arg=None):
        await self.host_moderation(ctx, 'skip', arg)

    @commands.command(aliases=['remove'])
    async def kick(self, ctx, *, arg=None):
        await self.host_moderation(ctx, 'remove', arg)

    @commands.command()
    async def block(self, ctx, *, arg=None):
        await self.host_moderation(ctx, 'block', arg)

    async def host_moderation(self, ctx, action, arg):
        if not arg:
            return

        target_raid = None
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                is_host = ctx.author.id == host_id
                is_bot_admin = checks.check_bot_admin(ctx.author, ctx.bot)

                if not is_bot_admin and not is_host:
                    return await send_message(ctx, 'Host or admin only. Use `.q` instead.', error=True)
                target_raid = raid
                break

        if not target_raid:
            return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

        match = idPattern.search(arg)
        if not match:
            return await send_message(ctx, f'Please tag the member you\'d like to {action}', error=True)

        mention_id = int(match.group(1))
        member = ctx.message.guild.get_member(mention_id)
        if not member:
            return await send_message(ctx, f'Could not remove user! Please contact the mods to manually intervene.',
                                      error=True)

        if member == self.bot.user or member.id == target_raid.host_id:
            return await send_message(ctx, '', error=True, image_url=escuchameUrl)

        if action == 'skip':
            return await target_raid.skip(member)

        if action == 'remove':
            return await target_raid.kick(member, ctx)

        if action == 'block':
            await ctx.send(f'`.block` is not yet implemented, but for now we will attempt to `.remove` the user.')
            return await target_raid.kick(member, ctx)

    @commands.command()
    async def max(self, ctx, *, arg=''):
        uid = ctx.author.id
        if uid not in self.raids:
            return

        raid = self.raids[uid]
        if raid.channel != ctx.channel:
            return await send_message(ctx, f'You may only adjust this in your active raid channel.', error=True)

        if not arg:
            return await send_message(ctx, f'Please enter a number, i.e. `.max 18`', error=True)

        try:
            n = int(arg)
        except ValueError:
            return await send_message(ctx, f'Please enter a number, i.e. `.max 18`', error=True)

        n = clamp(n, 3, 30 if raid.mode == QUEUE else 50)
        size = raid.pool.size()
        if n < size:
            return await send_message(ctx, f'You cannot go lower than the current pool size ({size}).', error=True)

        if raid.max_joins == n:
            return await send_message(ctx, f'That\'s... already the max.', error=True)

        verb = 'increased' if raid.max_joins < n else 'decreased'
        raid.max_joins = n
        await raid.update_channel_emoji()
        await ctx.send(f'Max participants {verb} to **{n}**.')

    @commands.command()
    async def lock(self, ctx):
        target_raid = None
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                is_host = ctx.author.id == host_id
                is_bot_admin = checks.check_bot_admin(ctx.author, ctx.bot)

                if not is_bot_admin and not is_host:
                    return
                target_raid = raid
                break

        if not target_raid:
            return await send_message(ctx, f'You may only do this in your active raid channel.', error=True)

        target_raid.locked = not target_raid.locked

        if target_raid.locked:
            msg = 'Raid is now **locked**, the raid will continue but no new raiders will be able to join until you type `.lock` again.'
        else:
            msg = 'Raid is now **unlocked**, new raiders can join again (if there is room).'

        await target_raid.update_channel_emoji()
        await ctx.send(msg)

    @commands.command()
    async def private(self, ctx):
        uid = ctx.author.id
        if uid not in self.raids:
            return

        raid = self.raids[uid]
        if raid.channel != ctx.channel:
            return await send_message(ctx, f'You may only do this in your active raid channel.', error=True)

        raid.private = not raid.private

        if raid.private:
            raid.code = None
            msg = 'Raid is now **private**, which means the `.code` will be hidden. I\'ve removed the old one, so please set a secret one in the form `.code 1234 5678`. If you\'ve pinned it, you might want to `.unpin` it. Type `.private` again to toggle.'
        else:
            msg = f'''Raid is no longer private, which means the code is public again (it's **{raid.code}**). Type `.private` again to toggle.'''

        await ctx.send(msg)

    @commands.command()
    async def rename(self, ctx, *, arg=''):
        name = arg.strip()[:100]
        if not name:
            return await send_message(ctx, f'You have to specify a new channel name.', error=True)

        m = re.search(customEmojiPattern, name)
        if m:
            return await send_message(ctx, 'You cannot put images (custom emoji) into channel names on Discord.', error=True)

        emoji = None
        m = re.match(emojiPattern, name)
        print(name)
        if m:
            print('match')
            emoji = name[0]
            print(emoji)
            if emoji in [LOCKED_EMOJI, CLOSED_EMOJI]:
                emoji = RAID_EMOJI
            name = name[1:].strip()
            print(name)

        target_raid = None
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                is_host = ctx.author.id == host_id
                is_bot_admin = checks.check_bot_admin(ctx.author, ctx.bot)

                if not is_bot_admin and not is_host:
                    return
                target_raid = raid
                break

        if not target_raid:
            return await send_message(ctx, f'You may only do this in your active raid channel.', error=True)

        target_raid.emoji = emoji or RAID_EMOJI
        updated_name = f'{target_raid.emoji}┊{name}'
        await target_raid.channel.edit(name=updated_name)
        await target_raid.update_channel_emoji()
        await ctx.message.add_reaction('✅')

    '''
    pins
    '''

    @commands.command()
    async def pin(self, ctx):
        uid = ctx.author.id
        if uid not in self.raids:
            return

        raid = self.raids[uid]
        if raid.channel != ctx.channel:
            return await send_message(ctx, f'You may only pin channels in your active raid channel.', error=True)

        if len(raid.pins) >= 10:
            return await send_message(ctx,
                                      f'For now, you can only `.pin` a total of 10 times (even after `.unpin`ning them). This will be adjusted in the future.',
                                      error=True)

        last_msg = await ctx.channel.history().find(lambda m: m.id != ctx.message.id and m.author == ctx.author)
        if not last_msg:
            return await send_message(ctx, f'No message found by you in recent history (~50 messages).', error=True)

        raid.pins.append(last_msg)
        await last_msg.pin()

        n = len(raid.pins)
        await ctx.send(f'Message #**{n}** pinned! To remove it, type `.unpin {n}`.')

    @commands.command()
    async def unpin(self, ctx, *, arg=''):
        uid = ctx.author.id
        if uid not in self.raids:
            return

        raid = self.raids[uid]
        if raid.channel != ctx.channel:
            return await send_message(ctx, f'You may only unpin channels in your active raid channel.', error=True)

        if not arg:
            return await send_message(ctx, f'Invalid pin! You must include the number, e.g. `.unpin 3` ', error=True)

        try:
            n = int(arg) - 1
            if not (0 <= n < len(raid.pins)):
                raise ValueError
        except ValueError:
            return await send_message(ctx, f'Invalid pin! You must include the number, e.g. `.unpin 3` ', error=True)

        pinned_msg = raid.pins[n]
        if not pinned_msg:
            return await send_message(ctx, f'That message no longer exists and could not be unpinned.', error=True)

        await pinned_msg.unpin()
        raid.pins[n] = None

        await ctx.send(f'Message #**{n + 1}** unpinned! This cannot presently be undone.')

    @commands.command()
    async def echo(self, ctx, *, arg=''):
        uid = ctx.author.id
        if uid not in self.raids:
            return

        raid = self.raids[uid]
        if raid.channel != ctx.channel:
            return await send_message(ctx, f'You may only `.echo` in your active raid channel.', error=True)

        echo_help = f'_Usage_\n`.echo <tag> <message goes here>` to set an echo\n`.echo <tag>` to have me repeat it\nTo update an `.echo`, just set it again.'

        if not arg:
            msg = echo_help
            if len(raid.echos):
                tags = ' '.join([f'`{t}' for t in raid.echos.keys()])
                msg = f'{msg}{DBL_BREAK}Current Tags: {tags}'
            return await send_message(ctx, msg, error=True)

        msg = None
        try:
            tag, msg = arg.split(' ', 1)
        except ValueError:
            tag = arg

        if not msg:
            if tag in raid.echos:
                return await ctx.send(f'**Echo from the host**\n{raid.echos[tag]}')
            return await send_message(ctx, f'Echo not found for tag `{tag}`.{DBL_BREAK}{echo_help}', error=True)

        if len(raid.echos) > 5:
            return await send_message(ctx, f'You cannot have more than 5 echos at a time, currently. If you want more,let jacob know.', error=True)

        raid.echos[tag] = msg[:1000]
        return await ctx.send(f'**Raid echo set for tag `{tag}`**\nUse `.echo {tag}` to have me repeat it.{DBL_BREAK}{raid.echos[tag]}')

    '''
    misc
    '''

    async def view_queue(self, ctx, text=False):
        uid = ctx.author.id
        if uid in self.raids:
            if self.raids[uid].channel == ctx.channel:
                e = self.raids[uid].make_queue_embed(mentions=not text, cmd=ctx.invoked_with)
                return await ctx.send('', embed=e)

        for host_id, raid in self.raids.items():
            if raid.channel == ctx.channel:
                is_bot_admin = checks.check_bot_admin(ctx.author, ctx.bot)
                if is_bot_admin:
                    e = raid.make_queue_embed(mentions=not text, cmd=ctx.invoked_with)
                    return await ctx.send('', embed=e)
                await ctx.message.add_reaction('✅')
                e = raid.make_queue_embed(mentions=False, cmd=ctx.invoked_with)
                return await ctx.author.send(raid.raid_name, embed=e)

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    @commands.command(aliases=['q'])
    async def queue(self, ctx, arg=''):
        return await self.view_queue(ctx, False)

    @commands.command(name='qtext', aliases=['qt'])
    async def queue_text(self, ctx, arg=''):
        return await self.view_queue(ctx, True)

    @commands.command(name='qfc', aliases=['queuefc'])
    async def queue_fc(self, ctx, arg=''):
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                is_host = ctx.author.id == host_id
                is_bot_admin = checks.check_bot_admin(ctx.author, ctx.bot)

                if not is_bot_admin and not is_host:
                    return await send_message(ctx, 'Host or admin only. Use `.q` instead.', error=True)

                lines = []
                for i, uid in enumerate(raid.pool.q):
                    profile = await self.bot.trainers.get_wf_profile(uid)

                    fc = f"SW-{profile['friend_code']}"
                    switch = profile['switch_name']
                    text = f'''🗺 FC: `{fc}` / {EMOJI['discord']}  **{raid.member_name(uid)}** / 🎮 Switch Name: **{switch}** / {ign_as_text(profile, ' / ')}'''
                    lines.append(f"`{i + 1: >2}` {text}\n")

                messages = []
                msg = f'**Active Raider Profiles**\n\n'
                for line in lines:
                    if len(msg) + len(line) < 2000:
                        msg += line
                    else:
                        messages.append(msg)
                        msg = line

                messages.append(msg)
                for msg in messages:
                    await ctx.send(msg)

                return

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    @commands.command(aliases=['catch'])
    async def caught(self, ctx, arg=None):
        description = f' 🎊 ✨ 🍰   {ctx.author.mention} has **caught** the Pokémon!   🍰 ✨ 🎊 '
        color = random.choice(TYPE_COLORS)
        e = discord.Embed(description=description, color=color) \
            .set_author(name=f'Caught!', icon_url=pokeballUrl)

        caught_msg = await ctx.send('', embed=e)
        for reaction in [EMOJI['lovehonk'], EMOJI['pokeball']]:
            await caught_msg.add_reaction(reaction.strip('<>'))

    @commands.command()
    async def code(self, ctx, *, arg=None):
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                is_host = ctx.author.id == host_id

                if not arg or not is_host:
                    if not raid.code:
                        if is_host:
                            return await send_message(ctx, '''You haven't set a code yet. Please choose a valid 8-digit code in the form `.code 1234 5678`''', error=True)
                        else:
                            return await send_message(ctx, 'The code hasn\'t been set by the host.', error=True)

                    if not is_host:
                        msg = f'The code for `{raid.raid_name}` is currently **{raid.code}**. The host may change this later on, keep an eye out.'
                        if raid.private:
                            msg += ' Since this is a private raid, please do not share the code openly (to hide it from lurkers).'
                        # await ctx.message.add_reaction('✅')
                        await ctx.message.delete()
                        return await ctx.author.send(msg)

                error = False
                code = None
                if arg:
                    m = re.search(r'\d{4}\s?\d{4}', arg)
                    if m:
                        code = m.group(0)
                    else:
                        error = True

                if not code:
                    msg = ''
                    if raid.code:
                        msg = f'The code is currently set to **{raid.code}**, but you can change it with `.code 1234 5678` using any valid 8-digit number.'
                    else:
                        msg = f'''You haven't set a code yet. Please choose a valid 8-digit code in the form `.code 1234 5678`'''

                    if raid.private:
                        await ctx.message.add_reaction(ICON_CLOSE if error else '✅')
                        return await ctx.author.send(msg)

                    return await send_message(ctx, msg, error=True)

                raid.code = code
                if raid.private:
                    await ctx.message.delete()
                    msg = '''The code has been **updated**. Participants will have to get the new one by typing `.code` when it's their turn. I'll also send a DM if there's a queue. Since this is a private raid, please do not share the code openly (to hide it from lurkers).'''
                else:
                    msg = f'''The code has been updated to **{raid.code}**.'''

                return await ctx.send(msg)

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    @commands.command()
    async def leave(self, ctx, arg=None):
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                user = self.bot.get_user(ctx.author.id)
                if not user:
                    print(f'[ERROR] Could not `get_user` for `.leave` with id {ctx.author.id}')
                    return
                await raid.user_leave(user, True)
                await ctx.message.add_reaction('✅')
                return

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    @commands.command()
    async def miss(self, ctx, arg=None):
        for host_id, raid in self.raids.items():
            if raid and raid.channel == ctx.channel:
                user = self.bot.get_user(ctx.author.id)
                if not user:
                    print(f'[ERROR] Could not `get_user` for `.miss` with id {ctx.author.id}')
                    return
                return await raid.miss(user)

        return await send_message(ctx, texts.RAID_NOT_FOUND, error=True)

    '''
    raid admin
    '''

    # @checks.is_jacob()
    # @commands.command()
    # async def init(self, ctx):
    #     await ctx.invoke(self.bind, 661468408856051733)

    # @checks.is_jacob()
    # @raid.group()
    # async def admin(self, ctx):
    #     await ctx.send('**[Raid Admin]**')
    #     if ctx.invoked_subcommand is None:
    #         await ctx.send('Invalid admin command')
    #
    # @checks.is_jacob()
    # @admin.command()
    # async def bind(self, ctx, category_id: int):
    #     try:
    #         self.category = self.bot.get_channel(category_id)
    #         self.guild = self.category.guild
    #     except AttributeError:
    #         await ctx.send(f'Could not bind to {category_id}')
    #     await ctx.send(f'Succesfully bound to **{self.category.name}** on server **{self.category.guild.name}**')

    # @admin.command()
    # async def testmake(self, ctx, count: int):
    #
    #     if self.category is None or self.guild is None:
    #         await send_message(ctx, f'No category bound! Try `.raid admin bind <category id>', error=True)
    #         return
    #
    #     users = ['elliot', 'rory', 'jacob', 'stinky']
    #     pkmn = ['gmax-hatterene', 'shiny-meowth', 'pikachu', 'charmander']
    #
    #     for i in range(count):
    #         name = f'{random.choice(pkmn)}-{random.choice(users)}'
    #         channel = await self.make_channel(name)
    #         self.channels.append(channel)
    #

    @checks.is_jacob()
    @commands.command(name='dangerclearall', aliases=['dangerca'])
    async def clear_all(self, ctx, arg=None):
        await ctx.send('Clearing...')
        await self.clear_channels_dirty()
        await ctx.send('Done.')

    @commands.check_any(checks.is_jacob(), checks.is_wooloo_staff())
    @commands.command()
    async def pause(self, ctx, arg=None):
        self.creating_enabled = not self.creating_enabled
        await ctx.send(f'`creating_enabled = {self.creating_enabled}`')

    @checks.is_jacob()
    @commands.command(name='dbsave')
    async def db_save(self, ctx, arg=None):
        await self.save_raids_to_db()
        await ctx.send(f'Finished.')

    @checks.is_jacob()
    @commands.command(name='dbload')
    async def db_load(self, ctx):
        await self.load_raids_from_db()
        await ctx.send(f'Finished.')

    @checks.is_jacob()
    @commands.command(name='dangerremakedb')
    async def db_remake(self, ctx):
        self.remake_db()
        await ctx.send(f'Finished. If there are active raids, they will need to be cleaned.')

    @checks.is_bot_admin()
    @commands.command()
    async def announce(self, ctx, *, arg):

        if not self.category:
            return

        # emoji = list(re.findall(emojiPattern, arg, flags=re.DOTALL)) + list(re.findall(customEmojiPattern, arg, flags=re.DOTALL))
        emoji = list(demoji.findall(arg).keys()) + list(re.findall(customEmojiPattern, arg, flags=re.DOTALL))

        url = None
        m = re.search(r'http[\w\-._~:/?#[\]@!$&\'()*+,;=]+', arg, flags=re.DOTALL)
        if m:
            url = m.group(0)
            if 'png' in url or 'gif' in url or 'jpg' in url or 'jpeg' in url:
                arg = arg.replace(url, '')
            else:
                url = None
        color = random.choice(TYPE_COLORS)
        e = discord.Embed(title='Botte Announcement!', description=f'''{EMOJI['honk']} {arg}''', color=color)
        if url:
            e.set_image(url=url)

        for channel in self.announce_channels:
            msg = await channel.send('', embed=e)
            for reaction in emoji:
                await msg.add_reaction(reaction.strip('<> '))

        for raid in self.raids.values():
            if not raid.closed and raid.channel is not None:
                msg = await raid.channel.send('', embed=e)
                for reaction in emoji:
                    await msg.add_reaction(reaction.strip('<> '))

        await ctx.send('Sent!')

    @checks.is_bot_admin()
    @commands.command()
    async def raidsay(self, ctx, *, arg):

        if not self.category:
            return

        msg = arg

        for channel in self.announce_channels:
            await channel.send(msg)

        for raid in self.raids.values():
            if not raid.closed and raid.channel is not None:
                await raid.channel.send(msg)

        await ctx.send('Sent!')

    @checks.is_bot_admin()
    @commands.command(aliases=['cl'])
    async def clear(self, ctx):
        if self.archive:
            for channel in self.archive.text_channels:
                await channel.delete()

    async def clear_channels_dirty(self):
        if self.category:
            if self.listing_channel:
                await self.listing_channel.purge(limit=100, check=lambda m: m.author == self.bot.user)

            for channel in self.category.text_channels:
                if channel.name[0] in [RAID_EMOJI, LOCKED_EMOJI, CLOSED_EMOJI]:
                    await channel.edit(sync_permissions=True, category=self.archive, position=0)

    async def make_raid_channel(self, raid, channel_ctx):
        if self.category is None or self.guild is None:
            await channel_ctx.send(
                f"No category bound! jacob should write `.raid admin bind <category id>` {EMOJI['flop']}")
            return

        overwrites = {
            self.guild.default_role: discord.PermissionOverwrite(send_messages=False, read_messages=False if self.bot.config["test_mode"] else None, add_reactions=False),
            self.guild.me: discord.PermissionOverwrite(send_messages=True, read_messages=True if self.bot.config["test_mode"] else None, add_reactions=True),
            self.guild.get_member(raid.host_id): discord.PermissionOverwrite(send_messages=True, read_messages=True if self.bot.config["test_mode"] else None, add_reactions=True)
        }

        for role in self.admin_roles:
            overwrites[role] = discord.PermissionOverwrite(send_messages=True, read_messages=True if self.bot.config["test_mode"] else None, add_reactions=True)

        # todo ban list

        topic = 'please read the pinned messages before raiding'

        channel_name = self.make_channel_name(raid.raid_name, LOCKED_EMOJI if raid.locked else raid.emoji or RAID_EMOJI)
        channel = await self.guild.create_text_channel(channel_name, overwrites=overwrites, category=self.category, topic=topic)
        return channel

    def make_channel_name(self, raid_name, emoji):
        channel_name = f"{emoji}┊{raid_name.replace(' ', '-')}"[:100]
        return re.sub('[<>]', '', channel_name)

    @checks.is_jacob()
    @commands.command(name='manualconfigure')
    async def manual_configure(self, ctx, arg=None):
        self.configure()
        await ctx.send(f'Finished.')

    def configure(self):
        try:
            print(f'Configure step, bot ready: {self.bot.is_ready()}')
            self.category = self.bot.get_channel(self.bot.config['raids_cid'])
            if not self.category:
                return False

            print(f'''Attempted to load raid category {self.bot.config['raids_cid']}: / {self.category}''')
            self.archive = self.bot.get_channel(self.bot.config['archive_cid'])
            print(f'''Attempted to load archive category {self.bot.config['archive_cid']}: / {self.archive}''')

            self.guild = self.category.guild
            print(f'''Set guilt to self.category.guild: {self.category.guild}''')

            self.listing_channel = self.bot.get_channel(self.bot.config['listing_channel'])
            print(f'''Attempted to load listing channel {self.bot.config['listing_channel']}: / {self.listing_channel}''')
            self.thanks_channel = self.bot.get_channel(self.bot.config['thanks_channel'])
            print(f'''Attempted to load thanks channel {self.bot.config['thanks_channel']}: / {self.thanks_channel}''')
            self.log_channel = self.bot.get_channel(self.bot.config['log_channel'])
            print(f'''Attempted to load log channel {self.bot.config['log_channel']}: / {self.log_channel}''')
            self.announce_channels = [self.bot.get_channel(cid) for cid in self.bot.config['announce_channels']]
            print(f'''Attempted to announce channels {self.bot.config['announce_channels']}: / {self.announce_channels}''')

            self.admin_roles = [self.guild.get_role(rid) for rid in self.bot.config['raid_admin_roles']]
            self.max_raids = self.bot.config['max_raids']
        except Exception as e:
            return False
        print('[CONFIG] Loaded')
        return True

def setup(bot):
    cog = Cog(bot)
    bot.add_cog(cog)
    bot.raid_cog = cog