"""Run performance comparisons for versions of coverage"""

import collections
import contextlib
import dataclasses
import itertools
import os
import random
import shutil
import statistics
import subprocess
import sys
import time
from pathlib import Path

from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple

class ShellSession:
    """A logged shell session.

    The duration of the last command is available as .last_duration.
    """

    def __init__(self, output_filename: str):
        self.output_filename = output_filename
        self.last_duration: float = 0
        self.foutput = None

    def __enter__(self):
        self.foutput = open(self.output_filename, "a", encoding="utf-8")
        print(f"Logging output to {os.path.abspath(self.output_filename)}")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.foutput.close()

    def print(self, *args, **kwargs):
        """Print a message to this shell's log."""
        print(*args, **kwargs, file=self.foutput)

    def run_command(self, cmd: str) -> str:
        """
        Run a command line (with a shell).

        Returns:
            str: the output of the command.

        """
        self.print(f"\n========================\n$ {cmd}")
        start = time.perf_counter()
        proc = subprocess.run(
            cmd,
            shell=True,
            check=False,
            stdout=subprocess.PIPE,
            stderr=subprocess.STDOUT,
        )
        output = proc.stdout.decode("utf-8")
        self.last_duration = time.perf_counter() - start
        self.print(output, end="")
        self.print(f"(was: {cmd})")
        self.print(f"(in {os.getcwd()}, duration: {self.last_duration:.3f}s)")

        if proc.returncode != 0:
            self.print(f"ERROR: command returned {proc.returncode}")
            raise Exception(
                f"Command failed ({proc.returncode}): {cmd!r}, output was:\n{output}"
            )

        return output.strip()

def rmrf(path: Path) -> None:
    """
    Remove a directory tree.  It's OK if it doesn't exist.
    """
    if path.exists():
        shutil.rmtree(path)

@contextlib.contextmanager
def change_dir(newdir: Path) -> Iterator[Path]:
    """
    Change to a new directory, and then change back.

    Will make the directory if needed.
    """
    old_dir = os.getcwd()
    newdir.mkdir(parents=True, exist_ok=True)
    os.chdir(newdir)
    try:
        yield newdir
    finally:
        os.chdir(old_dir)

@contextlib.contextmanager
def file_replace(file_name: Path, old_text: str, new_text: str) -> Iterator[None]:
    """
    Replace some text in `file_name`, and change it back.
    """
    if old_text:
        file_text = file_name.read_text()
        if old_text not in file_text:
            raise Exception("Old text {old_text!r} not found in {file_name}")
        updated_text = file_text.replace(old_text, new_text)
        file_name.write_text(updated_text)
    try:
        yield
    finally:
        if old_text:
            file_name.write_text(file_text)

class ProjectToTest:
    """Information about a project to use as a test case."""

    # Where can we clone the project from?
    git_url: Optional[str] = None
    slug: Optional[str] = None

    def __init__(self):
        if not self.slug:
            if self.git_url:
                self.slug = self.git_url.split("/")[-1]

    def shell(self):
        return ShellSession(f"output_{self.slug}.log")

    def make_dir(self):
        self.dir = Path(f"work_{self.slug}")
        if self.dir.exists():
            rmrf(self.dir)

    def get_source(self, shell):
        """Get the source of the project."""
        shell.run_command(f"git clone {self.git_url} {self.dir}")

    def prep_environment(self, env):
        """Prepare the environment to run the test suite.

        This is not timed.
        """
        pass

    @contextlib.contextmanager
    def tweak_coverage_settings(
        self, settings: Iterable[Tuple[str, Any]]
    ) -> Iterator[None]:
        """Tweak the coverage settings.

        NOTE: This is not properly factored, and is only used by ToxProject now!!!
        """
        yield

    def pre_check(self, env):
        pass

    def post_check(self, env):
        pass

    def run_no_coverage(self, env):
        """Run the test suite with no coverage measurement."""
        pass

    def run_with_coverage(self, env, pip_args, cov_tweaks):
        """Run the test suite with coverage measurement."""
        pass

class EmptyProject(ProjectToTest):
    """A dummy project for testing other parts of this code."""

    def __init__(self, slug: str = "empty", fake_durations: Iterable[float] = (1.23,)):
        self.slug = slug
        self.durations = iter(itertools.cycle(fake_durations))

    def get_source(self, shell):
        pass

    def run_with_coverage(self, env, pip_args, cov_tweaks):
        """Run the test suite with coverage measurement."""
        return next(self.durations)

class ToxProject(ProjectToTest):
    """A project using tox to run the test suite."""

    def prep_environment(self, env):
        env.shell.run_command(f"{env.python} -m pip install 'tox<4'")
        self.run_tox(env, env.pyver.toxenv, "--notest")

    def run_tox(self, env, toxenv, toxargs=""):
        """Run a tox command. Return the duration."""
        env.shell.run_command(f"{env.python} -m tox -e {toxenv} {toxargs}")
        return env.shell.last_duration

    def run_no_coverage(self, env):
        return self.run_tox(env, env.pyver.toxenv, "--skip-pkg-install")

    def run_with_coverage(self, env, pip_args, cov_tweaks):
        self.run_tox(env, env.pyver.toxenv, "--notest")
        env.shell.run_command(
            f".tox/{env.pyver.toxenv}/bin/python -m pip install {pip_args}"
        )
        with self.tweak_coverage_settings(cov_tweaks):
            self.pre_check(env)  # NOTE: Not properly factored, and only used from here.
            duration = self.run_tox(env, env.pyver.toxenv, "--skip-pkg-install")
            self.post_check(
                env
            )  # NOTE: Not properly factored, and only used from here.
        return duration

class ProjectPytestHtml(ToxProject):
    """pytest-dev/pytest-html"""

    git_url = "https://github.com/pytest-dev/pytest-html"

    def run_with_coverage(self, env, pip_args, cov_tweaks):
        raise Exception("This doesn't work because options changed to tweaks")
        covenv = env.pyver.toxenv + "-cov"
        self.run_tox(env, covenv, "--notest")
        env.shell.run_command(f".tox/{covenv}/bin/python -m pip install {pip_args}")
        if cov_tweaks:
            replace = ("# reference: https", f"[run]\n{cov_tweaks}\n#")
        else:
            replace = ("", "")
        with file_replace(Path(".coveragerc"), *replace):
            env.shell.run_command("cat .coveragerc")
            env.shell.run_command(f".tox/{covenv}/bin/python -m coverage debug sys")
            return self.run_tox(env, covenv, "--skip-pkg-install")

class ProjectDateutil(ToxProject):
    """dateutil/dateutil"""

    git_url = "https://github.com/dateutil/dateutil"

    def prep_environment(self, env):
        super().prep_environment(env)
        env.shell.run_command(f"{env.python} updatezinfo.py")

    def run_no_coverage(self, env):
        env.shell.run_command("echo No option to run without coverage")
        return 0

class ProjectAttrs(ToxProject):
    """python-attrs/attrs"""

    git_url = "https://github.com/python-attrs/attrs"

    def tweak_coverage_settings(
        self, tweaks: Iterable[Tuple[str, Any]]
    ) -> Iterator[None]:
        return tweak_toml_coverage_settings("pyproject.toml", tweaks)

    def pre_check(self, env):
        env.shell.run_command("cat pyproject.toml")

    def post_check(self, env):
        env.shell.run_command("ls -al")

def tweak_toml_coverage_settings(
    toml_file: str, tweaks: Iterable[Tuple[str, Any]]
) -> Iterator[None]:
    if tweaks:
        toml_inserts = []
        for name, value in tweaks:
            if isinstance(value, bool):
                toml_inserts.append(f"{name} = {str(value).lower()}")
            elif isinstance(value, str):
                toml_inserts.append(f"{name} = '{value}'")
            else:
                raise Exception(f"Can't tweak toml setting: {name} = {value!r}")
        header = "[tool.coverage.run]\n"
        insert = header + "\n".join(toml_inserts) + "\n"
    else:
        header = insert = ""
    return file_replace(Path(toml_file), header, insert)

