#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

# Program: Get Courses from coursehunters.net
#
# Author: Andrey Klimov < ak545 at mail dot ru >
# https://github.com/ak545
#
# Current Version: 0.1.2
# Date: 14-07-2019 (dd-mm-yyyy)
#
# License:
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
#  GNU General Public License for more details.

import os
import sys
import platform
import argparse
import requests
import re
import itertools
from pathlib import Path
import time
import urllib3

try:
    from bs4 import BeautifulSoup
except ImportError:
    sys.exit("""You need bs4!
                install it from https://pypi.org/project/bs4
                or run pip install bs4.""")

try:
    from colorama import init
    from colorama import Fore, Back, Style
except ImportError:
    sys.exit(
        """You need colorama!
                install it from http://pypi.python.org/pypi/colorama
                or run pip install colorama"""
    )

# Init colorama
init(autoreset=True)

urllib3.disable_warnings()

# Check Python Version
if sys.version_info < (3, 6):
    print("Error. Python version 3.6 or later required to run this script")
    print("Your version:", sys.version)
    sys.exit(-1)

# Глобальные константы
__version__ = "0.1.2"

FR = Fore.RESET
FLW = Fore.LIGHTWHITE_EX
FLG = Fore.LIGHTGREEN_EX
FLR = Fore.LIGHTRED_EX
FLC = Fore.LIGHTCYAN_EX
FLY = Fore.LIGHTYELLOW_EX
FLM = Fore.LIGHTMAGENTA_EX
FLB = Fore.LIGHTBLUE_EX

BLB = Back.LIGHTBLACK_EX
BR = Back.RESET

SDIM = Style.DIM
SNORMAL = Style.NORMAL
SBRIGHT = Style.BRIGHT
SR = Style.RESET_ALL

SEP = os.sep

# Command line parameters
NAMESPACE = None

# User-Agent for requests
S_USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.46 Safari/537.36'

STREAM = sys.stderr

# Шаблоны для progress bar
BAR_TEMPLATE = '%s[%s%s] %s ( %s ) ( %s )\r'
MILL_TEMPLATE = '%s %s %i/%i\r'
DOTS_CHAR = '.'
BAR_FILLED_CHAR = '#'
BAR_EMPTY_CHAR = ' '
MILL_CHARS = ['|', '/', '-', '\\']

# How long to wait before recalculating the ETA
ETA_INTERVAL = 1
# How many intervals (excluding the current one)
# to calculate the simple moving average
ETA_SMA_WINDOW = 9

def humanize_bytes(bytesize, precision=2):
    """
    Humanize byte size figures
    :param bytesize: float
    :param precision: int
    :return: string
    """
    abbrevs = (
        (1 << 50, 'PB'),
        (1 << 40, 'TB'),
        (1 << 30, 'GB'),
        (1 << 20, 'MB'),
        (1 << 10, 'kB'),
        (1, 'bytes')
    )
    if bytesize == 1:
        return '1 byte'
    for factor, suffix in abbrevs:
        if bytesize >= factor:
            break
    return '%.*f %s' % (precision, bytesize / factor, suffix)

class MyBar(object):
    """
    Рисует прогресс бар в консоли
    """

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        self.done()
        return False  # we're not suppressing exceptions

    def __init__(self, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
                 filled_char=BAR_FILLED_CHAR, expected_size=None, every=1, total_length=None):
        self.label = label
        self.width = width
        self.hide = hide
        # Only show bar in terminals by default (better for piping, logging etc.)
        if hide is None:
            try:
                self.hide = not STREAM.isatty()
            except AttributeError:  # output does not support isatty()
                self.hide = True
        self.empty_char = empty_char
        self.filled_char = filled_char
        self.expected_size = expected_size
        self.every = every
        self.total_length = total_length
        self.str_total_length = humanize_bytes(self.total_length)
        self.str_percent = '0.00 %'
        self.start = time.time()
        self.ittimes = []
        self.eta = 0
        self.etadelta = time.time()
        self.etadisp = self.format_time(self.eta)
        self.last_progress = 0
        if (self.expected_size):
            self.show(0)

    def show(self, progress, count=None):
        if count is not None:
            self.expected_size = count
        if self.total_length is None:
            self.total_length = 0
        self.str_total_length = humanize_bytes(self.total_length)
        if self.expected_size is None:
            raise Exception("expected_size not initialized")

        if (progress == self.expected_size):
            self.str_percent = "100 %"
        else:
            self.str_percent = "{:6.2f} %".format(progress * 100 / self.expected_size)

        self.last_progress = progress
        if (time.time() - self.etadelta) > ETA_INTERVAL:
            self.etadelta = time.time()
            self.ittimes = \
                self.ittimes[-ETA_SMA_WINDOW:] + \
                [-(self.start - time.time()) / (progress + 1)]
            self.eta = \
                sum(self.ittimes) / float(len(self.ittimes)) * \
                (self.expected_size - progress)
            self.etadisp = self.format_time(self.eta)
        x = int(self.width * progress / self.expected_size)
        if not self.hide:
            if ((progress % self.every) == 0 or  # True every "every" updates
                    (progress == self.expected_size)):  # And when we're done
                STREAM.write(BAR_TEMPLATE % (
                    self.label, self.filled_char * x,
                    self.empty_char * (self.width - x), self.etadisp, self.str_percent, self.str_total_length))

                STREAM.flush()

    def done(self):
        # self.elapsed = time.time() - self.start
        # elapsed_disp = self.format_time(self.elapsed)
        elapsed_disp = self.format_time(0)
        self.str_percent = "100.00 %"
        if not self.hide:
            # Print completed bar with elapsed time
            STREAM.write(BAR_TEMPLATE % (
                self.label, self.filled_char * self.width,
                self.empty_char * 0, elapsed_disp, self.str_percent, self.str_total_length))

            STREAM.write('\n')
            STREAM.flush()

    def format_time(self, seconds):
        return time.strftime('%H:%M:%S', time.gmtime(seconds))

# Функция рисования progress bar
def bar(it, label='', width=32, hide=None, empty_char=BAR_EMPTY_CHAR,
        filled_char=BAR_FILLED_CHAR, expected_size=None, every=1, total_length=None):
    """Progress iterator. Wrap your iterables with it."""

    count = len(it) if expected_size is None else expected_size

    with MyBar(label=label, width=width, hide=hide, empty_char=empty_char,
               filled_char=filled_char, expected_size=count, every=every, total_length=total_length) \
            as bar:
        for i, item in enumerate(it):
            yield item
            bar.show(i + 1)

def dots(it, label='', hide=None, every=1):
    """Progress iterator. Prints a dot for each item being iterated"""

    count = 0

    if not hide:
        STREAM.write(label)

    for i, item in enumerate(it):
        if not hide:
            if i % every == 0:  # True every "every" updates
                STREAM.write(DOTS_CHAR)
                sys.stderr.flush()

        count += 1

        yield item

    STREAM.write('\n')
    STREAM.flush()

