import time
from math import inf
from typing import Type, List, Union, TYPE_CHECKING

from core.constant import PCRelement
from core.pcr_checker import Checker, LockTimeoutError, LockMaxRetryError

if TYPE_CHECKING:
    from core.Automator import Automator

"""

4. 场景类
场景类PCRScene是一个集合了该场景全部可交互信息的手段，把相关参数和场景交互方法封装在一个类中，
使得程序可读性更高。
PCRScene的每一个交互的返回值均为一个PCRScene类，表示经过该交互后场景的变化。
PCRSceneBase类是全部场景的基类，由于需要对Automator进行操控，PCRSceneBase含有参数_a，表示对应Automator。
    生成场景时，必须要提供_a参数。
PCRSceneBase类也会提供Automator中的常用方法，如click, lock_img等等，只是相当于简化了self._a.click -> self.click。
一个成功的场景切换一般包括两个步骤：
（在特殊ES不触发时）
    当前场景的特征消失
    下一场景的特征出现
因此，为了保证切换的顺利，每个PCRSceneBase类都含有特殊的特征函数：
    PCRSceneBase.feature
    返回值为True时，说明检测到了该场景的特征。
在场景切换中，首先锁定直到该场景特征消失，再锁定直到后一场景特征出现。当然
同时考虑到每个场景刚进入后都可能需要ES，所以每个PCRSceneBase类还提供了一个initFC：
    PCRSceneBase.initFC
    在场景刚进入时会挂载init_FC到特定Group中，直到一次有效的场景交互被成功执行，或手动调用
        PCRSceneBase.clear_initFC()

"""

class PCRSceneBase:

    def __init__(self, a, *args, **kwargs):
        self._a: "Automator" = a
        self.scene_name = "BaseScene"
        self._a.scenes += [self]
        self.initFC = None
        self.feature = None  # screen -> True/False
        self._raise = self._a._raise
        self.check_ocr_running = self._a.check_ocr_running
        self.click = self._a.click
        self.click_img = self._a.click_img
        self.lock_img = self._a.lock_img
        self.lock_no_img=self._a.lock_no_img
        self.click_btn=self._a.click_btn
        self.getFC=self._a.getFC
        self.is_exists=self._a.is_exists
        self.img_prob=self._a.img_prob
        self.img_where_all=self._a.img_where_all
        self.img_equal=self._a.img_equal
        self.wait_for_stable=self._a.wait_for_stable
        self.wait_for_change = self._a.wait_for_change
        self.wait_for_loading = self._a.wait_for_loading
        self.getscreen = self._a.getscreen
        self.lock_fun = self._a.lock_fun
        self.chulijiaocheng = self._a.chulijiaocheng
        self.check_dict_id = self._a.check_dict_id
        self.ocr_center = self._a.ocr_center
        self.ocr_int = self._a.ocr_int
        self.ocr_A_B = self._a.ocr_A_B

    def fun_feature_exist(self, element: PCRelement):
        def fun(screen):
            return self.is_exists(element, screen=screen)

        return fun

    def fun_click(self, *args, **kwargs):
        def fun():
            self.click(*args, **kwargs)

        return fun

    @property
    def last_screen(self):
        return self._a.last_screen

    @property
    def log(self):
        return self._a.log

    @property
    def debug_record(self):
        return self._a.debug_record

    def goto(self, scene: Union[Type["PCRSceneBase"], Type["PCRMsgBoxBase"]], gotofun, use_in_feature_only=None,
             before_clear=True, timeout=None, interval=8, retry=None):
        next_scene = scene(self._a)
        if use_in_feature_only is None:
            if PCRMsgBoxBase in scene.__mro__:
                use_in_feature_only = True
            else:
                use_in_feature_only = False

        def featureout(screen):
            if use_in_feature_only:
                return True if next_scene.feature(screen) else False
            else:
                return True if not self.feature(screen) else False

        if before_clear:
            self.clear_initFC()
        if self.feature is not None:
            self._a.getFC().getscreen().wait_for_loading(). \
                add(Checker(featureout, name=f"{self.scene_name} - Feature Out"), rv=True). \
                add_intervalprocess(gotofun, retry=retry, interval=interval, name="gotofun", raise_retry=True).lock(
                timeout=timeout)
        return next_scene.enter()

    def enter(self,timeout=None):
        def featurein(screen):
            return True if self.feature(screen) else False

        # Clear Other Scenes InitFC
        self._a.clear_all_initFC(self.scene_name)
        self._a.scenes = [self]
        if self.initFC is not None:
            self._a.ES.register(self.initFC, group=self.scene_name)
        if self.feature is not None:
            self._a.getFC().getscreen().wait_for_loading(). \
                add(Checker(featurein, name=f"{self.scene_name} - Feature In"), rv=True).lock(timeout=timeout)
        return self

    def clear_initFC(self):
        self._a.ES.clear(self.scene_name)
        return self

    def set_initFC(self):
        self._a.ES.register(self.initFC, self.scene_name)
        return self

    def no_initFC(self):
        """
        with self.no_initFC():
            XXX

        该部分的内容不会受到InitFC的影响
        """
        obj=self
        class _no_initFC:
            def __init__(self):
                self._flag=True

            def __enter__(self):
                if obj.scene_name not in obj._a.ES.FCs:
                    self._flag = False
                else:
                    obj.clear_initFC()

            def __exit__(self, *args, **kwargs):
                if self._flag:
                    obj.set_initFC()

        return _no_initFC()
    def exit(self, exitfun, before_clear=True, timeout=None, interval=8, retry=None):
        """
        不断执行exitfun直到退出Scene（feature无法识别）
        """

        def featureout(screen):
            return True if not self.feature(screen) else False

        if before_clear:
            self.clear_initFC()
        if self.feature is not None:
            self._a.getFC().getscreen(). \
                add(Checker(featureout, name=f"{self.scene_name} - Feature Out"), rv=True). \
                add_intervalprocess(exitfun, retry=retry, interval=interval, name="gotofun").lock(timeout=timeout)
        if not before_clear:
            self.clear_initFC()
        return None

class PCRMsgBoxBase(PCRSceneBase):
    def __init__(self, a):
        super().__init__(a)
        self.scene_name = "PCRMsgBox"

class PossibleSceneList(PCRSceneBase):
    """
    可能一个操作之后，产生数个msgbox，这些msgbox的内容、顺序不一定一致，而且可能会有多个跳出。
    使用PossibleMsgBoxList来把这些msgbox全部同时考虑住。
    scene_list:包含msgbox的列表，每个msgbox必须有feature参数以确认是否显示。
    no_scene_feature：当检测到时，则认定当前没有msgbox弹出；若不指定，则只有当全部msgbox_list均未弹出才判定为无msgbox

    缺陷：如果两个msgbox之间弹出间隔过长，则可能该方法失效。
    解决方法：双重判定。(double_check)：为一个时间， 表示必须满足间隔为double_check的两次判定均生效才认定无msgbox。
        double_check设置为None则不使用双重判定。

    Example:
        PossibleSceneList([
    """

    def __init__(self, a, scene_list: List[PCRSceneBase], no_scene_feature=None, double_check=2., timeout=10.,
                 max_retry=3):
        self.scene_list = scene_list
        self.no_scene_feature = no_scene_feature
        self.double_check = double_check
        self.timeout = timeout if timeout is not None else inf
        self.max_retry = max_retry if max_retry is not None else inf
        super().__init__(a)

    def check(self):
        """
        检查场景上是否存在满足要求的scene。
        若存在指定scene，则返回该scene。
        若无scene，则返回None
        """

        last_check_time = None
        no_scene = False
        start_time = time.time()
        retry = 0

        while retry < self.max_retry:
            if self.timeout is not None and time.time() - start_time > self.timeout:
                raise LockTimeoutError("SceneList判断超时！")
            for scene in self.scene_list:
                screen = self.getscreen()
                if scene.feature(screen):
                    return scene
                if self.no_scene_feature is not None:
                    no_scene = self.no_scene_feature(screen)
                else:
                    no_scene = True
            if no_scene:
                start_time = time.time()
                if last_check_time is None:
                    last_check_time = time.time()
                else:
                    if time.time() - last_check_time > self.double_check:
                        return None  # No Msg
                    else:
                        time.sleep(-time.time() + last_check_time + self.double_check)  # Double Check
            else:
                last_check_time = None
                retry += 1
        raise LockMaxRetryError("SceneList判断超出尝试上限！")


import enum
from typing import Any, Union

from pymongo.database import Database as MongoDatabase
import pymongo

from src.storages.mongo.collections.base import BaseCollection
from src.storages.errors import DoesNotExistError
from src.models.rating import LikeDislikeRating

class ObjectType(str, enum.Enum):
    """Вид объекта, для которого ставится оценка."""
    MOVIE = "movie"
    """Фильм."""
    REVIEW = "review"
    """Рецензия."""

class RatingsCollection(BaseCollection):
    """Класс коллекции оценок."""

    def __init__(self, db: MongoDatabase) -> None:
        super().__init__(db)

        self.coll = self.get_collection("ratings")
        self.coll.create_index(
            [
                ("object_id", pymongo.ASCENDING),
                ("object_type", pymongo.ASCENDING),
                ("username", pymongo.ASCENDING)
            ],
            unique=True
        )

    def add(
        self,
        object_id: Any,
        object_type: ObjectType,
        username: str,
        rating: int
    ):
        """Поставить оценку.

        Args:
            object_id: ИД объекта
            object_type: тип объекта класса `ObjectType`
            username: имя пользователя
            rating: рейтинг

        """
        self.coll.insert_one({
            "object_id": object_id,
            "object_type": object_type.value,
            "username": username,
            "rating": rating
        })

    def edit(
        self,
        object_id: Any,
        object_type: ObjectType,
        username: str,
        rating: int
    ) -> None:
        """Изменить оценку.

        Args:
            object_id: ИД объекта
            object_type: тип объекта класса `ObjectType`
            username: имя пользователя
            rating: рейтинг

        """
        result = self.coll.find_one_and_update(
            {
                "object_id": object_id,
                "object_type": object_type.value,
                "username": username
            },
            {
                "$set": {
                    "rating": rating
                }
            }
        )
        if not result:
            raise DoesNotExistError()

    def delete(
        self,
        object_id: Any,
        object_type: ObjectType,
        username: str
    ):
        """Удалить оценку.

        Args
            object_id: ИД объекта
            object_type: тип объекта класса `ObjectType`
            username: имя пользователя

        """
        result = self.coll.find_one_and_delete({
            "object_id": object_id,
            "object_type": object_type.value,
            "username": username
        })
        if not result:
            raise DoesNotExistError()

    def get(
        self,
        object_id: Any,
        object_type: ObjectType,
        username: str
    ) -> Union[int, None]:
        """Получить оценку пользователя.

        Args
            object_id: ИД объекта
            object_type: тип объекта класса `ObjectType`
            username: имя пользователя

        Returns:
            int | None: оценка или None

        """
        result = self.coll.find_one({
            "object_id": object_id,
            "object_type": object_type.value,
            "username": username
        })
        if result:
            return result["rating"]
        return None

    def get_aggregated_rating(
        self,
        object_id: Any,
        object_type: ObjectType
    ) -> Union[float, None]:
        """Получить агрегированную оценку.

        Args
            object_id: ИД объекта
            object_type: тип объекта класса `ObjectType`

        Returns:
            float | None: оценка или None

        """
        ratings = list(self.coll.aggregate([
            {
                "$match": {
                    "object_id": object_id,
                    "object_type": object_type.value
                }
            },
            {
                "$group": {
                    "_id": "movie_id",
                    "avg_rating": {
                        "$avg": "$rating"
                    }
                }
            }
        ]))
        if ratings:
            return ratings[0]["avg_rating"]
        return None

    def get_likes_dislikes_count(
        self,
        object_id: Any,
        object_type: ObjectType
    ) -> LikeDislikeRating:
        """Получить количество лайков и дизлайков.

        Args
            object_id: ИД объекта
            object_type: тип объекта класса `ObjectType`

        Returns:
            `LikeDislikeRating`: модель с количеством лайков и дизлайков

        """
        result = list(self.coll.aggregate([
            {
                "$match": {
                    "object_id": object_id,
                    "object_type": object_type.value,
                }
            },
            {
                "$project": {
                    "likes": {"$cond": [{"$eq": ["$rating", 10]}, 1, 0]},
                    "dislikes": {"$cond": [{"$eq": ["$rating", 0]}, 1, 0]},
                }
            },
            {
                "$group": {
                    "_id": "object_id",
                    "likes": {
                        "$sum": "$likes"
                    },
                    "dislikes": {
                        "$sum": "$dislikes"
                    }
                }
            }
        ]))

        return LikeDislikeRating(
            likes=result[0]["likes"] if result else 0,
            dislikes=result[0]["dislikes"] if result else 0
        )