class AdHocProject(ProjectToTest):
    """A standalone program to run locally."""

    def __init__(self, python_file, cur_dir=None, pip_args=None):
        super().__init__()
        self.python_file = Path(python_file)
        if not self.python_file.exists():
            raise ValueError(f"Couldn't find {self.python_file} to run ad-hoc.")
        self.cur_dir = Path(cur_dir or self.python_file.parent)
        if not self.cur_dir.exists():
            raise ValueError(f"Couldn't find {self.cur_dir} to run in.")
        self.pip_args = pip_args
        self.slug = self.python_file.name

    def get_source(self, shell):
        pass

    def prep_environment(self, env):
        env.shell.run_command(f"{env.python} -m pip install {self.pip_args}")

    def run_no_coverage(self, env):
        with change_dir(self.cur_dir):
            env.shell.run_command(f"{env.python} {self.python_file}")
        return env.shell.last_duration

    def run_with_coverage(self, env, pip_args, cov_tweaks):
        env.shell.run_command(f"{env.python} -m pip install {pip_args}")
        with change_dir(self.cur_dir):
            env.shell.run_command(f"{env.python} -m coverage run {self.python_file}")
        return env.shell.last_duration

class SlipcoverBenchmark(AdHocProject):
    """
    For running code from the Slipcover benchmarks.

    Clone https://github.com/plasma-umass/slipcover to /src/slipcover

    """

    def __init__(self, python_file):
        super().__init__(
            python_file=f"/src/slipcover/benchmarks/{python_file}",
            cur_dir="/src/slipcover",
            pip_args="six pyperf",
        )

class PyVersion:
    """A version of Python to use."""

    # The command to run this Python
    command: str
    # Short word for messages, directories, etc
    slug: str
    # The tox environment to run this Python
    toxenv: str

class Python(PyVersion):
    """A version of CPython to use."""

    def __init__(self, major, minor):
        self.command = self.slug = f"python{major}.{minor}"
        self.toxenv = f"py{major}{minor}"

class PyPy(PyVersion):
    """A version of PyPy to use."""

    def __init__(self, major, minor):
        self.command = self.slug = f"pypy{major}.{minor}"
        self.toxenv = f"pypy{major}{minor}"

class AdHocPython(PyVersion):
    """A custom build of Python to use."""

    def __init__(self, path, slug):
        self.command = f"{path}/bin/python3"
        self.slug = slug
        self.toxenv = None

@dataclasses.dataclass
class Coverage:
    """A version of coverage.py to use, maybe None."""

    # Short word for messages, directories, etc
    slug: str
    # Arguments for "pip install ..."
    pip_args: Optional[str] = None
    # Tweaks to the .coveragerc file
    tweaks: Optional[Iterable[Tuple[str, Any]]] = None

class CoveragePR(Coverage):
    """A version of coverage.py from a pull request."""

    def __init__(self, number, tweaks=None):
        super().__init__(
            slug=f"#{number}",
            pip_args=f"git+https://github.com/nedbat/coveragepy.git@refs/pull/{number}/merge",
            tweaks=tweaks,
        )

class CoverageCommit(Coverage):
    """A version of coverage.py from a specific commit."""

    def __init__(self, sha, tweaks=None):
        super().__init__(
            slug=sha,
            pip_args=f"git+https://github.com/nedbat/coveragepy.git@{sha}",
            tweaks=tweaks,
        )

class CoverageSource(Coverage):
    """The coverage.py in a working tree."""

    def __init__(self, directory, tweaks=None):
        super().__init__(
            slug="source",
            pip_args=directory,
            tweaks=tweaks,
        )

@dataclasses.dataclass
class Env:
    """An environment to run a test suite in."""

    pyver: PyVersion
    python: Path
    shell: ShellSession

ResultKey = Tuple[str, str, str]

DIMENSION_NAMES = ["proj", "pyver", "cov"]

class Experiment:
    """A particular time experiment to run."""

    def __init__(
        self,
        py_versions: List[PyVersion],
        cov_versions: List[Coverage],
        projects: List[ProjectToTest],
    ):
        self.py_versions = py_versions
        self.cov_versions = cov_versions
        self.projects = projects
        self.result_data: Dict[ResultKey, List[float]] = {}

    def run(self, num_runs: int = 3) -> None:
        total_runs = (
            len(self.projects)
            * len(self.py_versions)
            * len(self.cov_versions)
            * num_runs
        )
        total_run_nums = iter(itertools.count(start=1))

        all_runs = []

        for proj in self.projects:
            print(f"Prepping project {proj.slug}")
            with proj.shell() as shell:
                proj.make_dir()
                proj.get_source(shell)

                for pyver in self.py_versions:
                    print(f"Making venv for {proj.slug} {pyver.slug}")
                    venv_dir = f"venv_{proj.slug}_{pyver.slug}"
                    shell.run_command(f"{pyver.command} -m venv {venv_dir}")
                    python = Path.cwd() / f"{venv_dir}/bin/python"
                    shell.run_command(f"{python} -V")
                    env = Env(pyver, python, shell)

                    with change_dir(proj.dir):
                        print(f"Prepping for {proj.slug} {pyver.slug}")
                        proj.prep_environment(env)
                        for cov_ver in self.cov_versions:
                            all_runs.append((proj, pyver, cov_ver, env))

        all_runs *= num_runs
        random.shuffle(all_runs)

        run_data: Dict[ResultKey, List[float]] = collections.defaultdict(list)

        for proj, pyver, cov_ver, env in all_runs:
            total_run_num = next(total_run_nums)
            print(
                "Running tests: "
                + f"{proj.slug}, {pyver.slug}, cov={cov_ver.slug}, "
                + f"{total_run_num} of {total_runs}"
            )
            with env.shell:
                with change_dir(proj.dir):
                    if cov_ver.pip_args is None:
                        dur = proj.run_no_coverage(env)
                    else:
                        dur = proj.run_with_coverage(
                            env,
                            cov_ver.pip_args,
                            cov_ver.tweaks,
                        )
            print(f"Tests took {dur:.3f}s")
            result_key = (proj.slug, pyver.slug, cov_ver.slug)
            run_data[result_key].append(dur)

        # Summarize and collect the data.
        print("# Results")
        for proj in self.projects:
            for pyver in self.py_versions:
                for cov_ver in self.cov_versions:
                    result_key = (proj.slug, pyver.slug, cov_ver.slug)
                    med = statistics.median(run_data[result_key])
                    self.result_data[result_key] = med
                    print(
                        f"Median for {proj.slug}, {pyver.slug}, "
                        + f"cov={cov_ver.slug}: {med:.3f}s"
                    )

    def show_results(
        self,
        rows: List[str],
        column: str,
        ratios: Iterable[Tuple[str, str, str]] = (),
    ) -> None:
        dimensions = {
            "cov": [cov_ver.slug for cov_ver in self.cov_versions],
            "pyver": [pyver.slug for pyver in self.py_versions],
            "proj": [proj.slug for proj in self.projects],
        }

        table_axes = [dimensions[rowname] for rowname in rows]
        data_order = [*rows, column]
        remap = [data_order.index(datum) for datum in DIMENSION_NAMES]

        WIDTH = 20

        def as_table_row(vals):
            return "| " + " | ".join(v.ljust(WIDTH) for v in vals) + " |"

        header = []
        header.extend(rows)
        header.extend(dimensions[column])
        header.extend(slug for slug, _, _ in ratios)

        print()
        print(as_table_row(header))
        dashes = [":---"] * len(rows) + ["---:"] * (len(header) - len(rows))
        print(as_table_row(dashes))
        for tup in itertools.product(*table_axes):
            row = []
            row.extend(tup)
            col_data = {}
            for col in dimensions[column]:
                key = (*tup, col)
                key = tuple(key[i] for i in remap)
                result_time = self.result_data[key]  # type: ignore
                row.append(f"{result_time:.1f} s")
                col_data[col] = result_time
            for _, num, denom in ratios:
                ratio = col_data[num] / col_data[denom]
                row.append(f"{ratio * 100:.0f}%")
            print(as_table_row(row))

