import logging
import cv2
import numpy as np
import pytesseract
import os
import time
import json
import re

from multiprocessing import Pool
from Levenshtein import distance

from .input_handler import InputHandler
from .grabscreen import grab_screen
from .utils import get_config, filter_mod

# This is a position of the inventory as fraction of the resolution
OWN_INVENTORY_ORIGIN = (0.6769531, 0.567361)

# These are the sockets positions as measured on 2560x1440 resolution
# with X_SCALE and Y_SCALE applied, i.e., scale * SOCKETS[i] is the i:th
# sockets absolute pixel position with origin in the middle of the skill tree
# I think the SCALE variables are in fact useless and a relics from the
# positions initially being measured at a view which wasn't zoomed out maximally
SOCKETS = {
    1: (-650.565, -376.013),
    2: (648.905, -396.45),
    3: (6.3354, 765.658),
    4: (-1700.9, 2424.17),
    5: (-2800.66, -215.34),
    6: (-1435.02, -2635.39),
    7: (1855.53, -2360.1),
    8: (2835.84, 230.5361),
    9: (1225.37, 2625.76),
    10: (-120.12471, 5195.44),
    11: (-3580.19, 5905.92),
    12: (-5395.86, 2120.42),
    13: (-6030.95, -115.7007),
    14: (-5400.59, -1985.18),
    15: (-3035.14, -5400.87),
    16: (160.10728, -5196.32),
    17: (3382.05, -5195.21),
    18: (5730.2, -1625.75),
    19: (6465.24, 190.3341),
    20: (5542.76, 1690.07),
    21: (3322.76, 6090.5),
}

# The offsets are specified in the same fashion as SOCKETS and are rough
# guesses which allow us to move to the general area and later refine the
# position of the socket through template matching
SOCKET_MOVE_OFFSET = {
    1: (0, 150),
    2: (0, 150),
    3: (0, 200),
    4: (0, 150),
    5: (-300, 200),
    6: (-100, 150),
    7: (-150, 0),
    8: (0, -150),
    9: (-100, -125),
    10: (170, 0),
    11: (-400, -900),
    12: (0, 300),
    13: (400, 200),
    14: (-250, -150),
    15: (-100, -150),
    16: (150, -150),
    17: (150, 500),  #
    18: (-300, 400),
    19: (-1000, -150),
    20: (-500, 500),
    21: (100, -1000),
}

# Scalers for the SOCKETS positions to convert them to 2560x1440 pixel positions
X_SCALE = 0.2
Y_SCALE = 0.2
CIRCLE_EFFECTIVE_RADIUS = 300

IMAGE_FOLDER = "data/images/"

# We're using a lot of template matching and all templates are defined here
# with matching thresholds (scores) and sizes per resolution
TEMPLATES = {
    "AmbidexterityCluster.png": {
        "1440p_size": (34, 34),
        "1440p_threshold": 0.95,
        "1080p_size": (26, 26),
        "1080p_threshold": 0.95,
    },
    "FreeSpace.png": {
        "1440p_size": (41, 41),
        "1440p_threshold": 0.98,
        "1080p_size": (30, 30),
        "1080p_threshold": 0.98,
    },
    "Notable.png": {
        "1440p_size": (30, 30),
        "1440p_threshold": 0.89,
        "1080p_size": (23, 23),
        "1080p_threshold": 0.85,
    },
    "NotableAllocated.png": {
        "1440p_size": (30, 30),
        "1440p_threshold": 0.93,
        "1080p_size": (23, 23),
        "1080p_threshold": 0.90,
    },
    "Jewel.png": {
        "1440p_size": (30, 30),
        "1440p_threshold": 0.92,
        "1080p_size": (23, 23),
        "1080p_threshold": 0.92,
    },
    "JewelSocketed.png": {
        "1440p_size": (30, 30),
        "1440p_threshold": 0.9,
        "1080p_size": (23, 23),
        "1080p_threshold": 0.9,
    },
    "LargeJewel.png": {
        "1440p_size": (39, 39),
        "1440p_threshold": 0.9,
        "1080p_size": (30, 30),
        "1080p_threshold": 0.88,
    },
    "LargeJewelSocketed.png": {
        "1440p_size": (39, 39),
        "1440p_threshold": 0.9,
        "1080p_size": (30, 30),
        "1080p_threshold": 0.88,
    },
    "Skill.png": {
        "1440p_size": (21, 21),
        "1440p_threshold": 0.87,
        "1080p_size": (15, 15),
        "1080p_threshold": 0.91,
    },
    "SkillAllocated.png": {
        "1440p_size": (21, 21),
        "1440p_threshold": 0.93,
        "1080p_size": (15, 15),
        "1080p_threshold": 0.91,
    },
}

# Defines the position of the text box which is cropped out and OCR'd per node
TXT_BOX = {"x": 32, "y": 0, "w": 900, "h": 320}

mod_files = {
    "passives": "data/passives.json",
    "passivesAlt": "data/passivesAlternatives.json",
    "passivesAdd": "data/passivesAdditions.json",
    "passivesVaalAdd": "data/passivesVaalAdditions.json",
}