import pandas as pd
import copy
from collections import deque
import random
import json
import numpy as np
from copy import copy
import math

from property_selector import PropertySelector
from property_selector import SetPropertySelector

sets = pd.read_csv('set_inputs.txt', delimiter='\t')
weapons = pd.read_csv('weapons.txt', delimiter='\t')
armor = pd.read_csv('armor.txt', delimiter='\t')
jewelry = pd.read_csv('jewelry.txt', delimiter='\t')

weapons = weapons.query('spawnable == 1')
weapons = weapons.query('type != "tpot"')

armor = armor.query('spawnable == 1')
jewelry = jewelry.query('spawnable == 1')

weapons['itemclass'] = 'weap'
armor['itemclass'] = 'armo'

# Load template file
with open('itemtemplate.json', 'r') as tfile:
    itemtemplate = json.load(tfile)
with open('settemplate.json', 'r') as tfile:
    settemplate = json.load(tfile)

def fetch_random_item(type_exclusions, type_restrictions = [], minlvl = 0, handsAvail = 2, allowHybrid = False):
    if handsAvail <= 0:
        type_exclusions.append('shie')
        df = pd.concat([armor, jewelry])
    else:
        w = weapons
        if len(type_restrictions) > 0:
            w = w.query(f'type in @type_restrictions')

        if handsAvail == 1 and allowHybrid:
            w = w[(w['2handed'] != 1) | (w['1or2handed'] == 1)]
        elif handsAvail == 1 and ~allowHybrid:
            w = pd.DataFrame()

        df = pd.concat([w, armor, jewelry])

    if minlvl > 0:
        df = df.query(f'level > {minlvl-6} and level <= {minlvl+6}')
    if len(type_exclusions) > 0:
        df = df.query(f'type not in @type_exclusions')

    sample = df.sample()
    return sample

def update_exclusion_types(exclusion_list, itemtype):
    exclusion_list.append(itemtype)
    if itemtype == 'helm':
        exclusion_list.append('circ')
    elif itemtype == 'circ':
        exclusion_list.append('helm')

num_items = np.random.normal(4, 2, sets.shape[0])
sets['NumberOfItems'] = num_items.astype(int)
sets['NumberOfItems'] = np.minimum(6, sets['NumberOfItems'])
sets['NumberOfItems'] = np.maximum(2, sets['NumberOfItems'])

sets = list(sets.values)
q = deque(sets)

idx = 1250

type_exclusions_by_class = {
    'ama' : ['wand', 'scep', 'knif', 'swor', 'axe', 'mace', 'club', 'hamm', 'tkni', 'taxe', 'staf', 'h2h', 'h2h2', 'orb', 'pelt', 'phlm', 'ashd', 'head'],
    'bar' : ['wand', 'scep', 'knif', 'staf', 'h2h', 'h2h2', 'orb', 'pelt', 'ashd', 'ajav', 'aspe', 'abow', 'head', 'bow', 'xbow'],
    'sor' : ['h2h', 'h2h2', 'pelt', 'ashd', 'ajav', 'aspe', 'abow', 'tkni', 'taxe', 'head','bow', 'xbow', 'jave', 'phlm'],
    'dru' : ['wand', 'scep', 'knif', 'tkni', 'taxe', 'staf', 'h2h', 'h2h2', 'orb', 'phlm', 'ashd', 'ajav', 'aspe', 'abow', 'head','bow', 'xbow', 'jave'],
    'ass' : ['wand', 'scep', 'knif', 'swor', 'axe', 'club', 'hamm', 'tkni', 'taxe', 'staf', 'orb', 'pelt', 'phlm', 'ashd', 'ajav', 'aspe', 'abow', 'head','bow', 'xbow', 'jave'],
    'nec' : ['scep', 'tkni', 'taxe', 'staf', 'h2h', 'h2h2', 'orb', 'pelt', 'phlm', 'ashd', 'ajav', 'aspe', 'abow','bow', 'xbow', 'jave'],
    'pal' : ['wand', 'knif', 'tkni', 'taxe', 'staf', 'h2h', 'h2h2', 'orb', 'pelt', 'phlm', 'ajav', 'aspe', 'abow', 'head', 'bow', 'xbow', 'jave']
}

# "[Class Skill Tab ID] = (Amazon = 0-2, Sorceress = 3-5, Necromancer = 6-8, Paladin = 9-11, Barbarian = 12-14, Druid = 15-17,  Assassin = 18-20)"
skilltablookup = {
    'ama' : {
        34 : 0,
        35 : 0,
        25 : 0,
        15 : 0,
        24 : 0,
        32 : 1,
        31 : 2,
        21 : 2,
        11 : 2,
        12 : 2,
        22 : 2,
        26 : 2,
        7 : 2,
        16 : 2,
        27 : 2
    },
    'ass' : {
        274 : 18,
        275 : 18,
        278 : 19,
        279 : 19,
        271 : 20,
        277 : 20,
    },
    'bar' : {
        148 : 13,
        145 : 13,
        153 : 13,
        151 : 14,
        147 : 14
    },
    'dru' : {
        250 : 15,
        225 : 15,
        229 : 15,
        249 : 15,
        248 : 16,
        238 : 16,
        233 : 16,
        247 : 17
    },
    'nec' : {
        75 : 6,
        85 : 6,
        94 : 6,
        90 : 6,
        84 : 7,
        93 : 7,
        92 : 7,
        73 : 7
    },
    'pal' : {
        122 : 10,
        103 : 10,
        119 : 10,
        118 : 10,
        114 : 10,
        102 : 10,
        106 : 11,
        121 : 11
    },
    'sor' : {
        59 : 3,
        64 : 3,
        57 : 4,
        48 : 4,
        53 : 4,
        62 : 5,
        51 : 5,
        56 : 5,
        52 : 5
    }
}
itemcache = set()

def getNameComponents():
    newnames = pd.read_csv('names.txt', delimiter = '\t', header=None)
    newnames = set(newnames[0].values)
    components = set()
    for n in newnames:
        parts = n.split(' ')
        components = components.union(set(parts))

    first = []
    second = []
    for k in components:
        if k in ['of', 'the']:
            continue
        first.append(k)
        if '\'' not in k and 'ing' not in k:
            second.append(k)
    return first, second

def generateItemName(first, second):
    global itemcache
    f, s = random.sample(first, 1)[0], random.sample(second, 1)[0]
    newname = ' '.join([f, s])
    if newname in itemcache:
        return generateItemName(first, second)
    else:
        itemcache.add(newname)
        return newname