def mill(it, label='', hide=None, expected_size=None, every=1):
    """Progress iterator. Prints a mill while iterating over the items."""

    def _mill_char(_i):
        if _i >= count:
            return ' '
        else:
            return MILL_CHARS[(_i // every) % len(MILL_CHARS)]

    def _show(_i):
        if not hide:
            if ((_i % every) == 0 or  # True every "every" updates
                    (_i == count)):  # And when we're done

                STREAM.write(MILL_TEMPLATE % (
                    label, _mill_char(_i), _i, count))
                STREAM.flush()

    count = len(it) if expected_size is None else expected_size

    if count:
        _show(0)

    for i, item in enumerate(it):
        yield item
        _show(i + 1)

    if not hide:
        STREAM.write('\n')
        STREAM.flush()

def save_html_dump(html, filename):
    """
    Сохранение отладочного дампа html в файл
    :param html: string  # Содержимое
    :param filename: string  # Имя файла для записи отладочного дампа
    :return: None
    """
    bs = BeautifulSoup(html, 'html.parser')
    dump = []
    dump.append(bs.prettify())
    open_mode = "w+"
    with open(filename, open_mode, encoding="utf-8", newline="\n") as f:
        for item in dump:
            f.write("%s\n" % item)

def get_html(url, error_msg=''):
    """
    Загрузка веб страницы курса

    Возвращает: Строку-дамп HTML-страницы

    :param url: string  # url курса
    :param error_msg: string  # Строка выводимая во время
                              # ошибки при попытке загрузить
                              # HTML страницу курса
    :return: str
    """
    global S_USER_AGENT
    if error_msg.strip() != '':
        error_msg = error_msg + ' невозможен'

    headers = {
        'User-Agent': S_USER_AGENT
    }
    try:
        result = requests.get(url, timeout=10, headers=headers, verify=False)
        result.raise_for_status()
        return result.text
    except requests.exceptions.RequestException as e:
        if error_msg.strip() != '':
            print(f'\r{FLR}{error_msg}         ')
        print(f'Не получилось подключиться к сайту: {FLG}{url}')
        print(f'{e}')
        sys.exit(-1)

def get_videos(url):
    """
    Поиск видео файлов на HTML странице курса

    Возвращает:
    язык курса (string),
    название курса (string),
    описание курса (string),
    список из словарей ["название видео", "ссылка на видео"]
        list[
                {'title': 'item1', 'link': 'item2'},
                {'title': 'item1', 'link': 'item2'},
                ...,
                {'title': 'item1', 'link': 'item2'},
            ]

    :param url: string  # url курса
    :return: string, string, list
    """
    title_course = ''
    title_description = ''
    data = []
    lng = 'en'
    html = get_html(url=url, error_msg='Поиск видео')
    if html:
        # save_html_dump(html=html, filename='dump.txt')
        bs = BeautifulSoup(html, 'html.parser')
        item = bs.find('h1', class_='hero-title')
        if item:
            title_course = item.text.strip()

        item = bs.find('div', class_='course-wrap-description')
        if item:
            title_description = str(item).strip()
            title_description = str(title_description).replace('
', '').replace(
                '
', '').replace('
', "\n").replace('
', "\n").replace(
                '
', '').replace('
', "\n").replace(
                '
', '').replace('
', "\n").replace('
', '- ').replace('
', "\n").replace(
                '', '').replace('', '').replace('', '').replace('', '')

        items = bs.find_all('div', class_='course-box-value')
        if items:
            for item in items:
                if 'Ангельский' in item.text.strip():
                    print(f'Язык     : [ {FLB}Ангельский{FR} ]')
                    lng = 'en'
                if 'Русский' in item.text.strip():
                    print(f'Язык     : [ {FLB}Русский{FR} ]')
                    lng = 'ru'

        items = bs.find_all('li', class_='lessons-item')
        if items:
            for item in items:
                item_title = item.find('div', class_='lessons-name')
                item_href = item.find('link', itemprop="contentUrl")
                if item_href:
                    data.append({
                        'title': item_title.text.strip(),
                        'link': item_href.get('href').strip()
                    })

        item = bs.find('a', class_='section-block-btn btn-outline')
        if item:
            data.append({
                'title': item.text.strip(),
                'link': item.get('href').strip()
            })
    return lng, title_course, title_description, data

def sanitize_filename(s, restricted=False, is_id=False):
    """
    Изменяет строку (s) так, чтобы её можно было использовать,
    как часть имени файла.
    Параметр restricted изменит строку на основании более
    строгого подмножества разрешенных для файловой системы
    символов (accent_chars).
    Параметр is_id позволяет не изменять идентификаторы
    (если это возможно).

    Modifies the string (s) so that it can be used as part of
    the file name.
    The restricted parameter will change the string based on
    a more strict subset of the allowed characters (accent_chars)
    for the file system.
    The is_id parameter allows not to change identifiers
    (if possible).

    :param s: string
    :param restricted: bool
    :param is_id: bool
    :return: string
    """

    def replace_insane(char):
        accent_chars = dict(zip('ÂÃÄÀÁÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖŐØŒÙÚÛÜŰÝÞßàáâãäåæçèéêëìíîïðñò'
                                'óôõöőøœùúûüűýþÿ',
                                itertools.chain(
                                    'AAAAAA',
                                    ['AE'],
                                    'CEEEEIIIIDNOOOOOOO',
                                    ['OE'],
                                    'UUUUUYP',
                                    ['ss'],
                                    'aaaaaa',
                                    ['ae'],
                                    'ceeeeiiiionooooooo',
                                    ['oe'],
                                    'uuuuuypy'
                                )))
        if restricted and char in accent_chars:
            return accent_chars[char]
        if char == '?' or ord(char) < 32 or ord(char) == 127:
            return ''
        elif char == '"':
            return '' if restricted else '\''
        elif char == ':':
            return '_-' if restricted else ' -'
        elif char in '\\/|*<>':
            return '_'
        if restricted and (char in '!&\'()[]{}$;`^,#' or char.isspace()):
            return '_'
        if restricted and ord(char) > 127:
            return '_'
        return char

    # Handle timestamps
    s = re.sub(r'[0-9]+(?::[0-9]+)+',
               lambda m: m.group(0).replace(':', '_'), s)
    result = ''.join(map(replace_insane, s))
    if not is_id:
        while '__' in result:
            result = result.replace('__', '_')
        result = result.strip('_')
        # Common case of "Foreign band name - English song title"
        if restricted and result.startswith('-_'):
            result = result[2:]
        if result.startswith('-'):
            result = '_' + result[len('-'):]
        result = result.lstrip('.')
        if not result:
            result = '_'
    return result

def download_file(url, file_out, title, i, total):
    """
    Загрузка файла
    Возвращает: код ошибки
    0 - файл успешно загружен
    1 - файл уже существует
    2 - ошибка загрузки файла
    28 - недостаточно место для сохранения файла

    :param url: string
    :param file_out: string
    :param title: string
    :param i: int
    :param total: int

    :return: int
    """

    global S_USER_AGENT
    headers = {
        'User-Agent': S_USER_AGENT
    }
    print(f'Имя      : [ {i}/{total} ] {FLC}{title}{FR}')
    try:
        r = requests.get(url, stream=True, headers=headers, verify=False)

        if r.status_code < 200 or r.status_code > 299:
            print(f'Статус   : [ {FLM}Error{FR} ]\n'
                  f'Cсылка   : {FLW}{url}{FR}\n'
                  f'Файл     : {FLW}{file_out}{FR}\n'
                  f'Код      : {FLM}{r.status_code}')
            exit(-1)

        total_length = int(r.headers.get('content-length'))
        # Проверка на наличие уже скаченного файла
        if Path(file_out).is_file():
            file_size = os.stat(Path(file_out)).st_size
            if file_size == int(total_length):
                print(f'Статус   : [ {FLG}Уже существует ]\n')
                return 1

        # Скачивание файла
        def_chunk_size = min(total_length, 8192)
        # def_chunk_size = min(total_length, 4096)
        with open(file_out, "wb") as f:
            for chunk in bar(r.iter_content(chunk_size=def_chunk_size), label="Загружено: ",
                             filled_char='#',
                             expected_size=(total_length / def_chunk_size) + 1,
                             total_length=total_length):
                if chunk:
                    f.write(chunk)
                    f.flush()

        print(f'Статус   : [ {FLG}Ok{FR} ]\n')
        return 0

    except Exception as e:
        print(f'Статус   : [ {FLM}Error{FR} ]\n'
              f'Cсылка   : {FLW}{url}{FR}\n'
              f'Файл     : {FLW}{file_out}{FR}\n'
              f'Код      : {FLM}{e}\n')
        if '[Errno 28]' in str(e):  # [Errno 28] No space left on device
            return 28
        else:
            return 2

def process_cli():
    """
    parses the CLI arguments and returns a domain or
        a file with a list of domains etc.
    :return: dict
    """
    parser = argparse.ArgumentParser(
        description="""Get Courses from coursehunters.net
        A simple python script for download the courses from coursehunters.net.
        """,
        usage='%(prog)s [-h][-v][-nb] -u URL [-o DIR]',
        epilog="(c) AK545 (Andrey Klimov) 2019, e-mail: ak545@mail.ru",
        add_help=False
    )
    parent_group = parser.add_argument_group(
        title="Options"
    )
    parent_group.add_argument(
        "-h",
        "--help",
        action="help",
        help="Help"
    )
    parent_group.add_argument(
        "-v",
        "--version",
        action="version",
        help="Display the version number",
        version="%(prog)s version: {}".format(__version__)
    )
    parent_group.add_argument(
        "-u",
        "--url",
        help="URL of the Course (default is None)",
        metavar="URL"
        # required=True
    )
    parent_group.add_argument(
        "-o",
        "--out",
        help="Download dir for the Course (default is None)",
        metavar="DIR"
    )
    parent_group.add_argument(
        "-nb",
        "--no-banner",
        action="store_true",
        default=False,
        help="Do not print banner (default is False)"
    )
    return parser

def print_namespase():
    """
    Print preset options to console
    :return: None
    """
    global NAMESPACE
    print(
        f"\tPreset options\n"
        f"\t-------------------------\n"
        f"\tUrl                      : {NAMESPACE.url}\n"
        f"\tOut Dir                  : {NAMESPACE.out}\n"
        f"\tPrint banner             : {NAMESPACE.no_banner}\n"
        f"\t-------------------------"
    )

def main():
    """
    Main function
    :return: None
    """
    global NAMESPACE

    if NAMESPACE.out:
        # Создание папки для сохранения видео и материалов курса
        try:
            Path(NAMESPACE.out).mkdir(parents=True, exist_ok=True)
        except:
            print(f'Ошибка создания папки:\n{FLY}{NAMESPACE.out}')
            print(f'Проверьте корректность задания параметров.')
            print(f'Не добавляйте в конце параметра обратный слеш "{FLY}\\"')
            sys.exit(-1)
    else:
        NAMESPACE.out = os.getcwd()

    if NAMESPACE.url:
        lng, title_course, title_description, data = get_videos(NAMESPACE.url)
        if title_course != "":
            print(f'Курс     : [ {FLC}{title_course}{FR} ]')
            print()
            title_course = sanitize_filename(title_course)
            NAMESPACE.out = NAMESPACE.out + SEP + '( ' + lng + ' ) ' + title_course
            # Создание папки для сохранения видео и материалов курса
            try:
                Path(NAMESPACE.out).mkdir(parents=True, exist_ok=True)
            except:
                print(f'Ошибка создания папки:\n{FLY}{NAMESPACE.out}')
                print(f'Проверьте корректность задания параметров.')
                print(f'Не добавляйте в конце параметра обратный слеш "{FLY}\\"')
                sys.exit(-1)

        if title_description != "":
            filename = NAMESPACE.out + SEP + "description.txt"
            with open(filename, "w+", encoding="utf-8", newline="\n") as f:
                f.write(title_description)

        data_len = len(data)
        for i, item in enumerate(data, start=1):
            url = str(item.get('link')).strip()
            f = str(item.get('link')).strip().split('/')[-1]
            f = sanitize_filename(f)
            title = str(item.get('title')).strip()
            title = sanitize_filename(title)
            file = os.path.join(str(NAMESPACE.out), str(i) + '. ' + title + ' - ' + f)

            ret = download_file(url, file, title, i, data_len)
            if ret == 28:  # No space left on device
                break
        print()
        print(f'{FLW}Завершена загрузка курса{FR}')
        print()

if __name__ == "__main__":
    print(f"{SR}")

    # Parsing command line
    parser = process_cli()
    NAMESPACE = parser.parse_args(sys.argv[1:])
    if not NAMESPACE.url:
        print(f'{FLM}Error! The following arguments are required: {FLY}-u{FR} / {FLY}--url')
        parser.print_help()
    if not NAMESPACE.no_banner:
        if platform.platform().startswith('Windows'):
            home_path = os.path.join(os.getenv('HOMEDRIVE'),
                                     os.getenv('HOMEPATH'))
        else:
            home_path = os.path.join(os.getenv('HOME'))
        print(
            f"\tPython  : {FLC}{sys.version}{FR}\n"
            f"\tNode    : {FLC}{platform.node()}{FR}\n"
            f"\tHome    : {FLC}{home_path}{FR}\n"
            f"\tOS      : {FLC}{platform.system()}{FR}\n"
            f"\tRelease : {FLC}{platform.release()}{FR}\n"
            f"\tVersion : {FLC}{platform.version()}{FR}\n"
            f"\tArch    : {FLC}{platform.machine()}{FR}\n"
            f"\tCPU     : {FLC}{platform.processor()}{FR}"
        )
    rc = 0
    try:
        main()
    except Exception as e:
        print('Error: %s' % e, file=sys.stderr)
    sys.exit(rc)


"""
	生成带图片的二维码
"""

import qrcode
from PIL import Image
import os, sys

def get_qrcode(string, path, logo=''):
	qr = qrcode.QRCode(
		version = 2,
		error_correction = qrcode.constants.ERROR_CORRECT_H,
		box_size = 8,
		border = 1
		)

	qr.add_data(string)
	qr.make(fit=True)

	img = qr.make_image()
	img = img.convert("RGBA")

	if logo and os.path.exists(logo):
		try:
			icon = Image.open(logo)
			img_w, img_h = img.size
		except Exception as e:
			print(e)
			sys.exit(1)

		factor = 4
		# 计算 logo 的尺寸
		size_w = int(img_w/factor)
		size_h = int(img_h/factor)

		# 比较并重新设置 logo 文件的尺寸
		icon = icon.resize((size_w, size_h), Image.ANTIALIAS)

		# 计算 logo 的位置
		x = int((img_w - size_w)/2)
		y = int((img_h - size_h)/2)

		icon = icon.convert('RGBA')
		img.paste(icon, (x, y), icon)

		img.save(path)

if __name__ == '__main__':
	info = 'http://www.zhuhai.pro'
	pic_path = 'logo.png'
	logo_path = 'yn.jpg'
	get_qrcode(info, pic_path, logo_path)


import pygame
import constants
import re, random
from game.bullet import Bullet

frame_regex = re.compile(r'^\d*[0-4]$')

class Plane(pygame.sprite.Sprite):
    # 飞机的绘制图片列表
    plane_imgs = []
    # 飞机毁灭时用来绘制的图片
    destory_imgs = []
    # 飞机毁灭时的音乐
    destory_sound = None
    # 飞机当前的状态
    active = True
    # 飞机的子弹组
    bullets = pygame.sprite.Group()

    def __init__(self, screen, speed=2):
        super().__init__()
        # 主屏幕
        self.screen = screen
        self.screen_wd, self.screen_ht = self.screen.get_size()
        self._imgs = []
        self._destory_imgs = []
        self._destory_sound = None
        # 加载静态资源
        self.source_load()
        #  设置飞机飞行速度
        self.speed = speed
        # 设置飞机位置
        self.rect = self._imgs[0].get_rect()
        self.wd, self.ht = self._imgs[0].get_size()
        self.rect.left = int((self.screen_wd - self.wd) / 2)
        self.rect.top = int(self.screen_ht - self.ht  - 10)

    def source_load(self):
        for img in self.plane_imgs:
            self._imgs.append(pygame.image.load(img))
        for img in self.destory_imgs:
            self._destory_imgs.append(pygame.image.load(img))
        if self.destory_sound:
            self._destory_sound = pygame.mixer.Sound(self.destory_sound)

    def blit_self(self):
        self.screen.blit(self._imgs[0], self.rect)

    def move_up(self):
        self.rect.top -= self.speed

    def move_down(self):
        self.rect.top += self.speed

    def move_left(self):
        self.rect.left -= self.speed

    def move_right(self):
        self.rect.left += self.speed

    # 飞机坠毁
    def borken_down(self):
        if self._destory_sound:
            self._destory_sound.play()
        for img in self._destory_imgs:
            self.screen.blit(img, self.rect)
        self.active = False

    # 飞机的射击功能
    def shoot(self):
        bul = Bullet(self.screen, self)
        self.bullets.add(bul)

class OurPlane(Plane):
    plane_imgs = [constants.PLANE_IMG_1, constants.PLANE_IMG_2]
    destory_imgs = constants.PLANE_DESTORY_IMGS
    destory_sound = constants.PLANE_DESTORY_SOUND

    def update(self, frame, enemies_group, key_down):
        # 保证持续移动
        self.move(key_down)
        # 切换飞机的动画效果
        if frame_regex.match(str(frame)):
            self.screen.blit(self._imgs[0], self.rect)
        else:
            self.screen.blit(self._imgs[1], self.rect)
        # 检测飞机是否撞击到了敌方飞机
        if pygame.sprite.spritecollide(self, enemies_group, False):
            self.borken_down()
            self.active = False
            # 返回 False 代表游戏已经结束了
            return True
        return False

    def move(self, key):
        if key == pygame.K_DOWN:
            self.move_down()
        elif key == pygame.K_UP:
            self.move_up()
        elif key == pygame.K_LEFT:
            self.move_left()
        elif key == pygame.K_RIGHT:
            self.move_right()

    def move_up(self):
        super().move_up()
        if self.rect.top <= 0:
            self.rect.top = 0

    def move_left(self):
        super().move_left()
        if self.rect.left <= 0:
            self.rect.left = 0

    def move_down(self):
        super().move_down()
        if self.rect.top + self.ht >= self.screen_ht:
            self.rect.top = self.screen_ht - self.ht

    def move_right(self):
        super().move_right()
        if self.rect.left + self.wd >= self.screen_wd:
            self.rect.left = self.screen_wd - self.wd

class SmallEnemyPlane(Plane):
    # 飞机的绘制图片列表
    plane_imgs = [constants.SMALL_ENEMY_PLANE_IMG]
    # 飞机毁灭时用来绘制的图片
    destory_imgs = constants.SMALL_ENEMY_PLANE_DESTORY_IMGS
    # 飞机毁灭时的音乐
    destory_sound = constants.SMALL_ENEMY_PLANE_DESTORY_SOUND
    # 飞机当前的状态
    active = True

    def __init__(self, screen, speed=3):
        super().__init__(screen, speed)
        self.rect.left = random.randint(0, self.screen_wd - self.wd)
        self.rect.top = random.randint(-5 * self.ht, -self.ht)

    def update(self):
        self.move_down()
        self.blit_self()
        if self.rect.top > self.screen_ht:
            self.active = False
            self.kill()


import sys
import matplotlib.colors

target_color_hex = str(sys.argv[1])  # example input: "#44ca23"
main_color_hex = "#4169e1"

target_color_hsv = matplotlib.colors.rgb_to_hsv(matplotlib.colors.to_rgb(target_color_hex))
main_color_hsv = matplotlib.colors.rgb_to_hsv(matplotlib.colors.to_rgb(main_color_hex))

colors = {
    "$c1": "#06102b",
    "$c2": main_color_hex,
    "$c3": "#2f4893",
    "$c4": "#23366d",
    "$c5": "#2f4893",
    "$c6": "#3759bf",
}

new_colors = {}
for color in colors:
    selected_color_hsv = matplotlib.colors.rgb_to_hsv(matplotlib.colors.to_rgb(colors[color]))
    diff = selected_color_hsv - main_color_hsv
    new_color_hsv = target_color_hsv + diff
    if new_color_hsv[2] < 0.05:
        print(f"Warning: The light value ({round(new_color_hsv[2] * 100) / 100}) was too low. Now setting it to 0.05. The color relations are not kept!")
        new_color_hsv[2] = 0.05
    if new_color_hsv[1] > 1:
        print(f"Warning: The saturation value ({round(new_color_hsv[1] * 100) / 100}) was too high. Now setting it to 1. The color relations are not kept!")
        new_color_hsv[1] = 1
    new_colors[color] = matplotlib.colors.to_hex(matplotlib.colors.hsv_to_rgb(new_color_hsv))

dark_filter_name = "$f1"
dark_filter_hue = 191

def get_dark_filter(hue):
    return f"invert(90%) sepia(50%) saturate(1000%) hue-rotate({hue}deg) brightness(98%) contrast(92%)"

diff = target_color_hsv - main_color_hsv
new_dark_filter_hue = dark_filter_hue + diff[0] * 360

with open('colors.scss', 'w') as f:
    for color in new_colors:
        f.write(f"{color}: {new_colors[color]};\n")
    f.write(f"{dark_filter_name}: {get_dark_filter(new_dark_filter_hue)};")


import subprocess
import re
import pathlib

def get_output_results(comparison_directory:pathlib.Path):
    bash_script_path = "2_output_results.sh"
    res = subprocess.run(["bash", bash_script_path], check=True, cwd=comparison_directory, capture_output=True)
    return res.stdout.decode("utf-8")

def parse_relative_abundances(output_results):
    read_ratio_pattern = r"^(\S+)\s+Ratio of reads:(.*?)$"
    bases_ratio_pattern = r"^(\S+)\s+Ratio of bases:(.*?)$"
    label_value_pattern = r"(\S+):\s+([-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?)+(?:\s|$)"

    read_ratios = {}
    for line in output_results.split("\n"):
        read_ratio_match = re.search(read_ratio_pattern, line)
        if not read_ratio_match:
            continue
        tool_name = read_ratio_match.group(1)
        label_value_pairs = read_ratio_match.group(2)
        label_value_matches = re.findall(label_value_pattern, label_value_pairs)
        tool_read_ratios = {label: float(value) for label, value in label_value_matches}
        read_ratios[tool_name] = tool_read_ratios

    bases_ratios = {}
    for line in output_results.split("\n"):
        bases_ratio_match = re.search(bases_ratio_pattern, line)
        if not bases_ratio_match:
            continue
        tool_name = bases_ratio_match.group(1)
        label_value_pairs = bases_ratio_match.group(2)
        label_value_matches = re.findall(label_value_pattern, label_value_pairs)
        tool_bases_ratios = {label: float(value) for label, value in label_value_matches}
        bases_ratios[tool_name] = tool_bases_ratios

    #merge
    tools = set(read_ratios.keys()).intersection(set(bases_ratios.keys()))
    res = {}
    for tool in tools:
        res[tool] = {}
        for label in read_ratios[tool].keys():
            res[tool][label] = {"read_ratio": read_ratios[tool][label], "bases_ratio": bases_ratios[tool][label]}

    return res

def get_euclidean_distance(first_tool_relative_abundances, second_tool_relative_abundances):
    read_ratio_diff_squared_sum = 0
    base_ratio_diff_squared_sum = 0
    for oranism, values in first_tool_relative_abundances.items():
        read_ratio_diff = values["read_ratio"] - second_tool_relative_abundances[oranism]["read_ratio"]
        bases_ratio_diff = values["bases_ratio"] - second_tool_relative_abundances[oranism]["bases_ratio"]
        read_ratio_diff_squared_sum += read_ratio_diff**2
        base_ratio_diff_squared_sum += bases_ratio_diff**2
    return {
        'read_ratio': read_ratio_diff_squared_sum**0.5,
        'bases_ratio': base_ratio_diff_squared_sum**0.5
    }

def latex_escape(string):
    return string.replace('_', '\_')

def write_latex_table(relative_abundances, output_path, data_source='read_ratio'):
    #relative_abundances has toolnames as keys
    #table will have tools in rows and datasets in columns
    organism_count = len(relative_abundances['true_mappings'])
    for tool, organisms in relative_abundances.items():
        assert len(organisms) == organism_count

    renamed_relative_abundances = {}
    for tool, organisms in relative_abundances.items():
        escaped_organisms = {}
        for organism in organisms:
            escaped_organisms[latex_escape(organism_name_to_prety_name(organism))] = organisms[organism]
        renamed_relative_abundances[tool_name_to_pretty_name(tool)] = escaped_organisms

    ordered_tools = [
        'Ground Truth',
        'minimap2',
        'Uncalled',
        'Sigmap',
        'RawHash',
        'RawAlign'
    ]

    ordered_organisms = [
        'SARS-CoV-2',
        'E.coli',
        'Yeast',
        'Green Algae',
        'Human',
        'Distance'
    ]

    with open(output_path, 'w') as f:
        n = '\n'
        f.write(r'\begin{tabular}{l' + ' r'*organism_count + '}' + n)
        f.write(r'\hline' + n)
        f.write(
            ' & '.join(
                f'\\textbf{{{header}}}'
                for header in ['Tool'] + ordered_organisms
            )
        )
        f.write(r'\\' + n)
        #f.write(r'Tool & ' + ' & '.join(ordered_organisms) + r'\\' + n)

        best_distance = min([renamed_relative_abundances[tool]['Distance'][data_source] for tool in ordered_tools if tool != 'Ground Truth'])

        f.write(r'\hline' + n)
        for tool in ordered_tools:
            organisms = renamed_relative_abundances[tool]
            f.write(tool + ' & ')
            formatted_numbers = [
                (
                    f'{organisms[organism][data_source]:,.3f}'
                    if organisms[organism][data_source] != best_distance else
                    f'\\textbf{{{organisms[organism][data_source]:,.3f}}}'
                )
                if organism != 'Distance' or tool != 'Ground Truth'
                else '-'
                for organism in ordered_organisms
            ]
            f.write(' & '.join(formatted_numbers))
            f.write(r'\\' + n)
            #f.write(r'\hline' + n)
        f.write(r'\hline' + n)
        f.write(r'\end{tabular}' + n)

def tool_name_to_pretty_name(tool_name):
    if 'rawalign' in tool_name:
        return 'RawAlign'
    elif 'uncalled' in tool_name:
        return 'Uncalled'
    elif 'rawhash' in tool_name:
        return 'RawHash'
    elif 'sigmap' in tool_name:
        return 'Sigmap'
    elif 'true_mappings' in tool_name:
        return 'minimap2'
    elif 'fastq' in tool_name:
        return 'Ground Truth'
    else:
        return tool_name

def organism_name_to_prety_name(organism_name):
    if 'covid' in organism_name:
        return 'SARS-CoV-2'
    elif 'ecoli' in organism_name:
        return 'E.coli'
    elif 'yeast' in organism_name:
        return 'Yeast'
    elif 'green_algae' in organism_name:
        return 'Green Algae'
    elif 'human' in organism_name:
        return 'Human'
    elif 'distance' in organism_name:
        return 'Distance'
    else:
        return organism_name

if __name__ == '__main__':
    results = get_output_results(pathlib.Path('test/evaluation/relative_abundance/comparison/'))
    relative_abundances = parse_relative_abundances(results)

    ground_truth = relative_abundances['fastq']
    for tool, organisms in relative_abundances.items():
        organisms['euclidean_distance'] = get_euclidean_distance(organisms, ground_truth)

    write_latex_table(relative_abundances, pathlib.Path('paperfigures/read_ratio_relative_abundances.tex'), data_source='read_ratio')
    write_latex_table(relative_abundances, pathlib.Path('paperfigures/bases_ratio_relative_abundances.tex'), data_source='bases_ratio')


"""soft delete

Revision ID: a67ffc6928ab
Revises: e652369d0a02
Create Date: 2023-04-12 09:31:48.602594

"""
from alembic import op
import sqlalchemy as sa

# Polar Custom Imports

# revision identifiers, used by Alembic.
revision = "a67ffc6928ab"
down_revision = "e652369d0a02"
branch_labels: tuple[str] | None = None
depends_on: tuple[str] | None = None

def upgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.add_column(
        "accounts", sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True)
    )
    op.add_column(
        "issue_dependencies",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "issue_references",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "issues", sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True)
    )
    op.add_column(
        "notifications",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "oauth_accounts",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "organizations",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "pledges", sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True)
    )
    op.add_column(
        "pull_requests",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "repositories",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "user_organization_settings",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.add_column(
        "user_organizations",
        sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True),
    )
    op.drop_column("user_organizations", "status")
    op.add_column(
        "users", sa.Column("deleted_at", sa.TIMESTAMP(timezone=True), nullable=True)
    )
    # ### end Alembic commands ###