class TreeNavigator:
    def __init__(self, resolution, halt_value):
        self.resolution = resolution
        self.input_handler = InputHandler(self.resolution)
        logging.basicConfig(
            level=logging.INFO,
            format="%(asctime)s %(message)s",
            datefmt="[%H:%M:%S %d-%m-%Y]",
        )
        self.log = logging.getLogger("tree_nav")
        self.config = get_config("tree_nav")
        self.find_mod_value_re = re.compile("(\(?(?:[0-9]*\.?[0-9]-?)+\)?)")
        self.nonalpha_re = re.compile("[^a-zA-Z]")
        self.origin_pos = (self.resolution[0] / 2, self.resolution[1] / 2)
        self.ingame_pos = [0, 0]
        self.px_multiplier = self.resolution[0] / 2560
        self.resolution_prefix = str(self.resolution[1]) + "p_"
        self.templates_and_masks = self.load_templates()
        self.passive_mods, self.passive_names = self.generate_good_strings(mod_files)
        self.passive_nodes = list(self.passive_mods.keys()) + list(
            self.passive_names.keys()
        )
        self.halt = halt_value
        self.first_run = True

    def _run(self):
        return not bool(self.halt.value)

    def eval_jewel(self, item_location):
        self.ingame_pos = [0, 0]
        item_name, item_desc = self._setup(item_location, copy=True)

        pool = Pool(self.config["ocr_threads"])
        jobs = {}

        if self.first_run:
            # We just initiated the module and not sure where we are
            # Thus, we better rectify our position estimate before starting
            self._refind_position(SOCKETS[1])
            self.first_run = False

        for socket_id in sorted(SOCKETS.keys()):
            if not self._run():
                return None, None, None
            found_socket = self._move_screen_to_socket(socket_id)
            if not found_socket and socket_id == 1:
                self.log.info("We are lost - trying to find known location")
                # We just initiated the search and have no clue where we are
                # Thus, we better rectify our position estimate before starting
                self._refind_position(SOCKETS[1])
            socket_nodes = self._analyze_nodes(socket_id)

            # Convert stats for the socket from image to lines in separate process
            self.log.info("Performing asynchronous OCR")
            jobs[socket_id] = pool.map_async(OCR.node_to_strings, socket_nodes)
            self.log.info("Analyzed socket %s" % socket_id)

        # Return to socket 1 to ease next search
        self._move_to_tree_pos_using_spaces(SOCKETS[1])

        self._setup(item_location)
        self.log.info("Waiting for last OCR to finish")
        item_stats = [
            {
                "socket_id": socket_id,
                "socket_nodes": self._filter_ocr_lines(
                    jobs[socket_id].get(timeout=300)
                ),
            }
            for socket_id in jobs
        ]
        pool.close()
        pool.join()
        return item_name, item_desc, item_stats

    def load_templates(self, threshold=128):
        templates_and_masks = {}
        for template_name in TEMPLATES.keys():
            template_path = os.path.join(IMAGE_FOLDER, template_name)
            img = cv2.imread(template_path, cv2.IMREAD_UNCHANGED)
            size = TEMPLATES[template_name][self.resolution_prefix + "size"]
            channels = cv2.split(img)
            mask = None
            if len(channels) > 3:
                mask = np.array(channels[3])
                mask[mask <= threshold] = 0
                mask[mask > threshold] = 255
                mask = cv2.resize(mask, size)

            img = cv2.imread(template_path, 0)
            img = cv2.resize(img, size)
            templates_and_masks[template_name] = {"image": img, "mask": mask}
        return templates_and_masks

    def _move_screen_to_socket(self, socket_id):
        self.log.debug("Moving close to socket %s" % socket_id)
        move_offset_tx, move_offset_ty = SOCKET_MOVE_OFFSET[socket_id]
        move_offset = self._tree_pos_to_xy(
            [move_offset_tx, move_offset_ty], offset=True
        )

        socket_tx, socket_ty = SOCKETS[socket_id]
        socket_xy = self._tree_pos_to_xy([socket_tx, socket_ty])
        compensation_offset = self._find_socket(socket_xy)
        if compensation_offset is None:
            found_socket = False
            compensation_offset = [0, 0]
        else:
            found_socket = True
            self.log.debug("Compensated navigation with %s" % compensation_offset)

        move_to = [
            socket_xy[0] + compensation_offset[0] + move_offset[0],
            socket_xy[1] + compensation_offset[1] + move_offset[1],
        ]

        x_offset = move_to[0] - self.resolution[0] / 2
        y_offset = move_to[1] - self.resolution[1] / 2

        self.input_handler.click(
            *move_to, *move_to, button=None, raw=True, speed_factor=1
        )
        self.input_handler.drag(self.origin_pos[0], self.origin_pos[1], speed_factor=1)
        self.input_handler.rnd_sleep(min=200, mean=300, sigma=100)
        self.ingame_pos = [socket_tx + move_offset_tx, socket_ty + move_offset_ty]
        return found_socket

    def _refind_position(self, desired_tree_pos):
        # If the current location has been determined to be incorrect
        # we can go to the bottom right corner and find a cluster close
        # to socket 21, namely the Ambidexterity cluster
        # This is a known location, which can then be used to calculate
        # our way to a desired position
        self.log.debug("Centering screen position")

        # Correct our tree position to a known value
        self._locate_screen_using_ambidexterity()

        # Find our way to the desired position
        self._move_to_tree_pos_using_spaces(desired_tree_pos)

    def _move_to_tree_pos_using_spaces(self, desired_tree_pos, max_position_error=5):
        dx = desired_tree_pos[0] - self.ingame_pos[0]
        dy = desired_tree_pos[1] - self.ingame_pos[1]
        self.log.debug("Moving to tree pos using spaces. Deltas: ({}, {})".format(dx, dy))
        while (abs(dx) + abs(dy)) > max_position_error:
            # Choose quadrant to find spaces in based on dx, dy
            right, bottom = dx >= 0, dy >= 0
            if right and not bottom:
                quadrant = 0
            elif not right and not bottom:
                quadrant = 1
            elif not right and bottom:
                quadrant = 2
            elif right and bottom:
                quadrant = 3

            # Find empty spaces that we can drag from
            spaces = self._find_empty_space(quadrant)
            if spaces is None:
                raise ValueError("Could not find an empty space, quitting.")

            # Choose a random empty space for maximum drag
            chosen_space = spaces[np.random.randint(spaces.shape[0])]

            # How far to drag the window to end up in the optimal place
            screen_move_x, screen_move_y = self._tree_pos_to_xy([dx, dy],
                                                                offset=True)

            # Calculate where our drag should end up to perform the move
            drag_x = chosen_space[0] - screen_move_x
            drag_y = chosen_space[1] - screen_move_y

            # We should only drag within the screen's resolution
            # Additionally, we use 100px margin to not trigger tree scroll
            drag_x = np.clip(drag_x, 100, self.resolution[0] - 100)
            drag_y = np.clip(drag_y, 100, self.resolution[1] - 100)

            # Drag
            self.input_handler.click(
                *chosen_space, *chosen_space, button=None, raw=True, speed_factor=1
            )
            self.input_handler.drag(drag_x, drag_y, speed_factor=1)
            self.input_handler.rnd_sleep(min=200, mean=300, sigma=100)

            # Calculate how far we've actually moved
            effective_move_x = chosen_space[0] - drag_x
            effective_move_y = chosen_space[1] - drag_y

            # Update our internal tree position
            self.ingame_pos = self._add_xy_offset_to_tree_pos(
                [effective_move_x, effective_move_y]
            )

            # Figure out how much we have left to move
            dx = desired_tree_pos[0] - self.ingame_pos[0]
            dy = desired_tree_pos[1] - self.ingame_pos[1]

    def _locate_screen_using_ambidexterity(self):
        # Essentially, this is _move_to_tree_pos_using_spaces but
        # only used to find the tree position by navigating to a known point
        self.log.debug("Moving to ambidexterity")
        ambidexterity_position = None
        assumed_ambidexterity_position = (0.25234375, 0.20555556)
        while ambidexterity_position is None:
            # Find empty spaces that we can drag from
            spaces = self._find_empty_space(3)
            if spaces is None:
                raise ValueError("Could not find an empty space, quitting.")

            # Choose the farthest empty space for maximum drag
            chosen_space = spaces[np.argmax(spaces.sum(axis=1))]

            # An arbitrary position in the top left region
            drag_location = (200, 200)

            # Drag
            self.input_handler.click(
                *chosen_space, *chosen_space, button=None, raw=True, speed_factor=1
            )
            self.input_handler.drag(drag_location[0], drag_location[1], speed_factor=1)
            self.input_handler.rnd_sleep(min=200, mean=300, sigma=100)

            # Are we there yet?
            # i.e., have we reached Ambidexterity, which in that case is at
            # roughly (646, 296) in absolute 1440p screen px position
            ambidexterity_position = self._find_icon(
                assumed_ambidexterity_position, "AmbidexterityCluster.png"
            )
        # Ambidexterity is located (-560, 850) from socket 21
        # Thus, this plus any (scaled) offset found by the template matcher is
        # our tree position
        self.ingame_pos = [
            SOCKETS[21][0]
            - 560
            + ambidexterity_position[0] / (X_SCALE * self.px_multiplier),
            SOCKETS[21][1]
            + 850
            + ambidexterity_position[1] / (Y_SCALE * self.px_multiplier),
        ]

    def _find_empty_space(self, quadrant):
        # Finds empty spaces that can be used to drag the screen
        # Used to recenter the screen
        # The quadrant argument is an int in [0, 1, 2, 3], corresponding to
        # [top-right, top-left, bottom-left, bottom-right]
        quadrant_translation = {0: [0.5, 0], 1: [0, 0], 2: [0, 0.5], 3: [0.5, 0.5]}
        fractional_lt = quadrant_translation[quadrant]
        lt = [
            int(fractional_lt[0] * self.resolution[0]),
            int(fractional_lt[1] * self.resolution[1]),
        ]
        rb = [int(lt[0] + self.resolution[0] / 2),
              int(lt[1] + self.resolution[1] / 2)]
        searched_area = grab_screen(tuple(lt + rb))
        searched_area = cv2.cvtColor(searched_area, cv2.COLOR_BGR2GRAY)

        locations = np.zeros_like(searched_area)

        centered_coordinates = self._match_image(searched_area, "FreeSpace.png")
        locations[tuple(centered_coordinates)] = 1

        rel_space_pos_yx = np.argwhere(locations == 1)
        rel_space_pos = rel_space_pos_yx.T[::-1].T
        if len(rel_space_pos) == 0:
            self.log.warning("Could not find any free spaces in tree!")
            return None
        screen_space_pos = rel_space_pos + lt

        # remove positions that are close to edges as these trigger scroll
        screen_space_pos = screen_space_pos[(screen_space_pos[:, 0] > 100) &
                            (screen_space_pos[:, 1] > 100) &
                            (screen_space_pos[:, 0] < self.resolution[0] - 100) &
                            (screen_space_pos[:, 1] < self.resolution[1] - 100)]
        return screen_space_pos

    def _find_icon(self, assumed_position, icon_name):
        # Finds the ambidexerity cluster icon in the region it sits in
        # if we are at the bottom-right corner of the tree
        # The exact location is used to refine our knowledge of our position
        abs_assumed_position = (
            assumed_position[0] * self.resolution[0],
            assumed_position[1] * self.resolution[1],
        )
        margin_side = int(0.05 * self.resolution[0])
        lt = [
            int(abs_assumed_position[0] - margin_side / 2),
            int(abs_assumed_position[1] - margin_side / 2),
        ]
        rb = [
            int(abs_assumed_position[0] + margin_side / 2),
            int(abs_assumed_position[1] + margin_side / 2),
        ]
        searched_area = grab_screen(tuple(lt + rb))
        searched_area = cv2.cvtColor(searched_area, cv2.COLOR_BGR2GRAY)
        locations = np.zeros((margin_side, margin_side))
        centered_coordinates = self._match_image(searched_area, icon_name)
        locations[tuple(centered_coordinates)] = 1
        rel_icon_pos_yx = np.argwhere(locations == 1)
        rel_icon_pos = rel_icon_pos_yx.T[::-1].T
        if len(rel_icon_pos) == 0:
            return None
        icon_offset = [
            int(rel_icon_pos[0][0] - margin_side / 2 + abs_assumed_position[0]),
            int(rel_icon_pos[0][1] - margin_side / 2 + abs_assumed_position[1]),
        ]
        return icon_offset

    def _click_socket(self, socket_pos, insert=True):
        self.log.debug("Clicking socket")
        xy = socket_pos
        lt = [xy[0] - 5 * self.px_multiplier, xy[1] - 5 * self.px_multiplier]
        rb = [xy[0] + 5 * self.px_multiplier, xy[1] + 5 * self.px_multiplier]
        if insert:
            self.input_handler.click(*lt, *rb, button="left", raw=True)
        else:
            self.input_handler.click(*lt, *rb, button="right", raw=True)
        self.input_handler.rnd_sleep(min=200, mean=300)

    def _tree_pos_to_xy(self, pos, offset=False):
        if offset:
            return [
                pos[0] * X_SCALE * self.px_multiplier,
                pos[1] * Y_SCALE * self.px_multiplier,
            ]
        uncentered_xy = [
            (pos[0] - self.ingame_pos[0]) * X_SCALE * self.px_multiplier,
            (pos[1] - self.ingame_pos[1]) * Y_SCALE * self.px_multiplier,
        ]
        xy = [
            int(uncentered_xy[0] + self.origin_pos[0]),
            int(uncentered_xy[1] + self.origin_pos[1]),
        ]
        return xy

    def _add_xy_offset_to_tree_pos(self, offset):
        tree_pos = [
            self.ingame_pos[0] + offset[0] / (X_SCALE * self.px_multiplier),
            self.ingame_pos[1] + offset[1] / (Y_SCALE * self.px_multiplier),
        ]
        return tree_pos

    def _analyze_nodes(self, socket_id):
        self.log.info("Analyzing nodes for socket id %s" % socket_id)
        nodes = []
        node_locations, socket_pos = self._find_nodes(socket_id)
        self.log.debug(
            "Found %s nodes for socket id %s" % (len(node_locations), socket_id)
        )
        self._click_socket(socket_pos)
        for location in node_locations:
            if not self._run():
                return
            node_stats = self._get_node_data(location)
            node = {
                "location": self._socket_offset_pos(socket_pos, location),
                "stats": node_stats,
            }
            nodes.append(node)
        self._click_socket(socket_pos, insert=False)
        return nodes

    def _socket_offset_pos(self, socket_pos, node_location):
        circle_radius = CIRCLE_EFFECTIVE_RADIUS * self.px_multiplier
        return [
            (node_location[0] - socket_pos[0]) / circle_radius,
            (node_location[1] - socket_pos[1]) / circle_radius,
        ]

    def _filter_ocr_lines(self, nodes_lines, max_dist=4):
        filtered_nodes = []
        for node in nodes_lines:
            names = []
            mods = []
            for line in node["stats"]:
                filtered_line = self._filter_nonalpha(line)
                if len(filtered_line) < 4 or filtered_line == "Unallocated":
                    continue
                if filtered_line in self.passive_names:
                    names.append(self.passive_names[filtered_line])
                elif filtered_line in self.passive_mods:
                    filtered_mod, value = filter_mod(line, regex=self.nonalpha_re)
                    new_mod = re.sub(
                        self.find_mod_value_re,
                        str(value),
                        self.passive_mods[filtered_line],
                        count=1,
                    )
                    mods.append(new_mod)
                else:
                    # Sometimes the OCR might return strange results. If so,
                    # as a last resort, check levenshtein distance to closest
                    # node. This shouldn't happen often.
                    best_distance = 99999999999
                    best_match = None
                    for possible_mod in self.passive_nodes:
                        d = distance(filtered_line, possible_mod)
                        if d < best_distance:
                            best_distance = d
                            best_match = possible_mod
                    if best_distance > max_dist:
                        continue
                    if best_match in self.passive_names:
                        names.append(self.passive_names[best_match])
                    elif best_match in self.passive_mods:
                        filtered_mod, value = filter_mod(line, regex=self.nonalpha_re)
                        new_mod = re.sub(
                            self.find_mod_value_re,
                            str(value),
                            self.passive_mods[best_match],
                            count=1,
                        )
                        mods.append(new_mod)

            if mods:
                filtered_nodes.append(
                    {"location": node["location"], "name": names, "mods": mods}
                )

        return filtered_nodes

    def _find_nodes(self, socket_id):
        self.input_handler.click(0.5, 0.07, 0.51, 0.083, button=None)
        socket_pos = self._tree_pos_to_xy(SOCKETS[socket_id])
        socket_offset = self._find_socket(socket_pos)
        if socket_offset is None:
            found_socket = False
            socket_offset = [0, 0]
        else:
            found_socket = True
            self.log.debug("Jewel socket offset correction: %s" % socket_offset)

        socket_pos[0] += socket_offset[0]
        socket_pos[1] += socket_offset[1]

        # Add some margin so that we dont accidentally cut any nodes off
        margin = 20 * self.px_multiplier

        x1 = int(socket_pos[0] - CIRCLE_EFFECTIVE_RADIUS * self.px_multiplier - margin)
        y1 = int(socket_pos[1] - CIRCLE_EFFECTIVE_RADIUS * self.px_multiplier - margin)
        x2 = int(x1 + 2 * CIRCLE_EFFECTIVE_RADIUS * self.px_multiplier + 2 * margin)
        y2 = int(y1 + 2 * CIRCLE_EFFECTIVE_RADIUS * self.px_multiplier + 2 * margin)

        nodes = self._get_node_locations_from_screen((x1, y1, x2, y2))
        nodes = self._filter_nodes(nodes, socket_pos)
        return nodes, socket_pos

    def _find_socket(self, socket_pos, side_len=100):
        lt = [int(socket_pos[0] - side_len / 2), int(socket_pos[1] - side_len / 2)]
        rb = [lt[0] + side_len, lt[1] + side_len]
        socket_area = grab_screen(tuple(lt + rb))
        socket_area = cv2.cvtColor(socket_area, cv2.COLOR_BGR2GRAY)

        locations = np.zeros((side_len, side_len))

        for template_name in [
            "Jewel.png",
            "JewelSocketed.png",
            "LargeJewel.png",
            "LargeJewelSocketed.png",
        ]:
            centered_coordinates = self._match_image(socket_area, template_name)
            locations[tuple(centered_coordinates)] = 1

        rel_node_pos_yx = np.argwhere(locations == 1)
        rel_node_pos = rel_node_pos_yx.T[::-1].T
        if len(rel_node_pos) == 0:
            self.log.warning("Could not find any jewel socket for compensating offset!")
            return None
        socket_offset = [
            int(rel_node_pos[0][0] - side_len / 2),
            int(rel_node_pos[0][1] - side_len / 2),
        ]
        return socket_offset

    def _filter_nodes(self, nodes, socket_pos, duplicate_min_dist=10):
        # filter duplicate nodes
        kept_node_indices = [len(nodes) - 1]
        z = np.array([[complex(c[0], c[1]) for c in nodes]])
        dist_matrix = abs(z.T - z)
        for node_idx in range(len(nodes) - 1):
            if np.min(dist_matrix[node_idx + 1 :, node_idx]) >= duplicate_min_dist:
                kept_node_indices.append(node_idx)
        nodes = np.array(nodes)
        nodes = nodes[kept_node_indices, :]

        # filter nodes outside jewel socket radius
        distances_to_socket = np.sqrt(np.sum((nodes - socket_pos) ** 2, axis=1))
        nodes = nodes[
            distances_to_socket <= CIRCLE_EFFECTIVE_RADIUS * self.px_multiplier
        ]
        return nodes

    def _get_node_locations_from_screen(self, box):
        jewel_area_bgr = grab_screen(box)
        jewel_area_gray = cv2.cvtColor(jewel_area_bgr, cv2.COLOR_BGR2GRAY)

        locations = np.zeros((box[2] - box[0], box[3] - box[1]))

        for template_name in [
            "Notable.png",
            "NotableAllocated.png",
            "Skill.png",
            "SkillAllocated.png",
        ]:
            centered_coordinates = self._match_image(jewel_area_gray, template_name)
            locations[tuple(centered_coordinates)] = 1

        rel_node_pos_yx = np.argwhere(locations == 1)
        rel_node_pos = rel_node_pos_yx.T[::-1].T
        abs_node_pos = rel_node_pos + [box[0], box[1]]
        return abs_node_pos

    def _match_image(self, screen, template_name):
        template = self.templates_and_masks[template_name]["image"]
        mask = self.templates_and_masks[template_name]["mask"]
        res = cv2.matchTemplate(screen, template, cv2.TM_CCORR_NORMED, mask=mask)
        coordinates = np.where(
            res >= TEMPLATES[template_name][self.resolution_prefix + "threshold"]
        )
        icon_size = (
            int(TEMPLATES[template_name][self.resolution_prefix + "size"][0]),
            int(TEMPLATES[template_name][self.resolution_prefix + "size"][1]),
        )
        icon_center_offset = [int(icon_size[0] / 2), int(icon_size[1] / 2)]
        centered_coordinates = [
            coordinates[0] + icon_center_offset[0],
            coordinates[1] + icon_center_offset[1],
        ]

        return centered_coordinates

    def _get_node_data(self, location):
        self.log.debug("Getting node stats at location %s" % location)
        lt = [
            location[0] - 7 * self.px_multiplier,
            location[1] - 7 * self.px_multiplier,
        ]
        rb = [
            location[0] + 7 * self.px_multiplier,
            location[1] + 7 * self.px_multiplier,
        ]
        self.input_handler.click(
            *lt,
            *rb,
            button=None,
            raw=True,
            speed_factor=self.config["node_search_speed_factor"]
        )
        textbox_lt = location + [TXT_BOX["x"], TXT_BOX["y"]]
        textbox_rb = textbox_lt + [
            int(TXT_BOX["w"] * self.px_multiplier),
            int(TXT_BOX["h"] * self.px_multiplier),
        ]

        jewel_area_bgr = grab_screen(tuple(np.concatenate([textbox_lt, textbox_rb])))
        return jewel_area_bgr

    def _setup(self, item_location, copy=False):
        item_desc = None
        item_name = None
        self.input_handler.click_hotkey("p")
        self.input_handler.rnd_sleep(min=150, mean=200, sigma=100)
        self.input_handler.click_hotkey("i")
        if copy:
            self.input_handler.rnd_sleep(min=150, mean=200, sigma=100)
            item = self.input_handler.inventory_copy(
                *item_location, OWN_INVENTORY_ORIGIN, speed_factor=2
            )
            item_desc = item.split("\n")[9].strip()
            item_name = item.split("\n")[1].strip()
        self.input_handler.rnd_sleep(min=150, mean=200, sigma=100)
        self.input_handler.inventory_click(*item_location, OWN_INVENTORY_ORIGIN)
        self.input_handler.rnd_sleep(min=150, mean=200, sigma=100)
        self.input_handler.click_hotkey("i")
        self.input_handler.rnd_sleep(min=150, mean=200, sigma=100)
        return item_name, item_desc

    def generate_good_strings(self, files):
        mods = {}
        names = {}
        for name in files:
            path = files[name]
            with open(path) as f:
                data = json.load(f)
                if isinstance(data, dict):
                    names.update({self._filter_nonalpha(k): k for k in data.keys()})
                    for key in data.keys():
                        if isinstance(data[key]["passives"][0], list):
                            mods.update(
                                {
                                    self._filter_nonalpha(e): e
                                    for e in data[key]["passives"][0]
                                }
                            )
                        else:
                            mods.update(
                                {
                                    self._filter_nonalpha(e): e
                                    for e in data[key]["passives"]
                                }
                            )
                else:
                    mods.update({self._filter_nonalpha(e): e for e in data})
        mods.pop("", None)
        return mods, names

    def _filter_nonalpha(self, value):
        return re.sub(self.nonalpha_re, "", value)