setNames = set()
def create_items(firstNames, secondNames):
    global idx
    setItems = []
    sets = []
    propertySelector = PropertySelector(affix_file = 'set_item_affixes.txt')
    setPropertySelector = SetPropertySelector(affix_file = 'set_bonus_affixes.txt')

    # For each set...
    while q:
        skill, id, charclass, element, minilvl, type_restrictions, numitems = q.popleft()

        # Randomly uplevel the set
        level = random.randint(minilvl, 80)

        # Get Set Name
        setName = generateItemName(firstNames, secondNames)
        firstName = setName.split(' ')[0:1]
        firstNames.remove(firstName[0])
        print(skill, id, charclass, element, minilvl, level, numitems, firstName[0])

        setObject = copy(settemplate)
        setObject['index'] = setName
        setObject['name'] = setName

        exclusion_list = copy(type_exclusions_by_class[charclass])
        restriction_list = type_restrictions.split(',') if type(type_restrictions) == type('') else []

        items = []
        rings = 0
        hands = 2
        auraSet = False
        oskillSet = False
        exclusion_list.append('shie')

        # For each item ...
        while len(items) < numitems:
            item = fetch_random_item(exclusion_list, type_restrictions = restriction_list, minlvl = level, handsAvail = hands, allowHybrid = True if charclass == 'bar' else False)
            item['level'] = level

            itemtype = item['type'].values[0]
            itemclass = item['itemclass'].values[0]

            if itemtype == 'shie':
                hands -= 1
                update_exclusion_types(exclusion_list, itemtype)
            elif itemclass == 'weap':
                hands -= 1
                if item['2handed'].values[0] == 1:
                    hands -= 1

                if item['1or2handed'].values[0] == 1 and charclass == 'bar':
                    hands += 1

                if hands == 1:
                    exclusion_list.remove('shie')
            elif itemtype == 'ring':
                rings += 1
                if rings >= 2:
                    exclusion_list.append(itemtype)
            else:
                update_exclusion_types(exclusion_list, itemtype)

            # Sanitize level 0 -> level 1
            level = max(level, 1)
            # Sanitize level > 100 -> level 99
            level = min(level, 99)

            propertyCodes = []

            if auraSet:
                propertyCodes.append('aura')
            if oskillSet:
                propertyCodes.append('oskill')

            # Number of properties
            minprop, maxprop = int(np.log(level) * 1), int(np.log(level) * 1.5)
            minprop, maxprop = max(minprop, 2), max(min(maxprop, 6), 3)
            numprop = random.randint(minprop, maxprop)

            # Select elemental type
            etype = element

            outitem = copy(itemtemplate)
            outitem['index'] = generateItemName(firstName, secondNames)
            outitem['*ID'] = idx+407
            outitem['set'] = setName
            outitem['lvl'] = int(level)
            outitem['item'] = item['code'].values[0]
            outitem['add func'] = 2
            outitem['*ItemName'] = item['name'].values[0]

            c = 0
            hlevel = 1
            # For each property
            while c < numprop:
                prop = propertySelector.search_property(level, itemclass, itemtype, etype, hlevel, propertyCodes, forceClass = charclass)
                propertyCodes.append(prop['Property'].values[0])

                if prop['Property'].values[0] == 'aura':
                    auraSet = True
                elif prop['Property'].values[0] == 'oskill':
                    oskillSet = True

                if hlevel == 1:
                    hlevel = random.randint(1, 2)
                elif hlevel == 2:
                    hlevel = random.sample([2, 2, 3], 1)[0]
                elif hlevel == 3:
                    hlevel = random.sample([3, 3, 3, 1], 1)[0]

                param = prop['Param'].values[0]
                if type(param) == type('str') and param.isnumeric() == False:
                    param = random.sample(param.split(','), 1)[0]
                    param = param.strip()

                func = prop['LvlFunc'].values[0]
                minval = prop['MinValue'].values[0]
                maxval = prop['MaxValue'].values[0]

                try:
                    if func != '':

                        if func == 'linear':
                            minval = str(minval).replace('x', str(level))
                            maxval = str(maxval).replace('x', str(level))
                            minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))
                        elif func == 'ln':
                            x = np.log(level)

                            minval = str(minval).replace('x', str(x))
                            maxval = str(maxval).replace('x', str(x))
                            minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))

                        minval, maxval = float(minval), float(maxval)
                except SyntaxError:
                    print('ERROR')
                    print(prop, func, minval, maxval)
                    raise
                except ValueError:
                    print('ERROR')
                    print(prop, func, minval, maxval)
                    raise

                maxval = max(maxval, minval)
                outitem[f'prop{c+1}'] = prop['Property'].values[0]
                outitem[f'par{c+1}'] = '' if pd.isna(param) else param
                outitem[f'min{c+1}'] = int(minval)
                outitem[f'max{c+1}'] = int(maxval)

                c += 1

            anumprop = random.randint(math.floor(numitems/2), min(5, numitems-1))

            apropertyCodes = []
            propertyType = 'ascaler'

            c = 0
            # For each aproperty itype, etype, prop_type, used_prop_codes,
            while c < anumprop:
                prop = setPropertySelector.search_property(itemclass, element, propertyType, apropertyCodes, forceClass = charclass)
                propcode = prop['prop'].values[0]
                apropertyCodes.append(propcode)

                param = prop['param'].values[0]
                if type(param) == type('str') and param.isnumeric() == False:
                    param = random.sample(param.split(','), 1)[0]

                func = prop['LvlFunc'].values[0]
                minval = prop['MinValue'].values[0]
                maxval = prop['MaxValue'].values[0]

                try:
                    if func != '':

                        if func == 'linear':
                            minval = str(minval).replace('x', str(level))
                            maxval = str(maxval).replace('x', str(level))
                            minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))
                        elif func == 'ln':
                            x = np.log(level)

                            minval = str(minval).replace('x', str(x))
                            maxval = str(maxval).replace('x', str(x))
                            minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))

                        minval, maxval = float(minval), float(maxval)
                except SyntaxError:
                    print('ERROR')
                    print(prop, func, minval, maxval)
                    raise
                except ValueError:
                    print('ERROR')
                    print(prop, func, minval, maxval)
                    raise

                if propcode == 'skill':
                    param = id
                elif propcode == 'skilltab':
                    param = skilltablookup[charclass][id]

                maxval = max(maxval, minval)
                outitem[f'aprop{c+1}a'] = propcode
                outitem[f'apar{c+1}a'] = '' if pd.isna(param) else param
                outitem[f'amin{c+1}a'] = int(minval)
                outitem[f'amax{c+1}a'] = int(maxval)

                c += 1

            idx += 1
            items.append(outitem)
            setItems.append(outitem)

        # Build partial set props
        pnumprops = random.randint(2, max(2, min(4,numitems-1)))

        ppropertyCodes = []
        propertyType = 'pscaler'

        c = 0
        while c < pnumprops:
            prop = setPropertySelector.search_property('all', element, propertyType, ppropertyCodes, forceClass = charclass)
            propcode = prop['prop'].values[0]
            ppropertyCodes.append(propcode)

            param = prop['param'].values[0]
            if type(param) == type('str') and param.isnumeric() == False:
                param = random.sample(param.split(','), 1)[0]

            func = prop['LvlFunc'].values[0]
            minval = prop['MinValue'].values[0]
            maxval = prop['MaxValue'].values[0]

            try:
                if func != '':

                    if func == 'linear':
                        minval = str(minval).replace('x', str(level))
                        maxval = str(maxval).replace('x', str(level))
                        minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))
                    elif func == 'ln':
                        x = np.log(level)

                        minval = str(minval).replace('x', str(x))
                        maxval = str(maxval).replace('x', str(x))
                        minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))

                    minval, maxval = float(minval), float(maxval)
            except SyntaxError:
                print('ERROR')
                print(prop, func, minval, maxval)
                raise
            except ValueError:
                print('ERROR')
                print(prop, func, minval, maxval)
                raise

            if propcode == 'skill':
                param = id
            elif propcode == 'skilltab':
                param = skilltablookup[charclass][id]

            maxval = max(maxval, minval)
            setObject[f'PCode{c+2}a'] = propcode
            setObject[f'PParam{c+2}a'] = '' if pd.isna(param) else param
            setObject[f'PMin{c+2}a'] = int(minval)
            setObject[f'PMax{c+2}a'] = int(maxval)

            c += 1

        # Build full set props
        fnumprops = random.randint(numitems, 8)

        fpropertyCodes = []
        propertyType = 'fscaler'

        c = 0
        while c < fnumprops:
            prop = setPropertySelector.search_property('all', element, propertyType, fpropertyCodes, forceClass = charclass)
            propcode = prop['prop'].values[0]
            fpropertyCodes.append(propcode)

            param = prop['param'].values[0]
            if type(param) == type('str') and param.isnumeric() == False:
                param = random.sample(param.split(','), 1)[0]

            func = prop['LvlFunc'].values[0]
            minval = prop['MinValue'].values[0]
            maxval = prop['MaxValue'].values[0]

            try:
                if func != '':

                    if func == 'linear':
                        minval = str(minval).replace('x', str(level))
                        maxval = str(maxval).replace('x', str(level))
                        minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))
                    elif func == 'ln':
                        x = np.log(level)

                        minval = str(minval).replace('x', str(x))
                        maxval = str(maxval).replace('x', str(x))
                        minval, maxval = math.floor(eval(minval)), math.ceil(eval(maxval))

                    minval, maxval = float(minval), float(maxval)
            except SyntaxError:
                print('ERROR')
                print(prop, func, minval, maxval)
                raise
            except ValueError:
                print('ERROR')
                print(prop, func, minval, maxval)
                raise

            if propcode == 'skill':
                param = id
            elif propcode == 'skilltab':
                param = skilltablookup[charclass][id]

            maxval = max(maxval, minval)
            setObject[f'FCode{c+1}'] = propcode
            setObject[f'FParam{c+1}'] = '' if pd.isna(param) else param
            setObject[f'FMin{c+1}'] = int(minval)
            setObject[f'FMax{c+1}'] = int(maxval)

            c += 1

        sets.append(setObject)

    df = pd.DataFrame(setItems)
    df.to_csv('setitems.txt', index=False, sep='\t')

    df = pd.DataFrame(sets)
    df.to_csv('sets.txt', index=False, sep='\t')

firstNames, secondNames = getNameComponents()
create_items(firstNames, secondNames)

id = 375000
jsons = []
for n in itemcache:
    jsons.append(
        {
            "id": id,
            "Key": n,
            "enUS": n
        }
    )
    id += 1

with open('names.json', 'w') as ofile:
    json.dump(jsons, ofile, indent=4)


import time
import unittest
from selenium import webdriver
from selenium.webdriver.common.keys import Keys

# Selenium is a "property based" tool, a.k.a "second generation".
# Might wanna look up what that means, since you slept through that
# part of the lecture.

driver = webdriver.Chrome()

# launch the app
driver.get("http://www.wikipedia.org")
time.sleep(2)

# find elements
driver.find_element_by_id("js-link-box-en").click()
time.sleep(2)

# interact
driver.find_element_by_id("searchInput").send_keys("tiger")
time.sleep(2)
driver.find_element_by_id("searchInput").send_keys(Keys.ENTER)
time.sleep(2)

# check
assert "Tiger - Wikipedia" == driver.title

assert "Tiger" == driver.find_element_by_id("firstHeading").text

print("all good")

driver.close()

# Pretty cool tool. Might be able to use this for a bunch of
# fun online automation things. HLTV stats perhaps? Quickly
# calculating


from __future__ import print_function

import argparse
import logging
import random
import time
import numpy as np
import pandas as pd
import tensorflow as tf
import keras.backend as K
from critic import Critic
from actor import Actor
from experience_memory import ExperienceMemory
from copy import deepcopy
from carla.client import make_carla_client,VehicleControl
from carla.sensor import Camera
from carla.settings import CarlaSettings
from carla.tcp import TCPConnectionError
from carla.util import print_over_same_line

# =============================================================================
#  Subtaskok makrói
# =============================================================================
SUBTASK_DRIVE_IN_LANE = 1
SUBTASK_TURN_AROUND = 2
SUBTASK_TURN_LEFT = 3
SUBTASK_TURN_RIGHT = 4

# =============================================================================
# Hyperparams
# =============================================================================
CRITIC_LR = 0.001
ACTOR_LR = 0.0001

MINI_BATCH_SIZE = 128

TARGET_UPDATE_BASE_FREQ = 100
TARGET_UPDATE_MULTIPLIER = 1.05
GAMMA = 0.95

def run_carla_client(args):
    # Here we will run 3 episodes with 300 frames each.
    number_of_episodes = 60000
    frames_per_episode = 400

    # We assume the CARLA server is already waiting for a client to connect at
    # host:port. To create a connection we can use the `make_carla_client`
    # context manager, it creates a CARLA client object and starts the
    # connection. It will throw an exception if something goes wrong. The
    # context manager makes sure the connection is always cleaned up on exit.
    with make_carla_client(args.host, args.port,30) as client:
        print('CarlaClient connected')

# =============================================================================
#       Global initialisations
# =============================================================================
        config = tf.ConfigProto()
        config.gpu_options.allow_growth = True
        sess = tf.Session(config=config)
        K.set_session(sess)

        state_size={'state_2D':(64,64,9,),
                    'state_1D':(17,)}
        action_size=(5,)

        critic = Critic(sess,state_size,action_size,CRITIC_LR)
        critic.target_train()
        actor = Actor(sess,state_size,action_size,ACTOR_LR)
        actor.target_train()
        memory = ExperienceMemory(100000,False)

        target_update_counter = 0
        target_update_freq = TARGET_UPDATE_BASE_FREQ

        explore_rate = 0.2

        success_counter = 0

        total_t = 0
        t=0
        #NOTE Ez csak egy próba, eztmég át kell alakítani
        target = {'pos':np.array([-3.7,236.4,0.9]),
                  'ori':np.array([0.00,-1.00,0.00])}

        if args.settings_filepath is None:
            # Create a CarlaSettings object. This object is a wrapper around
            # the CarlaSettings.ini file. Here we set the configuration we
            # want for the new episode.
            settings = CarlaSettings()
            settings.set(
                SynchronousMode=True,
                SendNonPlayerAgentsInfo=True,
                NumberOfVehicles=0,
                NumberOfPedestrians=0,
                WeatherId=random.choice([1]),
                QualityLevel=args.quality_level)
#            settings.randomize_seeds()
#
#            settings.randomize_seeds()
            # The default camera captures RGB images of the scene.
            camera0 = Camera('CameraRGB')
            # Set image resolution in pixels.
            camera0.set_image_size(64, 64)
            # Set its position relative to the car in centimeters.
            camera0.set_position(0.30, 0, 1.30)
            settings.add_sensor(camera0)
        else:

            # Alternatively, we can load these settings from a file.
            with open(args.settings_filepath, 'r') as fp:
                settings = fp.read()
        scene = client.load_settings(settings)

# =============================================================================
#       EPISODES LOOP
# =============================================================================
        for episode in range(0, number_of_episodes):
            # Start a new episode.
            # Choose one player start at random.
            number_of_player_starts = len(scene.player_start_spots)
            player_start = random.randint(0, max(0, number_of_player_starts - 1))
            player_start = 0
            total_reward = 0.
            # Notify the server that we want to start the episode at the
            # player_start index. This function blocks until the server is ready
            # to start the episode.
            print('Starting new episode...')
            client.start_episode(player_start)

            #TODO Ezen belül kéne implementálni a tanuló algoritmusunkat