def downgrade() -> None:
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_column("users", "deleted_at")
    op.add_column(
        "user_organizations",
        sa.Column("status", sa.INTEGER(), autoincrement=False, nullable=False),
    )
    op.drop_column("user_organizations", "deleted_at")
    op.drop_column("user_organization_settings", "deleted_at")
    op.drop_column("repositories", "deleted_at")
    op.drop_column("pull_requests", "deleted_at")
    op.drop_column("pledges", "deleted_at")
    op.drop_column("organizations", "deleted_at")
    op.drop_column("oauth_accounts", "deleted_at")
    op.drop_column("notifications", "deleted_at")
    op.drop_column("issues", "deleted_at")
    op.drop_column("issue_references", "deleted_at")
    op.drop_column("issue_dependencies", "deleted_at")
    op.drop_column("accounts", "deleted_at")
    # ### end Alembic commands ###


import discord
from discord.ext import commands
import requests
from dotenv import load_dotenv
from PIL import Image
import os
import time
import asyncio
from datetime import datetime
import logging
import pytz
import nacl

logging.basicConfig(level=logging.INFO)

discord_token = "YOURDISCORDBOTTOKEN"

load_dotenv()
client = commands.Bot(command_prefix="*", intents=discord.Intents.all())
directory = os.getcwd()
print(directory)