# Adapted from https://github.com/klayveR/python-poe-timeless-jewel
class OCR:
    @staticmethod
    def clahe(img, clip_limit=2.0, grid_size=(8, 8)):
        clahe = cv2.createCLAHE(clipLimit=clip_limit, tileGridSize=grid_size)
        return clahe.apply(img)

    @staticmethod
    def getFilteredImage(src):
        srcH, srcW = src.shape[:2]
        src = cv2.resize(src, (int(srcW * 2), int(srcH * 2)))

        # HSV thresholding to get rid of as much background as possible
        src = cv2.cvtColor(src, cv2.COLOR_BGRA2BGR)
        hsv = cv2.cvtColor(src.copy(), cv2.COLOR_BGR2HSV)
        # Define 2 masks and combine them
        # mask1 for blue affix text
        # mask2 for yellow passive node name
        lower_blue = np.array([80, 10, 40])
        upper_blue = np.array([130, 180, 255])
        lower_yellow = np.array([10, 10, 190])
        upper_yellow = np.array([30, 200, 255])
        mask1 = cv2.inRange(hsv, lower_blue, upper_blue)
        mask2 = cv2.inRange(hsv, lower_yellow, upper_yellow)
        mask = cv2.bitwise_or(mask1, mask2)
        result = cv2.bitwise_and(src, src, mask=mask)
        b, g, r = cv2.split(result)
        b = OCR.clahe(b, 5, (5, 5))
        inverse = cv2.bitwise_not(b)
        return inverse

    @staticmethod
    def imageToStringArray(img):
        t = pytesseract.image_to_string(img, lang="eng", config="--oem 3 --psm 12 poe")
        t = t.replace("\n\n", "\n")
        lines = t.split("\n")
        return lines

    @staticmethod
    def node_to_strings(node):
        img = node["stats"]
        filt_img = OCR.getFilteredImage(img)
        text = OCR.imageToStringArray(filt_img)
        return {"location": node["location"], "stats": text}


