"""Calculate various statistics for the CEA playerbase, and stores in a spreadsheet.

Attributes:
    counts (Counter): counting number of games
    EXTRA_GAMES_FILE (str): File to be used if we need to input extra games
    K (int): K-value used for elo ratings.
"""
import csv
import json
import os
import re
import string
import sys
import traceback
from datetime import datetime
from datetime import timedelta
from collections import Counter, deque
import mpyq
import sc2reader
import trueskill
import glicko2
import cea_team_name_parser
import xlsxwriter
import pandas as pd
from sc2reader.engine.plugins import APMTracker, SelectionTracker # unused
from consts import SEASONS, STARTING_DATE, WEEKS
from setup_replays import find_team, replay_directory, teams_file
from zeroNumber import zeroNumber
from elo import EloRating

sc2reader.engine.register_plugin(APMTracker())

UNKNOWN_TEAM = "TEAM_NOT_KNOWN"
EXTRA_GAMES_FILE = "extra_games.csv"
# K is used for elo.
K=80
counts = Counter()

class PlayerObject:
  def __init__(self, name, season, team):
    self.name = name
    self.aliases = set()
    self.wins = 0
    self.rating = 1000
    self.glicko = glicko2.Player()
    # long term glicko rating
    self.glicko_longterm = glicko2.Player()
    self.trueskill = trueskill.Rating()
    self.peak_rating = 1000
    self.games = []
    self.teams = {season : team}
    self.zeroNumber = sys.maxsize

  losses = property(fget=lambda self: len(self.games) - self.wins)
  mmr = property(fget=lambda self: max(game.mmr for game in self.games))

  def setRating(self, rating):
    self.rating = rating
    if rating > self.peak_rating:
      self.peak_rating = rating

  def isActive(self):
    return 0 in self.teams

  def addTeam(self, season, team):
    self.teams[season] = team

  @property
  def race(self):
    race_counter = Counter([game.race for game in self.games])
    return race_counter.most_common(1)[0][0]

  @property
  def opponents_beaten(self):
    return [game.opponent for game in self.games if game.win]

  @property
  def opponents_lost_to(self):
    return [game.opponent for game in self.games if not game.win]

  @property
  def mostRecentTeam(self):
    return self.teams[sorted(list(self.teams.keys()))[0]]

  def addGames(self, game):
    self.games.append(game)
    if game.win:
      self.wins += 1

class GameObject:

  """ Struct containing information about a game, given 1 player.

  Attributes:
      duration (int): Length of the game in seconds
      opponent (str): Name of the opponent
      race (str): Selected race
  """

  def __init__(self, opponent, race, opponent_race, map_name, mmr, win, duration,
               season, glicko_longterm, opp_glicko_longterm):
    self.opponent = opponent
    self.race = race
    self.opponent_race = opponent_race
    self.mmr = mmr
    self.win = win
    self.map = map_name
    self.glicko_rating = glicko_longterm.getRating()
    self.glicko_rd = glicko_longterm.getRd()
    self.opp_glicko_rating = opp_glicko_longterm.getRating()
    self.opp_glicko_rd = opp_glicko_longterm.getRd()
    # self.apm = apm
    self.duration = duration
    self.season = season

# Add in extra games
# Games is 2d array: each one has [date, player1, player2, win]
def input_extra_elo(players, games, current_date, season):
  """Add in extra games.

  Args:
      players (Array[PlayerObject]): array of the 2 players
      games (str[n,4]): Each column is [date, player1, player2, win].
        Each row is a game.
      current_date (datetime): current date. don't process games after date.
      season (int): season. 0 is most recent
  """
  while games and games[0][0] and current_date > datetime.strptime(games[0][0], "%m/%d/%Y"):
    # ISSUE: doesn't resolve aliases, doesn't work if player has not already been processed.
    player_names = [games[0][1].lower(), games[0][2].lower()]
    for index, player in enumerate(player_names):
      # add them in if not in there
      if player not in players:
        players[player] = PlayerObject(player,
                season, find_team(teams, player))
    for index, player in enumerate(player_names):
      gameObject = GameObject(opponent=player_names[1-index], race="", opponent_race="", map_name="", mmr=0,
                              win=games[0][3].lower() == player,
                              duration=0, season=season,
                              glicko_longterm = players[player].glicko_longterm,
                              opp_glicko_longterm = players[player_names[1 - index]].glicko_longterm)
      players[player].addGames(gameObject)

    winner = games[0][3].lower() == player_names[0]
    update_rating(players[player_names[0]], players[player_names[1]], winner)

    games.popleft()

def update_rating(player1, player2, win):
  """Update player ratings after a game

  Args:
      player1 (PlayerObject):
      player2 (PlayerObject):
      win (bool): whether player 1 won
  """
  # Update Elo rating
  A,B = EloRating(player1.rating, player2.rating, K, win)
  player1.rating = A
  player2.rating = B

  # Update Glicko-2 rating
  player1.glicko.update_player([player2.glicko.getRating()], [player2.glicko.getRd()], [win])
  player2.glicko.update_player([player1.glicko.getRating()], [player1.glicko.getRd()], [not win])

  # Update Trueskill rating

  winner, loser = trueskill.rate_1vs1(player1.trueskill, player2.trueskill) if win == 1 else trueskill.rate_1vs1(player2.trueskill, player1.trueskill)
  player1.trueskill = winner if win else loser
  player2.trueskill = loser if win else winner

def update_glicko_longterm(players):
    """Updates Longterm Glicko ratings

    Args:
        players (Dict<Player>[String]): Dictionary of players: key is player
            name (lowercase), value is PlayerObject
    """

    # Iterate through seasons in reverse order (oldest to newest)
    for season in reversed(range(len(SEASONS))):
      for player in players.values():
        # First, gather all the glicko ratings in their games
        opp_ratings = []
        opp_rds = []
        win = []
        for game in player.games:
            if game.season == season:
              opp_ratings.append(game.opp_glicko_rating)
              opp_rds.append(game.opp_glicko_rd)
              win.append(game.win)
        if not opp_ratings:
          player.glicko_longterm.did_not_compete()
        else:
          player.glicko_longterm.update_player(opp_ratings, opp_rds, win)

def load_value(replay_filename, value):
  """Gets values from replay file

  Args:
      replay_filename (Replay): Replay
      value (String): Key to get from replay. (I.e MMR)

  Returns:
      TYPE: Description
  """
  archive = mpyq.MPQArchive(replay_filename)
  jsondata = archive.read_file("replay.gamemetadata.json").decode("utf-8")
  obj = json.loads(jsondata)

  mmrs = [0,0]
  for i in [0,1]:
    mmrs[i] = 0 if value not in obj['Players'][i] else obj['Players'][i][value]
  return mmrs

def calculate_elo(directory, players, teams, aliases, season, games):
  # Using mypq, load the replay file
  matcher = re.compile(r'\.SC2Replay$', re.IGNORECASE)
  def myFunc(replay):
    replay_file = sc2reader.load_replay(os.path.join(directory, replay), load_level=2)
    return replay_file.date
  replays = [file for file in os.listdir(directory) if matcher.search(file)]
  replays.sort(key=myFunc)
  print("Found %d replays to scan" % len(replays))

  for replay in replays:
    try:
      replay_filename = os.path.join(directory, replay)
      replay_file = sc2reader.load_replay(replay_filename, load_level=2)
      player_list = replay_file.players
      player_names = [player_list[0].name,
                      player_list[1].name]
      player_mmrs = load_value(replay_filename, 'MMR')

      input_extra_elo(players, games, replay_file.date, season)

      # ignore 2v2
      if len(replay_file.players) > 2:
        print(replay)
        continue

      # resolve aliases for players who play under several accounts
      for i in range(len(player_names)):
        if player_names[i].lower() in aliases:
            player_names[i] = aliases[player_names[i].lower()].lower()
        else:
          player_names[i] = player_names[i].lower()

      # Ignore it
      if replay_file.winner is None:
        print(replay)
        continue
      # Add them to the player list if they're not there
      for index, player in enumerate(player_list):
        player_name = player_names[index]
        if player_name not in players:
          players[player_name] = PlayerObject(player.name,
                season, find_team(teams, player.name))
        else:
          players[player_name].addTeam(season, find_team(teams, player.name))

      # Loop again to add the games
      for index, player in enumerate(player_list):
        player_name = player_names[index]
        gameObject = GameObject(opponent=player_names[1-index],
          race = player.pick_race,
          opponent_race=player_list[1-index].pick_race,
          map_name = replay_file.map_name,
          mmr = player_mmrs[index],
          win=replay_file.winner.players[0] == player,
          duration=replay_file.real_length,
          season=season,
          glicko_longterm=players[player_name].glicko_longterm,
          opp_glicko_longterm=players[player_names[1 - index]].glicko_longterm)
        players[player_name].addGames(gameObject)

      winner = replay_file.winner.players[0] == player_list[0]

      update_rating(players[player_names[0]], players[player_names[1]], winner)

    except:
      print("Error processing replay: %s" % replay)
      traceback.print_exc()