# Process images based on date range
async def process_images_by_date(start_date, end_date):
    for guild in client.guilds:
        for channel in guild.channels:
            if isinstance(channel, discord.TextChannel):
                await process_channel(channel, start_date, end_date, output_folder)

async def process_channel(channel, start_date, end_date):
    async for msg in channel.history(limit=None):
        for attachment in msg.attachments:
            if attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
                created_at = attachment.created_at

                if start_date <= created_at <= end_date:
                    await download_image(attachment.url, attachment.filename)

# Process channel messages based on date range
async def process_channel_by_date(channel, start_date, end_date, output_folder):
    async for msg in channel.history(limit=None):
        for attachment in msg.attachments:
            if attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
                created_at = msg.created_at  # Use the created_at of the message

                if start_date <= created_at <= end_date:
                    # Check if the message contains "Upscaled by" or "Image #"
                    if "Upscaled by" in msg.content or "Image #" in msg.content:
                        await download_image(attachment.url, attachment.filename, output_folder, msg)  # Pass 'msg' as an argument
                        print(f"Downloaded and processed: {attachment.filename}")
                    else:
                        await download_image(attachment.url, attachment.filename, output_folder, msg)  # Pass 'msg' as an argument
                        print(f"Downloaded and processed: {attachment.filename}")

    # Add a message when all images have been processed
    print("##################")
    print("###           ###")
    print("###  D O N E  ###")
    print("###           ###")
    print("#################")

async def download_image(url, filename, output_folder, message):
    response = requests.get(url)
    if response.status_code == 200:
        # Define the input folder paths
        input_folder = "input"
        output_folder = f"output_{message.channel.name}_{message.channel.id}"

        # Check if the output folder exists, and create it if necessary
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)

        # Check if the input folder exists, and create it if necessary
        if not os.path.exists(input_folder):
            os.makedirs(input_folder)

        output_path = os.path.join(output_folder, filename)

        if not os.path.exists(output_path):
            with open(f"{directory}/{input_folder}/{filename}", "wb") as f:
                f.write(response.content)
            print(f"Image downloaded: {filename}")
            input_file = os.path.join(input_folder, filename)

            if "Image #" not in message.content:
                file_prefix = os.path.splitext(filename)[0]
                # Split the image
                top_left, top_right, bottom_left, bottom_right = split_image(input_file)
                # Save the output images with dynamic names in the output folder
                top_left.save(os.path.join(output_folder, file_prefix + "_top_left.png"))
                top_right.save(os.path.join(output_folder, file_prefix + "_top_right.png"))
                bottom_left.save(os.path.join(output_folder, file_prefix + "_bottom_left.png"))
                bottom_right.save(os.path.join(output_folder, file_prefix + "_bottom_right.png"))
            else:
                try:
                    os.rename(f"{directory}/{input_folder}/{filename}", f"{directory}/{output_folder}/{filename}")
                except FileNotFoundError as e:
                    print(f"Error renaming the file: {e}")

             # Delete the input file if it exists
            if os.path.exists(f"{directory}/{input_folder}/{filename}"):
                os.remove(f"{directory}/{input_folder}/{filename}")

        else:
            # Print a message indicating the file was skipped
            print(f"Skipped {filename} - already present in the output folder.")

