import os

import click
import jinja2

import common

_global = common.get_global()
logger = common.get_logger()

_cachito_repo_path = common.get_cachito_repository_path()

def _new_template_interceptor(container_file_path: str, services: dict) -> str:
    """Intercept the Containerfile and create a new one with the Cachito instructions.

    Returns:
        str: The path to the new Containerfile
    """

    # Check if '#<cachito-env>' is present
    container_file_content = ""
    with open(container_file_path, "r") as f:
        container_file_content = f.read()

    # Check for "#<cachito-disable>" and "#<cachito-proxy>"
    lines = container_file_content.splitlines()
    cachito_disable_index = -1
    cachito_proxy_index = -1
    for i, line in enumerate(lines):
        line_clean = line.strip().replace(" ", "")
        if "#<cachito-disable>" in line_clean:
            cachito_disable_index = i
        if "#<cachito-proxy>" in line_clean:
            cachito_proxy_index = i
    if cachito_disable_index == -1:
        logger.fatal(
            "Cachito instructions not found in Containerfile. Aborting\n"
            "├─ Please add the following line to your Containerfile:\n"
            "├─ #<cachito-disable>"
        )
        exit(1)
    if cachito_proxy_index == -1:
        logger.fatal(
            "Cachito instructions not found in Containerfile. Aborting\n"
            "├─ Please add the following line to your Containerfile:\n"
            "├─ #<cachito-proxy>"
        )
        exit(1)

    if cachito_disable_index > cachito_proxy_index:
        logger.fatal(
            "The '#<cachito-disable>' instruction must be before the '#<cachito-proxy>' instruction. Aborting"
        )
        exit(1)

    # Create the template data
    template_data = {
        "container_file_content_before_disable": "\n".join(
            lines[:cachito_disable_index]
        ),
        "container_file_content_after_disable": "\n".join(
            lines[cachito_disable_index + 1 : cachito_proxy_index]
        ),
        "container_file_content_after_proxy": "\n".join(
            lines[cachito_proxy_index + 1 :]
        ),
    }
    template_data["custom_envs"] = {}
    for service in services.values():
        if "custom_envs" in service.keys():
            template_data["custom_envs"].update(service["custom_envs"])

    # Generate the new Containerfile
    template_string = """
{{ container_file_content_before_disable }}
#<cachito-disable> BEGIN
RUN set -x \\
    && echo "nameserver 1.1.1.1" > /etc/resolv.conf \\
    && echo "nameserver 8.8.8.8" > /etc/resolv.conf
#<cachito-disable> END
{{ container_file_content_after_disable }}
#<cachito-proxy> BEGIN
RUN set -x \\
    && rm -f /etc/resolv.conf
ENV PIP_NO_BINARY=:all:
{% for k, v in custom_envs.items() %}
ENV {{ k }}={{ v }}
{%- endfor %}
#<cachito-proxy> END
{{ container_file_content_after_proxy }}
"""
    template_env = jinja2.Environment(
        loader=jinja2.FileSystemLoader("."), autoescape=True
    )
    template = template_env.from_string(template_string)
    template_result = template.render(template_data)

    # Add new proxies to template_result
    # TODO create new proxies instead of using the "cachito-pip-proxy"
    template_result = template_result.replace("<PIP_REPO_NAME>", "cachito-pip-proxy")

    # Write the new Containerfile
    new_containerfile_path = os.path.abspath(
        os.path.join(os.path.dirname(container_file_path), "cachito.containerfile")
    )
    logger.info("Writing new Containerfile to: " + new_containerfile_path)
    with open(new_containerfile_path, "w") as f:
        f.write(template_result)

    return new_containerfile_path

def _build_validate(file, build_context):
    """Validate the build parameters"""

    # Check if Containerfile exists
    if not os.path.isfile(file):
        logger.error("Containerfile not found: " + file)
        exit(1)

    # Check if build context exists
    if build_context:
        if not os.path.isdir(build_context):
            logger.error("Build context not found: " + build_context)
            exit(1)
    else:
        build_context = os.path.dirname(os.path.abspath(file))

def _create_python_requirements_file(file_abs: str, repo_data: dict) -> None:
    def _generate_python_requirements_file(
        repo_data: dict,
        python_requirements_file_path: str,
    ) -> None:
        """Generate a requirements.txt file for a Python project"""
        dependencies_list = []
        for dependency in repo_data["dependencies"]:
            if dependency["format"] == "pypi":
                dependencies_list.append(
                    f"{dependency['name']}=={dependency['version']}"
                )
        dependencies_list.sort()

        out = ""
        for dependency in dependencies_list:
            out += f"{dependency}\n"
        with open(python_requirements_file_path, "w") as f:
            f.write(out)

    if os.path.isdir(file_abs):
        python_requirements_file_path = os.path.abspath(
            os.path.join(os.path.dirname(file_abs), "requirements.txt")
        )
    else:
        python_requirements_file_path = file_abs
    logger.info("Retrieving: " + python_requirements_file_path)
    _generate_python_requirements_file(repo_data, python_requirements_file_path)

def dump_dependencies_from_cachito_pip_proxy_to_file(
    cachito_repo_path: str,
    requirements_out: str,
):
    """Dump the dependencies list from the Cachito pip proxy repo to a file"""
    services = common.get_services(cachito_repo_path)
    repo_data = common._nexus_get_repo_data(services, "cachito-pip-proxy")
    _create_python_requirements_file(requirements_out, repo_data)

@click.command()
@click.option(
    "--clone-path",
    "-p",
    default=os.getcwd() + "/cache/cachito_repo",
    help="Path where the Cachito repository is located",
)
@click.option(
    "--file",
    "-f",
    default="./Containerfile",
    help="Path to the Containerfile to build",
)
@click.option(
    "--build-context",
    "-c",
    help="Path to the build context. Defaults to the directory of the Containerfile",
)
@click.option("--tag", "-t", required=True, help="Tag to apply to the built image")
@click.option(
    "--no-cache",
    is_flag=True,
    default=False,
    help="Do not use cache when building the container image (default: False)",
)
def cmd_build(clone_path, file, build_context, tag, no_cache):
    """Build a container image using Cachito servers"""
    _build_validate(file, build_context)

    additional_args = []
    if no_cache:
        additional_args.append("--no-cache")

    services = common.get_services(clone_path)
    networks = [service["network"] for service in services.values()]
    if len(set(networks)) != 1:
        logger.error("All services must use the same network")
        exit(1)

    file_abs = os.path.abspath(file)
    if build_context:
        build_context_abs = os.path.abspath(build_context)
    else:
        build_context_abs = os.path.dirname(file_abs)

    new_file_abs = _new_template_interceptor(file_abs, services)

    try:
        command = (
            [
                "podman",
                "build",
                "-f",
                new_file_abs,
                "--dns",
                "none",
            ]
            + additional_args
            + [
                "-t",
                tag,
                build_context_abs,
            ]
        )
        common.cmd_log(command)
        rc = os.system(" ".join(command))
        if rc != 0:
            raise Exception(f"Return code: {rc}")

    except Exception as e:
        logger.error("Error building image. Aborting")
        logger.error(e)
        exit(1)

    logger.info("Image built successfully")

    # TODO create new proxies instead of using the "cachito-pip-proxy"
    repo_data = common._nexus_get_repo_data(services, "cachito-pip-proxy")

    _create_python_requirements_file(file_abs, repo_data)

@click.command()
@click.option(
    "--clone-path",
    "-p",
    default=os.getcwd() + "/cache/cachito_repo",
    help="Path where the Cachito repository is located",
)
@click.option(
    "--file",
    "-f",
    default="./Containerfile",
    help="Path to the Containerfile to build",
)
@click.option(
    "--build-context",
    "-c",
    help="Path to the build context. Defaults to the directory of the Containerfile",
)
def cmd_get_dependencies(clone_path, file, build_context):
    """Get the dependencies files for a container build"""
    _build_validate(file, build_context)

    services = common.get_services(clone_path)
    # TODO create new proxies instead of using the "cachito-pip-proxy"
    repo_data = common._nexus_get_repo_data(services, "cachito-pip-proxy")
    file_abs = os.path.abspath(file)

    _create_python_requirements_file(file_abs, repo_data)

# cmd_pip_generate
# Params: requirements_file_in, requirements_file_out
@click.command()
@click.option(
    "--requirements-file-in",
    "-i",
    default="./requirements.txt",
    help="Path to the requirements file to be processed",
)
@click.option(
    "--requirements-file-out",
    "-o",
    default="./requirements-cachito.txt",
    help="Path to the requirements file to be generated",
)
def cmd_pip_generate(requirements_file_in, requirements_file_out):
    """Reads a requirements.txt file and generates a requirements-cachito.txt file
    with all the indirect dependencies of the requirements.txt file.
    """
    import cli_server

    # Check if server is running, start OR restart if needed
    cli_server._check_dependencies()

    requirements_file_in_abs = os.path.abspath(requirements_file_in)
    requirements_file_out_abs = os.path.abspath(requirements_file_out)

    if not os.path.isfile(requirements_file_in_abs):
        logger.error("requirements_file_in not found: " + requirements_file_in_abs)
        exit(1)

    # TODO create new proxies instead of using the "cachito-pip-proxy"
    repo_data = common._nexus_get_repo_data({}, "cachito-pip-proxy")

    dependencies_list = []
    for dependency in repo_data["dependencies"]:
        if dependency["format"] == "pypi":
            dependencies_list.append(f"{dependency['name']}=={dependency['version']}")
    dependencies_list.sort()

    out = ""
    for dependency in dependencies_list:
        out += f"{dependency}\n"
    with open(requirements_file_out_abs, "w") as f:
        f.write(out)