PERF_DIR = Path("/tmp/covperf")

def run_experiment(
    py_versions: List[PyVersion],
    cov_versions: List[Coverage],
    projects: List[ProjectToTest],
    rows: List[str],
    column: str,
    ratios: Iterable[Tuple[str, str, str]] = (),
):
    slugs = [v.slug for v in py_versions + cov_versions + projects]
    if len(set(slugs)) != len(slugs):
        raise Exception(f"Slugs must be unique: {slugs}")
    if any(" " in slug for slug in slugs):
        raise Exception(f"No spaces in slugs please: {slugs}")
    ratio_slugs = [rslug for ratio in ratios for rslug in ratio[1:]]
    if any(rslug not in slugs for rslug in ratio_slugs):
        raise Exception(f"Ratio slug doesn't match a slug: {ratio_slugs}, {slugs}")
    if set(rows + [column]) != set(DIMENSION_NAMES):
        raise Exception(
            f"All of these must be in rows or column: {', '.join(DIMENSION_NAMES)}"
        )

    print(f"Removing and re-making {PERF_DIR}")
    rmrf(PERF_DIR)

    with change_dir(PERF_DIR):
        exp = Experiment(
            py_versions=py_versions, cov_versions=cov_versions, projects=projects
        )
        exp.run(num_runs=int(sys.argv[1]))
        exp.show_results(rows=rows, column=column, ratios=ratios)


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

import neat
import pickle

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

# Defining Global Constants

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

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

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

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

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

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

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

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

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

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

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

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

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

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

class Ball_Play:
    r = 10
    start_vel = 0.5

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

        self.start_x = x
        self.start_y = y

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        return right_score, left_score, outcome

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

class Ball():
    r = 10
    start_vel = 0.5

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

        self.start_x = x
        self.start_y = y

        self.paddle = paddle

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

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

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

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

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

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

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

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

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

        # Check for collisions with paddles

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

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

        elif self.x > WIN_WIDTH:
            outcome = -1

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

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

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

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

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

        self.ball.reset()

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

        return self.ball.update(dt)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        for p in paddles:
            p.draw(WIN)

        pygame.display.flip()

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

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

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

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

        outcome = AI.update(dt)

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

        WIN.fill(c_black)

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

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

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

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

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

        Game_play._draw_score()

        pygame.display.flip()

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

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

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

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

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

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


# -*- coding: utf-8 -*-
# import discord
import asyncio
import requests
import distutils.util
import os
import platform

from src.trackers.COMMON import COMMON
from src.console import console

class BLU():
    """
    Edit for Tracker:
        Edit BASE.torrent with announce and source
        Check for duplicates
        Set type/category IDs
        Upload
    """
    def __init__(self, config):
        self.config = config
        self.tracker = 'BLU'
        self.source_flag = 'BLU'
        self.search_url = 'https://blutopia.cc/api/torrents/filter'
        self.torrent_url = 'https://blutopia.cc/api/torrents/'
        self.upload_url = 'https://blutopia.cc/api/torrents/upload'
        self.signature = f"\n[center][url=https://blutopia.cc/forums/topics/3087]Created by L4G's Upload Assistant[/url][/center]"
        self.banned_groups = [
            '[Oj]', '3LTON', '4yEo', 'AFG', 'AniHLS', 'AnimeRG', 'AniURL', 'AROMA', 'aXXo', 'Brrip', 'CM8', 'CrEwSaDe', 'd3g', 'DeadFish', 'DNL', 'ELiTE', 'eSc', 'FaNGDiNG0', 'FGT', 'Flights',
            'FRDS', 'FUM', 'HAiKU', 'HD2DVD', 'HDS', 'HDTime', 'Hi10', 'ION10', 'iPlanet', 'JIVE', 'KiNGDOM', 'Leffe', 'LEGi0N', 'LOAD', 'MeGusta', 'mHD', 'mSD', 'NhaNc3', 'nHD', 'nikt0', 'NOIVTC',
            'nSD', 'PiRaTeS', 'playBD', 'PlaySD', 'playXD', 'PRODJi', 'RAPiDCOWS', 'RARBG', 'RDN', 'REsuRRecTioN', 'RMTeam', 'SANTi', 'SicFoI', 'SPASM', 'STUTTERSHIT', 'Telly', 'TM', 'TRiToN', 'UPiNSMOKE',
            'URANiME', 'WAF', 'x0r', 'xRed', 'XS', 'YIFY', 'ZKBL', 'ZmN', 'ZMNT',
            ['EVO', 'Raw Content Only'], ['TERMiNAL', 'Raw Content Only'], ['ViSION', 'Note the capitalization and characters used'], ['CMRG', 'Raw Content Only']
        ]

        pass

    async def upload(self, meta):
        common = COMMON(config=self.config)

        blu_name = meta['name']
        desc_header = ""
        if meta.get('webdv', False):
            blu_name, desc_header = await self.derived_dv_layer(meta)
        await common.edit_torrent(meta, self.tracker, self.source_flag)
        await common.unit3d_edit_desc(meta, self.tracker, self.signature, comparison=True, desc_header=desc_header)
        cat_id = await self.get_cat_id(meta['category'], meta.get('edition', ''))
        type_id = await self.get_type_id(meta['type'])
        resolution_id = await self.get_res_id(meta['resolution'])
        region_id = await common.unit3d_region_ids(meta.get('region'))
        distributor_id = await common.unit3d_distributor_ids(meta.get('distributor'))
        if meta['anon'] == 0 and bool(distutils.util.strtobool(str(self.config['TRACKERS'][self.tracker].get('anon', "False")))) == False:
            anon = 0
        else:
            anon = 1

        if meta['bdinfo'] != None:
            mi_dump = None
            bd_dump = open(f"{meta['base_dir']}/tmp/{meta['uuid']}/BD_SUMMARY_00.txt", 'r', encoding='utf-8').read()
        else:
            mi_dump = open(f"{meta['base_dir']}/tmp/{meta['uuid']}/MEDIAINFO.txt", 'r', encoding='utf-8').read()
            bd_dump = None
        desc = open(f"{meta['base_dir']}/tmp/{meta['uuid']}/[BLU]DESCRIPTION.txt", 'r').read()
        open_torrent = open(f"{meta['base_dir']}/tmp/{meta['uuid']}/[BLU]{meta['clean_name']}.torrent", 'rb')
        files = {'torrent': ("placeholder.torrent", open_torrent, "application/x-bittorrent")}
        data = {
            'name' : blu_name,
            'description' : desc,
            'mediainfo' : mi_dump,
            'bdinfo' : bd_dump,
            'category_id' : cat_id,
            'type_id' : type_id,
            'resolution_id' : resolution_id,
            'tmdb' : meta['tmdb'],
            'imdb' : meta['imdb_id'].replace('tt', ''),
            'tvdb' : meta['tvdb_id'],
            'mal' : meta['mal_id'],
            'igdb' : 0,
            'anonymous' : anon,
            'stream' : meta['stream'],
            'sd' : meta['sd'],
            'keywords' : meta['keywords'],
            'personal_release' : int(meta.get('personalrelease', False)),
            'internal' : 0,
            'featured' : 0,
            'free' : 0,
            'doubleup' : 0,
            'sticky' : 0,
        }
        # Internal
        if self.config['TRACKERS'][self.tracker].get('internal', False) == True:
            if meta['tag'] != "" and (meta['tag'][1:] in self.config['TRACKERS'][self.tracker].get('internal_groups', [])):
                data['internal'] = 1

        if region_id != 0:
            data['region_id'] = region_id
        if distributor_id != 0:
            data['distributor_id'] = distributor_id
        if meta.get('category') == "TV":
            data['season_number'] = meta.get('season_int', '0')
            data['episode_number'] = meta.get('episode_int', '0')
        headers = {
            'User-Agent': f'Upload Assistant/2.1 ({platform.system()} {platform.release()})'
        }
        params = {
            'api_token': self.config['TRACKERS'][self.tracker]['api_key'].strip()
        }

        if meta['debug'] == False:
            response = requests.post(url=self.upload_url, files=files, data=data, headers=headers, params=params)
            try:
                console.print(response.json())
            except:
                console.print("It may have uploaded, go check")

                return
        else:
            console.print(f"[cyan]Request Data:")
            console.print(data)
        open_torrent.close()

    async def get_cat_id(self, category_name, edition):
        category_id = {
            'MOVIE': '1',
            'TV': '2',
            'FANRES': '3'
            }.get(category_name, '0')
        if category_name == 'MOVIE' and 'FANRES' in edition:
            category_id = '3'
        return category_id

    async def get_type_id(self, type):
        type_id = {
            'DISC': '1',
            'REMUX': '3',
            'WEBDL': '4',
            'WEBRIP': '5',
            'HDTV': '6',
            'ENCODE': '12'
            }.get(type, '0')
        return type_id

    async def get_res_id(self, resolution):
        resolution_id = {
            '8640p':'10',
            '4320p': '11',
            '2160p': '1',
            '1440p' : '2',
            '1080p': '2',
            '1080i':'3',
            '720p': '5',
            '576p': '6',
            '576i': '7',
            '480p': '8',
            '480i': '9'
            }.get(resolution, '10')
        return resolution_id

    async def derived_dv_layer(self, meta):
        name = meta['name']
        desc_header = ""
        # Exit if not DV + HDR
        if not all([x in meta['hdr'] for x in ['HDR', 'DV']]):
            return name, desc_header
        import cli_ui
        console.print("[bold yellow]Generating the required description addition for Derived DV Layers. Please respond appropriately.")
        ask_comp = True
        if meta['type'] == "WEBDL":
            if cli_ui.ask_yes_no("Is the DV Layer sourced from the same service as the video?"):
                ask_comp = False
                desc_header = "[code]This release contains a derived Dolby Vision profile 8 layer. Comparisons not required as DV and HDR are from same provider.[/code]"

        if ask_comp:
            while desc_header == "":
                desc_input = cli_ui.ask_string("Please provide comparisons between HDR masters. (link or bbcode)", default="")
                desc_header = f"[code]This release contains a derived Dolby Vision profile 8 layer. Comparisons between HDR masters: {desc_input}[/code]"

        if "hybrid" not in name.lower():
            if "REPACK" in name:
                name = name.replace('REPACK', 'Hybrid REPACK')
            else:
                name = name.replace(meta['resolution'], f"Hybrid {meta['resolution']}")
        return name, desc_header

    async def search_existing(self, meta):
        dupes = []
        console.print("[yellow]Searching for existing torrents on site...")
        params = {
            'api_token' : self.config['TRACKERS'][self.tracker]['api_key'].strip(),
            'tmdbId' : meta['tmdb'],
            'categories[]' : await self.get_cat_id(meta['category'], meta.get('edition', '')),
            'types[]' : await self.get_type_id(meta['type']),
            'resolutions[]' : await self.get_res_id(meta['resolution']),
            'name' : ""
        }
        if meta['category'] == 'TV':
            params['name'] = params['name'] + f" {meta.get('season', '')}{meta.get('episode', '')}"
        if meta.get('edition', "") != "":
            params['name'] = params['name'] + f" {meta['edition']}"
        try:
            response = requests.get(url=self.search_url, params=params)
            response = response.json()
            for each in response['data']:
                result = [each][0]['attributes']['name']
                # difference = SequenceMatcher(None, meta['clean_name'], result).ratio()
                # if difference >= 0.05:
                dupes.append(result)
        except:
            console.print('[bold red]Unable to search for existing torrents on site. Either the site is down or your API key is incorrect')
            await asyncio.sleep(5)

        return dupes


