import datetime  # new game with village designed before
import logging  # debugging module
import random  # random numbers and choices
import sys  # sys.exit() sys.argv
import time  # time delays time.sleep()

import data  # zville database
import zville  # zville functions

# SETTING UP

# debugging setup #levels:debug, info, warning, error, critical
# logging to file
# logging.basicConfig(filename='debug_zville.txt', level=logging.DEBUG,
#                    format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%d/%m/%Y %H:%M:%S')  # DEBUG to file
# logging to console
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')  # DEBUG to console
# logging levels to disable
logging.disable(logging.DEBUG)  # disables all logging messages at <LEVEL> or lover level

# variables setup
sim_speed = 2  # need to be saved between simulations so its outside loop
saved_family = ()  # tuple here will store designed family
saved_village = ()  # tuple here will store designed village
random_village = True
random_family = True

# MAIN LOOP
while True:
    # SETTING_UP VARIABLES
    logging.info('Started main loop, # setting up variables')
    familyChar = []
    familyStats = []
    village_name = ''
    village_pop = 0

    # set up second game that had family or village designed
    if not random_family:
        familyChar = list(saved_family[0])  # tuple to list
        familyStats = list(saved_family[1])  # tuple to list
    if not random_village:
        village_name = saved_village[0]
        village_pop = saved_village[1]
        real_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    real_time = None

    intro_family, spam = zville.family_gen(True)
    initial_wave = len(intro_family)
    patient_zero = random.choice(intro_family)
    zombies_win = False  # useful for end game code

    # MAIN MENU CODE
    logging.info('User menu loop')
    while True:  # Handles user menu choices before game starts
        # zville.user_menu_choice(), # Main game menu returns integer
        main_choice = zville.user_menu_choice()

        if main_choice == 0:  # Intro game
            zville.intro_sim(data.STORY)  # Intro story
            random_family = True
            random_village = True
            village_name, village_pop, real_time = zville.village_gen(random_village)
            familyChar, familyStats = zville.family_gen(random_family)
            sim_speed = 1
            break

        elif main_choice == 5:  # Set Sim Speed
            while True:
                try:
                    print(' Set sim speed: '.center(50, '='))
                    print('Choose digit to set simulation speed')
                    print('\n1 - controlled by user pressing or holding enter key\n\n'
                          '2 - controlled by time intervals (default speed)\n\n3 - instant '
                          'simulation running to the end')
                    sim_speed = int(input())
                    if sim_speed not in [1, 2, 3]:
                        raise ValueError
                    print_s = ' SPEED SET = {sim_speed} '.format(sim_speed=sim_speed)
                    print(print_s.center(50, '+'))
                    break
                except ValueError:
                    print('Type an integer in range 1-3'.center(50, '='))
            continue

        elif main_choice == 1:  # Start Random Sim, will reset designed village and designed family
            random_family = True
            random_village = True
            familyChar, familyStats = zville.family_gen(random_family)
            village_name, village_pop, real_time = zville.village_gen(random_village)
            break

        elif main_choice == 2:  # Start Designed Sim, make checks whether family
            # or village are designed by user and asks for confirmation
            if not random_family and not random_village:
                print('Family: ' + ' '.join(familyChar), '\nvillage is: ', village_name,
                      village_pop, 'sim speed =', sim_speed)
            elif not random_family and random_village:
                print('Family: ' + ' '.join(familyChar), '\nVillage name and size will be random '
                      'and sim speed =', sim_speed)
                village_name, village_pop, real_time = zville.village_gen(random_village)  # case where only village is random,
                # if above is not generated here causes IndexError: list index out of range at introduction
            elif random_family and not random_village:
                print('Village: ', village_name, village_pop, '\nFamily members will be random and '
                      'sim speed =', sim_speed)
                familyChar, familyStats = zville.family_gen(random_family)
            else:
                print('Nothing designed yet.')
                continue
            if zville.yes_or_no('Do you want to start simulation with those settings?') == 'no':
                print('\nSetting village and family to random.')
                random_family = True
                familyChar = []
                familyStats = []
                random_village = True
                village_name, village_pop, real_time = None, None, None
                continue
            else:
                break  # in case user accept custom settings game starts with them

        elif main_choice == 3:  # Design Village
            random_village = False
            village_name, village_pop, real_time = zville.village_gen(random_village)
            saved_village = (village_name, village_pop)  # retaining village data for next games
            continue
        elif main_choice == 4:  # Design Family
            random_family = False
            familyChar, familyStats = zville.family_gen(random_family)
            saved_family = (tuple(familyChar), tuple(familyStats))  # saves family for next game, must be tuple to NOT reference familyChar familyStats lists
            continue
        elif main_choice == 6:  # Exit Sim
            sys.exit()

    # BEGINNING SCENE
    logging.info('Beginning scene.')
    print('='*79)
    story2 = (
        'Village {Yeovil}  {date_time}\npopulation size {p1530}\n{r_weather}\nThere {are}'
        '{John_and_Mark}\n...{doing_shopping}.\nAll of sudden {patient_zero} falls on the '
        'ground, pale like snow and is all in tremors...\n').format(
            Yeovil=village_name, date_time=real_time, p1530=str(village_pop),
            r_weather=zville.f_weather('day'), are='is ' if len(intro_family) == 1 else 'are ',
            John_and_Mark=', '.join(intro_family), doing_shopping=random.choice(data.LOCATIONS),
            patient_zero=patient_zero)

    twist_a = (
        'Everybody are shocked.\n{random_intro_family} crouches trying to help and '
        'something terrific happens...\n{patient_zero} turns into a zombie and attacks '
        'the living!\nBlood rushes forth...\n'
        'Soon there are {initial_wave} zombies shuffling toward nearest house...\n\n')\
        .format(random_intro_family=random.choice(intro_family),
                patient_zero=patient_zero, initial_wave=initial_wave)

    twist_b = (
        'There is nobody at hand to help. After a minute someone notices '
        'lying body\n... and runs away.\nMeanwhile {patient_zero} arises as a'
        ' zombie and shuffles toward nearest house.\nThere is'
        ' just this one zombie to brave new world...\n\n').format(patient_zero=patient_zero)

    # how fast story is printed out
    if sim_speed == 1:
        story2_delay = 0.03
    elif sim_speed == 2:
        story2_delay = 0.02
    else:
        story2_delay = 0

    # printing out above story with correct twist
    narration = story2
    while True:
        for item in narration:
            print(item, end='')
            time.sleep(story2_delay)
        if narration in [twist_a, twist_b]:
            break
        elif len(intro_family) != 1:
            intro_family.remove(patient_zero)
            narration = twist_a
        else:
            narration = twist_b

    # time delay after intro scene
    zville.press_enter() if sim_speed in [1, 2] else time.sleep(0)

    # NOW BUNCH OF VARIABLES FOR COMING SIMULATION
    logging.info('Bunch of variables after beginning scene.')
    pulped_body = 0
    grid_data, houses_number   = zville.gen_grid(village_pop)             # takes: population size, returns: grid_data (list of lists) and houses_number(integer)
    family_custom = False                                                 # Is family customised and saved by user ? Used for zville.family_fight()
    incubation_time = 12                                                  # x5 seconds (one round) Virus latency, how long until infected turns zombie
    current_pop     = village_pop - initial_wave                          # amount of population now, integer
    current_zombies = initial_wave                                        # amount of zombies now, integer

    # various speed parameters

    zed_speed_kmh   = 3.22                                              # set zeds speed here
    # how hard for zombies is to move forward (home fortifications), used to calculate average zeds speed
    obstacle_level  = 4
    # how many seconds are in one round
    round_sec = 5
    # zville.speed_round() - average zombie speed per round
    zed_speed       = zville.speed_round(zed_speed_kmh, obstacle_level, round_sec)
    # last amount of zombies per cell attacked, also used for human win condition check (turns to string and ends game)
    family_cache = 0
    # rounds counter
    round_count     = 1
    timer = [0, 0]  # minutes, seconds
    # how much rounds it take to move the swarm to next fight ?
    countdown_set = int(25 / zed_speed) + 1  # 25 meters distance / zed speed decreased + 1 because we round up to prevent exception if 0

    # CHOOSING RANDOM TILES FOR PATIENT ZERO AND FAMILY HOUSE LOCATION
    logging.info('# CHOOSING RANDOM TILES FOR PATIENT ZERO AND FAMILY HOUSE LOCATION')
    # first infected cell
    temp_x = random.randint(0, len(grid_data)-1)
    grid_data[temp_x][random.randint(0, len(grid_data[temp_x])-1)] = '#'

    # family-cell location
    while True:  # loop asserts family-cell not infected
        int_y = random.randint(0, len(grid_data) - 1)  # (y coord),(eg 5) represent list number in grid data
        int_x = random.randint(0, len(grid_data[0]) - 1)  # x coord eg 0 represent value number in random list inside grid_data
        family_coord = (int_y, int_x)  # tuple (y, x) eg (5, 0) < - list 5 value 0 of grid_data, is checked after fight and when is '#' triggers family_fight()

        if grid_data[int_y][int_x] != '#':  # checking if chosen coord doesn't contain infected tile
            grid_data[int_y][int_x] = '~'  # turning grid representation to family icon if not infected tile
            break

    # INITIAL DATA PRINTED OUT
    logging.info('# INITIAL DATA PRINTED OUT')
    print('=' * 79)
    print('zombie speed =', zed_speed_kmh, 'kmh')
    print('population of humans = {current_pop}'.format(current_pop=current_pop))
    print('virus incubation time = 60 seconds')
    print_s = ' {village_name}  {village_pop} villagers '\
        .format(village_name=village_name.upper(), village_pop=village_pop)
    print(print_s.center(79, '='))  # Prints village name and population above grid

    # draws grid for first time with one infected cell and with family location in bright cell
    zville.draw_grid_data(grid_data)
    print('=' * 79)

    print('Zombies head toward first house. Victims are unsuspecting...\n')

    # first game help message
    if main_choice == 0:
        print('=' * 79)
        print('On village map grid there is one most bright cell. When infection zone reaches\n'
              'that spot, family fight is triggered. One family is chosen as example, to\n'
              'represent simulation in more personal and detailed way.')
        print('=' * 79)

    # time delay after printing grid for the first time
    if sim_speed in [1, 2]:
        zville.press_enter(text='PRESS ENTER TO START APOCALYPSE')
    else:
        time.sleep(0)

    # MAIN LOOP FOR VIRUS SPREAD, iteration 5 sec
    logging.info('# MAIN LOOP FOR VIRUS SPREAD')
    while True:
        # ZOMBIES vs VILLAGERS FIGHT INSTANCE
        if countdown_set == 0:
            print('=' * 79)
            print('defences shattered, {current_zombies} zombies attack !'
                  .format(current_zombies=current_zombies))
            print('=' * 79)

            # zville.fight() and its arguments
            grid_data, current_zombies, current_pop, pulped_body, round_count, family_cache = \
                zville.fight(grid_data, current_zombies, current_pop, pulped_body, round_count,
                             sim_speed)
            if family_cache == 'humans_won':  # check for end game condition
                break

            # after each fight draw new grid data
            zville.draw_grid_data(grid_data)

            # FAMILY FIGHT SECTION zville.family_fight()
            logging.debug(' # FAMILY FIGHT SECTION')
            # checks if family alive and if family tile in infected cell, if yes triggers zville.family_fight()
            if family_custom != 'dead' and grid_data[family_coord[0]][family_coord[1]] == '#':

                # FAMILY_FIGHT() FUNCTION RUNS HERE !
                family_custom, familyChar, familyStats, zombiesPulped = \
                    zville.family_fight(family_cache, familyChar, familyStats, sim_speed,
                                        current_pop, current_zombies)

                current_zombies -= zombiesPulped  # reducing amount of zombies by those pulped by family
                pulped_body += zombiesPulped
                if current_zombies < 1:
                    print('Family involvement helped to stop the apocalypse. Humans won !')
                    break
            # FAMILY FIGHT SECTION ENDS

            # how much rounds it take to move the swarm to next fight ?
            countdown_set = int(25 / zed_speed) + 1  # 25 meters to go / zombie game speed + 1 because we round up to prevent exception if 0

            print('\npopulation of humans  = {current_pop}'.format(current_pop=current_pop))
            print('population of zombies = {current_zombies}     pulped bodies = {pulped_body}\n'
                  .format(current_zombies=current_zombies, pulped_body=pulped_body))

            # time delay at end of fight
            if sim_speed == 1:
                zville.press_enter()
            time.sleep(4) if sim_speed == 2 else time.sleep(0)

        # VARIOUS
        logging.debug('# VARIOUS')
        countdown_set -= 1

        if current_pop < 1:  # check for new game condition
            zombies_win = True
            break

        # iteration + 1 counter update
        round_count += 1

        # updating time 5 seconds per iteration, translating 60s to 1m,
        timer[1] += 5  # seconds
        if timer[1] == 60:
            timer[1] = 0
            timer[0] += 1  # minutes

        # time delay for zombies moving
        time.sleep(0.05) if sim_speed in [1, 2] else time.sleep(0)
        print('{timer_min}:{timer_sec} min passed'.format(timer_min=timer[0], timer_sec=timer[1]))

    if zombies_win:
        print('The village has been wiped out in {timer_m}:{timer_s} min'.format(timer_m=timer[0],
                                                                                timer_s=timer[1]))
        print('Zombies are crawling among smoldering ruins of {village_name}.'
              .format(village_name=village_name))
        print('Pulped corpses left for eating = {pulped_body}.'.format(pulped_body=pulped_body))

    # PLAY AGAIN ?
    logging.info('# PLAY AGAIN ?')
    answer = zville.yes_or_no('Do you want to run simulation again ?')

    if answer == 'no' and current_zombies > 1:  # no, zombies won
        print(' ZOMBIES '.center(80, '+'), '\n')
        time.sleep(0.5)
        print(' M O A N '.center(80, '@'))
        break
    elif answer == 'no':  # no, humans won
        break

    else:  # user choose new game, intro game speed will change to default
        if sim_speed == 1:
            sim_speed = 2