from unittest import TestCase

import pytest

from boa import BOAConfig

def test_wpr_params_to_boa(denormed_param_parse_config):
    # parameter_keys = denormed_param_parse_config.parameter_keys
    config = denormed_param_parse_config
    # How the parameter names should
    names = {
        "params_a_x2",
        "params_a_x1",
        "params_b_x1",
        "params_b_x2",
        "params_a_x1_0",
        "params_a_x2_0",
        "params2_0_a_x1",
        "params2_0_a_x2",
        "params2_1_b_x1",
        "params2_1_b_x2",
    }
    for key in config.parameters:
        assert key["name"] in names
    assert len(config.parameters) == len(names)

def test_boa_params_to_wpr(denormed_param_parse_config):
    config = denormed_param_parse_config
    # parameter_keys = denormed_param_parse_config.parameter_keys
    orig_params = config.boa_params_to_wpr(config.parameters, config.mapping, from_trial=False)

    d = {k: v for k, v in config.orig_config.items() if k in [keys[0] for keys in config.parameter_keys]}
    TestCase().assertDictEqual(d, orig_params)

def test_config_run_cmd_sets_to_run_model():
    config = {
        "objective": {"metrics": [{"name": "a"}]},
        "parameters": {"x": 10},
        "n_trials": 1,
        "script_options": {"run_cmd": "some command"},
    }
    c = BOAConfig(**config)
    assert c.script_options.run_model == "some command"

def test_config_run_cmd_and_run_model_error():
    config = {
        "objective": {"metrics": [{"name": "a"}]},
        "parameters": {"x": 10},
        "n_trials": 1,
        "script_options": {
            "run_cmd": "some command",
            "run_model": "some command",
        },
    }
    with pytest.raises(TypeError):
        BOAConfig(**config)

def test_config_weights_can_be_set_on_obj_passed_to_metrics():
    weights = [1, 2]
    config = {
        "objective": {"metrics": [{"name": "a"}, {"name": "b"}], "weights": weights},
        "parameters": {"x": 10},
        "n_trials": 1,
        "script_options": {"run_cmd": "some command"},
    }
    c = BOAConfig(**config)
    for metric, weight in zip(c.objective.metrics, weights):
        assert metric.weight == weight

def test_config_obj_weights_and_metric_weights_error():
    weights = [1, 2]
    config = {
        "objective": {"metrics": [{"name": "a", "weight": 1}, {"name": "b", "weight": 2}], "weights": weights},
        "parameters": {"x": 10},
        "n_trials": 1,
        "script_options": {"run_cmd": "some command"},
    }
    with pytest.raises(TypeError):
        BOAConfig(**config)


import matplotlib.pyplot as plt
import numpy as np
import numpy.typing as npt
import seaborn as sns

import pyssht as ssht

import sleplet

sns.set(context="paper")

L = 16
SAMPLING_SCHEME = "MWSS"
THETA_MAX = 40

def main() -> None:
    """Create a plot of Slepian coefficients against rank."""
    region = sleplet.slepian.Region(theta_max=np.deg2rad(THETA_MAX))
    earth = sleplet.functions.Earth(L, region=region)
    field = ssht.inverse(earth.coefficients, L, Method=SAMPLING_SCHEME)
    integrate_region = _helper_region(L, region, field, earth.coefficients)
    integrate_sphere = _helper_sphere(L, region, field, earth.coefficients)
    N = sleplet.slepian.SlepianPolarCap(L, np.deg2rad(THETA_MAX)).N
    ax = plt.gca()
    sns.scatterplot(
        x=range(N),
        y=integrate_region,
        ax=ax,
        label="region",
        linewidth=0,
        marker=".",
    )
    sns.scatterplot(
        x=range(N),
        y=integrate_sphere,
        ax=ax,
        label="sphere",
        linewidth=0,
        marker="*",
    )
    ax.set_xlabel("coefficients")
    ax.set_ylabel("relative error")
    ax.set_yscale("log")
    print(f"Opening: fp_error_earth_polar{THETA_MAX}_L{L}")
    plt.tight_layout()
    plt.show(block=False)
    plt.pause(10)
    plt.close()

def _helper_sphere(
    L: int,
    region: sleplet.slepian.Region,
    f: npt.NDArray[np.complex_],
    flm: npt.NDArray[np.complex_ | np.float_],
) -> npt.NDArray[np.float_]:
    """Calculate the difference in Slepian coefficients by integration of the sphere."""
    slepian = sleplet.slepian_methods.choose_slepian_method(L, region)
    output = np.abs(sleplet.slepian_methods.slepian_forward(L, slepian, f=f))
    desired = np.abs(sleplet.slepian_methods.slepian_forward(L, slepian, flm=flm))
    return np.abs(output - desired) / desired

def _helper_region(
    L: int,
    region: sleplet.slepian.Region,
    f: npt.NDArray[np.complex_],
    flm: npt.NDArray[np.complex_ | np.float_],
) -> npt.NDArray[np.float_]:
    """Calculate the difference in Slepian coefficients by integration of the region."""
    slepian = sleplet.slepian_methods.choose_slepian_method(L, region)
    output = np.abs(
        sleplet.slepian_methods.slepian_forward(L, slepian, f=f, mask=slepian.mask),
    )
    desired = np.abs(sleplet.slepian_methods.slepian_forward(L, slepian, flm=flm))
    return np.abs(output - desired) / desired

if __name__ == "__main__":
    main()


# annotate dataset
import os
import argparse

def create_annotxt(insp_dir):
    ref_dict = {
        "Kicking": 1,
        "Pushing": 2,
        "Handshake": 3,
        "Hi-Five": 4,
        "Hugging": 5,
                }
    anno_test = []
    anno_train = []
    anno_validate = []
    for root, dir, files in os.walk(r"{}".format(insp_dir)):
        if 'test' in root and 'kpts' in root:
            for key in ref_dict:
                if key in root:
                    cat = ref_dict[key]
                    break
            test_row = '{} 1 {} {}\n'.format(root.replace(insp_dir,'')[1:], len(files), cat)
            anno_test.append(test_row)
        elif 'train' in root and 'kpts' in root:
            for key in ref_dict:
                if key in root:
                    cat = ref_dict[key]
                    break
            train_row = '{} 1 {} {}\n'.format(root.replace(insp_dir, '')[1:], len(files), cat)
            anno_train.append(train_row)
        elif 'validate' in root and 'kpts' in root:
            for key in ref_dict:
                if key in root:
                    cat = ref_dict[key]
                    break
            train_row = '{} 1 {} {}\n'.format(root.replace(insp_dir, '')[1:], len(files), cat)
            anno_validate.append(train_row)
    with open('annotation_test.txt','a') as test_file:
        test_file.writelines(anno_test)
    with open('annotation_train.txt', 'a') as test_file:
        test_file.writelines(anno_train)
    with open('annotation_validate.txt', 'a') as test_file:
        test_file.writelines(anno_validate)

if __name__ == "__main__":
    praser = argparse.ArgumentParser()
    praser.add_argument('--viddir', type=str, required=True, help='directory to inspect')
    args = praser.parse_args()

    create_annotxt(args.viddir)


from django.shortcuts import render, get_object_or_404
from rest_framework.decorators import api_view
from rest_framework.response import Response   # api에 대한 응답을 하도록
from .models import Music, Artist, Comment
from .serializers import MusicSerializer, ArtistSerializer, CommentSerializer, ArtistDetailSerializer

# 음악 여러개 가지고 오는 것
@api_view(['GET'])  # 요청 들어온게 어떤 메서드로 처리될 것인지 설정
def music_list(request):
    # 127.0.0.1:8000 : domain
    # /api/v1/musics/ : endpoint <- urls에 우리가 작성하는 것
    # ?artist_pk=1/ : query parameter <- 더 많은 정보 받게 우리가 지정
    params = {}
    artist_pk = request.GET.get('artist_pk')

    if artist_pk is not None:
        params['artist_id'] = artist_pk

    # 내가 보여주고 싶은 data를 db에서 꺼낸다.
    musics = Music.objects.filter(**params)  # 전체를 가지고 오겠다.
    # 여기서 musics는 파이썬만 알고 있는 쿼리셋임. 다른 언어에서는 해석할 수 없음
    # 키, 밸류로 구성되는 스트링으로 넘겨야 "serializing"(django-rest framework사용): 바이트 형식
    #   -> json으로 만들려면 serializers.py 생성
    # json file -> python file 은 deserialize임

    # 꺼냈으면, 다른 어디에서든 꺼낼 수 있게 함
    serializer = MusicSerializer(musics, many=True)  # if 하나면 -> MusicSerializer(music)

    # json형태로 응답
    return Response(serializer.data)

# 음악 하나 가지고 오는 것
@api_view(['GET', 'PUT', 'DELETE'])
def music_detail_update_delete(request, music_pk):
    music = get_object_or_404(Music, pk=music_pk)
    if request.method == 'GET':
        serializer = MusicSerializer(music)  # 한개의 music이므로 many=True 넣지 않음
        return Response(serializer.data)
    elif request.method == 'PUT':
        serializer = MusicSerializer(data=request.data, instance=music)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)
    # if request.method == 'DELETE':
    else:
        music.delete()
        return Response({'message': 'Music has been deleted!'})

@api_view(['GET'])
def artist_list(request):
    artists = Artist.objects.all()
    serializer = ArtistSerializer(artists, many=True)
    return Response(serializer.data)

@api_view(['GET'])
def artist_detail(request, artist_pk):
    artist = get_object_or_404(Artist, pk=artist_pk)
    serializer = ArtistDetailSerializer(artist)
    return Response(serializer.data)

@api_view(['GET'])
def comment_list(request):
    comments = Comment.objects.all()
    serializer = CommentSerializer(comments, many=True)
    return Response(serializer.data)

@api_view(['POST'])
def comments_create(request, music_pk):
    serializer = CommentSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):  # 검증에 실패하면 400 Bad request 오류를 발생
                            # raise_exception: valid하지 않으면 예외처리하겠닫는 의미
        serializer.save(music_id=music_pk)  # music_id입력하지 않아도 알아서 저장됨.
    return Response(serializer.data)