class Builder:
    config = None

    def __init__(self, config_file_path: str):
        self._load_config(config_file_path)

        # Create the workdir
        os.makedirs(self.config.workdir.path, exist_ok=True)

    def _load_config(self, config_file_path: str):
        """Load the config file and validate it"""
        self.config_file_path = config_file_path
        if not os.path.isfile(config_file_path):
            logger.error("Config file not found: " + config_file_path)
            exit(1)
        with open(config_file_path, "r") as f:
            config_file_content = f.read()
            import yaml

            config_data = yaml.safe_load(config_file_content)
        if not self._is_config_file_valid(config_data):
            logger.error("Config file is not valid: " + config_file_path)
            exit(1)
        self.config = common.dotdict(config_data)

        # Workdir must be an absolute path
        if self.config.workdir.path.startswith("/"):
            self.config.workdir.path = self.config.workdir.path.path
        else:
            _tmp_path = os.path.join(
                os.path.dirname(self.config_file_path),
                self.config.workdir.path,
            )
            self.config.workdir.path = os.path.abspath(_tmp_path)

    def _is_config_file_valid(self, config: dict):
        """Validate the config file"""
        from schema import And, Optional, Schema, SchemaError

        def _is_url(s):
            """Validates URL format"""
            import re

            match = "^https?:\/\/.*$"
            return bool(re.match(match, s))

        def _validate_name_n_version(s):
            """Validates name and version"""
            import re

            # format: <name>==<num>optional(.<num>)*2
            match = "^[a-zA-Z0-9\.\-_]+==[0-9]+(\.[0-9]+)*$"
            return bool(re.match(match, s))

        schema_template = Schema(
            {
                "kind": And(
                    str,
                    lambda s: s in ("container"),
                    error="Invalid kind. Valid values: [container]",
                ),
                "workdir": {
                    "path": And(str, len, error="Invalid path."),
                },
                "packageManagers": {
                    Optional("ansible"): {
                        Optional("collections"): [
                            And(
                                str,
                                len,
                                _validate_name_n_version,
                                error="Invalid name and version format. Valid example: community-general==6.2.0",
                            )
                        ],
                        Optional("roles"): [
                            And(
                                str,
                                len,
                                _validate_name_n_version,
                                error="Invalid name and version format. Valid example: cloudalchemy.node_exporter==2.0.0",
                            )
                        ],
                    },
                    Optional("python"): {
                        "pythonVersion": And(
                            str,
                            len,
                            error="Invalid pythonVersion. Valid example: ^3.9",
                        ),
                        "includeDependencies": And(bool),
                        "dependencies": [
                            And(
                                str,
                                len,
                                _validate_name_n_version,
                                error="Invalid name and version format. Valid example: ansible-core==2.14.11",
                            )
                        ],
                    },
                },
                "sources": [
                    {
                        "kind": And(
                            str,
                            lambda s: s in ("git"),
                            error="Invalid kind. Valid values: [git]",
                        ),
                        "url": And(
                            str,
                            len,
                            _is_url,
                            error="Invalid URL. Valid example: https://github.com/thenets/rinted-container.git",
                        ),
                        "ref": And(str, len, error="Invalid ref."),
                        "path": And(str, len, error="Invalid path."),
                    }
                ],
                "containers": [
                    {
                        "name": And(str, len),  # TODO: validate [a-z0-9\-_]
                        "imageName": And(str, len),
                        Optional("containerfilePath"): And(str, len),
                        Optional("containerfileContent"): And(str, len),
                        "restrictions": {
                            "disableDnsResolution": And(bool),
                        },
                        "proxies": {
                            "python": And(bool),
                            "golang": And(bool),
                        },
                        "sources_subpath": And(str, len),
                        "podmanCacheEnabled": And(bool),
                    }
                ],
            }
        )

        try:
            schema_template.validate(config)
        except SchemaError as e:
            logger.error(e)
            return False

        # Must have at one of the following: containerfilePath, containerfileContent
        for container in config["containers"]:
            try:
                path = container["containerfilePath"]
            except:
                path = ""
            try:
                content = container["containerfileContent"]
            except:
                content = ""
            if not path and not content:
                logger.error(f"Container: {container['imageName']}")
                logger.error("├─ ContainerfilePath and containerfileContent not found")
                logger.error("├─ One of the above must be present")
                logger.error("└─ Aborting...")
                exit(1)
            if path and content:
                logger.error(f"Container: {container['imageName']}")
                logger.error("├─ ContainerfilePath and containerfileContent found")
                logger.error("├─ Only one of the above must be present")
                logger.error("└─ Aborting...")
                exit(1)

        # TODO: each container.imageName must be unique

        return True

    def _pull_sources(self):
        """Pull the sources"""
        for source in self.config.sources:
            _source_path = os.path.join(
                self.config.workdir.path,
                "constructor/sources/",
                source.path,
            )
            if source.kind == "git":
                logger.info("Pulling source [git]: " + _source_path)
                common.git_pull(
                    source.url,
                    source.ref,
                    _source_path,
                )
            else:
                logger.error("Unsupported source kind: " + source.kind)
                exit(1)

    def _setup_package_managers(self):
        def _create_python_dependencies_files(in_dependencies) -> list:
            """Get the list of Python dependencies

            Use poetry to get the list of dependencies
            """
            _python_base_path = os.path.join(
                self.config.workdir.path,
                "constructor/packagemanager/python",
            )

            if not self.config.packageManagers.python.includeDependencies:
                # Write the dependencies to $WORKDIR/constructor/packagemanager/python/requirements-freeze.txt
                # but create the directory first
                os.makedirs(_python_base_path, exist_ok=True)
                with open(
                    os.path.join(_python_base_path, "requirements-freeze.txt"),
                    "w",
                ) as f:
                    f.write("\n".join(in_dependencies))

                return list(self.config.packageManagers.python.dependencies)

            # Use poetry
            # Create the pyproject.toml file
            _pyproject_toml_path = os.path.join(
                _python_base_path,
                "pyproject.toml",
            )
            logger.info("Creating pyproject.toml file: " + _pyproject_toml_path)
            template_string = """[tool.poetry]
name = "test"
version = "0.0.1"
description = "test"
authors = ["test"]

[tool.poetry.dependencies]
python = "{{ pythonVersion }}"
{% for dependency in dependencies -%}
{{ dependency }}
{% endfor %}

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
"""
            _parsed_dependencies = []
            for dependency in in_dependencies:
                _parsed_dependencies.append(
                    f'{dependency.split("==")[0]} = "{dependency.split("==")[1]}"'
                )
            print(_parsed_dependencies)
            template_data = {
                "pythonVersion": self.config.packageManagers.python.pythonVersion,
                "dependencies": _parsed_dependencies,
            }
            common.create_file_from_template(
                _pyproject_toml_path,
                template_string,
                template_data,
            )

            # Create extract-dependencies.sh
            _extract_dependencies_sh_path = os.path.join(
                _python_base_path,
                "extract-dependencies.sh",
            )
            logger.info(
                "Creating extract-dependencies.sh file: "
                + _extract_dependencies_sh_path
            )
            template_string = """#!/bin/sh
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
set -ex

export PYTHON_KEYRING_BACKEND=keyring.backends.null.Keyring
export PIP_NO_BINARY=:all:

cd $SCRIPT_DIR

rm -f ./requirements-freeze.txt
rm -f ./poetry.lock

python3 -m venv poetry-venv
./poetry-venv/bin/pip install -U pip
./poetry-venv/bin/pip install poetry
./poetry-venv/bin/pip install poetry-plugin-export
cat pyproject.toml
./poetry-venv/bin/poetry install --no-root --all-extras --compile --no-cache
./poetry-venv/bin/poetry export --without-hashes --all-extras --format=requirements.txt > ./requirements-freeze.txt
"""
            common.create_file_from_template(
                _extract_dependencies_sh_path,
                template_string,
                {},
            )
            common.run(["chmod", "+x", _extract_dependencies_sh_path])

            # Run extract-dependencies.sh
            logger.info("Running extract-dependencies.sh")
            common.run([_extract_dependencies_sh_path])

        if "ansible" in self.config.packageManagers:
            # TODO
            pass

        if "python" in self.config.packageManagers:
            _python_all_dependencies = _create_python_dependencies_files(
                self.config.packageManagers.python.dependencies
            )

    def _build_image(self, container: dict):
        """Build the container image"""
        _containerfile_path = None

        if not common.is_running(_cachito_repo_path):
            logger.error("Cachito server is not running")
            exit(1)

        # Pull the base image if content is not present
        if container.containerfilePath:
            _containerfile_path = os.path.join(
                self.config.workdir.path, container.containerfilePath
            )
            logger.info("Copying Containerfile: " + _containerfile_path)
            _original_file_path = os.path.join(
                os.path.dirname(self.config_file_path),
                container.containerfilePath,
            )
            common.run(["cp", _original_file_path, _containerfile_path])
        else:
            _containerfile_path = os.path.join(
                self.config.workdir.path, "base_image.containerfile"
            )
            logger.info("Creating Containerfile: " + _containerfile_path)
            with open(_containerfile_path, "w") as f:
                f.write(container.containerfileContent)

        # Build the image
        logger.info(f"Building image: {container.imageName}")
        _build_args = []
        if not container.podmanCacheEnabled:
            _build_args.append("--no-cache")
        if container.restrictions.disableDnsResolution:
            _build_args.append("--dns=none")
        common.run(
            [
                "podman",
                "build",
            ]
            + _build_args
            + [
                "-f",
                _containerfile_path,
                "-t",
                container.imageName,
                self.config.workdir.path,
            ],
            print_output=True,
        )

    def _build_proxy(self):
        # Create's the proxy script at
        # $WORKDIR/constructor/proxy/<container.name>/proxy.sh

        if not common.is_running(_cachito_repo_path):
            logger.error("Cachito server is not running")
            exit(1)

        services = common.get_services(_cachito_repo_path)
        networks = [service["network"] for service in services.values()]
        if len(set(networks)) != 1:
            logger.error("All services must use the same network")
            exit(1)

        template_string = """#!/bin/sh
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

# Get current -e and -x states
_e=$(set +o | grep -q 'errexit'; echo $?)
_x=$(set +o | grep -q 'xtrace'; echo $?)

set -ex

{% if container.proxies.python %}
#<cachito-proxy> BEGIN
export PIP_NO_BINARY=:all:
# Special vars
{%- for k, v in custom_envs.items() %}
export {{ k }}={{ v }}
{%- endfor %}
#<cachito-proxy> END
{% endif %}

# Restore -e and -x states
if [ $_e -eq 0 ]; then
    set -e
fi
if [ $_x -eq 0 ]; then
    set -x
fi
"""
        # Create the proxy script per container
        for container in self.config.containers:
            template_data = {}
            template_data["container"] = container
            template_data["custom_envs"] = {}
            for service in services.values():
                if "custom_envs" in service.keys():
                    template_data["custom_envs"].update(service["custom_envs"])

            _proxy_sh_path = os.path.join(
                self.config.workdir.path,
                "constructor/proxy",
                container.name,
                "proxy.sh",
            )
            logger.info("Creating proxy script: " + _proxy_sh_path)
            common.create_file_from_template(
                _proxy_sh_path,
                template_string,
                template_data,
                # Add new proxies to template_result
                # TODO create new proxies instead of using the "cachito-pip-proxy"
                lambda s: s.replace("<PIP_REPO_NAME>", "cachito-pip-proxy"),
            )

    def build(self):
        self._pull_sources()
        self._setup_package_managers()

        # HACK: if any self.config.proxies is true, then restart the proxies
        # _restart_proxies = any(self.config.proxies.values())
        _restart_proxies = False
        if _restart_proxies:
            import cli_server

            cli_server._check_dependencies()
            cli_server.restart(_cachito_repo_path)

        self._build_proxy()

        for container in self.config.containers:
            self._build_image(container)

        dump_dependencies_from_cachito_pip_proxy_to_file(
            _cachito_repo_path,
            os.path.join(
                self.config.workdir.path,
                "requirements-from-proxy.txt",
            ),
        )

        # Success message
        logger.info("Images built successfully")
        for container in self.config.containers:
            logger.info(f"├─ {container.imageName}")