#-*-coding: utf-8 -*-
import sys
import xml.sax
from xml.sax.handler import ContentHandler

class MyHandler(ContentHandler):    #핸들러를 상속받는다.
    _name_node = ['friends', 'address', 'name']     #엘리먼트 목록지정
    _address_node=['friends','address','addr']
    def __init__(self, writer = sys.stdout):    #표준 출력을 지정 및 초기화
        self.writer = writer
        self._node = []
        ContentHandler.__init__(self)

    def startElement(self, name, attrs):    #ContentHandler의 재정의 메소드(=오버라이딩한 메소드)
        self._node.append(name)

    def endElement(self, name):     #ContentHandler의 재정의 메소드
        self._node.pop()

    def characters(self, data):     #ContentHandler의 재정의 메소드
        if self._node == self._name_node:
            self.writer.write(u'name:%s\n' % data)
        elif self._node == self._address_node:
            self.writer.write(u'address:%s\n' %data)
            self.writer.write(u'------------\n')
    #위에 start~, end~, characters메소드들은 명시적으로 호출하는 명령문이 없어, 자동 호출됨 ->callback 메소드
    #시작 태그를 만나면, 종료 태그를 만나면, 데이터를 만나면 자동 호출됨

if __name__ == "__main__":
    parser = xml.sax.make_parser()      #SAX SMLReader 객체를 만들어 반환한다
    parser.setContentHandler(MyHandler())
    parser.parse("myfriend.xml")


import json
import jwt
import requests
from datetime     import datetime

from django.http  import JsonResponse
from django.views import View

from .models     import User
from my_settings import SECRET_KEY

class KakaoSigninView(View):
    def get(self, request):
        try:
            access_token = request.headers.get("Authorization")

            if not access_token:
                return JsonResponse({"message" : "NEED_TOKEN"}, status = 400)

            response = requests.get(
                "https://kapi.kakao.com/v2/user/me",
                headers = {"Authorization" : f"Bearer {access_token}"},
                timeout = 5
            )

            if not response.status_code == 200:
                raise ConnectionError

            profile_json  = response.json()

            kakao_id      = profile_json.get("id")
            kakao_account = profile_json.get("kakao_account")
            name          = kakao_account["profile"]["nickname"]
            thumbnail     = kakao_account["profile"]["thumbnail_image_url"]
            month         = kakao_account["birthday"][0:2]
            day           = kakao_account["birthday"][2:4]
            birthday      = datetime.strptime(f"2000-{month}-{day}", "%Y-%m-%d").date() if kakao_account["birthday_needs_agreement"] else None

            user, created = User.objects.get_or_create(
                kakao     = kakao_id,
                defaults  = {
                    "name"      : name,
                    "thumbnail" : thumbnail,
                    "birthday"  : birthday,
                    "is_host"   : False
            })

            user.name      = name
            user.thumbnail = thumbnail
            user.birthday  = birthday
            user.save()

            token = jwt.encode({"id" : user.id}, SECRET_KEY, algorithm = "HS256")
            return JsonResponse({"message" : "SUCCESS", "token" : token}, status = 200)

        except KeyError:
            return JsonResponse({"message" : "KEY_ERROR"}, status = 400)

        except ConnectionError:
            return JsonResponse({"message" : "KAKAO_ERROR"}, status = 408)


"""Tests of argument validation"""

import pytest

from hipscat_import.index.arguments import IndexArguments

def test_none():
    """No arguments provided. Should error for required args."""
    with pytest.raises(ValueError):
        IndexArguments()