def split_image(image_file):
    with Image.open(image_file) as im:
        # Get the width and height of the original image
        width, height = im.size
        # Calculate the middle points along the horizontal and vertical axes
        mid_x = width // 2
        mid_y = height // 2
        # Split the image into four equal parts
        top_left = im.crop((0, 0, mid_x, mid_y))
        top_right = im.crop((mid_x, 0, width, mid_y))
        bottom_left = im.crop((0, mid_y, mid_x, height))
        bottom_right = im.crop((mid_x, mid_y, width, height))
        return top_left, top_right, bottom_left, bottom_right

async def handle_special_image(input_file, output_folder, filename):
    os.rename(input_file, os.path.join(output_folder, filename))

@client.event
async def on_ready():
    print("Bot connected")

@client.event
async def on_message(message):
    logging.info(message.content)

    # Determine the output folder based on the channel's ID
    # The folder has format channelname_uniqueid this is due to perhaps channel naming across servers
    output_folder = f"output_{message.channel.name}_{message.channel.id}"
    if not os.path.exists(output_folder):
        os.makedirs(output_folder)

    # Process images based on dynamic date range if message contains "datehistory"
    if "datehistory:" in message.content:
        date_range = message.content.split(":")[1].strip()  # Extract the date range

        try:
            start_date_str, end_date_str = date_range.split(" to ")
            tz = pytz.timezone('US/Eastern')  # Replace 'YOUR_TIMEZONE' with your desired timezone, e.g., 'US/Eastern'
            start_date = tz.localize(datetime.strptime(start_date_str, "%Y-%m-%d"))
            end_date = tz.localize(datetime.strptime(end_date_str, "%Y-%m-%d"))
            print(f"Start Date: {start_date}, End Date: {end_date}")

            await process_channel_by_date(message.channel, start_date, end_date, output_folder)

            # Send a completion message to the channel
            await message.channel.send("Processing complete!")

        except ValueError as e:
            print(f"Error: {e}")
            return

    for attachment in message.attachments:
        if "Upscaled by" in message.content:
            file_prefix = 'UPSCALED_'
        else:
            file_prefix = ''

        if "Image #" in message.content and attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
            try:
                response = requests.get(attachment.url)
                if response.status_code == 200:
                    output_folder = f"output_{message.channel.name}_{message.channel.id}"
                    if not os.path.exists(output_folder):
                        os.makedirs(output_folder)
                    with open(os.path.join(output_folder, attachment.filename), "wb") as f:
                        f.write(response.content)
            except:
                await asyncio.sleep(10)
                continue
        elif attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
            try:
                response = requests.get(attachment.url)
                if response.status_code == 200:
                    input_folder = "input"
                    output_folder = f"output_{message.channel.name}_{message.channel.id}"
                    if not os.path.exists(input_folder):
                        os.makedirs(input_folder)
                    if not os.path.exists(output_folder):
                        os.makedirs(output_folder)
                    with open(f"{directory}/{input_folder}/{attachment.filename}", "wb") as f:
                        f.write(response.content)
                    print(f"Image downloaded: {attachment.filename}")
                    input_file = os.path.join(input_folder, attachment.filename)
                    file_prefix = os.path.splitext(attachment.filename)[0]
                    top_left, top_right, bottom_left, bottom_right = split_image(input_file)
                    top_left.save(os.path.join(output_folder, file_prefix + "_top_left.png"))
                    top_right.save(os.path.join(output_folder, file_prefix + "_top_right.png"))
                    bottom_left.save(os.path.join(output_folder, file_prefix + "_bottom_left.png"))
                    bottom_right.save(os.path.join(output_folder, file_prefix + "_bottom_right.png"))
                    os.remove(f"{directory}/{input_folder}/{attachment.filename}")
            except:
                await asyncio.sleep(10)
                continue

            # Save the message content as a text file
            if attachment.filename.lower().endswith((".png", ".jpg", ".jpeg", ".gif")):
                try:
                    # Create a folder to store text files if it doesn't exist
                    text_folder = "message_text"
                    if not os.path.exists(text_folder):
                        os.makedirs(text_folder)

                    # Save the message content to a text file
                    message_text = message.content
                    text_filename = f"{attachment.filename}.txt"
                    with open(os.path.join(text_folder, text_filename), "w", encoding="utf-8") as text_file:
                        text_file.write(message_text)
                except Exception as e:
                    print(f"Error saving message text: {e}")

@client.command()
async def explain_datehistory(ctx):
    explanation = "The `datehistory` format is used to specify a date range for processing images. " \
                   "It should be in the format: `datehistory: YYYY-MM-DD to YYYY-MM-DD`.\n" \
                   "For example: `datehistory: 2023-07-10 to 2023-07-11` will process images " \
                   "created between July 10, 2023, and July 11, 2023."
    await ctx.send(explanation)

client.run(discord_token)


from .models import Permission, Permission_types
from identifiers.models import Identifier
from identifiers.serializers import IdentifierSerializer, PermIdentSerializer
from doors.serializers import DoorSerializer
from rest_framework import serializers

class PermissionTypeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Permission_types
        fields = (
            'id', 'name', 'created_date', 'updated_date'
        )

class PermissionSerializer(serializers.ModelSerializer):
#    permission = PermissionTypeSerializer()
#    door = DoorSerializer()
#    identifier = IdentifierSerializer()
    class Meta:
        model = Permission
        fields = (
            'id', 'identifier', 'door', 'permission', 'created_date', 'updated_date'
        )

class IdentSerializer(serializers.ModelSerializer):

    identifier = PermIdentSerializer()
    class Meta:
        model = Permission
        fields = (
            'id','identifier'
        )


from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String, DateTime

Base = declarative_base()

class Sample(Base):
    __tablename__ = 'samples'

    id              = Column(Integer, primary_key=True)
    lab_tech        = Column(String)
    receive_time    = Column(DateTime)

    def __repr__(self):
        return "<Sample(%r, %r)>" % (self.lab_tech, self.receive_time)

class Cell(Base):
    __tablename__ = 'cells'

    cell_id         = Column(Integer)


import sympy as sp
import streamlit as st
import plotly.graph_objects as go
import pandas as pd
import plotly.express as px

from collections import namedtuple

Section = namedtuple("Section", ["begin", "end"])

def get_sections_with_root(f, domain: Section, partition_count):
    h = (domain.end - domain.begin) / partition_count
    points = [domain.begin + i * h for i in range(partition_count + 1)]

    section_with_roots = []

    for begin, end in zip(points[:-1], points[1:]):
        if f(end) * f(begin) <= 0:
            section_with_roots.append(Section(begin, end))

    return section_with_roots

def apply_secant_method(f, section: Section, eps: float):
    x_0 = section.begin
    x_1 = section.end

    def new_solution(x_1, x_0):
        return x_1 - f(x_1) / (f(x_1) - f(x_0)) * (x_1 - x_0)

    x_2 = new_solution(x_1, x_0)
    while abs(x_2 - x_1) >= eps:
        x_0 = x_1
        x_1 = x_2
        x_2 = new_solution(x_1, x_0)

    return x_2

def get_roots(f, domain: Section, partition_count=100000, eps=1e-12):
    return list(
        set(
            apply_secant_method(f, section, eps)
            for section in get_sections_with_root(f, domain, partition_count)
        )
    )

def get_legendre_polynomials(max_degree):
    class LegendrePolynom:
        def __init__(self, prev_poly, prev_prev_poly, degree):
            self.prev_poly = prev_poly
            self.prev_prev_poly = prev_prev_poly
            self.deegre = degree

        def __call__(self, x):
            return (2 * self.deegre - 1) / self.deegre * self.prev_poly(x) * x - (
                self.deegre - 1
            ) / self.deegre * self.prev_prev_poly(x)

    polynomials = [lambda _: 1, lambda x: x]
    if max_degree <= 1:
        return polynomials[: max_degree + 1]

    for degree in range(2, max_degree + 1):
        polynomials.append(LegendrePolynom(polynomials[-1], polynomials[-2], degree))

    return polynomials

@st.cache(ttl=3600, max_entries=10)
def find_nodes_coefficients_gauss(max_degree, eps=1e-12):
    polynomials = get_legendre_polynomials(max_degree)
    node_count = len(polynomials) - 1

    polynom = polynomials[node_count]
    roots = get_roots(polynom, Section(-1, 1), eps=eps)

    assert len(roots) == node_count

    coefficients = [
        (
            2
            * (1 - roots[k - 1] ** 2)
            / ((node_count ** 2) * (polynomials[node_count - 1](roots[k - 1]) ** 2))
        )
        for k in range(1, node_count + 1)
    ]

    return roots, coefficients

def linear_offset(nodes, coefficients, domain):
    q = (domain.end - domain.begin) / 2
    return list(map(lambda node: domain.begin + q * (node + 1), nodes)), list(
        map(lambda coef: q * coef, coefficients)
    )

def calculate_gauss_integral(f, nodes, coefficients):
    return sum(
        float(f.evalf(subs={"x": node})) * coef
        for node, coef in zip(nodes, coefficients)
    )

@st.cache(ttl=3600, max_entries=10)
def calculate_expected_integral(f_str, p_str, begin, end):
    f: sp.Expr = sp.sympify(f_str)
    p: sp.Expr = sp.sympify(p_str)
    return float(sp.integrate(f * p, ("x", begin, end)))

def show_nodes_on_axis(nodes):
    fig = go.Figure()
    fig.add_trace(
        go.Scatter(
            x=nodes,
            y=[0 for _ in nodes],
            marker={"size": 7},
            line={"color": "red", "width": 3},
            name="Узлы",
            mode="markers",
            hovertemplate="%{x}",
        )
    )
    fig.update_xaxes(showgrid=False)
    fig.update_yaxes(
        showgrid=False,
        zeroline=True,
        zerolinecolor="gray",
        zerolinewidth=2,
        showticklabels=False,
    )
    fig.update_layout(height=70, plot_bgcolor="white", margin=dict(b=0, t=60))
    st.write(fig)