@click.command()
@click.option(
    "--config-file",
    "-c",
    default="./constructor.yml",
    help="Path to the constructor config file",
)
def cmd_run(config_file):
    """creates a build from a constructor config file"""
    builder = Builder(config_file)
    logger.info("Workdir: " + builder.config.workdir.path)
    builder.build()

# Click
# ====================
def click_add_group(cli: click.Group) -> None:
    """Add the group to the CLI"""
    cmd_server = click.Group("builder", help="Container builder commands")
    cmd_server.add_command(name="run", cmd=cmd_run)
    cli.add_command(cmd_server)

{'yaml': 'yaml', 'And': 'schema.And', 'Optional': 'schema.Optional', 'Schema': 'schema.Schema', 'SchemaError': 'schema.SchemaError', 're': 're', 'cli_server': 'cli_server'}


# ras371@uw.edu
# Ryan Siu and Tyler Sverak
# 8 June 2019
# CSE 163 AC

# This file contains all the helper functions and main function answering
# question 1, "What makes a successful team?" and question 4,
# "How big (if any) of a factor is Home-Field advantage?"

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np

matches = pd.read_csv("matches.csv")
matches = matches.iloc[[1, 100, 1000, 2000, 3000, 4000, 5000,
                        6000]]
players = pd.read_csv("players.csv")
players = players.iloc[[62595, 3000, 5000, 7000, 15000, 25000, 40000,
                        54321, 60000]]
top30 = pd.read_csv("top30.csv")
tournaments = pd.read_csv("tournaments.csv")
top30Obj = {'Liquid': ['nitr0', 'NAF', 'EliGE', 'Stewie2K', 'Twistzz'],
            'Astralis': ['Xyp9x', 'dupreeh', 'gla1ve', 'device', 'Magisk'],
            'ENCE': ['allu', 'Aerial', 'xseveN', 'Aleksib', 'sergej'],
            'Vitality': ['NBK-', 'RpK', 'apEX', 'ALEX', 'ZywOo'],
            'FaZe': ['NEO', 'olofmeister', 'GuardiaN', 'NiKo', 'rain'],
            'Natus Vincere': ['Zeus', 'flamie', 's1mple', 'electronic',
                              'Edward'],
            'fnatic': ['Xizt', 'JW', 'twist', 'KRIMZ', 'Brollan'],
            'MIBR': ['FalleN', 'fer', 'coldzera', 'TACO', 'felps'],
            'NiP': ['f0rest', 'GeT_RiGhT', 'dennis', 'Lekr0', 'REZ'],
            'NRG': ['daps', 'tarik', 'Brehze', 'Ethan', 'CeRq'],
            'FURIA': ['arT', 'yuurih', 'VINI', 'KSCERATO', 'ableJ'],
            'Renegades': ['jks', 'AZR', 'jkaem', 'Gratisfaction', 'Liazz'],
            'G2': ['JaCkz', 'shox', 'kennyS', 'AmaNEk', 'Lucky'],
            'mousesports': ['karrigan', 'chrisJ', 'woxic', 'frozen', 'ropz'],
            'North': ['aizy', 'Kjaerbye', 'JUGi', 'gade', 'valde'],
            'AVANGAR': ['fitch', 'SANJI', 'buster', 'qikert', 'Jame'],
            'Valiance': ['huNter', 'LETN1', 'nexa', 'ottoNd', 'EspiranTo'],
            'Grayhound': ['Sico', 'dexter', 'erkaSt', 'malta', 'DickStacy'],
            'Windigo': ['bubble', 'v1c7oR', 'blocker', 'SHiPZ', 'poizon'],
            'Ghost': ['steel', 'freakazoid', 'koosta', 'WARDELL', 'neptune'],
            'OpTic': ['Snappi', 'MSL', 'k0nfig', 'niko', 'refrezh'],
            'BIG': ['gob b', 'tabseN', 'tiziaN', 'denis', 'XANTARES'],
            'Heroic': ['friberg', 'es3tag', 'NaToSaphiX', 'stavn', 'blameF'],
            'HellRaisers': ['ANGE1', 'oskar', 'loWel', 'ISSAA', 'nukkye'],
            'Cloud9': ['cajunb', 'RUSH', 'autimatic', 'vice', 'Golden'],
            'AGO': ['SZPERO', 'Furlan', 'GruBy', 'phr', 'kaper'],
            'Tricked': ['HUNDEN', 'Bubzkji', 'b0RUP', 'acoR', 'sjuush'],
            'MVP PK': ['zeff', 'XigN', 'HSK', 'xeta', 'stax'],
            'Sprout': ['syrsoN', 'Spiidi', 'mirbit', 'k1to', 'faveN'],
            'forZe': ['facecrack', 'almazer', 'FL1T', 'xsepower', 'Jerry']}

top30Teams = top30Obj.keys()

intl_teams = ['FaZe', 'mousesports', 'Valiance', 'HellRaisers']
dome_teams = ['Liquid', 'Astralis', 'ENCE', 'Vitality', 'Natus Vincere',
              'fnatic', 'MIBR', 'NiP', 'NRG', 'FURIA', 'Renegades', 'G2',
              'North', 'AVANGAR', 'Grayhound', 'Windigo', 'Ghost', 'OpTic',
              'BIG', 'Heroic', 'Cloud9', 'AGO', 'Tricked', 'MVP PK',
              'Sprout', 'forZe']

large_tourneys = ["FACEIT Major 2018",
                  'FACEIT Major 2018 Main Qualifier',
                  'ELEAGUE Major 2018',
                  'ELEAGUE Major 2018 Main Qualifier',
                  'IEM Katowice 2019 Main Qualifier',
                  'IEM Katowice 2019']

vowels = ["a", "e", "i", "o", "u"]

def get_current_lineups():
    """
    Returns a filtered DataFrame of player data that only includes data
    where a player is playing for the team they currently represent
    """
    out = []
    pf = players[players["team"].isin(top30Teams)]
    for index, row in pf.iterrows():
        # Make sure that we only use player data where a player is
        # playing for their current team
        if(row["name"] in top30Obj[row["team"]]):
            out.append(row)
    return pd.DataFrame(out)

current_lineups = get_current_lineups()

def main():
    # Should be 3.5 for Liquid since Nitr0 gets 3 and 4 openers over 2 games
    # Equaling and avg of 3.5
    a = plot_average_stat_per_team("opening_kills")
    print(players[players["team"] == "Liquid"]["opening_kills"].mean())
    print(a[a["name"] == "Liquid"]["opening_kills"])

    print("////////////////////////////////////////////////////")
    # Should be 2 for ENCE since ENCE is the only team in the top30 list and
    # have a total of 2 clutches over 1 match
    b = plot_average_stat_total_per_team("clutches")
    print(matches[matches["team1"] == "ENCE"]["team1_clutches"].values)
    print(b[b["team"] == "ENCE"])

    print("////////////////////////////////////////////////////")
    # Should print out a .5 winrate on nuke for Astralis, which means it should
    # print two rows of matches from the matches.csv
    c = plot_map_rate_per_team("Nuke")
    print(matches[(matches["winner"] == "Astralis") &
                  (matches["team1"] == "Astralis") | (matches["team2"] ==
                                                      "Astralis")])
    print(matches[(matches["winner"] != "Astralis") &
                  (matches["team1"] == "Astralis") | (matches["team2"] ==
                                                      "Astralis")])
    print(c[c["team"] == "Astralis"])

    print("////////////////////////////////////////////////////")

    # Similar  to the tests for question 1 & 4, this should return player
    # data for an American, Serbian, and Frenchman
    d = plot_country_representation()
    print(d)
    print(current_lineups)

    print("////////////////////////////////////////////////////")

    # Should print 18 for Heroic and 4 for INTZ
    e = compare_stat_advantage_per_match("kills")
    print(e)
    print(players[players["team"] == "Heroic"]["kills"].sum())
    print(players[players["team"] == "INTZ"]["kills"].sum())

def plot_average_stat_per_team(stat):
    """
    Plots a bar chart and regression line for a given stat average
    per team in the top 30 list
    """

    df = players.copy()

    # Ensure all our data is only top30 players
    df = df[df['team'].isin(top30["name"].unique())]

    # Edge case for KAST, must convert str 30% to float 30
    if stat == "kast":
        df["kast"] = df["kast"].apply(lambda x: float(x.strip("%")))

    # Groupby team stat averages
    df = df.groupby("team")[stat].mean()

    # Convert to DataFrame so we can merge with top30
    df = pd.DataFrame(df)
    df = df.merge(top30, left_on="team", right_on="name", how="outer")

    # Sort by ranking position
    df = df.sort_values(by="position")

    # Testing
    return df

def plot_average_stat_total_per_team(stat):
    """
    Plots a bar chart and regression line for a given stat
    sum averaged over every match played for each top30 team
    """

    # Ensure that the team in question is a top 30 team
    d1 = matches[matches['team1'].isin(top30["name"].unique())]
    d2 = matches[matches['team2'].isin(top30["name"].unique())]

    # Group by average stat per match
    d1 = d1.groupby("team1")["team1_" + stat].mean()
    d2 = d2.groupby("team2")["team2_" + stat].mean()

    # Convert groupby series to DF for merge
    d1 = pd.DataFrame(d1)
    d2 = pd.DataFrame(d2)

    # Merge
    df = d1.merge(d2, left_on=d1.index, right_on=d2.index, how="inner")

    # Make New Column
    df["avg"] = (df["team1_" + stat] + df["team2_" + stat])/2

    # Rename merged column
    df = df.rename(columns={"key_0": "team"})

    # Merge with top30 so we can order the rows by ranking
    df = df.merge(top30, left_on="team", right_on="name", how="inner")

    # Order rows by ranking
    df = df.sort_values(by="position")

    return df

def plot_player_stats_dist_per_team(stat):
    """
    Plots a strip(dot) plot of every player on a team and their stat average
    over every match, with a pointplot illustrating probability distribution
    """
    pf = current_lineups.copy()

    # Edge case for KAST
    if stat == "kast":
        pf["kast"] = pf["kast"].apply(lambda x: float(x.strip("%")))

    # Group by average stat per player
    pf = pf.groupby("name")[stat].mean()

    # Creating a dataframe we will use to merge player stat avg with their
    # respective teams
    name_team = current_lineups.copy()

    # Remove duplicates since we only care about player and their team
    name_team = name_team.drop_duplicates(["name", "team"], keep="first")
    name_team = name_team[["name", "team"]]

    # Merge dataframes so we have player stat average and their team
    pf = pd.DataFrame(pf).merge(name_team, left_on="name",
                                right_on="name", how="inner")

    # Merge with top30 so we have ranking positions
    pf = pf.merge(top30, left_on="team", right_on="name")
    pf = pf.rename(columns={"name_x": "name"})

    # Sort by position
    pf = pf.sort_values(by="position")

    # Plotting
    return pf