def test_empty_required(tmp_path, small_sky_object_catalog):
    """*Most* required arguments are provided."""
    ## Input path is missing
    with pytest.raises(ValueError, match="input_catalog_path"):
        IndexArguments(
            indexing_column="id",
            output_path=tmp_path,
            output_artifact_name="small_sky_object_index",
        )

    ## Input path is bad
    with pytest.raises(ValueError, match="input_catalog_path"):
        IndexArguments(
            input_catalog_path="/foo",
            indexing_column="id",
            output_path=tmp_path,
            output_artifact_name="small_sky_object_index",
            overwrite=True,
        )

    ## Indexing column is required.
    with pytest.raises(ValueError, match="indexing_column "):
        IndexArguments(
            input_catalog_path=small_sky_object_catalog,
            indexing_column="",
            output_path=tmp_path,
            output_artifact_name="small_sky_object_index",
            overwrite=True,
        )

def test_invalid_paths(tmp_path, small_sky_object_catalog):
    """Required arguments are provided, but paths aren't found."""
    ## Prove that it works with required args
    IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
    )
    ## Input path is invalid catalog
    with pytest.raises(ValueError, match="input_catalog_path not a valid catalog"):
        IndexArguments(
            input_catalog_path="path",
            indexing_column="id",
            output_path=f"{tmp_path}/path",
            output_artifact_name="small_sky_object_index",
        )

def test_good_paths(tmp_path, small_sky_object_catalog):
    """Required arguments are provided, and paths are found."""
    tmp_path_str = str(tmp_path)
    args = IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
    )
    assert args.input_catalog_path == small_sky_object_catalog
    assert str(args.output_path) == tmp_path_str
    assert str(args.tmp_path).startswith(tmp_path_str)

def test_column_inclusion_args(tmp_path, small_sky_object_catalog):
    """Test errors for healpix partitioning arguments"""
    with pytest.raises(ValueError, match="one of"):
        IndexArguments(
            input_catalog_path=small_sky_object_catalog,
            indexing_column="id",
            output_path=tmp_path,
            output_artifact_name="small_sky_object_index",
            include_hipscat_index=False,
            include_order_pixel=False,
        )
    _ = IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
        overwrite=True,
        include_hipscat_index=True,
        include_order_pixel=True,
    )

    _ = IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
        overwrite=True,
        include_hipscat_index=True,
        include_order_pixel=False,
    )
    _ = IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
        overwrite=True,
        include_hipscat_index=False,
        include_order_pixel=True,
    )

def test_compute_partition_size(tmp_path, small_sky_object_catalog):
    """Test validation of compute_partition_size."""
    with pytest.raises(ValueError, match="compute_partition_size"):
        IndexArguments(
            input_catalog_path=small_sky_object_catalog,
            indexing_column="id",
            output_path=tmp_path,
            output_artifact_name="small_sky_object_index",
            compute_partition_size=10,  ## not a valid join option
        )

def test_to_catalog_info(small_sky_object_catalog, tmp_path):
    """Verify creation of catalog parameters for index to be created."""
    args = IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
        include_hipscat_index=True,
        include_order_pixel=True,
    )
    catalog_info = args.to_catalog_info(total_rows=10)
    assert catalog_info.catalog_name == args.output_artifact_name
    assert catalog_info.total_rows == 10

def test_provenance_info(small_sky_object_catalog, tmp_path):
    """Verify that provenance info includes index-specific fields."""
    args = IndexArguments(
        input_catalog_path=small_sky_object_catalog,
        indexing_column="id",
        output_path=tmp_path,
        output_artifact_name="small_sky_object_index",
        include_hipscat_index=True,
        include_order_pixel=True,
    )

    runtime_args = args.provenance_info()["runtime_args"]
    assert "input_catalog_path" in runtime_args


def solution(record):
    answer = []
    user={}
    for i in record:
        s = i.split(' ')
        print(s);
        if s[0]=="Enter":
            user[s[1]]=s[2]
        elif s[0]=="Change":
            user[s[1]]=s[2]

    print(user)
    for i in record:
        s = i.split(' ')
        if s[0]=="Enter":
            answer.append(user[s[1]]+"님이 들어왔습니다.")
        elif s[0]=="Leave":
            answer.append(user[s[1]]+"님이 나갔습니다.")
    return answer


# -*- coding: utf-8 -*-
"""
this file implements the core functionality for collectors and analyzers
"""

__author__ = "Lukas Reiter"
__license__ = "GPL v3.0"
__copyright__ = """Copyright 2018 Lukas Reiter

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.
"""
__version__ = 0.1

import logging
import argparse
from queue import Queue
from threading import Lock
from threading import Thread
from database.core import Engine
from database.model import Path
from database.model import File
from database.model import MatchRule
from database.model import SearchLocation
from database.model import FileRelevance
from config.config import FileHunter as FileHunterConfig

logger = logging.getLogger('analyzer')

class BaseAnalyzer(Thread):
    """
    This class implements all base functionalities for collectors and analyzers
    """

    DB_OPERATION_MUTEX = Lock()

    def __init__(self,
                 engine: Engine,
                 args: argparse.Namespace,
                 config: FileHunterConfig,
                 file_queue: Queue,
                 daemon: bool = False):
        super().__init__(daemon=daemon)
        self.engine = engine
        self.file_queue = file_queue
        self._args = args
        self.workspace = args.workspace
        self.config = config

    def is_file_size_below_threshold(self, path: Path, size: int) -> bool:
        """
        This method determines if the given file size in bytes is below the configured threshold.
        """
        return self.config.is_below_threshold(path, size)

    def add_content(self, path: Path, rule: MatchRule = None, file: File = None):
        """
        This method adds the rule matching result to the database.
        """
        if rule and file:
            raise ValueError("parameters rule and file are mutual exclusive")
        elif not rule and not file:
            raise ValueError("either parameter rule or file must be given")
        with BaseAnalyzer.DB_OPERATION_MUTEX:
            with self.engine.session_scope() as session:
                workspace = self.engine.get_workspace(session, name=self.workspace)
                host = self.engine.add_host(session=session,
                                            workspace=workspace,
                                            address=path.service.host.address)
                service = self.engine.add_service(session=session,
                                                  port=path.service.port,
                                                  name=path.service.name,
                                                  host=host)
                if rule:
                    match_file = self.engine.add_match_rule(session=session,
                                                            search_location=rule.search_location,
                                                            search_pattern=rule.search_pattern,
                                                            relevance=rule.relevance,
                                                            accuracy=rule.accuracy,
                                                            category=rule.category)
                    file = self.engine.add_file(session=session,
                                                workspace=workspace,
                                                file=path.file)
                    file.add_match_rule(match_file)
                self.engine.add_path(session=session,
                                     service=service,
                                     full_path=path.full_path,
                                     share=path.share,
                                     file=file,
                                     access_time=path.access_time,
                                     modified_time=path.modified_time,
                                     creation_time=path.creation_time)

    def _analyze_content(self, path: Path) -> FileRelevance:
        """
        This method analyzes the file's content for interesting information.
        :param path: The path object whose content shall be analyzed.
        :return: True if file is of relevance
        """
        result = None
        for rule in self.config.matching_rules[SearchLocation.file_content.name]:
            if rule.is_match(path):
                logger.info("Match: {} ({})".format(str(path), rule.get_text(not self._args.nocolor)))
                result = rule.relevance
                self.add_content(path=path, rule=rule)
                break
        return result

    def _analyze_path_name(self, path: Path) -> FileRelevance:
        """
        This method analyzes the file's name for interesting information.
        :param path: The path object whose name shall be analyzed.
        :return: True if file is of relevance
        """
        result = None
        # First we search the full path
        for rule in self.config.matching_rules[SearchLocation.full_path.name]:
            if rule.is_match(path):
                logger.info("Match: {} ({})".format(str(path), rule.get_text(not self._args.nocolor)))
                result = rule.relevance
                self.add_content(rule=rule, path=path)
                break
        else:
            # If nothing is found, then we search the file name
            for rule in self.config.matching_rules[SearchLocation.file_name.name]:
                if rule.is_match(path):
                    logger.info("Match: {} ({})".format(str(path), rule.get_text(not self._args.nocolor)))
                    result = rule.relevance
                    self.add_content(rule=rule, path=path)
                    break
        return result