#!/usr/bin/env python3

from pymongo import MongoClient
from urllib.parse import urlparse

class Mongodb_Unauthorized_BaseVerify:
    def __init__(self, url):
        self.info = {
            'name': 'Mongodb 未授权访问漏洞',
            'description': 'Mongodb 未授权访问漏洞',
            'date': '',
            'exptype': 'check',
            'type': 'Unauthorized'
        }
        self.url = url
        url_parse = urlparse(self.url)
        self.host = url_parse.hostname
        self.port = url_parse.port
        if not self.port:
            self.port = '27017'

    def check(self):

        """
        检测是否存在漏洞

        :param:

        :return bool True or False: 是否存在漏洞
        """

        try:
            conn = MongoClient(self.host, int(self.port), socketTimeoutMS = 5000)
            dbname = conn.database_names()
            print('存在MongoDB存在未授权访问')
            return True
        except Exception as e:
            print('不存在MongoDB存在未授权访问')
            return False
        finally:
            conn.close()
            pass

if  __name__ == "__main__":
    Mongodb_Unauthorized = Mongodb_Unauthorized_BaseVerify('http://10.4.33.52:50000')
    Mongodb_Unauthorized.check()


import matplotlib.pyplot as plt
from Heap import hs as hs
import Geometry
from GrahamScan import grscan as gr