def plot_map_rate_per_team(map):
    """
    Plots winrate on a given map for each team with a
    regression line over
    """
    m = matches.copy()

    # Only get matches where a top30 team is present
    m = m[m['team1'].isin(top30["name"].unique()) |
          m['team2'].isin(top30["name"].unique())]

    # These maps aren't in the competitive map pool anymore; matches on them
    # aren't reflective of current performance
    deprecated = (m["map"] != "Cobblestone") & (m["map"] != "Cache")

    # Get dataframes where we only have rows where a specific team won or lost
    won1 = m[(m["team1"] == m["winner"]) & deprecated]
    loss1 = m[(m["team1"] != m["winner"]) & deprecated]
    won2 = m[(m["team2"] == m["winner"]) & deprecated]
    loss2 = m[(m["team2"] != m["winner"]) & deprecated]

    # Group dataframe by team, map, and count the wins
    won1 = won1.groupby(["team1", "map"])["winner"].count()
    won2 = won2.groupby(["team2", "map"])["winner"].count()
    loss1 = loss1.groupby(["team1", "map"])["winner"].count()
    loss2 = loss2.groupby(["team2", "map"])["winner"].count()

    # Push team name and map into columns
    won1 = pd.DataFrame(won1).reset_index()
    won2 = pd.DataFrame(won2).reset_index()
    loss1 = pd.DataFrame(loss1).reset_index()
    loss2 = pd.DataFrame(loss2).reset_index()

    # Rename columns
    won1 = won1.rename(columns={"team1": "team", "winner": "won"})
    won2 = won2.rename(columns={"team2": "team", "winner": "won"})
    loss1 = loss1.rename(columns={"team1": "team", "winner": "lost"})
    loss2 = loss2.rename(columns={"team2": "team", "winner": "lost"})

    # merge the wins and losses of team1 and team2 to get totals
    wins = won1.merge(won2, left_on=["team", "map"], right_on=[
                      "team", "map"], how="outer")
    losses = loss1.merge(loss2, left_on=["team", "map"], right_on=[
                         "team", "map"], how="outer")

    # NaN should be 0, for 0 wins/losses
    wins = wins.fillna(0)
    losses = losses.fillna(0)

    # Aggregate wins and losses
    wins["wins"] = wins["won_x"] + wins["won_y"]
    losses["losses"] = losses["lost_x"] + losses["lost_y"]

    # remove useless information
    wins = wins[["team", "map", "wins"]]
    losses = losses[["team", "map", "losses"]]

    # Merge wins and losses
    data = wins.merge(losses, left_on=["team", "map"], right_on=[
                      "team", "map"], how="outer")
    data = data.fillna(0)

    # Create winrate column
    data["winrate"] = data["wins"] / (data["wins"] + data["losses"])

    # Merge win/loss data with top30 data to sort by position
    data = data.merge(top30, left_on="team",
                      right_on="name").sort_values(by="position")

    return data

def compare_stats_intl_dom(stat):
    """
    Plots a bar graph comparing the top 4 international teams, the top 4
    single nationality teams, and the bottom 4 single nationality teams.
    (Of the top 30)
    """
    df = current_lineups.copy()

    # KAST edge case
    if stat == "kast":
        df["kast"] = df["kast"].apply(lambda x: float(x.strip("%")))

    # Splitting into 3 dataframes
    intl = df[df["team"].isin(intl_teams)]  # Top 4 international teams
    dome = df[df["team"].isin(dome_teams[:4])]  # Top 4 national teams
    dome_last = df[df["team"].isin(dome_teams[-4:])]  # Lowest 4 national teams

    # Grouping by avg of stat and coverting to dataframe
    intl = pd.DataFrame(intl.groupby("team")[stat].mean().reset_index())
    dome = pd.DataFrame(dome.groupby("team")[stat].mean().reset_index())
    dome_last = pd.DataFrame(dome_last.groupby("team")[
                             stat].mean().reset_index())

    # Adding team_type column to each dataframe
    intl["team_type"] = "International"
    dome["team_type"] = "Single Nationality (Top 4)"
    dome_last["team_type"] = "Single Nationality (Lowest 4)"

    # Combine all 3 dataframes
    stats = intl.append(dome).append(dome_last)

    return stats

def compare_stat_advantage_per_match(stat):
    # Get a copy of matches.csv with only the id and winner columns
    df = matches.copy()
    df = df[df["team1"].isin(top30["name"].unique()) |
            df["team2"].isin(top30["name"].unique())]
    df = df[["matchId", "winner"]]

    # Group by teams and matchId and sum up stats
    sum1 = players.groupby(["team", "matchId"])[stat].sum().reset_index()
    sum2 = sum1.copy()

    return sum1
    # The reason we have 2 sums is to ensure we get all the data, and avoid
    # duplicates
    sum1.columns = ["team1", "id", "team1_stat"]
    sum2.columns = ["team2", "id", "team2_stat"]

    # Merging sum1 and sum2 and removing duplicate matches
    sums = sum1.merge(sum2, left_on="id", right_on="id")
    sums = sums[sums["team1"] != sums["team2"]]
    sums = sums.drop_duplicates(subset="id", keep="first")

    # Merge in data from matches.csv so we know who won the match
    sums = sums.merge(df, left_on="id", right_on="matchId")

    won1 = sums[(sums["team1"] == sums["winner"]) & (
        sums["team1_stat"] > sums["team2_stat"])]
    loss1 = sums[(sums["team1"] != sums["winner"]) & (
        sums["team1_stat"] > sums["team2_stat"])]

    won2 = sums[(sums["team2"] == sums["winner"]) & (
        sums["team2_stat"] > sums["team1_stat"])]
    loss2 = sums[(sums["team2"] != sums["winner"]) & (
        sums["team2_stat"] > sums["team1_stat"])]

    # Group dataframe by team, map, and count the wins
    won1 = won1.groupby("team1")["winner"].count()
    won2 = won2.groupby("team2")["winner"].count()
    loss1 = loss1.groupby("team1")["winner"].count()
    loss2 = loss2.groupby("team2")["winner"].count()

    # Push team name into columns
    won1 = pd.DataFrame(won1).reset_index()
    won2 = pd.DataFrame(won2).reset_index()
    loss1 = pd.DataFrame(loss1).reset_index()
    loss2 = pd.DataFrame(loss2).reset_index()

    won1 = won1.rename(columns={"team1": "team", "winner": "won"})
    won2 = won2.rename(columns={"team2": "team", "winner": "won"})
    loss1 = loss1.rename(columns={"team1": "team", "winner": "lost"})
    loss2 = loss2.rename(columns={"team2": "team", "winner": "lost"})

    # merge the wins and losses of team1 and team2 to get totals
    wins = won1.merge(won2, left_on=["team"], right_on=["team"], how="outer")
    losses = loss1.merge(loss2, left_on=["team"], right_on=[
                         "team"], how="outer")
    wins = wins.fillna(0)
    losses = losses.fillna(0)

    # Aggregate win/loss data
    wins["wins"] = wins["won_x"] + wins["won_y"]
    losses["losses"] = losses["lost_x"] + losses["lost_y"]

    # Calculate winrates
    data = wins.merge(losses, left_on="team", right_on="team", how="outer")
    data["winrate"] = data["wins"] / (data["wins"] + data["losses"])

    # Merge with top30 for positional data
    data = data.merge(top30, left_on="team",
                      right_on="name").sort_values(by="position")

def get_tournaments_by_location(loc):
    """
    Takes a location name and returns a list of tournaments that took place
    at that location
    """
    return set(tournaments[tournaments[" location"].str.lower() == loc.lower()]
               ["name"].unique())

def plot_victories_tournament(team, tour, spec=None, name=None):
    """
    Takes a team name and a list of tournaments and plots that team's winrate
    at those tournaments versus their winrates at other tournaments per map. If
    spec if set not to None, the "away" matches will be those defined in spec
    as to all matches not in the tournament list
    """
    df = matches.copy()

    # These maps aren't played recently, matches on them aren't reflective of
    # current performance
    df = df[(df["map"] != "Cobblestone") & (df["map"] != "Cache")]

    # Ensure that we only look the specified team
    isTeam = (df["team1"] == team) | (df["team2"] == team)

    # Boolean series for whether or not the match was a win for the team
    winner = df["winner"] == team
    loser = df["winner"] != team

    # If spec is defined, we use spec as the "away" group
    if spec is None:
        at = df["tournament"].isin(tour)
        away = ~df["tournament"].isin(tour)
    else:
        at = df["tournament"].isin(tour)
        away = df["tournament"].isin(spec)

    # Mask and groupby to get counts of won and lost matches at home
    won_home = df[at & isTeam & winner]
    loss_home = df[at & isTeam & loser]
    won_home = (won_home.groupby("map")['winner'].count())
    loss_home = (loss_home.groupby("map")['winner'].count())

    # Mask and groupby to get counts of won and lost matches away from home
    won_away = df[away & isTeam & winner]
    loss_away = df[away & isTeam & loser]
    won_away = (won_away.groupby("map")['winner'].count())
    loss_away = (loss_away.groupby("map")['winner'].count())

    # Create subplots for plotting
    fig, [[ax1, ax2], [ax3, ax4]] = plt.subplots(2, figsize=(20, 12), ncols=2)
    if spec is None:
        ax1.set_title(team + ": At Home")
        ax2.set_title(team + ": Away From")
        ax3.set_title(team + ": Winrate At Home")
        ax4.set_title(team + ": Winrate Away From")
    else:
        ax1.set_title(team + ": At " + name)
        ax2.set_title(team + ": Away From " + name)
        ax3.set_title(team + ": Winrate At " + name)
        ax4.set_title(team + ": Winrate After " + name)

    # Plot win/loss per map at home
    total_home = pd.concat([won_home, loss_home], axis=1, sort=True)
    total_home.columns = ["won", "lost"]
    total_home.plot.bar(stacked=True, ax=ax1)

    # Plot winrate per map at home
    total_home = total_home.fillna(0)
    total_home["winrate"] = total_home["won"] / \
        (total_home["won"] + total_home["lost"])
    total_home = total_home["winrate"]
    total_home.plot.bar(ax=ax3, ylim=(0, 1), color="#43bc43")

    # Plot win/loss away from home
    total_away = pd.concat([won_away, loss_away], axis=1, sort=True)
    total_away.columns = ["won", "lost"]
    total_away.plot.bar(stacked=True, ax=ax2)

    # Plot winrate away from home
    total_away = total_away.fillna(0)
    total_away["winrate"] = total_away["won"] / \
        (total_away["won"] + total_away["lost"])
    total_away = total_away["winrate"]
    total_away.plot.bar(ax=ax4, ylim=(0, 1), color="#43bc43")

    plt.subplots_adjust(hspace=0.5)
    fig.savefig("tournament_winrates/" + team + ".png")