def writeProfile(value, workbook, player_dictionary):
  if value.name not in workbook.sheetnames:
    sheet_name = value.name
  else:
    sheet_name = value.name + ' 1'
  playerWorksheet = workbook.add_worksheet(sheet_name)
  main_sheet = "Main"
  playerWorksheet.write_url(0, 0, f"internal:'{main_sheet}'!A1", string='Back to Main Sheet')
  playerWorksheet.write(0, 1, 'Player Name')
  playerWorksheet.write(1, 1, value.name)
  playerWorksheet.set_column(1, 1, max(len('Player Name'), len(value.name))+1)
  playerWorksheet.write(0, 2, 'Teams')
  playerWorksheet.set_column(2, 2, 20)
  playerWorksheet.set_column(3, 4, 12)
  playerWorksheet.write(0, 4, 'Games')
  playerWorksheet.write(0, 5, 'Opponent Team')
  playerWorksheet.set_column(5, 5, 15)
  playerWorksheet.write(0, 6, 'Opponent')
  playerWorksheet.set_column(6, 6, 15)
  playerWorksheet.write(0, 7, 'Player Race')
  playerWorksheet.set_column(7, 7, 8)
  playerWorksheet.write(0, 8, 'Opponent Race')
  playerWorksheet.set_column(8, 8, 8)
  playerWorksheet.write(0, 9, 'Match Result')
  playerWorksheet.set_column(9, 9, 6)
  playerWorksheet.write(0, 10, 'Map')
  playerWorksheet.set_column(10, 10, 20)
  playerWorksheet.write(0, 12, 'Records')
  playerWorksheet.set_column(11, 11, 25)

  index = 1
  for season, team in value.teams.items():
    startIndex = 2
    playerWorksheet.write(index, startIndex, team)
    playerWorksheet.write(index, startIndex + 1, SEASONS[season])
    index += 1
  indexGame = 1
  raceWinCounter = Counter()
  raceLossCounter = Counter()
  for game in value.games:
    win = "Win" if game.win else "Loss"
    if game.opponent_race:
      if game.win:
        raceWinCounter[game.opponent_race] += 1
      else:
        raceLossCounter[game.opponent_race] += 1

    startIndex = 4
    playerWorksheet.write(indexGame, startIndex, SEASONS[game.season])
    if game.season in player_dictionary[game.opponent].teams:
      oppTeam = player_dictionary[game.opponent].teams[game.season]
    else:
      oppTeam = "UNKOWN_TEAM"
    playerWorksheet.write(indexGame, startIndex + 1, oppTeam)
    playerWorksheet.write(indexGame, startIndex + 2, player_dictionary[game.opponent].name)
    playerWorksheet.write(indexGame, startIndex + 3, game.race)
    playerWorksheet.write(indexGame, startIndex + 4, game.opponent_race)
    playerWorksheet.write(indexGame, startIndex + 5, win)
    playerWorksheet.write(indexGame, startIndex + 6, game.map)
    indexGame += 1

  # For Player Records
  opponentsBeaten = Counter(value.opponents_beaten)
  opponentsLostTo = Counter(value.opponents_lost_to)
  indexRecord = 1

  for opponent in set(value.opponents_beaten + value.opponents_lost_to):
    count = 0
    startIndex = 11
    if opponent in opponentsBeaten:
      count += opponentsBeaten[opponent]
    if opponent in opponentsLostTo:
      count += opponentsLostTo[opponent]
    if count >= 2:
      playerWorksheet.write(indexRecord, startIndex, player_dictionary[opponent].name)
      playerWorksheet.write(indexRecord, startIndex+1, "{0}:{1}".format(opponentsBeaten[opponent], opponentsLostTo[opponent]))
      indexRecord += 1

  indexRecord += 1
  for race in ['Terran', 'Zerg', 'Protoss', 'Random']:
    playerWorksheet.write(indexRecord, startIndex, "vs " + race)
    playerWorksheet.write(indexRecord, startIndex + 1, "{0}:{1}".format(raceWinCounter[race], raceLossCounter[race]))
    indexRecord += 1

  return sheet_name

def write_profiles(player_dictionary):
  workbook = xlsxwriter.Workbook('cea_season_stats.xlsx')
  index = 0
  for key, value in player_dictionary.items():
    writeProfile(value, workbook, player_dictionary)
    index +=1
  workbook.close()

def make_csv(player_dictionary):
  # calculate zero number
  maxPlayer = zeroNumber(player_dictionary)
  headers_arr = ["Team Name", "Name", "Wins", "Losses", "Elo (avg=1000)", "Trueskill Rating (avg=25)", "Peak MMR", maxPlayer + " Number", "Active", "Race",
                "Players Defeated", "Players Lost To"]
  workbook = xlsxwriter.Workbook('cea_season_stats.xlsx')
  worksheet1 = workbook.add_worksheet("Main")
  worksheet1.write_row(0, 0, headers_arr)
  worksheet1.freeze_panes(1, 0)
  # worksheet1.autofilter('A1:L500')
  index = 0
  for key, value in player_dictionary.items():
    new_entry = []
    # Name
    new_entry.append(value.mostRecentTeam)

    new_entry.append(value.name)

    # Wins
    new_entry.append(int(value.wins))

    # Losses
    new_entry.append(int(value.losses))

    # Elo
    new_entry.append(int(value.rating))

    # Glicko-2
    # new_entry.append("{} ± {}".format(int(value.glicko.getRating()), int(value.glicko.getRd())) )

    # Trueskill Rating

    new_entry.append("{:.2f} ± {:.1f}".format(value.trueskill.mu, value.trueskill.sigma))

    # MMR
    new_entry.append(int(value.mmr))

    # zero number
    zeroNum = int(value.zeroNumber) if value.zeroNumber < sys.maxsize else ''
    new_entry.append(zeroNum)

    new_entry.append("Yes" if value.isActive() else "No")

    # Race
    new_entry.append(value.race)
    # APM
    # new_entry.append(int(value.apm))

    # Retrieve list of opponents beaten / lost to, with MMR differential.
    def opponent_func(opponents_list, descending):
      new_opponents_list = [opp_nickname for opp_nickname in opponents_list]
      new_opponents_list = sorted(new_opponents_list, key=lambda item: (
          player_dictionary[item].rating), reverse=descending)
      new_opponents_list = [player_dictionary[opponent].name for opponent in new_opponents_list]
      return new_opponents_list

    opponents_beaten = opponent_func(value.opponents_beaten, True)
    opponents_lost_to = opponent_func(value.opponents_lost_to, False)

    # Opponents beaten / lost to
    new_entry.append(" ; ".join(opponents_beaten))
    new_entry.append(" ; ".join(opponents_lost_to))

    worksheet1.write_row(index + 1, 0, new_entry)
    if 0 in value.teams or (1 in value.teams and len(value.games) >= 5):
      playerSheet = writeProfile(value, workbook, player_dictionary)
      worksheet1.write_url(index + 1, 1, f"internal:'{playerSheet}'!A1", string=value.name)
    index += 1
  worksheet1.conditional_format('E2:E500', {'type': '3_color_scale'})
  print("Done creating CSV")
  workbook.close()

if __name__ == "__main__":
  players = {}
  extra_games = cea_team_name_parser.init_extra_games(EXTRA_GAMES_FILE)

  # Instantiate Trueskill
  trueskill.setup(draw_probability=0)

  # Iterate seasons descending from oldest to newest
  for season in reversed(range(len(SEASONS))):
  #for season in [3]:
    teams, aliases = cea_team_name_parser.init_dictionary(teams_file(season))
    calculate_elo(replay_directory(season), players,
                    teams, aliases, season, extra_games)

  # Input extra elo for newest season
  input_extra_elo(players, extra_games, datetime.today(), 0)

  make_csv(players)
  #write_profiles(players)


import random
import numpy as np
from scipy.optimize import fsolve

# velocity upper bound from Wu et al (https://flow-project.github.io/papers/wu17a.pdf )
# This is an approximation

def v_eq_max_function(v, *args):
    """Return the error between the desired and actual equivalent gap."""
    num_vehicles, length = args

    # maximum gap in the presence of one rl vehicle
    s_eq_max = (length - num_vehicles * 5) / (num_vehicles - 1)

    v0 = 30
    s0 = 2
    tau = 1
    gamma = 4

    error = s_eq_max - (s0 + v * tau) * (1 - (v / v0) ** gamma) ** -0.5

    return error

def get_velocity_upper_bound(num_vehicles, length):
    """Return the velocity upper bound for the given number of vehicles."""
    v_guess = 4
    return fsolve(v_eq_max_function, np.array(v_guess), args=(num_vehicles, length))[0]

def get_desired_velocity(num_vehicles, length, method_name = None):
    """
    Desired velocity is gotten as the uniform flow equillibrium velocity
    Only some controllers require this
    """

    # some known values are hard coded:
    if length == 220:
        # reduce to 2.7 for FS
        if method_name == "fs":
            return 2.7
        else:
            return 3.0

    elif length == 230:
        return 3.45

    elif length == 260:
        # From hit and trial, for
        return 4.82 # Value from LORR paper, other sources

    elif length == 270:
        return 5.2

    else:
        scaler = 0.93 # 93% of the upper bound may be desired?
        print("Scaler: ", scaler)
        return get_velocity_upper_bound(num_vehicles, length) * scaler

# Shock
# Define shock models

def get_shock_model(identifier, length = None, network_scaler=1, bidirectional=False, high_speed = False):
    # Network scaler 6 used in the bottleneck
    # Accel/ Decel value, duration, frequency (in the interval between shock start and shock end)
    # Duration: In seconds, for which each shock is applied
    # Frequency: In the interval, how many shocks are applied
    # if identifier == 1:
    #     return (-1.4, 2, 10)

    if identifier == 2:
        # Thiese ranges are obtained form data
        # sample frequency
        frequency = network_scaler*np.random.randint(5, 20) # value of 10 means once shock every 3000/10 = 300 steps, 5 = 600 steps, 15 = 200 steps

        intensity_collect = []
        duration_collect = []
        if high_speed:
            intensity_abs_min = 1.5
            intensity_abs_max = 4.0
        else:
            intensity_abs_min = 1
            intensity_abs_max = 3.0
        print("Frequency:", frequency)

        for i in range(frequency):
            if bidirectional:
                # between (-abs_max to -abs_min) and (abs_min to abs_max) but not between (-abs_min to abs_min)
                intensity = random.uniform(-intensity_abs_max, intensity_abs_max)
                while intensity > -intensity_abs_min and intensity < intensity_abs_min:
                    intensity = random.uniform(-intensity_abs_max, intensity_abs_max)

            else:
                intensity = random.uniform(-intensity_abs_max, -intensity_abs_min)

            print("Intensity:", intensity)

            durations = np.linspace(0.1, 2.5, 20) # In seconds
            abs_intensity = abs(intensity)
            intensity_bucket = np.linspace(intensity_abs_min, intensity_abs_max,len(durations))
            loc = np.searchsorted(intensity_bucket, abs_intensity)

            left = loc
            right = len(durations) - loc
            probabilities_left = np.linspace(0.0, 10, left)
            # print("Probabilities left:", probabilities_left, probabilities_left.sum())

            probabilities_right = np.linspace(10, 0.0, right)
            # print("Probabilities right:", probabilities_right, probabilities_right.sum())

            probabilities = np.concatenate((probabilities_left, probabilities_right))
            probabilities /= probabilities.sum()
            #print("Probabilities:", probabilities, probabilities.sum())

            duration = round(np.random.choice(durations, 1, p=probabilities)[0], 1)
            print("Duration:", duration)

            intensity_collect.append(intensity)
            duration_collect.append(duration)

        # return intensity, durations (second), frequency
        return (np.asarray(intensity_collect), np.asarray(duration_collect), frequency)

    # Stability test
    elif identifier == -1:
        # velocity, duration, frequency
        # Stability tests have velocity manipulation, so the first param here is speed at the velocity dip
        # Duration and frequency are also used
        # Just apply once is enough
        if length ==220:
            vel_set = 2.0
            duration = 1

        elif length == 270:
            vel_set = 3.0
            duration = 2

        elif length == 260:
            vel_set = 3.0
            duration = 2

        else:
            vel_set = 5.0
            duration = 2
        print("\n\nVelocity set: ", vel_set)
        return (vel_set, duration, 1)
        #return (2, 10, 10)

    else:
        raise ValueError("Shock model identifier not recognized")