@api_view(['PUT', 'DELETE'])
def comments_update_and_delete(request, comment_pk):
    comment = get_object_or_404(Comment, pk=comment_pk)
    if request.method == 'PUT':
        serializer = CommentSerializer(data=request.data, instance=comment)
        if serializer.is_valid(raise_exception=True):
            serializer.save()
            return Response(serializer.data)
    # if request.method == 'DELETE':
    else:
        comment.delete()
        return Response({'message': 'Comment has been deleted!'})


# -*- encoding:utf-8 -*-
import os
import torch
import codecs
import random
import pickle
import re
from tqdm import tqdm
from multiprocessing import Pool
from uer.utils.constants import *
from uer.utils.misc import count_lines
from uer.utils.seed import set_seed

# import pkuseg
# pku_seg = pkuseg.pkuseg(model_name="medicine",user_dict="../uer/utils/pku_seg_dict.txt")
# pku_seg_pos = pkuseg.pkuseg(model_name="medicine",user_dict="../uer/utils/pku_seg_dict.txt",postag=True)

# pos_dict = {}
# with open('../uer/utils/pos_tags_old.txt','r',encoding='utf-8') as f:
#     i = 0
#     for line in f.readlines():
#         if line:
#             pos_dict[line.strip().split()[0]] = i
#             i += 1

# # 获取本地术语表
# a = []
# term_dict = {}
# with open('../uer/utils/medical_terms/medical_terms(final).txt', 'r', encoding='utf-8') as f:
#     for line in f.readlines():
#         line = line.strip()
#         a.append(line)
#         term_dict[line.lower()] = 1

# term_set = set(a)

# max_num = max([len(line) for line in a])

def mask_seq(src, vocab_size):
    """
    mask input sequence for MLM task
    args:
        src: a list of tokens
        vocab_size: the vocabulary size
    """
    tgt_mlm = []
    for (i, token) in enumerate(src):
        if token == CLS_ID or token == SEP_ID:
            continue
        prob = random.random()
        if prob < 0.15:
            prob /= 0.15
            if prob < 0.8:
                src[i] = MASK_ID
            elif prob < 0.9:
                while True:
                    rdi = random.randint(1, vocab_size-1)
                    if rdi not in [CLS_ID, SEP_ID, MASK_ID]:
                        break
                src[i] = rdi
            tgt_mlm.append((i, token))
    return src, tgt_mlm

def merge_dataset(dataset_path, workers_num):
        # Merge datasets.
        f_writer = open(dataset_path, "wb")
        for i in range(workers_num):
            tmp_dataset_reader = open("dataset-tmp-"+str(i)+".pt", "rb")
            while True:
                tmp_data = tmp_dataset_reader.read(2^20)
                if tmp_data:
                    f_writer.write(tmp_data)
                else:
                    break
            tmp_dataset_reader.close()
            os.remove("dataset-tmp-"+str(i)+".pt")
        f_writer.close()

def seg_char(sent):
    """
    把句子按字分开，不破坏英文及数字结构
    """
    # 按中文汉字分割
    pattern = re.compile(r'([\u4e00-\u9fa5])')
    parts = pattern.split(sent)
    parts = [w for w in parts if len(w.strip())>0]

    # 按英文标点符号分割
    chars_list = []
    pattern = re.compile(r'([,?:;\'"!()<>，。；’‘、！])')
    for part in parts:
        chars = pattern.split(part)
        chars = [w for w in chars if len(w.strip())>0]
        chars_list += chars

    return chars_list

def max_match(txt, ano_dict, max_num):
    word_list = seg_char(txt) # 中文单字切割，保留英文和数字
    # print(word_list)
    new_word_list = []
    term_labels = []
    N = len(word_list)
    k = max_num
    i = 0
    while i < N:
        if i <= N - k:
            j = k
            while j > 0:
                token_tmp = ''.join(word_list[i:i + j])
                # print(token_tmp)
                if token_tmp.lower() in ano_dict.keys():
                    # print(token_tmp,'！!！!!!！!!!！!！!！!')
                    new_word_list.append(token_tmp)
                    term_labels.append(1)
                    i += j
                    break
                else:
                    j -= 1
            if j == 0:
                new_word_list.append(word_list[i])
                term_labels.append(0)
                i += 1
        else:
            j = N - i
            while j > 0:
                token_tmp = ''.join(word_list[i:i + j])
                # print(token_tmp)
                if token_tmp.lower() in ano_dict.keys():
                    # print(token_tmp, '！!！!!!！!!!！!！!！!')
                    new_word_list.append(token_tmp)
                    term_labels.append(1)
                    i += j
                    break
                else:
                    j -= 1
            if j == 0:
                new_word_list.append(word_list[i])
                term_labels.append(0)
                i += 1
    return new_word_list,term_labels

# ## 最大匹配分词
# txt = '我爱北京天安门，天安门上太阳升'
#
# ano_dict={'天安门':1,'北京':1,'太阳':1}
#
# max_num = 2
#
# print(max_match(txt,ano_dict,max_num))
# exit()

class Dataset(object):
    def __init__(self, args, vocab, tokenizer):
        self.vocab = vocab
        self.tokenizer = tokenizer
        self.corpus_path = args.corpus_path
        self.dataset_path = args.dataset_path
        self.seq_length = args.seq_length
        self.seed = args.seed
        self.stats_tokens = args.stats_tokens
        self.stats_instances = args.stats_instances
        self.stats_lines = args.stats_lines

    def build_and_save(self, workers_num):
        """
        Build dataset from the given corpus.
        Start workers_num processes and each process deals with a part of data.
        """

        if self.stats_instances:
            print('stats_instances...')
            ins_count = 0
            with open(self.dataset_path,'rb') as f:
                i = 0
                while True:
                    try:
                        instance = pickle.load(f)
                        ins_count += len(instance)
                    except EOFError:
                        break
                    i += 1
            print("instances: ", ins_count)
            exit()

        if self.stats_tokens:
            print('stats_tokens...')
            tokens_count = 0
            with open(self.corpus_path, mode="r", encoding="utf-8") as f:
                for line in tqdm(f.readlines()):
                    line = line.strip()
                    if line:
                        tokens_count += len(self.tokenizer.tokenize(line))
            print("tokens: ",tokens_count)
            exit()

        if self.stats_lines:
            lines_count = 0
            with open(self.corpus_path, mode="r", encoding="utf-8") as f:
                for line in tqdm(f.readlines()):
                    line = line.strip()
                    if line:
                        lines_count += 1
            print("lines: ",lines_count)
            exit()

        lines_num = count_lines(self.corpus_path)
        print("Total %d lines in courpus ... " % lines_num)
        print("Starting %d workers for building datasets ... " % workers_num)
        assert(workers_num >= 1)
        if workers_num == 1:
            self.worker(0, 0, lines_num)
        else:
            pool = Pool(workers_num)
            for i in range(workers_num):
                start = i * lines_num // workers_num
                end = (i+1) * lines_num // workers_num
                pool.apply_async(func=self.worker, args=[i, start, end])
            pool.close()
            pool.join()

        # Merge datasets.
        merge_dataset(self.dataset_path, workers_num)

    def worker(self, proc_id, start, end):
        raise NotImplementedError()

class DataLoader(object):
    def __init__(self, args, dataset_path, batch_size, proc_id, proc_num, shuffle=False):
        self.batch_size = batch_size
        self.instances_buffer_size = args.instances_buffer_size
        self.proc_id = proc_id
        self.proc_num = proc_num
        self.shuffle = shuffle
        self.f_read = open(dataset_path, "rb")
        self.read_count = 0
        self.start = 0
        self.end = 0
        self.buffer = []
        self.add_pos = args.add_pos

    def _fill_buf(self):
        try:
            self.buffer = []
            while True:
                instance = pickle.load(self.f_read)
                self.read_count += 1
                if (self.read_count - 1) % self.proc_num == self.proc_id:
                    self.buffer.append(instance)
                    if len(self.buffer) >= self.instances_buffer_size:
                        break
        except EOFError:
            # Reach file end.
            self.f_read.seek(0)

        if self.shuffle:
            random.shuffle(self.buffer)
        self.start = 0
        self.end = len(self.buffer)

    def _empty(self):
        return self.start >= self.end

    def __del__(self):
        self.f_read.close()