"""This will convert a polyglot book to a list of move sequences and corresponding probabilities
given that moves are selected in proportion to their polyglot weight (i.e., win-rate)

E.g.:
"""

import chess
import chess.polyglot
from collections import namedtuple
import fire
import math
import numpy as np

Entry = namedtuple('Entry', 'move weight')

def dfs(reader, board, depth, ll=0):
    """Yield a list of move-sequences and corresponding log-likelihoods"""
    total_weight = 0
    entries = []
    for pentry in reader.find_all(board):
        total_weight += pentry.weight
        entries.append(Entry(pentry.move(), pentry.weight))
    for entry in entries:
        board.push(entry.move)
        ll_cur = ll + math.log(entry.weight/total_weight)
        if depth<=1:
            moves = ' '.join(move.uci() for move in board.move_stack)
            yield((moves, ll_cur))
        else:
            yield from dfs(reader, board, depth-1, ll_cur)
        board.pop()

def main(book_filename, length):
    """Output a list of fixed-length move-sequences and corresponding probabilities
    in a polyglot opening book

    book_filename: Filename of polyglot opening book
    length: Length of move sequences to extract
    """
    assert(length>=1)
    with chess.polyglot.open_reader("bookfish.bin") as reader:
        moves, lls = zip(*dfs(reader, chess.Board(), length))
    lls = np.array(lls)  # log likelihoods
    ls = np.exp(lls - max(lls))  # likelihoods
    ps = ls/sum(ls)  # probabilities
    seqs = zip(ps, moves)
    seqs = sorted(seqs, key=lambda it: it[0], reverse=True)
    for item in seqs:
        print("{:.9e} {}".format(*item))

# main("bookfish.bin", 8)
fire.Fire(main)


from vantage6.client import Client
from pathlib import Path
import time

print("Attempt login to Vantage6 API")
client = Client("http://localhost", 5000, "/api")
client.authenticate("node1-user", "node1-password")

client.setup_encryption(None)

input_ = {
    "master": "true",
    "method":"master",
    "args": [],
    "kwargs": {}
}

print("Requesting to execute colnames algorithm")

task = client.post_task(
    name="testing",
    image="jaspersnel/v6-colnames-py",
    collaboration_id=client.collaboration.list()[0]['id'],
    input_= input_,
    organization_ids=[client.collaboration.list()[0]['organizations'][0]['id']]
)

print("Wait and fetch results")
res = client.result.get(id_=task.get("results")[0]['id'])
attempts=1
while((res["result"] == None) and attempts < 7):
    print("waiting...")
    time.sleep(5)
    res = client.result.get(id_=task.get("results")[0]['id'])
    attempts += 1

print(res)


import sqlite3

#connect to database
db = sqlite3.connect('tour_merchandise.db')

#allows you to access columns by column name
db.row_factory = sqlite3.Row

#create cursor object to execute commands
cur = db.cursor()

#create a tables for the databse if they don't exist
cur.execute('create table if not exists venue(Venue text, Date date, Street text, City text, State text, ZipCode text)')
cur.execute('create table if not exists merchandise(ItemName text, Description text, Price float, Quantity integer)')
cur.execute('create table if not exists sales(Venue text, ItemName text, NumItems integer, '
            'FOREIGN KEY(Venue) REFERENCES venue(Venue), FOREIGN KEY(ItemName) REFERENCES merchandise(ItemName))')

#method used to add venues to the database
def add_venue_to_db(venue, concertDate, street, city, state, zipCode):
    try:
        with db:
            cur.execute('insert into venue values(?, ?, ?, ?, ?, ?)', (venue, concertDate, street, city, state, zipCode))
    except sqlite3.Error as error:
        print('Error adding venue.')
        print(error)

#method used to add new merchandise to database
def add_merch_to_db(itemName, description, price, quantity):
    try:
        with db:
            cur.execute('insert into merchandise values(?, ?, ?, ?)', (itemName, description, price, quantity))
    except sqlite3.Error as error:
        print('Error adding merchandise.')
        print(error)

#method used to add sales information to database
def add_sales_to_db(venue, itemName, numItems):
    try:
        with db:
            cur.execute('insert into sales values(?, ?, ?)', (venue, itemName, numItems))
    except sqlite3.Error as error:
        print('Error adding sale.')
        print(error)

#method to perform searches on the venue table
def search_venue(choice, venue, itemName):
    if choice == '1':
        results = cur.execute('select Max(NumItems) from sales group by venue')
        print(results[0])

#method used to perform searches on the merchandise table
def search_merchandise():
    return

#method used to perform searches on the sales table
def search_sales():
    return

#prints menu selections and gets/returns selection
def main_menu_print():
    print('1. Search')
    print('2. Add Venue')
    print('3. Add Merchandise')
    print('4. Add Sales Information')
    print('5. Exit')
    user_input = int(input('Enter the number of your selection. '))
    return user_input

#used to give the user a choice on which table to add/make changes
def secondary_menu_print():
    #get required information
    print('1. Venue')
    print('2. Merchandise')
    print('3. Sale')
    choice = input('Enter the number of your choice. ')
    return choice

#method used to query the database.
def search_record(choice):
    #determine which table to look in
    tableName = ''
    if choice == '1':
        tableName = 'venue'
        print('What search would you like to perform?')
        print('1. How much merchandise was sold at single venue.')
        print('2. Where was the most merchandise sold.')
        print('3. Where was the least merchandise sold.')
        search = input('Enter the number of your choice. ')
        if search == '1':
            venue = input('Enter the name of the venue. ')
            itemName = input('Enter the name of the item. ')
            search_venue(search, venue, itemName)
        elif search == '2':
            search_venue(2)
    elif choice == '2':
        tableName = 'merchandise'
        print('What search would you like to perform?')
        print('1. ')
        print('2. ')
        search = input('Enter the number of your choice. ')
        if search == '1':
            search_merchandise()
    elif choice == '3':
        tableName = 'sales'
        print('What search would you like to perform?')
        print('1. How much merchandise was sold at single venue.')
        print('2. What is the biggest seller.')
        print('3. What sells the least.')

    #print the results

#method to add records to the database
def add_venue():
    venue = input('What is the venue name? ')
    concertDate = input('What is the concert date? (yyyy-mm-dd) ')
    street = input('What is the street address? ')
    city = input('What city is the venue in? ')
    state = input('What state is the venue in? ')
    zipCode = input('Enter the Zip Code. ')
    #add record to database
    add_venue_to_db(venue, concertDate, street, city, state, zipCode)

def add_merchandise():
    #get new merchandise info
    itemName = input('What is the item name? ')
    description = input('Enter a description of the item. ')
    price = float(input('Enter a price for the item. '))
    quantity = int(input('How many of these do you have? '))
    #add record to database
    add_merch_to_db(itemName, description, price, quantity)

def add_sales():
    #get sale information
    venue = input('At what venue was this sale completed? ')
    itemName = input('What is the item name? ')
    numItems = input('How many were sold? ')
    #add record to database
    add_sales_to_db(venue, itemName, numItems)

#main method
def main():
    #display menu and get user input
    user_input = main_menu_print()
    #call appropriate method
    while user_input != 5:
        if user_input == 1:
            print('Where do you want to search?')
            choice = secondary_menu_print()
            search_record(choice)
        elif user_input == 2:
            add_venue()
        elif user_input == 3:
            add_merchandise()
        elif user_input == 4:
            add_sales()
        #re-display the menu
        user_input = main_menu_print()

    #when 5 is selected, exit the program
    exit()

main()