def plot_country_representation():
    """
    Plots bar graphs of number of players from each country in our entire
    dataset, top30 teams only, and at the large tournaments
    """

    # Get all player data, drops duplicates
    all_players = players.copy().drop_duplicates(subset="name", keep="first")
    # Groupy origin, count unique names (unique since there are no duplicates)
    all_players = all_players.groupby("origin")["name"].count()
    # Push name and origin into columns
    all_players = pd.DataFrame(all_players.reset_index())

    # Get all top30 player data, drop duplicates
    top30_players = current_lineups.drop_duplicates(
        subset="name", keep="first")
    # Groupy origin, count unique names (unique since there are no duplicates)
    top30_players = top30_players.groupby("origin")["name"].count()
    # Push name and origin into columns
    top30_players = pd.DataFrame(top30_players.reset_index())

    # Get all player data
    majors = players.copy()
    # Filter so only players that have attended Major Tournaments are present
    majors = majors[majors["tournament"].isin(large_tourneys)]
    # Drop duplicates
    majors = majors.drop_duplicates(subset="name", keep="first")
    # Groupby origin, count names
    majors = majors.groupby("origin")["name"].count()
    # Add name and origin back to columns
    majors = pd.DataFrame(majors.reset_index())

    # Sort values by count of player
    all_players = all_players.sort_values(by="name", ascending=False)
    top30_players = top30_players.sort_values(by="name", ascending=False)
    majors = majors.sort_values(by="name", ascending=False)

    # Renaming columns to better describe data
    top30_players = top30_players.rename(
        columns={"name": "Number of Players", "origin": "Country"})
    all_players = all_players.rename(
        columns={"name": "Number of Players", "origin": "Country"})
    majors = majors.rename(
        columns={"name": "Number of Players", "origin": "Country"})

    return top30_players

if __name__ == '__main__':
    main()


import streamlit as st

from dvds.config import set_dvds_to_display
from dvds.config import scope_dvd_season_list

def dvd_doctor_selectbox(scope):
	widget_key = 'widget_dvd_doctor'
	previous_selection = scope.dvd_selected_doctor
	pos_for_previous = scope.dvd_doctor_list.index(previous_selection)

	st.selectbox(
					label='Available Doctors for ' + scope.dvd_selected_series,
					options=scope.dvd_doctor_list,
					index=pos_for_previous,
					on_change=set_dvd_doctor,
					args=(scope, widget_key),
					key=widget_key,
					)

	scope_dvd_season_list(scope)

def set_dvd_doctor(scope, widget_key):
	selected_doctor = (scope[widget_key])
	scope.dvd_selected_doctor = selected_doctor
	set_dvds_to_display(scope)


import carla
import PIL.Image as Image
import time
import numpy as np
from agents.navigation import basic_agent
import random

town_name = 'town10'
def process_rgb_image(image):
    image.convert(carla.ColorConverter.Raw)
    array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
    array = np.reshape(array, (image.height, image.width, 4))
    array = array[:, :, :3]
    array = array[:, :, ::-1]
    temp = np.copy(array)
    img = Image.fromarray(temp, mode="RGB")
    print('before save')
    img_name = 'carla_images_ft/images/{}/{:0>8}.png'.format(town_name, image.frame)
    img.save(img_name)

def process_seg_image(img_frame,image):
    image.convert(carla.ColorConverter.CityScapesPalette)
    array = np.frombuffer(image.raw_data, dtype=np.dtype("uint8"))
    array = np.reshape(array, (image.height, image.width, 4))
    array = array[:, :, :3]
    array = array[:, :, ::-1]
    temp = np.copy(array)
    img = Image.fromarray(temp, mode="RGB")
    print('before save seg')
    img_name = 'carla_images_ft/labels/{}/{:0>8}.png'.format(town_name, img_frame.frame)
    img.save(img_name)

def get_random_waypoint():
    waypoint = random.choice(map.get_spawn_points())
    return waypoint

# python script location(x,y,z  m)  location in carlaUE4(cm)

# Connect to the CARLA server and get the world and map
client = carla.Client('localhost', 2000)
client.set_timeout(100.0)
world = client.get_world()
# settings = world.get_settings()
# settings.fixed_delta_seconds = 0.01
# world.apply_settings(settings)
map = world.get_map()
spectator = world.get_spectator()
settings = world.get_settings()
# settings.actor_active_distance = 2000
# 定义RGB相机的参数
rgb_bp = world.get_blueprint_library().find('sensor.camera.rgb')
rgb_bp.set_attribute('image_size_x', '3840')
rgb_bp.set_attribute('image_size_y', '2160')
rgb_bp.set_attribute('fov', '40')
rgb_bp.set_attribute('sensor_tick', '3.0')
rgb_bp.set_attribute('motion_blur_intensity', '0.0')

# 定义分割相机的参数
seg_bp = world.get_blueprint_library().find('sensor.camera.semantic_segmentation')
seg_bp.set_attribute('image_size_x', '3840')
seg_bp.set_attribute('image_size_y', '2160')
seg_bp.set_attribute('fov', '40')
seg_bp.set_attribute('sensor_tick', '3.0')

weather = carla.WeatherParameters()
weather.sun_azimuth_angle = 90
weather.sun_altitude_angle = 90
world.set_weather(weather)
car_bp = world.get_blueprint_library().filter('vehicle.*')[0]
car_bp.set_attribute('role_name', 'hero')
spawn_point = map.get_spawn_points()[1]
my_car = world.spawn_actor(car_bp, spawn_point)
time.sleep(1)
rgb_transform = carla.Transform(carla.Location(x=1.6, z=1.3))
rgb_camera = world.spawn_actor(rgb_bp, rgb_transform, attach_to=my_car)
# print(rgb_transform)
seg_transform = carla.Transform(carla.Location(x=1.6, z=1.3))
seg_camera = world.spawn_actor(seg_bp, seg_transform, attach_to=my_car)

rgb_image_queue = []
segmentation_image_queue = []
agent = basic_agent.BasicAgent(my_car)
agent.set_target_speed(60)
agent.ignore_traffic_lights(True)
rgb_camera.listen(rgb_image_queue.append)
seg_camera.listen(segmentation_image_queue.append)
waypoint = get_random_waypoint()
agent.set_destination(waypoint.location)
while(True):
    if len(rgb_image_queue) >= 100 and len(segmentation_image_queue) >= 100:
        break
    spectator.set_transform(rgb_camera.get_transform())
    control = agent.run_step()
    if my_car.get_location().distance(waypoint.location) < 2.0:
        waypoint = get_random_waypoint()
        agent.set_destination(waypoint.location)
    my_car.apply_control(control)
    world.tick()

for i in range(len(rgb_image_queue)):
    process_rgb_image(rgb_image_queue[i])
    process_seg_image(rgb_image_queue[i],segmentation_image_queue[i])
    print('save image {}'.format(i))
    time.sleep(0.01)

my_car.destroy()
rgb_camera.destroy()
seg_camera.destroy()


'''
Created on 2020-04-07

@author: wf
'''
from flask import Flask, Response
from flask import render_template
from flask import request
import os
from flask.helpers import send_from_directory
import argparse
import sys
from ptp.lookup import Lookup

#from json2xml import json2xml
#https://github.com/vinitkumar/json2xml/issues/59
#from flask_accept import accept

scriptdir=os.path.dirname(os.path.abspath(__file__))
app = Flask(__name__,static_url_path='',static_folder=scriptdir+'/../web', template_folder=scriptdir+'/../templates')
# create a most current eventall database
lookup=Lookup("webserver",singleDB=True)

@app.route('/')
def home():
    return index()

def index(titles="",tc=None,errs=None,result=None,message=None,metadebug=True):
    """ render index page with the given parameters"""
    return render_template('index.html',titles=titles,tc=tc,errs=errs,result=result,message=message,metadebug=metadebug,examples=Lookup.getExamples())

@app.route('/settings')
def settings():
    return render_template('settings.html',lookup=lookup)

@app.route('/parse', methods=['POST','GET'])
#@accept('text/html')
def parseTitles():
    """ endpoint for proceedings title parsing"""
    if request.method == 'POST':
        titleLines=request.form.get('titles')
        metadebug=request.form.get('metadebug')
    else:
        titleLines=request.args.get('titles')
        metadebug=request.args.get('metadebug')
    metadebug=metadebug is not None
    tp=lookup.tp
    tp.fromLines(titleLines.splitlines(), 'line',clear=True)
    tc,errs,result=tp.parseAll()
    responseFormat=request.args.get('format')
    if responseFormat is None:
        responseFormat="html"
        # handle content negotiation
        acceptJson=request.accept_mimetypes['application/json']
        if acceptJson==1: responseFormat="json"
        acceptXml=request.accept_mimetypes['application/xml']==1 or request.accept_mimetypes['text/xml']==1
        if acceptXml==1: responseFormat="xml"
        acceptCsv=request.accept_mimetypes['text/csv']==1
        if acceptCsv==1: responseFormat="csv"
    if responseFormat=='json':
        response = Response(status=200,mimetype='application/json')
        jsonText=tp.asJson(result)
        response.set_data(jsonText)
        return response
    elif responseFormat=='xml':
        response = Response(status=200,mimetype='application/xml')
        xml=tp.asXml(result)
        response.set_data(xml)
        return response
    elif responseFormat=='wikison':
        response = Response(status=200,mimetype='text/plain')
        wikison=tp.asWikiSon(result)
        response.set_data(wikison)
        return response
    elif responseFormat=="csv":
        response = Response(status=200,mimetype='text/plain')
        csv=tp.asCsv(result)
        response.set_data(csv)
        return response
    else:
        return index(titleLines,tc,errs,result,metadebug=metadebug)

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Proceedings Title Parser webservice")
    parser.add_argument('--debug',
                                 action='store_true',
                                 help="run in debug mode")
    parser.add_argument('--port',
                                 type=int,
                                 default=5004,
                                 help="the port to use")
    parser.add_argument('--host',
                                 default="0.0.0.0",
                                 help="the host to serve for")
    args=parser.parse_args(sys.argv[1:])
    app.run(debug=args.debug,port=args.port,host=args.host)


# This is a example for fine tune

import pandas as pd
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from tensorflow.keras import Model, Sequential
from tensorflow.keras.models import load_model
from tensorflow.keras.layers import Dense, Flatten, Conv2D, \
    BatchNormalization, Activation, MaxPool2D, Dropout
from tensorflow.keras.optimizers import RMSprop
from tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, TensorBoard
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from sklearn.metrics import confusion_matrix
import itertools
import datetime

train = pd.read_csv("./digit_recognizer/train.csv")  # don't forget the dot "."
test = pd.read_csv("./digit_recognizer/test.csv")
print(train.shape)   # (42000, 785)
print(test.shape)    # (28000, 784)

y_train = train["label"]

x_train = train.drop(labels=["label"], axis=1)
del train  # free some space

# Normalize
x_train = x_train / 255.0
test = test / 255.0

# Reshape
x_train = x_train.values.reshape(-1, 28, 28, 1)
test = test.values.reshape(-1, 28, 28, 1)