## Shock utils
def get_time_steps_stability(duration, frequency, shock_start_time, shock_end_time):
        # Convert duration to env steps
        duration = duration*10

        # Based on this frequency, get the time steps at which the shock is applied
        start_times = np.linspace(shock_start_time, shock_end_time - duration, frequency, dtype=int)
        end_times = np.linspace(shock_start_time + duration, shock_end_time, frequency, dtype=int)
        shock_time_steps = np.stack((start_times, end_times), axis=1)

        print("Start times: ", start_times)
        print("End times: ", end_times)
        print("Shock times: \n", shock_time_steps)

        # TODO: Perform overlap tests and warn if there is overlap
        # if start_times[1] < end_times[0]:
        #     import sys
        #     sys.exit()

        return shock_time_steps

def get_time_steps(durations, frequency, shock_start_time, shock_end_time):
        # Convert duration to env steps
        durations = durations*10
        print("Durations: ", durations)

        # Based on this frequency, get the time steps at which the shock is applied
        start_times = np.linspace(shock_start_time, shock_end_time - durations[-1], frequency, dtype=int)
        end_times = []

        for i in range(frequency):
            end_times.append(start_times[i] + durations[i])
        shock_time_steps = np.stack((start_times, end_times), axis=1)

        print("Start times: ", start_times)
        print("End times: ", end_times)
        print("Shock times: \n", shock_time_steps)

        # TODO: Perform overlap tests and warn if there is overlap
        # if start_times[1] < end_times[0]:
        #     import sys
        #     sys.exit()

        return shock_time_steps

# use
# sm = shock_model(2)
# get_time_steps(durations, frequency, 8000, 10000)
#print(sm[0][1])


from guardata.client.client_events import ClientEvent
import pytest
from unittest.mock import ANY
from pendulum import datetime

from guardata.api.data import UserManifest, WorkspaceEntry
from guardata.client.types import WorkspaceRole, LocalUserManifest, EntryID
from guardata.client.fs import (
    FSError,
    FSWorkspaceNotFoundError,
    FSBackendOfflineError,
    FSSharingNotAllowedError,
)
from backendService.realm import RealmGrantedRole, RealmRole

from tests.common import freeze_time, create_shared_workspace

@pytest.mark.trio
async def test_share_unknown(running_backend, alice_user_fs, bob):
    wid = EntryID()
    with pytest.raises(FSWorkspaceNotFoundError):
        await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)

@pytest.mark.trio
async def test_share_to_oneself(running_backend, alice_user_fs, alice):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")

    with pytest.raises(FSError) as exc:
        await alice_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.MANAGER)
    assert str(exc.value) == "Cannot share to oneself"

@pytest.mark.trio
async def test_share_bad_recipient(running_backend, alice_user_fs, alice, mallory):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")

    with pytest.raises(FSError) as exc:
        await alice_user_fs.workspace_share(wid, mallory.user_id, WorkspaceRole.MANAGER)
    assert str(exc.value) == "User `mallory` doesn't exist in backend"

@pytest.mark.trio
async def test_share_offline(running_backend, alice_user_fs, bob):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")

    with running_backend.offline():
        with pytest.raises(FSBackendOfflineError):
            await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)

@pytest.mark.trio
@pytest.mark.parametrize("presynced", (True, False))
async def test_share_ok(running_backend, alice_user_fs, bob_user_fs, alice, bob, presynced):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")

    if presynced:
        await alice_user_fs.sync()

    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)

    with bob_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-03"):
            await bob_user_fs.process_last_messages()
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {
            "new_entry": WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 3),
                role=WorkspaceRole.MANAGER,
            ),
            "previous_entry": None,
        },
    )

    aum = alice_user_fs.get_user_manifest()
    bum = bob_user_fs.get_user_manifest()
    assert len(aum.workspaces) == 1
    assert len(bum.workspaces) == 1
    awe = aum.get_workspace_entry(wid)
    bwe = bum.get_workspace_entry(wid)

    assert bwe.name == "w1"
    assert bwe.id == awe.id
    assert bwe.role == WorkspaceRole.MANAGER

    aw = alice_user_fs.get_workspace(wid)
    bw = bob_user_fs.get_workspace(wid)
    aw_stat = await aw.path_info("/")
    bw_stat = await bw.path_info("/")
    assert aw_stat == bw_stat

@pytest.mark.trio
async def test_share_workspace_then_rename_it(
    running_backend, alice_user_fs, bob_user_fs, alice, bob
):
    # Share a workspace between Alice and Bob
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w")
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)
    with freeze_time("2000-01-03"):
        await bob_user_fs.process_last_messages()

    # Now Bob and alice both rename the workpsace for there own taste
    await bob_user_fs.workspace_rename(wid, "from_alice")
    await alice_user_fs.workspace_rename(wid, "to_bob")

    await bob_user_fs.sync()
    await alice_user_fs.sync()

    # This should have not changed the workspace in any way
    bw = bob_user_fs.get_workspace(wid)
    aw = alice_user_fs.get_workspace(wid)
    await bw.touch("/ping_bob.txt")
    await aw.mkdir("/ping_alice")

    await bw.sync()
    await aw.sync()
    await bw.sync()

    aw_stat = await aw.path_info("/")
    bw_stat = await bw.path_info("/")
    assert aw_stat == bw_stat
    assert aw_stat["id"] == wid

@pytest.mark.trio
async def test_unshare_ok(running_backend, alice_user_fs, bob_user_fs, alice, bob):
    # Share a workspace...
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.OWNER)
    await bob_user_fs.process_last_messages()

    # ...and unshare it
    await bob_user_fs.workspace_share(wid, alice.user_id, None)
    with alice_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-03"):
            await alice_user_fs.process_last_messages()
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {
            "new_entry": WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 3),
                role=None,
            ),
            "previous_entry": WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 2),
                role=WorkspaceRole.OWNER,
            ),
        },
    )

    aum = alice_user_fs.get_user_manifest()
    aw = aum.workspaces[0]
    assert not aw.role

    # TODO: check workspace access is no longer possible

@pytest.mark.trio
async def test_unshare_not_shared(running_backend, alice_user_fs, bob_user_fs, alice, bob):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
    await alice_user_fs.workspace_share(wid, bob.user_id, None)
    with alice_user_fs.event_bus.listen() as spy:
        await bob_user_fs.process_last_messages()
    assert not spy.events

    # Workspace unsharing should have been ignored
    bum = bob_user_fs.get_user_manifest()
    assert not bum.workspaces

@pytest.mark.trio
async def test_share_to_another_after_beeing_unshared(
    running_backend, alice_user_fs, bob_user_fs, alice, bob
):
    # Share a workspace...
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)
    await bob_user_fs.process_last_messages()

    # ...and unshare it
    await alice_user_fs.workspace_share(wid, bob.user_id, None)
    await bob_user_fs.process_last_messages()

    # Shouldn't be able to share the workspace anymore
    with pytest.raises(FSSharingNotAllowedError):
        await bob_user_fs.workspace_share(wid, alice.user_id, None)

@pytest.mark.trio
async def test_reshare_workspace(running_backend, alice_user_fs, bob_user_fs, alice, bob):
    # Share a workspace...
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)
    with freeze_time("2000-01-03"):
        await bob_user_fs.process_last_messages()

    # ...and unshare it...
    await alice_user_fs.workspace_share(wid, bob.user_id, None)
    with freeze_time("2000-01-04"):
        await bob_user_fs.process_last_messages()

    # ...and re-share it !
    await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)
    with bob_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-05"):
            await bob_user_fs.process_last_messages()
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {
            "new_entry": WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 5),
                role=WorkspaceRole.MANAGER,
            ),
            "previous_entry": WorkspaceEntry(
                name="w1",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 2),
                role_cached_on=datetime(2000, 1, 4),
                role=None,
            ),
        },
    )

    # Check access
    aum = alice_user_fs.get_user_manifest()
    bum = bob_user_fs.get_user_manifest()
    assert len(aum.workspaces) == 1
    assert len(bum.workspaces) == 1
    aw = aum.workspaces[0]
    bw = bum.workspaces[0]

    assert bw.name == "w1"
    assert bw.id == aw.id
    assert bw.role == WorkspaceRole.MANAGER

@pytest.mark.trio
async def test_share_with_different_role(running_backend, alice_user_fs, bob_user_fs, alice, bob):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
    aum = alice_user_fs.get_user_manifest()
    aw = aum.workspaces[0]

    previous_entry = None
    for role in WorkspaceRole:
        # (re)share with rights
        await alice_user_fs.workspace_share(wid, bob.user_id, role)
        with bob_user_fs.event_bus.listen() as spy:
            await bob_user_fs.process_last_messages()
        new_entry = spy.partial_obj(WorkspaceEntry, name="w1", id=wid, role=role)
        if not previous_entry:
            spy.assert_event_occured(
                ClientEvent.SHARING_UPDATED, {"new_entry": new_entry, "previous_entry": None}
            )

        else:
            spy.assert_event_occured(
                ClientEvent.SHARING_UPDATED,
                {"new_entry": new_entry, "previous_entry": previous_entry},
            )
        previous_entry = new_entry

        # Check access
        bum = bob_user_fs.get_user_manifest()
        assert len(bum.workspaces) == 1
        bw = bum.workspaces[0]

        assert bw.name == "w1"
        assert bw.id == aw.id
        assert bw.role == role

@pytest.mark.trio
async def test_share_no_manager_right(running_backend, alice_user_fs, alice, bob):
    with freeze_time("2000-01-02"):
        wid = await alice_user_fs.workspace_create("w1")
        await alice_user_fs.sync()

    # Drop manager right (and give to Bob the ownership)
    await running_backend.backend.realm.update_roles(
        alice.organization_id,
        RealmGrantedRole(
            realm_id=wid,
            user_id=bob.user_id,
            certificate=b"<dummy>",
            role=RealmRole.OWNER,
            granted_by=alice.device_id,
            granted_on=datetime(2000, 1, 3),
        ),
    )
    await running_backend.backend.realm.update_roles(
        alice.organization_id,
        RealmGrantedRole(
            realm_id=wid,
            user_id=alice.user_id,
            certificate=b"<dummy>",
            role=RealmRole.CONTRIBUTOR,
            granted_by=bob.device_id,
            granted_on=datetime(2000, 1, 4),
        ),
    )

    with pytest.raises(FSSharingNotAllowedError) as exc:
        await alice_user_fs.workspace_share(wid, bob.user_id, WorkspaceRole.MANAGER)
    assert (
        exc.value.message
        == "Must be Owner or Manager on the workspace is mandatory to share it: {'status': 'not_allowed'}"
    )