if __name__ == '__main__':
    file = open("Points.txt", "r")  # Opening txt file
    pnt_list = []
    for i, row in enumerate(file):  # Creating list of Point2D for txt file
        if i == 0:
            continue
        a, b, c = row.split()
        pnt_list.append(Geometry.Point2D(int(a), int(b), int(c)))

    convex = gr(pnt_list)  # Receiving convex hull list of Point2D
    x_cnvx = []
    y_cnvx = []
    x_pnt = []
    y_pnt = []
    for point in convex:  # Separating convex list to x,y lists
        x_cnvx.append(point.x)
        y_cnvx.append(point.y)
    for point in pnt_list:  # Separating original input list to x,y lists
        x_pnt.append(point.x)
        y_pnt.append(point.y)

    plt.fill(x_cnvx, y_cnvx, edgecolor='b', fill=False)  # Plotting Convex hull
    plt.plot(x_pnt, y_pnt, 'ro')  # Plotting original points
    plt.axis(
        [min(x_cnvx) - abs(max(x_cnvx)) // 10, max(x_cnvx) + max(x_cnvx) // 10, min(y_cnvx) - abs(max(y_cnvx)) // 10,
         max(y_cnvx) + max(y_cnvx) // 10])  # Increasing plot gaps by 10%
    plt.show()


import sys

if sys.version_info.major >= 3:
	import configparser as ConfigParser
else:
	import ConfigParser
import json

####################### DEFAULT CONFIGURATION ############################

thresholds = [40, 48, 56, 60]
#             ^   ^   ^   ^
#             0   1   2   3

# Set interval of updates
delay = 10 #Seconds

bus = '1'
device = '0x6c'
address = '1'
speeds = ['0', '2', '3', '4', '1']
speedLabels = ['OFF', '25 percent', '50 percent', '75 percent', 'Full speed']
margin = 2

##########################################################################

class ConfigException(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return str(self.value)

def getConfig(configPath):
	dirty = False
	Config = ConfigParser.ConfigParser()
	try:
		Config.read(configPath)
	except ConfigParser.InterpolationError:
		raise ConfigException('Invalid config file')

	configHash = {}

	# Global
	if not 'Global' in Config.sections():
		"""
		Config['Global'] = {
				'thresholds': json.dumps(thresholds),
				'delay': delay
		}
		"""
		Config.add_section('Global')
		dirty = True

	try:
		configHash['thresholds'] = json.loads(Config.get('Global', 'thresholds'))
	except ConfigParser.NoOptionError:
		configHash['thresholds'] = thresholds
		#Config['Global']['thresholds'] = json.dumps(thresholds)
		Config.set('Global', 'thresholds', json.dumps(thresholds))
		dirty = True
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	try:
		configHash['delay'] = int(Config.get('Global', 'delay'))
	except ConfigParser.NoOptionError:
		configHash['delay'] = delay
		#Config['Global']['delay'] = delay
		Config.set('Global', 'delay', str(delay))
		dirty = True
	except ValueError:
		raise ConfigException('Invalid value for "delay" option')
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	# Advanced
	if not 'Advanced' in Config.sections():
		"""
		Config['Advanced'] = {
				'bus': bus,
				'device': device,
				'address': address,
				'speeds': json.dumps(speeds),
				'speedLabels': json.dumps(speedLabels),
				'margin': margin
		}
		"""
		Config.add_section('Advanced')
		dirty = True

	try:
		configHash['bus'] = Config.get('Advanced', 'bus')
	except ConfigParser.NoOptionError:
		configHash['bus'] = bus
		#Config['Advanced']['bus'] = bus
		Config.set('Advanced', 'bus', bus)
		dirty = True
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	try:
		configHash['device'] = Config.get('Advanced', 'device')
	except ConfigParser.NoOptionError:
		configHash['device'] = device
		#Config['Advanced']['device'] = device
		Config.set('Advanced', 'device', device)
		dirty = True
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	try:
		configHash['address'] = Config.get('Advanced', 'address')
	except ConfigParser.NoOptionError:
		configHash['address'] = address
		#Config['Advanced']['address'] = address
		Config.set('Advanced', 'address', address)
		dirty = True
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	try:
		configHash['speeds'] = json.loads(Config.get('Advanced', 'speeds'))
	except ConfigParser.NoOptionError:
		configHash['speeds'] = speeds
		#Config['Advanced']['speeds'] = json.dumps(speeds)
		Config.set('Advanced', 'speeds', json.dumps(speeds))
		dirty = True
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	try:
		configHash['speedLabels'] = json.loads(Config.get('Advanced', 'speedLabels'))
	except ConfigParser.NoOptionError:
		configHash['speedLabels'] = speedLabels
		#Config['Advanced']['speedLabels'] = json.dumps(speedLabels)
		Config.set('Advanced', 'speedLabels', json.dumps(speedLabels))
		dirty = True
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	try:
		configHash['margin'] = int(Config.get('Advanced', 'margin'))
	except ConfigParser.NoOptionError:
		configHash['margin'] = margin
		#Config['Advanced']['margin'] = margin
		Config.set('Advanced', 'margin', str(margin))
		dirty = True
	except ValueError:
		raise ConfigException('Invalid value for "margin" option')
	except ConfigParser.InterpolationError as e:
		raise ConfigException('Error in config file. Message was:\n    ' + str(e))

	if dirty:
		with open(configPath, 'w+') as configfile:
			Config.write(configfile)

	return configHash


import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
import joblib

# Load data (assuming it's loaded from a CSV file in this example)
heart_data = pd.read_csv("E:\Health Web App\dataset\heart.csv")

# Split data into features and target
X = heart_data.drop(columns="target", axis=1)
Y = heart_data["target"]

# Split data into training and testing sets
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2, stratify=Y, random_state=2)

# Train the Logistic Regression Model
model = LogisticRegression(max_iter=10000)
model.fit(X_train, Y_train)

# Calculate accuracy on training data
X_train_prediction = model.predict(X_train)
training_data_accuracy = accuracy_score(X_train_prediction, Y_train)
print("Training Accuracy:", training_data_accuracy)

# Calculate and print accuracy on testing data
X_test_prediction = model.predict(X_test)
testing_data_accuracy = accuracy_score(X_test_prediction, Y_test)
print("Testing Accuracy:", testing_data_accuracy)

# Save the model
joblib.dump(model, "model_heart.pkl")


from telegram import end_active_telegram_client, init_telegram_client
from sys import exit
from logger import log_error
from telegram_functions import telegram_functions
from telegram_utils import add_api_json, display_apis, read_api_json, retrieve_api, select_api
from utils import clear, colored, get_menu_choice

main_menu = [
    "View APIs",
    "Add APIs",
    "Login"
]

clear()
print(colored(66, 215, 245, '\n\x1B[3mThe Awesome Telegram Scrapper\x1B[0m\n'))
print(colored(245, 56, 56, 'Status : Not logged in'))

while (True):
    main_choice = get_menu_choice(main_menu, 'Main menu')

    if(main_choice == 0):
        exit()
    elif(main_choice == 1):
        apis = read_api_json('../apis/api.json')
        display_apis(apis)
    elif(main_choice == 2):
        add_api_json()
    elif(main_choice == 3):
        apis = read_api_json('../apis/api.json')
        api_dict = select_api(apis)

        if(api_dict.get('status')):
            api = api_dict.get('api')
            client = init_telegram_client(
                api.get('phone'), api.get('api_id'), api.get('api_hash'))
            telegram_functions(client)
        else:
            print(colored(245, 56, 56, 'No APIs added!'))


from __future__ import annotations

import logging
import time
from collections import Counter
from copy import deepcopy
from dataclasses import dataclass, field
from math import isclose
from typing import List, Optional

import numpy as np

from cctbx import uctbx
from iotbx import mtz
from libtbx import env
from rstbx.cftbx.coordinate_frame_helpers import align_reference_frame
from scitbx import matrix
from scitbx.math import r3_rotation_axis_and_angle_from_matrix

import dials.util.ext
from dials.algorithms.scaling.scaling_library import (
    MergedHalfDatasets,
    determine_best_unit_cell,
)
from dials.array_family import flex
from dials.util.batch_handling import (
    assign_batches_to_reflections,
    calculate_batch_offsets,
    get_image_ranges,
)
from dials.util.filter_reflections import filter_reflection_table
from dials.util.multi_dataset_handling import (
    assign_unique_identifiers,
    parse_multiple_datasets,
)
from dials.util.version import dials_version

logger = logging.getLogger(__name__)

class MTZWriterBase:
    """Helper for adding metadata, crystals and datasets to an mtz file object."""

    def __init__(self, space_group, unit_cell=None):
        """If a unit cell is provided, will be used as default unless specified
        for each crystal."""
        mtz_file = mtz.object()
        mtz_file.set_title(f"From {env.dispatcher_name}")
        date_str = time.strftime("%Y-%m-%d at %H:%M:%S %Z")
        if time.strftime("%Z") != "GMT":
            date_str += time.strftime("  (%Y-%m-%d at %H:%M:%S %Z)", time.gmtime())
        mtz_file.add_history(f"From {dials_version()}, run on {date_str}")
        mtz_file.set_space_group_info(space_group.info())
        self.mtz_file = mtz_file
        if unit_cell:
            self.unit_cell = unit_cell
        self.current_crystal = None
        self.current_dataset = None
        self.n_crystals = 0
        self.n_datasets = 0

    def add_crystal(self, crystal_name=None, project_name=None, unit_cell=None):
        """Add a crystal to the mtz file object."""
        if not unit_cell:
            if not self.unit_cell:
                raise ValueError("Unit cell must be provided.")
            else:
                unit_cell = self.unit_cell
        if not crystal_name:
            crystal_name = f"crystal_{self.n_crystals + 1}"
        if not project_name:
            project_name = "DIALS"
        self.current_crystal = self.mtz_file.add_crystal(
            crystal_name, project_name, unit_cell.parameters()
        )
        self.n_crystals += 1

    def add_empty_dataset(self, wavelength, name=None):
        """Add an empty dataset object to the mtz file."""
        if not name:
            name = "FROMDIALS"
        self.current_dataset = self.current_crystal.add_dataset(name, wavelength)
        self.n_datasets += 1

class MergedMTZWriter(MTZWriterBase):
    """Mtz writer for merged data."""

    def add_dataset(
        self,
        merged_array=None,
        anom_array=None,
        amplitudes=None,
        anom_amplitudes=None,
        dano=None,
        multiplicities=None,
        anom_multiplicities=None,
        suffix=None,
        half_datasets: Optional[MergedHalfDatasets] = None,
        r_free_array=None,
    ):
        """Add merged data to the most recent dataset.

        Args:
            merged_array: A merged miller array of IMEAN intensities
            wavelength: The wavelength of the dataset
            anom_array (Optional): An anomalous merged miller array
            amplitudes (Optional): A merged miller array of amplitudes
            anom_amplitudes (Optional): An anomalous merged array of amplitudes
            suffix (Optional[str]): Column name suffix to use for this dataset.
        """
        if not suffix:
            suffix = ""
        if merged_array:
            self.current_dataset.add_miller_array(merged_array, "IMEAN" + suffix)
        if multiplicities:
            self.current_dataset.add_miller_array(multiplicities, "N" + suffix)
        if amplitudes:
            self.current_dataset.add_miller_array(amplitudes, "F" + suffix)
        if anom_array:
            self.current_dataset.add_miller_array(anom_array, "I" + suffix)
        if anom_multiplicities:
            self.current_dataset.add_miller_array(anom_multiplicities, "N" + suffix)
        if anom_amplitudes:
            self.current_dataset.add_miller_array(anom_amplitudes, "F" + suffix)
        if dano:
            self.current_dataset.add_miller_array(
                dano, "DANO" + suffix, column_types="DQ"
            )
        if half_datasets:
            self.current_dataset.add_miller_array(
                half_datasets.data1, "IHALF1" + suffix, column_types="JQ"
            )
            self.current_dataset.add_miller_array(
                half_datasets.data2, "IHALF2" + suffix, column_types="JQ"
            )
            self.current_dataset.add_miller_array(
                half_datasets.multiplicity1,
                "NHALF1" + suffix,
            )
            self.current_dataset.add_miller_array(
                half_datasets.multiplicity2,
                "NHALF2" + suffix,
            )
        if r_free_array:
            self.current_dataset.add_miller_array(
                r_free_array, column_root_label="FreeR_flag", column_types="I"
            )

class MADMergedMTZWriter(MergedMTZWriter):
    """Mtz writer for multi-wavelength merged data."""

    def add_dataset(
        self,
        merged_array=None,
        anom_array=None,
        amplitudes=None,
        anom_amplitudes=None,
        dano=None,
        multiplicities=None,
        anom_multiplicities=None,
        suffix=None,
        half_datasets: Optional[MergedHalfDatasets] = None,
        r_free_array=None,
    ):
        if not suffix:
            suffix = f"_WAVE{self.n_datasets}"
        super().add_dataset(
            merged_array,
            anom_array,
            amplitudes,
            anom_amplitudes,
            dano,
            multiplicities,
            anom_multiplicities,
            suffix,
            half_datasets,
            r_free_array=r_free_array,
        )

class UnmergedMTZWriter(MTZWriterBase):
    def add_batch_list(
        self,
        image_range,
        experiment,
        wavelength,
        dataset_id,
        batch_offset,
        force_static_model,
    ):
        """Add batch metadata to the mtz file."""

        # Recalculate useful numbers and references here
        n_batches = image_range[1] - image_range[0] + 1
        phi_start = flex.float(n_batches, 0)
        phi_range = flex.float(n_batches, 0)
        umat_array = flex.float(flex.grid(n_batches, 9))
        cell_array = flex.float(flex.grid(n_batches, 6))

        # Reciprocal lattice vectors in the lab frame at zero scan angle
        if experiment.goniometer:
            S = matrix.sqr(experiment.goniometer.get_setting_rotation())
            F = matrix.sqr(experiment.goniometer.get_fixed_rotation())
            UBlab = S * F * matrix.sqr(experiment.crystal.get_A())

            axis = matrix.col(experiment.goniometer.get_rotation_axis())
            axis_datum = matrix.col(experiment.goniometer.get_rotation_axis_datum())

        else:
            UBlab = matrix.sqr(experiment.crystal.get_A())

        i0 = image_range[0]
        for i in range(n_batches):
            if experiment.scan:
                phi_start[i], phi_range[i] = experiment.scan.get_image_oscillation(
                    i + i0
                )

            # Unit cell and UB matrix for the centre of the image for scan-varying model
            if (
                not force_static_model
                and experiment.crystal.num_scan_points > 0
                and experiment.goniometer
            ):

                # Get the index of the image in the sequence e.g. first => 0, second => 1
                image_index = i + i0 - experiment.scan.get_image_range()[0]

                # Find the U matrix at the frame centre by calculating the linear transform
                # that goes from the start of the frame to the end, and then applying half of
                # that to the start value
                U0 = matrix.sqr(experiment.crystal.get_U_at_scan_point(image_index))
                U1 = matrix.sqr(experiment.crystal.get_U_at_scan_point(image_index + 1))
                M = U1 * U0.inverse()
                (
                    angle_M,
                    axis_M,
                ) = M.r3_rotation_matrix_as_unit_quaternion().unit_quaternion_as_axis_and_angle(
                    deg=False
                )
                M_half = axis_M.axis_and_angle_as_r3_rotation_matrix(
                    angle_M / 2, deg=False
                )
                Ucentre = M_half * U0

                # Find the B matrix at the frame centre by interpolation
                B0 = matrix.sqr(experiment.crystal.get_B_at_scan_point(image_index))
                B1 = matrix.sqr(experiment.crystal.get_B_at_scan_point(image_index + 1))
                Bcentre = (B0 + B1) / 2

                # Unit cell at the frame centre
                unit_cell = uctbx.unit_cell(
                    orthogonalization_matrix=Bcentre.transpose().inverse()
                )

                # Get full lab frame UB then unwind to zero scan angle
                phi_centre = phi_start[i] + phi_range[i] / 2
                R = matrix.sqr(
                    axis_datum.axis_and_angle_as_r3_rotation_matrix(
                        phi_centre, deg=False
                    )
                )
                Rlab_inv = matrix.sqr(
                    axis.axis_and_angle_as_r3_rotation_matrix(-phi_centre, deg=False)
                )
                _UBlab = Rlab_inv * S * R * F * Ucentre * Bcentre

            else:
                unit_cell = experiment.crystal.get_unit_cell()
                _UBlab = UBlab

            # We assume a single-axis goniometer as it is not clear that multi-
            # axis goniometry was ever fully supported in MTZ format. Orientation
            # will be taken from the laboratory frame for this image.
            U = matrix.sqr(dials.util.ext.ub_to_mosflm_u(_UBlab, unit_cell))

            # FIXME need to get what was refined and what was constrained from the
            # crystal model - see https://github.com/dials/dials/issues/355
            _unit_cell_params = unit_cell.parameters()
            for j in range(6):
                cell_array[i, j] = _unit_cell_params[j]
            # Transpose to put in column-major order for MTZ export
            U_t_elements = U.transpose().elems
            for j in range(9):
                umat_array[i, j] = U_t_elements[j]

        # We ignore panels beyond the first one, at the moment
        panel = experiment.detector[0]
        panel_size = panel.get_image_size()
        panel_distance = panel.get_directed_distance()

        if experiment.goniometer:
            axis = flex.float(experiment.goniometer.get_rotation_axis())
        else:
            axis = flex.float((0.0, 0.0, 0.0))

        source = flex.float(experiment.beam.get_sample_to_source_direction())

        # get the mosaic spread though today it may not actually be set - should
        # this be in the BATCH headers?
        try:
            mosaic = experiment.crystal.get_mosaicity()
        except AttributeError:
            mosaic = 0.0

        # Jump into C++ to do the rest of the work
        dials.util.ext.add_dials_batches(
            self.mtz_file,
            dataset_id,
            image_range,
            batch_offset,
            wavelength,
            mosaic,
            phi_start,
            phi_range,
            cell_array,
            umat_array,
            panel_size,
            panel_distance,
            axis,
            source,
        )

    def write_columns(self, reflection_table):
        """Write the column definitions AND data to the current dataset."""

        # now create the actual data structures - first keep a track of the columns

        # H K L M/ISYM BATCH I SIGI IPR SIGIPR FRACTIONCALC XDET YDET ROT WIDTH
        # LP MPART FLAG BGPKRATIOS

        # gather the required information for the reflection file

        nref = len(reflection_table["miller_index"])
        assert nref
        xdet, ydet, _ = [
            flex.double(x) for x in reflection_table["xyzobs.px.value"].parts()
        ]

        # now add column information...

        # FIXME add DIALS_FLAG which can include e.g. was partial etc.

        type_table = {
            "H": "H",
            "K": "H",
            "L": "H",
            "I": "J",
            "SIGI": "Q",
            "IPR": "J",
            "SIGIPR": "Q",
            "BG": "R",
            "SIGBG": "R",
            "XDET": "R",
            "YDET": "R",
            "BATCH": "B",
            "BGPKRATIOS": "R",
            "WIDTH": "R",
            "MPART": "I",
            "M_ISYM": "Y",
            "FLAG": "I",
            "LP": "R",
            "FRACTIONCALC": "R",
            "ROT": "R",
            "QE": "R",
        }

        # derive index columns from original indices with
        #
        # from m.replace_original_index_miller_indices
        #
        # so all that is needed now is to make space for the reflections - fill with
        # zeros...

        self.mtz_file.adjust_column_array_sizes(nref)
        self.mtz_file.set_n_reflections(nref)
        dataset = self.current_dataset

        # assign H, K, L, M_ISYM space
        for column in "H", "K", "L", "M_ISYM":
            dataset.add_column(column, type_table[column]).set_values(
                flex.double(nref, 0.0).as_float()
            )

        self.mtz_file.replace_original_index_miller_indices(
            reflection_table["miller_index"]
        )

        dataset.add_column("BATCH", type_table["BATCH"]).set_values(
            reflection_table["batch"].as_double().as_float()
        )

        # if intensity values used in scaling exist, then just export these as I, SIGI
        if "intensity.scale.value" in reflection_table:
            I_scaling = reflection_table["intensity.scale.value"]
            V_scaling = reflection_table["intensity.scale.variance"]
            # Trap negative variances
            assert V_scaling.all_gt(0)
            dataset.add_column("I", type_table["I"]).set_values(I_scaling.as_float())
            dataset.add_column("SIGI", type_table["SIGI"]).set_values(
                flex.sqrt(V_scaling).as_float()
            )
            dataset.add_column("SCALEUSED", "R").set_values(
                reflection_table["inverse_scale_factor"].as_float()
            )
            dataset.add_column("SIGSCALEUSED", "R").set_values(
                flex.sqrt(reflection_table["inverse_scale_factor_variance"]).as_float()
            )
        else:
            if "intensity.prf.value" in reflection_table:
                if "intensity.sum.value" in reflection_table:
                    col_names = ("IPR", "SIGIPR")
                else:
                    col_names = ("I", "SIGI")
                I_profile = reflection_table["intensity.prf.value"]
                V_profile = reflection_table["intensity.prf.variance"]
                # Trap negative variances
                assert V_profile.all_gt(0)
                dataset.add_column(col_names[0], type_table["I"]).set_values(
                    I_profile.as_float()
                )
                dataset.add_column(col_names[1], type_table["SIGI"]).set_values(
                    flex.sqrt(V_profile).as_float()
                )
            if "intensity.sum.value" in reflection_table:
                I_sum = reflection_table["intensity.sum.value"]
                V_sum = reflection_table["intensity.sum.variance"]
                # Trap negative variances
                assert V_sum.all_gt(0)
                dataset.add_column("I", type_table["I"]).set_values(I_sum.as_float())
                dataset.add_column("SIGI", type_table["SIGI"]).set_values(
                    flex.sqrt(V_sum).as_float()
                )
        if (
            "background.sum.value" in reflection_table
            and "background.sum.variance" in reflection_table
        ):
            bg = reflection_table["background.sum.value"]
            varbg = reflection_table["background.sum.variance"]
            assert (varbg >= 0).count(False) == 0
            sigbg = flex.sqrt(varbg)
            dataset.add_column("BG", type_table["BG"]).set_values(bg.as_float())
            dataset.add_column("SIGBG", type_table["SIGBG"]).set_values(
                sigbg.as_float()
            )

        dataset.add_column("FRACTIONCALC", type_table["FRACTIONCALC"]).set_values(
            reflection_table["fractioncalc"].as_float()
        )

        dataset.add_column("XDET", type_table["XDET"]).set_values(xdet.as_float())
        dataset.add_column("YDET", type_table["YDET"]).set_values(ydet.as_float())
        dataset.add_column("ROT", type_table["ROT"]).set_values(
            reflection_table["ROT"].as_float()
        )
        if "lp" in reflection_table:
            dataset.add_column("LP", type_table["LP"]).set_values(
                reflection_table["lp"].as_float()
            )
        if "qe" in reflection_table:
            dataset.add_column("QE", type_table["QE"]).set_values(
                reflection_table["qe"].as_float()
            )
        elif "dqe" in reflection_table:
            dataset.add_column("QE", type_table["QE"]).set_values(
                reflection_table["dqe"].as_float()
            )
        else:
            dataset.add_column("QE", type_table["QE"]).set_values(
                flex.double(nref, 1.0).as_float()
            )

def export_mtz(
    reflection_table,
    experiment_list,
    intensity_choice,
    filename,
    best_unit_cell=None,
    partiality_threshold=0.4,
    combine_partials=True,
    min_isigi=-5,
    filter_ice_rings=False,
    d_min=None,
    force_static_model=False,
    crystal_name=None,
    project_name=None,
    wavelength_tolerance=1e-4,
):
    """Export data from reflection_table corresponding to experiment_list to an
    MTZ file hklout."""

    # First get the experiment identifier information out of the data
    expids_in_table = reflection_table.experiment_identifiers()
    if not list(expids_in_table.keys()):
        reflection_tables = parse_multiple_datasets([reflection_table])
        experiment_list, refl_list = assign_unique_identifiers(
            experiment_list, reflection_tables
        )
        reflection_table = flex.reflection_table()
        for reflections in refl_list:
            reflection_table.extend(reflections)
        expids_in_table = reflection_table.experiment_identifiers()
    reflection_table.assert_experiment_identifiers_are_consistent(experiment_list)
    expids_in_list = list(experiment_list.identifiers())

    # Convert geometry to the Cambridge frame
    experiment_list = convert_to_cambridge(experiment_list)

    # Convert experiment_list to a real python list or else identity assumptions
    # fail like:
    #   assert experiment_list[0] is experiment_list[0]
    # And assumptions about added attributes break
    experiment_list = list(experiment_list)

    # Validate multi-experiment assumptions
    if len(experiment_list) > 1:
        # All experiments should match crystals, or else we need multiple crystals/datasets
        if not all(
            x.crystal == experiment_list[0].crystal for x in experiment_list[1:]
        ):
            logger.warning(
                "Experiment crystals differ. Using first experiment crystal for file-level data."
            )

        # At least, all experiments must have the same space group
        if len({x.crystal.get_space_group().make_tidy() for x in experiment_list}) != 1:
            raise ValueError("Experiments do not have a unique space group")

    wavelengths = match_wavelengths(experiment_list, wavelength_tolerance)
    for w in wavelengths.values():
        w.calculate_weighted_mean([reflection_table])

    if len(wavelengths) > 1:
        identifiers_list = [e.identifier for e in experiment_list]
        logger.info(
            "Multiple wavelengths found: \n%s",
            "\n".join(
                "  Wavlength: %.5f, experiment numbers: %s "
                % (
                    v.weighted_mean,
                    ",".join(
                        map(str, [identifiers_list.index(i) for i in v.identifiers])
                    ),
                )
                for v in wavelengths.values()
            ),
        )

    # also only work correctly with one panel (for the moment)
    if any(len(experiment.detector) != 1 for experiment in experiment_list):
        logger.warning("Ignoring multiple panels in output MTZ")

    if best_unit_cell is None:
        best_unit_cell = determine_best_unit_cell(experiment_list)
    reflection_table["d"] = best_unit_cell.d(reflection_table["miller_index"])

    # Clean up the data with the passed in options
    reflection_table = filter_reflection_table(
        reflection_table,
        intensity_choice=intensity_choice,
        partiality_threshold=partiality_threshold,
        combine_partials=combine_partials,
        min_isigi=min_isigi,
        filter_ice_rings=filter_ice_rings,
        d_min=d_min,
    )

    # get batch offsets and image ranges - even for scanless experiments
    batch_offsets = [
        expt.scan.get_batch_offset()
        for expt in experiment_list
        if expt.scan is not None
    ]
    unique_offsets = set(batch_offsets)
    if len(set(unique_offsets)) <= 1:
        logger.debug("Calculating new batches")
        batch_offsets = calculate_batch_offsets(experiment_list)
        batch_starts = [
            e.scan.get_image_range()[0] if e.scan else 0 for e in experiment_list
        ]
        effective_offsets = [o + s for o, s in zip(batch_offsets, batch_starts)]
        unique_offsets = set(effective_offsets)
    else:
        logger.debug("Keeping existing batches")
    image_ranges = get_image_ranges(experiment_list)
    if len(unique_offsets) != len(batch_offsets):

        raise ValueError(
            "Duplicate batch offsets detected: %s"
            % ", ".join(
                str(item) for item, count in Counter(batch_offsets).items() if count > 1
            )
        )

    # Create the mtz file
    mtz_writer = UnmergedMTZWriter(experiment_list[0].crystal.get_space_group())

    # FIXME TODO for more than one experiment into an MTZ file:
    #
    # - add an epoch (or recover an epoch) from the scan and add this as an extra
    #   column to the MTZ file for scaling, so we know that the two lattices were
    #   integrated at the same time
    # ✓ decide a sensible BATCH increment to apply to the BATCH value between
    #   experiments and add this

    for id_ in expids_in_table.keys():
        # Grab our subset of the data
        loc = expids_in_list.index(
            expids_in_table[id_]
        )  # get strid and use to find loc in list
        experiment = experiment_list[loc]
        identifier = experiment.identifier

        if len(wavelengths) > 1:
            for i, wl in enumerate(wavelengths.values()):
                if identifier in wl.identifiers:
                    wavelength = wl.weighted_mean
                    dataset_id = i + 1
                    break
        else:
            wavelength = list(wavelengths.values())[0].weighted_mean
            dataset_id = 1

        reflections = reflection_table.select(reflection_table["id"] == id_)
        batch_offset = batch_offsets[loc]
        image_range = image_ranges[loc]
        reflections = assign_batches_to_reflections([reflections], [batch_offset])[0]
        experiment.data = dict(reflections)

        s0n = matrix.col(experiment.beam.get_s0()).normalize().elems
        logger.debug("Beam vector: %.4f %.4f %.4f" % s0n)

        mtz_writer.add_batch_list(
            image_range,
            experiment,
            wavelength,
            dataset_id,
            batch_offset=batch_offset,
            force_static_model=force_static_model,
        )

        # Create the batch offset array. This gives us an experiment (id)-dependent
        # batch offset to calculate the correct batch from image number.
        experiment.data["batch_offset"] = flex.int(
            len(experiment.data["id"]), batch_offset
        )

        # Calculate whether we have a ROT value for this experiment, and set the column
        _, _, z = experiment.data["xyzcal.px"].parts()
        if experiment.scan:
            experiment.data["ROT"] = experiment.scan.get_angle_from_array_index(z)
        else:
            experiment.data["ROT"] = z

    mtz_writer.add_crystal(
        crystal_name=crystal_name,
        project_name=project_name,
        unit_cell=best_unit_cell,
    )
    # Note: add unit cell here as may have changed basis since creating mtz.
    # For multi-wave unmerged mtz, we add an empty dataset for each wavelength,
    # but only write the data into the final dataset (for unmerged the batches
    # link the unmerged data to the individual wavelengths).
    for wavelength in wavelengths.values():
        mtz_writer.add_empty_dataset(wavelength.weighted_mean)

    # Combine all of the experiment data columns before writing
    combined_data = {k: v.deep_copy() for k, v in experiment_list[0].data.items()}
    for experiment in experiment_list[1:]:
        for k, v in experiment.data.items():
            combined_data[k].extend(v)
    # ALL columns must be the same length
    assert len({len(v) for v in combined_data.values()}) == 1, "Column length mismatch"
    assert len(combined_data["id"]) == len(
        reflection_table["id"]
    ), "Lost rows in split/combine"

    # Write all the data and columns to the mtz file
    mtz_writer.write_columns(combined_data)

    logger.info(
        "Saving %s integrated reflections to %s", len(combined_data["id"]), filename
    )
    mtz_file = mtz_writer.mtz_file
    mtz_file.write(filename)

    return mtz_file

@dataclass
class WavelengthGroup:
    min_wl: float
    max_possible_wl: float
    identifiers: list[str] = field(default_factory=list)
    exp_nos: list[int] = field(default_factory=list)
    wavelengths: list[float] = field(default_factory=list)
    weighted_mean: float = 0

    def add_experiment(self, identifier: str, loc_in_list: int, wl: float) -> None:
        self.identifiers.append(identifier)
        self.exp_nos.append(loc_in_list)
        self.wavelengths.append(wl)

    def calculate_weighted_mean(
        self, reflection_tables: List[flex.reflection_table]
    ) -> None:
        n, nw = (0, 0)
        for i, w in zip(self.identifiers, self.wavelengths):
            for table in reflection_tables:
                refls = table.select_on_experiment_identifiers([i])
                n_this = refls.select(
                    refls.get_flags(refls.flags.integrated, all=False)
                ).size()
                if n_this:
                    n += n_this
                    nw += n_this * w
                    break
        if n:
            self.weighted_mean = nw / n

def match_wavelengths(experiments, absolute_tolerance=1e-4):
    wavelengths = {}
    for i, x in enumerate(experiments):
        w = x.beam.get_wavelength()
        matches = [isclose(w, k, abs_tol=absolute_tolerance) for k in wavelengths]
        if not any(matches):
            wavelengths[w] = WavelengthGroup(w, w + absolute_tolerance)
            wavelengths[w].add_experiment(x.identifier, i, w)
        else:
            match_w = list(wavelengths.keys())[matches.index(True)]
            wavelengths[match_w].add_experiment(x.identifier, i, w)
    return wavelengths

def convert_to_cambridge(experiments):
    """Rotate the geometry of an experiment list to match the Cambridge frame,
    in which X is along the idealized X-ray beam and Z is along the primary
    rotation axis"""

    # First handle the potential for shared experiment models - we don't
    # want to apply multiple transformations to shared models, simplest way is
    # to make a copy for each experiment.
    n_expt = len(experiments)
    if len(experiments.crystals()) < n_expt:
        for expt in experiments:
            expt.crystal = deepcopy(expt.crystal)
    if len(experiments.beams()) < n_expt:
        for expt in experiments:
            expt.beam = deepcopy(expt.beam)
    if len(experiments.detectors()) < n_expt:
        for expt in experiments:
            expt.detector = deepcopy(expt.detector)
    if any(experiments.goniometers()) and len(experiments.goniometers()) < n_expt:
        for expt in experiments:
            expt.goniometer = deepcopy(expt.goniometer)

    for expt in experiments:
        if expt.goniometer:
            primary_axis = matrix.col(expt.goniometer.get_rotation_axis_datum())
        else:
            primary_axis = matrix.col((1.0, 0.0, 0.0))
        us0 = expt.beam.get_unit_s0()

        R = align_reference_frame(primary_axis, (0, 0, 1), us0, (1, 0, 0))
        axis_angle = r3_rotation_axis_and_angle_from_matrix(R)
        axis = matrix.col(axis_angle.axis)
        angle = axis_angle.angle()
        logger.debug(
            f"Rotating experiment{'s' if len(experiments) else ''} about axis {axis.elems} by {np.degrees(angle):.2f}°"
        )
        expt.detector.rotate_around_origin(axis, angle, deg=False)
        expt.beam.rotate_around_origin(axis, angle, deg=False)

        # For the goniometer, each component needs transformation
        if expt.goniometer:
            F = matrix.sqr(expt.goniometer.get_fixed_rotation())
            expt.goniometer.set_fixed_rotation(R * F * R.transpose())
            expt.goniometer.set_rotation_axis_datum(R * primary_axis)
            S = matrix.sqr(expt.goniometer.get_setting_rotation())
            expt.goniometer.set_setting_rotation(R * S * R.transpose())

        if expt.crystal is not None:
            expt.crystal = rotate_crystal(expt.crystal, R, axis, angle)

    return experiments

def rotate_crystal(crystal, Rmat, axis, angle):

    Amats = []
    if crystal.num_scan_points > 0:
        scan_pts = list(range(crystal.num_scan_points))
        Amats = [
            Rmat
            * matrix.sqr(crystal.get_U_at_scan_point(t))
            * matrix.sqr(crystal.get_B_at_scan_point(t))
            for t in scan_pts
        ]

    crystal.rotate_around_origin(axis, angle, deg=False)
    if Amats:
        crystal.set_A_at_scan_points(Amats)

    return crystal


from django.conf.urls import url
from django.views.generic.base import RedirectView

from . import views

urlpatterns = [
    url(r'^catalogue_goods$', views.catalogue_goods, name='catalogue_goods'),
    url(r'^catalogue_stored$', views.catalogue_stored, name='catalogue_stored'),
    url(r'^catalogue_rented$', views.catalogue_rented, name='catalogue_rented'),
    url(r'^receive_back_item/(?P<item_pk>[0-9]+)$', views.receive_back_item, name='receive_back_item'),
    url(r'^rent_item/(?P<item_pk>[0-9]+)$', views.rent_item, name='rent_item'),
    url(r'^return_item/(?P<item_pk>[0-9]+)$', views.return_item, name='return_item'),
    url(r'^search_rented$', views.search_items_rented, name='search_rented'),
    url(r'^search_stored$', views.search_items_stored, name='search_stored'),
    url(r'^search_goods$', views.search_items_goods, name='search_goods'),
    url(r'^item_stored/(?P<item_pk>[0-9]+)', views.item_stored_fullinfo, name='item_stored_fullinfo'),
    url(r'^item_rented/(?P<item_pk>[0-9]+)', views.item_rented_fullinfo, name='item_rented_fullinfo'),
    url(r'^item_good/(?P<item_pk>[0-9]+)', views.item_good_fullinfo, name='item_good_fullinfo'),
    url(r'^reset_status', views.reset_status, name='reset_status'),
    url(r'^.*$', RedirectView.as_view(url='catalogue_goods', permanent=False))
]


import matplotlib.pyplot as plt
import numpy as np
from sklearn import datasets,linear_model
from sklearn.metrics import mean_squared_error,r2_score

df=datasets.load_diabetes()
df['feature_names']

x,y=datasets.load_diabetes(return_X_y=True)
x.shape

x=x[:,:1]
x.shape

x_train=x[:-20]
x_test=x[-20:]

y_train=y[:-20]
y_test=y[-20:]

regr=linear_model.LinearRegression()
regr.fit(x_train,y_train)

y_pred=regr.predict(x_test)

print(regr.coef_)
print(mean_squared_error(y_test,y_pred))
print(r2_score(y_test,y_pred))

plt.scatter(x_test,y_test)
plt.plot(x_test,y_pred)

x,y=datasets.load_diabetes(return_X_y=True)
x=x[:,[0,2]]

x_train=x[:-20]
x_test=x[-20:]

y_train=y[:-20]
y_test=y[-20:]

regr=linear_model.LinearRegression()
regr.fit(x_train,y_train)

ypred=regr.predict(x_test)

print(regr.coef_)
print(regr.intercept_)
print(mean_squared_error(y_test,y_pred))
print(r2_score(y_test,y_pred))


from PyQt5.QtWidgets import*
from Tasarım_python import Ui_Form
from PyQt5.QtGui import QIcon
from Yonetici_page import Arayuz_Yonetici
from Ogretmen_page import Arayuz_Ogretmen
from Ogrenci_page import Arayuz_Ogrenci
import sqlite3

class Arayuz(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = Ui_Form()  # classın adını girdik uideki
        self.ui.setupUi(self)  # uideki setup fonksiyonunu çağırdık
        self.conn = sqlite3.connect("ogrenci_staj_projesi.db")
        self.c = self.conn.cursor()
        self.setWindowIcon(QIcon ("login.jpg"))
        self.yonetici_page = Arayuz_Yonetici()
        self.ui.radioButton_Ogrenci.clicked.connect(lambda: self.ogrenciSecildi())
        self.ui.radioButton_Ogretmen.clicked.connect(lambda: self.ogretmenSecildi())
        self.ui.radioButton_Yonetici.clicked.connect(lambda: self.yoneticiSecildi())
        self.ui.pushButton_Giris.clicked.connect(lambda: self.girisYap())# Giriş butonuna tıklandığında
        self.giristuru = -1
        self.ui.label_GirisTuru.close()
        self.ui.label_hata.close()
    # Öğrenci radiobutonunun seçilmesi durumunda çalışır.
    def ogrenciSecildi(self):
        self.ui.label_Id.setText("Öğrenci No : ")
        self.giristuru = 2
        self.ui.lineEdit_Sifre.setPlaceholderText("Şifreniz Tc No'nuzdur.")
        self.ui.label_GirisTuru.close()
    # Öğretmen radiobutonunun seçilmesi durumunda çalışır.
    def ogretmenSecildi(self):
        self.ui.label_Id.setText("Sicil No : ")
        self.giristuru = 1
        self.ui.lineEdit_Sifre.setPlaceholderText("")
        self.ui.label_GirisTuru.close()
    # Yönetici radiobutonunun seçilmesi durumunda çalışır.
    def yoneticiSecildi(self):
        self.ui.label_Id.setText("Kullanıcı Adı : ")
        self.giristuru = 0
        self.ui.lineEdit_Sifre.setPlaceholderText("")
        self.ui.label_GirisTuru.close()
    def girisYap(self): # Giriş türüne göre fonksiyon çağırıyor.
        if self.giristuru == -1:
            self.ui.label_GirisTuru.show()
        if self.giristuru ==0:
            if self.ui.lineEdit_ID.text() == "admin" and self.ui.lineEdit_Sifre.text() == "admin":
                self.open_yonetici_page()
            else:
                self.ui.label_hata.show()
        elif self.giristuru == 1:
            if self.ogretmenLogin(self.ui.lineEdit_ID.text(), self.ui.lineEdit_Sifre.text()) == 1:
                self.ogretmen_page = Arayuz_Ogretmen(self.ogretmenBilgi(self.ui.lineEdit_ID.text()))
                self.open_ogretmen_page()
                self.ui.label_hata.close()
            else:
                self.ui.label_hata.show()
        elif self.giristuru ==2:
            if self.ogrenciLogin(self.ui.lineEdit_ID.text(), self.ui.lineEdit_Sifre.text()) == 1:
                self.ogrenci_page = Arayuz_Ogrenci(self.ogrenciBilgi(self.ui.lineEdit_ID.text()))
                self.open_ogrenci_page()
                self.ui.label_hata.close()
            else:
                self.ui.label_hata.show()

    def open_yonetici_page(self): # yönetici penceresini açmak için
        self.yonetici_page.show()
    def open_ogretmen_page(self): # öğretmen penceresini açmak için
        self.ogretmen_page.show()
    def open_ogrenci_page(self): # öğrenci pencereyi açmak için
        self.ogrenci_page.show()
    def ogretmenLogin(self, sicil_no, sifre):
        self.c.execute("SELECT * from ogretmenler WHERE sicil_no=? AND sifre=?", (sicil_no, sifre))
        if len(self.c.fetchall()) == 0:
            return 0
        else:
            return 1

    def ogretmenBilgi(self, sicil_no):
        self.c.execute("SELECT * FROM ogretmenler WHERE sicil_no=?", (sicil_no,))
        ogretmenBilgi = self.c.fetchone()
        return ogretmenBilgi

    def ogrenciLogin(self, ogrenci_no, tc_no):
        self.c.execute("SELECT * from ogrenciler WHERE ogrenci_no=? AND tc_no=?", (ogrenci_no, tc_no))
        if (len(self.c.fetchall()) == 0):
            return 0
        else:
            return 1

    def ogrenciBilgi(self, ogrenci_no):
        self.c.execute("SELECT * FROM ogrenciler WHERE ogrenci_no=?", (ogrenci_no,))
        ogrenciBilgi = self.c.fetchone()
        return ogrenciBilgi

uygulama = QApplication([])
pencere = Arayuz()
pencere.show()
uygulama.exec_()


import urllib.request
import json
import random
import sys

def main(argv):
    #智研上报的URL
    url='http://zhiyan.monitor.access.inner.woa.com:8080/access_v1.http_service/HttpCurveReportRpc'
    report_data = [
        {"metric":str(argv[1]),"value": argv[2],"tags":{"metricType": "test"}},
        {"metric":str(argv[3]),"value": argv[4],"tags":{"metricType": "test"}},
        {"metric":str(argv[5]),"value": argv[6],"tags":{"metricType": "test"}},
        {"metric":str(argv[7]),"value": argv[8],"tags":{"metricType": "test"}}]
    report_datas = json.dumps(report_data)
    rand_num = random.randint(100000, 999999)
    request_data = {
        "pkg_seq": rand_num,
        "report_cnt": 1 ,
        "app_mark": "3633_20699_test_qinglin",
        "sec_lvl_en_name": "default",
        "env":"prod",
        "report_data": report_datas,
        "instance_mark": "9.134.235.217",
        "report_ip": "9.134.235.217",
        "data_type": 0 ,
        "method": 1
    }

    report_datas = json.dumps(request_data)
    headers={"Content-Type":"application/json"}
    report_datas=bytes(report_datas,'utf8')
    #发送POST请求
    request = urllib.request.Request(url, report_datas, headers)
    response = urllib.request.urlopen(request)
    ret = response.read()
    print (ret)

if __name__ == "__main__":
    main(sys.argv)


import cv2, sys, numpy as np
from matplotlib.pyplot import get

if len (sys.argv) != 2:
    print ("Usage: %s <image-file>" % sys.argv[1], file=sys.stderr)
    exit (1)
print ("The filename to work on is %s." % sys.argv[1])

image = cv2.imread (sys.argv[1])
b_image = image[:,:,0]

cv2.namedWindow (sys.argv[0], cv2.WINDOW_NORMAL)
ny, nx, nc = image.shape
cv2.resizeWindow (sys.argv[0], nx//2, ny//2)
cv2.imshow (sys.argv[0], b_image)
cv2.waitKey (0)


from flask import Flask, render_template, flash, redirect
import os

from flask_wtf import FlaskForm
from wtforms import StringField, IntegerField, SubmitField, EmailField
from wtforms.validators import DataRequired, Email, InputRequired

from flask_sqlalchemy import SQLAlchemy
from datetime import datetime

base_dir = os.path.abspath(os.path.dirname(__file__))

app = Flask(__name__)
app.config['SECRET_KEY'] = "mysecretkeyiwillhideit:)"
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///'+os.path.join(base_dir, 'users.db')

######### FORM ################
class NamerForm(FlaskForm):
    name = StringField('Enter your name:', validators=[DataRequired()])
    age = IntegerField('Enter your age:')
    submit = SubmitField('Register')

class UserForm(FlaskForm):
    name = StringField('Enter your Name:', validators=[DataRequired()])
    email = EmailField('Enter your Email:', validators=[DataRequired()])
    submit = SubmitField('Register User')

####### DATABASE ##############
db = SQLAlchemy(app)

class UsersDb(db.Model):

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, nullable=False )
    email = db.Column(db.String, nullable=False, unique=True)
    date_added = db.Column(db.DateTime, default=datetime.utcnow)

    def __repr__(self):
        return f'{self.name} and email:{self.email}'

############## VIEWS #######################
@app.route("/")
def hello():
    return render_template('hello.html')

@app.route("/user/<name>")
def user(name):
    return render_template('user.html', name=name)

@app.route("/user/add", methods=['GET', 'POST'])
def add_user_to_db():
    form = UserForm()
    name = None
    email = None

    if form.validate_on_submit():
        name = form.name.data
        email = form.email.data

        # querying the db if the email is already present
        user = UsersDb.query.filter_by(email=email).first()
        if user:
            flash(f'{email} already exists. Try logging-in')
        else:
            new_user = UsersDb(name=name, email=email)
            db.session.add(new_user)
            db.session.commit()
            flash(f'{name} with {email} was registered successfully!')

        form = UserForm(formdata=None)

    # quering all users and ordering with date_added
    disp_all_users = UsersDb.query.order_by(UsersDb.date_added)

    return render_template('add-user-to-db.html', form=form, name=name, email=email, disp_all_users=disp_all_users)

@app.route("/name", methods=['GET', 'POST'])
def enter_name():
    name = None
    age = None
    form = NamerForm()

    if form.validate_on_submit():
        name = form.name.data
        age = form.age.data

        form=NamerForm(formdata=None)
        flash(f'{name} was registered successfully!')

        # return redirect(url_for('enter_name'))

    return render_template('name.html', form=form, name=name, age=age)

###### custom error page

# invalid url
@app.errorhandler(404)
def page_not_found(e):
    return render_template('404.html'), 404

#internal server error
@app.errorhandler(500)
def internal_server_e(e):
    return render_template('500.html'), 500

if __name__ == "__main__":
    app.run(debug=True)


import numpy as np
import cv2
from matplotlib import pyplot as plt

cap = cv2.VideoCapture('videos/pull_slinky.MOV')
cv2.namedWindow('Frame',cv2.WINDOW_NORMAL)

OPENCV_OBJECT_TRACKERS = {
	"csrt": cv2.TrackerCSRT_create,
	"kcf": cv2.TrackerKCF_create,
	"boosting": cv2.TrackerBoosting_create,
	"mil": cv2.TrackerMIL_create,
	"tld": cv2.TrackerTLD_create,
	"medianflow": cv2.TrackerMedianFlow_create,
	"mosse": cv2.TrackerMOSSE_create
}

trackers = cv2.MultiTracker_create()
tracker_name = "csrt"
tracker = OPENCV_OBJECT_TRACKERS[tracker_name]()

def get_iou(bb1, bb2):

	assert bb1['x1'] < bb1['x2']
	assert bb1['y1'] < bb1['y2']
	assert bb2['x1'] < bb2['x2']
	assert bb2['y1'] < bb2['y2']

	x_left = max(bb1['x1'], bb2['x1'])
	y_top = max(bb1['y1'], bb2['y1'])
	x_right = min(bb1['x2'], bb2['x2'])
	y_bottom = min(bb1['y2'], bb2['y2'])

	if x_right < x_left or y_bottom < y_top:
		return 0.0

	intersection_area = (x_right - x_left) * (y_bottom - y_top)

	bb1_area = (bb1['x2'] - bb1['x1']) * (bb1['y2'] - bb1['y1'])
	bb2_area = (bb2['x2'] - bb2['x1']) * (bb2['y2'] - bb2['y1'])

	iou = intersection_area / float(bb1_area + bb2_area - intersection_area)
	assert iou >= 0.0
	assert iou <= 1.0
	return iou

def checkallboxes(bb):
	length_list = len(bb)

	bb_copy = bb.copy()
	for i in range(length_list):
			for j in range(i+1, length_list):
				try:
					iou = get_iou(bb_copy[i], bb_copy[j])
					if iou != 0:
						bb_copy.remove(bb_copy[j])
						length_list -= 1

				except Exception as e:
					pass
	return bb_copy

def boxes_to_track(frame, no_of_features=10, bbox_size=20):
	frame_copy = frame.copy()
	gray = cv2.cvtColor(frame_copy,cv2.COLOR_BGR2GRAY)

	corners = cv2.goodFeaturesToTrack(gray,no_of_features,0.01,10)
	corners = np.int0(corners)
	bboxes = []
	for i in corners:
		x,y = i.ravel()
		bbox = {'x1':x-bbox_size//2,'x2':x+bbox_size//2, 'y1':y-bbox_size//2, 'y2':y+bbox_size//2}
		bboxes.append(bbox)

	bbox_cleaned = checkallboxes(bboxes)
	boxes_mod = []

	for box in bbox_cleaned:
		w,h = box['x2'] - box['x1'],  box['y2']-box['y1']
		box_mod = (box['x1'],  box['y1'], w, h)
		boxes_mod.append(box_mod)

	return boxes_mod

if __name__ == '__main__':

	ret,frame = cap.read()
	boxes_calc = boxes_to_track(frame,no_of_features=50)
	frame_no = 0
	while True:

		if frame_no < len(boxes_calc):
			(success, boxes) = trackers.update(frame)

			box = boxes_calc[frame_no]
			(x, y, w, h) = [int(v) for v in box]
			cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)
			cv2.circle(frame,(x+w//2,y+w//2), 3,(255,0,0),thickness=3)

			tracker = OPENCV_OBJECT_TRACKERS[tracker_name]()
			trackers.add(tracker, frame, box)
			cv2.imshow("Frame", frame)
			key = cv2.waitKey(1) & 0xFF

		else:
			ret,frame = cap.read()
			(success, boxes) = trackers.update(frame)
			for box in boxes:
				(x, y, w, h) = [int(v) for v in box]
				cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 2)

			# save this frame as video
			cv2.imshow("Frame", frame)
			key = cv2.waitKey(1) & 0xFF
			if key == ord("q"):
				break

		frame_no+=1

	cap.release()
	cv2.destroyAllWindows()


import torch.nn as nn

class AlexNetEncoder(nn.Module):
    def __init__(self, additional_layers=False):
        super(AlexNetEncoder, self).__init__()

        network = [
            nn.Conv2d(3, 96, kernel_size=11, stride=4),
            nn.ReLU(True),
            nn.LocalResponseNorm(size=5, k=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(96, 256, kernel_size=5, padding=2, stride=1),
            nn.ReLU(True),
            nn.LocalResponseNorm(size=5, k=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(256, 384, kernel_size=3, padding=1, stride=1),
            nn.ReLU(True)
        ]

        if additional_layers:
            network += [
                nn.Conv2d(384, 384, kernel_size=3, padding=1, stride=1),
                nn.ReLU(True)
            ]

        self.network = nn.Sequential(*network)

    def forward(self, x):
        return self.network(x)

class AlexNetProcessor(nn.Module):
    def __init__(self):
        super(AlexNetProcessor, self).__init__()

        self.network = nn.Sequential(
            nn.Conv2d(384, 384, kernel_size=3, padding=1, stride=1),
            nn.ReLU(True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1, stride=1),
            nn.ReLU(True),
            nn.MaxPool2d(kernel_size=3, stride=2)
        )

    def forward(self, x):
        return self.network(x)

class AlexNetDecoder(nn.Module):
    def __init__(self, num_classes):
        super(AlexNetDecoder, self).__init__()

        self.network = nn.Sequential(
            nn.AdaptiveAvgPool2d((6, 6)),
            nn.Flatten(),
            nn.Dropout(),
            nn.Linear(256*6*6, 4096),
            nn.ReLU(True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(True),
            nn.Linear(4096, num_classes)
        )

    def forward(self, x):
        return self.network(x)

def alexnet(num_classes, additional_layers=False):
    return \
        AlexNetEncoder(additional_layers), \
        AlexNetProcessor(), \
        AlexNetDecoder(num_classes)


from flask import Flask
import os
from flask import render_template
import yaml
import string
import re

app = Flask(__name__)

@app.route('/')
def index():
    projects = [
      {
        'title': 'Machine Learning Course in Yerevan',
        'description': 'Machine Leraning Course, to make people ready to work in the industry doing ML.',
        'url': 'ml_afternoon/',
      },
      {
        'title': 'Hygir',
        'description': 'Help people write in Armenian',
        'url': 'http://hygir.com',
      }
    ]
    overview_dict = yaml.load(open('knowledge/overview.yml', 'r'))
    profiles = overview_dict['Social Profiles']
    interests = overview_dict['Interests']
    return render_template('index.html',
                           interests=interests,
                           profiles=profiles,
                           projects=projects)

@app.route('/reading/')
def reading():
    books_dict = yaml.load(open('knowledge/books.yml', 'r'))
    reading = books_dict['Reading']
    recomendations = books_dict['Recommend']
    still_reading = books_dict['Still Reading']
    return render_template('reading.html',
                           title='Reading',
                           block_to_books={'Currently Reading': reading,
                                           'Still Reading': still_reading,
                                           'I recommend': recomendations})

@app.route('/reading_history/')
def reading_history():
    books_dict = yaml.load(open('knowledge/books.yml', 'r'))
    all_books = books_dict['Reading History']
    return render_template('reading.html',
                           title='Books I Have Read',
                           block_to_books={
                              'Reading History (2015-present)': all_books})

@app.route('/photos/')
def photos():
    return render_template('photos.html',
                           title='Photos',
                           photos={})

@app.route('/ml_afternoon/')
def ml_afternoon():
    path = os.path.join(app.root_path, 'templates/ml_afternoon/')
    files = next(os.walk(path))[2]
    lectures, slides, homeworks, practicals = {}, {}, {}, {}
    for _file in files:
        if _file.endswith('.html'):
            without_ext = os.path.splitext(_file)[0]
            name = string.capwords(re.sub('_', ' ', without_ext))
            name = re.sub('(\d+) ', r'\1. ', name)
            if name.endswith('.slides'):
                slides[name] = '../ml_afternoon/{}/'.format(without_ext)
            elif name.lower().startswith('homework'):
                homeworks[name] = '../ml_afternoon/{}/'.format(without_ext)
            elif name.lower().startswith('practical'):
                practicals[re.sub('ractical', '', name)] = '../ml_afternoon/{}/'.format(without_ext)
            else:
                lectures[name] = '../ml_afternoon/{}/'.format(without_ext)
    return render_template('ml_afternoon.html',
                           title='Machine Learning Course',
                           lectures=sorted(lectures.items()),
                           homeworks=sorted(homeworks.items()),
                           practicals=sorted(practicals.items()),
                           slides=sorted(slides.items()))

@app.route('/ml_evening/')
def ml_evening():
    path = os.path.join(app.root_path, 'templates/ml_evening/')
    files = next(os.walk(path))[2]
    lectures, slides, homeworks, practicals = {}, {}, {}, {}
    for _file in files:
        if _file.endswith('.html'):
            without_ext = os.path.splitext(_file)[0]
            name = string.capwords(re.sub('_', ' ', without_ext))
            name = re.sub('(\d+) ', r'\1. ', name)
            if name.endswith('.slides'):
                slides[name] = '../ml_evening/{}/'.format(without_ext)
            elif name.lower().startswith('homework'):
                print(_file)
                homeworks[name] = '../ml_evening/{}/'.format(without_ext)
            elif name.lower().startswith('practical'):
                practicals[re.sub('ractical', '', name)] = '../ml_evening/{}/'.format(without_ext)
            else:
                lectures[name] = '../ml_evening/{}/'.format(without_ext)
    return render_template('ml_evening.html',
                           title='Machine Learning Course',
                           lectures=sorted(lectures.items()),
                           homeworks=sorted(homeworks.items()),
                           practicals=sorted(practicals.items()),
                           slides=sorted(slides.items()))

@app.route('/ml_afternoon/<string:lecture_name>/')
def ml_afternoon_lectures(lecture_name):
    return render_template('ml_afternoon/%s.html' % lecture_name)

@app.route('/ml_evening/<string:lecture_name>/')
def ml_evening_lectures(lecture_name):
    return render_template('ml_evening/%s.html' % lecture_name)


import pygame
from pygame.locals import *
import random
import math

import neat
import pickle

pygame.init()
pygame.font.init()

# Defining Global Constants

c_white = (255, 255, 255)
c_black = (0  ,   0,   0)
c_red   = (255,   0,   0)

# Global Window vars
WIN_WIDTH  = 1280
WIN_HEIGHT = 720
WIN = pygame.display.set_mode((WIN_WIDTH,WIN_HEIGHT))
pygame.display.set_caption('Pong with NEAT')

# Global fonts
FNT_DEBUG = pygame.font.SysFont('Arial', 32)

class Game:
    SCORE_FONT = pygame.font.SysFont("comicsans", 50)
    WHITE = (255, 255, 255)
    BLACK = (0, 0, 0)
    RED = (255, 0, 0)

    def __init__(self, window):
        self.FNT_DEBUG = None
        self.left_score = 0
        self.right_score = 0
        self.left_hits = 0
        self.right_hits = 0
        self.window = window

    def _draw_score(self):
        left_score_text = self.SCORE_FONT.render(
            f"{self.left_score}", 1, self.WHITE)
        right_score_text = self.SCORE_FONT.render(
            f"{self.right_score}", 1, self.WHITE)
        self.window.blit(left_score_text, (1280 //
                                           4 - left_score_text.get_width()//2, 20))
        self.window.blit(right_score_text, (1280 * (3/4) -
                                            right_score_text.get_width()//2, 20))

    def reset(self):
        """Resets the entire game."""
        self.left_score = 0
        self.right_score = 0

class Paddle_Play:
    width = 25
    height = 135  # Center at y + 67
    speed = 0.5
    player_controlled = False
    keybinds = {
        'up': pygame.K_w,
        'down': pygame.K_s
    }

    def __init__(self, x, y, keybinds=None):
        self.x = x
        self.y = y
        self.start_x = x
        self.start_y = y
        if keybinds is not None:
            self.keybinds = keybinds
        self.col = (random.randrange(125, 200), random.randrange(125, 200), random.randrange(125, 200))

    def reset(self):
        self.x = self.start_x
        self.y = self.start_y

    def update(self, dt):
        if self.player_controlled:
            if pygame.key.get_pressed()[self.keybinds['up']]:
                self.move_up(dt)
            if pygame.key.get_pressed()[self.keybinds['down']]:
                self.move_down(dt)

    def move_up(self, dt):
        if self.y == 0:
            return False
        self.y -= min(self.speed * dt, self.y)
        return True

    def move_down(self, dt):
        if self.y + self.height == WIN_HEIGHT:
            return False
        self.y += self.speed * dt
        if self.y + self.height > WIN_HEIGHT:
            self.y = WIN_HEIGHT - self.height
        return True

    def draw(self, surf):
        surf.fill(self.col, (self.x, self.y, self.width, self.height))

class Ball_Play:
    r = 10
    start_vel = 0.5

    def __init__(self, x, y, col):
        self.col = col
        self.x = x
        self.y = y

        self.start_x = x
        self.start_y = y

        dx = (-1 if random.random() > 0.5 else 1) * 0.5
        dy = (random.random() - 0.5) * 2
        v = math.sqrt(dx ** 2 + dy ** 2)

        self.vel = (dx/v * self.start_vel, dy/v * self.start_vel)

    def reset(self):
        self.x = self.start_x
        self.y = self.start_y

        dx = (-1 if random.random() > 0.5 else 1) * 0.5
        dy = (random.random() - 0.5) * 2
        v = math.sqrt(dx ** 2 + dy ** 2)

        self.vel = (dx/v * self.start_vel, dy/v * self.start_vel)

    def update(self, dt, LPaddle, RPaddle):
        # Move
        outcome = 0
        left_score = 0;
        right_score = 0;
        self.y += self.vel[1] * dt
        self.x += self.vel[0] * dt

        v = math.sqrt(self.vel[0] ** 2 + self.vel[1] ** 2)

        angle = math.atan(self.vel[1]/ self.vel[0])
        if self.vel[0] < 0:
            angle += math.pi

        # Adjust position for collisions with ceiling and floor
        if self.y < 0:
            self.y *= -1 # Flip distance to top borders
            angle *= -1
        if self.y > WIN_HEIGHT:
            self.y -= 2*(self.y - WIN_HEIGHT)
            angle *= -1

        if self.x >= RPaddle.x and self.x <= RPaddle.x + RPaddle.width and self.y >= RPaddle.y and self.y <= RPaddle.y + RPaddle.height:
            # Ball collided with paddle
            outcome = 1
            v = math.log(math.exp(v) + 0.1)  # Add a logarithmic speed boost to the ball
            ydelt = self.y - (RPaddle.y + RPaddle.height / 2)
            angle = ydelt / RPaddle.height * math.pi / 2  # Value ranging from -pi/4 to pi/4 depending upon where the ball hit the paddle
            if self.vel[0] > 0:
                angle = math.pi - angle

        if self.x >= LPaddle.x and self.x <= LPaddle.x + LPaddle.width and self.y >= LPaddle.y and self.y <= LPaddle.y + LPaddle.height:
            # Ball collided with paddle
            outcome = 1
            v = math.log(math.exp(v) + 0.1)  # Add a logarithmic speed boost to the ball
            ydelt = self.y - (LPaddle.y + LPaddle.height / 2)
            angle = ydelt / LPaddle.height * math.pi / 2  # Value ranging from -pi/4 to pi/4 depending upon where the ball hit the paddle
            if self.vel[0] > 0:
                angle = math.pi - angle

        # Bounce the ball off the left wall
        if self.x < 0:
            outcome = 10
            right_score = right_score + 1;

        elif self.x > WIN_WIDTH:
            outcome = 11
            left_score = left_score + 1;

        self.vel = (v * math.cos(angle), v * math.sin(angle))

        return right_score, left_score, outcome

    def draw(self, surf):
        pygame.draw.circle(surf, self.col, (self.x, self.y), self.r)

class Ball():
    r = 10
    start_vel = 0.5

    def __init__(self, x, y, paddle, col):
        self.col = col
        self.x = x
        self.y = y

        self.start_x = x
        self.start_y = y

        self.paddle = paddle

        dx = (-1 if random.random() > 0.5 else 1) * 0.5
        dy = (random.random() - 0.5) * 2
        v = math.sqrt(dx ** 2 + dy ** 2)

        self.vel = (dx/v * self.start_vel, dy/v * self.start_vel)

    def reset(self):
        self.x = self.start_x
        self.y = self.start_y

        dx = (-1 if random.random() > 0.5 else 1) * 0.5
        dy = (random.random() - 0.5) * 2
        v = math.sqrt(dx ** 2 + dy ** 2)

        self.vel = (dx/v * self.start_vel, dy/v * self.start_vel)

    def update(self, dt):
        # Move
        outcome = 0
        self.y += self.vel[1] * dt
        self.x += self.vel[0] * dt

        v = math.sqrt(self.vel[0] ** 2 + self.vel[1] ** 2)

        angle = math.atan(self.vel[1]/ self.vel[0])
        if self.vel[0] < 0:
            angle += math.pi

        # Adjust position for collisions with ceiling and floor
        if self.y < 0:
            self.y *= -1 # Flip distance to top borders
            angle *= -1
        if self.y > WIN_HEIGHT:
            self.y -= 2*(self.y - WIN_HEIGHT)
            angle *= -1

        # Check for collisions with paddles

        if self.x >= self.paddle.x and self.x <= self.paddle.x + self.paddle.width and self.y >= self.paddle.y and self.y <= self.paddle.y + self.paddle.height:
            # Ball collided with paddle
            outcome = 1
            v = math.log(math.exp(v) + 0.1) #Add a logarithmic speed boost to the ball
            ydelt = self.y - (self.paddle.y + self.paddle.height/2)
            angle = ydelt/self.paddle.height * math.pi / 2 #Value ranging from -pi/4 to pi/4 depending upon where the ball hit the paddle
            if self.vel[0] > 0:
                angle = math.pi - angle

        # Bounce the ball off the left wall
        if self.x < 0:
            self.x *= -1
            angle  = math.pi - angle # flip the direction the ball is traveling over the y axis
            angle = random.uniform(math.pi/-4 , math.pi/4)
            #outcome = -1

        elif self.x > WIN_WIDTH:
            outcome = -1

        self.vel = (v * math.cos(angle), v * math.sin(angle))
        return outcome

    def draw(self, surf):
        pygame.draw.circle(surf, self.col, (self.x, self.y), self.r)

class Paddle():
    width = 25
    height = 135 # Center at y + 67
    speed = 0.5
    player_controlled=False
    keybinds = {
        'up':pygame.K_w,
        'down':pygame.K_s
    }

    def __init__(self, x, y, keybinds=None):
        self.x = x
        self.y = y
        self.start_x = x
        self.start_y = y
        if keybinds is not None:
            self.keybinds = keybinds
        self.col = (random.randrange(125, 200), random.randrange(125, 200), random.randrange(125, 200))
        self.ball = Ball(WIN_WIDTH/2, WIN_HEIGHT/2, self, self.col)

    def reset(self):
        self.x = self.start_x
        self.y = self.start_y

        self.ball.reset()

    def update(self, dt):
        if self.player_controlled:
            if pygame.key.get_pressed()[self.keybinds['up']]:
                self.move_up(dt)
            if pygame.key.get_pressed()[self.keybinds['down']]:
                self.move_down(dt)

        return self.ball.update(dt)

    def move_up(self, dt):
        if self.y == 0:
            return False
        self.y -= min(self.speed*dt, self.y)
        return True

    def move_down(self, dt):
        if self.y + self.height == WIN_HEIGHT:
            return False
        self.y += self.speed*dt
        if self.y + self.height > WIN_HEIGHT:
            self.y = WIN_HEIGHT - self.height
        return True

    def draw(self, surf):
        surf.fill(self.col, (self.x, self.y, self.width, self.height))
        self.ball.draw(surf)

def gaus(z):
    return 1/math.sqrt(2*math.pi) * math.exp(-(z**2)/2)

def eval_genomes(genomes, config):
    paddles = []
    nets = []
    ge = []

    for genome_id, genome in genomes:
        genome.fitness = 0
        net = neat.nn.FeedForwardNetwork.create(genome, config)
        ge.append(genome)
        nets.append(net)
        paddles.append(Paddle(WIN_WIDTH-30, WIN_HEIGHT/2 - 67))

    # Create engine
    clock = pygame.time.Clock()

    # Run the game until manual exit, all paddles have died, or a paddle has reflected at least 25 balls
    while len(paddles) > 0:
        dt = clock.tick(60)

        # Check if there is a manual exit
        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                pygame.quit()
                quit()

        rem = []
        for x, p in enumerate(paddles):
            n = nets[x]
            g = ge[x]

            inputs = (  p.ball.x,       # Ball pos
                        p.ball.y,
                        #p.ball.vel[0], # Ball Vel
                        #p.ball.vel[1],
                        p.y)            # Paddle pos (1D)
            output = n.activate(inputs)

            if (output[0] > 0.5) ^ (output[1] > 0.5):
                moved = False
                if output[0] > 0.5:
                    moved = p.move_up(dt)
                if output[1] > 0.5:
                    moved = p.move_down(dt)

                if moved:
                    ydelt = abs(p.y + p.height/2 - p.ball.y) / p.height
                    g.fitness += 5*gaus(ydelt)
                else:
                    g.fitness -= 1
            else:
                g.fitness -= 0.05

            outcome = p.update(dt)
            if outcome == 1:
                # Ball was successfully reflected, reward genome
                #g.fitness += 50
                pass

            if outcome == -1:
                # Paddle missed ball, remove it from the game
                rem.append(x)

        # Remove all the dropped paddles
        for x in rem[::-1]:
            ge.pop(x)
            paddles.pop(x)
            nets.pop(x)

        # === DRAW ===
        WIN.fill(c_black)

        # Draw lines in the middle
        num_lines = math.ceil(WIN_HEIGHT / 25 / 2)
        for i in range(num_lines):
            start_pos = (WIN_WIDTH / 2, 25 * 2 * i)
            end_pos =   (WIN_WIDTH / 2, 25 * 2 * i + 25)
            pygame.draw.line(WIN, c_white, start_pos, end_pos, 3)

        for p in paddles:
            p.draw(WIN)

        pygame.display.flip()

def test_ai(net):
    User = Paddle_Play(30, WIN_HEIGHT/2, keybinds=True)
    AI = Paddle_Play(WIN_WIDTH-30, WIN_HEIGHT/2)
    #test_P = Paddle_Play(WIN_WIDTH-30, WIN_HEIGHT/2, keybinds=True)
    Ball_In = Ball_Play(WIN_WIDTH/2, WIN_HEIGHT/2, c_white)
    Game_play = Game(window=WIN)
    clock = pygame.time.Clock()

    run = True
    while run:
        dt = clock.tick(60)

        for e in pygame.event.get():
            if e.type == pygame.QUIT:
                run = False
                break

        inputs = (Ball_In.x,  # Ball pos
                  Ball_In.y,
                  #AI.ball.vel[0], # Ball Vel
                  #AI.ball.vel[1],
                  AI.y)  # Paddle pos (1D)
        output = net.activate(inputs)
        if output[0] > 0.5:
            AI.move_up(dt)
        if output[1] > 0.5:
            AI.move_down(dt)

        outcome = AI.update(dt)

        keys = pygame.key.get_pressed()
        if keys[pygame.K_w]:
            User.move_up(dt)
            #test_P.move_up(dt)
        if keys[pygame.K_s]:
            User.move_down(dt)
            #test_P.move_down(dt)

        WIN.fill(c_black)

        # Draw lines in the middle
        num_lines = math.ceil(WIN_HEIGHT / 25 / 2)
        for i in range(num_lines):
            start_pos = (WIN_WIDTH / 2, 25 * 2 * i)
            end_pos = (WIN_WIDTH / 2, 25 * 2 * i + 25)
            pygame.draw.line(WIN, c_white, start_pos, end_pos, 3)

        AI.draw(WIN)
        #test_P.draw(WIN)
        User.draw(WIN)
        Ball_In.draw(WIN)
        RScore, LScore, Check_Score = Ball_In.update(dt=dt, LPaddle=User, RPaddle=AI)

        if Check_Score == 10 or Check_Score == 11:
            Game_play.right_score = Game_play.right_score + RScore
            Game_play.left_score = Game_play.left_score + LScore
            #test_P.reset()
            #AI.reset()
            #User.reset()
            Ball_In.reset()

        # if Game_play.right_score == 5 or Game_play.left_score == 5:
        #     if Game_play.right_score == 5:
        #         text = FNT_DEBUG.render("AI Wins", True, c_white)
        #         textRect = text.get_rect()
        #         textRect.center = (1280 // 2, 720 // 2)
        #         WIN.blit(text, textRect)
        #         # for g in pygame.event.get():
        #         #     if g.type == pygame.K_w:
        #         #         break
        #         event = pygame.event.wait()
        #         if event.type == QUIT:
        #             pygame.quit()
        #         elif event.type == KEYDOWN:
        #             pass

            # else:
            #     text = FNT_DEBUG.render("You Win", True, c_white)
            #     textRect = text.get_rect()
            #     textRect.center = (1280 // 2, 720 // 2)
            #     WIN.blit(text, textRect)

        Game_play._draw_score()

        pygame.display.flip()

def play_ai(config):
    with open("best_net.pkl", "rb") as f:
        winner = pickle.load(f)
    #winner_net = neat.nn.FeedForwardNetwork.create(winner,config)
    test_ai(winner)

def run(config_path):
    config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                         neat.DefaultSpeciesSet, neat.DefaultStagnation,
                         config_path)
    p = neat.Population(config)
    p.add_reporter(neat.StdOutReporter(True))
    #Added -Josh
    stats = neat.StatisticsReporter()
    p.add_reporter(stats)
    p.add_reporter(neat.Checkpointer(1))

    winner = p.run(eval_genomes, 300)
    winner_net = neat.nn.FeedForwardNetwork.create(winner, config)
    pickle.dump(winner_net, open('./best_net.pkl', 'wb'))

if __name__ == '__main__':
    #Train AI
    #run('C:/Pycharm/Pong NEAT/config.txt')

    #Play Against AI
    play_ai('C:/Pycharm/Pong NEAT/config.txt')

# TODO
    # Add in current highest fintess within and out of generation