# Encode labels to one hot vectors (ex : 2 -> [0,0,1,0,0,0,0,0,0,0]
y_train = tf.keras.utils.to_categorical(y_train, num_classes=10)

# split train set and test set
x_train, x_val, y_train, y_val = train_test_split(x_train, y_train, test_size=0.1, random_state=2)

pretrained_model = load_model("model")
pretrained_model.summary()

# fine-tun: True, frozen: False
for i in range(len(pretrained_model.layers)):
    pretrained_model.layers[i].trainable = False
    print("This is {} layer.".format(i), "\n", pretrained_model.layers[i].name)

extracted_layers = pretrained_model.layers[:-1]
extracted_layers.append(Activation(tf.nn.relu, name="new_act1"))
extracted_layers.append(Dense(10, name="dense_3"))
extracted_layers.append(Activation(tf.nn.softmax, name="new_act2"))

for i in range(len(extracted_layers)):
    print("This is {} layer.".format(i), "\n", extracted_layers[i].name)

model = Sequential(extracted_layers)
optimizer = RMSprop(lr=0.001, rho=0.9, epsilon=1e-08, decay=0.0)
loss = tf.keras.losses.categorical_crossentropy
model.compile(optimizer=optimizer, loss=loss, metrics="acc")
learning_rate_reduction = ReduceLROnPlateau(monitor='val_acc',
                                            patience=3,
                                            verbose=1,
                                            factor=0.5,
                                            min_lr=0.00001)
early_stopper = EarlyStopping(monitor="val_loss", min_delta=0,
                              patience=1, verbose=1, mode="auto",
                              baseline=None, restore_best_weights=False)  # paticnce: Number of epochs with no improvement after which training will be stopped.

epochs = 1
batch_size = 84

# tensorboard
log_dir = "./transfer_logs"
tensorboard = TensorBoard(log_dir=log_dir, histogram_freq=1)

datagen = ImageDataGenerator(featurewise_center=False,
                             samplewise_center=False,
                             featurewise_std_normalization=False,
                             samplewise_std_normalization=False,
                             zca_whitening=False,
                             rotation_range=10,
                             zoom_range=0.1,
                             width_shift_range=0.1,
                             height_shift_range=0.1,
                             horizontal_flip=False,
                             vertical_flip=False)

datagen.fit(x_train)
history = model.fit(datagen.flow(x_train, y_train, batch_size=batch_size),
                    epochs=epochs, validation_data=(x_val, y_val),
                    verbose=2, steps_per_epoch=x_train.shape[0] // batch_size,
                    callbacks=[learning_rate_reduction, early_stopper, tensorboard])
model.summary()


import requests
import json
import time
import os
from datetime import date, datetime, timedelta

class Finnhub:

    def __init__(self, ticker):
        self.ticker = ticker
        self.token = os.environ['FINNHUB_API_KEY']

    @property
    def last_year(self):

        today = datetime.today()
        n_days_ago = today - timedelta(days=365)

        today_unix = int(time.mktime(today.timetuple()))
        n_days_ago_unix = int(time.mktime(n_days_ago.timetuple()))

        url = f'https://finnhub.io/api/v1/stock/candle?symbol={self.ticker}&resolution=D&from={n_days_ago_unix}&to={today_unix}&token={self.token}'

        try:
            data = json.loads(requests.get(url).text)
        except json.JSONDecodeError:
            print(f'JSON ERROR SKIPPING {t}')

        if data['s'] == 'no_data':
            return None

        return data['c']

    @property
    def current_value(self):

        url = f'https://finnhub.io/api/v1/quote?symbol={self.ticker}&token={self.token}'

        try:
            return json.loads(requests.get(url).text)['c']
        except json.JSONDecodeError:
            print(f'JSON ERROR SKIPPING {t}')
        return None

    @property
    def stock_name(self):

        url = f'https://finnhub.io/api/v1/search?q={self.ticker}&token={self.token}'

        try:
            data = json.loads(requests.get(url).text)
        except json.JSONDecodeError:
            print(f'JSON ERROR')
            return None

        for result in data['result']:
            if result['symbol'] == self.ticker:
                return result['description'].title()

        return None


import os.path
import cherrypy

class Root:
    @cherrypy.expose
    def index(self):
        return """

                <title>Energy</title>

Energy Insights (Half-hourly data)

                """

if __name__ == '__main__':
    current_dir = os.path.dirname(os.path.abspath(__file__))
    # Set up site-wide config first so we get a log if errors occur.
    cherrypy.config.update({'server.socket_host': 'localhost',
                            'server.socket_port': 8080, })

    conf = {'/results': {'tools.staticdir.on': True,
                      'tools.staticdir.dir': os.path.join(current_dir, 'results')}}

    cherrypy.quickstart(Root(), '/', config=conf)


#!/usr/bin/env python
import pathlib

from setuptools import find_packages, setup

# The directory containing this file
HERE = pathlib.Path(__file__).parent

# The text of the README file
README = (HERE / "README.md").read_text()

setup(
      name="plusGitRepo",
      version="0.1.1",
      license='3-clause BSD',
      url="https://github.com/edina/plusGitRepo/",
      description="Jupyter Classic Notebook extension to add a dialogue to gather info for the nbgitpuller extension",
      long_description=open('README.md').read(),
      long_description_content_type='text/markdown',
      author="James Crone, Ian Stuart",
      author_email='edina@ed.ac.uk',
      packages=find_packages(),
#      packages=['plusGitRepo'],
      package_data={'plusGitRepo': ['main:/*']},
      install_requires=['nbgitpuller>=0.9.0'],
      include_package_data=True,
      classifiers=[
            'Development Status :: 4 - Beta',
            'License :: OSI Approved :: BSD License',
            'Operating System :: POSIX',
            'Operating System :: MacOS',
            'Operating System :: Unix',
            'Programming Language :: Python :: 3',
            'Topic :: Software Development :: Libraries :: Python Modules',
      ],
      data_files=[
            ("share/jupyter/nbextensions/plusGitRepo", ['plusGitRepo/main/index.js']),
      ]
)


from flask import request, jsonify
from flask_restful import Resource
from ..misc import isTokenValid, isAdminValid, adminTokenInValidResponse, formatDateAndAddIndexInParam
from collections import OrderedDict

class AdminDataset(Resource):
    def __init__(self,*args,**kwargs):
        """
        """
        self.data = kwargs["data"]
        self.token = kwargs["token"]
        self.detailHeaders = self.data.getAPIParam("detail-params")
        self.adminTableHeader = ["Index"] + self.data.getAPIParam("dataset-admin-table-params")

        self.contactViaEmailParamName = self.data.getAPIParam("dataset-admin-email-param")

    def get(self):
        "Returns the parameters of all datasets"

        token = request.args.get('token', default="None", type=str)

        if not isAdminValid(token,self.token):
            return adminTokenInValidResponse

        dataIDs = self.data.dataCollection.getDataIDs()

        parameters = [self.data.getParams(dataID) for dataID in dataIDs]
        filteredParameters = [formatDateAndAddIndexInParam(param,idx) for idx,param in enumerate(parameters) if param is not None]

        if len(filteredParameters) > 0:
            return {"success":True,"tokenIsValid" : True,"data" : {"tableHeader" : self.adminTableHeader, "params" : filteredParameters, "emailParamName" : self.contactViaEmailParamName}}
        else:
            return {"success":False,"msg":"No datasets found.","tokenIsValid" : True}


import subprocess
from datetime import datetime

from tools import run_request, read_config

config = read_config()
chat_id = config["chat_id"]
bot_token = config["bot_token"]
available_gb_alert = int(config["available_gb_alert"])

output = subprocess.check_output('echo $(($(stat -f --format="%a*%S" .)))', shell=True, universal_newlines=True)
diskspace = round(float(output) / 1000000000, 2)

try:
    file = open("last-sent-diskspace.txt", "r")
    last_sent = int(file.readline())
    file.close()
except:
    last_sent = datetime.now().hour - 1

if diskspace < available_gb_alert and last_sent != datetime.now().hour:
    run_request("GET",
                f"https://api.telegram.org/bot{bot_token}/sendMessage?"
                f"chat_id={chat_id}&text=The available disk space is only {diskspace} GB!")
    file = open("last-sent-diskspace.txt", "w")
    file.write(str(datetime.now().hour))
    file.close()


import asyncio
import contextvars
import functools
import threading
from asyncio import events
import datetime
import time
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import Tk, Frame
from typing import Dict, Sequence, List
from tkinter.messagebox import showerror
from tkinter.simpledialog import askstring
import tkinter.filedialog
from tkcalendar import DateEntry
from ttkwidgets import CheckboxTreeview

from CSVdataloader import CSVdataloader
from agenda_cal import Agenda
from dataloader import No21DaysOfMoedBException
from scheduler import run_solver, GENETIC_SOL, SA_SOL
from genetic_solver import GeneticSolver
from objects import YearSemester, Major, MajorSemester
from state import SumEvaluator, State

EXAMS_A_TAG = "Exams_A"

EXAMS_B_TAG = "Exams_B"

HARD_CODED_EXAMS_TAG = 'hard_coded_exams'

NO_EXAMS_TAG = 'forbidden'

DATES_INFO_TAG = 'DATES_INFO_TAG'

MOADEI_B_TAG = '2nd_round'

MOADEI_A_TAG = '1st_round'

WINDOW_SIZE = '1280x800'
WINDOW_TITLE = 'Exam Scheduler'
HEADER2_FONT = ("Courier New", 16)
HEADER1_FONT = ("Courier New", 18, 'bold')
BODY_FONT = ("Courier New", 12)
BG = 'white'

SOLVER_NAMES = {"Genetic Algorithm": GENETIC_SOL, "Simulated Annealing": SA_SOL}

class WidgetWithLabel(Frame):

    def __init__(self, master: tk.BaseWidget, label: str, *args, **kwargs):
        super(WidgetWithLabel, self).__init__(master, *args, **kwargs)
        self.label = tk.Label(self, text=label, justify=tk.LEFT)
        self.label.pack(side=tk.LEFT)  # grid(row=0, column=0, sticky=tk.W)
        self.widget = None

    def set_widget(self, widget, *args, **kwargs):
        self.widget = widget(self, *args, **kwargs)
        self.widget.pack(side=tk.RIGHT)  # grid(row=0, column=1, sticky=tk.E)