# =============================================================================
#           Episodic intitialisations
# =============================================================================
            collisions ={'car':0,'ped':0,'other':0}
            reverse = -1.0
            measurements, sensor_data = client.read_data()
            state = get_state_from_data(measurements, sensor_data,reverse)
            goal = get_goal_from_data(target)
            t=0
            stand_still_counter=0
# =============================================================================
#           STEPS LOOP
# =============================================================================
            for frame in range(0, frames_per_episode):
                t=t+1
                total_t+=1
                target_update_counter+=1
                explore_dev = 0.6/(1+total_t/30000)
                explore_rate = 0.3/(1+total_t/30000)
            # Print some of the measurements.
            #   print_measurements(measurements)

                # Save the images to disk if requested.
                if args.save_images_to_disk and False:
                    for name, measurement in sensor_data.items():
                        filename = args.out_filename_format.format(episode, name, frame)
                        measurement.save_to_disk(filename)

                if state['state_1D'][9]<5 and t>50:
                    stand_still_counter+=1
                else:
                    stand_still_counter=0
                #Calculate the action
                a_pred = actor.model.predict([np.expand_dims(state['state_2D'],0),
                                              np.expand_dims(np.concatenate((state['state_1D'],goal)),0)])[0]
                #Add exploration noise to action
                a = add_noise(a_pred,explore_dev,explore_rate)
                control = get_control_from_a(a)
                #Sendcontrol to the server
                client.send_control(control)

#
# =============================================================================
#               TRAINING THE NETWORKS
# =============================================================================
                if memory.num_items > 6000:
                    batch,indeces = memory.sample_experience(MINI_BATCH_SIZE)
                    raw_states = [[e[0]['state_2D'],e[0]['state_1D']] for e in batch]
                    goals = np.asarray([e[5] for e in batch])
                    states = {'state_2D':np.atleast_2d(np.asarray([e[0] for e in raw_states[:]])),
                              'state_1D':np.atleast_2d(np.asarray([np.concatenate([e[1],goals[i]],axis=-1) for i,e in enumerate(raw_states[:])]))}

                    actions = np.asarray([e[1] for e in batch])
                    rewards = np.asarray([np.sum(e[2]) for e in batch]).reshape(-1,1)

                    raw_new_states = [[e[3]['state_2D'],e[3]['state_1D']] for e in batch]
                    new_states = {'state_2D':np.atleast_2d(np.asarray([e[0] for e in raw_new_states[:]])),
                                  'state_1D':np.atleast_2d(np.asarray([np.concatenate([e[1],goals[i]],axis=-1) for i,e in enumerate(raw_new_states[:])]))}

                    overs = np.asarray([e[4] for e in batch]).reshape(-1,1)

                    best_a_preds = actor.target_model.predict([new_states['state_2D'],new_states['state_1D']])
                    max_qs = critic.target_model.predict([new_states['state_2D'],
                                                          new_states['state_1D'],
                                                          best_a_preds])

                    ys = rewards + (1-overs)*GAMMA*max_qs
                    #Train Critic network
                    critic.model.train_on_batch([states['state_2D'],
                                                 states['state_1D'],
                                                 actions],ys)
                    #Train Actor network
                    a_for_grads = actor.model.predict([states['state_2D'],
                                                       states['state_1D']])
                    a_grads = critic.gradients(states,a_for_grads)
                    actor.train(states,a_grads)

                    #Train target networks
                    if target_update_counter >= int(target_update_freq):
                        target_update_counter = 0
                        target_update_freq = target_update_freq * TARGET_UPDATE_MULTIPLIER
                        critic.target_train()
                        actor.target_train()
# =============================================================================
#               GET AND STORE OBSERVATIONS
# =============================================================================
                #Get next measurements
                measurements, sensor_data = client.read_data()
                new_state = get_state_from_data(measurements, sensor_data, reverse, state)

                #TODO Calculate reward
                r_goal, success = calculate_goal_reward(np.atleast_2d(new_state['state_1D']),goal)
                r_general, collisions = calculate_general_reward(measurements,collisions)
                over = stand_still_counter>30 or success
                success_counter+=int(bool(success)*1)
                total_reward+=r_goal
                total_reward+=r_general
                #Store observation
                if t>10:
                    experience =  pd.DataFrame([[state,a,np.array([r_goal,r_general]),new_state,bool(over),goal,episode,0]],columns=['s', 'a', 'r', "s'", 'over','g','e','p'],copy = True)
                    memory.add_experience(experience)

                #Set the state to the next state
                state = new_state
                if over:
                    break
            sub_goal = deepcopy(state['state_1D'][0:6])
            print(str(episode)+". Episode###################")
            print("Total reward: "+str(total_reward))
            print("Success counter: "+str(success_counter))
            if (episode%10==0):
                print("############## DEBUG LOG ################")
                print("Memory state: "+str(memory.num_items))
                print("Target update counter: "+str(target_update_counter))
                print("Exploration rate: "+str(explore_rate))
                print("Exploration dev: "+str(explore_dev))
                print("Total timesteps: "+str(total_t))
                print("Average episode length: "+str(total_t/(episode+1)))
                print("#########################################")
# =============================================================================
#           REPLAY FOR SUBGOALS
# =============================================================================
            batch = memory.get_last_episode(t)
            raw_new_states = [[e[3]['state_2D'],e[3]['state_1D']] for e in batch]
            new_states = {'state_2D':np.atleast_2d(np.asarray([e[0] for e in raw_new_states[:]])),
                          'state_1D':np.atleast_2d(np.asarray([e[1] for e in raw_new_states[:]]))}
            rewards = np.asarray([e[2] for e in batch]).reshape(-1,2)
            r_subgoal = calculate_goal_reward(new_states['state_1D'],sub_goal)[0]
            rewards[:,0]=r_subgoal
            subgoal_batch = [[v[0],v[1],list(rewards)[i],v[3],v[4],sub_goal,v[6],v[7]] for i,v in enumerate(batch)]
            experiences =  pd.DataFrame(subgoal_batch,columns=['s', 'a', 'r', "s'", 'over','g','e','p'],copy = True)
            memory.add_experience(experiences)

def add_noise(a_pred,dev,explore_rate):
        noise = np.random.normal(0,dev,a_pred.shape)
        a = a_pred+noise
        a[0]=np.clip(a[0],-1.0,1.0)
        a[1]=np.clip(np.clip(a[1],0.0,1.0)+0.3,0.0,1.0)
        a[2]=np.clip(a[2]-0.8,0.0,1.0)
#        a[3]=1 if a[3]>=0.95 else 0
#        a[4]=1 if a[4]>=1 else 0
        a[3]=0
        a[4]=0

        if random.uniform(0,1) < explore_rate:
            a[0]=random.uniform(-1.0,1.0)
            a[1]=random.uniform(0.5,1)
        return a

def get_control_from_a(a):
    control = VehicleControl()
    control.steer=a[0]
    control.throttle=a[1]
    control.brake=a[2]
    control.hand_brake=bool(a[3])
    control.reverse=bool(a[4])
    return control

def get_goal_from_data(target):

        #Goal
    goal = np.hstack([target['pos'][0],target['pos'][1],target['pos'][2],
                      target['ori'][0],target['ori'][1],target['ori'][2]])
    return goal;

def get_state_from_data(measurements,sensor_data,reverse,prev_state=None):
    player_measurements = measurements.player_measurements

    pos_x=player_measurements.transform.location.x
    pos_y=player_measurements.transform.location.y
    pos_z=player_measurements.transform.location.z

    ori_x = player_measurements.transform.orientation.x
    ori_y = player_measurements.transform.orientation.y
    ori_z = player_measurements.transform.orientation.z

    acc_x = player_measurements.acceleration.x
    acc_y = player_measurements.acceleration.x
    acc_z = player_measurements.acceleration.x

    speed=player_measurements.forward_speed * 3.6

#    TODO Befejezni
#    NOTE Nem kéne bele a target pozi is? Esetleg az idő?
    state_1D = np.hstack([pos_x,pos_y,pos_z,
                          ori_x,ori_y,ori_z,
                          acc_x,acc_y,acc_z,
                          speed,reverse])
    if prev_state is None:
        state_2D = np.concatenate([sensor_data['CameraRGB'].data/255*2-1,
                                   sensor_data['CameraRGB'].data/255*2-1,
                                   sensor_data['CameraRGB'].data/255*2-1],-1)
    else:
        state_2D = np.concatenate([sensor_data['CameraRGB'].data/255*2-1,np.array(prev_state['state_2D'][:,:,0:-3])],-1)

    state={'state_1D':state_1D,
           'state_2D':state_2D}

    return state

#Caculate the reward for the subtask drive in lane
def calculate_goal_reward(state_1D,goal):
    reward = 0

    success=np.array([
        np.linalg.norm(state_1D[:,0:3]-goal[0:3],axis=1) < 1,
        np.linalg.norm(state_1D[:,3:6]-goal[3:6],axis=1) < 0.1]);

    reward = np.all(success,axis=0)*10

    return reward,np.all(reward>0)
#TODO TURN AORUND
#TODO OVERCOME
#TODO AVOID

#Calculate the general reward (collision,leaving the lane, etc.)
def calculate_general_reward(measurements,collisions):
    reward = 0
    player_measurements = measurements.player_measurements

#   Collisions
    ped_coeff = 0.3
    car_coeff = 0.2
    other_coeff = 0.1

    new_collisions={'car':player_measurements.collision_vehicles,
                    'ped':player_measurements.collision_pedestrians,
                    'other':player_measurements.collision_other
                    }

    col_cars = new_collisions['car'] - collisions['car']
    col_ped = new_collisions['ped'] - collisions['ped']
    col_other = new_collisions['other'] - collisions['other']
    col_punishment = np.clip(col_ped * ped_coeff + col_cars*car_coeff + col_other*other_coeff,
                             0.,
                             10.)
    reward -= col_punishment

#NOTE Itt is differenciálisan kéne büntetni
#   Leaving the lane
    offroad_coeff = 1
    other_lane_coeff = 1
    other_lane = player_measurements.intersection_otherlane,
    offroad = player_measurements.intersection_offroad,
    offlane_punishment = offroad * offroad_coeff + other_lane * other_lane_coeff
    offlane_punishment = np.asarray(offlane_punishment).sum()
    offlane_punishment = np.clip(offlane_punishment,0.,2.)

    reward-=offlane_punishment
#TODO Standstill punishment
    standstill_coeff = 1.0
    standstill_limit = 5.00

    standstill_punishment = (player_measurements.forward_speed*3.6<standstill_limit) * standstill_coeff
    reward-=standstill_punishment
#TODO Exceeding speed limit
#TODO Crossing a red semaphore
    return reward, new_collisions

def print_measurements(measurements):
    number_of_agents = len(measurements.non_player_agents)
    player_measurements = measurements.player_measurements
    message = 'Vehicle at ({pos_x:.1f}, {pos_y:.1f}), '
    message += '{speed:.2f} km/h, '
    message += 'Collision: {{vehicles={col_cars:.0f}, pedestrians={col_ped:.0f}, other={col_other:.0f}}}, '
    message += '{other_lane:.0f}% other lane, {offroad:.0f}% off-road, '
    message += '({agents_num:d} non-player agents in the scene)'
    message = message.format(
        pos_x=player_measurements.transform.location.x / 100, # cm -> m
        pos_y=player_measurements.transform.location.y / 100,
        speed=player_measurements.forward_speed,
        col_cars=player_measurements.collision_vehicles,
        col_ped=player_measurements.collision_pedestrians,
        col_other=player_measurements.collision_other,
        other_lane=100 * player_measurements.intersection_otherlane,
        offroad=100 * player_measurements.intersection_offroad,
        agents_num=number_of_agents)
    print_over_same_line(message)