@pytest.mark.trio
async def test_share_with_sharing_name_already_taken(
    running_backend, alice_user_fs, bob_user_fs, alice, bob
):
    # Bob and Alice both has a workspace with similar name
    with freeze_time("2000-01-01"):
        awid = await alice_user_fs.workspace_create("w")
        bwid = await bob_user_fs.workspace_create("w")
        bw2id = await bob_user_fs.workspace_create("w")

    # Sharing them shouldn't be a trouble
    await bob_user_fs.sync()
    await alice_user_fs.workspace_share(awid, bob.user_id, WorkspaceRole.MANAGER)

    # Bob should get a notification
    with bob_user_fs.event_bus.listen() as spy:
        with freeze_time("2000-01-02"):
            await bob_user_fs.process_last_messages()
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {
            "new_entry": WorkspaceEntry(
                name="w",
                id=awid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 1),
                role_cached_on=datetime(2000, 1, 2),
                role=WorkspaceRole.MANAGER,
            ),
            "previous_entry": None,
        },
    )

    assert len(bob_user_fs.get_user_manifest().workspaces) == 3

    b_aw_stat = await bob_user_fs.get_workspace(awid).path_info("/")
    a_aw_stat = await alice_user_fs.get_workspace(awid).path_info("/")
    b_aw_stat.pop("need_sync")
    a_aw_stat.pop("need_sync")
    assert b_aw_stat == a_aw_stat

    b_bw_stat = await bob_user_fs.get_workspace(bwid).path_info("/")
    assert b_bw_stat["id"] == bwid
    b_bw2_stat = await bob_user_fs.get_workspace(bw2id).path_info("/")
    assert b_bw2_stat["id"] == bw2id

@pytest.mark.trio
@pytest.mark.parametrize("first_to_sync", ("alice", "alice2"))
async def test_share_workspace_then_conflict_on_rights(
    running_backend, alice_user_fs, alice2_user_fs, bob_user_fs, alice, alice2, bob, first_to_sync
):
    # Bob shares a workspace with Alice...
    with freeze_time("2000-01-01"):
        wid = await bob_user_fs.workspace_create("w")
    with freeze_time("2000-01-02"):
        await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.MANAGER)

    # ...but only Alice's first device get the information
    with freeze_time("2000-01-03"):
        await alice_user_fs.process_last_messages()

    # Now Bob change the sharing rights...
    with freeze_time("2000-01-04"):
        await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.CONTRIBUTOR)

    # ...this time it's Alice's second device which get the info
    with freeze_time("2000-01-05"):
        # Note we will process the 2 sharing messages bob sent us, this
        # will attribute role_cached_on to the first message timestamp even
        # if we cache the second message role...
        await alice2_user_fs.process_last_messages()

    if first_to_sync == "alice":
        first = alice_user_fs
        second = alice2_user_fs
        synced_timestamp = datetime(2000, 1, 7)
        synced_version = 3
    else:
        first = alice2_user_fs
        second = alice_user_fs
        synced_timestamp = datetime(2000, 1, 6)
        synced_version = 2

    # Finally Alice devices try to reconciliate
    with freeze_time("2000-01-06"):
        await first.sync()
    with freeze_time("2000-01-07"):
        await second.sync()
    # Resync first device to get changes from the 2nd
    with freeze_time("2000-01-08"):
        await first.sync()

    am = alice_user_fs.get_user_manifest()
    a2m = alice2_user_fs.get_user_manifest()
    expected_remote = UserManifest(
        author=alice2.device_id,
        timestamp=synced_timestamp,
        id=alice2.user_manifest_id,
        version=synced_version,
        created=datetime(2000, 1, 1),
        updated=datetime(2000, 1, 5),
        last_processed_message=2,
        workspaces=(
            WorkspaceEntry(
                name="w",
                id=wid,
                key=ANY,
                encryption_revision=1,
                encrypted_on=datetime(2000, 1, 1),
                role_cached_on=datetime(2000, 1, 5),
                role=WorkspaceRole.CONTRIBUTOR,
            ),
        ),
    )
    expected = LocalUserManifest(
        base=expected_remote,
        need_sync=False,
        updated=expected_remote.updated,
        last_processed_message=expected_remote.last_processed_message,
        workspaces=expected_remote.workspaces,
    )
    assert am == expected
    assert a2m == expected

    a_w = alice_user_fs.get_workspace(wid)
    a2_w = alice2_user_fs.get_workspace(wid)

    a_w_stat = await a_w.path_info("/")
    a2_w_stat = await a2_w.path_info("/")

    a_w_entry = a_w.get_workspace_entry()
    a2_w_entry = a2_w.get_workspace_entry()

    assert a_w_stat == {
        "type": "folder",
        "is_placeholder": False,
        "id": wid,
        "created": ANY,
        "updated": ANY,
        "base_version": 1,
        "need_sync": False,
        "children": [],
        "confined": False,
    }
    assert a_w_stat == a2_w_stat

    assert a_w_entry == WorkspaceEntry(
        name="w",
        id=wid,
        key=ANY,
        encryption_revision=1,
        encrypted_on=datetime(2000, 1, 1),
        role_cached_on=datetime(2000, 1, 5),
        role=WorkspaceRole.CONTRIBUTOR,
    )
    assert a2_w_entry == a_w_entry

@pytest.mark.trio
async def test_sharing_events_triggered_on_sync(
    running_backend, alice_user_fs, alice2_user_fs, bob_user_fs, alice, bob
):
    # Share a first workspace
    with freeze_time("2000-01-02"):
        wid = await create_shared_workspace("w", bob_user_fs, alice_user_fs)

    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()
    expected_entry_v1 = WorkspaceEntry(
        name="w",
        id=wid,
        key=ANY,
        encryption_revision=1,
        encrypted_on=datetime(2000, 1, 2),
        role_cached_on=datetime(2000, 1, 2),
        role=WorkspaceRole.MANAGER,
    )
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED, {"new_entry": expected_entry_v1, "previous_entry": None}
    )

    # Change role
    await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.OWNER)
    with freeze_time("2000-01-03"):
        await alice_user_fs.process_last_messages()
    await alice_user_fs.sync()

    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()
    expected_entry_v2 = WorkspaceEntry(
        name="w",
        id=wid,
        key=ANY,
        encryption_revision=1,
        encrypted_on=datetime(2000, 1, 2),
        role_cached_on=datetime(2000, 1, 3),
        role=WorkspaceRole.OWNER,
    )
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {"new_entry": expected_entry_v2, "previous_entry": expected_entry_v1},
    )

    # Revoke
    await bob_user_fs.workspace_share(wid, alice.user_id, None)
    with freeze_time("2000-01-04"):
        await alice_user_fs.process_last_messages()
    await alice_user_fs.sync()

    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()
    expected_entry_v3 = WorkspaceEntry(
        name="w",
        id=wid,
        key=ANY,
        encryption_revision=1,
        encrypted_on=datetime(2000, 1, 2),
        role_cached_on=datetime(2000, 1, 4),
        role=None,
    )
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {"new_entry": expected_entry_v3, "previous_entry": expected_entry_v2},
    )

@pytest.mark.trio
async def test_no_sharing_event_on_sync_on_unknown_workspace(
    running_backend, alice_user_fs, alice2_user_fs, bob_user_fs, alice, bob
):
    # Share a workspace...
    wid = await create_shared_workspace("w", bob_user_fs, alice_user_fs)

    # ...and unshare it before alice2 even know about it
    await bob_user_fs.workspace_share(wid, alice.user_id, None)
    await alice_user_fs.process_last_messages()
    await alice_user_fs.sync()

    # No sharing event should be triggered !
    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()
    spy.assert_events_exactly_occured([ClientEvent.FS_ENTRY_REMOTE_CHANGED])

@pytest.mark.trio
async def test_sharing_event_on_sync_if_same_role(
    running_backend, alice_user_fs, alice2_user_fs, bob_user_fs, alice, bob
):
    # Share a workspace, alice2 knows about it
    with freeze_time("2000-01-02"):
        wid = await create_shared_workspace("w", bob_user_fs, alice_user_fs, alice2_user_fs)
    expected_entry_v1 = WorkspaceEntry(
        name="w",
        id=wid,
        key=ANY,
        encryption_revision=1,
        encrypted_on=datetime(2000, 1, 2),
        role_cached_on=datetime(2000, 1, 2),
        role=WorkspaceRole.MANAGER,
    )

    # Then change alice's role...
    await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.OWNER)
    with freeze_time("2000-01-03"):
        await alice_user_fs.process_last_messages()
    await alice_user_fs.sync()

    # ...and give back alice the same role
    await bob_user_fs.workspace_share(wid, alice.user_id, WorkspaceRole.MANAGER)
    with freeze_time("2000-01-04"):
        await alice_user_fs.process_last_messages()
    expected_entry_v3 = expected_entry_v1.evolve(role_cached_on=datetime(2000, 1, 4))
    await alice_user_fs.sync()

    # A single sharing event should be triggered
    with alice2_user_fs.event_bus.listen() as spy:
        await alice2_user_fs.sync()
    spy.assert_event_occured(
        ClientEvent.SHARING_UPDATED,
        {"new_entry": expected_entry_v3, "previous_entry": expected_entry_v1},
    )


import mne
import numpy as np
import pandas as pd
from mne.beamformer import make_lcmv, apply_lcmv, apply_lcmv_cov
from scipy.stats import pearsonr

import config
from config import fname, lcmv_settings
from time_series import simulate_raw, create_epochs

# Don't be verbose
mne.set_log_level(False)

fn_stc_signal = fname.stc_signal(vertex=config.vertex)
fn_simulated_raw = fname.simulated_raw(vertex=config.vertex)
fn_simulated_epochs = fname.simulated_epochs(vertex=config.vertex)

# fn_report_h5 = fname.report(vertex=config.vertex)
fn_report_h5 = None  # Don't produce a report

###############################################################################
# Simulate raw data and create epochs
###############################################################################

print('simulate data')
info = mne.io.read_info(fname.sample_raw)
info = mne.pick_info(info, mne.pick_types(info, meg=True, eeg=False))
fwd_disc_true = mne.read_forward_solution(fname.fwd_discrete_true)
fwd_disc_true = mne.pick_types_forward(fwd_disc_true, meg=True, eeg=False)
er_raw = mne.io.read_raw_fif(fname.ernoise, preload=True)

raw, stc_signal = simulate_raw(info=info, fwd_disc_true=fwd_disc_true, signal_vertex=config.vertex,
                               signal_freq=config.signal_freq, n_trials=config.n_trials,
                               noise_multiplier=config.noise, random_state=config.random,
                               n_noise_dipoles=config.n_noise_dipoles_vol, er_raw=er_raw)