class ExamSchedulerGui:

    def __init__(self, window_size=WINDOW_SIZE, window_title=WINDOW_TITLE):
        self.root = tk.Tk()
        self.root.geometry(window_size)
        self.root.title(window_title)

        self.__forbidden_dates: List[datetime.date] = []
        self.__majors_to_display: Dict[Major, Dict[MajorSemester, tk.BooleanVar]] = dict()

        self.__show_names = tk.BooleanVar()

        self.__solver_thread = None

        cp_frame_label = tk.Label(self.root, text="Setup", font=HEADER1_FONT)
        cp_frame_label.grid(row=0, column=0)
        self.cp_frame = Frame(self.root, borderwidth=2, relief=tk.GROOVE)

        cal_frame_label = tk.Label(self.root, text="Display Calendar", font=HEADER1_FONT)
        cal_frame_label.grid(row=0, column=1)
        self.cal_frame = Frame(self.root, borderwidth=2, width=700, height=600, relief=tk.GROOVE)
        self.currently_selected_date = tk.StringVar()

        self.agenda = Agenda(self.cal_frame, selectmode='day', textvariable=self.currently_selected_date,
                             rightclick_cb=lambda event: self.on_calendar_rightclick(event),
                             firstweekday="sunday", selectbackground='yellow', selectforeground='black',
                             disabledselectbackground='yellow', disabledselectforeground='black',
                             weekenddays=[7, 7],
                             normalbackground='gray60')
        self.agenda.pack(fill='both', expand=True)

        self.build_cp_frame(self.cp_frame)
        self.cp_frame.grid(row=1, column=0, sticky='swe')

        self.cal_frame.grid(row=1, column=1)

        self.display_cp_frame = Frame(self.root, borderwidth=2, relief=tk.GROOVE)
        self.root.mainloop()

    def build_display_cp_frame(self, root: Frame, sol_a, sol_b):

        checklist_frame = tk.Frame(root, borderwidth=2, relief=tk.GROOVE)
        checklist = CheckboxTreeview(checklist_frame)
        sb = ttk.Scrollbar(checklist_frame)
        sb.configure(command=checklist.yview)
        checklist.configure(yscrollcommand=sb.set)

        checklist.pack(side=tk.LEFT, expand=True, fill='y')
        sb.pack(expand=True, fill='y')

        def toggle(major, sem):
            self.root.update()
            self.__majors_to_display[major][sem].set(checklist.tag_has("checked", item=f"{major.major_name}.{sem.name}"))
            self.__show_solution(sol_a, EXAMS_A_TAG)
            self.__show_solution(sol_b, EXAMS_B_TAG)

        def toggle_major(major):
            self.root.update()
            for sem in self.__majors_to_display[major]:
                self.__majors_to_display[major][sem].set(checklist.tag_has("checked", item=f"{major.major_name}.{sem.name}"))
            self.__show_solution(sol_a, EXAMS_A_TAG)
            self.__show_solution(sol_b, EXAMS_B_TAG)

        for i, maj in enumerate(self.__majors_to_display):
            checklist.insert("", "end", f"{maj.major_name}", text=f"{maj.major_name}", tags=["checked", maj.major_name])
            checklist.tag_bind(maj.major_name, "<1>", lambda x, maj=maj: self.root.after_idle(toggle_major, maj))
            for j, sem in enumerate(self.__majors_to_display[maj]):
                checklist.insert(f"{maj.major_name}", "end", f"{maj.major_name}.{sem.name}", text=f"{sem.name}", tags=["checked", f"{maj.major_name}.{sem.name}"])
                checklist.tag_bind(f"{maj.major_name}.{sem.name}", "<1>", lambda x, maj=maj, sem=sem: self.root.after_idle(toggle, maj, sem))

        checklist_frame.grid(row=0, column=0, rowspan=5, sticky=tk.NS)

        jump_frame = tk.LabelFrame(root, text="Calendar view jump")
        tk.Button(jump_frame, text="Jump to Semester A",
                  command=lambda: self.agenda.see(self.sem_a_start_entry.widget.get_date())).pack(pady=5)
        tk.Button(jump_frame, text="Jump to Semester B",
                  command=lambda: self.agenda.see(self.sem_b_start_entry.widget.get_date())).pack(pady=5)
        jump_frame.grid(row=0, column=2, stick='new')

        def save_sol(sol: State):
            filename = tkinter.filedialog.asksaveasfilename()
            if filename:
                sol.save_to_csv(filename)

        show_names = tk.Checkbutton(root, text="Show Course Names", variable=self.__show_names)
        show_names.grid(row=1, column=2, sticky=tk.E)

        self.__show_names.trace_add('write', lambda x, y, z: (self.__show_solution(sol_a, EXAMS_A_TAG),
                                                     self.__show_solution(sol_b, EXAMS_B_TAG)))

        save_frame = tk.LabelFrame(root, text="Save Schedule")
        tk.Button(save_frame, text="Save Semester A Schedule",
                  command=lambda: save_sol(sol_a)).pack(pady=5)
        tk.Button(save_frame, text="Save Semester B Schedule",
                  command=lambda: save_sol(sol_b)).pack(pady=5)
        save_frame.grid(row=2, column=2, stick='swe')

        def reset():
            self.agenda.calevent_remove(tag=EXAMS_A_TAG)
            self.agenda.calevent_remove(tag=EXAMS_B_TAG)
            self.agenda.update()
            self.display_cp_frame.grid_remove()
            self.cp_frame.grid(row=1, column=0)

        tk.Button(root, text="Reset",
                  command=reset).grid(row=3, column=2, sticky=tk.SE, pady=5)

    def build_cp_frame(self, root: Frame):

        load_frame = tk.LabelFrame(root, text="Load Data")
        load_frame.pack(side=tk.TOP, pady=50)

        self.maj_file_input = WidgetWithLabel(load_frame, "Majors Data File")
        self.maj_file_input.set_widget(SelectFileFrame, width=10)
        self.maj_file_input.pack(side=tk.TOP, expand=True, fill=tk.BOTH, padx=5, pady=5)

        self.sem_a_courses_file_input = WidgetWithLabel(load_frame, "Semester A Courses File")
        self.sem_a_courses_file_input.set_widget(SelectFileFrame, width=10)
        self.sem_a_courses_file_input.pack(expand=True, fill=tk.BOTH, padx=5, pady=5)

        self.sem_b_courses_file_input = WidgetWithLabel(load_frame, "Semester B Courses File")
        self.sem_b_courses_file_input.set_widget(SelectFileFrame, width=10)
        self.sem_b_courses_file_input.pack(expand=True, fill=tk.BOTH, padx=5, pady=5)

        # self.__load_button = tk.Button(load_frame, text="Load", command=lambda: self.load_files)
        # self.__load_button.pack(side=tk.RIGHT, padx=10, pady=15, ipadx=5, ipady=2)

        dates_frame = tk.LabelFrame(root, text="Setup Schedule")
        dates_frame.pack(side=tk.BOTTOM)

        self.sem_a_start_entry = WidgetWithLabel(dates_frame, "Semester A Exams start")
        self.sem_a_start_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                          month=self.agenda.get_displayed_month()[0],
                                          year=self.agenda.get_displayed_month()[1])
        self.sem_a_start_entry.widget.bind('<<DateEntrySelected>>',
                                           lambda x: self.update_selected_dates(self.sem_a_start_entry))
        self.sem_a_start_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_a_a_end_entry = WidgetWithLabel(dates_frame, "Semester A 1st round end")
        self.sem_a_a_end_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                          month=self.agenda.get_displayed_month()[0],
                                          year=self.agenda.get_displayed_month()[1])
        self.sem_a_a_end_entry.widget.bind('<<DateEntrySelected>>',
                                           lambda x: self.update_selected_dates(self.sem_a_a_end_entry))
        self.sem_a_a_end_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_a_b_start_entry = WidgetWithLabel(dates_frame, "Semester A 2nd round start")
        self.sem_a_b_start_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                            month=self.agenda.get_displayed_month()[0],
                                            year=self.agenda.get_displayed_month()[1])
        self.sem_a_b_start_entry.widget.bind('<<DateEntrySelected>>',
                                             lambda x: self.update_selected_dates(self.sem_a_b_start_entry))
        self.sem_a_b_start_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_a_b_end_entry = WidgetWithLabel(dates_frame, "Semester A 2nd round end")
        self.sem_a_b_end_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                          month=self.agenda.get_displayed_month()[0],
                                          year=self.agenda.get_displayed_month()[1])
        self.sem_a_b_end_entry.widget.bind('<<DateEntrySelected>>',
                                           lambda x: self.update_selected_dates(self.sem_a_b_end_entry))
        self.sem_a_b_end_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_b_start_entry = WidgetWithLabel(dates_frame, "Semester B Exams start")
        self.sem_b_start_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                          month=self.agenda.get_displayed_month()[0],
                                          year=self.agenda.get_displayed_month()[1])
        self.sem_b_start_entry.widget.bind('<<DateEntrySelected>>',
                                           lambda x: self.update_selected_dates(self.sem_b_start_entry))
        self.sem_b_start_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_b_a_end_entry = WidgetWithLabel(dates_frame, "Semester B 1st round end")
        self.sem_b_a_end_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                          month=self.agenda.get_displayed_month()[0],
                                          year=self.agenda.get_displayed_month()[1])
        self.sem_b_a_end_entry.widget.bind('<<DateEntrySelected>>',
                                           lambda x: self.update_selected_dates(self.sem_b_a_end_entry))
        self.sem_b_a_end_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_b_b_start_entry = WidgetWithLabel(dates_frame, "Semester B 2nd round start")
        self.sem_b_b_start_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                            month=self.agenda.get_displayed_month()[0],
                                            year=self.agenda.get_displayed_month()[1])
        self.sem_b_b_start_entry.widget.bind('<<DateEntrySelected>>',
                                             lambda x: self.update_selected_dates(self.sem_b_b_start_entry))
        self.sem_b_b_start_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.sem_b_b_end_entry = WidgetWithLabel(dates_frame, "Semester B 2nd round end")
        self.sem_b_b_end_entry.set_widget(DateEntry, firstweekday="sunday", weekenddays=[7, 7],
                                          month=self.agenda.get_displayed_month()[0],
                                          year=self.agenda.get_displayed_month()[1])
        self.sem_b_b_end_entry.widget.bind('<<DateEntrySelected>>',
                                           lambda x: self.update_selected_dates(self.sem_b_b_end_entry))
        self.sem_b_b_end_entry.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.__choose_solver = WidgetWithLabel(dates_frame, "Solver Algorithm")
        self.__choose_solver.set_widget(ttk.Combobox, values=["Genetic Algorithm", "Simulated Annealing"])
        self.__choose_solver.widget.current(0)
        self.__choose_solver.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.__choose_iterations =  WidgetWithLabel(dates_frame, "Number of Iterations (0 for default)")
        self.__choose_iterations.set_widget(ttk.Spinbox, from_=0, to=100_000)
        self.__choose_iterations.pack(expand=True, fill=tk.X, pady=5, padx=5)

        self.__solve_button = tk.Button(dates_frame, text="Solve", command=self.run_solution)
        self.__solve_button.pack(side=tk.RIGHT, padx=10, pady=15, ipadx=5, ipady=2)

        self.__prog_bar_frame = tk.Frame(dates_frame)
        self.__prog_bar_frame.pack(side=tk.LEFT, expand=True, fill=tk.BOTH, padx=10, pady=15)

    def run_solution(self):
        self.__solve_button.configure(state=tk.DISABLED)
        pb = ttk.Progressbar(self.__prog_bar_frame, length=100, mode='determinate', orient='horizontal')

        def update(current_progress):
            pb['value'] = current_progress * 100
            self.root.update()

        def run():
            try:
                kwargs = dict()
                iterations = int(self.__choose_iterations.widget.get())
                if iterations:
                    kwargs['iterations'] = iterations
                sol_a, sol_b = run_solver(self.maj_file_input.widget.get_selected_filename(),
                                          self.sem_a_courses_file_input.widget.get_selected_filename(),
                                          self.sem_b_courses_file_input.widget.get_selected_filename(),
                                          self.sem_a_start_entry.widget.get_date(),
                                          self.sem_a_a_end_entry.widget.get_date(),
                                          self.sem_a_b_start_entry.widget.get_date(),
                                          self.sem_a_b_end_entry.widget.get_date(),
                                          self.sem_b_start_entry.widget.get_date(),
                                          self.sem_b_a_end_entry.widget.get_date(),
                                          self.sem_b_b_start_entry.widget.get_date(),
                                          self.sem_b_b_end_entry.widget.get_date(),
                                          self.__forbidden_dates,
                                          SOLVER_NAMES[self.__choose_solver.widget.get()], update, **kwargs)
                pb.destroy()
                self.__solve_button.configure(state=tk.NORMAL)
                self.show_solutions(sol_a, sol_b)
            except No21DaysOfMoedBException:
                showerror("Error!", "Selected dates do not include 21 days between the end of 1st round "
                                    "and the end of 2nd round. Please retry with different dates.")
                pb.destroy()
                self.__solve_button.configure(state=tk.NORMAL)

        pb.pack(fill=tk.BOTH, expand=True)
        self.__solver_thread = threading.Thread(target=run).start()

    def __show_solution(self, sol, tag):
        self.agenda.calevent_remove(tag=tag)
        for date, courses in sol.export_solution().items():
            for c in courses:
                course_flag = False
                for maj, info in c.get_majors().items():
                    if maj not in self.__majors_to_display:
                        self.__majors_to_display[maj] = dict()
                    for i in info:
                        if i[0] not in self.__majors_to_display[maj]:
                            self.__majors_to_display[maj][i[0]] = tk.BooleanVar(value=True)
                        if self.__majors_to_display[maj][i[0]].get():
                            course_flag = True
                if course_flag:
                    if self.__show_names.get():
                        text = c.name
                    else:
                        text = c.number
                    self.agenda.calevent_create(date, text, [tag])
        self.update_selected_dates(None)
        # self.update_tags()
        self.agenda.see(datetime.date(*self.agenda.get_displayed_month()[::-1], 1))

    def show_solutions(self, sol_a, sol_b):
        self.__show_solution(sol_a, EXAMS_A_TAG)
        self.__show_solution(sol_b, EXAMS_B_TAG)
        self.build_display_cp_frame(self.display_cp_frame, sol_a, sol_b)
        self.cp_frame.grid_remove()
        self.display_cp_frame.grid(row=1, column=0, sticky='nsew')

    def on_calendar_rightclick(self, event):
        # Create the menu options and bindings:
        top_menu = tk.Menu(event.widget, tearoff=0)
        sem_a_menu = tk.Menu(top_menu, tearoff=0)
        sem_b_menu = tk.Menu(top_menu, tearoff=0)
        top_menu.add_cascade(label="Set As Semester A:", menu=sem_a_menu)
        top_menu.add_cascade(label="Set As Semester B:", menu=sem_b_menu)
        top_menu.add_separator()

        sem_a_menu.add_command(label="Exams Start",
                               command=lambda: self.update_selected_dates(self.sem_a_start_entry,
                                                                          update_from_selection=True))
        sem_a_menu.add_command(label="Exams 1st Round End",
                               command=lambda: self.update_selected_dates(self.sem_a_a_end_entry,
                                                                          update_from_selection=True))
        sem_a_menu.add_command(label="Exams 2nd Round Start",
                               command=lambda: self.update_selected_dates(self.sem_a_b_start_entry,
                                                                          update_from_selection=True))
        sem_a_menu.add_command(label="Exams 2nd Round End",
                               command=lambda: self.update_selected_dates(self.sem_a_b_end_entry,
                                                                          update_from_selection=True))

        sem_b_menu.add_command(label="Exams Start",
                               command=lambda: self.update_selected_dates(self.sem_b_start_entry,
                                                                          update_from_selection=True))
        sem_b_menu.add_command(label="Exams 1st Round End",
                               command=lambda: self.update_selected_dates(self.sem_b_a_end_entry,
                                                                          update_from_selection=True))
        sem_b_menu.add_command(label="Exams 2nd Round Start",
                               command=lambda: self.update_selected_dates(self.sem_b_b_start_entry,
                                                                          update_from_selection=True))
        sem_b_menu.add_command(label="Exams 2nd Round End",
                               command=lambda: self.update_selected_dates(self.sem_b_b_end_entry,
                                                                          update_from_selection=True))

        is_forbidden = tk.BooleanVar()
        is_forbidden.set(not not self.agenda.get_calevents(self.agenda.selection_get(), "forbidden"))

        top_menu.add_checkbutton(label="No Exams on this Date",
                                 command=lambda: self.forbid_date(is_forbidden.get()), variable=is_forbidden)

        top_menu.tk_popup(event.x_root, event.y_root)

    def update_selected_dates(self, date_entry: WidgetWithLabel, update_from_selection=False):

        self.agenda.calevent_remove(tag=DATES_INFO_TAG)
        if update_from_selection:
            date_entry.widget.set_date(self.currently_selected_date.get())

        date_a: datetime.date = self.sem_a_start_entry.widget.get_date()
        self.agenda.calevent_create(date_a, self.sem_a_start_entry.label['text'], 'DATES_INFO_TAG')
        while date_a < self.sem_a_a_end_entry.widget.get_date():
            self.agenda.calevent_create(date_a, '',
                                        ['DATES_INFO_TAG', "1st_round"])
            date_a += datetime.timedelta(days=1)
        self.agenda.calevent_create(date_a, self.sem_a_a_end_entry.label['text'], 'DATES_INFO_TAG')

        date_a: datetime.date = self.sem_a_b_start_entry.widget.get_date()
        self.agenda.calevent_create(date_a, self.sem_a_b_start_entry.label['text'], 'DATES_INFO_TAG')
        while date_a < self.sem_a_b_end_entry.widget.get_date():
            self.agenda.calevent_create(date_a, '',
                                        ['DATES_INFO_TAG', "2nd_round"])
            date_a += datetime.timedelta(days=1)
        self.agenda.calevent_create(date_a, self.sem_a_b_end_entry.label['text'], 'DATES_INFO_TAG')

        date_a: datetime.date = self.sem_b_start_entry.widget.get_date()
        self.agenda.calevent_create(date_a, self.sem_b_start_entry.label['text'], 'DATES_INFO_TAG')
        while date_a < self.sem_b_a_end_entry.widget.get_date():
            self.agenda.calevent_create(date_a, '',
                                        ['DATES_INFO_TAG', "1st_round"])
            date_a += datetime.timedelta(days=1)
        self.agenda.calevent_create(date_a, self.sem_b_a_end_entry.label['text'], 'DATES_INFO_TAG')

        date_a: datetime.date = self.sem_b_b_start_entry.widget.get_date()
        self.agenda.calevent_create(date_a, self.sem_b_b_start_entry.label['text'], 'DATES_INFO_TAG')
        while date_a < self.sem_b_b_end_entry.widget.get_date():
            self.agenda.calevent_create(date_a, '',
                                        ['DATES_INFO_TAG', "2nd_round"])
            date_a += datetime.timedelta(days=1)
        self.agenda.calevent_create(date_a, self.sem_b_b_end_entry.label['text'], 'DATES_INFO_TAG')

        # self.update_tags()

    def update_tags(self):
        self.agenda.tag_config(MOADEI_A_TAG, background='DodgerBlue2', foreground='red')
        self.agenda.tag_config(EXAMS_A_TAG, background='DodgerBlue2', foreground='red')
        self.agenda.tag_config(MOADEI_B_TAG, background='cyan2', foreground='green')
        self.agenda.tag_config(EXAMS_B_TAG, background='cyan2', foreground='green')
        self.agenda.tag_config(DATES_INFO_TAG, foreground='yellow')
        self.agenda.tag_config(NO_EXAMS_TAG, background='red', foreground='yellow')

    def forbid_date(self, is_forbidden: bool):
        date_to_forbid = self.agenda.selection_get()
        if is_forbidden:
            self.agenda.calevent_create(date_to_forbid, "No Exams", NO_EXAMS_TAG)
            self.__forbidden_dates.append(date_to_forbid)
        else:
            self.agenda.calevent_remove(date=date_to_forbid, tag=NO_EXAMS_TAG)
            self.__forbidden_dates.remove(date_to_forbid)
        self.update_tags()

    def load_files(self):
        # self.__exam_scheduler = ExamScheduler(self.maj_file_input.widget.get_selected_filename(),
        #                                       self.sem_a_courses_file_input.widget.get_selected_filename(),
        #                                       self.sem_b_courses_file_input.widget.get_selected_filename())
        self.maj_file_input.widget.set_enabled(False)
        self.sem_a_courses_file_input.widget.set_enabled(False)
        self.sem_b_courses_file_input.widget.set_enabled(False)
        self.__load_button.configure(enabled=False)

