import pygame
import pytmx
import pyscroll
import time
from math import *
from random import randint

class SpriteSheet():
    def __init__(self,name):
        self.sprite_sheet = pygame.image.load(name)
        self.images =dict()

    def load_images(self ,name,numbers = 1,row = 0):
        images = []
        for i in range(0,numbers):
            image = pygame.Surface([32,32])
            image.blit(self.sprite_sheet,(0,0),(i * 32,32*row,32,32))
            images.append(image)
        self.images.update({name : images})

    def get_images(self,name):
        return self.images[name]

class Player(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super().__init__()
        tmx_data = pytmx.util_pygame.load_pygame("map jeu.tmx")
        self.spawn_point = tmx_data.get_object_by_name("player_spawn")
        self.sprite_sheet= SpriteSheet("player.png")
        self.sprite_sheet.load_images("idle_r",numbers = 8)
        self.sprite_sheet.load_images("idle_l",numbers = 8,row = 1)
        self.sprite_sheet.load_images("run_r",numbers = 8,row = 2)
        self.sprite_sheet.load_images("run_l",numbers = 8,row = 3)
        self.sprite_sheet.load_images("attack_r",numbers = 8,row = 4)
        self.sprite_sheet.load_images("attack_l",numbers = 8,row = 5)
        self.sprite_sheet.load_images("hit_r",numbers = 8,row = 6)
        self.sprite_sheet.load_images("hit_l",numbers = 8,row = 7)
        self.sprite_sheet.load_images("death_r",numbers = 8,row = 8)
        self.sprite_sheet.load_images("death_l",numbers = 8,row = 9)
        self.sprite_sheet.load_images("fall_r",numbers = 8,row = 10)
        self.sprite_sheet.load_images("fall_l",numbers = 8,row = 11)
        self.image = self.sprite_sheet.get_images("idle_r")[0]
        self.rect = self.image.get_rect()
        self.position = [x,y]
        self.feet = pygame.Rect(0,0,self.rect.width,12)
        self.collision = pygame.Rect(0,0,self.rect.width,self.rect.height)
        self.current_animation = "idle_r"
        self.old_position = self.position
        self.current_animation_index = 0
        self.cooldown = -100
        self.cooldown_fall = 0
        self.possible_fall = True
        self.speed = 3
        self.life = 3
        self.alive = True
        self.movable = True
        self.last_view = "r"
        self.play = True

    def move_right(self):
        self.position[0] += self.speed

    def move_left(self):
        self.position[0] -= self.speed

    def move_up(self):
        self.position[1] -= self.speed

    def move_down(self):
        self.position[1] += self.speed

    def move_back(self):
        self.position = self.old_position

    def death(self):
        self.movable = False
        self.current_animation_index = 0
        self.change_animation("death_" + self.last_view)
        self.alive = False

    def fall(self,spawn):
        self.position = spawn
        self.change_animation("idle_" + self.last_view)
        self.possible_fall = True
        self.movable = True
        self.life -= 1
        if self.life > 1:
            print("Vous êtes tombés et il vous reste",self.life,"points de vie")
        else:
            print("Vous êtes tombés et il vous reste",self.life,"point de vie")

    def cool_fall(self):
        if self.cooldown_fall > 0:
            if self.possible_fall == True:
                self.current_animation_index = 0
                self.change_animation("fall_" + self.last_view)
                self.possible_fall = False
                self.movable = False
        if self.cooldown_fall >= 800:
            self.cooldown_fall = 0
            self.fall([self.spawn_point.x,self.spawn_point.y])

    def teleportation(self,spawn):
        self.position = spawn

    def change_animation(self,name):
        self.current_animation = name

    def get_animation(self):
        return self.sprite_sheet.get_images(self.current_animation)

    def animation(self):
        if self.cooldown > 100 :
            self.current_animation_index += 1
            if self.current_animation_index == len(self.get_animation()):
                self.current_animation_index = 0
            self.cooldown = 0

    def save_location(self):
        self.old_position = self.position.copy()

    def update(self):
        if self.alive == False:
            if self.current_animation_index == 7:
                self.play = False
        if self.play == True:
            self.cool_fall()
            self.rect.topleft = self.position
            self.feet.midbottom = self.rect.midbottom
            self.animation()
            self.image = self.get_animation()[self.current_animation_index%8]
            self.image.set_colorkey((0,0,0))
            self.image = pygame.transform.scale(self.image,(50,50))
            self.start = time.time()

class Pnj(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super().__init__()
        self.name="pnj_1"
        self.sprite_sheet = SpriteSheet("female.png")
        self.sprite_sheet.load_images("idle",numbers = 6, row = 0)
        self.current_animation = "idle"
        self.image = self.sprite_sheet.get_images("idle")[0]
        self.position = [x,y]
        self.current_animation_index = 0
        self.cooldown_anim = 0
        self.rect= self.image.get_rect()

    def get_animation(self):
        return self.sprite_sheet.get_images(self.current_animation)

    def update(self):
        self.rect.topleft=self.position
        self.animation()
        self.image=self.get_animation()[self.current_animation_index]
        self.image.set_colorkey((0,0,0))
        self.image = pygame.transform.scale(self.image,(50,50))
        self.start = time.time()

    def animation(self):
        if self.cooldown_anim > 100 :
            self.current_animation_index += 1
            if self.current_animation_index == len(self.get_animation()):
                self.current_animation_index = 0
            self.cooldown_anim = 0

class Cobra(pygame.sprite.Sprite):
    def __init__(self,x,y):
        super().__init__()
        self.name="cobra"
        self.sprite_sheet = SpriteSheet("Cobra.png")
        self.sprite_sheet.load_images("idle_r",numbers = 8, row = 0)
        self.sprite_sheet.load_images("idle_l",numbers = 8, row = 1)
        self.sprite_sheet.load_images("move_r",numbers = 8, row = 2)
        self.sprite_sheet.load_images("move_l",numbers = 8, row = 3)
        self.sprite_sheet.load_images("attack_r",numbers = 8, row = 4)
        self.sprite_sheet.load_images("attack_l",numbers = 8, row = 5)
        self.sprite_sheet.load_images("hit_r",numbers = 8, row = 6)
        self.sprite_sheet.load_images("hit_l",numbers = 8, row = 7)
        self.sprite_sheet.load_images("die_r",numbers = 8, row = 8)
        self.sprite_sheet.load_images("die_l",numbers = 8, row = 9)
        self.current_animation = "idle_r"
        self.image = self.sprite_sheet.get_images("idle_r")[0]
        self.rect= self.image.get_rect()
        self.position = [x,y]
        self.old_position = self.position
        self.current_animation_index = 0
        self.cooldown_anim = 0
        self.cooldown_switch = 0
        self.cooldown_move = 0
        self.speed = 1.7
        self.switch = False
        self.possible_move = {"left":True,"right":True,"up":True,"down":True}
        self.randmove = ""
        self.randmove_time = 2500
        self.last_view = "r"

    def update(self):
        self.rect.topleft = self.position
        self.animation()
        if self.switch == True:
            self.move()
        else:
            self.change_animation("idle_"+self.last_view)
        self.lightswitch()
        self.image=self.get_animation()[self.current_animation_index%8]
        self.image.set_colorkey((0,0,0))
        self.image = pygame.transform.scale(self.image,(50,50))
        self.start = time.time()

    def move(self):
        if self.cooldown_move > 100:
            if self.randmove == "left":
                self.move_left()
            elif self.randmove == "right":
                self.move_right()
            elif self.randmove == "up":
                self.move_up()
            elif self.randmove == "down":
                self.move_down()
            self.cooldown_move = 0

    def lightswitch(self):
        if self.cooldown_switch > self.randmove_time:
            if self.switch == False:
                self.switch = True
            else:
                self.switch = False

            if self.switch==True:
                self.direction_check()
            self.cooldown_switch = 0

    def direction_check(self):
        self.randmove_time = randint(2000,3000)
        movelist = []
        for i in self.possible_move:
            if self.possible_move[i]==True:
                movelist.append(i)
        choice=randint(0,len(movelist)-1)
        self.randmove=movelist[choice]

    def move_right(self):
        self.last_view = "r"
        self.change_animation("move_"+self.last_view)
        self.position[0] += self.speed

    def move_left(self):
        self.last_view = "l"
        self.change_animation("move_"+self.last_view)
        self.position[0] -= self.speed

    def move_up(self):
        self.change_animation("move_"+self.last_view)
        self.position[1] -= self.speed

    def move_down(self):
        self.change_animation("move_"+self.last_view)
        self.position[1] += self.speed

    def move_back(self):
        self.position = self.old_position

    def get_animation(self):
        return self.sprite_sheet.get_images(self.current_animation)

    def animation(self):
        if self.cooldown_anim > 100 :
            self.current_animation_index += 1
            if self.current_animation_index == len(self.get_animation()):
                self.current_animation_index = 0
            self.cooldown_anim = 0

    def change_animation(self,name):
        self.current_animation = name

class Game:
    def __init__(self):
        self.screen=pygame.display.set_mode((1080,720))
        pygame.display.set_caption("Hero Quest")
        # chargement de la carte
        tmx_data = pytmx.util_pygame.load_pygame("map jeu.tmx")
        map_data = pyscroll.data.TiledMapData(tmx_data)
        map_layer=pyscroll.orthographic.BufferedRenderer(map_data, self.screen.get_size())
        # chargement du groupe avec la map
        self.group = pyscroll.PyscrollGroup(map_layer=map_layer , default_layer = 1)
        #récupération de la position du spawn du joueur
        self.spawn_point = tmx_data.get_object_by_name("player_spawn")
        self.spawn_point_bis = tmx_data.get_object_by_name("spawn_2")
        self.sortie = tmx_data.get_object_by_name("player_spawn2")
        #chargement du joueur
        self.player=Player(self.spawn_point.x,self.spawn_point.y)
        self.group.add(self.player)
        #Zoom de la map
        map_layer.zoom = 2.5
        #chargement de toutes les collisions
        self.walls = []
        self.void = []
        self.exit = []
        self.exit2 = []
        #group des monstres
        self.monster = pygame.sprite.Group()
        for obj in tmx_data.objects:
            if obj.name == "collision" :
                self.walls.append(pygame.rect.Rect(obj.x,obj.y,obj.width,obj.height))
        for obj in tmx_data.objects:
            if obj.name == "void" :
                self.void.append(pygame.rect.Rect(obj.x,obj.y,obj.width,obj.height))
        for obj in tmx_data.objects:
            if obj.name == "exit" :
                self.exit.append(pygame.rect.Rect(obj.x,obj.y,obj.width,obj.height))
        for obj in tmx_data.objects:
            if obj.name == "exit2" :
                self.exit2.append(pygame.rect.Rect(obj.x,obj.y,obj.width,obj.height))
        for obj in tmx_data.objects:
            if obj.name == "ennemy" :
                self.cobra = Cobra(obj.x,obj.y)
                self.monster.add(self.cobra)
        for obj in tmx_data.objects:
            if obj.name == "pnj" :
                pnj=Pnj(obj.x, obj.y)
                self.monster.add(pnj)
        #chargement des monstres
        self.group.add(self.monster)

    def handle_input(self):
        pressed = pygame.key.get_pressed()
        if self.player.movable == True:
                if pressed[pygame.K_w]:
                    self.player.move_up()
                    self.player.change_animation("run_"+self.player.last_view)
                elif pressed[pygame.K_s]:
                    self.player.move_down()
                    self.player.change_animation("run_"+self.player.last_view)
                elif pressed[pygame.K_d]:
                    self.player.move_right()
                    self.player.change_animation("run_"+self.player.last_view)
                    self.player.last_view = "r"
                elif pressed[pygame.K_a]:
                    self.player.move_left()
                    self.player.change_animation("run_"+self.player.last_view)
                    self.player.last_view = "l"
                elif pressed[pygame.K_0]:
                    self.player.change_animation("attack_"+self.player.last_view)
                    self.player.last_view = "r"
                elif pressed[pygame.K_1]:
                    self.player.change_animation("attack_"+self.player.last_view)
                    self.player.last_view = "l"
                else:
                    self.player.change_animation("idle_"+self.player.last_view)

    def update(self):
        self.group.update()
        # vérifie la collision avec un mur
        if self.player.feet.collidelist(self.walls) >-1:
            # le joueur revient juste en arrière
            self.player.move_back()
            self.player.change_animation("hit")
            #vérifie la collision avec un trou
        if self.player.feet.collidelist(self.void) >-1:
            if self.player.cooldown_fall == 0:
                self.player.cooldown_fall = 1
            #vérifie si la collision entre le joueur et la sortie dans la grotte
        if self.player.feet.collidelist(self.exit) >-1:
            self.player.teleportation([self.spawn_point_bis.x,self.spawn_point_bis.y])
            print("en cours de développement")
            #vérifie la collision entre le joueur et la sortie dans la forêt
        if self.player.feet.collidelist(self.exit2) >-1:
            self.player.teleportation([self.sortie.x,self.sortie.y])
            print("en cours de développement")

    def run(self):
        running = True
        clock = pygame.time.Clock()
        song = pygame.mixer.Sound("musique _ambiance.ogg")
        song.set_volume(0.008)
        while running:
            #actualisation de la carte
            self.player.save_location()
            self.handle_input()
           # self.release_input()
            self.update()
            self.group.center(self.player.rect)
            self.group.draw(self.screen)
            self.player.cooldown += clock.get_time()
            if self.player.cooldown_fall > 0:
                self.player.cooldown_fall += clock.get_time()
            for monster in self.monster:
                monster.cooldown_anim += clock.get_time()
                if monster.name=="cobra":
                    monster.cooldown_switch += clock.get_time()
                    monster.cooldown_move += clock.get_time()
            if self.player.life <= 0 :
                if self.player.alive == True:
                    self.player.death()
            #actualisation de l'écran
            pygame.display.flip()
            song.play()
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False
            clock.tick(60)
        pygame.quit()

if __name__ == '__main__':
    pygame.init()
    game= Game()
    game.run()


# -*- coding: utf-8 -*-

import io
import os
import sys
import json
import yaml
import fnmatch
import nbformat
import requests
import warnings
import entrypoints

from contextlib import contextmanager

from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_exponential

from . import __version__
from .exceptions import (
    PapermillException,
    PapermillRateLimitException,
    missing_dependency_generator,
    missing_environment_variable_generator,
)
from .log import logger
from .utils import chdir

try:
    from .s3 import S3
except ImportError:
    S3 = missing_dependency_generator("boto3", "s3")

try:
    from .adl import ADL
except ImportError:
    ADL = missing_dependency_generator("azure.datalake.store", "azure")
except KeyError as exc:
    if exc.args[0] == "APPDATA":
        ADL = missing_environment_variable_generator("azure.datalake.store", "APPDATA")
    else:
        raise

try:
    from .abs import AzureBlobStore
except ImportError:
    AzureBlobStore = missing_dependency_generator("azure.storage.blob", "azure")

try:
    from gcsfs import GCSFileSystem
except ImportError:
    GCSFileSystem = missing_dependency_generator("gcsfs", "gcs")

try:
    from pyarrow.fs import HadoopFileSystem, FileSelector
except ImportError:
    HadoopFileSystem = missing_dependency_generator("pyarrow", "hdfs")

try:
    from github import Github
except ImportError:
    Github = missing_dependency_generator("pygithub", "github")

def fallback_gs_is_retriable(e):
    try:
        print(e.code)
        return e.code is None or e.code == 429
    except AttributeError:
        print(e)
        return False

try:
    try:
        # Default to gcsfs library's retry logic
        from gcsfs.retry import is_retriable as gs_is_retriable
    except ImportError:
        from gcsfs.utils import is_retriable as gs_is_retriable
except ImportError:
    gs_is_retriable = fallback_gs_is_retriable

try:
    FileNotFoundError
except NameError:
    FileNotFoundError = IOError

class PapermillIO(object):
    '''
    The holder which houses any io system registered with the system.
    This object is used in a singleton manner to save and load particular
    named Handler objects for reference externally.
    '''

    def __init__(self):
        self.reset()

    def read(self, path, extensions=['.ipynb', '.json']):
        # Handle https://github.com/nteract/papermill/issues/317
        notebook_metadata = self.get_handler(path, extensions).read(path)
        if isinstance(notebook_metadata, (bytes, bytearray)):
            return notebook_metadata.decode('utf-8')
        return notebook_metadata

    def write(self, buf, path, extensions=['.ipynb', '.json']):
        return self.get_handler(path, extensions).write(buf, path)

    def listdir(self, path):
        return self.get_handler(path).listdir(path)

    def pretty_path(self, path):
        return self.get_handler(path).pretty_path(path)

    def reset(self):
        self._handlers = []

    def register(self, scheme, handler):
        # Keep these ordered as LIFO
        self._handlers.insert(0, (scheme, handler))

    def register_entry_points(self):
        # Load handlers provided by other packages
        for entrypoint in entrypoints.get_group_all("papermill.io"):
            self.register(entrypoint.name, entrypoint.load())

    def get_handler(self, path, extensions=None):
        '''Get I/O Handler based on a notebook path

        Parameters
        ----------
        path : str or nbformat.NotebookNode or None
        extensions : list of str, optional
            Required file extension options for the path (if path is a string), which
            will log a warning if there is no match. Defaults to None, which does not
            check for any extensions

        Raises
        ------
        PapermillException: If a valid I/O handler could not be found for the input path

        Returns
        -------
        I/O Handler
        '''
        if path is None:
            return NoIOHandler()

        if isinstance(path, nbformat.NotebookNode):
            return NotebookNodeHandler()

        if extensions:
            if not fnmatch.fnmatch(os.path.basename(path).split('?')[0], '*.*'):
                warnings.warn(
                    "the file is not specified with any extension : " + os.path.basename(path)
                )
            elif not any(
                fnmatch.fnmatch(os.path.basename(path).split('?')[0], '*' + ext)
                for ext in extensions
            ):
                warnings.warn(
                    "The specified file ({}) does not end in one of {}".format(path, extensions)
                )

        local_handler = None
        for scheme, handler in self._handlers:
            if scheme == 'local':
                local_handler = handler

            if path.startswith(scheme):
                return handler

        if local_handler is None:
            raise PapermillException(
                "Could not find a registered schema handler for: {}".format(path)
            )

        return local_handler

class HttpHandler(object):
    @classmethod
    def read(cls, path):
        return requests.get(path, headers={'Accept': 'application/json'}).text

    @classmethod
    def listdir(cls, path):
        raise PapermillException('listdir is not supported by HttpHandler')

    @classmethod
    def write(cls, buf, path):
        result = requests.put(path, json=json.loads(buf))
        result.raise_for_status()

    @classmethod
    def pretty_path(cls, path):
        return path

class LocalHandler(object):
    def __init__(self):
        self._cwd = None

    def read(self, path):
        try:
            with chdir(self._cwd):
                with io.open(path, 'r', encoding="utf-8") as f:
                    return f.read()
        except IOError as e:
            try:
                # Check if path could be a notebook passed in as a
                # string
                json.loads(path)
                return path
            except ValueError:
                # Propagate the IOError
                raise e

    def listdir(self, path):
        with chdir(self._cwd):
            return [os.path.join(path, fn) for fn in os.listdir(path)]

    def write(self, buf, path):
        with chdir(self._cwd):
            dirname = os.path.dirname(path)
            if dirname and not os.path.exists(dirname):
                raise FileNotFoundError("output folder {} doesn't exist.".format(dirname))
            with io.open(path, 'w', encoding="utf-8") as f:
                f.write(buf)

    def pretty_path(self, path):
        return path

    def cwd(self, new_path):
        '''Sets the cwd during reads and writes'''
        old_cwd = self._cwd
        self._cwd = new_path
        return old_cwd

class S3Handler(object):
    @classmethod
    def read(cls, path):
        return "\n".join(S3().read(path))

    @classmethod
    def listdir(cls, path):
        return S3().listdir(path)

    @classmethod
    def write(cls, buf, path):
        return S3().cp_string(buf, path)

    @classmethod
    def pretty_path(cls, path):
        return path

class ADLHandler(object):
    def __init__(self):
        self._client = None

    def _get_client(self):
        if self._client is None:
            self._client = ADL()
        return self._client

    def read(self, path):
        lines = self._get_client().read(path)
        return "\n".join(lines)

    def listdir(self, path):
        return self._get_client().listdir(path)

    def write(self, buf, path):
        return self._get_client().write(buf, path)

    def pretty_path(self, path):
        return path

class ABSHandler(object):
    def __init__(self):
        self._client = None

    def _get_client(self):
        if self._client is None:
            self._client = AzureBlobStore()
        return self._client

    def read(self, path):
        lines = self._get_client().read(path)
        return "\n".join(lines)

    def listdir(self, path):
        return self._get_client().listdir(path)

    def write(self, buf, path):
        return self._get_client().write(buf, path)

    def pretty_path(self, path):
        return path

class GCSHandler(object):
    RATE_LIMIT_RETRIES = 3
    RETRY_DELAY = 1
    RETRY_MULTIPLIER = 1
    RETRY_MAX_DELAY = 4

    def __init__(self):
        self._client = None

    def _get_client(self):
        if self._client is None:
            self._client = GCSFileSystem()
        return self._client

    def read(self, path):
        with self._get_client().open(path) as f:
            return f.read()

    def listdir(self, path):
        return self._get_client().ls(path)

    def write(self, buf, path):
        # Wrapped so we can mock retry options during testing
        @retry(
            retry=retry_if_exception_type(PapermillRateLimitException),
            stop=stop_after_attempt(self.RATE_LIMIT_RETRIES),
            wait=wait_exponential(
                multiplier=self.RETRY_MULTIPLIER, min=self.RETRY_DELAY, max=self.RETRY_MAX_DELAY
            ),
            reraise=True,
        )
        def retry_write():
            try:
                with self._get_client().open(path, 'w') as f:
                    return f.write(buf)
            except Exception as e:
                try:
                    message = e.message
                except AttributeError:
                    message = "Generic exception {} raised".format(type(e))
                if gs_is_retriable(e):
                    raise PapermillRateLimitException(message)
                # Reraise the original exception without retries
                raise

        return retry_write()

    def pretty_path(self, path):
        return path

class HDFSHandler(object):
    def __init__(self):
        self._client = None

    def _get_client(self):
        if self._client is None:
            self._client = HadoopFileSystem(host="default")
        return self._client

    def read(self, path):
        with self._get_client().open_input_stream(path) as f:
            return f.read()

    def listdir(self, path):
        return [f.path for f in self._get_client().get_file_info(FileSelector(path))]

    def write(self, buf, path):
        with self._get_client().open_output_stream(path) as f:
            return f.write(str.encode(buf))

    def pretty_path(self, path):
        return path

class GithubHandler(object):
    def __init__(self):
        self._client = None

    def _get_client(self):
        if self._client is None:
            token = os.environ.get('GITHUB_ACCESS_TOKEN', None)
            if token:
                self._client = Github(token)
            else:
                self._client = Github()
        return self._client

    def read(self, path):
        splits = path.split('/')
        org_id = splits[3]
        repo_id = splits[4]
        ref_id = splits[6]
        sub_path = '/'.join(splits[7:])
        repo = self._get_client().get_repo(org_id + '/' + repo_id)
        content = repo.get_contents(sub_path, ref=ref_id)
        return content.decoded_content

    def listdir(self, path):
        raise PapermillException('listdir is not supported by GithubHandler')

    def write(self, buf, path):
        raise PapermillException('write is not supported by GithubHandler')

    def pretty_path(self, path):
        return path

class StreamHandler(object):
    '''Handler for Stdin/Stdout streams'''
    def read(self, path):
        return sys.stdin.read()

    def listdir(self, path):
        raise PapermillException('listdir is not supported by Stream Handler')

    def write(self, buf, path):
        try:
            return sys.stdout.buffer.write(buf.encode('utf-8'))
        except AttributeError:
            # Originally required by https://github.com/nteract/papermill/issues/420
            # Support Buffer.io objects
            return sys.stdout.write(buf.encode('utf-8'))

    def pretty_path(self, path):
        return path

class NotebookNodeHandler(object):
    '''Handler for input_path of nbformat.NotebookNode object'''
    def read(self, path):
        return nbformat.writes(path)

    def listdir(self, path):
        raise PapermillException('listdir is not supported by NotebookNode Handler')

    def write(self, buf, path):
        raise PapermillException('write is not supported by NotebookNode Handler')

    def pretty_path(self, path):
        return 'NotebookNode object'

class NoIOHandler(object):
    '''Handler for output_path of None - intended to not write anything'''

    def read(self, path):
        raise PapermillException('read is not supported by NoIOHandler')

    def listdir(self, path):
        raise PapermillException('listdir is not supported by NoIOHandler')

    def write(self, buf, path):
        return

    def pretty_path(self, path):
        return 'Notebook will not be saved'

# Hack to make YAML loader not auto-convert datetimes
# https://stackoverflow.com/a/52312810
class NoDatesSafeLoader(yaml.SafeLoader):
    yaml_implicit_resolvers = {
        k: [r for r in v if r[0] != 'tag:yaml.org,2002:timestamp']
        for k, v in yaml.SafeLoader.yaml_implicit_resolvers.items()
    }

# Instantiate a PapermillIO instance and register Handlers.
papermill_io = PapermillIO()
papermill_io.register("local", LocalHandler())
papermill_io.register("s3://", S3Handler)
papermill_io.register("adl://", ADLHandler())
papermill_io.register("abs://", ABSHandler())
papermill_io.register("http://", HttpHandler)
papermill_io.register("https://", HttpHandler)
papermill_io.register("gs://", GCSHandler())
papermill_io.register("hdfs://", HDFSHandler())
papermill_io.register("http://github.com/", GithubHandler())
papermill_io.register("https://github.com/", GithubHandler())
papermill_io.register("-", StreamHandler())
papermill_io.register_entry_points()

def read_yaml_file(path):
    """Reads a YAML file from the location specified at 'path'."""
    return yaml.load(papermill_io.read(path, ['.json', '.yaml', '.yml']), Loader=NoDatesSafeLoader)

def write_ipynb(nb, path):
    """Saves a notebook object to the specified path.
    Args:
        nb_node (nbformat.NotebookNode): Notebook object to save.
        notebook_path (str): Path to save the notebook object to.
    """
    papermill_io.write(nbformat.writes(nb), path)

def load_notebook_node(notebook_path):
    """Returns a notebook object with papermill metadata loaded from the specified path.

    Args:
        notebook_path (str): Path to the notebook file.

    Returns:
        nbformat.NotebookNode

    """
    nb = nbformat.reads(papermill_io.read(notebook_path), as_version=4)
    nb_upgraded = nbformat.v4.upgrade(nb)
    if nb_upgraded is not None:
        nb = nb_upgraded

    if not hasattr(nb.metadata, 'papermill'):
        nb.metadata['papermill'] = {
            'default_parameters': dict(),
            'parameters': dict(),
            'environment_variables': dict(),
            'version': __version__,
        }

    for cell in nb.cells:
        if not hasattr(cell.metadata, 'tags'):
            cell.metadata['tags'] = []  # Create tags attr if one doesn't exist.

        if not hasattr(cell.metadata, 'papermill'):
            cell.metadata['papermill'] = dict()

    return nb

def list_notebook_files(path):
    """Returns a list of all the notebook files in a directory."""
    return [p for p in papermill_io.listdir(path) if p.endswith('.ipynb')]

def get_pretty_path(path):
    return papermill_io.pretty_path(path)

@contextmanager
def local_file_io_cwd(path=None):
    try:
        local_handler = papermill_io.get_handler("local")
    except PapermillException:
        logger.warning("No local file handler detected")
    else:
        try:
            old_cwd = local_handler.cwd(path or os.getcwd())
        except AttributeError:
            logger.warning("Local file handler does not support cwd assignment")
        else:
            try:
                yield
            finally:
                local_handler.cwd(old_cwd)


#!/usr/bin/python
# -*- coding: utf-8 -*-

from __future__ import print_function

import os
import shutil

import yaml
from xml.etree import ElementTree as et
import time
from os import path
import numpy as np

from performance_modelling_py.utils import backup_file_if_exists, print_info, print_error
from performance_modelling_py.component_proxies.ros1_component import Component

class BenchmarkRun(object):
    def __init__(self, run_id, run_output_folder, benchmark_log_path, environment_folder, parameters_combination_dict, benchmark_configuration_dict, show_ros_info, headless):

        # run configuration
        self.run_id = run_id
        self.benchmark_log_path = benchmark_log_path
        self.run_parameters = parameters_combination_dict
        self.benchmark_configuration = benchmark_configuration_dict
        self.components_ros_output = 'screen' if show_ros_info else 'log'
        self.headless = headless

        # environment parameters
        self.environment_folder = environment_folder
        self.map_info_file_path = path.join(environment_folder, "data", "map.yaml")
        laser_scan_max_range = self.run_parameters['laser_scan_max_range']
        laser_scan_fov_deg = self.run_parameters['laser_scan_fov_deg']
        laser_scan_fov_rad = (laser_scan_fov_deg-1)*np.pi/180
        map_resolution = self.run_parameters['map_resolution']
        self.run_output_folder = path.join(environment_folder, "data", "realistic_map", "res_{res}_fov_{fov}_max_range_{max_range}_no_scan_matching".format(res=map_resolution, fov=laser_scan_fov_deg, max_range=laser_scan_max_range))
        backup_file_if_exists(self.run_output_folder)  # backup data to avoid overwriting previous maps, since we are saving the data to the dataset

        self.gazebo_model_path_env_var = ":".join(map(
            lambda p: path.expanduser(p),
            self.benchmark_configuration['gazebo_model_path_env_var'] + [path.dirname(path.abspath(self.environment_folder)), self.run_output_folder]
        ))
        self.gazebo_resource_path_env_var = ":".join(map(
            lambda p: path.expanduser(p),
            self.benchmark_configuration['gazebo_resource_path_env_var']
        ))
        self.gazebo_plugin_path_env_var = ":".join(map(
            lambda p: path.expanduser(p),
            self.benchmark_configuration['gazebo_plugin_path_env_var']
        ))

        # run variables
        self.aborted = False

        # prepare folder structure
        run_configuration_path = path.join(self.run_output_folder, "components_configuration")
        run_info_file_path = path.join(self.run_output_folder, "run_info.yaml")
        backup_file_if_exists(self.run_output_folder)
        os.makedirs(self.run_output_folder)
        os.mkdir(run_configuration_path)
        output_map_file_path = path.join(self.run_output_folder, "map")
        output_pose_graph_file_path = path.join(self.run_output_folder, "pose_graph")

        # components original configuration paths
        components_configurations_folder = path.expanduser(self.benchmark_configuration['components_configurations_folder'])
        original_supervisor_configuration_path = path.join(components_configurations_folder, self.benchmark_configuration['components_configuration']['supervisor'])
        original_slam_toolbox_configuration_path = path.join(components_configurations_folder, self.benchmark_configuration['components_configuration']['slam_toolbox'])
        original_move_base_configuration_path = path.join(components_configurations_folder, self.benchmark_configuration['components_configuration']['move_base'])
        # self.original_rviz_configuration_path = path.join(components_configurations_folder, self.benchmark_configuration['components_configuration']['rviz'])
        original_gazebo_world_model_path = path.join(environment_folder, "gazebo", "gazebo_environment.model")
        original_gazebo_robot_model_config_path = path.join(environment_folder, "gazebo", "robot", "model.config")
        original_gazebo_robot_model_sdf_path = path.join(environment_folder, "gazebo", "robot", "model.sdf")
        original_robot_urdf_path = path.join(environment_folder, "gazebo", "robot.urdf")

        # components configuration relative paths
        supervisor_configuration_relative_path = path.join("components_configuration", self.benchmark_configuration['components_configuration']['supervisor'])
        slam_toolbox_configuration_relative_path = path.join("components_configuration", self.benchmark_configuration['components_configuration']['slam_toolbox'])
        move_base_configuration_relative_path = path.join("components_configuration", self.benchmark_configuration['components_configuration']['move_base'])
        gazebo_world_model_relative_path = path.join("components_configuration", "gazebo", "gazebo_environment.model")
        gazebo_robot_model_config_relative_path = path.join("components_configuration", "gazebo", "robot", "model.config")
        gazebo_robot_model_sdf_relative_path = path.join("components_configuration", "gazebo", "robot", "model.sdf")
        robot_gt_mb_urdf_relative_path = path.join("components_configuration", "gazebo", "robot_gt_mb.urdf")
        robot_gt_st_urdf_relative_path = path.join("components_configuration", "gazebo", "robot_gt_st.urdf")

        # components configuration paths in run folder
        self.supervisor_configuration_path = path.join(self.run_output_folder, supervisor_configuration_relative_path)
        self.slam_toolbox_configuration_path = path.join(self.run_output_folder, slam_toolbox_configuration_relative_path)
        self.move_base_configuration_path = path.join(self.run_output_folder, move_base_configuration_relative_path)
        self.gazebo_world_model_path = path.join(self.run_output_folder, gazebo_world_model_relative_path)
        gazebo_robot_model_config_path = path.join(self.run_output_folder, gazebo_robot_model_config_relative_path)
        gazebo_robot_model_sdf_path = path.join(self.run_output_folder, gazebo_robot_model_sdf_relative_path)
        self.robot_gt_mb_urdf_path = path.join(self.run_output_folder, robot_gt_mb_urdf_relative_path)
        self.robot_gt_st_urdf_path = path.join(self.run_output_folder, robot_gt_st_urdf_relative_path)

        # copy the configuration of the supervisor to the run folder and update its parameters
        with open(original_supervisor_configuration_path) as supervisor_configuration_file:
            supervisor_configuration = yaml.load(supervisor_configuration_file)
        supervisor_configuration['run_output_folder'] = self.run_output_folder
        supervisor_configuration['ground_truth_map_info_path'] = self.map_info_file_path
        supervisor_configuration['output_map_file_path'] = output_map_file_path
        supervisor_configuration['output_pose_graph_file_path'] = output_pose_graph_file_path
        if not path.exists(path.dirname(self.supervisor_configuration_path)):
            os.makedirs(path.dirname(self.supervisor_configuration_path))
        with open(self.supervisor_configuration_path, 'w') as supervisor_configuration_file:
            yaml.dump(supervisor_configuration, supervisor_configuration_file, default_flow_style=False)

        # copy the configuration of slam_toolbox to the run folder and update its parameters
        with open(original_slam_toolbox_configuration_path) as original_slam_toolbox_configuration_file:
            slam_toolbox_configuration = yaml.load(original_slam_toolbox_configuration_file)
        slam_toolbox_configuration['resolution'] = map_resolution
        if not path.exists(path.dirname(self.slam_toolbox_configuration_path)):
            os.makedirs(path.dirname(self.slam_toolbox_configuration_path))
        with open(self.slam_toolbox_configuration_path, 'w') as slam_toolbox_configuration_file:
            yaml.dump(slam_toolbox_configuration, slam_toolbox_configuration_file, default_flow_style=False)

        # copy the configuration of move_base to the run folder
        if not path.exists(path.dirname(self.move_base_configuration_path)):
            os.makedirs(path.dirname(self.move_base_configuration_path))
        shutil.copyfile(original_move_base_configuration_path, self.move_base_configuration_path)

        # copy the configuration of the gazebo world model to the run folder and update its parameters
        gazebo_original_world_model_tree = et.parse(original_gazebo_world_model_path)
        gazebo_original_world_model_root = gazebo_original_world_model_tree.getroot()
        gazebo_original_world_model_root.findall(".//include[@include_id='robot_model']/uri")[0].text = path.join("model://", path.dirname(gazebo_robot_model_sdf_relative_path))
        if not path.exists(path.dirname(self.gazebo_world_model_path)):
            os.makedirs(path.dirname(self.gazebo_world_model_path))
        gazebo_original_world_model_tree.write(self.gazebo_world_model_path)

        # copy the configuration of the gazebo robot sdf model to the run folder and update its parameters
        gazebo_robot_model_sdf_tree = et.parse(original_gazebo_robot_model_sdf_path)
        gazebo_robot_model_sdf_root = gazebo_robot_model_sdf_tree.getroot()
        # realistic sensor, for slam_toolbox (slam_toolbox needs some noise in the lidar to make a decent map)
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor']/ray/scan/horizontal/samples")[0].text = str(int(laser_scan_fov_deg))
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor']/ray/scan/horizontal/min_angle")[0].text = str(float(-laser_scan_fov_rad/2))
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor']/ray/scan/horizontal/max_angle")[0].text = str(float(+laser_scan_fov_rad/2))
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor']/ray/range/max")[0].text = str(float(laser_scan_max_range))
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor']/plugin[@name='turtlebot3_laserscan_realistic']/topicName")[0].text = "scan_gt_st"
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor']/plugin[@name='turtlebot3_laserscan_realistic']/frameName")[0].text = "base_scan_gt_st"
        # gt sensor, for move_base
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor_gt']/plugin[@name='turtlebot3_laserscan_gt']/topicName")[0].text = "scan_gt_mb"
        gazebo_robot_model_sdf_root.findall(".//sensor[@name='lidar_sensor_gt']/plugin[@name='turtlebot3_laserscan_gt']/frameName")[0].text = "base_scan_gt_mb"
        # ideal odometry, for slam_toolbox
        gazebo_robot_model_sdf_root.findall(".//plugin[@name='turtlebot3_diff_drive']/odometrySource")[0].text = "world"
        gazebo_robot_model_sdf_root.findall(".//plugin[@name='turtlebot3_diff_drive']/odometryTopic")[0].text = "odom"
        gazebo_robot_model_sdf_root.findall(".//plugin[@name='turtlebot3_diff_drive']/odometryFrame")[0].text = "odom_gt_st"
        gazebo_robot_model_sdf_root.findall(".//plugin[@name='turtlebot3_diff_drive']/robotBaseFrame")[0].text = "base_footprint_gt_st"
        # gt odometry, for move_base
        gazebo_robot_model_sdf_root.findall(".//plugin[@name='turtlebot3_diff_drive']/groundTruthParentFrame")[0].text = "odom_gt_mb"
        gazebo_robot_model_sdf_root.findall(".//plugin[@name='turtlebot3_diff_drive']/groundTruthRobotBaseFrame")[0].text = "base_footprint_gt_mb"
        if not path.exists(path.dirname(gazebo_robot_model_sdf_path)):
            os.makedirs(path.dirname(gazebo_robot_model_sdf_path))
        gazebo_robot_model_sdf_tree.write(gazebo_robot_model_sdf_path)

        # copy the configuration of the gazebo robot model to the run folder
        if not path.exists(path.dirname(gazebo_robot_model_config_path)):
            os.makedirs(path.dirname(gazebo_robot_model_config_path))
        shutil.copyfile(original_gazebo_robot_model_config_path, gazebo_robot_model_config_path)

        # copy the configuration of the robot urdf to the run folder and update the link names for ground truth move_base data
        robot_gt_mb_urdf_tree = et.parse(original_robot_urdf_path)
        robot_gt_mb_urdf_root = robot_gt_mb_urdf_tree.getroot()
        for link_element in robot_gt_mb_urdf_root.findall(".//link"):
            # if link_element.attrib['name'] == "base_link":
            #     continue
            link_element.attrib['name'] = "{}_gt_mb".format(link_element.attrib['name'])
        for joint_link_element in robot_gt_mb_urdf_root.findall(".//*[@link]"):
            # if joint_link_element.attrib['link'] == "base_link":
            #     continue
            joint_link_element.attrib['link'] = "{}_gt_mb".format(joint_link_element.attrib['link'])
        if not path.exists(path.dirname(self.robot_gt_mb_urdf_path)):
            os.makedirs(path.dirname(self.robot_gt_mb_urdf_path))
        robot_gt_mb_urdf_tree.write(self.robot_gt_mb_urdf_path)

        # copy the configuration of the robot urdf to the run folder and update the link names for ideal slam_toolbox data
        robot_gt_st_urdf_tree = et.parse(original_robot_urdf_path)
        robot_gt_st_urdf_root = robot_gt_st_urdf_tree.getroot()
        for link_element in robot_gt_st_urdf_root.findall(".//link"):
            link_element.attrib['name'] = "{}_gt_st".format(link_element.attrib['name'])
        for joint_link_element in robot_gt_st_urdf_root.findall(".//*[@link]"):
            joint_link_element.attrib['link'] = "{}_gt_st".format(joint_link_element.attrib['link'])
        if not path.exists(path.dirname(self.robot_gt_st_urdf_path)):
            os.makedirs(path.dirname(self.robot_gt_st_urdf_path))
        robot_gt_st_urdf_tree.write(self.robot_gt_st_urdf_path)

        # write run info to file
        run_info_dict = dict()
        run_info_dict["run_id"] = self.run_id
        run_info_dict["run_folder"] = self.run_output_folder
        run_info_dict["environment_folder"] = environment_folder
        run_info_dict["run_parameters"] = self.run_parameters
        run_info_dict["local_components_configuration"] = {
            'supervisor': supervisor_configuration_relative_path,
            'slam_toolbox': slam_toolbox_configuration_relative_path,
            'move_base': move_base_configuration_relative_path,
            'gazebo_world_model': gazebo_world_model_relative_path,
            'gazebo_robot_model_sdf': gazebo_robot_model_sdf_relative_path,
            'gazebo_robot_model_config': gazebo_robot_model_config_relative_path,
            'robot_gt_mb_urdf': robot_gt_mb_urdf_relative_path,
            'robot_gt_st_urdf': robot_gt_st_urdf_relative_path,
        }

        with open(run_info_file_path, 'w') as run_info_file:
            yaml.dump(run_info_dict, run_info_file, default_flow_style=False)

    def log(self, event):

        if not path.exists(self.benchmark_log_path):
            with open(self.benchmark_log_path, 'a') as output_file:
                output_file.write("timestamp, run_id, event\n")

        t = time.time()

        print_info("t: {t}, run: {run_id}, event: {event}".format(t=t, run_id=self.run_id, event=event))
        try:
            with open(self.benchmark_log_path, 'a') as output_file:
                output_file.write("{t}, {run_id}, {event}\n".format(t=t, run_id=self.run_id, event=event))
        except IOError as e:
            print_error("benchmark_log: could not write event to file: {t}, {run_id}, {event}".format(t=t, run_id=self.run_id, event=event))
            print_error(e)

    def execute_run(self):

        # components parameters
        rviz_params = {
            'headless': self.headless,
        }
        ground_truth_map_server_params = {
            'map': self.map_info_file_path,
        }
        environment_params = {
            'world_model_file': self.gazebo_world_model_path,
            'robot_gt_urdf_file': self.robot_gt_mb_urdf_path,
            'robot_realistic_urdf_file': self.robot_gt_st_urdf_path,
            'headless': True,
        }
        slam_toolbox_params = {
            'params_file': self.slam_toolbox_configuration_path,
        }
        navigation_params = {
            'params_file': self.move_base_configuration_path,
        }
        supervisor_params = {
            'params_file': self.supervisor_configuration_path,
        }
        recorder_benchmark_data_params = {
            'bag_file_path': path.join(self.run_output_folder, "benchmark_data.bag"),
            'topics': "/base_footprint_gt /cmd_vel /initialpose /map_gt /map_gt_metadata /map_gt_updates /map /map_metadata /map_updates /odom /particlecloud /gmapping/entropy /rosout /rosout_agg /scan /scan_gt /tf /tf_static /traversal_path",
        }

        # declare components
        roscore = Component('roscore', 'ground_truth_mapping', 'roscore.launch')
        environment = Component('gazebo', 'ground_truth_mapping', 'gazebo.launch', environment_params)
        ground_truth_map_server = Component('ground_truth_map_server', 'ground_truth_mapping', 'ground_truth_map_server.launch', ground_truth_map_server_params)
        rviz = Component('rviz', 'ground_truth_mapping', 'rviz.launch', rviz_params)
        recorder_benchmark_data = Component('recorder_sensor_data', 'ground_truth_mapping', 'rosbag_recorder.launch', recorder_benchmark_data_params)
        slam_toolbox = Component('slam_toolbox', 'ground_truth_mapping', 'slam_toolbox.launch', slam_toolbox_params)
        navigation = Component('move_base', 'ground_truth_mapping', 'move_base.launch', navigation_params)
        supervisor = Component('supervisor', 'ground_truth_mapping', 'ground_truth_mapping_supervisor.launch', supervisor_params)

        # set gazebo's environment variables
        os.environ['GAZEBO_MODEL_PATH'] = self.gazebo_model_path_env_var
        os.environ['GAZEBO_RESOURCE_PATH'] = self.gazebo_resource_path_env_var
        os.environ['GAZEBO_PLUGIN_PATH'] = self.gazebo_plugin_path_env_var

        # launch components
        roscore.launch()
        environment.launch()
        ground_truth_map_server.launch()
        rviz.launch()
        recorder_benchmark_data.launch()
        slam_toolbox.launch()
        navigation.launch()
        supervisor.launch()

        # launch components and wait for the supervisor to finish
        self.log(event="waiting_supervisor_finish")
        supervisor.wait_to_finish()
        # slam_toolbox.wait_to_finish()
        self.log(event="supervisor_shutdown")

        # shut down components
        navigation.shutdown()
        slam_toolbox.shutdown()
        recorder_benchmark_data.shutdown()
        rviz.shutdown()
        ground_truth_map_server.shutdown()
        environment.shutdown()
        roscore.shutdown()
        print_info("execute_run: components shutdown completed")


from telegram import Update, Bot
from telegram.ext import MessageHandler, Filters, CommandHandler
from telegram.ext.dispatcher import run_async

from tg_bot import dispatcher, LOGGER
from tg_bot.modules.helper_funcs.chat_status import bot_can_delete

FORBIDDEN_ENTITY_TYPES = ["url", "text_link", "email", "phone_number"]

@run_async
@bot_can_delete
def spam_filter(bot: Bot, update: Update):
    """
    Filter messages with spam into message's text
    """
    msg = update.effective_message.text
    message_entities = update.effective_message.parse_entities()
    message_caption_entities = update.effective_message.parse_caption_entities()

    found = False
    for descriptor, entity in message_entities.items():
        LOGGER.debug(f"Found message entity: {descriptor['type']} {entity}")
        if descriptor["type"] in FORBIDDEN_ENTITY_TYPES:
            found = True

    if found:
        spam_action(update)

def spam_action(update: Update):
    user = update.effective_user.username
    update.effective_message.reply_text(f"Non spammare! @{user}", quote=False)
    update.effective_message.delete()

def white_spam_add(bot: Bot, update: Update):
    if update.effective_message.reply_to_message:
        pass
    else:
        update.effective_message.reply_text("Cosa vuoi aggiungere in whitelist?")

SPAM_HANDLER = MessageHandler(Filters.all & Filters.group, spam_filter)
WHITE_SPAM_HANDLER = CommandHandler("whitespam", white_spam_add)
dispatcher.add_handler(SPAM_HANDLER)


from typing import Any, Dict, List

import tqdm

from src.pipeline.answerers.bert_NA_answer import BertAnswerNA
from src.pipeline.deterministic_qa_engine import QuestionAnswererNA
from src.pipeline.extractive_qa import refine_prediction as refine_prediction_with_RC
from src.pipeline.interface_question_answering import InterfaceQuestionAnswering, PredictedAnswer
from src.pipeline.question_category import QuestionCategory, QuestionCategoryClassifier, \
    GetCategoryFromQuestionStructure
from src.unpack_data import QuestionAnswerRecipe

class QuestionAnsweringDispatcher:

    def __init__(self, dispatching_table: Dict[str, InterfaceQuestionAnswering] = None,
                 question_classifier: QuestionCategoryClassifier = GetCategoryFromQuestionStructure()):
        """
        :param dispatching_table: Optional: dict[ category_id, answering_engine which should handle the rule]
        """
        self.dispatching_table = dispatching_table if dispatching_table \
            else QuestionAnsweringDispatcher.__build_default_dispatcher()
        self.question_category_classifier: QuestionCategoryClassifier = question_classifier

    @staticmethod
    def __build_default_dispatcher() -> Dict[str, InterfaceQuestionAnswering]:
        return {
            x: QuestionAnswererNA() for x in QuestionCategory.CATEGORIES
        }

    def predict_answer(self, question: QuestionAnswerRecipe, more_info: Dict[str, Any] = {},
                       bert_answer_na: BertAnswerNA = None) -> PredictedAnswer:
        """
        :param bert_answer_na: added if postprocessing with bert NA checker
        :param question: question to be answered
        :param more_info: Additional info to be handled (currently ignored)
        :return:
        """
        category = self.question_category_classifier.predict_category(question)
        assert isinstance(category, QuestionCategory)

        engine = self.dispatching_table[category.category]
        ret = engine.answer_a_question(question=question, question_category=category, more_info=more_info)

        if "RC" in self.dispatching_table:
            rc_engine = self.dispatching_table["RC"]
            ret = refine_prediction_with_RC(engine=rc_engine, question=question, category=category,
                                            rule_based_prediction=ret, more_info=more_info)
        if bert_answer_na and ret.answer and category.category == '4':
            ret.answer = bert_answer_na.check_bert_na_answer(ret.answer, question)
            if ret.answer is None:
                ret.more_info["details_for_excel"] += 'Modify in postprocessing BERT NA'

        ret.more_info["predicted_category"] = category.category
        ret.more_info["predicted_category_description"] = category.description
        ret.more_info["with_postprocessing"] = bool(bert_answer_na)
        ret.more_info["answering_engine"] = engine.__class__.__name__
        return ret

    def predict_answers(self, which_dataset: str, with_postprocessing: bool, questions: List[QuestionAnswerRecipe],
                        more_info: Dict[str, Any] = {}) -> List[PredictedAnswer]:
        use_tqdm = more_info.get("use_tqdm", False)
        iterator = tqdm.tqdm(questions, desc="answering") if use_tqdm else questions
        if with_postprocessing:
            bert_na_answer = BertAnswerNA(which_dataset)
            return [self.predict_answer(q, more_info, bert_na_answer) for q in iterator]

        return [self.predict_answer(q, more_info) for q in iterator]


import argparse

p = argparse.ArgumentParser()
p.add_argument("--msg")
p.add_argument("--touch")

args = p.parse_args()

if args.msg:
    print(args.msg)

if args.touch:
    open(args.touch, "a").close()


import os
import sys
import argparse

from tempest_parser import const
from tempest_parser.reports import reporter
from tempest_parser.reports.csv_reporter import CSVReporter
from tempest_parser.manager.test_manager import TestsManager
from tempest_parser.parser.tempest_log_parser import TempestLogParser
from tempest_parser.manager.importers import XMLImporter, JSONImporter, \
    CSVImporter, SubunitImporter
from tempest_parser.utils.config import ParserConfigFile

pkg_dir = os.path.dirname(__file__)
pkg_dir = os.path.normpath(pkg_dir)

class MyParser(argparse.ArgumentParser):
    def error(self, message):
        sys.stderr.write('Error: {0}\n\n'.format(message))
        self.print_help()
        sys.exit(2)

def help_message():
    print("""
    Please, supply pytest output file as a parameter. \n
          ...or previously exported CSV file \n
          ...or rally's tool exported JSON file \n
          ...or subunit's exported XML file \n
          ...or saved subunit output \n
          ...or folder full of files with types mentioned.\n
    """)

    return

def detect_format_from_filename(filename):
    if filename.endswith(".json"):
        # this is Rally's json file
        return const.FMT_RALLY_JSON
    elif filename.endswith(".csv"):
        # this is simple csv file
        return const.FMT_CSV
    elif filename.endswith(".xml"):
        # this is an xml file, parse it using specific importer
        # default to tempest type
        return const.FMT_XML_TEMPEST
    elif filename.endswith(".log"):
        # ..it is a bare testr output, parse it
        return const.FMT_PYTEST
    elif filename.endswith(".subunit"):
        # ..it is a subunit stream saved as a file, pass it along
        return const.FMT_SUBUNIT
    else:
        print("Error: Input filename's extension is not supported")
        sys.exit(1)

def do_parse_file(source, tm, fmt=None, force_single=None, filters=[]):
    close_source = False
    filename = "unknown/stdin"
    if not hasattr(source, "read"):
        if fmt is None:
            fmt = detect_format_from_filename(source)
        filename = source
        source = open(source, "rt")
        close_source = True

    importer = None

    if fmt is const.FMT_RALLY_JSON:
        # this is Rally's json file
        importer = JSONImporter(tm, source, status_filters=filters)
    elif fmt is const.FMT_CSV:
        # this is simple csv file
        importer = CSVImporter(tm, source, status_filters=filters)
    elif fmt is const.FMT_XML_TEMPEST:
        # this is an xml file from tempest
        # parse it using specific importer
        importer = XMLImporter(tm, source, status_filters=filters)
    elif fmt is const.FMT_XML_RAW:
        # this is a raw xml file
        # parse it and use test names as is
        importer = XMLImporter(
            tm,
            source,
            use_raw_names=True,
            force_single_execution=force_single,
            status_filters=filters
        )
    elif fmt is const.FMT_PYTEST:
        # ..it is a bare testr output, parse it
        importer = TempestLogParser(tm, source)
    elif fmt is const.FMT_SUBUNIT:
        # ..it is a subunit stream saved as a file, pass it along
        importer = SubunitImporter(tm, source)

    importer.parse()
    print("Imported {}".format(filename))

    if close_source:
        source.close()

# main
def tempest_cli_parser_main():
    parser = MyParser(prog="Tempest CLI Parser")

    parser.add_argument(
        "inputfile", nargs="?", default=sys.stdin,
        help="""
    File with tempest results (CSV/LOG/XML/JSON) or folder full of these files
    """)

    parser.add_argument(
        "-f",
        "--input-format",
        help="""
    Input format. Defaults to SUBUNIT for pipe.
    Tries to detect from extension if filename is supplied
    """)

    parser.add_argument(
        "--omit-status",
        action="append",
        help="Do not include tests with target status in final report. "
             "Option can be used multiple times."
    )

    parser.add_argument(
        "-d",
        "--detailed",
        action="store_true",
        help="""
    Include messages column with tracebacks and other messages.
    Default comes from config
    """)

    parser.add_argument(
        "-i",
        "--include-required",
        action="store_true", default=False,
        help="Initialise first column as baseline test list. I.e. required"
    )

    parser.add_argument(
        "-c",
        "--csv-file",
        help="Force output to CSV"
    )

    parser.add_argument(
        "--config-file",
        help="""
    Use specific configuration file instead of standard.
    Use absolute path, please.
    """
    )

    parser.add_argument(
        "--force-single",
        action="store_true", default=False,
        help="All files in folder treated as single execution"
    )

    parser.add_argument(
        "-r",
        "--html-trending-filename",
        help="When set, creates HTML Trending Report"
    )

    parser.add_argument(
        "-e",
        "--html-errors-filename",
        help="When set, creates HTML Errors Report"
    )

    args = parser.parse_args()

    # Init Config
    _config_file_path = os.path.join(pkg_dir, 'etc', 'tempest-parser.conf')
    if args.config_file:
        _config_file_path = args.config_file
    config = ParserConfigFile(_config_file_path)

    # use config to set value for 'detailed' option
    _config_detailed_default = config.get_detailed_column_default_value()
    _args_detailed = args.detailed

    do_detailed = _args_detailed if _args_detailed \
        else _config_detailed_default

    # prepare format
    input_fmt = None
    if args.input_format is None:
        pass
    else:
        _fmt = args.input_format.strip()
        if _fmt in const.ALL_INPUT_FORMATS:
            input_fmt = const.ALL_INPUT_FORMATS[_fmt]
        else:
            print("Supplied format is not supported: '{}'".format(
                args.input_format
            ))

    # prepare filter
    if args.omit_status:
        status_filters = args.omit_status
    else:
        status_filters = []

    pipe_fmt = None
    # Detect pipe input, prior to handle args
    # print("istty?={}, fifo?={}, fd?={}".format(
    #     sys.stdin.isatty(),
    #     S_ISFIFO(os.fstat(0).st_mode),
    #     args.inputfile
    # ))
    if hasattr(args.inputfile, "name"):
        if args.inputfile.name == "<stdin>":
            if input_fmt is None:
                pipe_fmt = const.FMT_SUBUNIT
                print("No PIPE format set, defaulted to: '{}'".format(
                    const.FORMAT_LABELS[pipe_fmt]
                ))
            else:
                pipe_fmt = input_fmt
        else:
            print("Error: Unknown PIPE: '{}', '<stdin>' expected".format(
                args.inputfile.name
            ))
            sys.exit(1)
    else:
        # Check for supplied folder/file to be exists
        if not os.path.exists(args.inputfile):
            print("Error: Supplied path/file not exists, '{}'".format(
                args.inputfile
            ))
            sys.exit(1)

        # Check if errors report is set and file is supplied
        if not os.path.isfile(args.inputfile):
            if args.html_errors_filename:
                print("Error: Errors report require single file, "
                      "folder given: '{}'".format(args.inputfile))
                sys.exit(1)

    if hasattr(args.inputfile, "name"):
        print("Reading '{}' from '{}'".format(
            const.FORMAT_LABELS[pipe_fmt],
            args.inputfile.name
        ))

    # At this point we must load tests to combine executions with
    # for now it will be all tests
    tests_manager = TestsManager()
    if args.include_required and args.html_errors_filename is None:
        _all_tests_filename = config.get_all_tests_list_filepath()
        print("Preloading all tests from '{}'".format(
            _all_tests_filename
        ))
        _use_raw_names = True if input_fmt == const.FMT_XML_RAW else False
        tests_manager.add_required(
            _all_tests_filename,
            use_raw_names=_use_raw_names
        )
    # # Parse objects from raw file
    # # and Collect / sort objects into executions and parse them
    if pipe_fmt is not None:
        # this is a pipe input, pass file descriptor and format
        do_parse_file(
            args.inputfile,
            tests_manager,
            fmt=pipe_fmt,
            filters=status_filters
        )
    elif os.path.isfile(args.inputfile):
        # this is a file, parse it using supplied input format
        do_parse_file(
            args.inputfile,
            tests_manager,
            fmt=input_fmt,
            filters=status_filters
        )
    else:
        print("Importing tests from folder '{}'".format(args.inputfile))
        # this is a folder, get files one by one
        _folder_content = os.listdir(args.inputfile)

        for _file in _folder_content:
            # parse files according to format
            _target_extension = const.FORMAT_LABELS[input_fmt]
            if input_fmt is not None and not _file.endswith(_target_extension):
                print(
                    "Skipped '{}', "
                    "extension not corresponds to given format ({})".format(
                        _file,
                        _target_extension
                    ))
                continue
            # if extension fits - process file
            if args.force_single:
                do_parse_file(
                    os.path.join(
                        args.inputfile,
                        _file
                    ),
                    tests_manager,
                    fmt=input_fmt,
                    force_single=args.inputfile,
                    filters=status_filters
                )
            else:
                do_parse_file(
                    os.path.join(
                        args.inputfile,
                        _file
                    ),
                    tests_manager,
                    fmt=input_fmt,
                    filters=status_filters
                )

    if args.html_trending_filename:
        # prepare the reporting subsystem
        trending_report = reporter.ReportToFile(
            reporter.HTMLTrendingReport(),
            args.html_trending_filename
        )
        # call-n-render report
        print("Generating HTML Trending report...")
        trending_report(tests_manager.get_tests_list(), detailed=do_detailed)

    if args.html_errors_filename:
        errors_report = reporter.ReportToFile(
            reporter.HTMLErrorsReport(),
            args.html_errors_filename
        )
        # call-n-render report
        print("Generating HTML Errors report...")
        errors_report(tests_manager.get_tests_list())

    if args.csv_file is not None:
        print("Generating CSV report...")
        csv_reporter = CSVReporter(tests_manager)
        csv_reporter.generate_to_file(args.csv_file, detailed=do_detailed)

if __name__ == '__main__':
    tempest_cli_parser_main()
    sys.exit(0)