true_ori = fwd_disc_true['src'][0]['nn'][config.vertex]

# del info, fwd_disc_true, er_raw

epochs = create_epochs(raw)

###############################################################################
# Sensor-level analysis
###############################################################################

epochs_grad = epochs.copy().pick_types(meg='grad')
epochs_mag = epochs.copy().pick_types(meg='mag')
epochs_joint = epochs.copy().pick_types(meg=True)

# Make cov matrices
cov = mne.compute_covariance(epochs, tmin=-1, tmax=1, method='empirical')
signal_cov = mne.compute_covariance(epochs, tmin=0, tmax=1, method='empirical')
noise_cov = mne.compute_covariance(epochs, tmin=-1, tmax=0, method='empirical')

# Compute evokeds
evoked_grad = epochs_grad.average()
evoked_mag = epochs_mag.average()
evoked_joint = epochs_joint.average()

###############################################################################
# Compute LCMV beamformer results
###############################################################################

# Read in forward solution
fwd_disc_man = mne.read_forward_solution(fname.fwd_discrete_man)

dists = []
focs = []
corrs = []
ori_errors = []
for setting in lcmv_settings:
    reg, sensor_type, pick_ori, inversion, weight_norm, normalize_fwd, use_noise_cov, reduce_rank, project_pca = setting
    try:
        if sensor_type == 'grad':
            evoked = evoked_grad
        elif sensor_type == 'mag':
            evoked = evoked_mag
        elif sensor_type == 'joint':
            evoked = evoked_joint
        else:
            raise ValueError('Invalid sensor type: %s', sensor_type)

        if project_pca and pick_ori != 'vector':
            raise NotImplementedError('project_pca=True only makes sense when pick_ori="vector"')

        filters = make_lcmv(evoked.info, fwd_disc_man,
                            cov if use_noise_cov else signal_cov,
                            reg=reg,
                            pick_ori=pick_ori, weight_norm=weight_norm,
                            inversion=inversion,
                            depth=1. if normalize_fwd else None,
                            noise_cov=noise_cov if use_noise_cov else None,
                            reduce_rank=reduce_rank)

        stc_est = apply_lcmv(evoked, filters).crop(0.001, 1)

        if pick_ori == 'vector':
            # Combine vector time source
            if project_pca:
                stc_proj, _ = stc_est.project('pca', fwd_disc_man['src'])
            else:
                stc_proj = stc_est.magnitude()
            stc_est_power = (stc_proj ** 2).sum()
            peak_vertex, peak_time = stc_est_power.get_peak(vert_as_index=True, time_as_index=True)
            estimated_time_course = np.abs(stc_proj.data[peak_vertex])
        else:
            stc_est_power = (stc_est ** 2).sum()
            peak_vertex, peak_time = stc_est_power.get_peak(vert_as_index=True, time_as_index=True)
            estimated_time_course = np.abs(stc_est.data[peak_vertex])

        # Compute distance between true and estimated source locations
        pos_est = fwd_disc_man['source_rr'][peak_vertex]
        pos_true = fwd_disc_man['source_rr'][config.vertex]
        dist = np.linalg.norm(pos_est - pos_true)

        # Ratio between estimated peak activity and all estimated activity.
        focality_score = stc_est_power.data[peak_vertex, 0] / stc_est_power.data.sum()

        # Correlation between true and reconstructed timecourse
        true_time_course = stc_signal.copy().crop(0, 1).data[0]
        corr = pearsonr(np.abs(true_time_course), estimated_time_course)[0]

        # Angle between estimated and true source orientation
        if pick_ori == 'max-power':
            estimated_ori = filters['max_power_ori'][config.vertex]
            ori_error = np.rad2deg(np.arccos(estimated_ori @ true_ori))
            if ori_error > 90:
                ori_error = 180 - ori_error
        elif pick_ori == 'vector':
            estimated_ori = stc_est.data[peak_vertex, :, peak_time]
            estimated_ori /= np.linalg.norm(estimated_ori)
            ori_error = np.rad2deg(np.arccos(estimated_ori @ true_ori))
            if ori_error > 90:
                ori_error = 180 - ori_error
        else:
            ori_error = np.nan
    except Exception as e:
        print(e)
        dist = np.nan
        focality_score = np.nan
        corr = np.nan
        ori_error = np.nan
    print(setting, dist, focality_score, corr, ori_error)

    dists.append(dist)
    focs.append(focality_score)
    corrs.append(corr)
    ori_errors.append(ori_error)

###############################################################################
# Save everything to a pandas dataframe
###############################################################################

df = pd.DataFrame(lcmv_settings,
                  columns=['reg', 'sensor_type', 'pick_ori', 'inversion',
                           'weight_norm', 'normalize_fwd', 'use_noise_cov',
                           'reduce_rank', 'project_pca'])
df['dist'] = dists
df['focality'] = focs
df['corr'] = corrs
df['ori_error'] = ori_errors

df.to_csv(fname.lcmv_results(vertex=config.vertex, noise=config.noise))
print('OK!')


import nltk
from collections import Counter

nltk.download('vader_lexicon')

from nltk.sentiment import SentimentIntensityAnalyzer

#Зчитуємо файл який дали в завданні
filename = "data.csv"
with open(filename, 'r') as f:
    reviews = f.readlines()

# ініціалізуємо SentimentIntensityAnalyzer (бібліотека для визначення настроїв)
sia = SentimentIntensityAnalyzer()

# рахуємо загальний настрій відгуків
compound_scores = [sia.polarity_scores(review)['compound'] for review in reviews]
overall_sentiment = sum(compound_scores) / len(compound_scores)

# класифікуємо відгуки на позитивні, негативні та нейтральні (рахує всі відгуки пропускаючи ті де немає числового значення в колонці "Stars"
positive_reviews = [review for review in reviews if sia.polarity_scores(review)['compound'] > 0]
negative_reviews = [review for review in reviews if sia.polarity_scores(review)['compound'] < 0]
neutral_reviews = [review for review in reviews if sia.polarity_scores(review)['compound'] == 0]

#positive_reviews = [review for review in reviews if review.strip() and int(review.split('Stars \n')[0]) >= 4]
#negative_reviews = [review for review in reviews if review.strip() and int(review.split('Stars \n')[0]) <= 2]
#neutral_reviews = [review for review in reviews if review.strip() and int(review.split('Stars \n')[0]) == 3]

# рахуємо кількість повторюваних слів
word_count = Counter(word for review in reviews for word in review.split())
most_common_words = word_count.most_common(5)

num_positive = len(positive_reviews)
num_negative = len(negative_reviews)
num_neutral = len(neutral_reviews)

with open('report.txt', 'w') as file:
    file.write('\n Аналіз відгуків:\n')
    file.write(f"Загальний настрій відгуків: ({overall_sentiment}):\n")
    file.write(f"Позитивні: ({len(positive_reviews)}):\n")
    file.write(f"Негативні: ({len(negative_reviews)}):\n")
    file.write(f"Нейтральні: ({len(neutral_reviews)}):\n")

with open('repeating words.txt', 'w') as file:
    file.write("\n П'ять найбільш вживаних слів: \n")
    for word, count in most_common_words:
        file.write(f"{word}: {count}\n")

    file.write("Кількість повторюваних слів: \n")
    for word, count in word_count.items():
        file.write(f"{word}: {count}\n")

# Для перевірки
print("Аналіз настроїв:")
print("Загальний настрій відгуків: {:.2f}".format(overall_sentiment))
print("")

print("Аналіз негативних, позитивних і природних відгуків:")
print("Кількість позитивних відгуків: {}".format(num_positive))
print("Кількість негативних відгуків: {}".format(num_negative))
print("Кількість нейтральних відгуків: {}".format(num_neutral))
print("")


from test_framework import mininode
from test_framework.test_framework import BitcoinTestFramework
from test_framework.util import *
import time
from test_framework.blocktools import create_block, create_coinbase

class BsvProtoconfViolationTest(BitcoinTestFramework):

    def add_options(self, parser):
        parser.add_option("--testbinary", dest="testbinary",
                          default=os.getenv("BITCOIND", "bitcoind"),
                          help="bitcoind binary to test")

    def set_test_params(self):
        self.setup_clean_chain = True
        self.num_nodes = 1

    def setup_network(self):
        self.setup_nodes()

    def run_test(self):
        test_node = mininode.NodeConnCB()

        connections = []
        connections.append(
            mininode.NodeConn('127.0.0.1', p2p_port(0), self.nodes[0], test_node))

        test_node.add_connection(connections[0])

        mininode.NetworkThread().start()  # Start up network handling in another thread

        # 1. Test that protoconf is sent after verack
        test_node.wait_for_verack()
        test_node.wait_for_protoconf()

        logger.info("Received time of verack: {} ".format(test_node.msg_timestamp["verack"]))
        logger.info("Received time of protoconf: {} ".format(test_node.msg_timestamp["protoconf"]))

        logger.info("Received msg_index of verack: {} ".format(test_node.msg_index["verack"]))
        logger.info("Received msg_index of protoconf: {} ".format(test_node.msg_index["protoconf"]))

        assert_greater_than(test_node.msg_index["protoconf"], test_node.msg_index["verack"])

        # 2. Test that protoconf can only be sent once (if is sent twice --> disconnection)
        assert_equal(len(self.nodes[0].listbanned()), 0)# Before, there are zero banned node

        # First protoconf was already sent from mininode.
        # Another protoconf message will cause disconnection (but not banning).
        test_node.send_message(mininode.msg_protoconf())
        test_node.wait_for_disconnect()

        assert(self.nodes[0].closed) # disconnected
        assert_equal(len(self.nodes[0].listbanned()), 0) # After, there are also zero banned node

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


#PYTHON CAMERA MODEL
import cv2
import numpy as np
i=0

def capturing(event,x,y,flags,param):
    global i
    if event==cv2.EVENT_LBUTTONUP:
        name="photo_"+str(i)+".png"
        wname="CAPTURED IMAGE"
        cv2.imwrite(name,frame)
        h=cv2.imread(name)
        cv2.namedWindow(wname)
        cv2.imshow(wname,h)
        cv2.moveWindow(wname,700,50)
        i+=1
        cv2.waitKey(1000)
        cv2.destroyWindow(wname)