class SelectFileFrame(Frame):

    def __init__(self, master: tk.BaseWidget, dialog_title: str = "Choose file...", width=None, *args,
                 **kwargs):
        super(SelectFileFrame, self).__init__(master, *args, **kwargs)
        if width:
            self.field = tk.Entry(self, width=width)
        else:
            self.field = tk.Entry(self)
        self.field.pack(side=tk.LEFT, padx=3)
        self.browse_button = tk.Button(self, text="Browse...", command=lambda: self.browse())
        self.browse_button.pack()
        self.dialog_title = dialog_title

    def browse(self):
        filename = tk.filedialog.askopenfilename(parent=self.master, title=self.dialog_title)
        self.field.delete(0, "end")
        self.field.insert(0, filename)

    def get_selected_filename(self):
        return self.field.get()

    def set_enabled(self, enabled: bool):
        self.field.configure(enabled=enabled)
        self.browse_button.configure(enabled=enabled)

async def to_thread(func, *args, **kwargs):
    """Asynchronously run function *func* in a separate thread.
    Any *args and **kwargs supplied for this function are directly passed
    to *func*. Also, the current :class:`contextvars.Context` is propogated,
    allowing context variables from the main thread to be accessed in the
    separate thread.
    Return a coroutine that can be awaited to get the eventual result of *func*.
    """
    loop = events.get_running_loop()
    ctx = contextvars.copy_context()
    func_call = functools.partial(ctx.run, func, *args, **kwargs)
    return await loop.run_in_executor(None, func_call)

if __name__ == '__main__':
    gui = ExamSchedulerGui()