def main():
    argparser = argparse.ArgumentParser(description=__doc__)
    argparser.add_argument(
        '-v', '--verbose',
        action='store_true',
        dest='debug',
        help='print debug information')
    argparser.add_argument(
        '--host',
        metavar='H',
        default='152.66.240.18',
        help='IP of the host server (default: localhost)')
    argparser.add_argument(
        '-p', '--port',
        metavar='P',
        default=4000,
        type=int,
        help='TCP port to listen to (default: 2000)')
    argparser.add_argument(
        '-a', '--autopilot',
        action='store_true',
        help='enable autopilot')
    argparser.add_argument(
        '-l', '--lidar',
        action='store_true',
        help='enable Lidar')
    argparser.add_argument(
        '-q', '--quality-level',
        choices=['Low', 'Epic'],
        type=lambda s: s.title(),
        default='Low',
        help='graphics quality level, a lower level makes the simulation run considerably faster.')
    argparser.add_argument(
        '-i', '--images-to-disk',
        action='store_true',
        dest='save_images_to_disk',
        help='save images (and Lidar data if active) to disk')
    argparser.add_argument(
        '-c', '--carla-settings',
        metavar='PATH',
        dest='settings_filepath',
        default=None,
        help='Path to a "CarlaSettings.ini" file')

    args = argparser.parse_args()

    log_level = logging.DEBUG if args.debug else logging.INFO
    logging.basicConfig(format='%(levelname)s: %(message)s', level=log_level)

    logging.info('listening to server %s:%s', args.host, args.port)

    args.out_filename_format = '_out/episode_{:0>4d}/{:s}/{:0>6d}'

    while True:
        try:

            run_carla_client(args)

            print('Done.')
            return

        except TCPConnectionError as error:
            logging.error(error)
            time.sleep(1)

if __name__ == '__main__':

    try:
        main()
        print("Ready")
    except KeyboardInterrupt:
        print('\nCancelled by user. Bye!')


from collections import deque

class Cave:
    def __init__(self, name: str):
        self.name = name
        self.connected_to: set[Cave] = set()
        self.visited = False

    def find_all_paths_part_a(self, to, path=None):
        if path is None:
            path = deque()
        count = 0
        self.visit()
        path.append(self.name)
        for cave in self.connected_to:
            if cave.name == to:
                count += 1
            else:
                if cave.can_visit():
                    count += cave.find_all_paths_part_a(to, path)
        self.un_visit()
        path.pop()
        return count

    def find_all_paths_part_b(self, to, path=None, all_paths=None):
        if all_paths is None:
            all_paths = set()
        if path is None:
            path = deque()
        self.visit()
        path.append(self.name)
        for cave in self.connected_to:
            if cave.name == to:
                all_paths.add(str(path))
            else:
                if cave.can_visit():
                    ap = cave.find_all_paths_part_b(to, path, all_paths)
                    for _cave in ap:
                        all_paths.add(_cave)
        self.un_visit()
        path.pop()
        return all_paths

    def can_visit(self):
        return True

    def visit(self):
        self.visited = True

    def un_visit(self):
        self.visited = False

    def __eq__(self, other):
        return self.name == other.name

    def __hash__(self):
        return self.name.__hash__()

class SmallCave(Cave):
    def __init__(self, name: str):
        super().__init__(name)
        self.visit_tickets = 1

    def visit(self):
        super().visit()
        self.visit_tickets -= 1

    def un_visit(self):
        super().un_visit()
        self.visit_tickets += 1

    def can_visit(self):
        return self.visit_tickets > 0

def part_a(start_cave: Cave):
    return start_cave.find_all_paths_part_a("end")

def part_b(start_cave: Cave, caves):
    all_paths = set()
    for cave in caves.values():
        if cave.name != "start" and cave.name != "end":
            print(f"on cave: {cave.name}")
            cave.un_visit()
            paths = start_cave.find_all_paths_part_b("end")
            cave.visit()
            for _cave in paths:
                all_paths.add(_cave)
    return len(all_paths)

def make_cave(name: str) -> Cave:
    if name.isupper():
        return Cave(name)
    else:
        return SmallCave(name)

def main(file) -> tuple:
    caves: dict[str, Cave] = dict()
    with open(file) as f:
        for line in f:
            left, right = line[:-1].split("-")
            if left in caves and right in caves:
                caves[left].connected_to.add(caves[right])
                caves[right].connected_to.add(caves[left])
            elif left in caves:
                right_cave = make_cave(right)
                caves[left].connected_to.add(right_cave)
                caves[right] = right_cave
                caves[right].connected_to.add(caves[left])
            elif right in caves:
                left_cave = make_cave(left)
                caves[right].connected_to.add(left_cave)
                caves[left] = left_cave
                caves[left].connected_to.add(caves[right])
            else:
                left_cave = make_cave(left)
                right_cave = make_cave(right)
                left_cave.connected_to.add(right_cave)
                right_cave.connected_to.add(left_cave)
                caves[left] = left_cave
                caves[right] = right_cave

    return part_a(caves["start"]), part_b(caves["start"], caves)

if __name__ == '__main__':
    import time
    file_name = "../input.txt"
    start_time = time.perf_counter()
    a, b = main(file_name)
    end_time = time.perf_counter()
    print(f"Result part_a: {a}")
    print(f"Result part_b: {b}")
    print(f"Time: {(end_time - start_time):4f} sec")


import tensorflow as tf

import os
import re
import gc
import json
import glob
import codecs
import csv
import random
import string
import numpy as np
import pandas as pd
import collections
import xml.etree.ElementTree as ET
from pathlib import Path
from tqdm import tqdm
from pickle import dump,load
from ..utils import Display

from dataclasses import dataclass

@dataclass
class Box:
  class_index: int
  class_name: str
  corners: np.ndarray

  def __repr__(self):
    return "[class=%s (%f,%f,%f,%f)]" % (self.class_name, self.corners[0], self.corners[1], self.corners[2], self.corners[3])

  def __str__(self):
    return repr(self)

class Dataset:
  def __init__(self, config, verbose = True):
    self.verbose = verbose
    self.config  = config

    # Run-time environment
    cuda_available = tf.test.is_built_with_cuda()
    gpu_available  = tf.config.list_physical_devices('GPU')
    print("CUDA Available : %s" % ("yes" if cuda_available else "no"))
    print("GPU Available  : %s" % ("yes" if gpu_available else "no"))
    print("Eager Execution: %s" % ("yes" if tf.executing_eagerly() else "no"))

  def df_load_descriptions(self, df, title='[title]', number_of_captions = 5):
    mapping = dict()
    for i, r in tqdm(df.iterrows(), total=df.shape[0], desc=title):
      image_id = r['image_id']
      caption  = r['caption']

      if image_id not in mapping:
        mapping[image_id] = list()

      if len(mapping[image_id]) >= number_of_captions:
        continue
      mapping[image_id].append(caption)

    #self.clean_descriptions (mapping) // english only

    drops = list()
    for k, v in mapping.items():
      if not os.path.isfile(k):
        drops.append(k)
        print ('pop (not os.path.isfile): ', k)
      elif len(v) != number_of_captions:
        drops.append(k)
        print ('pop (number_of_captions): ', k, len(v))

    for k in drops:
      mapping.pop(k)

    return mapping

  def clean_descriptions(self, descriptions):
    table = str.maketrans('', '', string.punctuation)
    for key, desc_list in descriptions.items():
      for i in range(len(desc_list)):
        desc = desc_list[i]
        desc = desc.split()
        desc = [word.lower() for word in desc]
        desc = [w.translate(table) for w in desc]
        desc = [word for word in desc if len(word)>1]
        desc = [word for word in desc if word.isalpha()]
        desc_list[i] =  ' '.join(desc)

  def to_vocabulary(self, descriptions):
    all_desc = set()
    for key in descriptions.keys():
      [all_desc.update(d.split()) for d in descriptions[key]]
    return all_desc