cap=cv2.VideoCapture(0)
while True:
    ret,frame = cap.read()
    win="CAPTURE"
    cv2.imshow("CAMERA",frame)
    cv2.moveWindow("CAMERA",50,50)
    cv2.namedWindow(win)
    img=np.zeros((150,150,3))
    cv2.putText(img,"CLICK",(35,65),cv2.FONT_HERSHEY_SIMPLEX,0.85,(255,255,255),2,cv2.LINE_AA)
    cv2.putText(img,"HERE",(35,90),cv2.FONT_HERSHEY_SIMPLEX,0.85,(255,255,255),2,cv2.LINE_AA)
    cv2.imshow(win,img)
    cv2.moveWindow(win,250,560)
    cv2.setMouseCallback(win,capturing)
    if cv2.waitKey(1)==13:
        break
cap.release()
cv2.destroyAllWindows()


from api.models import EventTypes

# temp models

class GithubBodyModel(object):
    def __init__(self):
        self.type = ''
        self.preferred_labels = {}
        self.alternative_labels = []
        self.broader_labels = []
        self.narrower_labels = []
        self.related_labels = []
        self.exact_matches = []
        self.needed_for = None
        self.description = None
        self.reason = None
        self.scope_note = None
        self.groups = []
        self.organization = None
        self.yse_term = None

class GithubMeetingModel(object):
    def __init__(self, name, created_date, meeting_date):
        self.name = name
        self.created_date = created_date
        self.meeting_date = meeting_date

class GithubIssueModel(object):
    def __init__(self, name, status, meeting, created, modified, closed, body):
        self.name = name
        self.status = status
        self.meeting = meeting
        self.created = created
        self.modified = modified
        self.closed = closed
        self.body = body
        self.tags = []
        self.events = []
        self.comments = []

class GithubCommentModel(object):
    def __init__(self, created, modified, text):
        self.created = created
        self.modified = modified
        self.user_id = None
        self.event_type = EventTypes.COMMENT
        self.suggestion_id = None
        self.text = text


"""
ID: jasonhu5
LANG: PYTHON3
TASK: transform
"""

def reflect(ar):
    n = len(ar)
    res = ar.copy()
    for row in range(n):
        res[row] = res[row][::-1]
    return res

def solve(ar1, ar2):
    def rot_cw_90(A, B):
        for row in range(n):
            for col in range(n):
                if A[row][col] != B[col][n-1-row]:
                    return False
        return True

    def rot_cw_180(A, B):
        for row in range(n):
            for col in range(n):
                if A[row][col] != B[n-1-row][n-1-col]:
                    return False
        return True

    def rot_cw_270(A, B):
        for row in range(n):
            for col in range(n):
                if A[row][col] != B[n-1-col][row]:
                    return False
        return True

    def flipped(A, B):
        return reflect(A) == B

    def combination(A, B):
        flipped = reflect(A)
        return rot_cw_90(flipped, B) or rot_cw_180(flipped, B) or rot_cw_270(flipped, B)

    n = len(ar1)
    if rot_cw_90(ar1, ar2):
        return 1
    if rot_cw_180(ar1, ar2):
        return 2
    if rot_cw_270(ar1, ar2):
        return 3
    if flipped(ar1, ar2):
        return 4
    if combination(ar1, ar2):
        return 5
    if ar1 == ar2:
        return 6
    return 7

# ---- Unit Tests ----
def test_flip():
    # Flip
    assert reflect(['@-@','---','@@-']) == ['@-@','---','-@@']
    assert reflect(['*-@-','@---','-@@-','**0-']) == ['-@-*','---@','-@@-', '-0**']

def test_90():
    ar1 = ['@-@','---','@@-']
    ar2 = ['@-@','@--','--@']
    assert solve(ar1, ar2) == 1

def test_180():
    ar1 = ['@-@','---','@@-']
    ar2 = ['-@@','---','@-@']
    assert solve(ar1, ar2) == 2

def test_270():
    ar1 = ['@-@','---','@@-']
    ar2 = ['@--','--@','@-@']
    assert solve(ar1, ar2) == 3

def test_flipped():
    ar1 = ['@-@','---','@@-']
    ar2 = ['@-@','---','-@@']
    assert solve(ar1, ar2) == 4

def test_combination():
    ar1 = ['@-@','---','@@-']
    ar2 = ['--@','@--','@-@']
    assert solve(ar1, ar2) == 5

def test_no_change():
    ar1 = ['@-@','---','@@-']
    ar2 = ['@-@','---','@@-']
    assert solve(ar1, ar2) == 6

def test_invalid():
    ar1 = ['@-@','---','@@-']
    ar2 = ['-@-','-@-','-@-']
    assert solve(ar1, ar2) == 7

if __name__ == '__main__':
    # test_flip()
    # test_90()
    # test_180()
    # test_270()
    # test_flipped()
    # test_combination()
    # test_no_change()
    # test_invalid()
    fin = open('transform.in','r')
    fout = open('transform.out','w')
    N = int(fin.readline())
    ar1, ar2 = [], []
    for _ in range(N):
        s = fin.readline().strip()
        ar1.append(s)
    for _ in range(N):
        s = fin.readline().strip()
        ar2.append(s)
    ans = solve(ar1, ar2)
    fout.write('{}\n'.format(ans))


from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.support.ui import Select
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support import ui
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from collections import defaultdict
import time
import datetime
import csv
import unicodedata
import re
import hashlib
import os
from selenium.common.exceptions import ElementNotVisibleException

options = webdriver.ChromeOptions()
options.add_argument("--start-maximized")
driver = webdriver.Chrome(chrome_options=options)
actions = ActionChains(driver)
today =datetime.date.today()
def check_exists_by_xpath(xpath):
    try:
        while (driver.find_element_by_xpath("%s"%(xpath,))) :
        	driver.find_element_by_xpath("%s"%(xpath,)).click()
        	time.sleep(5)
    except ElementNotVisibleException:
        print ("element not found")

wait = ui.WebDriverWait(driver, 10)
driver.get('http://www.cwtv.com/shows/')
print(driver.current_url)
time.sleep(8)
(driver.page_source).encode('ascii','ignore')
shows_count =driver.find_elements_by_xpath(".//*[@id='cw-main-footer-1']/div[1]/ul/li/a")
print ("Shows count :[%s]"%(len(shows_count)),)
launch_id =[]
service_videos = {}
href =[]
release_year=0
multiples =1
for s in range (len(shows_count)):
	href.append(shows_count[s].get_attribute('href'))
print (href)
for h in range (len(href)):
	try:
		print (h)
		driver.get (href[h])
		episodes=driver.find_elements_by_xpath(".//*[@id='list_1']/div//li//a")
		multiples= len(episodes)/5
		print (multiples)
		for m in range (multiples) :
			for e in range (len(episodes)):
				print (len(episodes), e+1, m+1)
				if e+1==(5*(m+1)) :
					driver.find_element_by_xpath(".//*[contains(@id,'touchcarousel_1')]/button[2]").click()
					time.sleep  (3)
				epi_href =episodes[e].get_attribute('href')
				video_id =epi_href.split("=")[-1].encode('ascii', 'ignore')
				epi_details =driver.find_element_by_xpath("(.//*[@id='list_1']/div//li//a//div[contains(@class,'videodetails')]/p[1])[%s]"%(e+1)).text.encode('ascii', 'ignore')
				epi_title =epi_details.split("Ep.")[0].split("(")[0].strip()
				epi_sea_num =epi_details.split("Ep.")[1].split(")")[0]
				print (epi_details, epi_title, epi_sea_num)
				if (len (epi_sea_num) == 3) :
					epi_num=epi_details.split("Ep.")[1].split(")")[0][-2:]
					season_num =epi_details.split("Ep.")[1].split(")")[0][0]
				elif (len (epi_sea_num) == 4) :
					epi_num=epi_details.split("Ep.")[1].split(")")[0][-2:]
					season_num =epi_details.split("Ep.")[1].split(")")[0][0:2]
				series_title =driver.find_element_by_xpath(".//*[@id='show-logo']/a").get_attribute('title').encode('ascii', 'ignore')
				launch_id.append(video_id)
				service_videos ["cwtv"] =launch_id

				res=[today, "CWTV Shows", series_title, season_num, epi_num, epi_title, service_videos]
				print (res)
				with open(os.getcwd()+'/'+"cwtv_shows_output"+ '.csv', 'ab+') as mycsvfile:
					thedatawriter =csv.writer(mycsvfile)
					thedatawriter.writerow(res)
					launch_id =[]
					service_videos = {}
	except Exception as e:
		print(e)
		continue


import tkinter
import os
from PIL import Image, ImageTk

class OngletsPersonnage():

    def __init__(self, main_onglets):
        self.onglets_personnage = tkinter.ttk.Frame(main_onglets)
        self.onglets_personnage.pack()
        main_onglets.add(self.onglets_personnage, text='character')

        self.create_canvas_character()

    def set_character(self, character):
        self.character = character

    def create_canvas_character(self):
        self.canvas_gfx_character = tkinter.Canvas(self.onglets_personnage)
        self.create_charater("0","None","nul","nul")
        self.canvas_gfx_character.place(relx=0.03, rely=0.1, relwidth=1, relheight=1)

        self.canvas_vita = tkinter.Canvas(self.onglets_personnage)
        self.print_image("stats\\vitaliter.png",self.canvas_vita)
        self.canvas_vita.place(relx=0.75, rely=0.05, relwidth=0.1, relheight=0.12)

        self.canvas_sagesse = tkinter.Canvas(self.onglets_personnage)
        self.print_image("stats\\sagesse.png",self.canvas_sagesse)
        self.canvas_sagesse.place(relx=0.75, rely=0.20, relwidth=0.1, relheight=0.12)

        self.canvas_force = tkinter.Canvas(self.onglets_personnage)
        self.print_image("stats\\force.png",self.canvas_force)
        self.canvas_force.place(relx=0.75, rely=0.35, relwidth=0.1, relheight=0.12)

        self.canvas_intel = tkinter.Canvas(self.onglets_personnage)
        self.print_image("stats\\intelligence.png",self.canvas_intel)
        self.canvas_intel.place(relx=0.75, rely=0.50, relwidth=0.1, relheight=0.12)

        self.canvas_chance = tkinter.Canvas(self.onglets_personnage)
        self.print_image("stats\\chance.png",self.canvas_chance)
        self.canvas_chance.place(relx=0.75, rely=0.65, relwidth=0.1, relheight=0.12)

        self.canvas_agi = tkinter.Canvas(self.onglets_personnage)
        self.print_image("stats\\agilite.png",self.canvas_agi)
        self.canvas_agi.place(relx=0.75, rely=0.80, relwidth=0.1, relheight=0.12)

    def create_label_caracteristique(self,character):
        self.label_vita =  tkinter.Label(self.onglets_personnage, text = character.vie_max)
        self.label_vita.place(relx=0.80, rely=0.05, relwidth=0.1, relheight=0.12)

        self.label_sagesse = tkinter.Label(self.onglets_personnage, text = character.sagesse)
        self.label_sagesse.place(relx=0.80, rely=0.20, relwidth=0.1, relheight=0.12)

        self.label_force = tkinter.Label(self.onglets_personnage, text = character.force)
        self.label_force.place(relx=0.80, rely=0.35, relwidth=0.1, relheight=0.12)

        self.label_intel = tkinter.Label(self.onglets_personnage, text = character.intel)
        self.label_intel.place(relx=0.80, rely=0.50, relwidth=0.1, relheight=0.12)

        self.label_chance = tkinter.Label(self.onglets_personnage, text = character.chance)
        self.label_chance.place(relx=0.80, rely=0.65, relwidth=0.1, relheight=0.12)

        self.label_agi = tkinter.Label(self.onglets_personnage, text = character.agi)
        self.label_agi.place(relx=0.80, rely=0.80, relwidth=0.1, relheight=0.12)

    def print_image(self,path,canvas_):
        dir_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),"resource\\" +path )
        image = Image.open(dir_path)
        photo = ImageTk.PhotoImage(image)
        canvas_.create_image(photo.width(),photo.height(),image=photo)
        canvas_.image = photo

    def create_charater(self,gfx,speudo ,id_,lvl = ""):
        dir_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))),f"resource\\gfx\\{gfx}.png")
        image = Image.open(dir_path)
        photo = ImageTk.PhotoImage(image)
        self.canvas_gfx_character.create_image(photo.width()/4.5,photo.height()/2,image=photo)
        self.canvas_gfx_character.image = photo
        self.canvas_gfx_character.place(relx=0.05, rely=0.9, relwidth=0.5, relheight=0.5)
        self.canvas_gfx_character.place(relx=0.03, rely=0.1, relwidth=1, relheight=1)

        speudo_and_id ="SPEUDO: "+ speudo +"        ID: "+ id_ + "        LEVEL: "+ lvl
        name = tkinter.Label(self.onglets_personnage, text = speudo_and_id)
        name.place(relx=0.01, rely=0.017,relwidth=0.4, relheight=0.09)