class BertDataset(Dataset):
    """
    Construct dataset for MLM and NSP tasks from the given corpus.
    Each document consists of multiple sentences,
    and each sentence occupies a single line.
    Documents in corpus must be separated by empty lines.
    """
    def __init__(self, args, vocab, tokenizer):
        super(BertDataset, self).__init__(args, vocab, tokenizer)
        self.docs_buffer_size = args.docs_buffer_size
        self.dup_factor = args.dup_factor
        self.short_seq_prob = args.short_seq_prob

    def worker(self, proc_id, start, end):
        print("Worker %d is building dataset ... " % proc_id)
        set_seed(self.seed)
        docs_buffer = []
        document = []
        pos = 0
        f_write = open("dataset-tmp-" + str(proc_id) + ".pt", "wb")
        with open(self.corpus_path, mode="r", encoding="utf-8") as f:
            while pos < start:
                try:
                    f.readline()
                except:
                    continue
                finally:
                    pos += 1
            while True:
                try:
                    line = f.readline()
                except:
                    continue
                finally:
                    pos += 1
                if not line.strip():
                    if len(document) >= 1:
                        docs_buffer.append(document)
                    document = []
                    if len(docs_buffer) == self.docs_buffer_size:
                        # Build instances from documents.
                        instances = self.build_instances(docs_buffer)
                        # Save instances.
                        for instance in instances:
                            pickle.dump(instance, f_write)
                        # Clear buffer.
                        docs_buffer = []
                        instances = []
                    continue
                sentence = [self.vocab.get(w) for w in self.tokenizer.tokenize(line)]
                if len(sentence) > 0:
                    document.append(sentence)

                if pos >= end - 1:
                    if len(docs_buffer) > 0:
                        instances = self.build_instances(docs_buffer)
                        for instance in instances:
                            pickle.dump(instance, f_write)
                    break
        f_write.close()

    def build_instances(self, all_documents):
        instances = []
        for _ in range(self.dup_factor):
            for doc_index in range(len(all_documents)):
                instances.extend(self.create_ins_from_doc(all_documents, doc_index))
        return instances

    def create_ins_from_doc(self, all_documents, document_index):
        document = all_documents[document_index]
        max_num_tokens = self.seq_length - 3
        target_seq_length = max_num_tokens
        if random.random() < self.short_seq_prob:
            target_seq_length = random.randint(2, max_num_tokens)
        instances = []
        current_chunk = []
        current_length = 0
        i = 0
        while i < len(document):
            segment = document[i]
            current_chunk.append(segment)
            current_length += len(segment)
            if i == len(document) - 1 or current_length >= target_seq_length:
                if current_chunk:
                    a_end = 1
                    if len(current_chunk) >= 2:
                        a_end = random.randint(1, len(current_chunk) - 1)

                    tokens_a = []
                    for j in range(a_end):
                        tokens_a.extend(current_chunk[j])

                    tokens_b = []
                    is_random_next = 0

                    # Random next
                    if len(current_chunk) == 1 or random.random() < 0.5:
                        is_random_next = 1
                        target_b_length = target_seq_length - len(tokens_a)

                        for _ in range(10):
                            random_document_index = random.randint(0, len(all_documents) - 1)
                            if random_document_index != document_index:
                                break

                        random_document = all_documents[random_document_index]
                        random_start = random.randint(0, len(random_document) - 1)
                        for j in range(random_start, len(random_document)):
                            tokens_b.extend(random_document[j])
                            if len(tokens_b) >= target_b_length:
                                break

                        num_unused_segments = len(current_chunk) - a_end
                        i -= num_unused_segments

                    # Actual next
                    else:
                        is_random_next = 0
                        for j in range(a_end, len(current_chunk)):
                            tokens_b.extend(current_chunk[j])

                    self.truncate_seq_pair(tokens_a, tokens_b, max_num_tokens)

                    # assert len(tokens_a) >= 1
                    # assert len(tokens_b) >= 1

                    src = []

                    src.append(CLS_ID)
                    for token in tokens_a:
                        src.append(token)

                    src.append(SEP_ID)

                    seg_pos = [len(src)]

                    for token in tokens_b:
                        src.append(token)

                    src.append(SEP_ID)

                    seg_pos.append(len(src))

                    src, tgt_mlm = mask_seq(src, len(self.vocab))

                    while len(src) != self.seq_length:
                        src.append(PAD_ID)

                    instance = (src, tgt_mlm, is_random_next, seg_pos)
                    instances.append(instance)
                current_chunk = []
                current_length = 0
            i += 1
        return instances

    def truncate_seq_pair(self, tokens_a, tokens_b, max_num_tokens):
        """ truncate sequence pair to specific length """
        while True:
            total_length = len(tokens_a) + len(tokens_b)
            if total_length <= max_num_tokens:
                break

            trunc_tokens = tokens_a if len(tokens_a) > len(tokens_b) else tokens_b
            # assert len(trunc_tokens) >= 1

            if random.random() < 0.5:
                del trunc_tokens[0]
            else:
                trunc_tokens.pop()

class BertDataLoader(DataLoader):
    def __iter__(self):
        while True:
            while self._empty():
                self._fill_buf()
            if self.start + self.batch_size >= self.end:
                instances = self.buffer[self.start:]
            else:
                instances = self.buffer[self.start: self.start + self.batch_size]

            self.start += self.batch_size

            src = []
            tgt_mlm = []
            is_next = []
            seg = []

            masked_words_num = 0
            for ins in instances:
                masked_words_num += len(ins[1])
            if masked_words_num == 0:
                continue

            for ins in instances:
                src.append(ins[0])
                tgt_mlm.append([0]*len(ins[0]))
                for mask in ins[1]:
                    tgt_mlm[-1][mask[0]] = mask[1]
                is_next.append(ins[2])
                seg.append([1]*ins[3][0] + [2]*(ins[3][1]-ins[3][0]) + [PAD_ID]*(len(ins[0])-ins[3][1]))

            yield torch.LongTensor(src), \
                torch.LongTensor(tgt_mlm), \
                torch.LongTensor(is_next), \
                torch.LongTensor(seg)

class LmDataset(Dataset):
    def worker(self, proc_id, start, end):
        print("Worker %d is building dataset ... " % proc_id)
        set_seed(self.seed)
        pos = 0
        f_write = open("dataset-tmp-" + str(proc_id) + ".pt", "wb")
        with open(self.corpus_path, mode="r", encoding="utf-8") as f:
            while pos < start:
                try:
                    f.readline()
                except:
                    continue
                finally:
                    pos += 1
            while True:
                try:
                    line = f.readline()
                except:
                    continue
                finally:
                    pos += 1

                src = [self.vocab.get(w) for w in self.tokenizer.tokenize(line)]
                tgt = src[1:]
                src = src[:-1]
                seg = [1] * len(src)
                if len(src) >= self.seq_length:
                    src = src[:self.seq_length]
                    tgt = tgt[:self.seq_length]
                    seg = seg[:self.seq_length]
                else:
                    while len(src) != self.seq_length:
                        src.append(PAD_ID)
                        tgt.append(PAD_ID)
                        seg.append(PAD_ID)

                pickle.dump((src, tgt, seg), f_write)

                if pos >= end - 1:
                    break

        f_write.close()

class LmDataLoader(DataLoader):
    def __iter__(self):
        while True:
            while self._empty():
                self._fill_buf()
            if self.start + self.batch_size >= self.end:
                instances = self.buffer[self.start:]
            else:
                instances = self.buffer[self.start: self.start + self.batch_size]

            self.start += self.batch_size

            src = []
            tgt = []
            seg = []

            for ins in instances:
                src.append(ins[0])
                tgt.append(ins[1])
                seg.append(ins[2])

            yield torch.LongTensor(src), \
                torch.LongTensor(tgt), \
                torch.LongTensor(seg)

class BilmDataset(Dataset):
    def worker(self, proc_id, start, end):
        print("Worker %d is building dataset ... " % proc_id)
        set_seed(self.seed)
        pos = 0
        f_write = open("dataset-tmp-" + str(proc_id) + ".pt", "wb")
        with open(self.corpus_path, mode="r", encoding="utf-8") as f:
            while pos < start:
                try:
                    f.readline()
                except:
                    continue
                finally:
                    pos += 1
            while True:
                try:
                    line = f.readline()
                except:
                    continue
                finally:
                    pos += 1

                src = [self.vocab.get(w) for w in self.tokenizer.tokenize(line)]
                if len(src) < 1:
                    continue
                tgt_forward = src[1:] + [SEP_ID]
                tgt_backward = [CLS_ID] + src[:-1]
                seg = [1] * len(src)
                if len(src) >= self.seq_length:
                    src = src[:self.seq_length]
                    tgt_forward = tgt_forward[:self.seq_length]
                    tgt_backward = tgt_backward[:self.seq_length]
                    seg = seg[:self.seq_length]
                else:
                    while len(src) != self.seq_length:
                        src.append(PAD_ID)
                        tgt_forward.append(PAD_ID)
                        tgt_backward.append(PAD_ID)
                        seg.append(PAD_ID)

                pickle.dump((src, tgt_forward, tgt_backward, seg), f_write)

                if pos >= end - 1:
                    break

        f_write.close()

class BilmDataLoader(DataLoader):
    def __iter__(self):
        while True:
            while self._empty():
                self._fill_buf()
            if self.start + self.batch_size >= self.end:
                instances = self.buffer[self.start:]
            else:
                instances = self.buffer[self.start: self.start + self.batch_size]

            self.start += self.batch_size

            src = []
            tgt_forward = []
            tgt_backward = []
            seg = []

            for ins in instances:
                src.append(ins[0])
                tgt_forward.append(ins[1])
                tgt_backward.append(ins[2])
                seg.append(ins[3])

            yield torch.LongTensor(src), \
                torch.LongTensor(tgt_forward), \
                torch.LongTensor(tgt_backward), \
                torch.LongTensor(seg)

class ClsDataset(Dataset):
    def worker(self, proc_id, start, end):
        print("Worker %d is building dataset ... " % proc_id)
        set_seed(self.seed)
        pos = 0
        f_write = open("dataset-tmp-" + str(proc_id) + ".pt", "wb")
        with open(self.corpus_path, mode="r", encoding="utf-8") as f:
            while pos < start:
                try:
                    f.readline()
                except:
                    continue
                finally:
                    pos += 1
            while True:
                try:
                    line = f.readline()
                except:
                    continue
                finally:
                    pos += 1

                line = line.strip().split('\t')
                if len(line) == 2:
                    label = int(line[0])
                    text = " ".join(line[1:])
                    src = [self.vocab.get(t) for t in self.tokenizer.tokenize(text)]
                    src = [CLS_ID] + src
                    tgt = label
                    seg = [1] * len(src)
                    if len(src) >= self.seq_length:
                        src = src[:self.seq_length]
                        seg = seg[:self.seq_length]
                    else:
                        while len(src) != self.seq_length:
                            src.append(PAD_ID)
                            seg.append(PAD_ID)
                    pickle.dump((src, tgt, seg), f_write)
                elif len(line) == 3: # For sentence pair input.
                    label = int(line[0])
                    text_a, text_b = line[1], line[2]

                    src_a = [self.vocab.get(t) for t in self.tokenizer.tokenize(text_a)]
                    src_a = [CLS_ID] + tokens_a + [SEP_ID]
                    src_b = [vocab.get(t) for t in tokenizer.tokenize(text_b)]
                    src_b = tokens_b + [SEP_ID]

                    src = src_a + src_b
                    seg = [1] * len(src_a) + [2] * len(src_b)

                    if len(src) >= self.seq_length:
                        src = src[:self.seq_length]
                        seg = seg[:self.seq_length]
                    else:
                        while len(src) != self.seq_length:
                            src.append(PAD_ID)
                            seg.append(PAD_ID)
                    pickle.dump((src, tgt, seg), f_write)
                else:
                    pass

                if pos >= end - 1:
                    break

        f_write.close()

class ClsDataLoader(DataLoader):
    def __iter__(self):
        while True:
            while self._empty():
                self._fill_buf()
            if self.start + self.batch_size >= self.end:
                instances = self.buffer[self.start:]
            else:
                instances = self.buffer[self.start: self.start + self.batch_size]

            self.start += self.batch_size

            src = []
            tgt = []
            seg = []

            for ins in instances:
                src.append(ins[0])
                tgt.append(ins[1])
                seg.append(ins[2])

            yield torch.LongTensor(src), \
                torch.LongTensor(tgt), \
                torch.LongTensor(seg)