class DatasetVoc:
  def __init__(self, split, dir = "datasets/VOCdevkit/VOC2007", feature_pixels = 16, augment = True, shuffle = True, allow_difficult = False, cache = True):

    self.split = split
    self.dir   = dir
    self.class_index_to_name = self.get_classes (self.split)
    self.class_name_to_index = { class_name: class_index for (class_index, class_name) in self.class_index_to_name.items() }
    self.num_classes = len(self.class_index_to_name)
    self.filepaths = self.get_filepaths (self.split)
    self.num_samples = len(self.filepaths)
    self.gt_boxes_by_filepath = self.get_ground_truth_boxes(filepaths = self.filepaths, allow_difficult = False)
    self.i = 0
    self.iterable_filepaths = self.filepaths.copy()
    self.feature_pixelsa = feature_pixels
    self.augment = True
    self.augment = True
    self.shuffle = True
    self.cache   = True
    self.unaugmented_cached_sample_by_filepath = {}
    self.augmented_cached_sample_by_filepath = {}

  def __iter__(self):
    self.i = 0
    if self.shuffle:
      random.shuffle(self.iterable_filepaths)
    return self

  def __next__(self):
    if self.i >= len(self.iterable_filepaths):
      raise StopIteration

    filepath = self.iterable_filepaths[self.i]
    self.i += 1

    flip = random.randint(0, 1) != 0 if self.augment else 0
    cached_sample_by_filepath = self.augmented_cached_sample_by_filepath if flip else self.unaugmented_cached_sample_by_filepath

    if filepath in cached_sample_by_filepath:
      sample = cached_sample_by_filepath[filepath]
    else:
      sample = self.generate_training_sample(filepath = filepath, flip = flip)
    if self.cache:
      cached_sample_by_filepath[filepath] = sample

    return sample

  def generate_training_sample(self, filepath, flip):
    scaled_image_data, scaled_image, scale_factor, original_shape = image.load_image(url = filepath, min_dimension_pixels = 600, horizontal_flip = flip)
    _, original_height, original_width = original_shape

    scaled_gt_boxes = []
    for box in self.gt_boxes_by_filepath[filepath]:
      if flip:
        corners = np.array([
          box.corners[0],
          original_width - 1 - box.corners[3],
          box.corners[2],
          original_width - 1 - box.corners[1]
        ])
      else:
        corners = box.corners
      scaled_box = Box(
        class_index = box.class_index,
        class_name = box.class_name,
        corners = corners * scale_factor
      )
      scaled_gt_boxes.append(scaled_box)

    anchor_map, anchor_valid_map = anchors.generate_anchor_maps(image_shape = scaled_image_data.shape, feature_pixels = self.feature_pixels)
    gt_rpn_map, gt_rpn_object_indices, gt_rpn_background_indices = anchors.generate_rpn_map(anchor_map = anchor_map, anchor_valid_map = anchor_valid_map, gt_boxes = scaled_gt_boxes)

    return TrainingSample(
      anchor_map = anchor_map,
      anchor_valid_map = anchor_valid_map,
      gt_rpn_map = gt_rpn_map,
      gt_rpn_object_indices = gt_rpn_object_indices,
      gt_rpn_background_indices = gt_rpn_background_indices,
      gt_boxes = scaled_gt_boxes,
      image_data = scaled_image_data,
      image = scaled_image,
      filepath = filepath
    )

  def get_classes (self, split):
    dir = os.path.join(self.dir, "ImageSets", "Main")
    classes = set([ os.path.basename(path).split("_")[0] for path in Path(dir).glob("*_" + split + ".txt") ])
    class_index_to_name = { (1 + v[0]): v[1] for v in enumerate(sorted(classes)) }
    class_index_to_name[0] = "background"
    return class_index_to_name

  def get_filepaths(self, split):
    dir = os.path.join(self.dir, "ImageSets", "Main", split + ".txt")
    with open(dir) as f:
      basenames = [ line.strip() for line in f.readlines() ] # strip newlines
    image_paths = [ os.path.join(self.dir, "JPEGImages", basename) + ".jpg" for basename in basenames ]
    return image_paths

  def get_ground_truth_boxes(self, filepaths, allow_difficult):
    gt_boxes_by_filepath = {}
    for filepath in filepaths:
      basename = os.path.splitext(os.path.basename(filepath))[0]
      annotation_file = os.path.join(self.dir, "Annotations", basename) + ".xml"
      tree = ET.parse(annotation_file)
      root = tree.getroot()
      size = root.find("size")
      depth = int(size.find("depth").text)
      boxes = []
      for obj in root.findall("object"):
        is_difficult = int(obj.find("difficult").text) != 0
        if is_difficult and not allow_difficult:
          continue  # ignore difficult examples unless asked to include them
        class_name = obj.find("name").text
        bndbox = obj.find("bndbox")
        x_min = int(bndbox.find("xmin").text) - 1  # convert to 0-based pixel coordinates
        y_min = int(bndbox.find("ymin").text) - 1
        x_max = int(bndbox.find("xmax").text) - 1
        y_max = int(bndbox.find("ymax").text) - 1
        corners = np.array([ y_min, x_min, y_max, x_max ]).astype(np.float32)
        box = Box(class_index = self.class_name_to_index[class_name], class_name = class_name, corners = corners)
        boxes.append(box)
      gt_boxes_by_filepath[filepath] = boxes
    return gt_boxes_by_filepath

  def get_data (self):
    self.get_categories('datasets/NIA/annotations/4-2/', False, 2)

  def get_categories (self, dir, force = False, indent = None):
    name  = self.config['name']
    cfile = 'files/{}_categories.json'.format(name)

    if force or not os.path.isfile(cfile):
      categories = dict()
      for filename in tqdm(glob.glob(dir + '*.json')):
        object = json.load(codecs.open(filename, 'r', 'utf-8-sig'))

        for c in object['categories']:
          cid   = c['id']
          cname = c['name']
          if not cid in categories:
            categories[cid] = cname

      json.dump(categories, open(cfile, 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)

    else:
      categories = json.load(codecs.open(cfile, 'r', 'utf-8-sig'))

    print (categories)
    print (categories['2'])
    return categories

class flickr8k (Dataset):
  def __init__(self, config, verbose = True, force = True, indent = None):
    super().__init__ (config, verbose)

    name    = config['name']
    images  = config['images_dir']
    caption = config['caption_file']
    trains  = config['train_file']
    valids  = config['valid_file']
    tests   = config['test_file']
    limit1  = config['train_limit']
    limit2  = config['valid_limit']
    limit3  = config['test_limit']
    number_of_captions    = config['number_of_captions']

    self.get_data (name, images, caption, trains, valids, tests, limit1, limit2, limit3, force, number_of_captions, indent)

  def get_data (self, name, images, caption, trains, valids, tests, limit1, limit2, limit3, force = True, number_of_captions = 5, indent = None):
    if not force and os.path.isfile('files/{}_descr.json'.format(name)):
      return

    df  = pd.read_csv(caption, lineterminator='\n', names=['image_id', 'caption'], sep='\t')
    df1 = pd.read_csv(trains,  lineterminator='\n', names=['image_id'])
    df2 = pd.read_csv(valids,  lineterminator='\n', names=['image_id'])
    df3 = pd.read_csv(tests,   lineterminator='\n', names=['image_id'])
    df.loc[:,'image_id']  = images[0]+'/'+df.image_id.str.split('#').str[0]
    df1.loc[:,'image_id'] = images[0]+'/'+df1.image_id.str.split('#').str[0]
    df2.loc[:,'image_id'] = images[0]+'/'+df2.image_id.str.split('#').str[0]
    df3.loc[:,'image_id'] = images[0]+'/'+df3.image_id.str.split('#').str[0]
    df1 = df.loc[df['image_id'].isin(list(df1.image_id))]
    df2 = df.loc[df['image_id'].isin(list(df2.image_id))]
    df3 = df.loc[df['image_id'].isin(list(df3.image_id))]

    ds  = self.df_load_descriptions (df,  'Descriptions ', number_of_captions)
    vo  = self.to_vocabulary(ds)
    dx  = list(df['caption'])            # list for caption data
    ds1 = self.df_load_descriptions (df1, 'Train dataset', number_of_captions)
    ds1 = dict(list(ds1.items())[:limit1])
    ds2 = self.df_load_descriptions (df2, 'Valid dataset', number_of_captions)
    ds2 = dict(list(ds2.items())[:limit2])
    ds3 = self.df_load_descriptions (df3, 'Test dataset ', number_of_captions)
    ds3 = dict(list(ds3.items())[:limit3])

    print('Loaded: {}/df#{} (train={} valid={}, test={}, vocabulary={})'.format(len(ds), df.shape[0], len(ds1), len(ds2), len(ds3), len(vo)))

    json.dump(ds, open('files/{}_descr.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds1,open('files/{}_train.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds2,open('files/{}_valid.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds3,open('files/{}_test.json'.format(name),  'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(dx, open('files/{}_text.json'.format(name),  'w', encoding='utf-8'), indent=indent, ensure_ascii=False)

class nia (Dataset):
  def __init__(self, config, verbose = True, force = True, indent = None):
    super().__init__ (config, verbose)

    name    = config['name']
    data    = config['data_dir']
    jsons   = config['json_dir']
    images  = config['images_dir']
    caption = config['caption_file']
    trains  = config['train_file']
    valids  = config['valid_file']
    tests   = config['test_file']
    limit1  = config['train_limit']
    limit2  = config['valid_limit']
    limit3  = config['test_limit']
    split1  = config['train_val_split']
    split2  = config['val_test_split']
    number_of_captions    = config['number_of_captions']

    self.get_label (name[:-1], data, jsons, images[0], split1, split2, force)
    self.get_data  (name, images, caption, trains, valids, tests, limit1, limit2, limit3, force, number_of_captions, indent)

  def get_label (self, name, data_dir, json_dir, image_dir, split1, split2, force):
    if not force and os.path.isfile('{}/{}.token.kor.txt'.format(data_dir, name)):
      return

    if name == 'nia0404':
      frelation = open('{}/{}.relation.txt'.format(data_dir, name), 'w')
    fetoken   = open('{}/{}.token.eng.txt'.format(data_dir, name),  'w')
    fktoken   = open('{}/{}.token.kor.txt'.format(data_dir, name),  'w')
    fimages   = open('{}/{}.images.txt'.format(data_dir, name),     'w')
    ftrain    = open('{}/{}.trainImages.txt'.format(data_dir, name),'w')
    fvalid    = open('{}/{}.devImages.txt'.format(data_dir, name),  'w')
    ftest     = open('{}/{}.testImages.txt'.format(data_dir, name), 'w')

    images = []

    for file in tqdm(glob.glob(json_dir + '/*.json')):
      with open(file, encoding='utf-8-sig') as f:
        try:
          data = json.load(f)
          filename = data['images'][0]['file_name']
          image = filename.split('.')[0]
          if not os.path.isfile(image_dir + '/' + filename):
            print ('not found: ' + image_dir + filename + ' => ' + file)
            continue

          images.append ('{}.jpg'.format(image))
          for idx,caption in enumerate(data['annotations'][0]['text']):
            if name == 'nia0404':
              frelation.write ("{}.jpg#{}\t{}\t{}\t{}\n".format(image, idx,caption['entity1'],caption['entity2'],caption['relation']))
            fetoken.write ("{}.jpg#{}\t{}\n".format(image, idx,caption['english']))
            fktoken.write ("{}.jpg#{}\t{}\n".format(image, idx,caption['korean']))
          if idx != 9:
            print (idx, file)
        except JSONDecodeError as e:
          print ('Decoding JSON has failed: ', file, e)

    random.shuffle(images)
    fimages.write('\n'.join(images))

    random.shuffle(images)
    nsize  = len(images)
    ntrain = int(nsize*split1)
    nvalid = int((nsize - int(nsize*split1))*split2)
    ntest = nsize - ntrain - nvalid
    print (nsize, ntrain, nvalid, ntest)

    train = images[:ntrain]
    valid = images[ntrain:ntrain+nvalid]
    test  = images[ntrain+nvalid:]

    print(len(train), len(valid), len(test))

    ftrain.write('\n'.join(train))
    fvalid.write('\n'.join(valid))
    ftest.write('\n'.join(test))

    if name == 'nia0404':
      frelation.close()
    fimages.close()
    fetoken.close()
    fktoken.close()
    ftrain.close()
    fvalid.close()
    ftest.close()

  def get_data (self, name, images, caption, trains, valids, tests, limit1, limit2, limit3, force, number_of_captions, indent):
    if not force and os.path.isfile('files/{}_descr.json'.format(name)):
      return

    df  = pd.read_csv(caption, lineterminator='\n', names=['image_id', 'caption'], sep='\t')
    df1 = pd.read_csv(trains,  lineterminator='\n', names=['image_id'])
    df2 = pd.read_csv(valids,  lineterminator='\n', names=['image_id'])
    df3 = pd.read_csv(tests,   lineterminator='\n', names=['image_id'])
    df.loc[:,'image_id']  = images[0]+'/'+df.image_id.str.split('#').str[0]
    df1.loc[:,'image_id'] = images[0]+'/'+df1.image_id.str.split('#').str[0]
    df2.loc[:,'image_id'] = images[0]+'/'+df2.image_id.str.split('#').str[0]
    df3.loc[:,'image_id'] = images[0]+'/'+df3.image_id.str.split('#').str[0]
    df1 = df.loc[df['image_id'].isin(list(df1.image_id))]
    df2 = df.loc[df['image_id'].isin(list(df2.image_id))]
    df3 = df.loc[df['image_id'].isin(list(df3.image_id))]

    print (df.head())
    ds  = self.df_load_descriptions (df,  'Descriptions ', number_of_captions)
    vo  = self.to_vocabulary(ds)
    dx  = list(df['caption'])            # list for caption data

    ds1 = self.df_load_descriptions (df1, 'Train dataset', number_of_captions)
    ds1 = dict(list(ds1.items())[:limit1])
    ds2 = self.df_load_descriptions (df2, 'Valid dataset', number_of_captions)
    ds2 = dict(list(ds2.items())[:limit2])
    ds3 = self.df_load_descriptions (df3, 'Test dataset ', number_of_captions)
    ds3 = dict(list(ds3.items())[:limit3])

    print('Loaded: {}/df#{} (train={} valid={}, test={}, vocabulary={})'.format(len(ds), df.shape[0], len(ds1), len(ds2), len(ds3), len(vo)))

    json.dump(ds, open('files/{}_descr.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds1,open('files/{}_train.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds2,open('files/{}_valid.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds3,open('files/{}_test.json'.format(name),  'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(dx, open('files/{}_text.json'.format(name),  'w', encoding='utf-8'), indent=indent, ensure_ascii=False)

class coco (Dataset):

  def __init__(self, config, verbose = True, force = True, indent = None):
    super().__init__ (config, verbose)

    name   = config['name']
    images = config['images_dir']
    trains = config['train_file']
    valids = config['valid_file']
    limit1 = config['train_limit']
    limit2 = config['valid_limit']
    split  = config['val_test_split']
    number_of_captions    = config['number_of_captions']

    self.get_data (name, images, trains, valids, limit1, limit2, split, force, number_of_captions, indent)

  def get_data (self, name, images, trains, valids, limit1, limit2, split, force = False, number_of_captions = 5, indent = None):
    if not force and os.path.isfile('files/{}_descr.json'.format(name)):
      return

    if name == 'coco2014':
      df1 = pd.DataFrame(list(pd.read_json(trains, lines=True).annotations[0]))
      df1.loc[:,'image_id'] = images[0] + '/COCO_train2014_'+df1['image_id'].map('{:012d}'.format) +'.jpg'
      df2 = pd.DataFrame(list(pd.read_json(valids, lines=True).annotations[0]))
      df2.loc[:,'image_id'] = images[1] + '/COCO_val2014_'+df2['image_id'].map('{:012d}'.format) +'.jpg'
    else:
      df1 = pd.DataFrame(list(pd.read_json(trains, lines=True).annotations[0]))
      df1.loc[:,'image_id'] = images[0] + '/'+df1['image_id'].map('{:012d}'.format) + '.jpg'
      df2 = pd.DataFrame(list(pd.read_json(valids, lines=True).annotations[0]))
      df2.loc[:,'image_id'] = images[1] + '/'+df2['image_id'].map('{:012d}'.format) + '.jpg'

    df  = pd.concat([df1, df2])
    ds  = self.df_load_descriptions (df,  'Descriptions ', number_of_captions)
    vo  = self.to_vocabulary(ds)
    dx  = list(df['caption'])            # list for caption data

    ds1 = self.df_load_descriptions (df1, 'Train dataset', number_of_captions)
    ds1 = dict(list(ds1.items())[:limit1])

    dst = self.df_load_descriptions (df2, 'Valid dataset', number_of_captions)
    dst = dict(list(dst.items())[:limit2])
    ds2 = dict(list(dst.items())[:int(len(dst)*split)])
    ds3 = dict(list(dst.items())[int(len(dst)*split):])

    print('Loaded: {}/df#{} (train={} valid={}, test={}, vocabulary={})'.format(len(ds), df.shape[0], len(ds1), len(ds2), len(ds3), len(vo)))

    json.dump(ds, open('files/{}_descr.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds1,open('files/{}_train.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds2,open('files/{}_valid.json'.format(name), 'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(ds3,open('files/{}_test.json'.format(name),  'w', encoding='utf-8'), indent=indent, ensure_ascii=False)
    json.dump(dx, open('files/{}_text.json'.format(name),  'w', encoding='utf-8'), indent=indent, ensure_ascii=False)

class voc (Dataset):

  def __init__(self, config, verbose = True, force = True, indent = None):
    super().__init__ (config, verbose)

    train_data = DatasetVoc(dir = config['dataset_dir'], split = 'trainval', augment = True,  shuffle = True,  cache = True )
    eval_data  = DatasetVoc(dir = config['dataset_dir'], split = 'test',     augment = False, shuffle = False, cache = False)
    dump(train_data, open('files/{}_rpn_train.pkl'.format(config['name']), 'wb'))
    dump(eval_data,  open('files/{}_rpn_eval.pkl'.format(config['name']),  'wb'))


""""""
import json
import time

from flask import Blueprint, jsonify, request
from ..services import components, component_data, units
from ..helpers import cleanQueryArgs, trace

bp = Blueprint('search', __name__,
               url_prefix='/hardware/search')

def compare_values(op, value1, value2, units1, units2, debug=False):
    """ Compare two values, using the given op and provided units.
    """
    if value1 is None or value2 is None:
        trace("Value1 or Value2 are None", debug)
        return False

    try:
        u1 = units.first(name=units1)
        u2 = units.first(name=units2)

        value1 = float(value1) * (u1.conversion_to_SI if u1 else 1)
        value2 = float(value2) * (u2.conversion_to_SI if u2 else 1)
        trace("Comparing %s(%s) %s %s(%s)" %
              (value1, u1.name, op, value2, u2.name), debug)

        if op == 'gt' or op == '>':
            return value1 > value2
        elif op == 'gte' or op == '>=':
            return value1 >= value2
        elif op == 'lt' or op == '<':
            return value1 < value2
        elif op == 'lte' or op == '<=':
            return value1 <= value2
        elif op == 'ne' or op == 'neq' or op == '!=':
            return value1 != value2
        else:
            return value1 == value2
    except:
        trace("comparison error", debug)
        return False

@bp.route('/', methods=["GET", "POST"])
def search():
    """ Return list of components based upon search criteria.

    """
    start = time.clock()

    # Parse the query string
    params = cleanQueryArgs(request.args.to_dict())  \
        if request.method == 'GET' \
        else request.get_json(force=True)
    debug = params.pop('debug', False)

    trace("params = %s" % params, debug)

    ids = []        # contains ids of components garnered from component_data
    results = []    # contains components filtered by all parameters.

    # pull out the 'q' parameter.
    q = params.pop('q', "")

    # Process filters from q parameter.
    if q:

        # parse the filters using either a GET or POST request.
        filters = json.loads(
            q)['filters'] if request.method == 'GET' else q['filters']
        filter_length = len(filters)
        counter = 0

        while counter < filter_length:
            trace("counter = %s" % counter, debug)

            # find component parameters based on the name.
            comp_data = component_data.find(
                field_name=filters[counter]['name'])
            trace("found %s parameters with name '%s'" %
                  (comp_data.count(), filters[counter]['name']), debug)

            # We should do adjacent comparison if there enough filters to
            # check for and the "name" of the filter is the same as the
            # adjacent filter.
            do_adjacent_comparisons =  \
                counter + 1 < filter_length and \
                filters[counter]['name'] == filters[counter + 1]['name']

            for result in comp_data:

                # Testing a range of filters temporarily using
                # unconverted field_value. will change to
                # field_value_SI once the conversions are complete
                compare1 = compare_values(filters[counter]['op'],
                                          result.field_value,
                                          filters[counter]['value'],
                                          result.units.name if result.units else "",
                                          filters[counter]['units'])
                compare2 = compare_values(filters[counter + 1]['op'],
                                          result.field_value,
                                          filters[counter + 1]['value'],
                                          result.units.name if result.units else "",
                                          filters[counter + 1]['units']
                                          ) if do_adjacent_comparisons else True

                if compare1 and compare2:
                    ids.append(result.component_id)
                    trace("Added %s to ids from attribute id = %s" %
                          (result.component_id, result.id), debug)

            # skip to the next filter or next 2 filters.
            counter += 1
            if do_adjacent_comparisons:
                counter += 1

    # Search for components by meta parameters and by ids
    trace("ids:%s and params:%s" % (ids, params), debug)
    if len(ids) > 0 or len(params) > 0:
        query = components.find(ids=ids, **params)
        trace("sql: %s\n recordcount: %s" % (query, query.count()), debug)

        for comp in query:
            results.append(comp.to_dict({'component_type': True,
                                         'subsystem': True,
                                         'id': True,
                                         'manufacturer': True,
                                         'attributes': debug,
                                         'vendor': True}))

    return jsonify({'count':        len(results),
                    'elapsed':      time.clock() - start,
                    'searched':     results,
                    })

@bp.route('/test', methods=["GET", "POST"])
def test_search():
    """ test searching.
    """


import argparse

import numpy as np
from vispy import app, scene
from vispy.io import imread, load_data_file, read_mesh
from vispy.scene.visuals import Mesh
from vispy.scene import transforms
from vispy.visuals.filters import TextureFilter

parser = argparse.ArgumentParser()
parser.add_argument('--shading', default='smooth',
                    choices=['none', 'flat', 'smooth'],
                    help="shading mode")
args, _ = parser.parse_known_args()

dir = '/Users/liuchu/code_every_day/games102/hw8'
mesh_path = load_data_file('spot/spot.obj.gz')
texture_path = load_data_file('spot/spot.png')
vertices, faces, normals, texcoords = read_mesh(mesh_path)
print(texcoords)
texture = np.flipud(imread(texture_path))

canvas = scene.SceneCanvas(keys='interactive', bgcolor='white',
                           size=(800, 600))
view = canvas.central_widget.add_view()

view.camera = 'arcball'
# Adapt the depth to the scale of the mesh to avoid rendering artefacts.
view.camera.depth_value = 10 * (vertices.max() - vertices.min())

shading = None if args.shading == 'none' else args.shading
mesh = Mesh(vertices, faces, shading=shading, color='white')
mesh.transform = transforms.MatrixTransform()
mesh.transform.rotate(90, (1, 0, 0))
mesh.transform.rotate(135, (0, 0, 1))
mesh.shading_filter.shininess = 1e+1
view.add(mesh)
texture = np.flipud(imread(texture_path))
texture_filter = TextureFilter(texture, texcoords)
mesh.attach(texture_filter)

@canvas.events.key_press.connect
def on_key_press(event):
    if event.key == "t":
        texture_filter.enabled = not texture_filter.enabled
        mesh.update()

def attach_headlight(mesh, view, canvas):
    light_dir = (0, 1, 0, 0)
    mesh.shading_filter.light_dir = light_dir[:3]
    initial_light_dir = view.camera.transform.imap(light_dir)

    @view.scene.transform.changed.connect
    def on_transform_change(event):
        transform = view.camera.transform
        mesh.shading_filter.light_dir = transform.map(initial_light_dir)[:3]

attach_headlight(mesh, view, canvas)

canvas.show()

if __name__ == "__main__":
    app.run()


import time

import pyautogui

# move: 현재 좌표에서 10,100만큼 이동
# pyautogui.move(10,100,duration =1)

# moveTo: 절대 좌표에서 10,100만큼 이동
# pyautogui.moveTo(10,100,duration =1)

# 현재 좌표 가져오기
pos = pyautogui.position()
print(pos)

pyautogui.click(20,1050,duration =1)
time.sleep(1)
pyautogui.click(389,599,duration =1)


"""
Generic tracking and analytics interface
Supports Google Analytics through ga.py, others may follow
"""
import six

from tardis.analytics import ga

service = ga
"""This can become a setting to other service in the future"""

track_login = service.track_login
track_logout = service.track_logout
track_download = service.track_download
# sig: label, session_id, ip, user,
# total_size=None, num_files=None,
# ua=None

class IteratorTracker(object):
    """wraps file iterator to track successful and incomplete downloads"""

    def __init__(self, iterator, tracker_data=None):
        self._iterator = iterator
        self.tracker_data = tracker_data
        self.complete = False

    def __iter__(self):
        return self

    def close(self):
        if hasattr(self._iterator, 'close'):
            self._iterator.close()

    def __next__(self):
        try:
            if six.PY3:
                return self._iterator.__next__()
            return self._iterator.next()
        except StopIteration:
            self.complete = True
            raise StopIteration()

    def next(self):
        return self.__next__()

    def __del__(self):
        if not self.complete:
            self.tracker_data['label'] = self.tracker_data.get(
                'label', '') + '-aborted'
        track_download(**self.tracker_data)


"""
Workflow for running model optimization.
"""
from dataset import Dataset
from model import Model
from optimizer import Optimizer

def run(epochs=5, init_lr=3e-5, model_name="small_bert/bert_en_uncased_L-4_H-512_A-8"):
    """
    Executes the workflow for model optimization.
    """
    dataset = Dataset()
    print(dataset)
    model = Model(model_name)
    opt = Optimizer(dataset, epochs, init_lr)
    model.compile(opt)
    _history = model.fit(dataset, opt)
    model.save("bert.h5")
    # model.save_to_json("bert-json-dir")


#Credits:
#M. Attene. A lightweight approach to repairing digitized polygon meshes.
#The Visual Computer, 2010. (c) Springer. DOI: 10.1007/s00371-010-0416-3
#http://pymeshfix.pyvista.org
#TetGen, a Delaunay-Based Quality Tetrahedral Mesh Generator
#https://github.com/pyvista/tetgen
#
# pip install pymeshfix
# pip install tetgen
#
import pymeshfix
import tetgen
import vedo

vedo.settings.use_depth_peeling = True

amesh = vedo.Mesh(vedo.dataurl+'290.vtk')

# repairing also closes the mesh in a nice way
meshfix = pymeshfix.MeshFix(amesh.points(), amesh.faces())
meshfix.repair()
repaired = vedo.Mesh(meshfix.mesh).linewidth(1).alpha(0.5)

# tetralize the closed surface
tet = tetgen.TetGen(repaired.points(), repaired.faces())
tet.tetrahedralize(order=1, mindihedral=20, minratio=1.5)
tmesh = vedo.TetMesh(tet.grid)

# save it to disk
#tmesh.write("my_tetmesh.vtk")

plt = vedo.Plotter(N=3, axes=1)
plt.at(0).show("Original mesh", amesh)
plt.at(1).show("Repaired mesh", repaired)
plt.at(2).show("Tetrahedral mesh\n(click & press shift-X)", tmesh.tomesh().shrink())
plt.interactive().close()


# pylint:disable=g-multiple-import
"""Creates an environment for the lowest level of a hierarchical framework"""
import sys
import inspect
import os

from hct.envs.goal import Goal
from hct.envs.tools import *
from hct.envs.ant_test import AntTest
from hct.training.configs import NetworkArchitecture, SMALL_TRANSFORMER_CONFIGS, DEFAULT_MLP_CONFIGS
from hct.io import model
from hct.envs.maze.maze_attributes import *

from brax import base, generalized
from brax.envs.base import Env, PipelineEnv, State
from brax.io import mjcf
from brax.kinematics import forward

from etils import epath

import jax
from jax import numpy as jp

from typing import Optional, Literal, Tuple

from absl import logging

class FlatMazeEnv(PipelineEnv):

  def __init__(
    self,
    task_information: bool = False,
    reward_per_milestone = 1,
    reward_sparsity = 1,
    architecture_name='MLP',
    architecture_configs=DEFAULT_MLP_CONFIGS, # trial larger network
    reward_type: Literal['sparse', 'sparse_with_information', 'dense'] = 'sparse',
    reward_movement: Literal['position', 'velocity'] = 'velocity',
    **kwargs
  ):

    frame = inspect.currentframe()
    args, _, _, values = inspect.getargvalues(frame)
    self.parameters = {arg: values[arg] for arg in args}
    self.parameters.pop('self')

    logging.info('Initialising environment...')

    path = epath.resource_path('hct') / f'envs/assets/ant_maze.xml'
    sys = mjcf.load(path)

    n_frames = 5

    backend = 'positional'

    if backend in ['spring', 'positional']:
      sys = sys.replace(dt=0.005)
      n_frames = 10

    if backend == 'positional':
      sys = sys.replace(
        actuator=sys.actuator.replace(
          gear=200 * jp.ones_like(sys.actuator.gear)
        )
      )

    kwargs['n_frames'] = kwargs.get('n_frames', n_frames)

    super().__init__(sys=sys, backend=backend, **kwargs)

    # Agent attributes
    self.dof = jp.array(self.sys.dof.limit).T.shape[0]
    self.num_links = sys.num_links()
    self.link_parents = sys.link_parents
    self.task_information = task_information

    # Reward attributes
    self.reward_milestone = reward_sparsity * reward_per_milestone
    self.reward_sparsity = reward_sparsity
    self.reward_type = reward_type
    self.reward_movement = reward_movement

    self.non_actuator_nodes = 0
    self.action_mask = None
    self.num_nodes = 9
    self.obs_mask = None

    # Network architecture
    self.network_architecture = NetworkArchitecture.create(name=architecture_name, **architecture_configs)
    num_attn_heads = self.network_architecture.configs['policy_num_heads'] if self.network_architecture.name=='Transformer' else 2
    self.max_actions_per_node = 1 if self.network_architecture.name=='Transformer' else 8

    # Observation attributes
    self.root_mask = base.Transform(jp.array([0.0, 0.0, 1.0]), jp.array([1.0, 1.0, 1.0, 1.0]))
    self.state_obs_width = 13
    concat_obs_width = self.state_obs_width
    if concat_obs_width % num_attn_heads != 0:
        self.concat_obs_width = ((concat_obs_width // num_attn_heads) + 1) * num_attn_heads
    else:
        self.concat_obs_width = concat_obs_width

    self.action_repeat = 1
    self.episode_length = 1000
    self.action_shape = (8, 1)

    logging.info('Environment initialised.')

  def reset(self, rng: jp.ndarray) -> State:
    """Resets the environment to an initial state."""
    return reset(self, rng)

  def step(self, state: State, action: jp.ndarray) -> State:
    """Run one timestep of the environment's dynamics."""
    return step(self, state, action)

  def take_action(self, state: State, action: jp.array, rng):
    return self.pipeline_step(state, action), rng

  def get_obs(self, state: base.State) -> jp.ndarray:
    """
    Processes a state and goal into observation format

    Args:
        state: dynamic State object, changing every step
        goal: Goal object, containing egocentric Transform goals
          and/or world frame velocity goals

    Returns:
        obs: (num_links, 13 + goal_size) array containing goal observations:
    """

    def _get_state_obs(state: Union[base.State, Goal]):
      """Returns world root position (masking XY position) and limb position relative to parent"""
      return self._world_to_relative(state)

    sx, sxd =  _get_state_obs(state)
    sx, sxd = concatenate_attrs(sx), concatenate_attrs(sxd)

    obs = jp.concatenate([sx, sxd], axis = -1)

    if self.network_architecture.name == 'MLP':
      obs = obs.reshape(*obs.shape[:-2], -1)

    return obs

  def _world_to_relative(self, state: base.State):
    return world_to_relative(state, self)

'''
def _sample_goal(self, rng: jp.ndarray, state: base.State):
    """
    Samples normalised goal and outputs a goal state

    Goal is restricted to ensure a valid state, and acheive a range of positions
    that are expected to be achieved by the optimal policy. Restictions on
      Z position of all links
      Root rotation
      Number of feet in contact with ground:
        Randomly sample an ordered subset of end effector IDs
        Ensure that these end effectors are in contact with ground

    Args:
      rng: jp.ndarray

    Returns:
      goal: Goal
    """

    rng, rng1, rng2 = jax.random.split(rng, 3)
    randint = jax.random.randint(rng1,shape=(1,), minval = 0, maxval = 4)
    randchoice = jax.random.choice(rng2, jp.array([self.goal_contact_cond, self.goal_z_cond[1]]), shape=(1,), p = jp.array([0.9, 0.1]))
    goal_contact_cond = jp.full((4,), self.goal_z_cond[1]).at[randint].set(randchoice)

    def _create_goal(g: jp.ndarray, state: base.State) -> Goal:
      """
      Creates a goal state

      Args:
        g: jp.ndarray normalised goal
        state: environment state

      Returns:
        Goal: goal state
      """
      q = q_spherical_to_quaternion(g[:self.dof], state, self.sys)
      qd = g[-self.dof:]
      x_world, xd_world = forward(self.sys, q, qd)
      x_world, xd_world = x_world * self.goal_x_mask, xd_world * self.goal_xd_mask
      x_rel, xd_rel = world_to_relative(x_world, self.sys), world_to_relative(xd_world, self.sys)
      x_rel, xd_rel = x_rel * self.goal_x_mask, xd_rel * self.goal_xd_mask
      return Goal(g, q, qd, x_world, x_rel, xd_world, xd_rel)

    def _sample(carry):
      rng, _ = carry
      rng, rng1 = jax.random.split(rng)
      g = jax.random.uniform(
        rng1,
        shape=self.goal_size,
        minval=self.goalsampler_limit[:, 0],
        maxval=self.goalsampler_limit[:, 1]
      )
      goal = _create_goal(g, state)
      return rng, goal

    def _reject(carry):
      rng, goal = carry
      #cpos_foot_z = contact(self.sys, goal.x).pos[foot_contact_idx, 2]
      foot_z = goal.x_world.pos[self.end_effector_idx, 2]
      z = goal.x_world.pos[:,2]
      polar = goal.g[4]
      cond = \
        jp.any(z < self.goal_z_cond[0]) | jp.any(z > self.goal_z_cond[1]) | \
        (polar > self.goal_polar_cond) | \
        jp.any(foot_z > goal_contact_cond)
      return cond

    init_g = jax.random.uniform(
      rng,
      shape=self.goal_size,
      minval=self.goalsampler_limit[:, 0],
      maxval=self.goalsampler_limit[:, 1]
    )

    init_val = rng, _create_goal(init_g, state)
    goal = jax.lax.while_loop(_reject, _sample, init_val)[1]

    return goal

  def _sample_goal(self, rng: jp.ndarray, state: base.State):
    """
    Samples normalised goal and outputs a goal state

    Goal is restricted to ensure a valid state, and acheive a range of positions
    that are expected to be achieved by the optimal policy. Restictions on
      Z position of all links
      Root rotation
      Number of feet in contact with ground:
        Randomly sample an ordered subset of end effector IDs
        Ensure that these end effectors are in contact with ground

    Args:
      rng: jp.ndarray

    Returns:
      goal: Goal
    """

    def _create_goal(g: jp.ndarray, state: base.State) -> Goal:
      """
      Creates a goal state

      Args:
        g: jp.ndarray normalised goal
        state: environment state

      Returns:
        Goal: goal state
      """

      q = q_spherical_to_quaternion(g[:self.dof], state, self.sys)
      qd = g[-self.dof:]
      x_world, xd_world = forward(self.sys, q, qd)
      x_world, xd_world = x_world * self.goal_x_mask, xd_world * self.goal_xd_mask
      x_rel, xd_rel = world_to_relative(x_world, self.sys), world_to_relative(xd_world, self.sys)
      x_rel, xd_rel = x_rel * self.goal_x_mask, xd_rel * self.goal_xd_mask
      return Goal(g, q, qd, x_world, x_rel, xd_world, xd_rel)

    rng, rng1, rng2 = jax.random.split(rng, 3)
    g = jax.random.uniform(
      rng,
      shape=self.goal_size,
      minval=self.goalsampler_limit[:, 0],
      maxval=self.goalsampler_limit[:, 1]
    )
    goal = _create_goal(g, state)
    z = goal.x_world.pos[:,2]
    z_root = z[0] - jp.min(z) + self.goal_z_cond[0]
    z_root = z_root + jax.random.choice(rng1, jp.array([0, 1]), p=jp.array([0.9, 0.1])) * jax.random.uniform(rng2, minval=z_root, maxval=self.goal_z_cond[1]-z_root)

    q = goal.q.at[2].set(z_root)
    qd = goal.qd
    x_world, xd_world = forward(self.sys, q, qd)
    x_world, xd_world = x_world * self.goal_x_mask, xd_world * self.goal_xd_mask
    x_rel, xd_rel = world_to_relative(x_world, self.sys), world_to_relative(xd_world, self.sys)
    x_rel, xd_rel = x_rel * self.goal_x_mask, xd_rel * self.goal_xd_mask

    return Goal(g, q, qd, x_world, x_rel, xd_world, xd_rel)

'''