from transformers import AutoModelForSequenceClassification, AutoTokenizer
import torch
from torch.utils.data import Dataset, DataLoader

# Load pre-trained model and tokenizer
model_name = 'bert-base-uncased'
model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2)
tokenizer = AutoTokenizer.from_pretrained(model_name)

# Define your custom dataset
class CustomDataset(Dataset):
    def __init__(self, texts, labels):
        self.texts = texts
        self.labels = labels

    def __getitem__(self, idx):
        encoding = tokenizer(self.texts[idx], truncation=True, padding=True, return_tensors='pt')
        label = torch.tensor(self.labels[idx])
        return encoding, label

    def __len__(self):
        return len(self.texts)

# Load your custom dataset
train_texts = [
    "I loved the movie. The storyline was captivating and the acting was brilliant.",
    "The movie was hilarious and had great performances. A must-watch comedy.",
    "The film was a mix of romance and action. It kept me engaged throughout.",
    "The movie had stunning visuals and impressive cinematography. A visual treat.",
    "The film was a psychological thriller with a twist ending. It kept me on the edge of my seat."
]

train_labels = [
    ["Positive"],
    ["Positive", "Funny"],
    ["Positive", "Romance", "Action"],
    ["Positive", "Visuals"],
    ["Positive", "Thriller"]
]

train_dataset = CustomDataset(train_texts, train_labels)
train_dataloader = DataLoader(train_dataset, batch_size=16, shuffle=True)

# Fine-tune the model
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-5)
loss_fn = torch.nn.CrossEntropyLoss()

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