import mysql.connector
from mysql.connector import pooling

class Database:
    def __init__(self, config):
        self.config = config
        self.cnxpool = self.create_cnxpool()

    def create_cnxpool(self):
        try:
            cnxpool = pooling.MySQLConnectionPool(
                pool_name = "cnxpool",
                pool_size = 3,
                **self.config
            )
        except mysql.connector.Error as err:
            print(err)

        return cnxpool

    def close(self, cursor, cnx):
        cursor.close()
        cnx.close()

    def execute_sql(self, sql, sql_data, commit=False):
        try:
            cnx = self.cnxpool.get_connection()
            cursor = cnx.cursor(dictionary = True)

            cursor.execute(sql, sql_data)
            result = cursor.fetchall()

        except:
            cnx.rollback()

        finally:
            if commit is True:
                cnx.commit()
                self.close(cursor, cnx)
            else:
                self.close(cursor, cnx)

        return result


import os
from bson.json_util import dumps
from dotenv import load_dotenv
# from flask import jsonify
import pymongo

load_dotenv()  # use dotenv to hide sensitive credential as environment variables
DATABASE_URL = f'mongodb+srv://{os.environ.get("user")}:{os.environ.get("passwort")}' \
               '@flask-mongodb-atlas.wicsm.mongodb.net/' \
               'flaura?retryWrites=true&w=majority'  # get connection url from environment

client = pymongo.MongoClient(DATABASE_URL)  # establish connection with database

# plants.config['MONGO_DBNAME'] = 'restdb'
# plants.config['MONGO_URI'] = 'mongodb://localhost:27017/restdb'
# mongo = PyMongo(plants)

mydb = client.flaura
mycol = mydb.plants

def getPlantsByName(name):
    cursor = mycol.find({"name": {"$regex": '.*'+name+'.*', "$options": 'i'}})
    list_cur = list(cursor)
    plants = dumps(list_cur)
    return plants

def getAllPlants():
    cursor = mycol.find()
    list_cur = list(cursor)
    plantList = dumps(list_cur)
    return plantList

def setNewPlant(name, waterAmount, critMoist, sleepTime):
    newPlant = {"name": name, "waterAmountML": waterAmount, "criticalMoisture": critMoist, "sleepTime": sleepTime}
    mycol.insert_one(newPlant)

# function Get List of Plants that contain <name>
# function Get All Plants??
# function Add new Plant to DB


import pygame
from pygame.locals import *
from entities import User, Enemy
from fonctions import *
from stage import *
from hud import *

import random
import time
import zmq
import threading
from stage import *

from tkinter import *
from playsound import playsound

def choix1():
    global perso
    perso=1
    button1.configure(relief=SUNKEN)
    button2.configure(relief=RAISED)
    button3.configure(relief=RAISED)

def choix2():
    global perso
    perso=2
    button1.configure(relief=RAISED)
    button2.configure(relief=SUNKEN)
    button3.configure(relief=RAISED)

def choix3():
    global perso
    perso=3
    button1.configure(relief=RAISED)
    button2.configure(relief=RAISED)
    button3.configure(relief=SUNKEN)

perso=1
fen=Tk()
fen.geometry("250x300+200+0")
fen.configure(bg = "white")
user1=PhotoImage(file='images/user1.gif')
user2=PhotoImage(file='images/user2.gif')
user3=PhotoImage(file='images/user3.gif')
fen.title("LE JEU")
Label(fen,text="                ",bg="white").grid(row=1,column=0)
Label(fen,text="LE JEU \n\n ",bg="white").grid(row=0,column=2)
Button(fen,text="Jouer ",bg="white",command=fen.destroy).grid(row=1,column=2)
Label(fen,text="\n"*3,bg="white").grid(row=2,column=1)
button1=Button(fen, image=user1,bg="white",command=choix1, relief=SUNKEN)
button1.grid(row=3,column=1)
button2=Button(fen, image=user2,bg="white",command=choix2)
button2.grid(row=3,column=2)
button3=Button(fen, image=user3,bg="white",command=choix3)
button3.grid(row=3,column=3)
Label(fen,text="\n"*3,bg="white").grid(row=4,column=1)
Button(fen,text="Quitter",command=exit).grid(row=5,column=2)
jeu=0
playsound('musique_menu.mp3',block = False)

fen.mainloop()

gameOver = False
pygame.init()
screen = pygame.display.set_mode((620,480))
pygame.display.set_caption('User 1')
screen.fill((50,60,50))
pygame.display.update()

user = User(screen,1,perso)
coop = User(screen,2,3)
hud = HUD(screen)

context = zmq.Context()

usersChan = context.socket(zmq.PAIR)
usersChan.bind("tcp://127.0.0.1:1111".format(coop.id))

murs, enemies, potions, portes, eaus = classic(screen)

def recv(usersChan):
    global coop, gameOver, points
    while True:
        if gameOver == True:
            if points == 16:
                print("WIN !")
            else:
                print("Game Over ! Vous avez {} points".format(points))
            exit()
            return

        try:
            data = usersChan.recv_pyobj(flags=zmq.NOBLOCK)
            coop.pos = data["user"]["pos"]
            coop.vie = data["user"]["vie"]
            coop.attaque = data["user"]["attaque"]
            coop.defense = data["user"]["defense"]
            coop.level = data["user"]["level"]
            coop.xp= data["user"]["xp"]
            ### Supprimer objets qui ne sont pas en communs entre 2 listes python
            for potion in potions:
                ok = False
                for p in data["potions"]:
                    if potion.pos == p["pos"]:
                        ok = True
                if ok == False:
                    potions.remove(potion)

            for enemy in enemies:
                ok = False
                for e in data["enemies"]:
                    if enemy.pos == e["pos"]:
                        enemy.vie = e["vie"]
                        ok = True
                if ok == False:
                    enemies.remove(enemy)

            refresh()
        except zmq.ZMQError as err:
            pass

def refresh():

    screen.fill((50,60,50))
    hud.show(user,coop)
    user.show()
    coop.show()
    for enemy in enemies:
        enemy.show()
    for mur in murs:
        mur.show()
    for potion in potions:
        potion.show()
    for porte in portes:
        porte.show()
    for eau in eaus:
        eau.show()
    user.show()
    coop.show()
    pygame.display.flip()
    pygame.display.update()

# Envoyez première data
usersChan.send_pyobj(setData(user,coop,murs,potions,portes,eaus,enemies,True))
points = 0
# Création du Thread pour recevoir les données
threadRecv = threading.Thread(target=recv, args=(usersChan,))
threadRecv.start()

while not gameOver:
    changement = False
    if user.vie <= 0:
        gameOver = True

    if coop.vie <= 0:
        gameOver = True
    for event in pygame.event.get():

        # Alt + F4 ou fléche en haut
        if event.type == QUIT:
            gameOver = True

            # Si touche pressée
        if event.type == KEYDOWN:
            action = 1
            if event.key == K_UP:
                coord = (0,-1)
            elif event.key == K_DOWN:
                coord = (0,1)
            elif event.key == K_LEFT:
                coord = (-1,0)
            elif event.key == K_RIGHT:
                coord = (1,0)
            else:
                action = 0

            if action != 0:
                user.mouvement(coord)
                if user.pos == coop.pos:
                    user.mouvement((-coord[0],-coord[1]))

            for enemy in enemies:
                if enemy.pos == user.pos:
                    # Attaquer :
                    enemy.vie -= user.attaque + user.arme
                    user.vie -= enemy.defense

                    if user.vie <= 0:
                        user.vie = 0
                        gameOver == True

                    # print("Vie restante :", user.vie, "Vie enemmi :", enemy.vie)
                    if enemy.vie <= 0:
                        user.xp += enemy.level
                        enemies.remove(enemy)
                    # Revenir en arriére
                    else:
                        user.mouvement((-coord[0],-coord[1]))

            if user.xp >= user.level * 2:
                user.levelUP()

            for mur in murs:
                if mur.pos == user.pos :
                    if mur.genre == "lave":
                        user.vie -= 15
                    elif mur.genre == "pont":
                        pass
                    elif mur.genre == "levier":
                        pass
                    else:
                        user.mouvement((-coord[0],-coord[1]))

            for eau in eaus:
                if eau.pos == user.pos :
                    user.mouvement((-coord[0],-coord[1]))

            for potion in potions:
                if user.pos == potion.pos:
                    if potion.type == "heal":
                        user.heal()
                    elif potion.type == "atk":
                        user.atk()
                    elif potion.type == "atkboss":
                        for i in range (20):
                            user.atk()
                    elif potion.type == "xp":
                        user.levelUP()
                    potions.remove(potion)

            for porte in portes:
                if porte.pos == user.pos or porte.pos == coop.pos:
                    print("Changement de map")
                    points += 1
                    user.pos = [32,160]
                    coop.pos = [32,192]
                    if points == 1:
                        murs, enemies, potions, portes, eaus = deux(screen)
                    elif points == 2:
                        murs, enemies, potions, portes, eaus = troix(screen)
                    elif points == 15:
                        murs, enemies, potions, portes, eaus = six(screen)
                    elif points == 16:
                        gameOver = True
                    else:
                        murs, enemies, potions, portes, eaus = random.choice([quatre(screen), cinq(screen)])

                    changement = True

            ### Renvoyez les données
            try:
                message = setData(user,coop,murs,potions,portes,eaus,enemies,changement)
                usersChan.send_pyobj(message)

            except zmq.ZMQError as err:
                print ("Error while trying to send the value " + message + " : " + str(err))

            refresh()

        pygame.display.flip()
    pygame.display.update()
    pygame.time.wait(10)


