"""

Main file for running a blockchain node

"python xcoin.py" creates a command line interface for running the node
"python xcoin.py -d" creates a peer for running on a docker simulations without commandline input
"python xcoin.py -m" creates a peer mirror for running a peer on your local machine

"""

from node import NodeFactory

import argparse
import pickle
from twisted.internet.protocol import Factory, ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet import reactor, stdio
from twisted.internet.task import LoopingCall
import nacl.encoding
import nacl.signing
from generate_seed_random import generateRandomSeed
import netifaces as ni
import network_settings as ns

#Find machine's own ip address
#ni.ifaddresses('en0')
try:
    myIP = ni.ifaddresses('eth0')[ni.AF_INET][0]['addr']
except ValueError:
    myIP = ni.ifaddresses('en0')[ni.AF_INET][0]['addr']

#Parse command line arguments
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--mirror", help="run node as a mirror", action="store_true")
parser.add_argument("-n", "--nirror", help="run node as a mirror", action="store_true")
parser.add_argument("-b", "--bootstrap", help="run as docker bootstrap", action="store_true")
parser.add_argument("-p", "--peer", help="run as docker peer, add additional bootstrap address", action="store_true")
parser.add_argument("address", nargs='?', help="print out if p tag", type=str)
parser.add_argument("-d", "--docker", help="auto generate key for docker nodes", action="store_true")
args = parser.parse_args()
if args.mirror:
    ledger_dir = "mirror/ledger.p"
    seed_dir = "mirror/seed.p"
    PORT = 8124
    PEER_PORT = 8123
elif args.bootstrap:
    ledger_dir = "ledger.p"
    seed_dir = "seed.p"
    PORT = 8123
    PEER_PORT = 8123
elif args.peer:
    ledger_dir = "peer/ledger.p"
    seed_dir = "seed.p"
    PORT = 8123
    PEER_PORT = 8123
    BOOTSTRAP_ADDRESS = args.address
elif args.nirror:
    ledger_dir = "ledger.p"
    seed_dir = "seed.p"
    PORT = 8125
    PEER_PORT = 8123
else:
    ledger_dir = "ledger.p"
    seed_dir = "seed.p"
    PORT = 8123
    PEER_PORT = 8124

if args.docker:
    generateRandomSeed()

#Set configuration for network settings
PEER_LIST_SIZE = 30

#Import python ledger object, data type to be updated to allow easier modifictaion
ledger = pickle.load( open(ledger_dir, "rb" ) )

#Import secret key
seed = pickle.load( open(seed_dir, "rb") )
signing_key = nacl.signing.SigningKey(seed.encode("ascii"))
verify_key = signing_key.verify_key
pubkey = verify_key.encode(encoder=nacl.encoding.HexEncoder)

print(myIP)
print(pubkey)

#Enter address for node block rewards
my_address = pubkey

factory = NodeFactory(reactor, ledger, my_address, signing_key, PEER_PORT, "myIP", ns)
reactor.callLater(5, factory.startPOW)

stdio.StandardIO(factory.buildCommandProtocol())

if args.peer:
    reactor.connectTCP(BOOTSTRAP_ADDRESS, PEER_PORT, factory)

# def maintainPeerList(factory):
#     """ Looping call function for maintaing a list of peers """
#     if factory.peerListSize() < ns.PEER_LIST_SIZE:
#         factory.requestPeers()
#         print("maintain")

# lc = LoopingCall(maintainPeerList, factory)
# # reactor.callLater(5, lc)
# lc.start(20)

reactor.listenTCP(PORT, factory)
reactor.run()


# coding: utf-8

import sys
from setuptools import setup, find_packages

NAME = "swagger_server"
VERSION = "1.0.0"

# To install the library, run the following
#
# python setup.py install
#
# prerequisite: setuptools
# http://pypi.python.org/pypi/setuptools

REQUIRES = ["connexion"]

setup(
    name=NAME,
    version=VERSION,
    description="API Teatro Colon",
    author_email="",
    url="",
    keywords=["Swagger", "API Teatro Colon"],
    install_requires=REQUIRES,
    packages=find_packages(),
    package_data={'': ['swagger/swagger.yaml']},
    include_package_data=True,
    long_description="""\
    API para la reserva de butacas de teatro Colon
    """
)


from train import train
from ray import tune

import math

from algo.algo_irat import IRATConfig, IRATTrainer
from callbacks import MultiAgentDrivingCallbacks
from env.env_wrappers import get_rllib_compatible_env, get_neighbour_env
from env.env_utils import get_metadrive_ma_env_cls
from utils import (
    get_train_parser,
    get_training_resources,
    get_other_training_configs,
)

TEST = False # <~~ Toggle TEST mode here!
# TEST = True

ALGO_NAME = "IRAT"
SCENE = "intersection" if not TEST else "intersection"

# === Env Seeds ===
# seeds = [5000, 6000, 7000, 8000, 9000, 10000, 11000, 12000]
# seeds = [5000, 6000, 7000]
SEEDS = [5000]

# NUM_AGENTS = [30]
NUM_AGENTS = 30
NEI_DISTANCE = 40
NUM_NEIGHBOURS = 4
# EXP_DES = "v<idv:0->0.2, team:1->0.8>"
EXP_DES = "BEST(0,1.25)(10,0)_8"
# EXP_DES = "v1<kl_coeff><(0,0.5)(1, 0.5)>"

if __name__ == "__main__":
    # === Get Args ===
    args = get_train_parser().parse_args()
    NUM_AGENTS, exp_name, num_rollout_workers, SEEDS, TEST = \
        get_other_training_configs(
            args=args,
            algo_name=ALGO_NAME,
            exp_des=EXP_DES,
            scene=SCENE,
            num_agents=NUM_AGENTS,
            seeds=SEEDS,
            test=TEST)

    # === Get Environment ===
    env_name, env_cls = \
        get_rllib_compatible_env(
            get_neighbour_env(
                get_metadrive_ma_env_cls(SCENE)),
            return_class=True)

    # === Environmental Configs ===
    env_config = dict(
        num_agents=tune.grid_search(NUM_AGENTS),
        return_single_space=True,
        start_seed=tune.grid_search(SEEDS),
        delay_done=25,
        vehicle_config=dict(
            lidar=dict(
                num_lasers=72,
                distance=40,
                num_others=0)),
        # == neighbour config ==
        add_nei_state=False,
        use_dict_obs=False,
        add_compact_state=False,
        neighbours_distance=NEI_DISTANCE,
    )

    # ────────────── for test ────────────── #
    stop = {"timesteps_total": 1.5e6}
    if TEST : stop ={"training_iteration": 1}
    # ────────────────────────────────────── #

    # === Algo Configs ===
    algo_config = (
        IRATConfig()
        .framework('torch')
        .resources(
            num_cpus_per_worker=0.25,
            **get_training_resources()
        )
        .rollouts(
            num_rollout_workers=num_rollout_workers,
        )
        .callbacks(MultiAgentDrivingCallbacks)
        .multi_agent(
        )
        .training(
            train_batch_size=1024,
            gamma=0.99,
            lr=3e-5,
            sgd_minibatch_size=512,
            num_sgd_iter=5,
            lambda_=0.95,
            model=dict(
                # == RLlib built-in attention net config ==
                use_attention=False,
                max_seq_len=10,
                attention_num_transformer_units=1,
                attention_num_heads=2,
                attention_head_dim=32,
                attention_position_wise_mlp_dim=32,
                custom_model_config=dict(
                ),
                free_log_std=False,
                fcnet_activation='tanh',
            ),
            # use_kl_loss=False, # for single ppo's kl(pi_old|pi_new)
            entropy_coeff=0, # 不能加上最大化熵? # 默认为0
            # grad_clip=40,
            # grad_clip_by='norm',
            vf_loss_coeff=1,
            kl_coeff=0, # 约束新旧策略更新 # 默认为0.2
            # kl_target=0,
            _enable_learner_api=False,
        )
        .rl_module(_enable_rl_module_api=False)
        .environment(env=env_name, render_env=False, env_config=env_config, disable_env_checking=True)
        .update_from_dict(dict(
            # == IRAT ==
            idv_policy_no_team=True,
            idv_clip_param=0.5,
            team_clip_param=0.5,
            # idv_kl_coeff=0.2,
            # idv_kl_end_coeff=0.5,
            idx_kl_coeff_schedule=[
                (0, 0),
                (0.85*NUM_AGENTS[0]*1.5e6, 1)
            ],
            team_kl_coeff_schedule=[
                (0, 10),
                (tune.grid_search([1.15*NUM_AGENTS[0]*1.5e6, 1.25*NUM_AGENTS[0]*1.5e6]), 0)
            ],
            # team_kl_coeff=0.2,
            # team_kl_end_coeff=0.5,
            # == SaCo ==
            use_svo=True, #tune.grid_search([True, False]), # whether or not to use svo to change reward, if False, use original reward
            use_fixed_svo=False,
            fixed_svo=math.pi/4, #tune.grid_search([math.pi/4, math.pi/6, math.pi/3]),
            svo_init_value='0', # in [ '0', 'pi/4', 'pi/2', 'random' ]
            svo_mode='full', #tune.grid_search(['full', 'restrict']),
            # nei_rewards_mode='mean_nei_rewards', #'attentive_one_nei_reward',
            nei_rewards_mode='mean', #'attentive_one_nei_reward',
            nei_reward_if_no_nei='self',
            nei_rewards_add_coeff=1,
            norm_adv=True,
            # == Common ==
            huber_value_loss=True,
            # old_value_loss=True,
            num_neighbours=NUM_NEIGHBOURS, # the max num of neighbours in use
            # == CC ==
            use_central_critic=False,
            counterfactual=False,
            fuse_mode="none",
        ))
    )
    print(algo_config.is_multi_agent())
    exit()
    # === Launch training ===
    train(
        IRATTrainer,
        config=algo_config,
        stop=stop,
        exp_name=exp_name,
        checkpoint_freq=5,
        keep_checkpoints_num=5,
        num_gpus=0,
        results_path='exp_IRAT',
        test_mode=TEST,
    )


import os
import tempfile

import numpy as np
from pytest import approx

import gimpenums
from pgimp.GimpFile import GimpFile, LayerType, ColorMap, GimpFileType
from pgimp.util import file
from pgimp.util.TempFile import TempFile

rgb_file = GimpFile(file.relative_to(__file__, 'test-resources/rgb.xcf'))
"""
The file rgb.xcf contains a 3x2 image with white 'Background' layer and 'Red', 'Green', 'Blue' layers with differing
opacity. The layer 'Background' contains a black pixel at y=0, x=1, the others pixels are white.
"""