def calculate_gauss_integral_with_partition(
    f, nodes, coefficients, partition_count, begin, end
):
    h = (end - begin) / partition_count
    actual_with_partition = 0
    for i in range(1, partition_count + 1):
        new_section = Section(begin + (i - 1) * h, begin + i * h)
        partition_nodes, partition_coefficients = linear_offset(
            nodes, coefficients, new_section
        )
        actual_with_partition += calculate_gauss_integral(
            f, partition_nodes, partition_coefficients
        )
    return actual_with_partition

@st.cache(ttl=3600, max_entries=10)
def calculate_moments(p_str, begin, end, nodes_count):
    p: sp.Expr = sp.sympify(p_str)
    return [
        float(sp.integrate(p * (sp.sympify("x") ** i), ("x", begin, end)))
        for i in range(2 * int(nodes_count))
    ]

@st.cache(ttl=3600, max_entries=10)
def show_error_minimizing(
    partition_count, f_str, p_str, nodes, coefficients, expected, begin, end
):
    errors = []
    f = sp.sympify(f_str)
    p = sp.sympify(p_str)
    for i in range(1, partition_count + 1):
        errors.append(
            float(
                abs(
                    calculate_gauss_integral_with_partition(
                        f * p, nodes, coefficients, i, begin, end
                    )
                    - expected
                )
            )
        )

    return px.line(
        pd.DataFrame(
            {
                "Absolute error": errors,
                "Partition count": range(1, partition_count + 1),
            }
        ),
        x="Partition count",
        y="Absolute error",
    )


from django.shortcuts import render
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, generics
from rest_framework import permissions
from .models import Studio, ImageAttachment, Location,Course,Enroll,Drop, ClassDate, Amenitie
from .serializers import (
    StudioSerializer,
    ImageSerializer,
    Studio2Serializer,
    LocationSerializer,
    CourseSerializer,
    EnrollSerializer,
    DropSerializer,
    AddCourseSerializer,
    DropCourseSerializer,
    ClassDateSerializer,
    EnrollmentSerializer,
    AmenetiesSerializer,
    # DropClassDateSerializer
)
from rest_framework.parsers import FileUploadParser, MultiPartParser
from rest_framework.permissions import  IsAdminUser, IsAuthenticated
from location_field.models.plain import PlainLocationField
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.generics import GenericAPIView
from rest_framework import filters
from rest_framework.pagination import PageNumberPagination
import datetime as dt
from datetime import timedelta
import ast
import copy
from django.conf import settings
from collections import OrderedDict
from django.db.models import Case, When

class CustomPageNumberPagination(PageNumberPagination):
    page_size_query_param = 'size'  # items per page

# class DropClassDateView(generics.UpdateAPIView):
#     queryset = Enroll.objects.all()
#     permission_classes = (IsAuthenticated, )
#     serializer_class = DropClassDateSerializer

    # def get_queryset(self):
    #     return Enroll.objects.filter(user=self.request.user)

class StandardResultsSetPagination(PageNumberPagination):
    page_size = 1
    # page_size_query_param = 'page_size'
    # max_page_size = 2

class StudioListALLApiView(generics.ListAPIView):
    queryset = Studio.objects.all()
    serializer_class = StudioSerializer
    permission_classes = (IsAuthenticated,)
    pagination_class = None
    def get(self, request, *args, **kwargs):
        studioList = Studio.objects
        serializer = StudioSerializer(studioList, many = True, context={"request": request})
        sortedData = sorted(serializer.data, key = lambda k:k['distance'])
        # print(sortedData)
        return Response(sortedData, status = status.HTTP_200_OK)

class StudioListApiView(generics.ListAPIView):
    queryset = Studio.objects.all()
    serializer_class = StudioSerializer
    permission_classes = (IsAuthenticated,)
    pagination_class = CustomPageNumberPagination
    # def get(self, request, *args, **kwargs):
    #     studioList = Studio.objects
    #     serializer = StudioSerializer(studioList, many = True, context={"request": request})
    #     sortedData = sorted(serializer.data, key = lambda k:k['distance'])
    #     # print(sortedData)
    #     return Response(sortedData, status = status.HTTP_200_OK)
    def get_queryset(self):
        studioList = Studio.objects
        serializer = StudioSerializer(studioList, many = True, context={"request": self.request})
        sortedData = sorted(serializer.data, key = lambda k:k['distance'])

        print("sortedData", sortedData)

        returnlist = []
        for ele in sortedData:
            print("\n")
            dict_ele = dict(ele)
            print("ele", dict_ele)
            # print("id", ele[1])
            returnlist.append(dict_ele["id"])
            # pass

        # print("s", returnlist)
        # print("ssss", list(map(lambda id: Studio.objects.get(pk=id), returnlist)))
        return list(map(lambda id: Studio.objects.get(pk=id), returnlist))

# class StudioListApiView(generics.ListAPIView):
#     queryset = Studio.objects.all()
#     serializer_class = StudioSerializer
#     permission_classes = (IsAuthenticated,)
#     pagination_class = PageNumberPagination
#     # pagination_class = settings.REST_FRAMEWORK['DEFAULT_PAGINATION_CLASS' ]
#     # pagination_class = StandardResultsSetPagination
#     # pagination.PageNumberPagination.page_size = 1

#     def get(self, request, *args, **kwargs):
#         paginator = StandardResultsSetPagination()
#         studioList = Studio.objects.all()
#         paginate_queryset = paginator.paginate_queryset(studioList, request)
#         serializer = StudioSerializer(paginate_queryset, many = True, context={"request": request}).data

#         # serializer = StudioSerializer(studioList, many = True, context={"request": request})

#         data2 = paginator.get_paginated_response(serializer).data
#         print(data2['results'])
#         data2['results'] = sorted(data2['results'], key = lambda k:k['distance'])
#         # print(settings.REST_FRAMEWORK)
#         # sortedData = sorted(data2.items(), key  = lambda k:k['distance'])
#         # print(settings.REST_FRAMEWORK['PAGE_SIZE'], "page size")
#         # print(sortedData)
#         return Response(data2, status = status.HTTP_200_OK)

class StudioSearchListApiView(generics.ListAPIView):
    # parser_classes = (FileUploadParser, MultiPartParser)
    # queryset = Studio.objects.all().order_by('distance')
    queryset = Studio.objects.all()
    serializer_class = StudioSerializer
    permission_classes = (IsAuthenticated,)
    # filter_backends = [filters.OrderingFilter,DjangoFilterBackend,filters.SearchFilter]
    filter_backends = [DjangoFilterBackend,filters.SearchFilter]
    pagination_class = CustomPageNumberPagination

    filterset_fields = ['name','amenities__Amname','classes__classNames', 'coaches__coachNames']
    search_fields = ['name','amenities__Amname','classes__classNames', 'coaches__coachNames']
    # ordering_fields = ['distance',]

# class StudioListApiView(APIView):
#     permission_classes = [permissions.IsAuthenticated]
#     filter_backends = [DjangoFilterBackend]
#     filterset_fields = ['name',]
#     def get(self, request, *args, **kwargs):
#         studioList = Studio.objects
#         serializer = StudioSerializer(studioList, many = True)
#         sortedData = sorted(serializer.data, key = lambda k:k['distance'])
#         return Response(sortedData, status = status.HTTP_200_OK)

class StudioProfileApiView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    def get(self, request, *args, **kwargs):
        studioNum = Studio.objects.get(id = self.kwargs['pk'])
        serializer = Studio2Serializer(studioNum, many = False)
        return Response(serializer.data, status = status.HTTP_200_OK)

# class StudioEditApiView(generics.RetrieveUpdateDestroyAPIView):
#     serializer_class = StudioSerializer
#     queryset = Studio.objects.all()
#     permission_classes = (IsAdminUser,)

class ImageListView(generics.ListCreateAPIView):
    queryset = ImageAttachment.objects.all()
    serializer_class = ImageSerializer

class ImageEditView(generics.RetrieveUpdateDestroyAPIView):
    serializer_class = ImageSerializer
    queryset = ImageAttachment.objects.all()

class CreateSpecificLocation(generics.CreateAPIView):
    # permission_classes = [permissions.IsAuthenticated]
    # def post(self, request, *args, **kwargs):
    #     location = self.objects
    #     return location
    serializer_class = LocationSerializer
    queryset = Location.objects.all()
    permission_classes = (IsAuthenticated,)

class UpdateSpecificLocation(generics.UpdateAPIView):
    # permission_classes = [permissions.IsAuthenticated]
    # def post(self, request, *args, **kwargs):
    #     location = self.objects
    #     return location
    serializer_class = LocationSerializer
    queryset = Location.objects.all()
    permission_classes = (IsAuthenticated,)

    def get_object(self):
        return Location.objects.filter(user=self.request.user)[0]