class MlmDataset(Dataset):
    def __init__(self, args, vocab, tokenizer):
        super(MlmDataset, self).__init__(args, vocab, tokenizer)
        self.dup_factor = args.dup_factor

    def worker(self, proc_id, start, end):
        print("Worker %d is building dataset ... " % proc_id)
        set_seed(self.seed)
        f_write = open("dataset-tmp-" + str(proc_id) + ".pt", "wb")
        for _ in range(self.dup_factor):
            pos = 0
            with open(self.corpus_path, mode="r", encoding="utf-8") as f:
                while pos < start:
                    try:
                        f.readline()
                    except:
                        continue
                    finally:
                        pos += 1
                pbar = tqdm(total=end - start)
                pbar.set_description("Worker %d:" % proc_id)
                while True:
                    pbar.update(1)
                    try:
                        line = f.readline()
                    except:
                        continue
                    finally:
                        pos += 1

                    line = line.strip()
                    if not line:
                        continue

                    src = [self.vocab.get(w) for w in self.tokenizer.tokenize(line)]

                    if len(src) > self.seq_length:
                        src = src[:self.seq_length]
                    seg = [1] * len(src)

                    src, tgt = mask_seq(src, len(self.vocab))

                    while len(src) != self.seq_length:
                        src.append(PAD_ID)
                        seg.append(PAD_ID)

                    pickle.dump((src, tgt, seg), f_write)

                    if pos >= end - 1:
                        break
                pbar.close()
        f_write.close()

class MlmDataLoader(DataLoader):
    def __iter__(self):
        while True:
            while self._empty():
                self._fill_buf()
            if self.start + self.batch_size >= self.end:
                instances = self.buffer[self.start:]
            else:
                instances = self.buffer[self.start: self.start + self.batch_size]

            self.start += self.batch_size

            src = []
            tgt = []
            seg = []

            masked_words_num = 0
            for ins in instances:
                masked_words_num += len(ins[1])
            if masked_words_num == 0:
                continue

            for ins in instances:
                src.append(ins[0])
                seg.append(ins[2])
                tgt.append([0]*len(ins[0]))
                for mask in ins[1]:
                    tgt[-1][mask[0]] = mask[1]

            yield torch.LongTensor(src), \
                torch.LongTensor(tgt), \
                torch.LongTensor(seg)

class Csci_mlmDataset(Dataset):

    def __init__(self, args, vocab, tokenizer):
        super(Csci_mlmDataset, self).__init__(args, vocab, tokenizer)
        self.dup_factor = args.dup_factor

    def worker(self, proc_id, start, end):
        print("Worker %d is building dataset ... " % proc_id)
        set_seed(self.seed)
        f_write = open("dataset-tmp-" + str(proc_id) + ".pt", "wb")
        for _ in range(self.dup_factor):
            pos = 0
            with open(self.corpus_path, mode="r", encoding="utf-8") as f:
                while pos < start:
                    try:
                        f.readline()
                    except:
                        continue
                    finally:
                        pos += 1
                pbar = tqdm(total=end - start)
                pbar.set_description("Worker %d:" % proc_id)
                while True:
                    pbar.update(1)
                    try:
                        line = f.readline()
                    except:
                        continue
                    finally:
                        pos += 1

                    line = line.strip().lower()
                    if not line:
                        continue

                    tokens = []
                    for word in pku_seg.cut(line):
                        for w in self.tokenizer.tokenize(word):
                            tokens.append(w)

                    src_word = [self.vocab.get(w) for w in tokens]

                    # src_word = [self.vocab.get(w) for w in self.tokenizer.tokenize(line)]

                    if len(src_word) > self.seq_length:
                        src_word = src_word[:self.seq_length]
                    seg = [1] * len(src_word)

                    src_word, tgt = mask_seq(src_word, len(self.vocab))
                    # print('len(src_word)',len(src_word))

                    # tokens = [w for w in self.tokenizer.tokenize(line)]
                    # print([(i,a) for (i,a) in enumerate(tokens)])

                    src_pos = []
                    src_term = []
                    ## 加入pos 和terms
                    for (word, tag) in pku_seg_pos.cut(line):
                        piece_num = len(self.tokenizer.tokenize(word))
                        if word in term_set:
                            # print(word)
                            [src_term.append(1) for i in range(piece_num)]
                        else:
                            [src_term.append(0) for i in range(piece_num)]

                        [src_pos.append(pos_dict[tag]) for i in range(piece_num)]

                    if len(src_pos) > self.seq_length:
                        src_pos = src_pos[:self.seq_length]

                    # terms,labels = max_match(line.strip(),term_dict,max_num)
                    #
                    # for i,term in enumerate(terms):
                    #     if labels[i]:
                    #         for w in self.tokenizer.tokenize(term):
                    #             src_term.append(1)
                    #     else:
                    #         for w in self.tokenizer.tokenize(term):
                    #             src_term.append(0)

                    if len(src_term) > self.seq_length:
                        src_term = src_term[:self.seq_length]

                    # print('len(src_term)',len(src_term))
                    # print('src_term:',src_term)
                    # exit()

                    while len(src_word) != self.seq_length:
                        src_word.append(PAD_ID)
                        src_pos.append(pos_dict['[PAD]'])
                        src_term.append(2)
                        seg.append(PAD_ID)

                    if len(src_pos) != self.seq_length:
                        print('src_pos Problem~~~')
                        print(line)

                        tokens = [w for w in self.tokenizer.tokenize(line)]
                        print('tokens:\n', [(i, a) for (i, a) in enumerate(tokens)])

                        cut_to_tokens = []

                        for (word, tag) in pku_seg.cut(line.strip()):
                            # print(word,tag)
                            for w in self.tokenizer.tokenize(word):
                                cut_to_tokens.append(w)

                        print('cut to tokens:\n', [(i, a) for (i, a) in enumerate(cut_to_tokens)])

                        print('src_pos:\n', [(i, a) for (i, a) in enumerate(src_pos)])

                        exit()

                    if len(src_term) != self.seq_length:
                        print(line)

                        print('terms:\n',terms)
                        print('lables:\n',labels)

                        term_to_tokens = []
                        for i, term in enumerate(terms):
                            if labels[i]:
                                for w in self.tokenizer.tokenize(term):
                                    src_term.append(1)
                                    term_to_tokens.append(w)
                            else:
                                for w in self.tokenizer.tokenize(term):
                                    src_term.append(0)
                                    term_to_tokens.append(w)

                        print('term to tokens:\n',[(i,a) for (i,a) in enumerate(term_to_tokens)])

                        # tokens = [w for w in self.tokenizer.tokenize(line)]
                        print('tokens:\n',[(i,a) for (i,a) in enumerate(tokens)])
                        print('src_word:\n',[(i,a) for (i,a) in enumerate(src_word)])
                        print('src_pos:\n',[(i,a) for (i,a) in enumerate(src_pos)])
                        print('src_term:\n',[(i,a) for (i,a) in enumerate(src_term)])
                        print("tgt\n",[(i,a) for (i,a) in enumerate(tgt)])
                        print("seg\n",[(i,a) for (i,a) in enumerate(seg)])
                        exit()

                    # print(line)
                    # print('tokens:\n', [(i, a) for (i, a) in enumerate(tokens)])
                    # print('src_word:\n', [(i, a) for (i, a) in enumerate(src_word)])
                    # print('src_pos:\n', [(i, a) for (i, a) in enumerate(src_pos)])
                    # print('src_term:\n', [(i, a) for (i, a) in enumerate(src_term)])
                    # print("tgt\n", [(i, a) for (i, a) in enumerate(tgt)])
                    # print("seg\n", [(i, a) for (i, a) in enumerate(seg)])
                    # exit()

                    pickle.dump((src_word, src_pos, src_term, tgt, seg), f_write)

                    if pos >= end - 1:
                        break
                pbar.close()
        f_write.close()

class Csci_mlmDataLoader(DataLoader):
    def __iter__(self):
        while True:
            while self._empty():
                self._fill_buf()
            if self.start + self.batch_size >= self.end:
                instances = self.buffer[self.start:]
            else:
                instances = self.buffer[self.start: self.start + self.batch_size]

            self.start += self.batch_size

            src_word = []

            ## 加入pos和term
            src_pos = []
            src_term = []

            tgt = []
            seg = []

            masked_words_num = 0
            for ins in instances:
                masked_words_num += len(ins[3])
            if masked_words_num == 0:
                continue

            for ins in instances:
                src_word.append(ins[0])

                ## 加入pos和term
                src_pos.append(ins[1])
                src_term.append(ins[2])

                seg.append(ins[4])
                tgt.append([0]*len(ins[0]))
                for mask in ins[3]:
                    tgt[-1][mask[0]] = mask[1]

            yield torch.LongTensor(src_word), \
                torch.LongTensor(src_pos), \
                torch.LongTensor(src_term), \
                torch.LongTensor(tgt), \
                torch.LongTensor(seg)


import os
import openai
import re
import random
import pandas as pd

# https://beta.openai.com/examples

def get_resp_text(response: str) -> list:
    return [c['text'] for c in response["choices"]]

def chatbot_query(prompt):
    # marv chatbot settings
    return openai.Completion.create(
        model="text-davinci-003",
        prompt=prompt,
        temperature=0.5,
        max_tokens=60,
        top_p=0.3,
        frequency_penalty=0.5,
        presence_penalty=0.0
    )

def sassy_chatbot(question: str):
    prompt = "Marv is a chatbot that reluctantly answers questions with sarcastic responses:\n\nYou: How many pounds are in a kilogram?\nMarv: This again? There are 2.2 pounds in a kilogram. Please make a note of this.\nYou: What does HTML stand for?\nMarv: Was Google too busy? Hypertext Markup Language. The T is for try to ask better questions in the future.\nYou: When did the first airplane fly?\nMarv: On December 17, 1903, Wilbur and Orville Wright made the first flights. I wish theyâ€™d come and take me away.\nYou: What is the meaning of life?\nMarv: Iâ€™m not sure. Iâ€™ll ask my friend Google."
    prompt += "You: " + question + "\nMarv:"
    return get_resp_text(chatbot_query(prompt))

def color_pallet(description: str):
    response = openai.Completion.create(
        model="text-davinci-003",
        prompt=f"The best CSS color pallete for {description}:\n\nPrimary color: #",
        temperature=0,
        max_tokens=64,
        top_p=1.0,
        frequency_penalty=0.0,
        presence_penalty=0.0,
        stop=[";"]
    )
    str_reponse = '#'+get_resp_text(response)[0]
    find_hex=r"(#([A-Z0-9])+)"
    colors = [c for c,_ in re.findall(find_hex,str_reponse)]
    return colors