model.train()
for epoch in range(3):  # Adjust the number of epochs as needed
    running_loss = 0.0
    for inputs, labels in train_dataloader:
        inputs = {key: value.to(device) for key, value in inputs.items()}
        labels = labels.to(device)

        optimizer.zero_grad()
        outputs = model(**inputs)
        loss = loss_fn(outputs.logits, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()

    epoch_loss = running_loss / len(train_dataloader)
    print(f"Epoch {epoch + 1} Loss: {epoch_loss:.4f}")

# Save the fine-tuned model
model.save_pretrained('fine_tuned_model')
tokenizer.save_pretrained('fine_tuned_model')


from common_classes  import thing_pprint

from lexer_test      import (tt_identifier, tt_number, tt_operator, tt_terminator
                             , tt_space, tt_comment, tt_literal, tt_list, tt_skip_list
                             , tok_openbr, tok_closebr, tok_opencurlybr, tok_closecurlbr
                             , tok_opensqbr, tok_closesqbr, tok_squote, tok_dquote
                             , tok_comma, tok_period, tok_colon, tok_semicolon
                             , tok_accent

                             , token_iterator, token_reader)

from parser          import (Grammar, Node, TRef)

from lexer           import (TokenIterator, TokenReader)

from iters_readers   import TextReader

from readable_things import (Literal, NotNeed, Seq, Num
                             , ZeroOrOne, ZeroOrMore, OneOrMore
                             , Look, Rx, Concat, Or, Not)

from utils import SetRecursionLimit

cur_grammar = Grammar()

pn_identifier = Node("pn_identifier", OneOrMore(tt_identifier), grammar=cur_grammar)
pn_terminal = Node("terminal", OneOrMore(tt_literal), grammar=cur_grammar)
pn_ident_or_term = Or(pn_identifier, pn_terminal)

pn_lhs = Node("pn_lhs", pn_identifier, grammar=cur_grammar)

pn_option = Node("pn_option"
                 , Seq(NotNeed(tok_opensqbr), TRef("pn_rhs", grammar=cur_grammar, flat=True), NotNeed(tok_closesqbr))
                 , grammar=cur_grammar)
pn_repetition = Node("pn_repetition"
                     , Seq(NotNeed(tok_opencurlybr), TRef("pn_rhs", grammar=cur_grammar, flat=True), NotNeed(tok_closecurlbr))
                     , grammar=cur_grammar)
pn_grouping = Node("group"
                   , Seq(NotNeed(tok_openbr), TRef("pn_rhs", grammar=cur_grammar, flat=True), NotNeed(tok_closebr))
                   , grammar=cur_grammar)
pn_negation = Node("pn_negation"
                   , Seq(NotNeed("^"), TRef("pn_rhs", grammar=cur_grammar, flat=True), NotNeed("^"))
                   , grammar=cur_grammar)
pn_regexp = Node("pn_regexp"
                 , Seq(NotNeed("rx"), tt_literal)
                 , grammar=cur_grammar)

pn_groups = Or(pn_option, pn_repetition, pn_grouping, pn_negation, pn_regexp)

pn_ident_or_term_or_pn_groups = Or(pn_ident_or_term, pn_groups)

pn_concatenation = Node("pn_concatenation"
                        , Seq(pn_ident_or_term_or_pn_groups, NotNeed(","), TRef("pn_rhs", grammar=cur_grammar, flat=True))
                        , flat_eq_name=True, grammar=cur_grammar)

pn_alteration = Node("pn_alteration"
                     , Seq(pn_ident_or_term_or_pn_groups, NotNeed("|"), TRef("pn_rhs", grammar=cur_grammar, flat=True))
                     , flat_eq_name=True, grammar=cur_grammar)

pn_rhs = Node("pn_rhs", Or(pn_concatenation
                           , pn_alteration
                           , pn_groups
                           , pn_ident_or_term)
              , grammar=cur_grammar)

pn_rule = Node("pn_rule", Seq(pn_lhs, NotNeed("="), pn_rhs, NotNeed(";")), grammar=cur_grammar)
pn_grammar = Node("ebnf_grammar", ZeroOrMore(pn_rule), grammar=cur_grammar)

#token_iterator = TokenIterator(TextReader(open("test1.txt"))
#                               , tt_list, tt_skip_list)
#token_reader = TokenReader(token_iterator)
#token_reader = TokenReader(TokenIterator(TextReader(open("test1.txt")), tt_list, tt_skip_list))

if __name__ == '__main__':
    # SetRecursionLimit(5000)
    # lexing with tokens reduces recursion level
    rslt = pn_grammar.read_from(token_reader)
    thing_pprint(rslt.readedlist)


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

import data  # zville database
import zville  # zville functions

# SETTING UP

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

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

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

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

    real_time = None

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    # various speed parameters

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        # iteration + 1 counter update
        round_count += 1

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

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

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

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

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

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


"""
Helper functions for working with S3 and STS
"""
import base64
import datetime
import hashlib
import hmac
import os
import posixpath
import sys
import urllib.parse
import uuid
from typing import List, Optional, Set, Tuple, TypedDict, Union

import boto3
import minio
from botocore.client import Config

from . import schemas

class Boto3ClientCache:
    """
    There may be many s3 nodes in the cluser.  Once a client has been established,
    cache it for future use.  This class works for sts and s3 type clients.
    """

    def __init__(self):
        self.cache: Dict[str, boto3.Session] = {}

    @staticmethod
    def _get_primary_key(client_type: str, node: schemas.StorageNodeOperator) -> str:
        if not client_type in ["s3", "sts"]:
            raise ValueError(f"{client_type} unsupported by cache")
        primary_key = (
            (
                f"{client_type}{node.region_name}{node.api_url}"
                f"{node.access_key_id}{node.secret_access_key}"
            )
            .lower()
            .encode("utf-8")
        )
        return hashlib.sha256(primary_key).hexdigest()

    def get_client(
        self,
        client_type: str,
        node: schemas.StorageNodeOperator,
    ) -> boto3.Session:
        primary_key_short_sha256 = Boto3ClientCache._get_primary_key(client_type, node)
        client = self.cache.get(primary_key_short_sha256, None)
        if client is None:
            config = Config()
            if client_type == "s3":
                config = Config(signature_version="s3v4")
                client = boto3.client(
                    client_type,
                    region_name=node.region_name,
                    endpoint_url=node.api_url,
                    aws_access_key_id=node.access_key_id,
                    aws_secret_access_key=node.secret_access_key,
                    config=config,
                )
            elif client_type == "sts":
                is_aws = node.assume_role_arn is not None
                api_url = node.api_url
                if node.sts_api_url:
                    api_url = node.sts_api_url
                elif is_aws:
                    api_url = f"https://sts.{node.region_name}.amazonaws.com"
                client = boto3.client(
                    client_type,
                    region_name=node.region_name,
                    endpoint_url=api_url,
                    aws_access_key_id=node.access_key_id,
                    aws_secret_access_key=node.secret_access_key,
                )
            else:
                raise NotImplementedError("Client type not implemented")
            self.cache[primary_key_short_sha256] = client
        return client

    def get_minio_sdk_client(self, node: schemas.StorageNodeOperator) -> minio.Minio:
        primary_key_short_sha256 = (
            Boto3ClientCache._get_primary_key("s3", node) + "minio"
        )
        client = self.cache.get(primary_key_short_sha256, None)
        url = urllib.parse.urlparse(node.api_url)
        if client is None:
            client = minio.Minio(
                url.netloc,
                access_key=node.access_key_id,
                secret_key=node.secret_access_key,
                secure=False,
            )
            self.cache[primary_key_short_sha256] = client
        return client

def sanitize(name: str) -> str:
    # TODO
    return name

def getWorkspaceKey(
    workspace: schemas.WorkspaceDB,
    root: Optional[schemas.WorkspaceRootDB] = None,
) -> str:
    """
    Determine full object prefix for a given workspace
    """
    root_base_path = root.base_path if root else workspace.root.base_path
    inner_path = posixpath.join(workspace.owner.username, sanitize(workspace.name))
    if workspace.base_path != None:
        inner_path = str(workspace.base_path)
    return posixpath.join(
        root_base_path,
        inner_path,
    ).strip("/")

S3V4Headers = TypedDict(
    "S3V4Headers",
    {
        "x-amz-date": str,
        "Authorization": str,
    },
)

def get_s3v4_headers(
    access_key: str,
    secret_key: str,
    region="us-east-1",
    service="s3",
    host="localhost:9000",
    endpoint="http://localhost:9000",
    uri="/",
    request_parameters="",
) -> S3V4Headers:
    method = "GET"
    # Key derivation functions. See:
    # http://docs.aws.amazon.com/general/latest/gr/signature-v4-examples.html#signature-v4-examples-python
    def sign(key, msg):
        return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

    def getSignatureKey(key, dateStamp, regionName, serviceName):
        kDate = sign(("AWS4" + key).encode("utf-8"), dateStamp)
        kRegion = sign(kDate, regionName)
        kService = sign(kRegion, serviceName)
        kSigning = sign(kService, "aws4_request")
        return kSigning

    # Create a date for headers and the credential string
    t = datetime.datetime.utcnow()
    amzdate = t.strftime("%Y%m%dT%H%M%SZ")
    datestamp = t.strftime("%Y%m%d")  # Date w/o time, used in credential scope

    # ************* TASK 1: CREATE A CANONICAL REQUEST *************
    # http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

    # Step 1 is to define the verb (GET, POST, etc.)--already done.

    # Step 2: Create canonical URI--the part of the URI from domain to query
    # string (use '/' if no path)
    canonical_uri = uri

    # Step 3: Create the canonical query string. In this example (a GET request),
    # request parameters are in the query string. Query string values must
    # be URL-encoded (space=%20). The parameters must be sorted by name.
    # For this example, the query string is pre-formatted in the request_parameters variable.
    canonical_querystring = request_parameters

    # Step 4: Create the canonical headers and signed headers. Header names
    # must be trimmed and lowercase, and sorted in code point order from
    # low to high. Note that there is a trailing \n.
    canonical_headers = "host:" + host + "\n" + "x-amz-date:" + amzdate + "\n"

    # Step 5: Create the list of signed headers. This lists the headers
    # in the canonical_headers list, delimited with ";" and in alpha order.
    # Note: The request can include any headers; canonical_headers and
    # signed_headers lists those that you want to be included in the
    # hash of the request. "Host" and "x-amz-date" are always required.
    signed_headers = "host;x-amz-date"

    # Step 6: Create payload hash (hash of the request body content). For GET
    # requests, the payload is an empty string ("").
    payload_hash = hashlib.sha256(("").encode("utf-8")).hexdigest()

    # Step 7: Combine elements to create canonical request
    canonical_request = (
        method
        + "\n"
        + canonical_uri
        + "\n"
        + canonical_querystring
        + "\n"
        + canonical_headers
        + "\n"
        + signed_headers
        + "\n"
        + payload_hash
    )

    # ************* TASK 2: CREATE THE STRING TO SIGN*************
    # Match the algorithm to the hashing algorithm you use, either SHA-1 or
    # SHA-256 (recommended)
    algorithm = "AWS4-HMAC-SHA256"
    credential_scope = datestamp + "/" + region + "/" + service + "/" + "aws4_request"
    string_to_sign = (
        algorithm
        + "\n"
        + amzdate
        + "\n"
        + credential_scope
        + "\n"
        + hashlib.sha256(canonical_request.encode("utf-8")).hexdigest()
    )

    # ************* TASK 3: CALCULATE THE SIGNATURE *************
    # Create the signing key using the function defined above.
    signing_key = getSignatureKey(secret_key, datestamp, region, service)

    # Sign the string_to_sign using the signing_key
    signature = hmac.new(
        signing_key, (string_to_sign).encode("utf-8"), hashlib.sha256
    ).hexdigest()

    # ************* TASK 4: ADD SIGNING INFORMATION TO THE REQUEST *************
    # The signing information can be either in a query string value or in
    # a header named Authorization. This code shows how to use a header.
    # Create authorization header and add to request headers
    authorization_header = (
        algorithm
        + " "
        + "Credential="
        + access_key
        + "/"
        + credential_scope
        + ", "
        + "SignedHeaders="
        + signed_headers
        + ", "
        + "Signature="
        + signature
    )

    # The request can include any headers, but MUST include "host", "x-amz-date",
    # and (for this scenario) "Authorization". "host" and "x-amz-date" must
    # be included in the canonical_headers and signed_headers, as noted
    # earlier. Order here is not significant.
    # Python note: The 'host' header is added automatically by the Python 'requests' library.
    headers: S3V4Headers = {
        "x-amz-date": amzdate,
        "Authorization": authorization_header,
    }
    return headers


import tabula
import os

cur_dir = os.path.abspath('.')
pdf_dir = '../../pdf'
csv_dir = '../../csv'

files = os.listdir(pdf_dir)
for file in files:
    name = file.split('.')[0]
    pdf_path = os.path.join(cur_dir, pdf_dir, file)
    csv_path = os.path.join(cur_dir, csv_dir, f'{name}.csv')
    try:
        tabula.convert_into(pdf_path, csv_path, output_format="csv", lattice=True, relative_area=True, pages='all')
    except KeyboardInterrupt:
        break
    except:
        # os.remove(pdf_path)
        print(pdf_path)


import csv
from datetime import datetime

def read_csv(csv_file, float_numbers: bool):
    with open(csv_file, newline='') as f:
        reader = csv.DictReader(f)
        data = []
        for row in reader:
            if '-' not in row["price"] and float(row["price"]) > 0:
                if float_numbers:
                    row["price"] = int(float(row["price"]) * 100)
                    row["profit"] = int(float(row["profit"]) * 100)
                else:
                    row["price"] = float(row["price"])
                    row["profit"] = float(row["profit"])
                data.append(row)

    return data

def timer(func):
    def wrapper(*args, **kwargs):
        start_time = datetime.now()
        func(*args, **kwargs)
        end_time = datetime.now()
        print(end_time - start_time)
        return func(*args)

    return wrapper