class ClassDateListView(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = ClassDateSerializer
    pagination_class = CustomPageNumberPagination

    def get_queryset(self):
        return ClassDate.objects.filter(
            course_id=self.kwargs["pk"],
            date_start__gte = dt.datetime.now()
        ).order_by('date_start')

class ClassListView(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = CustomPageNumberPagination

    # queryset = Course.objects.all()
    # for i in queryset:
    #     print(i.start_time)
        # print(i.end_time)
    # print('queryset',queryset)
    filter_backends = [DjangoFilterBackend,filters.SearchFilter]
    serializer_class = CourseSerializer
    filterset_fields = ['name','coach',]
    search_fields = ['name','coach',]

    def get_queryset(self):
        courses = Course.objects.filter(
            studio=Studio.objects.get(id = self.kwargs["pk"]),
        ).order_by('start_time')

        c_list = []
        for course in courses:
            if course.times.rrules[0].until.replace(tzinfo=None) > dt.datetime.today():
                c_list.append(course.id)

        return Course.objects.filter(id__in=c_list)

class ClassListViewSchedule(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = CustomPageNumberPagination

    # queryset = Course.objects.all()
    # for i in queryset:
    #     print(i.start_time)
        # print(i.end_time)
    # print('queryset',queryset)
    filter_backends = [DjangoFilterBackend,filters.SearchFilter]
    serializer_class = ClassDateSerializer
    filterset_fields = ['name','coach',]
    search_fields = ['name','coach',]

    def get_queryset(self):
        classes = ClassDate.objects.filter(studio_id = Studio.objects.get(id=self.kwargs["pk"]).id,).order_by('date_start')
        c_list = []
        for classi in classes:
            if classi.date_end.replace(tzinfo=None) > dt.datetime.today():
                c_list.append(classi.id)
        return ClassDate.objects.filter(id__in = c_list).order_by("date_start")

    # def get_queryset(self):
    #     # studio_name = Studio.objects.filter(id = self.kwargs['pk'])
    #     classList = Course.objects.filter(studio_id = self.kwargs['pk'])
    #     # print(classList)
    #     c_list = []
    #     for c in classList:
    #         if c.times.rrules[0].until is None:
    #             print('No End date')
    #             print('start',c.times.dtstart)
    #             c_list.append(c.id)
    #         else: # filter past occurrence
    #             # occurrence = c.times.occurrences()
    #             occurrence = c.times.between(c.start_time.replace(tzinfo=None), c.times.rrules[0].until.replace(tzinfo=None))
    #             if len(list(occurrence)) > 0:
    #                 c_list.append(c.id)
    #             # print(list(occurrence))
    #             # print(c.start_time.replace(tzinfo=None))
    #             # print(c.times.rrules[0].until.replace(tzinfo=None))

    #     #newClassList include no end date events +  end date events has future occurrence
    #     newClassList = Course.objects.filter(id__in=c_list, studio_id = self.kwargs['pk'])
    #     serializer = CourseSerializer(newClassList, many = True)
    #     results = []
    #     for obj in newClassList:
    #         times_dict = {}
    #         occur_list = []
    #         occurrence = obj.times.between(obj.start_time.replace(tzinfo=None), obj.times.rrules[0].until.replace(tzinfo=None))
    #         if obj.times.rrules[0].until is None:
    #             occur_list.append("Indefinitely")
    #         else:
    #             for occ in occurrence:
    #                 occur_list.append([dt.datetime.combine(occ.date(), obj.start_time.time()), dt.datetime.combine(occ.date(), obj.end_time.time())])

    #         data = {}
    #         data['name'] = obj.name
    #         data['description'] = obj.description
    #         data['coach'] = obj.coach
    #         data['start_time'] = dt.datetime.combine(occurrence[0].date(), obj.start_time.time())
    #         data['end_time'] = dt.datetime.combine(occurrence[0].date(), obj.end_time.time())
    #         data['keywords'] = obj.keywords
    #         data['capacity'] = obj.capacity

    #         # print(list(obj.times))
    #         text_rules = []
    #         for rule in obj.times.rrules:
    #             text_rules.append(rule.to_text())

    #         text_exrules = []
    #         for rule in obj.times.exrules:
    #             text_exrules.append(rule.to_text())

    #         times_dict['starts'] = obj.start_time.time()
    #         times_dict['ends'] = obj.end_time.time()
    #         times_dict["RRULE"] = text_rules
    #         times_dict["exrules"] = text_exrules
    #         times_dict["occurrence"] = occur_list

    #         data['times'] = times_dict

    #         results.append(data)
    #         obj.start_time = data['start_time']
    #         obj.end_time = data['end_time']

    #         # obj.times = str(data['times'])

    #         # obj.save()
    #     sortedResults = sorted(results, key=lambda d: d['start_time'])

    #     # return Response(sortedResults, status = status.HTTP_200_OK)
    #     # print(dt.datetime.now())
    #     return Course.objects.filter(start_time__gte=dt.datetime.now()).order_by('start_time')

class EnrollmentView(generics.CreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = EnrollSerializer
    queryset = Enroll.objects.all()
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)
    # print(list(queryset))

class EnrollmentCourseView(generics.CreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = AddCourseSerializer
    queryset = Enroll.objects.all()
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

class DropView(generics.CreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = DropSerializer
    queryset = Drop.objects.all()
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

class DropCourseView(generics.CreateAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = DropCourseSerializer
    queryset = Drop.objects.all()
    def perform_create(self, serializer):
        serializer.save(user=self.request.user)

class DropDateListView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    def get(self, request, *args, **kwargs):
        dropDateList = Drop.objects.filter(user = request.user.id)
        serializer = DropSerializer(dropDateList, many = True)
        # sortedData = sorted(serializer.data, key = lambda k:k['distance'])
        return Response(serializer.data, status = status.HTTP_200_OK)

# class EnrollmentView(generics.CreateAPIView):
#     permission_classes = [permissions.IsAuthenticated]
#     serializer_class = EnrollmentSerializer
#     queryset = Enrollment.objects.all()
#     def perform_create(self, serializer):
#         serializer.save(user=self.request.user)
#     # print(list(queryset))

        data2['results'] = sorted(data2['results'], key = lambda k:k['distance'])
        return Response(data2, status = status.HTTP_200_OK)

class EnrollmentCourseListView(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = CustomPageNumberPagination
    serializer_class = CourseSerializer

    def get_queryset(self):
        enrollmentList = Enroll.objects.filter(user = self.request.user.id, is_dropped = False, enrollDate__date_end__gte = dt.datetime.now())

        course_id_list = set()
        for enroll in enrollmentList:
            course_id_list.add(enroll.enrollDate.course_id)

        course_id_list = list(course_id_list)
        return Course.objects.filter(id__in=course_id_list)

class EnrollmentListView(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = CustomPageNumberPagination
    # serializer_class = ClassDateSerializer
    serializer_class = EnrollmentSerializer

    def get_queryset(self):
        # enrollmentList = Enroll.objects.filter(user = request.user.id, is_dropped = False, enrollDate__date_end__gte = dt.datetime.now()+timedelta(days=8))
        enrollmentList = Enroll.objects.filter(user = self.request.user.id, is_dropped = False, enrollDate__date_end__gte = dt.datetime.now())
        # serializer = EnrollSerializer(enrollmentList, many = True)
        # return enrollmentList

        start_time_dict = {}
        for enroll in enrollmentList:
            start_time_dict[enroll.id] = enroll.enrollDate.date_start

        sorted_id = list(dict(sorted(start_time_dict.items(), key=lambda item : item[1], reverse=False)).keys())

        preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(sorted_id)])
        queryset = Enroll.objects.filter(pk__in=sorted_id).order_by(preserved)

        return queryset

        results = []
        for obj1 in enrollmentList:
            data = {}
            data["enroll_id"] = obj1.id
            data['classDate_id'] = obj1.enrollDate.id
            data["course_id"] = obj1.enrollDate.course_id
            data['date_start'] = obj1.enrollDate.date_start
            data['date_end'] = obj1.enrollDate.date_end
            results.append(data)

        sortedResults = sorted(results, key = lambda k:k['date_start'])
        print(sortedResults)

        returnlist = []
        for ele in sortedResults:
            returnlist.append(ele["classDate_id"])
        # return Response(sortedResults, status = status.HTTP_200_OK)

        print("returnlist",returnlist)
        print(ClassDate.objects.filter(id__in = returnlist ))
        # return ClassDate.objects.filter(id__in = returnlist )
        return list(map(lambda id: ClassDate.objects.get(pk=id), returnlist))

# class EnrollmentListView(APIView):
#     permission_classes = [permissions.IsAuthenticated]
#     pagination_class = PageNumberPagination
#     def get(self, request, *args, **kwargs):
#         # enrollmentList = Enroll.objects.filter(user = request.user.id, is_dropped = False, enrollDate__date_end__gte = dt.datetime.now()+timedelta(days=8))
#         paginator = StandardResultsSetPagination()
#         enrollmentList = Enroll.objects.filter(user = request.user.id, is_dropped = False, enrollDate__date_end__gte = dt.datetime.now())
#         paginate_queryset = paginator.paginate_queryset(enrollmentList, request)
#         serializer = EnrollSerializer(paginate_queryset, many = True).data
#         print('!!!!!!!!', serializer)
#         data2 = paginator.get_paginated_response(serializer).data

#         results = []
#         for obj1 in enrollmentList:
#             data = OrderedDict()
#             data["enroll_id"] = obj1.id
#             data['classDate_id'] = obj1.enrollDate.id
#             data["course_id"] = obj1.enrollDate.course_id
#             data['date_start'] = obj1.enrollDate.date_start
#             data['date_end'] = obj1.enrollDate.date_end
#             results.append(data)

#         # print('?????????',ord_dict)
#         data2['results'] = sorted(results, key = lambda k:k['date_start'])
#         # data2['results'] = sorted(data2['results'], key = lambda k:k['date_start'])

#         # print(data2)
#         return Response(data2, status = status.HTTP_200_OK)

class HistoryListView2(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    pagination_class = CustomPageNumberPagination
    serializer_class = EnrollmentSerializer

    def get_queryset(self):
        enrollmentList = Enroll.objects.filter(user = self.request.user, enrollDate__date_end__lt = dt.datetime.now())

        print(Enroll.objects.filter(user=self.request.user))

        start_time_dict = {}
        for enroll in enrollmentList:
            start_time_dict[enroll.id] = enroll.enrollDate.date_start

        sorted_id = list(dict(sorted(start_time_dict.items(), key=lambda item : item[1], reverse=False)).keys())

        preserved = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(sorted_id)])
        queryset = Enroll.objects.filter(pk__in=sorted_id).order_by(preserved)

        return queryset

class HistoryListView(APIView):
    permission_classes = [permissions.IsAuthenticated]
    def get(self, request, *args, **kwargs):
        # date_now_more_5_days = (datetime.now() + timedelta(days=5) )
        # enrollmentList = Enroll.objects.filter(user = request.user.id, enrollDate__date_end__lt = dt.datetime.now()+timedelta(days=8))
        enrollmentList = Enroll.objects.filter(user = request.user.id, enrollDate__date_end__lt = dt.datetime.now())
        serializer = EnrollSerializer(enrollmentList, many = True)

        results = []
        for obj1 in enrollmentList:
            data = {}
            data['enroll_id'] = obj1.id
            data["classDate_id"] = obj1.enrollDate.id
            data["course_id"] = obj1.enrollDate.course_id
            data['date_start'] = obj1.enrollDate.date_start
            data['date_end'] = obj1.enrollDate.date_end
            data["is_dropped"] = obj1.is_dropped
            results.append(data)

        sortedResults = sorted(results, key = lambda k:k['date_start'])
        print(sortedResults)
        return Response(sortedResults, status = status.HTTP_200_OK)

# class DropCourseView(generics.RetrieveDestroyAPIView):
#     permission_classes = [permissions.IsAuthenticated]
#     serializer_class = DropCourseSerializer
#     queryset = Enrollment.objects.all()
#     # serializer_class = DropCourseSerializer
#     # queryset = DropCourse.objects.all()
#     # permission_classes = (IsAuthenticated,)

#     def get(self, request, *args, **kwargs):
#         enrolCourseNum = Enrollment.objects.get(id = self.kwargs['pk'])
#         serializer = EnrollmentSerializer(enrolCourseNum, many = False)
#         # print('self.request.user',self.request.user.id)
#         if serializer.data['user']!=self.request.user.id:
#             return HttpResponse('Unauthorized', status=401)

#         return Response(serializer.data, status = status.HTTP_200_OK)

#     def post(self, request, *args, **kwargs):
#         enrol = Enrollment.objects.get(id = self.kwargs['pk'])
#         enrolCourseSessionstr = enrol.session
#         print(enrolCourseSessionstr)
#         if enrolCourseSessionstr == None:
#             pass
#         else:
#             enrolCourseSession = ast.literal_eval(enrolCourseSessionstr)

#             exclude_start_get = request.POST.get('exclude_start')
#             if exclude_start_get == '':
#                 exclude_start = None
#             else:
#                 exclude_start = dt.datetime.strptime(exclude_start_get, "%Y-%m-%d").date()

#             exclude_end_get = request.POST.get('exclude_end')
#             if exclude_end_get == '':
#                 exclude_end = None
#             else:
#                 exclude_end = dt.datetime.strptime(exclude_end_get, "%Y-%m-%d").date()
#             newsession_li = []
#             if len(enrolCourseSession) > 0:
#                 if enrolCourseSession[0] == "Indefinitely":
#                     pass
#                 else:
#                     if exclude_start == None and exclude_end == None:
#                         newsession_li = copy.deepcopy(enrolCourseSession)

#                     elif exclude_start == None and exclude_end != None:
#                         for ele in enrolCourseSession:
#                             end_t = dt.datetime.strptime(ele[1], "%Y-%m-%d %H:%M:%S").date()

#                             if exclude_end < end_t:
#                                 newsession_li.append(ele)

#                     elif exclude_start != None and exclude_end == None:
#                         for ele in enrolCourseSession:
#                             start_t = dt.datetime.strptime(ele[0], "%Y-%m-%d %H:%M:%S").date()
#                             if exclude_start > start_t:
#                                 newsession_li.append(ele)

#                     elif exclude_start != None and exclude_end != None:
#                         for ele in enrolCourseSession:
#                             start_t = dt.datetime.strptime(ele[0], "%Y-%m-%d %H:%M:%S").date()
#                             end_t = dt.datetime.strptime(ele[1], "%Y-%m-%d %H:%M:%S").date()
#                             print('exclude_end',exclude_end)
#                             print('end_t',end_t)
#                             if exclude_start > start_t and exclude_end < end_t:
#                                 newsession_li.append(ele)

#             enrol.session = newsession_li
#             enrol.save()

#         return Response(status = status.HTTP_200_OK)

class filterDateView(generics.ListAPIView):
    # serializer_class = CourseSerializer
    serializer_class = ClassDateSerializer
    pagination_class = CustomPageNumberPagination

    def get_queryset(self):
        filterDate = self.kwargs['filterDate']
        filterDate_startT_str = filterDate + " 00:00:00"
        filterDate_endT_str = filterDate + " 23:59:59"
        filterDate_startT = dt.datetime.strptime(filterDate_startT_str, "%Y-%m-%d %H:%M:%S")
        filterDate_endT = dt.datetime.strptime(filterDate_endT_str, "%Y-%m-%d %H:%M:%S")
        returnlist=[]
        cdf = ClassDate.objects.filter(studio_id = self.kwargs['pk'], date_start__gte = filterDate_startT, date_start__lte = filterDate_endT )
        return cdf.order_by("date_start")
        # for cd in cdf:
        #     returnlist.append(cd.course_id)
        # return Course.objects.filter(id__in = returnlist )

class filterTimeRangeView(generics.ListAPIView):
    # serializer_class = CourseSerializer
    serializer_class = ClassDateSerializer
    pagination_class = CustomPageNumberPagination
    def get_queryset(self):
        filterstartT_str = self.kwargs['filterstart']
        filterendT_str = self.kwargs['filterend']

        filterstartT = dt.datetime.strptime(filterstartT_str, "%H:%M:%S")
        filterendT = dt.datetime.strptime(filterendT_str, "%H:%M:%S")
        # print("filterstartT",filterstartT)
        course_id_li = []
        courseList = Course.objects.filter(studio_id = self.kwargs['pk'])
        for course_i in courseList:
            print("filterstartT.time()",filterstartT.time())
            print("course_i.start_time.time()",course_i.start_time.time())

            if (course_i.start_time.time() >= filterstartT.time() and course_i.start_time.time() <= filterendT.time()
                and course_i.end_time.time() >= filterstartT.time() and course_i.end_time.time() <= filterendT.time()):

                    course_id_li.append(course_i.id)

        classes = ClassDate.objects.filter(course_id__in = course_id_li)
        c_list = []
        for classi in classes:
            if classi.date_end.replace(tzinfo=None) > dt.datetime.today():
                c_list.append(classi.id)
        return ClassDate.objects.filter(id__in = c_list).order_by("date_start")

class ClassListAllView(generics.ListAPIView):
    permission_classes = [permissions.IsAuthenticated]
    serializer_class = CourseSerializer
    pagination_class = None
    def get_queryset(self):
        courses =  Course.objects.filter(studio=Studio.objects.get(id = self.kwargs["pk"])).order_by('start_time')

        c_list = []
        for course in courses:
            if course.times.rrules[0].until.replace(tzinfo=None) > dt.datetime.today():
                c_list.append(course.id)

        return Course.objects.filter(id__in=c_list)

class CourseListALLApiView(generics.ListAPIView):
    queryset = Course.objects.all()
    serializer_class = CourseSerializer
    permission_classes = (IsAuthenticated,)
    pagination_class = None

    def get_queryset(self):
        c_list = []
        for course in Course.objects.all():
            if course.times.rrules[0].until.replace(tzinfo=None) > dt.datetime.today():
                c_list.append(course.id)

        return Course.objects.filter(id__in=c_list)

class AmenityListALLApiView(generics.ListAPIView):
    queryset = Amenitie.objects.all()
    serializer_class = AmenetiesSerializer
    permission_classes = (IsAuthenticated,)
    pagination_class = None


"""add fides cloud table

Revision ID: 192f23f4c968
Revises: 093bb28a8270
Create Date: 2023-09-01 17:28:22.099543

"""
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import postgresql

# revision identifiers, used by Alembic.
revision = "192f23f4c968"
down_revision = "093bb28a8270"
branch_labels = None
depends_on = None

def upgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.create_table(
        "fidescloud",
        sa.Column("id", sa.String(length=255), nullable=False),
        sa.Column(
            "created_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=True,
        ),
        sa.Column(
            "updated_at",
            sa.DateTime(timezone=True),
            server_default=sa.text("now()"),
            nullable=True,
        ),
        sa.Column("config", postgresql.JSONB(astext_type=sa.Text()), nullable=True),
        sa.PrimaryKeyConstraint("id"),
    )
    op.create_index(op.f("ix_fidescloud_id"), "fidescloud", ["id"], unique=False)
    # ### end Alembic commands ###

def downgrade():
    # ### commands auto generated by Alembic - please adjust! ###
    op.drop_index(op.f("ix_fidescloud_id"), table_name="fidescloud")
    op.drop_table("fidescloud")
    # ### end Alembic commands ###


import numpy as np
from scipy.io import FortranFile as FF

import pynbody

def test_load_adaptahop_catalogue():
    f = pynbody.load("testdata/output_00080")
    h = f.halos()
    assert len(h) == h._headers["nhalos"] + h._headers["nsubs"]

def test_load_one_halo():
    def helper(path, nhalos):
        f = pynbody.load(path)
        h = f.halos()
        np.testing.assert_allclose(h[1].properties["members"], h[1]["iord"])
        assert len(h) == nhalos

    for path, nhalos in (
        ("testdata/output_00080", 170),
        ("testdata/new_adaptahop_output_00080", 2),
    ):
        yield helper, path, nhalos

def test_get_group():
    f = pynbody.load("testdata/output_00080")
    h = f.halos()

    group_array = h.get_group_array()
    iord = f.dm["iord"]

    for halo_id in range(1, len(h) + 1):
        mask = group_array == halo_id

        # Check that the indices
        # - read from halo (h[halo_id]['iord'])
        # - obtained from get_group_array masking
        # are the same (in term of sets)
        iord_1 = np.sort(iord[mask])
        iord_2 = np.sort(h[halo_id]["iord"])

        np.testing.assert_equal(iord_1, iord_2)

def test_halo_particle_ids():
    f = pynbody.load("testdata/output_00080")
    h = f.halos()

    with FF(h._fname, mode="r") as f:
        for halo_id in range(1, len(h) + 1):
            # Manually read the particle ids and make sure pynbody is reading them as it should
            f._fp.seek(h[halo_id].properties["file_offset"])

            f.read_ints()  # number of particles
            expected_members = f.read_ints("i")  # halo members

            np.testing.assert_equal(expected_members, h[halo_id].properties["members"])