def discord_user(message:str, author:str, mimic_uname:str=None):
    data = pd.read_csv('pinbot_msg.csv')

    if mimic_uname is None:
        source_material = data
    else:
        source_material = data[data.name == mimic_uname]

    if len(source_material) == 0:
        return

    source_material = [f'{row["name"]}:{row["message"]}' for i, row in source_material.iterrows()]
    random.shuffle(source_material)
    usr_prepend = '\n'.join(source_material[:60])

    setting_prepend = ""#f"{mimic_uname} is a poetic fan of Shakespear who responds to statements with wit and grace."
    prompt = setting_prepend + usr_prepend + f'\n{author}: {message}\n'
    if mimic_uname: prompt += f'{mimic_uname}:'
    print(prompt)

    response = chatbot_query(prompt)
    if response is not None:
        str_reponse =  get_resp_text(response)[0].strip()
        return str_reponse

if __name__ == "__main__":
    import argparse
    args = argparse.ArgumentParser("GPT Chat bot")
    args.add_argument('-t', '--task', help="The task of the both")
    args = args.parse_args()
    openai.api_key = open("secret.txt").read()

    if args.task == "marv":
        user_in = str(input("Ask Marv a question: "))
        print(sassy_chatbot(user_in))
    elif args.task == "color":
        user_in = str(input("The CSS color pallete for "))
        print("\n".join(color_pallet(user_in))) # https://www.colorhexa.com/
    elif args.task == "discord":
        prompt = """brb banning marth mains from this discord
        James: ðŸ”¨ðŸ˜"""
        resp = discord_user(prompt,"James")
        print(resp)


import logging
import logging.handlers
import os
import sys
try:
    import unittest2 as unittest
except ImportError:
    import unittest

libdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'lib'))
sys.path.insert(0, libdir)

# Third party modules
# from fb_tools.common import pp

# Own modules
from general import FbPdnsToolsTestcase, get_arg_verbose, init_root_logger

LOG = logging.getLogger('test_fb_pdnstools')

# =============================================================================
class TestPdnsToolsBase(FbPdnsToolsTestcase):
    """Testcase for tests on fb_pdnstools/__init__.py and fb_pdnstools.errors."""

    # -------------------------------------------------------------------------
    def test_import_modules(self):
        """Testing import of modules fb_pdnstools/__init__.py and fb_pdnstools.errors ..."""
        LOG.info('Test importing main module ...')

        LOG.debug('Importing fb_pdnstools ...')
        import fb_pdnstools

        LOG.debug('Version of fb_pdnstools: {!r}.'.format(fb_pdnstools.__version__))

        LOG.debug('Importing fb_pdnstools.errors ...')
        import fb_pdnstools.errors
        LOG.debug('Version of fb_pdnstools.errors: {!r}.'.format(
            fb_pdnstools.errors.__version__))

    # -------------------------------------------------------------------------
    def test_error_classes(self):
        """Test instantiating of different exception classes in fb_pdnstools.errors."""
        LOG.info('Testing loading and function of different error handler classes ...')

        LOG.debug('Testing PowerDNSHandlerError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PowerDNSHandlerError
        with self.assertRaises(PowerDNSHandlerError) as cm:
            raise PowerDNSHandlerError('bla')
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PowerDNSZoneError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PowerDNSZoneError
        with self.assertRaises(PowerDNSZoneError) as cm:
            raise PowerDNSZoneError('bla')
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PowerDNSRecordError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PowerDNSRecordError
        with self.assertRaises(PowerDNSRecordError) as cm:
            raise PowerDNSRecordError('bla')
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PowerDNSRecordSetError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PowerDNSRecordSetError
        with self.assertRaises(PowerDNSRecordSetError) as cm:
            raise PowerDNSRecordSetError('bla')
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PowerDNSWrongSoaDataError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PowerDNSWrongSoaDataError
        with self.assertRaises(PowerDNSWrongSoaDataError) as cm:
            raise PowerDNSWrongSoaDataError({'uhu': 'banane'})
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        msg = 'Undfined test error'

        LOG.debug('Testing PDNSApiError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PDNSApiError
        uris = (None, 'https://pdnsmaster.uhu-banane.eu')
        code = 404
        for uri in uris:
            with self.assertRaises(PDNSApiError) as cm:
                raise PDNSApiError(code, msg, uri)
            e = cm.exception
            LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PDNSApiNotAuthorizedError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PDNSApiNotAuthorizedError
        with self.assertRaises(PDNSApiNotAuthorizedError) as cm:
            raise PDNSApiNotAuthorizedError(401, msg)
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PDNSApiNotFoundError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PDNSApiNotFoundError
        with self.assertRaises(PDNSApiNotFoundError) as cm:
            raise PDNSApiNotFoundError(404, msg)
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PDNSApiValidationError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PDNSApiValidationError
        with self.assertRaises(PDNSApiValidationError) as cm:
            raise PDNSApiValidationError(422, msg, 'https://pdnsmaster.uhu-banane.eu')
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PDNSApiRateLimitExceededError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PDNSApiRateLimitExceededError
        with self.assertRaises(PDNSApiRateLimitExceededError) as cm:
            raise PDNSApiRateLimitExceededError(429, msg)
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

        LOG.debug('Testing PDNSApiRequestError from fb_pdnstools.errors ...')
        from fb_pdnstools.errors import PDNSApiRequestError
        with self.assertRaises(PDNSApiRequestError) as cm:
            raise PDNSApiRequestError(419, 'I am not a tea pot.')
        e = cm.exception
        LOG.debug('Got a {c}: {e}'.format(c=e.__class__.__name__, e=e))

# =============================================================================
if __name__ == '__main__':

    verbose = get_arg_verbose()
    if verbose is None:
        verbose = 0
    init_root_logger(verbose)

    LOG.info('Starting tests ...')

    loader = unittest.TestLoader()
    suite = unittest.TestSuite()

    suite.addTest(TestPdnsToolsBase('test_import_modules', verbose))
    suite.addTest(TestPdnsToolsBase('test_error_classes', verbose))

    runner = unittest.TextTestRunner(verbosity=verbose)

    result = runner.run(suite)

# =============================================================================

# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list

{'fb_pdnstools': 'fb_pdnstools', 'fb_pdnstools.errors': 'fb_pdnstools.errors', 'PowerDNSHandlerError': 'fb_pdnstools.errors.PowerDNSHandlerError', 'PowerDNSZoneError': 'fb_pdnstools.errors.PowerDNSZoneError', 'PowerDNSRecordError': 'fb_pdnstools.errors.PowerDNSRecordError', 'PowerDNSRecordSetError': 'fb_pdnstools.errors.PowerDNSRecordSetError', 'PowerDNSWrongSoaDataError': 'fb_pdnstools.errors.PowerDNSWrongSoaDataError', 'PDNSApiError': 'fb_pdnstools.errors.PDNSApiError', 'PDNSApiNotAuthorizedError': 'fb_pdnstools.errors.PDNSApiNotAuthorizedError', 'PDNSApiNotFoundError': 'fb_pdnstools.errors.PDNSApiNotFoundError', 'PDNSApiValidationError': 'fb_pdnstools.errors.PDNSApiValidationError', 'PDNSApiRateLimitExceededError': 'fb_pdnstools.errors.PDNSApiRateLimitExceededError', 'PDNSApiRequestError': 'fb_pdnstools.errors.PDNSApiRequestError'}


import pygame, time, sys
from scripts.globals import *
from scripts.terrain import *
from scripts.Map_render import *

import numpy as np

pygame.init()
Runing = True
Sec, Frame, FPS = 0, 0 , 0

worldTerrain = Map_Render.load_Map("world.map")

WHITE = (255, 255, 255)

def fps():
    global Sec, Frame, FPS

    if Sec == time.strftime("%S"):
        Frame += 1
    else:
        FPS = Frame
        Frame = 0
        Sec = time.strftime("%S")
def Render_Window():
    global window, Window_H, Window_W, window_title
    Window_W, Window_H = 800, 650
    window_title = "God Sim"
    pygame.display.set_caption(window_title)
    window = pygame.display.set_mode((Window_W, Window_H), pygame.HWSURFACE|pygame.DOUBLEBUF)

Render_Window()

while Runing:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            Runing = False

        if event.type == pygame.KEYDOWN:
            if event.key == pygame.K_w:
                Globals.cam_move = 1
            elif event.key == pygame.K_s:
                Globals.cam_move = -1
            elif event.key == pygame.K_d:
                Globals.cam_move = 2
            elif event.key == pygame.K_a:
                Globals.cam_move = -2
        elif event.type == pygame.KEYUP:
            Globals.cam_move=0

    if event.type == pygame.KEYDOWN:
        if Globals.cam_move == 1:
            Globals.cam_y +=1
        elif Globals.cam_move == -1:
            Globals.cam_y -=1
        elif Globals.cam_move == 2:
            Globals.cam_x -=1
        elif Globals.cam_move == -2:
            Globals.cam_x +=1

    window.fill(WHITE)
    #print(FPS)

    window.blit(worldTerrain, (Globals.cam_x, Globals.cam_y))

    fps()
    pygame.display.update()

pygame.quit()
quit()


from inference import ImportanceSampling, MetropolisHastings, Prob
from distribution import binomial, uniform
from test_inference import test

def laplace(prob: Prob, data: None) -> float:
    p = prob.sample(uniform(0., 1.))
    """g = prob.sample(binomial(p, 493472)
    prob.assume(g = 241945)"""
    prob.observe(binomial(p, 493472), 241945)
    return p

if __name__ == '__main__':
    model = laplace
    data = None
    name = "Laplace"
    options = {
        'shrink': False,
        'plot_with_support': True,
        'plot_style': 'line'
    }
    #Tourne beaucoup trop longtemps
    #test(model, data, name, method=RejectionSampling, **options)

    #Fonctionne
    test(model, data, name, method=ImportanceSampling, **options)  # type: ignore
    test(model, data, name, method=MetropolisHastings, **options)  # type: ignore

    #N'est pas applicable car uniforme n'a pas de support fini.
    #test(model, data, name, method=EnumerationSampling, **options)