"""
Main script: Autonomous Driving on Udacity Simulator
@author : nelsoonc

Undergraduate Thesis
Nelson Changgraini - Bandung Institute of Technology, Indonesia
"""

# Throttle 0 - 1 will produce speed 0 - 30 mph
# Steering -1 - 1 will produce angle -25 - 25 degrees

import os
import numpy as np
import socketio
import eventlet
from flask import Flask
import tensorflow as tf
from tensorflow.keras.models import load_model
import base64
from io import BytesIO
from PIL import Image
from train import rmse, get_lr_metric
from utils import preprocess
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'

# DIRECTORY PATH
MODEL_PATH = 'models/simulation_model.h5'

# VARIABLE
MAX_SPEED = 25

# FOR REAL TIME COMMUNICATION BETWEEN CLIENT AND SERVER
sio = socketio.Server()
# FLASK IS A MICRO WEB FRAMEWORK WRITTEN IN PYTHON
app = Flask(__name__)  # '__main__'

# Executing in graph mode
@tf.function
def predict(input_tensor, model):
    return model(input_tensor)

@sio.on('telemetry')
def telemetry(sid, data):
    speed = float(data['speed'])
    image = Image.open(BytesIO(base64.b64decode(data['image'])))
    image = np.asarray(image)
    image = preprocess(image)
    image = np.array([image])
    steering = float(predict(image, model))

    throttle = 1.0 - abs(steering) - speed / MAX_SPEED
    print('{}, {}, {}'.format(steering, throttle, speed))
    sendControl(steering, throttle)

@sio.on('connect')
def connect(sid, environ):
    print('Connected', sid)
    sendControl(0, 0)

@sio.on('disconnect')
def disconnect(sid):
    print('Disconnect', sid)

def sendControl(steering, throttle):
    sio.emit('steer', data={
        'steering_angle': steering.__str__(),
        'throttle': throttle.__str__()
    }, skip_sid=True)

if __name__ == '__main__':
    print('Setting up..')
    model = load_model(MODEL_PATH, custom_objects={'rmse': rmse, 'lr': get_lr_metric})
    if model:
        print('Model loaded')
    app = socketio.Middleware(sio, app)
    # LISTEN TO PORT 4567
    eventlet.wsgi.server(eventlet.listen(('', 4567)), app)


import os
import re
from typing import Tuple

from transformers import pipeline  # type: ignore

MODEL_PATH = os.environ.get("MODEL_PATH", "./distilbert-base-cased-distilled-squad")

class CardSourceGeneratorMock:
    def __call__(self, text: str, question: str) -> Tuple[int, int]:
        return 0, len(text) // 2

class CardSourceGenerator:
    def __init__(self) -> None:
        self._qa_model = pipeline(
            "question-answering", model=MODEL_PATH, tokenizer=MODEL_PATH
        )

    def __call__(self, text: str, question: str) -> Tuple[int, int]:
        answer = self._qa_model(question=question, context=text)  # type: ignore

        start, end = self._find_sentence_indices(text, answer["start"], answer["end"])
        return start, end

    def _find_sentence_indices(
        self, text: str, substring_start: int, substring_end: int
    ) -> Tuple[int, int]:
        """
        Finds the starting and ending indices of the sentence that contains the substring.
        """
        sentences = re.split(r"\n|(?<=[.!?])\s+", text)
        substring = text[substring_start:substring_end]

        for sentence in sentences:
            index = sentence.lower().find(substring.lower())
            if index != -1:
                start = text.index(sentence)
                end = start + len(sentence)
                return start, end

        return substring_start, substring_end


# -*- coding: utf-8 -*-
"""
Spyderエディタ

これは一時的なスクリプトファイルです
"""

#WEBクローリング
import time
import re

import requests
import lxml.html
from pymongo import MongoClient

def main():

    client = MongoClient('localhost', 27017)
    #scrapingデータベースのebooksコレクションを作成
    collection = client.scraping.ebooks
    #keyフィールドとしてユニークなインデックスを設定
    collection.create_index('key', unique=True)

    #Webページを取得、繰り返しアクセスするためSessionを使用
    response = requests.get('https://gihyo.jp/dp')
    #URLリストのジェネレータを取得
    urls = scrape_list_page(response)
    #url_list = [str(url) for url in urls]

    for url in urls:
        #url = url_list[0]
        #キーの取得
        key = extract_key(url)
        #キーが同じ最初のドキュメントを取得
        ebook = collection.find_one({'key': key})
        #キーが同じドキュメントが存在しない場合
        if not ebook:
            #各URLにアクセス
            time.sleep(1)
            response = requests.get(url)
            #ebookコレクションのドキュメントを作成
            ebook = scrape_detail_page(response)
            #DBにドキュメントを追加
            collection.insert_one(ebook)
        print(ebook)

#WEBページ（html）を入力
def scrape_list_page(response):
    #htmlをパース
    root = lxml.html.fromstring(response.content)
    #URLを相対パスから絶対パスに変換
    root.make_links_absolute(response.url)
    #id属性がlistBookの子孫で、a要素のitemprop属性がurlの値を取得（CSSセレクター）
    for a in root.cssselect('#listBook a[itemprop="url"]'):
        url = a.get('href')
        yield url

#各書籍の情報（タイトル、価格、目次）を取得
def scrape_detail_page(response):
    root = lxml.html.fromstring(response.content)
    ebook = {
            'url': response.url,
            'key': extract_key(response.url),
            'title': root.cssselect('#bookTitle')[0].text_content(),
            'price': root.cssselect('.buy')[0].text,
            'content': [normalize_spaces(h3.text_content()) for h3 in root.cssselect('#content > h3')],
            }
    return ebook

def extract_key(url):
    #末尾から遡って、最初の/までの文字列を取得
    m = re.search(r'([^/]+)$', url)
    return m.group(1)

#任意の空白文字を取り除く
def normalize_spaces(s):
    #return re.sub(r'\s+', ' ', s).strip()
    return re.sub(r'\u3000+', ': ', s).strip()

if __name__ == '__main__':
    main()

chk = {'a':0, 'b':1, 'c':3}
for val in chk:
    chk[val] += 1


import urllib

from flask import Blueprint, request, render_template, flash, redirect, url_for

from orders_tracker.blueprints.clients.service import add_client, update_client, remove_client, search_clients, \
    get_form_fields, get_path_args, \
    get_clients_count, render_empty, get_pagination_metadata, paginate_clients
from orders_tracker.forms import NewClientForm, DeleteConfirmForm
from orders_tracker.models import Client, Device
from orders_tracker.tables import ClientsTable

clients_blueprint = Blueprint('clients_bp', __name__, template_folder="templates")

@clients_blueprint.route('/clients/new', methods=['GET', 'POST'])
def new_client():
    form = NewClientForm()
    if request.method == 'POST':
        if form.validate_on_submit():
            created_client = Client(form.name.data, form.phone.data, form.address.data, form.notes.data)
            add_client(created_client)
            return redirect(url_for('clients_bp.clients'))
        else:
            flash('Перевірте введені значення.', category='warning')

    return render_template('new_client.html', form=form)

@clients_blueprint.route('/clients', methods=['GET', 'POST'])
def clients():
    if request.method == 'POST':
        search_field = get_form_fields()
        return redirect(url_for('clients_bp.clients',
                                search_query=search_field))

    search_arg, page_arg = get_path_args()

    stats = {'total': get_clients_count(), 'filter': -1}

    clients_query = search_clients(search_arg)

    stats['filter'] = clients_query.count()
    if stats['filter'] == 0:
        return render_empty(stats, search_arg)

    pagination_metadata = get_pagination_metadata(page_arg, clients_query)

    clients_list = paginate_clients(pagination_metadata, clients_query)
    table = ClientsTable(clients_list)

    return render_template('clients.html',
                           table=table,
                           stats=stats,
                           search_field_value=search_arg,
                           pagination_data=pagination_metadata)

@clients_blueprint.route('/clients/<client_id>', methods=['GET', 'POST'])
def client(client_id):
    address_link = None
    selected_client = Client.query.filter_by(id=client_id).first_or_404()
    if selected_client.address:
        address_link = "https://www.google.com/maps/search/?api=1&query=" + \
                       urllib.parse.quote_plus(selected_client.address)

    devices = Device.query.filter_by(client_id=client_id).all()

    return render_template('client.html',
                           client=selected_client,
                           devices=devices,
                           address_link=address_link)

@clients_blueprint.route('/clients/<client_id>/edit', methods=['GET', 'POST'])
def edit_client(client_id):
    edited_client = Client.query.filter_by(id=client_id).first()
    modal_form = NewClientForm()

    if request.method == 'POST':
        if modal_form.validate_on_submit():
            edited_client.name = modal_form.name.data
            edited_client.phone = modal_form.phone.data
            edited_client.address = modal_form.address.data
            edited_client.notes = modal_form.notes.data
            update_client(edited_client)
            return redirect(url_for('clients_bp.client', client_id=edited_client.id))
        else:
            flash('Дані про клієнта не оновлено.', category='warning')

    modal_form = NewClientForm(edited_client)
    return render_template('edit_client.html',
                           form=modal_form,
                           message_title="Редагування інформації про клієнта",
                           client_id=edited_client.id,
                           color="is-link")

@clients_blueprint.route('/clients/<client_id>/delete', methods=['GET', 'POST'])
def delete_client(client_id):
    deleted_client = Client.query.filter_by(id=client_id).first()
    form = DeleteConfirmForm()

    if request.method == 'POST':
        if form.validate_on_submit():
            remove_client(deleted_client)
            return redirect(url_for('clients_bp.clients'))

    return render_template('delete_confirm.html',
                           form=form,
                           client_id=deleted_client.id,
                           message_title="Видалення клієнта",
                           message="Ви дійсно бажаєте видалити клієнта " + deleted_client.name + "?")