def test_layer_to_numpy():
    actual = rgb_file.layer_to_numpy('Background')
    expected = np.array([
        [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
        [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
    ], dtype=np.uint8)

    assert np.all(expected == actual)
    assert actual.shape == (2, 3, 3)

def test_layers_to_numpy():
    use_temp_file_values = [True, False]
    for use_temp_file in use_temp_file_values:
        with TempFile('.xcf') as f:
            gimp_file = GimpFile(f) \
                .create('Red', np.zeros(shape=(1, 2, 3), dtype=np.uint8)) \
                .add_layer_from_numpy('Green', np.ones(shape=(1, 2, 3), dtype=np.uint8) * 127) \
                .add_layer_from_numpy('Blue', np.ones(shape=(1, 2, 3), dtype=np.uint8) * 255)
            np_array = gimp_file.layers_to_numpy(['Red', 'Green', 'Blue'], use_temp_file=use_temp_file)

        assert (1, 2, 9) == np_array.shape
        assert np.all(np.array([
            [[0, 0, 0, 127, 127, 127, 255, 255, 255], [0, 0, 0, 127, 127, 127, 255, 255, 255]],
        ], dtype=np.uint8) == np_array)

def test_create():
    filename = file.relative_to(__file__, 'test-resources/test-create.xcf')
    layer_bg = np.array([
        [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
        [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
    ], dtype=np.uint8)

    gimp_file = GimpFile(filename)
    gimp_file.create('Background', layer_bg)

    exists = os.path.exists(filename)
    actual = gimp_file.layer_to_numpy('Background')
    os.remove(filename)

    assert exists
    assert np.all(layer_bg == actual)

def test_add_layers_from_numpy():
    with TempFile('.xcf') as f:
        gimp_file = GimpFile(f).create('Background', np.zeros(shape=(1, 2), dtype=np.uint8))
        gimp_file.add_layers_from_numpy(
            ['Layer 1', 'Layer 2'],
            np.ones(shape=(2, 1, 2), dtype=np.uint8) * 255,
            opacity=55.,
            visible=False,
            position='Background'
        )
        assert gimp_file.layer_names() == ['Layer 1', 'Layer 2', 'Background']

def test_add_layers_from_numpy_with_list_config():
    with TempFile('.xcf') as f:
        gimp_file = GimpFile(f).create('Background', np.zeros(shape=(1, 2), dtype=np.uint8))
        gimp_file.add_layers_from_numpy(
            ['Layer 1', 'Layer 2'],
            np.ones(shape=(2, 1, 2), dtype=np.uint8) * 255,
            opacity=[100., 55.],
            visible=[False, True],
            position='Background',
            blend_mode=gimpenums.NORMAL_MODE
        )
        assert gimp_file.layer_names() == ['Layer 1', 'Layer 2', 'Background']

def test_add_layer_from_numpy():
    tmp_file = tempfile.mktemp(suffix='.xcf')
    layer_bg = np.array([
        [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
        [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
    ], dtype=np.uint8)
    layer_fg = np.array([
        [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
        [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
    ], dtype=np.uint8)

    gimp_file = GimpFile(tmp_file)
    gimp_file.create('Background', layer_bg)
    gimp_file.add_layer_from_numpy('Foreground', layer_fg, opacity=55., visible=False)

    actual_bg = gimp_file.layer_to_numpy('Background')
    actual_fg = gimp_file.layer_to_numpy('Foreground')

    os.remove(tmp_file)

    assert np.all(layer_bg == actual_bg)
    assert np.all(layer_fg == actual_fg)

def test_add_layer_from_numpy_with_position_index():
    data = np.array([[[255, 255, 255]]], dtype=np.uint8)
    with TempFile('.xcf') as tmp_file:
        gimp_file = GimpFile(tmp_file)
        gimp_file.create('Background', data)
        gimp_file.add_layer_from_numpy('Layer 1', data, position=1)
        gimp_file.add_layer_from_numpy('Layer 2', data, position=1)

        assert gimp_file.layer_names() == ['Background', 'Layer 2', 'Layer 1']

def test_add_layer_from_numpy_with_position_layer_name():
    data = np.array([[[255, 255, 255]]], dtype=np.uint8)
    with TempFile('.xcf') as tmp_file:
        gimp_file = GimpFile(tmp_file)
        gimp_file.create('Background', data)
        gimp_file.add_layer_from_numpy('Layer 1', data, position='Background')
        gimp_file.add_layer_from_numpy('Layer 2', data, position='Background')

        assert gimp_file.layer_names() == ['Layer 1', 'Layer 2', 'Background']

def test_add_layer_from_file():
    with TempFile('.xcf') as dst, TempFile('.xcf') as src:
        layer_bg = np.array([
            [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
            [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
        ], dtype=np.uint8)
        position = 1

        src_file = GimpFile(src)
        src_file.create('Background', np.zeros(shape=(2, 3, 3), dtype=np.uint8))
        src_file.add_layer_from_numpy('Yellow', np.array([
            [[240, 255, 0], [240, 255, 0], [240, 255, 0]],
            [[240, 255, 0], [240, 255, 0], [240, 255, 0]],
        ], dtype=np.uint8))

        dst_file = GimpFile(dst)
        dst_file.create('Background', layer_bg)
        dst_file.add_layer_from_file(src_file, 'Yellow', new_name='Yellow (copied)', new_position=position)

        assert 'Yellow (copied)' == dst_file.layers()[position].name
        assert np.all([240, 255, 0] == dst_file.layer_to_numpy('Yellow (copied)'))

def test_merge_layer_from_file():
    with TempFile('.xcf') as dst, TempFile('.xcf') as src:
        layer_bg = np.array([
            [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
            [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
        ], dtype=np.uint8)

        src_file = GimpFile(src)
        src_file.create('Background', np.zeros(shape=(2, 3, 3), dtype=np.uint8))
        src_file.add_layer_from_numpy('Yellow', np.array([
            [[240, 255, 0], [240, 255, 0], [240, 255, 0]],
            [[240, 255, 0], [240, 255, 0], [240, 255, 0]],
        ], dtype=np.uint8))

        dst_file = GimpFile(dst)
        dst_file.create('Yellow', layer_bg)
        dst_file.merge_layer_from_file(src_file, 'Yellow')

        new_layer_contents = dst_file.layer_to_numpy('Yellow')

        assert np.all([240, 255, 0] == new_layer_contents)

def test_merge_layer_from_file_with_cleared_selection():
    src = file.relative_to(__file__, 'test-resources/selection.xcf')
    with TempFile('.xcf') as dst:
        src_file = GimpFile(src)
        dst_file = GimpFile(dst)
        dst_file.create('Background', np.zeros(shape=(3, 3, 3), dtype=np.uint8))
        dst_file.merge_layer_from_file(src_file, 'Background')

        new_layer_contents = dst_file.layer_to_numpy('Background')

        assert np.all(np.array([
            [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
            [[255, 0, 0], [255, 0, 0], [255, 255, 255]],
            [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
        ], dtype=np.uint8) == new_layer_contents)

def test_merge_layer_from_file_without_cleared_selection():
    src = file.relative_to(__file__, 'test-resources/selection.xcf')
    with TempFile('.xcf') as dst:
        src_file = GimpFile(src)
        dst_file = GimpFile(dst)
        dst_file.create('Background', np.zeros(shape=(3, 3, 3), dtype=np.uint8))
        dst_file.merge_layer_from_file(src_file, 'Background', clear_selection=False)

        new_layer_contents = dst_file.layer_to_numpy('Background')

        assert np.all(np.array([
            [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
            [[255, 0, 0], [255, 0, 0], [0, 0, 0]],
            [[0, 0, 0], [0, 0, 0], [0, 0, 0]],
        ], dtype=np.uint8) == new_layer_contents)

def test_layers():
    layers = rgb_file.layers()

    assert ['Blue', 'Green', 'Red', 'Background'] == list(map(lambda x: x.name, layers))
    assert [0, 1, 2, 3] == list(map(lambda x: x.position, layers))
    assert [False, False, False, True] == list(map(lambda x: x.visible, layers))
    assert [23.92156862745098, 40.3921568627451, 52.54901960784314, 100.0] == list(map(lambda x: approx(x.opacity), layers))

def test_convert_to_indexed_using_predefined_colormap():
    tmp_file = tempfile.mktemp(suffix='.xcf')
    values = np.array([[i for i in range(0, 256)]], dtype=np.uint8)
    assert (1, 256) == values.shape

    gimp_file = GimpFile(tmp_file)
    gimp_file.create_indexed('Background', values, ColorMap.JET)
    gimp_file.add_layer_from_numpy('Values', values, type=LayerType.INDEXED)

    layer_bg = gimp_file.layer_to_numpy('Background')
    layer_values = gimp_file.layer_to_numpy('Values')

    os.remove(tmp_file)

    assert (1, 256, 1) == layer_bg.shape
    assert np.all(values == layer_bg[:, :, 0])
    assert (1, 256, 1) == layer_values.shape
    assert np.all(values == layer_values[:, :, 0])

def test_convert_to_indexed_using_custom_colormap():
    tmp_file = tempfile.mktemp(suffix='.xcf')
    values = np.array([[i for i in range(0, 256)]], dtype=np.uint8)
    assert (1, 256) == values.shape
    colormap = np.array([[255, 0, 0], [0, 255, 0], [0, 0, 255], *[[i, i, i] for i in range(3, 256)]], dtype=np.uint8)
    assert (256, 3) == colormap.shape

    gimp_file = GimpFile(tmp_file)
    gimp_file.create_indexed('Background', values, colormap=colormap)
    gimp_file.add_layer_from_numpy('Values', values, type=LayerType.INDEXED)

    layer_bg = gimp_file.layer_to_numpy('Background')
    layer_values = gimp_file.layer_to_numpy('Values')

    os.remove(tmp_file)

    assert (1, 256, 1) == layer_bg.shape
    assert np.all(values == layer_bg[:, :, 0])
    assert (1, 256, 1) == layer_values.shape
    assert np.all(values == layer_values[:, :, 0])

def test_remove_layer():
    tmp_file = tempfile.mktemp(suffix='.xcf')
    layer = np.array([
        [[255, 255, 255], [0, 0, 0], [255, 255, 255]],
        [[255, 255, 255], [255, 255, 255], [255, 255, 255]],
    ], dtype=np.uint8)

    gimp_file = GimpFile(tmp_file)
    gimp_file.create('Background', layer)
    gimp_file.add_layer_from_numpy('Layer', layer)
    all_layers = gimp_file.layer_names()

    gimp_file.remove_layer('Background')
    remaining_layers1 = gimp_file.layer_names()
    gimp_file.remove_layer('Layer')
    remaining_layers2 = gimp_file.layer_names()

    os.remove(tmp_file)

    assert ['Layer', 'Background'] == all_layers
    assert ['Layer'] == remaining_layers1
    assert [] == remaining_layers2

def test_copy():
    with TempFile('.xcf') as original, TempFile('.xcf') as copy:
        original_file = GimpFile(original).create('Background', np.zeros(shape=(2, 2), dtype=np.uint8))
        copied_file = original_file.copy(copy)
        original_file.add_layer_from_numpy('New', np.zeros(shape=(2, 2), dtype=np.uint8))
        assert ['Background'] == copied_file.layer_names()
        assert ['New', 'Background'] == original_file.layer_names()

def test_dimensions():
    assert (3, 2) == rgb_file.dimensions()

def test_create_from_template():
    with TempFile('.xcf') as original, TempFile('.xcf') as created:
        original_file = GimpFile(original).create('Background', np.zeros(shape=(3, 2), dtype=np.uint8))
        created_file = GimpFile(created).create_from_template(original_file)
        assert [] == created_file.layer_names()
        assert (2, 3) == created_file.dimensions()

def test_create_empty():
    with TempFile('.xcf') as f:
        gimp_file = GimpFile(f).create_empty(3, 2, GimpFileType.RGB)
        assert (3, 2) == gimp_file.dimensions()
        assert [] == gimp_file.layer_names()

def test_create_from_file():
    test_export()

def test_export():
    with TempFile('.xcf') as xcf, TempFile('.png') as png,  TempFile('.jpg') as jpg, TempFile('.xcf') as from_png:
        gimp_file = GimpFile(xcf) \
            .create('Background', np.zeros(shape=(1, 1), dtype=np.uint8)) \
            .add_layer_from_numpy('Foreground', np.ones(shape=(1, 1), dtype=np.uint8) * 255, opacity=50.) \
            # 50% opacity results in uint8 of 127 or 128 depending on the implementation -> needs test tolerance

        gimp_file.export(png)  # saved as grayscale with alpha (identify -format '%[channels]' FILE)
        gimp_file.export(jpg)

        reimported_np_from_png = GimpFile(from_png).create_from_file(png, layer_name='Image').layer_to_numpy('Image')
        assert np.isclose(reimported_np_from_png[0, 0, 0], 127.5, atol=0.5)
        assert reimported_np_from_png[0, 0, 1] == 255

        reimported_np_from_jpg = GimpFile(from_png).create_from_file(jpg, layer_name='Image').layer_to_numpy('Image')
        assert np.isclose([127.5], reimported_np_from_jpg, atol=0.5)


from covid import Covid
import sys
import matplotlib.pyplot as plt

covid = Covid()
countries = covid.list_countries()
Country_name=input("ENTER THE COUNTRY NAME:  ").capitalize()

#get the country's virus data
virus_data=covid.get_status_by_country_name(Country_name)
print(virus_data)

#save data related to confirmed cases for the future reference
Active_cases=virus_data['confirmed']

#remove unnecesary values like latitude longitude,lastupdate
remove=['id','country','latitude','longitude','last_update','confirmed']

for i in remove:
    virus_data.pop(i)

#virusdata is dictionary data type
#we should split them as keys and values respectively

id=list(virus_data.keys())
value=[str(i) for i in virus_data.values()]

#piechart creation
plt.pie(value,labels=id,colors=['r','yellow','g'] ,autopct='%1.1f%%')
plt.title("COUNTRY : "+Country_name.upper()+"\n TOTAL ACTIVE CASES :" +str(Active_cases))
plt.legend()
plt.show()


import os
import sys
import csv
import time

try:
    import tweepy
except ImportError:
    print("[ERROR] Unable to import Tweepy module: can't run!")
    sys.exit()

class FollowerScraper:

    def __init__(self):
        self._status = ""
        self._followers = list()
        self._csv_tweet = ""
        self._csv_tweet_writer = ""
        self._curr_follower = ""

    def get_followers(self, api, target, data):
        try:
            if os.name == "nt":
                self._csv_tweet = open(
                    data, 'w', encoding='utf-8', newline='')
            else:
                self._csv_tweet = open(data, 'w')

            self._csv_tweet_writer = csv.writer(self._csv_tweet)
            self._csv_tweet_writer.writerow(
                ["username"])

        except Exception as e:
            print("[ERROR] Unable to prepare CSV files!")
            print("\n[Details]: ", e)
            sys.exit()

        while True:
            try:
                print("[*] Downloading '", target, "' followers list")
                print("[*] Please, wait...")
                for self._tweet in tweepy.Cursor(
                        api.followers,
                        screen_name=target,
                        count=200
                ).items():

                    self._curr_follower = self._tweet.screen_name
                    print(self._curr_follower)

                    try:
                        self._csv_tweet_writer.writerow([
                            self._curr_follower
                        ])

                    except Exception as e:
                        print(
                            "[ERROR] Unable to write tweets on file: ",
                            data, ", details: ", e)
                        sys.exit()

            except tweepy.error.RateLimitError as e:
                print("[ERROR: TWEEPY API] Too many requests. Wait some minutes.")
                time.sleep(15 * 60)
            except tweepy.TweepError as e:
                if e.api_code == 429:
                    print("[ERROR: TWEEPY API] Too many requests. Wait some minutes.")
                    time.sleep(15 * 60)
                else:
                    print("[ERROR: TWEEPY API] " + str(e.text))
                sys.exit()
            except Exception as e:
                print("[ERROR]: ", e)
                sys.exit()


from glob import glob
import os
from setuptools import setup

package_name = 'cpmr_ch2'

setup(
    name=package_name,
    version='3.1.0',
    packages=[package_name],
    data_files=[
        ('share/ament_index/resource_index/packages', ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        (os.path.join('share', package_name), glob('launch/*.py')),
        (os.path.join('share', package_name), glob('maps/*.json')),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='jenkin',
    maintainer_email='jenkin@yorku.ca',
    description='Code associated with Chapter 2 of CPMR 3rd Edition',
    license='MIT License',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'drive_to_goal = cpmr_ch2.drive_to_goal:main',
            'add_obstacle = cpmr_ch2.add_obstacle:main',
            'build_map = cpmr_ch2.build_map:main',
            'destroy_map = cpmr_ch2.destroy_map:main',
        ],
    },
)


import shutil
import time
import urllib.parse
from dataclasses import dataclass
from typing import Callable, Dict

import dateutil.parser
import requests
import rich.progress as progress
import urllib3
from plib import Path
from retry import retry
from tqdm.utils import CallbackIOWrapper

from .progress import UIProgress

TRIES = 5

@dataclass
class Downloader:
    url: str
    dest: Path | str | None = None
    folder: Path | str = None
    session: requests.Session | None = None
    headers: Dict | None = None
    retry_amount: int = -1
    timeout: int = 10
    progress_callback: Callable = lambda p: None
    skip_same_size: bool = False
    set_time: bool = True  # set modified time to modified time on server

    def __post_init__(self):
        self.dest = self.prepare_dest(self.dest, self.folder)
        self.session = self.prepare_session(self.session, self.headers)

    def prepare_session(self, session, headers):
        session = session or requests.Session()
        browser = (
            "Mozilla/5.0 (X11; Linux x86_64) "
            "AppleWebKit/537.36 (KHTML, like Gecko) "
            "Chrome/85.0.4183.83 Safari/537.36"
        )
        download_time = time.gmtime(self.dest.mtime)
        download_time = time.strftime("%a, %d %b %Y %H:%M:%S GMT", download_time)
        headers = (headers or {}) | {
            "User-Agent": browser,
            "If-Modified-Since": download_time,
        }
        session.headers.update(headers)
        return session

    def prepare_dest(self, dest, folder):
        if dest is None:
            url_path = urllib.parse.urlparse(self.url).path
            name = urllib.parse.unquote(url_path).split("/")[-1]
            dest = name or "download"

        dest = Path(folder) / dest if folder else Path(dest)
        return dest

    @retry(
        (requests.exceptions.RequestException, urllib3.exceptions.HTTPError),
        tries=TRIES,
    )
    def download(self):
        self.retry_amount += 1
        headers = {"Range": f"bytes={self.temp_dest.size}-"}
        if "If-Modified-Since" in self.session.headers:
            self.session.headers.pop("If-Modified-Since")
        stream = self.session.get(
            self.url, timeout=self.timeout, stream=True, headers=headers
        )
        if stream.status_code != 304:
            self.download_modified(stream)

    def download_modified(self, stream):
        if stream.status_code == 416:  # range not satifiable or supported
            stream = self.session.get(self.url, timeout=self.timeout, stream=True)
        elif "Content-Range" in stream.headers:
            start = int(
                stream.headers["Content-Range"].split("bytes ")[1].split("-")[0]
            )
            if self.temp_dest.size > start:
                self.temp_dest.unlink()
                stream = self.session.get(self.url, timeout=self.timeout, stream=True)
        else:
            assert stream.ok

        skip = self.skip_same_size and self.dest.size == int(
            stream.headers["Content-Length"]
        )
        if not skip:
            self.start_download(stream)

    def start_download(self, stream):
        total = (
            int(
                stream.headers["Content-Length"]
            )  # length that still needs to be received
            if "Content-Length" in stream.headers
            else int(stream.headers["Content-Range"].split("/")[-1])
        )

        progress = UIProgress(self.description, total=total + self.dest.size)
        # downloaded size added to todo and done

        def progres_callback(value):
            progress.advance(value)
            self.progress_callback(value / total)

        progres_callback(self.dest.size)  # already downloaded part is progress

        stream_raw = CallbackIOWrapper(progres_callback, stream.raw)

        chunk_size = self.truncate(total // 10, 1 * 1024, 128 * 1024)
        with progress, self.temp_dest.open("ab") as fp:
            shutil.copyfileobj(stream_raw, fp, length=chunk_size)

        self.temp_dest.rename(self.dest)
        modified_key = "last-modified"
        if self.set_time and modified_key in stream.headers:
            server_mtime = stream.headers[modified_key]
            self.dest.mtime = dateutil.parser.parse(server_mtime).timestamp()

    @property
    def temp_dest(self):
        return self.dest.with_suffix(self.dest.suffix + ".part")

    @property
    def description(self):
        return (
            f"[retry {self.retry_amount}/{TRIES - 1}] {self.dest.name}"
            if self.retry_amount > 0
            else self.dest.name
        )

    @staticmethod
    def truncate(value, min_value, max_value):
        if value < min_value:
            value = min_value
        elif value > max_value:
            value = max_value
        return value


from kivy.uix.screenmanager import Screen, SlideTransition
from kivy.graphics import Line, Color
from kivy.properties import NumericProperty, ObjectProperty, StringProperty, BooleanProperty, ListProperty
from kivy.uix.button import Button
from kivy.uix.popup import Popup
from kivy.uix.image import Image
from kivy.uix.gridlayout import GridLayout
from kivy.resources import resource_find

import random as rand

# Import AI stuff
from .dotsandboxesAI.BoardEnvironment import BoardEnvironment
from .dotsandboxesAI.Agent import Agent
from .dotsandboxesAI.LeagueEnvironment import LeagueEnvironment
from time import sleep

def select_difficulty(auto=False):
    x = 0
    diffdict = {1: r'game_logic/dotsandboxesAI/qtables/easy.txt',
                2: r'game_logic/dotsandboxesAI/qtables/medium.txt',
                3: r'game_logic/dotsandboxesAI/qtables/hard.txt'}
    if not auto:
        while(x > 3 or x < 1):
            print("Select a difficulty:")
            print("1: Easy")
            print("2: Medium")
            print("3: Hard")
            x = int(input())

    else:
        x = rand.randint(1, 3)

    return diffdict[x]

class Dot(Image):
    """Custom widget inheriting from Image object. These are placed in the design/dotsandboxes.kv file

    Args:
        Image (kivy.uix.Image): Image widget
    """
    button_number = NumericProperty()
    def __init__(self, **kwargs):
        super(Dot, self).__init__(**kwargs)
        self.source = resource_find("images/dotsandboxes/dot.png")

class DotsAndBoxesScreen(Screen):
    """Screen object to be managed by ScreenManager object

    Args:
        Screen (kivy.uix.screenmanager.Screen): This inherits from kivy.relativelayout.RelativeLayout and uses that object's placement system. In the design file we used a gridlayout overwrite that placement system.

        The Object Property type lets kivy objects share variable names with the .kv files.
    """
    score = NumericProperty()
    ai_score = NumericProperty()
    game_grid = ObjectProperty(None)
    difficulty_setting = StringProperty("")
    match = StringProperty("")
    turn = BooleanProperty(True)
    piece = StringProperty("")
    lines = []
    dots = ListProperty()
    # dictionary mapping box scores to their image widget
    captured_boxes = []
    scoreboard = ObjectProperty(None)
    user_data = ObjectProperty(None)
    ai_data = ObjectProperty(None)
    first_league_run = True

    # a dictionary mapping dot numbers to acceptable dots to be paired with
    accepted_lines = {
        0: [1, 3],
        1: [0, 2, 4],
        2: [1, 5],
        3: [0, 6, 4],
        4: [1, 3, 5, 7],
        5: [2, 4, 8],
        6: [3, 7],
        7: [4, 6, 8],
        8: [7, 5]
    }

    # Map dots to lines in the board environment
    actual_lines = {
        (1, 2): 11,
        (0, 1): 10,
        (2, 5): 9,
        (1, 4): 8,
        (0, 3): 7,
        (4, 5): 6,
        (4, 3): 5,
        (5, 8): 4,
        (4, 7): 3,
        (3, 6): 2,
        (8, 7): 1,
        (6, 7): 0
    }

    start_dot = None

    def on_enter(self, *args):
        """Kivy Screen event listener that is run before the screen is entered

        Returns:
            None: Used to reset the board before entering the screen
        """

        self.clear_game_screen()
        self.board_env.reset()
        return super().on_enter(*args)

    def on_leave(self, *args):
        """Kivy Screen event listener that is run as the screen is left

        Returns:
            None:
        """
        self.restart()
        return super().on_leave(*args)

    def load_settings(self, diff, match):
        """
        Called whenever this screen is loaded
        self.lines is a list of tuples containing
        a line instruction object
        the start dot number
        the end dot number
        """

        self.difficulty_setting = diff
        self.match = match
        agent = Agent(resource_find(f"game_logic/dotsandboxesAI/qtables/{self.difficulty_setting.lower()}.txt"))
        self.board_env = BoardEnvironment(self, agent)
        self.scoreboard.size_hint_y = None

        # Load different settings based on game type
        if self.match == "Single Match":
            self.piece = self.board_env.turn
            if self.piece == "O":
                choice = [i for i, char in enumerate(self.board_env.board) if char == "X"][0]
                self.draw_ai_turn(choice, self.board_env.turn)

            # self.board_env.set_players(agent)
            self.board_env.print_board()
            self.scoreboard.height = 0
            self.ai_data.text, self.user_data.text = "", ""
            self.board_env.reset()
        else:
            # League Match
            self.first_league_run = True
            league = LeagueEnvironment(self.board_env, self)

            player_names = []
            board_agents = []
            league_agents = []

            player_names.append('learning strategy and tactics')
            board_agents.append(agent)
            league_agents.append(Agent(resource_find('game_logic/dotsandboxesAI/qtables/league.txt'), league))

            #player_names.append('learning tactics only')
            #board_agents.append(Agent(self.board_env, select_difficulty(True), 'max'))
            #league_agents.append(Agent(league, 'game_logic/dotsandboxesAI/qtables/league.txt', 'random'))

            #player_names.append('learning strategy only')
            #board_agents.append(Agent(self.board_env, select_difficulty(True), 'random'))
            #league_agents.append(Agent(league, 'game_logic/dotsandboxesAI/qtables/league.txt', 'max'))

            #player_names.append('no learning')
            #board_agents.append(Agent(self.board_env, select_difficulty(True), 'random'))
            #league_agents.append(Agent(league, 'game_logic/dotsandboxesAI/qtables/league.txt', 'random'))

            league.set_players(player_names, league_agents, board_agents)
            self.league_env = league
            self.scoreboard.height = 200
            for child in self.scoreboard.children:
                child.size_hint_y = None
                child.height = 200

            league.play_pair(self.first_league_run)
            self.first_league_run = False

    def menu(self):
        """Swap screen back to title screen
        """

        self.clear_game_screen()
        self.manager.transition = SlideTransition(direction="right")
        self.manager.current = "title"

    def restart(self):
        """This function is run whenever the "Restart Game" Button is pressed. Also used elsewhere
        """
        self.clear_game_screen()
        self.lines = []
        self.board_env.reset()
        self.piece = self.board_env.turn
        self.board_env.print_board()
        self.score, self.ai_score = 0, 0

    def league_restart(self):
        """This function is run whenever the "Restart Game" Button is pressed and the league environment is active
        """
        self.clear_game_screen()
        self.lines = []
        self.board_env.reset()
        self.league_env.reset_pair()
        self.league_env.play_pair(True)
        self.piece = self.board_env.turn
        self.board_env.print_board()
        self.score, self.ai_score = 0, 0

    def on_touch_down(self, touch):
        """Kivy event listener that is called when the user clicks in the Window

        Args:
            touch (kivy.MotionEvent): https://kivy.org/doc/stable/api-kivy.input.motionevent.html#kivy.input.motionevent.MotionEvent

            This function checks if any dot was clicked

        Returns:
        """
        # find what dot the mouse was over and save it to the start_dot property
        for i, _ in enumerate(self.dots):
            # if touch is within a box twice the radius of the dot, we draw
            if self.dots[i].collide_point(*touch.pos):
                self.start_dot = i
                break
        return super().on_touch_down(touch)

    def on_touch_up(self, touch):
        """kivy event listener that is called when the user lets go of the mouse or their touch

        Args:
            touch (kivy.MotionEvent): https://kivy.org/doc/stable/api-kivy.input.motionevent.html#kivy.input.motionevent.MotionEvent

            This function checks if any dot was clicked

        Returns:
        """
        # find what dot the mouse is over on mouse release and draw the appropriate line
        turn = self.board_env.turn
        if self.start_dot is None:
            return super().on_touch_up(touch)

        for dot_index, dot_obj in enumerate(self.dots):
            # if touch is within a box twice the radius of the dot, we draw
            if self.check_for_dot_click(touch, dot_index, dot_obj):
                with self.game_grid.canvas:
                    # save line to lines property to be updated on_size
                    points = list(self.dots[self.start_dot].center) + list(dot_obj.center)
                    # add half dot radius to x and y for the line because the dot position is measured from bottom left corner of dots (the radius of the dot is 10)
                    Color(1, 0, 0) if self.board_env.turn == "X" else Color(0,0,1)
                    self.lines.append((Line(points=points, width=3, group="lines"), self.start_dot, dot_index))

                # Add line to board environment
                # try except pairs to return line choice
                try:
                    choice = self.actual_lines[(self.start_dot, dot_index)]
                except KeyError:
                    choice = self.actual_lines[(dot_index, self.start_dot)]

                # play_game_turn returns true if the user scores
                # let ai move if user doesn't score
                if not self.board_env.play_game_turn(choice):
                    # Let AI think
                    sleep(.5)

                    if not self.board_env.is_full():
                        # Add AI move
                        self.board_env.play_game_turn()
                        self.board_env.print_board()
                    else:
                        self.is_full()

                # if the board is full, display the winner popup
                if self.board_env.is_full():
                    self.is_full()

                # change turn bool
                # self.turn = not self.turn
                self.start_dot = None
        return super().on_touch_up(touch)

    def check_for_dot_click(self, touch, dot_index, dot_obj):
        """Check if the user clicked a second dot valid with the current dot

        Args:
            touch (Touch): Kivy touch object
            dot_index (int): Index of dot we are checking
            dot_obj (kivy.Instruction): The object in memory that represents the dot drawing

        Returns:
            bool: Returns true if the dot that the user lets go of the mouse over is a valid pair with the dot from click
        """

        # warning monster bool
        return self.dots[dot_index].collide_point(*touch.pos) and self.start_dot is not None and dot_index in self.accepted_lines[self.start_dot] and self.check_dot_pair(dot_index, self.start_dot) in self.board_env.available_actions()

    def draw_ai_turn(self, choice, turn):
        """Draw the line based on AI turn. This function is called with the board environment method play_game_turn

        Args:
            choice (int): Choice decided by the AI.
        """
        actual_lines = {
            (1, 2): 11,
            (0, 1): 10,
            (2, 5): 9,
            (1, 4): 8,
            (0, 3): 7,
            (4, 5): 6,
            (4, 3): 5,
            (5, 8): 4,
            (4, 7): 3,
            (3, 6): 2,
            (8, 7): 1,
            (6, 7): 0
        }

        # Reverse the dictionary of lines and dots and find the dots based on the ai choice
        line_choice_dots = {value:key for key, value in actual_lines.items()}
        dots = line_choice_dots[choice] # get the entry in the dictionary that maps the line indexes to the dot indexes

        with self.game_grid.canvas:
            # save line to lines property to be updated on_size
            points = list(self.dots[dots[0]].center) + list(self.dots[dots[1]].center)
            # add half dot radius to x and y for the line because the dot position is measured from bottom left corner of dots (the radius of the dot is 10)
            # The amount added to i should be half of the dot_size set in the dotsandboxes.kv file
            Color(1, 0, 0) if turn == "X" else Color(0,0,1)

            line = Line(points=points, width=3, group="lines")
            self.lines.append((line, dots[0], dots[1]))

    def draw_captured_box(self, box_index, turn):

        # map box index to quadrant of grid
        boxes = {
            14:(0,1,3,4), # bottom left box
            15:(1,2,4,5), # bottom right box
            13:(4,5,8,7), # top right box
            12:(4,3,6,7) # top left box
        }

        avg_x, avg_y = 0, 0
        for dot_index in boxes[box_index]:
            #print(self.dots[dot_index].pos[0], self.dots[dot_index].pos[1])
            avg_x += self.dots[dot_index].center_x
            avg_y += self.dots[dot_index].center_y
        avg_x /= 4
        avg_y /= 4
        #print("Averages", avg_x, avg_y)
        color = "red" if turn == "X" else "blue"
        captured_box = Image(source=f"./images/dotsandboxes/{color}_{turn}.png", pos_hint={"center_x": avg_x/self.width, "center_y": avg_y/self.height})
        self.captured_boxes.append(captured_box)
        self.add_widget(captured_box)

    def is_full(self):
        """Display pop up when the board fills
        Called from within the self.board property of type BoardEnvironment

        Returns:
            None: Pop up that displays end condition of the game
            win
            lose
            tie
        """
        tie = self.ai_score == self.score

        winner_popup = Popup(size_hint=(0.8, 0.9), title="Winner Popup")
        # self.board.score_board is a dictionary of str:int with keys "X" and "O"
        if self.score > self.ai_score:
            text = "You are the winner"
            winner = True
        elif self.ai_score > self.score:
            text = "Computer is the winner"
            winner = False
        else:# it's a tie
            text = "There was a tie!"
            winner = None
        if self.match != "League Match":
            content = Button(text=text)
            self.board_env.print_board()
            content.bind(on_press=winner_popup.dismiss)
        else:
            content = Button(text=text)

            def play_on(pop_up_self):
                """functionality of button that shows at the end of a league Game

                Args:
                    pop_up_self (Button): This argument is the "self" call for the button we are binding the function to
                """
                self.league_env.play_pair_pt_2(winner, tie=tie)
                self.restart()
                self.league_env.play_pair(self.first_league_run)
                winner_popup.dismiss()

            content.bind(on_press=play_on)
        winner_popup.add_widget(content)
        winner_popup.open()

    def bet_options(self, options, message, func, AI_choice, cols=1):
        # creating grid for popup menu
        content = GridLayout(cols=cols, padding=50, spacing=50)
        # returning early if no options were passed to this function
        if len(options) == 0:
            return False
        # adding a button for each option with the option's text
        for option in options:
            content.add_widget(Button(text=option))
        # creating a popup with 'message' and 'content'
        option_popup = Popup(title=message, content=content, size_hint=(.8, .9), auto_dismiss=False)
        # function that will be called when a button is clicked
        def option_button(inner_self):
            # dismiss popup
            option_popup.dismiss()
            # grabbing the selected option
            result = inner_self.text
            # calling 'func' with the selected option and the AI's option
            self.league_env.play_pair_pt_1_5(result, AI_choice)
        # binding option_button button to each option
        for child in content.children:
            child.bind(on_press=option_button)
        # opening popup
        option_popup.open()

    def check_dot_pair(self, start, end):
        """Gives back the line index based on the pair of dots in kivy regardless of order from (dict) self.actual_lines

        Args:
            start (int): start dot index
            end (int): end dot index

        Returns:
            int: The index that cooresponds to a line in the BoardEnvironment's representation of the game board
        """
        try:
            return self.actual_lines[(start, end)]
        except KeyError:
            return self.actual_lines[(end, start)]

    def clear_game_screen(self):
        """Remove caputured box icons from screen
        """
        # clear lines
        self.game_grid.canvas.remove_group("lines")

        # clear captured boxes
        for box in self.captured_boxes:
            box.parent.remove_widget(box)
        self.captured_boxes = []

    def series_end(self, message):
        """Called when league series ends and displays final league betting information

        Args:
            message (str): Message sent by LeagueEnvironment with final betting info
        """
        content = GridLayout(cols=1, padding=50, spacing=50)
        content.add_widget(Button(text="Play again"))
        content.add_widget(Button(text="Return to menu"))
        series_end_popup = Popup(title=message, content=content, size_hint=(.8,.6 ), auto_dismiss=False)
        def play_again_button(inner_self):
            series_end_popup.dismiss()
            self.restart()
            self.clear_game_screen()
            self.league_env.play_pair(True)
        def end_game_button(inner_self):
            series_end_popup.dismiss()
            self.menu()
        content.children[1].bind(on_press=play_again_button)
        content.children[0].bind(on_press=end_game_button)
        series_end_popup.open()


import sys
from pathlib import Path
import click
from pptx import Presentation
import csv

def read_pptx(pptxpath, csvwriter):
    prs = Presentation(pptxpath)
    for i, slide in enumerate(prs.slides):
        for j, shape in enumerate(slide.shapes):
            if hasattr(shape, 'text'):
                csvwriter.writerow([pptxpath, i, j, '-',  shape.text])
            elif shape.has_table:
                for k, cell in enumerate(shape.table.iter_cells()):
                    csvwriter.writerow([pptxpath, i, j, k, cell.text])

@click.command()
@click.argument('src_pptx_path')
@click.argument('out_csv_file', type=click.File(mode='w', encoding='utf_8_sig'))
def extract_text_from_pptxs(src_pptx_path, out_csv_file):
    src_pptx_files = Path(src_pptx_path).glob('**\*.pptx')
    csvwriter = csv.writer(out_csv_file, lineterminator='\n')
    csvwriter.writerow(['filename', 'slide', 'shape', 'cell', 'text'])
    for src_pptx_file in src_pptx_files:
        try:
            read_pptx(src_pptx_file, csvwriter)
        except Exception as e:
            print(str(src_pptx_file) + ' cannot read : ' + str(e), file=sys.stderr)
    return

if __name__ == '__main__':
    extract_text_from_pptxs()


import copy
from typing import Any, Dict, List

class Parameters(object):
    __slots__ = ('_parameters', )

    def __init__(self, parameters: Dict[str, Any]):
        self._parameters = parameters

    def __getattr__(self, _name: str):
        if _name in self._parameters:
            return copy.deepcopy(self._parameters[_name])

        raise AttributeError("'Parameters' object has no attribute '{}'".format(_name))

    def __getitem__(self, key: str):
        if key in self._parameters:
            return copy.deepcopy(self._parameters[key])

        raise KeyError

    def __str__(self):
        return str(self._parameters)

    def __repr__(self):
        return self.__str__()

    def __iter__(self):
        for key in self._parameters.keys():
            yield key, self._parameters[key]

    def get(self, key: str, default: Any = None):
        if key in self._parameters:
            return copy.deepcopy(self._parameters[key])

        return default

    def has(self, key: str) -> bool:
        return key in self._parameters

    def all(self) -> dict:
        return copy.deepcopy(self._parameters)

    def extend(self, additional_parameters: dict):
        self._parameters.update(additional_parameters)

    @classmethod
    def from_cli_params(cls, parameters: List[str]) -> "Parameters":
        dict_parameters = {}
        key_name = None
        value = None
        for parameter in parameters:
            if parameter.startswith("-"):
                if key_name is not None:
                    if value is None:
                        value = True
                    dict_parameters[key_name] = value
                    value = None
                key_name = parameter.lstrip("-")
            elif key_name is not None:
                print(parameter)
                if value is None:
                    value = parameter
                elif isinstance(value, list):
                    value.append(parameter)
                else:
                    value = [value, parameter]

        if key_name is not None:
            if value is None:
                value = True
            dict_parameters[key_name] = value

        return cls(dict_parameters)


#!/usr/bin/python3
"""Base Module"""

import csv
import json
from typing import List, Dict
import turtle

class Base:
    """This module defines a base class."""
    __nb_objects = 0

    def __init__(self, id: int = None) -> None:
        """Constructor method that assigns the public instance attribute 'id'.

        Args:
            id (int): An integer value used to manage IDs in this project.

        Returns:
            None.
        """
        if id is not None:
            self.id = id
        else:
            Base.__nb_objects += 1
            self.id = Base.__nb_objects

    @staticmethod
    def to_json_string(list_dictionaries: List[Dict]) -> str:
        """Return the JSON serialization of a list of dictionaries.

        This method takes a list of dictionaries and returns its JSON representation.

        Args:
            list_dictionaries (list): A list of dictionaries to be serialized.

        Returns:
            str: JSON representation of the input list of dictionaries.
        """
        if list_dictionaries is None or list_dictionaries == []:
            return "[]"
        return json.dumps(list_dictionaries)

    @classmethod
    def save_to_file(cls, list_objs):
        """Serialize a list of objects and save it to a JSON file.

        Args:
            list_objs (list): A list of objects derived from Base class.
        """
        file_name = cls.__name__ + ".json"
        with open(file_name, "w") as jsonfile:
            if list_objs is None:
                json.dump([], jsonfile)
            else:
                list_dicts = [o.to_dictionary() for o in list_objs]
                json.dump(list_dicts, jsonfile)

    @staticmethod
    def from_json_string(json_string):
        """
        Convert a JSON string into a Python list.

        Args:
            json_string (str): The JSON string to be converted, representing a list of dictionaries.

        Returns:
            list: The Python list represented by the input JSON string. If the input string is None or empty,
                  an empty list is returned.
        """
        if json_string is None or json_string == "[]":
            return []

        try:
            return ast.literal_eval(json_string)
        except (ValueError, SyntaxError):
            return []

    @classmethod
    def create(cls, **attributes):
        """Return a class instantiated from a dictionary of attributes.

        Args:
            **attributes (dict): Key/value pairs of attributes to initialize the class.
        """
        instance = cls(1, 1) if cls.__name__ == "Rectangle" else cls(1)
        instance.update(**attributes)
        return instance

    @classmethod
    def load_from_file(cls):
        """
        Return a list of class instances created from a JSON file.

        Reads from a JSON file named '<ClassName>.json', where 'ClassName'
        is the name of the class in snake_case format.

        Returns:
            If the file does not exist - an empty list.
            Otherwise - a list of instantiated classes from the JSON data.
        """
        filename = f"{cls.__name__}.json"
        try:
            with open(filename, "r") as jsonfile:
                list_dicts = json.load(jsonfile)
                return [cls.create(**d) for d in list_dicts]
        except FileNotFoundError:
            return []

    @classmethod
    def save_to_file_csv(cls, list_objs):
        """Serialize a list of objects to a CSV file.

        Args:
            list_objs (list): A list of objects that inherit from the Base class.
            filename (str): The name of the file to write the CSV data to.
        """
        filename = f"{cls.__name__}.csv"
        with open(filename, "w", newline="") as csvfile:
            if not list_objs:
                csvfile.write("[]")
            else:
                fieldnames = ["id", "width", "height", "x", "y"] if cls.__name__ == "Rectangle" else ["id", "size", "x", "y"]
                writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
                writer.writeheader()
                for obj in list_objs:
                    writer.writerow(obj.to_dictionary())
    @classmethod
    def load_from_file_csv(cls):
        """Return a list of class objects instantiated from a CSV file.

        The function reads from '<cls.__name__>.csv' where 'cls' is the class name.

        Returns:
            If the file does not exist - an empty list.
            Otherwise - a list of class objects instantiated from the CSV file.
        """
        filename = f"{cls.__name__}.csv"
        try:
            with open(filename, "r", newline="") as csvfile:
                fieldnames = ["id", "width", "height", "x", "y"] if cls.__name__ == "Rectangle" else ["id", "size", "x", "y"]
                reader = csv.DictReader(csvfile, fieldnames=fieldnames)
                next(reader)  # Skip the header row
                list_dicts = [dict([(k, int(v)) for k, v in d.items()]) for d in reader]
                return [cls.create(**d) for d in list_dicts]
        except FileNotFoundError:
            return []


"""
Created on Tuesday 20 February 2018
Last update: Sunday 11 March 2018

@author: Michiel Stock
michielfmstock@gmail.com

Solution for the lecture optimal transport
"""

from itertools import permutations
import numpy as np
from sklearn.metrics.pairwise import pairwise_distances
from random import shuffle

blue = '#264653'
green = '#2a9d8f'
yellow = '#e9c46a'
orange = '#f4a261'
red = '#e76f51'
black = '#50514F'

def shuffle_rows(X):
    """
    Randomly shuffles rows of a numpy matrix
    """
    n, _ = X.shape
    indices = list(range(n))
    shuffle(indices)
    X[:] = X[indices]

def monge_brute_force(C):
    """
    Solves the Monge assignment problem using
    brute force.

    Inputs:
        - C: cost matrix (square, size n x n)

    Outputs:
        - best_perm: optimal assigments (list of n indices matching the rows
                to the columns)
        - best_cost: optimal cost corresponding to the best permutation

    DO NOT USE FOR PROBLEMS OF A SIZE LARGER THAN 12!!!
    """
    n, m = C.shape
    assert n==m  # C should be square
    best_perm = None
    best_cost = np.inf
    for perm in permutations(range(n)):
        cost = 0
        for i, p_i in enumerate(perm):
            cost += C[i, p_i]
        if cost < best_cost:
            best_cost = cost
            best_perm = perm
    return best_perm, best_cost

def compute_optimal_transport(C, a, b, lam, epsilon=1e-8,
                verbose=False, return_iterations=False):
    """
    Computes the optimal transport matrix and Slinkhorn distance using the
    Sinkhorn-Knopp algorithm

    Inputs:
        - C : cost matrix (n x m)
        - a : vector of marginals (n, )
        - b : vector of marginals (m, )
        - lam : strength of the entropic regularization
        - epsilon : convergence parameter
        - verbose : report number of steps while running
        - return_iterations : report number of iterations till convergence,
                default False

    Output:
        - P : optimal transport matrix (n x m)
        - dist : Sinkhorn distance
        - n_iterations : number of iterations, if `return_iterations` is set to
                        True
    """
    n, m = C.shape
    P = np.exp(- lam * C)
    iteration = 0
    while True:
        iteration += 1
        u = P.sum(1)  # marginals of rows
        max_deviation = np.max(np.abs(u - a))
        if verbose: print('Iteration {}: max deviation={}'.format(
                            iteration, max_deviation
                        ))
        if max_deviation < epsilon:
            break
        # scale rows
        P *= (a / u).reshape((-1, 1))
        # scale columns
        P *= (b / P.sum(0)).reshape((1, -1))
    if return_iterations:
        return P, np.sum(P * C), iteration
    else:
        return P, np.sum(P * C)

def barrycenters_entropic(B, C, weights, lam=1, L=100):
    """
    Finds a barrycenter of Sinkhorn distances for a set of
    proabiltiy vectors (of same dimension) with a cost matrix
    and a set of weigths.

    Inputs:
        - B: matrix containing the probability vectors (n x s), positive
            and normalized
        - C: cost matrix (n x n), same everywhere
        - weigths: s weights for the relative importance
        - lam: strength of the entropic regularization
        - L: number of Sinkhorn updates

    Output:
        - a: barrycenter (probability vector)
    """
    n, s = B.shape
    K = np.exp(- lam * C)
    U = np.ones_like(B) / n
    V = np.zeros_like(B)
    a = np.zeros((n, 1))
    for step in range(L):
        # update V
        V[:] = B / (K.T  @ U)
        # update a
        a[:] = np.exp(np.sum(np.log(K @ V) * weights.reshape((1, s)), axis=1, keepdims=True))
        U[:] = a / (K @ V)
    return a

if __name__ == '__main__':
    import matplotlib.pyplot as plt
    B = np.zeros((10, 2))
    B[:5,0] = 0.2
    B[5:,1] = 0.2

    C = pairwise_distances(np.arange(10).reshape((-1,1)),
                metric='sqeuclidean')

    A = np.zeros((10, 11))
    for i in range(11):
        A[:,i] = barrycenters_entropic(B, C, np.array((1-i/10, 0+i/10)))[:,0]

    plt.imshow(A, interpolation='nearest')
    plt.savefig('Figures/simpleinterpol.png')

    # images
    dim = 50
    B = np.zeros((dim**2, 4))

    # square
    square = np.zeros((dim, dim))
    square[dim//4:-dim//4,:][:,dim//4:-dim//4] = 1
    B[:,0] = square.flatten()

    # circle
    circle = np.array([[(i-dim/2)**2+(j-dim/2)**2 < dim**2 / 4**2 for i in range(dim)]
        for j in range(dim)], dtype=float)
    circle -= np.array([[(i-dim/2)**2+(j-dim/2)**2 < dim**2 / 6**2 for i in range(dim)]
        for j in range(dim)], dtype=float)
    B[:,1] = circle.flatten()

    # diamond
    diamond = np.array([[np.abs(i-dim/2)+np.abs(j-dim/2) < dim/4 for i in range(dim)]
        for j in range(dim)], dtype=float)
    B[:,2] = diamond.flatten()

    """
    # cross
    cross = np.zeros((dim, dim))
    cross[dim//3:-dim//3,:] = 1
    cross[:, dim//3:-dim//3] = 1
    B[:,3] = cross.flatten()
    """

    # two blobs

    two_blobs = np.zeros((dim, dim))
    two_blobs[:] += np.array([[(i-dim/4)**2+(j-dim/4)**2 < (dim/10)**2 for i in range(dim)]
        for j in range(dim)], dtype=float)
    two_blobs[:] += np.array([[(i-dim/4*3)**2+(j-dim/4*3)**2 < (dim/10)**2 for i in range(dim)]
        for j in range(dim)], dtype=float)
    B[:,3] = two_blobs.flatten()

    B /= B.sum(0)

    C = pairwise_distances([[i/dim, j/dim] for i in range(dim)
    for j in range(dim)], metric="sqeuclidean")

    A = np.zeros((dim**2, 25))

    image_nr = 0
    for di in np.linspace(0, 1, 5):
        for dj in np.linspace(0, 1, 5):
            weights = np.array([
                    (1-di) * (1-dj),
                    di * (1-dj),
                    (1-di) * dj,
                    di * dj
            ])
            A[:,image_nr] = barrycenters_entropic(B, C, weights,
                            lam=500, L=50)[:,0]
            image_nr += 1

    fig, axes = plt.subplots(nrows=5, ncols=5)

    for i in range(5):
        for j in range(5):
            ax = axes[i, j]
            ax.imshow(A[:,i+5*j].reshape((dim,dim)) > 1e-5, cmap='Greys',
                            interpolation='nearest')
            ax.set_yticks([])
            ax.set_xticks([])
    plt.savefig('Figures/barrycenters.png')


import logging
from unittest import TestCase

from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from django.db.models import Q
from rest_framework.authentication import TokenAuthentication
from rest_framework.parsers import JSONParser
from rest_framework.views import APIView

from api_test.common import code
from api_test.common.api_response import JsonResponse
from api_test.common.common import get_availability_project, record_dynamic, objects_paginator
from api_test.common.config_http import test_api
from api_test.common.parameter_check import project_status_check, parameter_ids_check
from api_test.models import Project, AutomationTestCase, TestCaseGroup, GlobalHost, AutomationCaseApi, \
    AutomationTestResult
from api_test.serializer import TestCaseDeserializer, TestCaseSerializer, AutomationTestResultSerializer

def all_parameter_check(data):
    try:
        if not data['project_id'] or not data['case_name'] or not data['test_case_group_id']:
            return JsonResponse(code=code.CODE_PARAMETER_ERROR)
        if not isinstance(data['project_id'],int) or not isinstance(data['test_case_group_id'],int):
            return JsonResponse(code=code.CODE_PARAMETER_ERROR)
        same_name_case=AutomationTestCase.objects.filter(case_name=data['case_name'],project=data['project_id'])
        if same_name_case:
            return JsonResponse(code=code.CODE_EXIST_SAME_NAME,msg='存在同名用例名称')
    except KeyError:
        return JsonResponse(code=code.CODE_KEY_ERROR)

class AddCase(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def post(self,request):
        """
        添加测试用例
        :param request:
        :return:
        """
        data=JSONParser().parse(request)
        project=get_availability_project(data,request.user)
        result=all_parameter_check(data) if isinstance(project,Project) else project
        if result:
            return result
        with transaction.atomic():
            try:
                serializer = TestCaseDeserializer(data=data)
                if serializer.is_valid():
                    try:
                        group = TestCaseGroup.objects.get(id=data['test_case_group_id'], project=project.id)
                        serializer.save(project=project, test_case_group=group, user=User.objects.get(id=request.user.pk))
                    except KeyError:
                        serializer.save(project=project,user=User.objects.get(id=request.user.pk))
                    record_dynamic(project=project, _type='添加', operation_object='测试用例', user=request.user.pk,
                           data='新增测试用例 %s' % data['case_name'])
                    return JsonResponse(data={'case_id':serializer.data.get('id')},code=code.CODE_SUCCESS)
                return JsonResponse(code=code.CODE_Failed)
            except Exception:
                return JsonResponse(code=code.CODE_Failed)

class CaseList(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def get(self,request):
        """
        获取用例列表
        :param request:
        :return:
        """
        result=project_status_check(request)
        if result:
            return result
        project_id, group_id, name = request.GET.get('project_id'), request.GET.get(
            'test_case_group_id'), request.GET.get('name')
        if group_id:
            if not group_id.isdecimal():
                return JsonResponse(code=code.CODE_PARAMETER_ERROR)
            if name:
                objects=AutomationTestCase.objects.filter(project=project_id,case_name__contains=name,test_case_group=group_id).order_by('id')
            else:
                objects=AutomationTestCase.objects.filter(project=project_id,test_case_group=group_id).order_by('id')
        else:
            if name:
                objects = AutomationTestCase.objects.filter(project=project_id, case_name__contains=name).order_by('id')
            else:
                objects = AutomationTestCase.objects.filter(project=project_id).order_by('id')
        result = objects_paginator(request, objects)
        serialize = TestCaseSerializer(result['obm'], many=True)
        return JsonResponse(data={"data": serialize.data,
                                  "page": result['page'],
                                  "total": result['total']
                                  }, code=code.CODE_SUCCESS)

class DelCase(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def post(self,request):
        """
        删除测试用例
        :param request:
        :return:
        """
        data=JSONParser().parse(request)
        project=get_availability_project(data,request.user)
        result=parameter_ids_check(data) if isinstance(project,Project) else project
        if result:
            return result
        id_list=Q()
        for id in data['ids']:
            id_list=id_list|Q(id=id)
        case_list=AutomationTestCase.objects.filter(id_list,project=project.id)
        name_list=[]
        for i in case_list:
            name_list.append(str(i.case_name))
        with transaction.atomic():
            case_list.delete()
            record_dynamic(project=project.id,_type='删除',operation_object='测试用例',user=request.user.pk,data="删除测试用例：%s" % name_list)
            return JsonResponse(code=code.CODE_SUCCESS)

class UpdateCase(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def post(self,request):
        """
        修改用例
        :param request:
        :return:
        """
        data=JSONParser().parse(request)
        project=get_availability_project(data,request.user)
        result=all_parameter_check(data) if isinstance(project,Project) else project
        if result:
            return result
        try:
            case=AutomationTestCase.objects.get(id=data['id'],project=project.id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg='测试用例不存在')
        try:
            group=TestCaseGroup.objects.get(id=data['test_case_group_id'],project=project.id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg='用例分组不存在')
        serializer=TestCaseDeserializer(data=data)
        if serializer.is_valid():
            serializer.update(instance=case,validated_data=data)
            record_dynamic(project=project.id,_type='更新',operation_object='测试用例',user=request.user.pk,data="修改测试用例：%s" % data['case_name'])
            return JsonResponse(code=code.CODE_SUCCESS)
        return JsonResponse(code=code.CODE_Failed)

class UpdateCaseGroup(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def post(self,request):
        """
        更新用例的分组
        :param request:
        :return:
        """
        data=JSONParser().parse(request)
        project=get_availability_project(data,request.user)
        result=parameter_ids_check(data) if isinstance(project,Project) else project
        if result:
            return result
        try:
            group=TestCaseGroup.objects.get(id=data['test_case_group_id'],project=project.id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg='测试用例分组不存在')

        id_list=Q()
        for i in data['ids']:
            id_list=id_list|Q(id=i)
        case_list=AutomationTestCase.objects.filter(id_list,project=project.id)

        case_name=[]
        for j in case_list:
            case_name.append(j.case_name)

        with transaction.atomic():
            case_list.update(test_case_group=group)
            record_dynamic(project=project, _type='修改', operation_object='测试用例', user=request.user.pk,
                           data='修改测试用例分组 %s' % case_name)
            return JsonResponse(code=code.CODE_SUCCESS)

class Test(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def parameter_check(self,data):
        """
        校验参数
        :param data:
        :return:
        """
        try:
            if not data['project_id'] or not data['case_id'] or not data['id'] or not data['host_id']:
                return JsonResponse(code=code.CODE_PARAMETER_ERROR)
        except KeyError:
            return JsonResponse(code=code.CODE_PARAMETER_ERROR)

    def post(self,request):
        """
        执行
        :param request:
        :return:0
        """
        data=JSONParser().parse(request)
        project=get_availability_project(data,request.user)
        result=self.parameter_check(data) if isinstance(project,Project) else project
        if result:
            return result
        try:
            case=AutomationTestCase.objects.get(id=data['case_id'],project=project.id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg='测试用例不存在')
        try:
            host=GlobalHost.objects.get(id=data['host_id'],project=project.pk)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg="host不存在")
        try:
            case_api=AutomationCaseApi.objects.get(id=data['id'],automation_test_case=case.id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg="接口不存在")
        AutomationTestResult.objects.filter(automation_case_api=case_api.id).delete()
        try:
            result=test_api(host_id=host.id,case_id=case.id,_id=case_api.id,project_id=project.id)
        except Exception as e:
            logging.exception(e)
            return JsonResponse(code=code.CODE_Failed)
        record_dynamic(project=project, _type='测试', operation_object='用例接口', user=request.user.pk,
                       data="测试用例 %s 接口\"%s\""%(case.case_name,case_api.name))
        return JsonResponse(data={
            "result":result
        },code=code.CODE_SUCCESS)

class LookResult(APIView):
    authentication_classes = (TokenAuthentication,)
    permission_classes = ()

    def get(self,request):
        """
        获取测试结果
        :param request:
        :return:
        """
        project_id,case_id,api_id=request.GET.get('project_id'),request.GET.get('case_id'),request.GET.get('api_id')
        result=project_status_check(request)
        if result:
            return result
        try:
            AutomationTestCase.objects.get(id=case_id,project_id=project_id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg="用例不存在")
        try:
            AutomationCaseApi.objects.get(id=api_id,automation_test_case=case_id)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_OBJECT_NOT_EXIST,msg="接口不存在")
        try:
            data=AutomationTestResult.objects.get(automation_case_api=api_id)
            serializer=AutomationTestResultSerializer(data)
            return JsonResponse(data=serializer.data,code=code.CODE_SUCCESS)
        except ObjectDoesNotExist:
            return JsonResponse(code=code.CODE_Failed)


from picamera import PiCamera
import time
from datetime import datetime

# take foto
def captureImage(fname):
    camera = PiCamera()
    camera.resolution = (1024,768)
    camera.framerate = 30
    camera.vflip = True

    now = datetime.now()
    dt = now.strftime("%Y%m%d_%H:%M:%S")
    print(dt)
    try:
        camera.capture("fotos/"+fname+dt+".jpg") # ,use_video_port=True)
        time.sleep(5)
    finally:
        camera.close() #sleep(10)

def captureVideo():
    camera = PiCamera()
    camera.resolution = (1024,768)
    camera.framerate = 30
    camera.vflip = True

    now = datetime.now()
    dt = now.strftime("%Y%m%d_%H%M%S")
    camera.start_recording("filmpje_"+str(dt)+".h264")
    time.sleep(15)
    camera.stop_recording()
    camera.close()

imagelocation='/home/pi/raspi/fotos/'
while True:
    captureImage(imagelocation+"kweekkas_")
    time.sleep(60*(60-datetime.now().minute) ) # de tweede en volgende foto's worden op het hele uur genomen

''''
while True:
    now = datetime.now()
    dt = now.strftime("%Y%m%d_%H%M%S")
    pir.wait_for_motion()
    print("motion detected")
    camera.start_recording("filmpje_"+str(dt)+".h264")
    time.sleep(10)
    pir.wait_for_no_motion()
    print("no motion")
#    camera.stop_recording()

'''


import nltk
import pandas as pd
from nltk.corpus import wordnet
from nltk.stem import PorterStemmer, SnowballStemmer, LancasterStemmer
from nltk.stem import WordNetLemmatizer

from chester.util import ReportCollector, REPORT_PATH

def get_stemmer(name=None):
    if name == "porter":
        return PorterStemmer()
    elif name == "snowball":
        return SnowballStemmer("english")
    elif name == "lancaster":
        return LancasterStemmer()
    else:
        return PorterStemmer()

def lemmatize(text, lemmatizer=WordNetLemmatizer()):
    lemmas = []
    pos_tags = nltk.pos_tag(text)
    for word, pos_tag in pos_tags:
        pos = get_wordnet_pos(pos_tag)
        lemma = lemmatizer.lemmatize(word, pos)
        lemmas.append(lemma)
    return lemmas

def get_wordnet_pos(treebank_tag):
    if treebank_tag.startswith('J'):
        return wordnet.ADJ
    elif treebank_tag.startswith('V'):
        return wordnet.VERB
    elif treebank_tag.startswith('N'):
        return wordnet.NOUN
    elif treebank_tag.startswith('R'):
        return wordnet.ADV
    else:
        return wordnet.NOUN

def tokenize(text):
    tokens = nltk.word_tokenize(text)
    return tokens

# get port stemmer
port_stemmer = PorterStemmer()
# get word net lemmatizer
world_ner_lemmatizer = WordNetLemmatizer()

def stem(words, stemmer=port_stemmer):
    stemmed_words = [stemmer.stem(word) for word in words]
    return stemmed_words

def preprocess_df_text(text_column, preprocessing_options: dict):
    print_preprocessing_options(**preprocessing_options)
    return text_column.apply(lambda x: preprocess_text(x, **preprocessing_options))

def print_preprocessing_options(stemmer=None, lemmatizer=None, stem_flag=False, lemmatize_flag=False,
                                tokenize_flag=True):
    print("Preprocessing step:")
    if stem_flag:
        print("Stemming text using {} stemmer".format(stemmer.__class__.__name__))
    if lemmatize_flag:
        print("Lemmatizing text using {} lemmatizer".format(lemmatizer.__class__.__name__))
    if tokenize_flag:
        print("Tokenizing text")

def preprocess_text(text,
                    stemmer=None,
                    lemmatizer=None,
                    stem_flag=False,
                    lemmatize_flag=False,
                    tokenize_flag=True):
    if stem_flag and lemmatize_flag:
        raise ValueError("Both stemmer and lemmatizer cannot be applied. Please choose one.")

    if tokenize_flag:
        tokens = tokenize(text)
    else:
        tokens = text

    if stem_flag:
        stemmer = stemmer if stemmer is not None else PorterStemmer()
        stemmed_words = stem(tokens, stemmer=stemmer)
    else:
        stemmed_words = tokens

    if lemmatize_flag:
        lemmatized_words = lemmatize(stemmed_words,
                                     lemmatizer=lemmatizer if lemmatizer is not None else world_ner_lemmatizer)
    else:
        lemmatized_words = stemmed_words

    return ' '.join(lemmatized_words)

class TextPreprocessor:
    def __init__(self,
                 df: pd.DataFrame = None,
                 text_column: str = 'text',
                 stemmer=None,
                 lemmatizer=None,
                 stem_flag=True,
                 lemmatize_flag=False,
                 tokenize_flag=True):
        self.df = df
        self.text_column = text_column
        self.stemmer = stemmer
        self.lemmatizer = lemmatizer
        self.stem_flag = stem_flag
        self.lemmatize_flag = lemmatize_flag
        self.tokenize_flag = tokenize_flag

    def generate_report(self):
        report_str = ""
        if self.stem_flag:
            if self.stemmer:
                if self.stemmer == "snowball":
                    report_str += "Using Snowball stemmer, "
                else:
                    report_str += f"Using {self.stemmer} stemmer, "
            else:
                report_str += "Stemming text, "
        if self.lemmatize_flag:
            if self.lemmatizer:
                report_str += f"Using {self.lemmatizer} lemmatizer, "
            else:
                report_str += "Lemmatizing text, "
        if self.tokenize_flag:
            report_str += "Tokenizing text, "
        if report_str:
            report_str = report_str[:-2]
            title_to_print = f"The following preprocessing steps will be applied to the column '{self.text_column}': {report_str}."
            print(title_to_print)
            rc = ReportCollector(REPORT_PATH)
            rc.save_text(title_to_print)
        else:
            print("No preprocessing steps selected.")

def preprocess_text_df(text_preprocessor: TextPreprocessor):
    df = text_preprocessor.df
    text_column = text_preprocessor.text_column

    df[text_column] = df[text_column].apply(lambda x: preprocess_text(
        x,
        stemmer=text_preprocessor.stemmer,
        lemmatizer=text_preprocessor.lemmatizer,
        stem_flag=text_preprocessor.stem_flag,
        lemmatize_flag=text_preprocessor.lemmatize_flag,
        tokenize_flag=text_preprocessor.tokenize_flag,
    ))
    return df


from datetime import date
from telegram_bot_calendar import DetailedTelegramCalendar

YEAR = 'y'
MONTH = 'm'
DAY = 'd'
SELECT = "s"
GOTO = "g"
NOTHING = "n"
LSTEP = {'y': 'год', 'm': 'месяц', 'd': 'день'}

STEPS = {MONTH: DAY}

class Calendar(DetailedTelegramCalendar):

    first_step = MONTH
    days_of_week = {
        'ru': ["ПН", "ВТ", "СР", "ЧТ", "ПТ", "СБ", "ВС"],
    }
    months = MONTHS = {
        'ru': ["январь ", "февраль", "март", "апрель", "май", "июнь",
               "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"],
    }

    def __init__(self, calendar_id=0, min_date=None):
        locale = 'ru'
        max_day = date.today()
        if min_date is None:
            min_date = date(year=max_day.year, month=1, day=1)
        super(DetailedTelegramCalendar, self).__init__(calendar_id=calendar_id, locale=locale,
                                                       min_date=min_date, max_date=max_day)


import argparse
import time
from labeller.labeller import get_labeller
import numpy as np
from sklearn.metrics import accuracy_score
from calibrator.lm_ensemble import EnsembleCalibrator
from utils import evaluate_end_model, save_results, get_sampler, get_label_model, update_results, get_filter_probs, \
    preprocess_data, evaluate_label_quality, get_reviser, select_thresholds, estimate_cov_acc_tradeoff,\
    plot_performance, calibrate_lm_threshold

def run_trilabel(train_data, valid_data, test_data, args, seed):
    start = time.process_time()
    if args.evaluate:
        # evaluate label quality
        results = {
            "n_labeled": [],  # number of expert labeled data
            "n_sampled": [],  # number of expert labeled data + pseudo-labeled data
            "frac_labeled": [],  # fraction of expert labeled data
            "sampled_acc": [],  # accuracy of sampled data
            "label_acc": [],  # label accuracy
            "label_nll": [],  # label NLL score
            "label_brier": [],  # label brier score
            "label_coverage": [],  # covered fraction of training data
            "dp_coverage": [],  # fraction of training data covered by DP
            "al_coverage": [],  # fraction of training data covered by AL
            "al_acc": [],  # accuracy of AL model
            "test_acc": [],  # end model's test accuracy
            "test_f1": [],  # end model's test f1
            "golden_test_acc": np.nan,  # end model's test accuracy using golden labels
            "golden_test_f1": np.nan  # end model's test f1 using golden labels
        }
    else:
        results = {}

    if args.record_runtime:
        last_timestamp = start
        lm_time = 0   # time used for training label model
        al_time = 0   # time used for sampling and training AL model
        thres_time = 0  # time used for threshold selection
        perf_time = 0  # time used for estimating coverage-accuracy tradeoff

    train_act_probs, train_act_indices = None, None
    M = train_data.n_lf
    covered_train_data = train_data.get_covered_subset()
    if args.calibration is None:
        label_model = get_label_model(args.label_model, seed=seed)
        label_model.fit(dataset_train=covered_train_data,
                        dataset_valid=valid_data)
    elif args.calibration == "EN":
        label_model = EnsembleCalibrator(train_data, valid_data, args.label_model,
                                         ensemble_size=10, feature_size=M, bootstrap=False, seed=seed)
    elif args.calibration == "EN+FS":
        feature_size = np.floor(M * 0.8).astype(int)
        label_model = EnsembleCalibrator(train_data, valid_data, args.label_model,
                                         ensemble_size=10, feature_size=feature_size,bootstrap=False, seed=seed)
    else:
        feature_size = np.floor(M * 0.8).astype(int)
        label_model = EnsembleCalibrator(train_data, valid_data, args.label_model,
                                         ensemble_size=10, feature_size=feature_size, bootstrap=True, seed=seed)

    if args.record_runtime:
        cur_timestamp = time.process_time()
        lm_time += cur_timestamp - last_timestamp
        last_timestamp = cur_timestamp

    if args.evaluate:
        lm_train_probs = label_model.predict_proba(covered_train_data)
        lm_train_preds = np.argmax(lm_train_probs, axis=1)
        if args.use_soft_labels:
            pred_train_labels = lm_train_probs
        else:
            pred_train_labels = lm_train_preds
        label_acc, label_nll, label_brier = evaluate_label_quality(covered_train_data.labels, lm_train_probs)
        label_coverage = len(covered_train_data) / len(train_data)
        perf, _ = evaluate_end_model(covered_train_data, pred_train_labels, valid_data, test_data, args, seed)
        update_results(results, n_labeled=0, frac_labeled=0.0,n_sampled=0, sampled_acc=np.nan,
                       label_acc=label_acc, label_nll=label_nll, label_brier=label_brier,
                       label_coverage=label_coverage,dp_coverage=label_coverage, al_coverage=0.0,
                       al_acc=np.nan, test_acc=perf["test_acc"], test_f1=perf["test_f1"])
        golden_perf, _ = evaluate_end_model(train_data, train_data.labels, valid_data, test_data, args, seed)
        results["golden_test_acc"] = golden_perf["test_acc"]
        results["golden_test_f1"] = golden_perf["test_f1"]

    labeller = get_labeller(args.labeller)
    encoder = None
    reviser = get_reviser(args.revision_model, train_data, valid_data, encoder, args.device, seed)

    if args.sampler in ["tri-pl", "tri-random", "tri-pl+random"]:
        lm_threshold = calibrate_lm_threshold(valid_data, label_model)
        valid_class_dist = np.bincount(valid_data.labels) / len(train_data)
        init_method = args.sampler[4:]
        sampler = get_sampler(args.sampler, train_data, labeller, label_model=label_model, revision_model=reviser,
                              valid_class_dist=valid_class_dist, init_method=init_method, lm_threshold=lm_threshold)
    else:
        sampler = get_sampler(args.sampler, train_data, labeller, label_model=label_model, revision_model=reviser)
    n_labeled = 0
    while n_labeled < args.sample_budget:
        n_to_sample = min(args.sample_budget - sampler.get_n_sampled(), args.sample_per_iter)
        sampler.sample_distinct(n_to_sample)
        indices, labels = sampler.get_sampled_points()
        n_labeled = sampler.get_n_sampled()
        frac_labeled = n_labeled / len(train_data)
        reviser.train_revision_model(indices, labels)
        sampler.update_stats()
        if args.record_runtime:
            cur_timestamp = time.process_time()
            al_time += cur_timestamp - last_timestamp
            last_timestamp = cur_timestamp

        cov_acc_dict = estimate_cov_acc_tradeoff(train_data, valid_data, label_model, reviser)
        candidate_theta_list = []
        cov_list = []
        acc_list = []
        epsilon_list = []
        for i in range(len(cov_acc_dict["lm_theta"])):
            for j in range(len(cov_acc_dict["rm_theta"])):

                acc = cov_acc_dict["acc"][i,j]
                epsilon = cov_acc_dict["epsilon"][i,j]
                cov = cov_acc_dict["cov"][i,j]
                if args.desired_label_acc is not None and acc < args.desired_label_acc:
                    continue
                if args.desired_label_cov is not None and cov < args.desired_label_cov:
                    continue

                idx = 0
                while idx < len(cov_list) and cov_list[idx] <= cov:
                    idx += 1

                if idx < len(cov_list) and acc_list[idx] >= acc:
                    # there exist a combination that have higher/same accuracy and higher coverage
                    continue

                if idx > 0 and cov_list[idx-1] == cov and acc_list[idx-1] >= acc:
                    # there exist a combination that have higher/same accuracy and same coverage
                    continue

                cov_list.insert(idx, cov)
                acc_list.insert(idx, acc)
                epsilon_list.insert(idx, epsilon)
                candidate_theta_list.insert(idx, (cov_acc_dict["lm_theta"][i], cov_acc_dict["rm_theta"][j]))
                while idx > 0 and acc_list[idx-1] <= acc:
                    cov_list.pop(idx-1)
                    acc_list.pop(idx-1)
                    candidate_theta_list.pop(idx-1)
                    idx -= 1

        if args.record_runtime:
            cur_timestamp = time.process_time()
            perf_time += cur_timestamp - last_timestamp
            last_timestamp = cur_timestamp

        if len(candidate_theta_list) == 0:
            # No configuration can satisfy user provided constraints at current label budget
            print(f"Constraints cannot be satisfied at label budget {n_labeled}. Increasing sample size.")
            if args.evaluate:
                indices, labels = sampler.get_sampled_points()
                sampled_acc = accuracy_score(np.array(train_data.labels)[indices], labels)
                update_results(results, n_labeled=n_labeled, frac_labeled=frac_labeled, n_sampled=len(indices),
                               sampled_acc=sampled_acc, label_acc=np.nan, label_nll=np.nan, label_brier=np.nan,
                               label_coverage=np.nan, dp_coverage=np.nan, al_coverage=np.nan,
                               al_acc=np.nan, test_acc=np.nan, test_f1=np.nan)
            continue

        if args.optimize_target == "f1":
            selected_theta, valid_f1, test_f1 = select_thresholds(train_data, valid_data, test_data,
                                                                   candidate_theta_list,
                                                                   label_model, reviser, args, seed)

            if args.plot_performance:
                theta_indices = np.nonzero(valid_f1 > 0)[0]
                evaluated_valid_f1 = valid_f1[theta_indices]
                evaluated_test_f1 = test_f1[theta_indices]
                evaluated_cov = np.array(cov_list)[theta_indices]
                evaluated_acc = np.array(acc_list)[theta_indices]
                evaluated_epsilon = np.array(epsilon_list)[theta_indices]
                gt_acc = np.zeros_like(evaluated_acc)
                for i in range(len(theta_indices)):
                    lm_theta, rm_theta = candidate_theta_list[theta_indices[i]]
                    train_probs, train_act_indices = get_filter_probs(train_data, label_model, reviser, lm_theta, rm_theta)
                    train_preds = np.argmax(train_probs, axis=1)
                    train_labels = np.array(train_data.labels)[train_act_indices]
                    gt_acc[i] = accuracy_score(train_labels, train_preds)

                plot_performance(args.dataset, evaluated_cov, evaluated_acc, evaluated_epsilon, gt_acc,
                                 evaluated_valid_f1, evaluated_test_f1, n_labeled, args.output_path)
        elif args.optimize_target == "accuracy":
            selected_theta = candidate_theta_list[0]  # select the one with highest label accuracy
        else:
            selected_theta = candidate_theta_list[-1]  # select the one with highest label coverage

        train_act_probs, train_act_indices = get_filter_probs(train_data, label_model, reviser,
                                                              selected_theta[0], selected_theta[1])
        if args.record_runtime:
            cur_timestamp = time.process_time()
            thres_time += cur_timestamp - last_timestamp
            last_timestamp = cur_timestamp

        if args.evaluate:
            indices, labels = sampler.get_sampled_points()
            sampled_acc = accuracy_score(np.array(train_data.labels)[indices], labels)
            lm_train_conf = np.max(lm_train_probs, axis=1)
            dp_coverage = np.sum(lm_train_conf >= selected_theta[0]) / len(train_data)
            al_coverage = len(train_act_indices) / len(train_data) - dp_coverage
            al_preds = reviser.predict(train_data)
            al_acc = accuracy_score(train_data.labels, al_preds)
            pred_train_data = train_data.create_subset(train_act_indices)
            label_acc, label_nll, label_brier = evaluate_label_quality(pred_train_data.labels, train_act_probs)
            label_coverage = len(pred_train_data) / len(train_data)
            if args.use_soft_labels:
                pred_train_labels = train_act_probs
            else:
                pred_train_labels = train_act_probs.argmax(axis=1)
            perf, _ = evaluate_end_model(pred_train_data, pred_train_labels, valid_data, test_data, args, seed)
            update_results(results, n_labeled=n_labeled, frac_labeled=frac_labeled,n_sampled=len(indices),
                           sampled_acc=sampled_acc, label_acc=label_acc, label_nll=label_nll, label_brier=label_brier,
                           label_coverage=label_coverage, dp_coverage=dp_coverage, al_coverage=al_coverage,
                           al_acc=al_acc, test_acc=perf["test_acc"], test_f1=perf["test_f1"])

    end = time.process_time()
    results["time"] = end - start
    if args.record_runtime:
        results["lm_time"] = lm_time
        results["al_time"] = al_time
        results["perf_time"] = perf_time
        results["thres_time"] = thres_time

    return train_act_probs, train_act_indices, results

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    # device info
    parser.add_argument("--device", type=str, default="cuda:0")
    # dataset
    parser.add_argument("--dataset", type=str, default="youtube")
    parser.add_argument("--dataset_path", type=str, default="../datasets/")
    parser.add_argument("--extract_fn", type=str, default="bert")  # method used to extract features
    parser.add_argument("--max_dim", type=int, default=300)  # dimension reduction using PCA
    # User provided constraints
    parser.add_argument("--sample_budget", type=float, default=300)  # Total sample budget
    parser.add_argument("--desired_label_acc", type=float, default=None)  # Desired Label Accuracy
    parser.add_argument("--desired_label_cov", type=float, default=None)  # Desired Label Coverage
    parser.add_argument("--optimize_target", type=str, choices=["accuracy", "coverage", "f1"], default="f1")
    # active learning sample strategy and model
    parser.add_argument("--sampler", type=str, default="uncertain-rm")
    parser.add_argument("--sample_per_iter", type=float, default=100)  # Sample per iteration (batch size)
    parser.add_argument("--revision_model", type=str, default="mlp")
    # label model and end models
    parser.add_argument("--label_model", type=str, default="metal")
    parser.add_argument("--end_model", type=str, default="mlp")
    parser.add_argument("--em_epochs", type=int, default=100)
    parser.add_argument("--use_soft_labels", action="store_true")
    # other settings
    parser.add_argument("--calibration", type=str, choices=["EN", "EN+FS", "EN+FS+BS"], default=None)
    parser.add_argument("--evaluate", action="store_true")  # evaluate label quality and performance using golden labels
    parser.add_argument("--record_runtime", action="store_true")  # record runtime of every component
    parser.add_argument("--plot_performance", action="store_true")  # draw performance plot
    parser.add_argument("--theta_explore_strategy", type=str, default="step")
    parser.add_argument("--theta_explore_num", type=int, default=10)  # maximum number of evaluated theta thresholds

    parser.add_argument("--labeller", type=str, default="oracle")
    parser.add_argument("--metric", type=str, default="f1_macro")
    parser.add_argument("--repeats", type=int, default=10)
    parser.add_argument("--seed", type=int, default=42)
    parser.add_argument("--output_path", type=str, default="output/")
    parser.add_argument("--verbose", action="store_true")
    parser.add_argument("--tag", type=str, default="test")
    args = parser.parse_args()
    if args.record_runtime:
        args.evaluate = False  # No evaluation of label quality when recording pipeline runtime

    train_data, valid_data, test_data = preprocess_data(args)

    if args.sample_budget <= 1:
        args.sample_budget = np.ceil(args.sample_budget * len(train_data)).astype(int)
        args.sample_per_iter = np.ceil(args.sample_per_iter * len(train_data)).astype(int)
    else:
        args.sample_budget = int(args.sample_budget)
        args.sample_per_iter = int(args.sample_per_iter)

    np.random.seed(args.seed)
    run_seeds = np.random.randint(1, 100000, args.repeats)
    results_list = []
    for i in range(args.repeats):
        print(f"Start run {i}")
        _, _, results = run_trilabel(train_data, valid_data, test_data, args, seed=run_seeds[i])
        results_list.append(results)

    id_tag = f"trilabel_{args.label_model}_{args.end_model}_{args.sampler}_{args.tag}"
    save_results(results_list, args.output_path, args.dataset, f"{id_tag}.json")