import pygame

from typing import Any, Tuple
from colorama import Fore, Style
import json
import pickle

from engine.core import EngineSettings

from asi import settings
from .sprites.player.player import PlayerSprite
from .sprites.environment.obstacle import Obstacle
from .sprites.environment.decoration import DecorationSprite
from .sprites.player.storage import Storage
from .sprites.npc.trader import Trader
from .sprites.enemy.kamikaze import Kamikaze
from .sprites.enemy.gunner import Gunner
from .sprites.environment.board import BoardSprite
from .sprites.player.HEAL import Heal
from .sprites.enemy.spike import Spike
from .sprites.enemy.boss import Boss
from .sprites.environment.trigger import Trigger
from .sprites.environment.door import Door
from asi.main.sprites.environment.platforms.platform_horizontally import PlatformHorizontally
from asi.main.sprites.environment.platforms.platform_vertically import PlatformVertically
from asi.main.sprites.environment.platforms.platform_block import PlatformBlock
from asi.main.sprites.environment.platforms.platform_quarter1 import PlatformCornerFirst
from asi.main.sprites.environment.platforms.platform_quarter2 import PlatformCornerSecond
from asi.main.sprites.environment.platforms.platform_quarter3 import PlatformCornerThird
from asi.main.sprites.environment.platforms.platform_quarter4 import PlatformCornerFourth
from asi.main.sprites.environment.platforms.platform_upper import PlatformUpper
from asi.main.sprites.environment.platforms.platform_bottom import PlatformBottom
from asi.main.sprites.environment.platforms.platform_left import PlatformLeft
from asi.main.sprites.environment.platforms.platform_right import PlatformRight

def load_level(filename):  # загрузка уровня
    filename = r"asi/main/resources/map/" + filename

    with open(filename, "r") as mapFile:
        level_map = [line for line in mapFile]

    max_width = max(map(len, level_map))

    return list(map(lambda x: list(x.ljust(max_width, ".")), level_map))  # возвращаем список списков карты

class Map:
    ENTITY_SYMBOL_DECODER = {
        "$": Storage,
        "t": Trader,
        "k": Kamikaze,
        "g": Gunner,
        "h": Heal,
        "s": Spike,
        "B": BoardSprite,
        "b": Boss,
        "*": Trigger,
        "z": Door
    }
    NO_UPDATE_SYMBOL_DECODER = {
        "-": PlatformHorizontally,
        "|": PlatformVertically,
        "<": PlatformLeft,
        ">": PlatformRight,
        "^": PlatformUpper,
        "V": PlatformBottom,
        "#": PlatformBlock,
        "1": PlatformCornerFirst,
        "2": PlatformCornerSecond,
        "3": PlatformCornerThird,
        "4": PlatformCornerFourth,
    }
    DECORATION_SYMBOL_DECODER = {
        # "G": {
        #     "image_path": "map/grass-1.png",
        #     "size": (50, 20)
        # },
        # "T": {
        #     "image_path": "map/tree-1.png",
        #     "size": (200, 200)
        # },
        "R": {
            "image_path": "map/dicoration/rock_horizontally.png",
            "size": (150, 100)
        },
        "O": {
            "image_path": "map/dicoration/rock_vertically.png",
            "size": (100, 150)
        },
        "S": {
            "image_path": "map/dicoration/plant_spike.png",
            "size": (100, 100)
        },
        "K": {
            "image_path": "map/dicoration/plant_spike.png",
            "size": (50, 50)
        },
        "Q": {
            "image_path": "map/dicoration/plant-1.png",
            "size": (50, 30)
        },
        "W": {
            "image_path": "map/dicoration/plant-2.png",
            "size": (50, 30)
        },
        "E": {
            "image_path": "map/dicoration/plant-3.png",
            "size": (50, 50)
        },
        "Y": {
            "image_path": "map/dicoration/plant-4.png",
            "size": (50, 75)
        },
        "U": {
            "image_path": "map/dicoration/sprout_left.png",
            "size": (30, 60)
        },
        "I": {
            "image_path": "map/dicoration/sprout_right.png",
            "size": (30, 60)
        },
    }

    def __init__(self, scene, surface):
        self.__update_sprite_group = pygame.sprite.Group()
        self.__no_update_sprite_group = pygame.sprite.Group()
        self.__player_sprite_group = pygame.sprite.Group()
        self.__scene = scene
        self.__surface = surface

        self.__player_render = EngineSettings.get_var("RENDER_DISTANCE")

        self.__background_image = pygame.image.load("asi/main/resources/background.png")

        self.block_size = 50
        self.map: list[list[Any]]
        self.map_symbol: list[list[str]]
        self.map_status: list[list[Any]]

        self.id_dialog = 0
        self.dialog = [["Гг - Что это? Похоже на какую-то табличку, на ней надписи, значит я не первый кто сюда пришёл.",
                       "О великий бог справедливости Якатекутли, я всю жизнь ждал этого момента, с тех пор как я в детстве\n"
                       " услышал легенду об этом волшебном месте созданном тобой, опасном лабиринте кишащими разными опасными\n"
                       " тварями, высокими обрывами, и смертельными копьями, но в конце: тот, кто пройдёт все испытания,\n"
                       " получит несметные богатства и вечную славу. Я сразу понял, что это моё предназначение.\n"
                       " Я тренировался всю свою жизнь ради этого момента, я научился в совершенстве владеть макуахутл.\n"
                       " Всевышней, молю, будь со мной милосерден. Только я не чествую свою силу, я ощущаю себя лишь \n"
                       "беспомощным духом, я.… я не понимаю, что со мной происходит. Ладно, надеюсь это скоро пройдёт, \n"
                       "главное, что со мной мой макуахутл. ",
                       "Гг - М-да, эти фанатики настолько верили в эти сказки, что тратили на это всю жизнь. Но, \n"
                       "всё что происходило со мной до этого, заставляет задуматься о реальности этого, главное,\n"
                       " чтобы концовка с золотом была реальна, остальное меня не волнует. "],
                      "О господи, я уже потерял всякий контроль над ситуацией, я шёл по этим джунглям, и тут на меня вылетел\n"
                      " какой-то злобный дух и запустил в меня какую-то штуку, я … я почувствовал адскую боль,\n"
                      " перестал чествовать своё тело, я начал растворятся в воздухе и взлетать, а дальше темнота.\n"
                      " Я же буквально умер... Но через какое-то время я снова появился там, откуда я начал, без единого\n"
                      " следа. Я благодарен тебе о великий Якатекутли, за то, что дал мне второй шанс, я обещаю тебе, в этот\n"
                      " раз я тебя не подведу. ", [
                          "Похоже я начинаю понимать, что значила фраза ualchikauak semahsitika в легенде, это не означало \n"
                          "какую-то волшебную мнимую душевную силу, это в прямом смысле значило силу твоей воли, благородство\n"
                          " и великодушие твоей души. Теперь я все понимаю, здесь обнажается твоя душа, все твои физические \n"
                          "качества становятся не важны, ты в прямом смысле становишься призраком, привязанным лишь \n"
                          "к твоей душе. ",
                          "Гг - Так, звучит это все конечно бредово, но это похоже это действительно правда. Это же\n"
                          " даже лучше... Да, определённо это лучше! Я проделал такой путь из Испании до этого места,\n"
                          " прошёл через такие опасности, которые этим индейцам даже снились, я столько всего преодолел \n"
                          "ради славы и богатств. Наверняка же у меня очень сильная душа... "],
                      "О господи, я так хочу домой, я ничего не понимаю, мне уже не нужны эти богатства, я ничего не хочу, \n"
                      "хочу просто обратно к своим друзьям, я потерял счёт сколько я раз умирал, десятки, а может сотни раз,\n"
                      " сколько дней прошло уже, здесь всё время светит солнце. Я не вижу конца, я все иду и иду, сражаюсь \n"
                      "и сражаюсь, умираю и умираю, а конца так и не видно. Но самое странное, после того как я возрождаюсь,\n"
                      " вместе со мной оживают все враги, которых я убил.", [
                          "Кажется, я понял, это вовсе не враги, это такие же люди как я, которые раньше пришли сюда. Страшно\n"
                          " представить, что они вообще пережили, тысячи, а может миллион смертей и страдания, после того \n"
                          "любой потеряет рассудок. У них на уме только одно, убивать, убивать и убивать, возрождаться и ещё \n"
                          "раз убивать всё. Это уже не люди, от них мало что осталось. Через какое время я стану таким... ",
                          "Гг - Господи, выходит среди них сейчас ходит один из этих индейцев, так, надо срочно выбираться, \n"
                          "прошло уже много времени, всё равно на эти сокровища, главное найти выход отсюда. "],
                      "Но всё равно, одно не вписывается в мою идею, кто такие торговцы, вроде бы это тоже живые души, но они\n"
                      "только торгуют с тобой, на все вопросы они не отвечают, странно все это. ", [
                          "Надо собраться, я обязан найти выход, даже если не для себя, то для моего народа, люди до сих пор\n"
                          " верят в эту легенду, столько людей уходят и все тут остаются навсегда. Я должен вернуться и\n"
                          " рассказать всю правду, чтобы больше люди не погибали. ",
                          "Гг - Ну теперь всё понятно, похоже индейцы поняли, что это погибель для всех, кто сюда идёт, и\n"
                          " отправили меня сюда, думали, что они самые умные, что избавятся от меня так, нет, я вернусь\n"
                          " несмотря не на что, и отомщу им за это. "], [
                          "Ура, наконец то спустя столько смертей, я нашёл его, я встретил какого-то каменного монстра,\n"
                          " совсем не похожего на души и за мной захлопнулась дверь, хоть я и умер от его каменной руки,\n"
                          " но ничего страшного, теперь я знаю, что у этого есть конец, осталось совсем немного",
                          "Гг - Так, отлично, мне надо туда, осталось всего лишь убить какую ту груду камней, и я выберусь отсюда. "],
                      ["Нет, это невозможно, теперь я окончательно потерял надежду выбраться отсюда, хоть я и нашёл конец\n"
                       " этого места, невозможно победить этого монстра, он как будто бессмертен, он слишком силён, он \n"
                       "пуляет лазеры из своей головы, кидает в меня камни, бьёт по полу, и каждый раз все мои попытки\n"
                       " заканчиваются одинаково, я умираю... ",
                       "Гг - Жалкий слабак, сдался, ну ничего, я убью этого монстра и выйду от сюда с золотом "]]

    def create_map_2(self, level_map):
        self.start_player_pos = self.__preload_player(level_map)

        self.map_size_x = len(max(level_map, key=lambda x: len(x[0])))
        self.map_size_y = len(level_map)

        self.__background_size = [
            max(self.map_size_x * self.block_size, settings.HEIGHT),
            max(self.map_size_y * self.block_size, settings.WIDTH)
        ]
        self.__background_pos = [0, 0]
        self.__background_image = pygame.transform.scale(self.__background_image, self.__background_size)

        self.map = [[None for x in range(self.map_size_x)] for y in range(self.map_size_y)]
        self.map_status = [[False for x in range(self.map_size_x)] for y in range(self.map_size_y)]
        self.map_symbol = [[" " for x in range(self.map_size_x)] for y in range(self.map_size_y)]

        f = False

        for y in range(len(level_map)):
            for x in range(len(level_map[y])):
                symbol = level_map[y][x]

                if symbol in Map.NO_UPDATE_SYMBOL_DECODER:
                    sprite = self.__scene.load_sprite(
                        Map.NO_UPDATE_SYMBOL_DECODER[symbol],
                        coords=[
                            self.block_size * x,
                            self.block_size * y
                        ]
                    )
                    self.map[y][x] = sprite

                    self.map_symbol[y][x] = symbol

                    if not f:
                        self.t = sprite
                        f = True

                if symbol in Map.ENTITY_SYMBOL_DECODER:
                    if symbol == 'B':
                        sprite = self.__scene.load_sprite(
                            Map.ENTITY_SYMBOL_DECODER[symbol],
                            coords=[
                                self.block_size * x,
                                self.block_size * y
                            ], text=self.dialog[self.id_dialog],
                        )
                        self.id_dialog += 1
                    else:
                        sprite = self.__scene.load_sprite(
                            Map.ENTITY_SYMBOL_DECODER[symbol],
                            coords=[
                                self.block_size * x,
                                self.block_size * y
                            ]
                        )
                    self.map[y][x] = sprite

                    self.map_symbol[y][x] = symbol

                if symbol in Map.DECORATION_SYMBOL_DECODER:
                    sprite = self.__scene.load_sprite(
                        DecorationSprite,
                        coords=[
                            self.block_size * x + self.block_size - Map.DECORATION_SYMBOL_DECODER[symbol]["size"][0],
                            self.block_size * y + self.block_size - Map.DECORATION_SYMBOL_DECODER[symbol]["size"][1]
                        ],
                        **Map.DECORATION_SYMBOL_DECODER[symbol]
                    )
                    self.map[y][x] = sprite

                    self.map_symbol[y][x] = symbol

        self.player = self.__scene.load_sprite(
            PlayerSprite,
            coords=[
                self.block_size * self.start_player_pos[0],
                self.block_size * self.start_player_pos[1]
            ]
        )
        self.__scene.player = self.player
        self.start_player_pos = [0, 0]
        self.__last_player_pos = self.get_payer_map_pos()
        # self.__update_sprite_group.add(self.player)

        self.__player_sprite_group.add(self.player)

        self.__scene._game_stack.sprite_group.update()

        player_pos = self.get_payer_map_pos()

        for y in range(
                max(player_pos[1] - self.__player_render[1], 0),
                min(player_pos[1] + self.__player_render[1], self.map_size_y)
        ):
            for x in range(
                    max(player_pos[0] - self.__player_render[0], 0),
                    min(player_pos[0] + self.__player_render[0], self.map_size_x)
            ):
                self.map_status[y][x] = True

                if self.map[y][x] and self.map_symbol[y][x] not in Map.NO_UPDATE_SYMBOL_DECODER:
                    self.__update_sprite_group.add(
                        self.map[y][x]
                    )

                if self.map[y][x] and self.map_symbol[y][x] in Map.NO_UPDATE_SYMBOL_DECODER:
                    self.__no_update_sprite_group.add(
                        self.map[y][x]
                    )

    def get_payer_map_pos(self):
        return [
            int(self.start_player_pos[0] + self.player.rect.center[0] // self.block_size),
            int(self.start_player_pos[1] + self.player.rect.center[1] // self.block_size)
        ]

    def in_render_zone(self, player_pos, coords: Tuple[int, int]):
        return (
                player_pos[0] - self.__player_render[0] <= coords[0] <= player_pos[0] + self.__player_render[0] and
                player_pos[1] - self.__player_render[1] <= coords[1] <= player_pos[1] + self.__player_render[1]
        )

    def draw_debug_map(self):
        for y in range(self.map_size_y):
            for x in range(self.map_size_x):
                if self.map_status[y][x]:
                    color = Fore.GREEN
                else:
                    color = Fore.RED

                print(self.map_symbol[y][x] + color, end=" ")
            print()

        print(Style.RESET_ALL)

    def draw_background(self):
        self.__background_image = pygame.transform.scale(self.__background_image, (
            max(self.map_size_x * self.block_size, settings.HEIGHT),
            max(self.map_size_y * self.block_size, settings.WIDTH))
                                                         )

        self.__surface.blit(self.__background_image, (min(self.t.rect.x, 0), min(self.t.rect.y, 0)))

    def update(self):
        player_pos = self.get_payer_map_pos()

        if self.__player_render != EngineSettings.get_var("RENDER_DISTANCE"):
            self.__player_render = EngineSettings.get_var("RENDER_DISTANCE")

            self.__update_sprite_group.empty()
            self.__no_update_sprite_group.empty()

            self.__player_sprite_group.add(self.player)

            for y in range(len(self.map_status)):
                for x in range(len(self.map_status[y])):
                    self.map_status[y][x] = False

            for y in range(
                    max(player_pos[1] - self.__player_render[1], 0),
                    min(player_pos[1] + self.__player_render[1], self.map_size_y)
            ):
                for x in range(
                        max(player_pos[0] - self.__player_render[0], 0),
                        min(player_pos[0] + self.__player_render[0], self.map_size_x)
                ):
                    self.map_status[y][x] = True

                    if self.in_render_zone(player_pos, (x, y)) and self.map_symbol[y][
                        x] not in Map.NO_UPDATE_SYMBOL_DECODER:
                        if self.map[y][x] and self.map[y][x] in self.__scene.sprite_group:
                            self.__update_sprite_group.add(
                                self.map[y][x]
                            )

                    if self.in_render_zone(player_pos, (x, y)) and self.map_symbol[y][
                        x] in Map.NO_UPDATE_SYMBOL_DECODER:
                        if self.map[y][x] and self.map[y][x] in self.__scene.sprite_group:
                            self.__no_update_sprite_group.add(
                                self.map[y][x]
                            )

            self.__surface.fill(pygame.Color("#3C2A21"))
            self.__no_update_sprite_group.draw(self.__surface)
            self.__update_sprite_group.draw(self.__surface)

        if self.__last_player_pos != player_pos:
            player_shift = [
                player_pos[0] - self.__last_player_pos[0],
                player_pos[1] - self.__last_player_pos[1]
            ]

            old_left_x_render = self.__last_player_pos[0] + self.__player_render[0]
            old_right_x_render = self.__last_player_pos[0] - self.__player_render[0]

            new_left_x_render = self.__last_player_pos[0] + player_shift[0] + self.__player_render[0]
            new_right_x_render = self.__last_player_pos[0] + player_shift[0] - self.__player_render[0]

            min_left_x_render, max_left_x_render = min(old_left_x_render, new_left_x_render), max(old_left_x_render,
                                                                                new_left_x_render)
            min_right_x_render, max_right_x_render = min(old_right_x_render, new_right_x_render), max(
                old_right_x_render, new_right_x_render)

            for y in range(
                    max(min(
                        player_pos[1],
                        self.__last_player_pos[1]
                    ) - self.__player_render[1], 0),
                    min(max(
                        player_pos[1],
                        self.__last_player_pos[1]
                    ) + self.__player_render[1] + 1, self.map_size_y)
            ):
                for x in (*range(max(min_left_x_render, 0), min(max_left_x_render + 1, self.map_size_x)),
                          *range(max(min_right_x_render, 0), min(max_right_x_render + 1, self.map_size_x))):
                    if self.in_render_zone(player_pos, (x, y)) and not self.map_status[y][x] and self.map_symbol[y][
                        x] not in Map.NO_UPDATE_SYMBOL_DECODER:
                        if self.map[y][x] and self.map[y][x] in self.__scene.sprite_group:
                            self.__update_sprite_group.add(
                                self.map[y][x]
                            )

                        self.map_status[y][x] = True

                    if self.in_render_zone(player_pos, (x, y)) and not self.map_status[y][x] and self.map_symbol[y][
                        x] in Map.NO_UPDATE_SYMBOL_DECODER:
                        if self.map[y][x] and self.map[y][x] in self.__scene.sprite_group:
                            self.__no_update_sprite_group.add(
                                self.map[y][x]
                            )

                        self.map_status[y][x] = True

                    if not self.in_render_zone(player_pos, (x, y)) and self.map_status[y][x]:
                        if self.map_symbol[y][x] in Map.NO_UPDATE_SYMBOL_DECODER:
                            self.__no_update_sprite_group.remove(
                                self.map[y][x]
                            )
                        else:
                            self.__update_sprite_group.remove(
                                self.map[y][x]
                            )

                        self.map_status[y][x] = False

            old_top_y_render = self.__last_player_pos[1] + self.__player_render[1]
            old_bottom_y_render = self.__last_player_pos[1] - self.__player_render[1]

            new_top_y_render = self.__last_player_pos[1] + player_shift[1] + self.__player_render[1]
            new_bottom_y_render = self.__last_player_pos[1] + player_shift[1] - self.__player_render[1]

            min_top_y_render, max_top_y_render = min(old_top_y_render, new_top_y_render), max(old_top_y_render,
                                                                                new_top_y_render)
            min_bottom_y_render, max_bottom_y_render = min(old_bottom_y_render, new_bottom_y_render), max(
                old_bottom_y_render, new_bottom_y_render)

            for x in range(
                    max(min(
                        player_pos[0],
                        self.__last_player_pos[0]
                    ) - self.__player_render[0], 0),
                    min(max(
                        player_pos[0],
                        self.__last_player_pos[0]
                    ) + self.__player_render[0] + 1, self.map_size_x)
            ):
                for y in (*range(max(min_top_y_render, 0), min(max_top_y_render + 1, self.map_size_y)),
                          *range(max(min_bottom_y_render, 0), min(max_bottom_y_render + 1, self.map_size_y))):
                    if self.in_render_zone(player_pos, (x, y)) and not self.map_status[y][x] and self.map_symbol[y][
                        x] not in Map.NO_UPDATE_SYMBOL_DECODER:
                        if self.map[y][x] and self.map[y][x] in self.__scene.sprite_group:
                            self.__update_sprite_group.add(
                                self.map[y][x]
                            )

                        self.map_status[y][x] = True

                    if self.in_render_zone(player_pos, (x, y)) and not self.map_status[y][x] and self.map_symbol[y][
                        x] in Map.NO_UPDATE_SYMBOL_DECODER:
                        if self.map[y][x] and self.map[y][x] in self.__scene.sprite_group:
                            self.__no_update_sprite_group.add(
                                self.map[y][x]
                            )

                        self.map_status[y][x] = True

                    if not self.in_render_zone(player_pos, (x, y)) and self.map_status[y][x]:
                        if self.map_symbol[y][x] in Map.NO_UPDATE_SYMBOL_DECODER:
                            self.__no_update_sprite_group.remove(
                                self.map[y][x]
                            )
                        else:
                            self.__update_sprite_group.remove(
                                self.map[y][x]
                            )

                        self.map_status[y][x] = False

            self.__last_player_pos = player_pos

        for sprite in self.__update_sprite_group.sprites():
            sprite._update(True)

        self.__surface.fill(pygame.Color(EngineSettings.get_var("GAME_BACKGROUND_COLOR")))

        if EngineSettings.get_var("DRAW_BACKGROUND"):
            self.draw_background()

        self.__no_update_sprite_group.draw(self.__surface)
        self.__update_sprite_group.draw(self.__surface)

        for sprite in self.__player_sprite_group.sprites():
            sprite._update(True)

        self.__player_sprite_group.draw(self.__surface)

        self.render(self.player.rect.center)

    def __preload_player(self, level_map):
        for y in range(len(level_map)):
            for x in range(len(level_map[y])):
                if level_map[y][x] == "p":
                    return [x, y]

        raise ValueError("Игрок не обнаружен на карте")

    def add_decorataion_sprite(self, coords, data):
        sprite = DecorationSprite(self.__scene, coords=coords, **data)
        self.__env_sprite_group.add(sprite)
        self.__scene._game_stack.sprite_group.add(sprite)

        return sprite

    def render(self, player_coords):
        self.__move_map_for_player(player_coords)

    def __move_map_for_player(self, player_coords):
        move_percent = 30

        WIDTH, HEIGHT = EngineSettings.get_var("SIZE")

        if (
                WIDTH * move_percent / 100 >= player_coords[0] or
                player_coords[0] >= WIDTH * (100 - move_percent) / 100 or
                HEIGHT * move_percent / 100 >= player_coords[1] or
                player_coords[1] >= HEIGHT * (100 - move_percent) / 100
        ):

            x, y = 0, 0

            if WIDTH * move_percent / 100 >= player_coords[0]:
                x = WIDTH * move_percent / 100 - player_coords[0]
            elif player_coords[0] >= WIDTH * (100 - move_percent) / 100:
                x = WIDTH * (100 - move_percent) / 100 - player_coords[0]

            if HEIGHT * move_percent / 100 >= player_coords[1]:
                y = HEIGHT * move_percent / 100 - player_coords[1]
            elif player_coords[1] >= HEIGHT * (100 - move_percent) / 100:
                y = HEIGHT * (100 - move_percent) / 100 - player_coords[1]

            self.move_map((int(x), int(y)))

    def move_map(self, coords):
        x, y = coords
        self.start_player_pos[0] -= x / self.block_size
        self.start_player_pos[1] -= y / self.block_size

        self.__scene.move_all_sprites((x, y))
        self.__background_pos[0] += x * self.block_size / 10
        self.__background_pos[0] += x * self.block_size

    def save_map_dump(self):
        from .sprites.player.player import PlayerСharacteristics

        data = {
            "map_data": [[None for x in range(len(self.map_symbol[y]))] for y in range(len(self.map_symbol))],
            "player": {},
            "player_characteristics": {key: PlayerСharacteristics.__dict__[key] for key in
                                       filter(lambda x: "__" not in x, PlayerСharacteristics.__dict__)},
            "map_variables": {
                "start_player_pos": list(self.start_player_pos)
            }
        }

        for y in range(len(self.map_symbol)):
            for x in range(len(self.map_symbol[y])):
                if self.map[y][x] not in self.__scene.sprite_group:
                    continue

                rect = self.map[y][x].rect
                pre_data = {
                    "rect": {
                        "left": rect.x,
                        "top": rect.y,
                        "width": rect.width,
                        "height": rect.height
                    }
                }

                for key, value in self.map[y][x].__dict__.items():
                    if key in NEED_VARS:
                        pre_data[key] = value

                data["map_data"][y][x] = pre_data

        rect = self.player.rect

        pre_data = {
            "rect": {
                "left": rect.x,
                "top": rect.y,
                "width": rect.width,
                "height": rect.height
            },
        }

        for key, value in self.player.__dict__.items():
            if key in NEED_VARS:
                pre_data[key] = value

        data["player"] = pre_data

        with open("dump.json", "w") as dump_file:
            dump_file.write(json.dumps(data))

    def load_map_dump(self, dump_path):
        if "map_symbol" in self.__dict__:
            for y in range(len(self.map_symbol)):
                for x in range(len(self.map_symbol[y])):
                    if self.map[y][x]:
                        self.map[y][x].kill()

            self.player.kill()

            self.__player_sprite_group.empty()
            self.__update_sprite_group.empty()
            self.__no_update_sprite_group.empty()
            self.__scene.sprite_group.empty()

        self.create_map_2(load_level(EngineSettings.get_var("MAP_NAME")))

        with open(dump_path, "r") as dump_file:
            data = json.loads(dump_file.read())

        for y in range(len(self.map_symbol)):
            for x in range(len(self.map_symbol[y])):
                if self.map[y][x] not in self.__scene.sprite_group:
                    continue

                if self.map[y][x] and not data["map_data"][y][x]:
                    self.map[y][x].kill()
                    continue

                rect = data["map_data"][y][x]["rect"]
                del data["map_data"][y][x]["rect"]

                self.map[y][x].__dict__.update(data["map_data"][y][x])

                self.map[y][x].rect.x = rect["left"]
                self.map[y][x].rect.y = rect["top"]

        rect = data["player"]["rect"]
        del data["player"]["rect"]

        self.player.__dict__.update(data["player"])

        self.player.rect.x = rect["left"]
        self.player.rect.y = rect["top"]

        from .sprites.player.player import PlayerСharacteristics

        for key, value in data["player_characteristics"].items():
            setattr(PlayerСharacteristics, key, value)

        for key, value in data["map_variables"].items():
            setattr(self, key, value)

        self.player.update_ui()

NEED_VARS = ('width', 'height', "_PlayerSprite__count_heal", "_PlayerSprite__count_big_heal", "health")


import praw
import datetime
import time
from feedgen.feed import FeedGenerator

SELF_POST_BONUS = 150

def readSecrets():
    with open("redditAppData", "r") as f:
        lines = [l.rstrip("\n") for l in f.readlines()]
        client_id, client_secret, user_agent = lines
    return {
            "client_id":client_id,
            "client_secret":client_secret,
            "user_agent" : user_agent
            }

reddit = praw.Reddit(**readSecrets())

def readTopPosts(subredditName, numPosts, minUpvotes, filters):
    submissions = [submission for submission in list(reddit.subreddit(subredditName).top(limit=numPosts*10, time_filter="day"))]
    if filters != []:
        submissions = [submission for submission in submissions if (submission.link_flair_text is None) or not any(flairToFilter.lower() in submission.link_flair_text.lower() for flairToFilter in filters)]
    submissions = [sub for sub in submissions if getScore(sub) >= minUpvotes]
    return submissions[:numPosts]

def getScore(submission):
    if submission.is_self:
        return submission.score * SELF_POST_BONUS
    else:
        return submission.score

def generateRss(submissions, outputFile, title, description, link, _id):
    feedGen = FeedGenerator()
    feedGen.id(_id)
    feedGen.title(title)
    feedGen.link({"href":link})
    feedGen.description(description)
    for submission in submissions:
        feedItem = feedGen.add_entry()
        submissionItem(feedItem, submission)
    try:
        feedGen.rss_str(pretty=True)
    except ValueError:
        return
    feedGen.rss_file(outputFile)

def submissionItem(feedEntry, submission):
    feedEntry.title(submission.title)
    feedEntry.id(submission.url + "/")
    feedEntry.link({"href":submission.shortlink})
    feedEntry.description(getDescription(submission))

def getDescription(submission):
    description = submission.selftext
    if not submission.is_self:
        description = description + "
".format(url=submission.url)
    return description

def generateSubredditRss(subredditName, fname, numPosts, minUpvotes, filters):
    generateRss(
            submissions=readTopPosts(subredditName, numPosts, minUpvotes, filters),
            outputFile="feeds/{}.xml".format(fname),
            title="{}".format(subredditName),
            description="Top posts {}".format(subredditName),
            link="reddit.com/r/{}/top".format(subredditName),
            _id="reddit.com/r/{}/top".format(subredditName)
            )

def generateMultiSubredditRss(subreddits):
    submissions = []
    name = ""
    for (subreddit, numPosts, filters) in subreddits:
        submissions = submissions + readTopPosts(subreddit, numPosts, filters)
        name = name + subreddit + "+"
    name = name[:-1]

    generateRss(
            submissions=submissions,
            outputFile="feeds/{}.xml".format(name),
            title="Top posts {}".format(name),
            description="Top posts {}".format(name),
            link="reddit.com/r/{}/top".format(name),
            _id="reddit.com/r/{}/top".format(name)
            )

def getSubredditList():
    with open("subreddits", "r") as f:
        for l in f.readlines():
            name, fname, numPosts, minUpvotes, *filters = l.replace("\n", "").split()
            numPosts = int(numPosts)
            minUpvotes = int(minUpvotes)
            yield (name, fname, numPosts, minUpvotes, filters)

for (subreddit, fname, numPosts, minUpvotes, *filters) in getSubredditList():
    generateSubredditRss(subreddit, fname, numPosts, minUpvotes, *filters)


from unittest import TestCase, main as unittest_main, mock
from bson.objectid import ObjectId
from app import app

sample_game_id = ObjectId('5d55cffc4a3d4031f42827a3')
sample_game = {
    'title': 'Cat Images',
    'price': 'Cats acting weird',
    'images': 'https://upload.wikimedia.org/wikipedia/commons/e/ee/Grumpy_Cat_by_Gage_Skidmore.jpg'

}
sample_form_data = {
    'title': sample_game['title'],
    'price': sample_game['price'],
    'images': '\n'.join(sample_game['images'])
}

class gamesTests(TestCase):
    """Flask tests."""

    def setUp(self):
        """Stuff to do before every test."""

        # Get the Flask test client
        self.client = app.test_client()

        # Show Flask errors that happen during tests
        app.config['TESTING'] = True

    def test_index(self):
        """Test the games homepage."""
        result = self.client.get('/')
        self.assertEqual(result.status, '200 OK')
        self.assertIn(b'game', result.data)

    def test_new(self):
        """Test the new game creation page."""
        result = self.client.get('/games/new')
        self.assertEqual(result.status, '200 OK')
        self.assertIn(b'New game', result.data)

    @mock.patch('pymongo.collection.Collection.find_one')
    def test_show_game(self, mock_find):
        """Test showing a single game."""
        mock_find.return_value = sample_game

        result = self.client.get(f'/games/{sample_game_id}')
        self.assertEqual(result.status, '200 OK')
        self.assertIn(b'Cat images', result.data)

    @mock.patch('pymongo.collection.Collection.find_one')
    def test_edit_game(self, mock_find):
        """Test editing a single game."""
        mock_find.return_value = sample_game

        result = self.client.get(f'/games/{sample_game_id}/edit')
        self.assertEqual(result.status, '200 OK')
        self.assertIn(b'Cat images', result.data)

    @mock.patch('pymongo.collection.Collection.insert_one')
    def test_submit_game(self, mock_insert):
        """Test submitting a new game."""
        result = self.client.post('/games', data=sample_form_data)

        # After submitting, should redirect to that game's page
        self.assertEqual(result.status, '302 FOUND')
        mock_insert.assert_called_with(sample_game)

    @mock.patch('pymongo.collection.Collection.update_one')
    def test_update_game(self, mock_update):
        result = self.client.post(
            f'/games/{sample_game_id}', data=sample_form_data)

        self.assertEqual(result.status, '302 FOUND')
        mock_update.assert_called_with({'_id': sample_game_id}, {
                                       '$set': sample_game})

    @mock.patch('pymongo.collection.Collection.delete_one')
    def test_delete_game(self, mock_delete):
        form_data = {'_method': 'DELETE'}
        result = self.client.post(
            f'/games/{sample_game_id}/delete', data=form_data)
        self.assertEqual(result.status, '308 PERMANENT REDIRECT')
        mock_delete.assert_called_with({'_id': sample_game_id})

if __name__ == '__main__':
    unittest_main()


# from pudb import set_trace; set_trace()
from typing import List
import math
import string

class Solution:
    def isAlienSorted(self, words: List[str], order: str) -> bool:
        """LeetCode 953

        Convert each word to regular alphabet with letter corresponding to the
        order.

        O(MN + NlogN), where M is the average length of each word and N is the
        length of words. 46 ms, faster than 39.14%
        """
        m = {x: y for x, y in zip(order, string.ascii_lowercase)}
        converted = [''.join(m[le] for le in word) for word in words]
        return converted == sorted(converted)

sol = Solution()
tests = [
    (["hello","leetcode"], "hlabcdefgijkmnopqrstuvwxyz", True),
    (["word","world","row"], "worldabcefghijkmnpqstuvxyz", False),
    (["apple","app"], "abcdefghijklmnopqrstuvwxyz", False),
]

for i, (words, order, ans) in enumerate(tests):
    res = sol.isAlienSorted(words, order)
    if res == ans:
        print(f'Test {i}: PASS')
    else:
        print(f'Test {i}; Fail. Ans: {ans}, Res: {res}')


sids = ['100000001', '100000002', '100000003', '100000004']
midtermMarks = [52.0, 71.5, 90.0, 44.25]

with open('midterm_grades.csv', 'w') as file:
    for i in range(len(sids)):
        file.write(sids[i] + ',' + str(midtermMarks[i]) + '\n')

'''
100000001, 52.0
100000002, 71.5
'''

import json
with open('students.json', 'r') as file:
    studentList = json.load(file)
    carla = studentList[0]
    print('Grades for', carla['firstName'], carla['lastName'])
    for grade in carla['grades']:
        print(grade * 0.5)

try:
    result = 3 / 0
    #print(midtermMarks[4]) # also generates an exception
except IndexError as e:
    print('IndexError:', e)
except ZeroDivisionError as e:
    print('Cannot divide by zero')

class YouAreTooYoungError(Exception):
    pass

def buyBeer(age):
    if age < 19:
        raise YouAreTooYoungError('Wait until you are 19')
    print('Here is your beer madam/sir!')

try:
    buyBeer(20)
except YouAreTooYoungError as error:
    print('Sorry, but you cannot buy beer yet!')

for n in [5,4,3,2,1,0]:
    try:
        print(1/n)
    except ZeroDivisionError as error:
        print('infinity')

print('I caught all them exceptions!')


import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn import datasets, svm
import re
from collections import defaultdict
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.preprocessing import LabelEncoder
from sklearn.model_selection import train_test_split
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import confusion_matrix, auc

dataset = pd.read_csv("./dataset/student-mat.csv", encoding='latin1')
list(dataset)
dataset.head()

g = dataset.isnull().mean()

print(dataset.shape)

def make_meshgrid(x, y, h=.02):
    """Create a mesh of points to plot in
    Parameters
    ----------
    x: data to base x-axis meshgrid on
    y: data to base y-axis meshgrid on
    h: stepsize for meshgrid, optional
    Returns
    -------
    xx, yy : ndarray
    """
    x_min, x_max = x.min() - 1, x.max() + 1
    y_min, y_max = y.min() - 1, y.max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                         np.arange(y_min, y_max, h))
    return xx, yy

def plot_contours(ax, clf, xx, yy, **params):
    """Plot the decision boundaries for a classifier.
    Parameters
    ----------
    ax: matplotlib axes object
    clf: a classifier
    xx: meshgrid ndarray
    yy: meshgrid ndarray
    params: dictionary of params to pass to contourf, optional
    """
    Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    out = ax.contourf(xx, yy, Z, **params)
    return out

C = 1.0

u = np.random.randint(0, 3, dataset.shape[0])
dataset_train = dataset[u != 0]
dataset_test = dataset[u == 0]

x0 = dataset_train.absences
x1 = dataset_train.freetime
x2 = dataset_train.age
x3 = dataset_train.Medu
x4 = dataset_train.Fedu
x5 = dataset_train.goout
x_train = pd.concat([x0, x1, x2, x3, x4, x5], axis = 1)

x0 = dataset_test.absences
x1 = dataset_test.freetime
x2 = dataset_test.age
x3 = dataset_test.Medu
x4 = dataset_test.Fedu
x5 = dataset_test.goout
x_test = pd.concat([x0, x1, x2, x3, x4, x5], axis = 1)

print(x_train.shape)
print(x_test.shape)

y_train = np.ravel(pd.get_dummies(dataset_train.schoolsup, drop_first=True))
y_test = np.ravel(pd.get_dummies(dataset_test.schoolsup, drop_first=True))

print("y_train shape ", y_train.shape)
print("y_test shape ", y_test.shape)

for fig_num, kernel in enumerate(('linear', 'rbf', 'poly')):
    print("training new model")
    clf = svm.SVC(kernel=kernel, gamma=0.01, C=100)
    clf.fit(x_train, y_train)
    score = clf.score(x_test, y_test)
    print("Score", score)

"""models = (svm.SVC(kernel='linear', C=C), svm.LinearSVC(C=C), svm.SVC(kernel='rbf', gamma=0.7, C=C), svm.SVC(kernel='poly', degree=3, C=C))

models = (clf.fit(x_train, y_train) for clf in models)
score = clf.score(x_test, y_test)
print(list(score))"""


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

import fcntl
import logging
import datetime

from odoo import models, fields, api
from odoo.modules.module import get_module_resource

_logger = logging.getLogger(__name__)

def _file_lock(flag=fcntl.LOCK_EX):
    FILE_PATH = get_module_resource('demo_sequence', 'static/SN.LOCK')
    file = open(FILE_PATH)
    fcntl.flock(file.fileno(), flag)
    _logger.info('Acquire Lock')
    return file

class DemoSequence(models.Model):
    _name = 'demo_sequence.fcntl'

    sn = fields.Char('SN', index=True)
    name = fields.Char('name')

    _sql_constraints = [
        ('sn_uniq', 'unique (sn)', "SN is duplicated!"),
    ]

    @api.model
    def create(self, vals):
        file = _file_lock()
        sn_prefix = 'SN' + datetime.date.today().strftime("%y%m%d")
        obj = self.env['demo_sequence.fcntl'].search_read([('sn', '=like', sn_prefix + '%')], limit=1, order='sn DESC')
        # 今天已经有序列号，在最新的序列号上递增
        if obj and obj[0]['sn'].startswith(sn_prefix):
            sn_suffix = int(obj[0]['sn'][-3:]) + 1
            vals['sn'] = sn_prefix + str(sn_suffix).zfill(3) # 补0
        else:
            vals['sn'] = sn_prefix + '001'

        res = super(DemoSequence, self).create(vals)
        # 关闭文件将自动解锁
        file.close()
        return res

class DemoSequence2(models.Model):
    _name = 'demo_sequence.sequence'

    sn = fields.Char('SN', index=True)
    name = fields.Char('name')

    _sql_constraints = [
        ('sn_uniq', 'unique (sn)', "SN is duplicated!"),
    ]

    @api.model
    def create(self, vals):
        vals['sn'] = self.env['ir.sequence'].next_by_code('demo_sequence.sequence')
        return super(DemoSequence2, self).create(vals)


#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
TODO: allow online interating. take a step.
"""

""" Usage
1. evaluation
    1. (multi-seeds) simulate a dataset with the behaviour policy, OPE the target policy.
    2. evaluate in the simulator (multi-seeds)
2. offline RL
    1. (multi-seeds) simulate a dataset with the behaviour policy, learn a policy (ours or competing), and evaluate in the simulator (multi-seeds)
"""
# import os, sys
# package_path = os.path.dirname(os.path.abspath(os.getcwd()))
# sys.path.insert(0, package_path + "/test_func")
# from _util_TRPO import *
from _util import *
import operator
########################################################################################################################################################################################################################################################################################################################################################################################

class OhioSimulator():
    def __init__(self,s_min=0,s_max=300, sd_G = 3, T = 20, N = 10):
        # the following parameters will not change with the LM fitting
        self.CONST = 39.03
        #####
        self.init_u_G = 162
        self.init_sd_G = 60
        #####
        self.p_D, self.u_D, self.sd_D = 0.17, 44.4, 35.5
        self.p_E, self.u_E, self.sd_E = 0.05, 4.9, 1.04
        self.p_A = [0.805, 0.084, 0.072, 0.029, 0.010] # new discritization

        self.range_a = [0, 1, 2, 3, 4]
        # left to right: t-4, .. , t-1
        self.coefficients = [-0.008     ,  0.106     , -0.481     ,  1.171  # glucose
          , 0.008     ,  -0.004     ,  0.08      ,  0.23      # diet
          , 0.009     , -1.542     , 3.097     , -3.489  # exercise
          , -0.30402253, -2.02343638, -0.3310525 , -0.43941028] # action
        self.tran_mat = np.expand_dims(arr(self.coefficients), 0)
        self.sd_G = sd_G
        self.T, self.N = T, N
        self.seed = 42
        self.true_lag = 4

        self.s_min = s_min
        self.s_max = s_max

    def Glucose2Reward(self, gls):
        low_gl, high_gl = 80, 140
        rewards = np.select([gls >= high_gl, gls <= low_gl, np.multiply(low_gl < gls, gls < high_gl)]
          , [-(gls - high_gl) ** 1.35 / 30, -(low_gl - gls) ** 2 / 30, 0])
        return rewards
############################################################################################################################################
############################################################################################################################################

    def init_MDPs(self, TT = None, NN = None, seed = 0):
        """ Randomly initialize
        1. G_t [0,..., 4]
        1. the other state variable: random.
        2. errors for G_t
        3. when to take how many diets/exercises [matters?]

        where T varies, seed is diff; dominated by init several states, and hence values are not monotone.
        """
        np.random.seed(seed)
        if TT is None and NN is None:
            T, N = self.T, self.N
        else:
            T,N = TT,NN
        obs = np.zeros((3, T, N))  # [Gi, D, Ex]
        e_D = abs(rnorm(self.u_D, self.sd_D, T * N))
        e_E = abs(rnorm(self.u_E, self.sd_E, T * N))
        e_G = rnorm(0, self.sd_G, T * N).reshape((T, N))

        obs[0, :4, :] = rnorm(self.init_u_G, self.init_sd_G, 4 * N).reshape(4, N)
        obs[1, :, :] = (rbin(1, self.p_D, T * N) * e_D).reshape((T, N))
        obs[2, :, :] = (rbin(1, self.p_E, T * N) * e_E).reshape((T, N))
        return obs, e_G
    ##################################################################################################################################################################
    def conc_single(self, obs, actions, t):
        # obs = [3, T]
        # actions = [T]
        As = actions[(t - self.true_lag + 1):t]
        S = obs[:, (t - self.true_lag + 1):(t + 1)]
        s = S.ravel(order='C')
        s = np.append(s, As)
        return s

    def reset(self, T = 1000):
        self.seed += 1
        if self.seed > 1e4:
            self.seed = 42
        self.T = T
        np.random.seed(self.seed)
        ### Initialization
        self.obs = np.zeros((3, T))
        self.e_D = abs(rnorm(self.u_D, self.sd_D, T))
        self.e_E = abs(rnorm(self.u_E, self.sd_E, T))
        self.e_G = rnorm(0, self.sd_G, T)
        self.obs[0, :4] = rnorm(self.init_u_G, self.init_sd_G, 4)
        self.obs[1, :] = rbin(1, self.p_D, T) * self.e_D
        self.obs[2, :] = rbin(1, self.p_E, T) * self.e_E
        self.t = 3
        self.actions = zeros(T)
        self.actions[:3] = np.random.choice(range(len(self.p_A)), size = 3, p = self.p_A)
        states = self.conc_single(obs = self.obs, actions = self.actions, t = self.t)
        return states

    def online_step(self, action):
        self.actions[self.t] = action
        # 1. transition
        states = self.conc_single(obs = self.obs, actions = self.actions, t = self.t)
        SA = np.append(states, action)
        self.t += 1
        next_G = self.CONST + self.tran_mat.dot(SA) + self.e_G[self.t]
        # 2. store
        self.obs[0, self.t] = next_G
        # 3. return
        observation_ = self.conc_single(obs = self.obs, actions = self.actions, t = self.t)
        done = (self.t == (self.T - 1))
        reward = self.Glucose2Reward(next_G)
        reward += (randn(1) / 1e4)
        reward = np.squeeze(reward)
#         s = np.hstack([obs[(self.t - self.true_lag + 1):self.t, :], actions[(self.t - self.true_lag + 1):self.t]]).ravel(order='C')
#         s = np.append(s, obs[self.t, :].ravel())
        return observation_, reward, done

############################################################################################################################################
############################################################################################################################################
    def step(self, states = None, errors = None):
        return np.array(self.CONST).reshape((1, 1)) + self.tran_mat.dot(states) + errors

    def simu_one_seed(self, seed = 42):
        """ Simulate N patient trajectories with length T, calibrated from the Ohio dataset.
        Returns:
            trajs = [traj], where traj [[S, A, R, SS]]
        """
        #########
        T, N = self.T, self.N
        np.random.seed(seed)
        # Initialization
        obs, e_G = self.init_MDPs(seed = seed)
        actions = np.random.choice(range(len(self.p_A)), size = T * N, p = self.p_A).reshape((T, N))

        ######### Transition
        for t in range(4, T):
            states = self.concatenate_useful_obs(obs = obs, actions = actions, t = t)
            obs[0, t, :] = self.step(states = states, errors = np.array([e_G[t, :]]))
        actions = actions.astype(float)

        ######### Collection: obs = (3, T, N), actions = (T, N)
        # conc_SA_2_state(obs, actions, t, multiple_N = False, J = 4)
        trajs = [[] for i in range(N)]
        for t in range(4, T - 1):
            conc_s = self.conc_SA_2_state(obs, actions, t, multiple_N = True, J = 4) # (dim_with_J, N)
            conc_ss = self.conc_SA_2_state(obs, actions, t + 1, multiple_N = True, J = 4)# (dim_with_J, N)
            As = actions[t, :]
            Rs = self.Glucose2Reward(conc_ss[-3, :])
            for i in range(N):
                trajs[i].append([conc_s[:, i], As[i], Rs[i], conc_ss[:, i]])
        return trajs

    def simu_multi_seeds(self, M, parallel = True):
        if parallel:
            return parmap(self.simu_one_seed, range(M))
        else:
            return [self.simu_one_seed(i) for i in range(M)]
############################################################################################################################################
############################################################################################################################################
    def cum_r(self, rewards, gamma):
        """ rewards -> culmulative reward
        """
        return sum(map(operator.mul, [gamma ** j for j in range(len(rewards))], rewards))

    def eval_trajs(self, trajs = None, gamma = .8):
        def get_rew(i):
            glucoses = arr([item[0][-3] for item in trajs[i]])
            rewards = np.roll(self.Glucose2Reward(glucoses), shift = -1).reshape(-1, 1)
            discounted_value = np.round(self.cum_r(rewards, gamma), 3)[0]
            average_value = round(np.mean(rewards))
            return [rewards, discounted_value, average_value]
        res = parmap(get_rew, range(len(trajs)))
        discounted_values = [a[1] for a in res]
        average_values = [a[2] for a in res]
        return discounted_values, average_values

    def eval_policy(self, pi, gamma = 1, seed = 42, return_value = True):
        """ Evaluate the value of a policy in simulation.
        sample the first four time points，and then begin to follow the policy and collect rewards
        transform into matrix so that linear transition is easier
        Concatenate data for training and evaluation
        """
        pi.seed = seed
        np.random.seed(seed)
        ##############################
        obs, e_G = self.init_MDPs(seed = seed) # obs = [3, T, N]
        actions = np.zeros((self.T, self.N)) # store previous actions+
        actions[:4, :] = np.random.choice(range(len(self.p_A)), size = 4 * self.N, p = self.p_A).reshape((4, self.N))
        self.init = [obs.copy(), e_G.copy(), actions[:4, :].copy()]
        ##############################
        curr_time = now()
        for t in range(4, self.T):
            # next observations: based on ...,t-1, to decide t.
            states = self.concatenate_useful_obs(obs = obs, actions = actions, t = t)
            obs[0, t, :] = self.step(states = states, errors = np.array([e_G[t, :]]))

            # choose actions based on status. obs = [3, T, N]
            S = self.conc_SA_2_state(obs, actions, t, multiple_N = True).T #  N * dim
            if t == 4:
                self.init_state = S.copy()
            S_norm = (S - self.s_min)/(self.s_max-self.s_min)
            A_t = pi.sample_A(S_norm) # s [N * dx] -> actions [N * 1] -> 1 * N
            actions[t, :] = np.squeeze(A_t)
        ##############################
        if return_value:
            discounted_values, average_values = self.cal_reward(obs, gamma)
            return discounted_values, average_values # len-N
        else:
            ######### Collection: obs = (3, T, N), actions = (T, N)
            # conc_SA_2_state(obs, actions, t, multiple_N = False, J = 4)
            trajs = [[] for i in range(self.N)]
            for t in range(4, self.T - 1):
                conc_s = self.conc_SA_2_state(obs, actions, t, multiple_N = True, J = 4) # (dim_with_J, N)
                conc_ss = self.conc_SA_2_state(obs, actions, t + 1, multiple_N = True, J = 4)# (dim_with_J, N)
                As = actions[t, :]
                Rs = self.Glucose2Reward(conc_ss[-3, :])
                for i in range(self.N):
                    trajs[i].append([conc_s[:, i], As[i], Rs[i], conc_ss[:, i]])
            return trajs

    def generate_mixture_policy_trajectory(self, pi, epsilon, gamma = 1, seed = 42):
        """ Evaluate the value of a policy in simulation.
        sample the first four time points，and then begin to follow the policy and collect rewards
        transform into matrix so that linear transition is easier
        Concatenate data for training and evaluation
        """
        pi.seed = seed
        np.random.seed(seed)
        ##############################
        obs, e_G = self.init_MDPs(seed = seed) # obs = [3, T, N]
        actions = np.zeros((self.T, self.N)) # store previous actions+
        actions[:4, :] = np.random.choice(range(len(self.p_A)), size = 4 * self.N, p = self.p_A).reshape((4, self.N))
        self.init = [obs.copy(), e_G.copy(), actions[:4, :].copy()]
        ##############################

        curr_time = now()
        for t in range(4, self.T):
            # next observations: based on ...,t-1, to decide t.
            states = self.concatenate_useful_obs(obs = obs, actions = actions, t = t)
            obs[0, t, :] = self.step(states = states, errors = np.array([e_G[t, :]]))

            # choose actions based on status. obs = [3, T, N]
            S = self.conc_SA_2_state(obs, actions, t, multiple_N = True).T #  N * dim
            if t == 4:
                self.init_state = S.copy()

            if np.random.random() < epsilon:
                S_norm = (S - self.s_min)/(self.s_max-self.s_min)
                A_t = pi.sample_A(S_norm) # s [N * dx] -> actions [N * 1] -> 1 * N
#                 A_t = pi.sample_A(S) # s [N * dx] -> actions [N * 1] -> 1 * N
            else:
                A_t =  np.random.choice(range(len(self.p_A)), size = self.N, p = self.p_A)
            actions[t, :] = np.squeeze(A_t)
        discounted_values, average_values = self.cal_reward(obs, gamma)

        ##############################

        trajs = [[] for i in range(self.N)]
        for t in range(4, self.T - 1):
            conc_s = self.conc_SA_2_state(obs, actions, t, multiple_N = True, J = 4) # (dim_with_J, N)
            conc_ss = self.conc_SA_2_state(obs, actions, t + 1, multiple_N = True, J = 4)# (dim_with_J, N)
            As = actions[t, :]
            Rs = self.Glucose2Reward(conc_ss[-3, :])
            for i in range(self.N):
                trajs[i].append([conc_s[:, i], As[i], Rs[i], conc_ss[:, i]])
        return trajs,discounted_values, average_values

##############################TODO######################################################

    def sample_S_4VE(self, pi, NN, TT,  seed = 42):
        """ Evaluate the value of a policy in simulation.
        sample the first four time points，and then begin to follow the policy and collect rewards
        transform into matrix so that linear transition is easier
        Concatenate data for training and evaluation
        """
        pi.seed = seed
        np.random.seed(seed)
        ##############################
        TT_trajs = TT+4
        obs, e_G = self.init_MDPs(TT = TT_trajs, NN = NN, seed = seed) # obs = [3, T, N]
        actions = np.zeros((TT_trajs, NN)) # store previous actions+
        S0 = self.conc_SA_2_state(obs, actions, 4, multiple_N = True).T
        dim_S = S0.shape[1]
        gene_S = np.zeros((TT,NN,dim_S))

        actions[:4, :] = np.random.choice(range(len(self.p_A)), size = 4 * NN, p = self.p_A).reshape((4, NN))
        self.init = [obs.copy(), e_G.copy(), actions[:4, :].copy()]
        ##############################
        curr_time = now()
        for t in range(4, TT_trajs):
            # next observations: based on ...,t-1, to decide t.
            states = self.concatenate_useful_obs(obs = obs, actions = actions, t = t)
            obs[0, t, :] = self.step(states = states, errors = np.array([e_G[t, :]]))

            # choose actions based on status. obs = [3, T, N]
            S = self.conc_SA_2_state(obs, actions, t-1, multiple_N = True).T #  N * dim
            gene_S[t-4,:,:] = S
            if t == 4:
                self.init_state = S.copy()

            S_norm = (S - self.s_min)/(self.s_max-self.s_min)
            A_t = pi.sample_A(S_norm) # s [N * dx] -> actions [N * 1] -> 1 * N
#             A_t = pi.sample_A(S) # s [N * dx] -> actions [N * 1] -> 1 * N
            actions[t, :] = np.squeeze(A_t)
        ##############################
        gene_S = gene_S.transpose(1,0,2)
        gene_S = gene_S.reshape(-1, dim_S)  # stack gene_S; (TTNN, dim_S)
        return gene_S

    def sample_cond_S_4VE(self, states, actions, next_states, pi, M, TT, seed = 42):

        pi.seed = seed
        np.random.seed(seed)
        ##############################
        TT_trajs = TT+4
        dim_S = states.shape[1]
        TN = states.shape[0]
        obs, e_G = self.init_MDPs(TT = TT_trajs, NN = TN*M, seed = seed) # obs = [3, T, N]
        A = np.zeros((TT_trajs, M*TN)) # store previous actions+
#         S0 = self.conc_SA_2_state(obs, actions, 4, multiple_N = True).T

        gene_S = np.zeros((TT,TN*M,dim_S))
        #####################################
        S0 = row_repeat(states,M,full_block=True)

        A0 = np.tile(actions,M)
        J = int(dim_S/4)
        for i in range(J):
            obs[:,i] = S0[:,(i*4):(i*4)+3].transpose()
            A[i,:] = S0[:,i*4+3]

        obs[:,3] = S0[:,12:].transpose()
        A[3, :] = A0
        self.init = [obs.copy(), e_G.copy(), A[:4, :].copy()]
        ##############################
        for t in range(4, TT_trajs):
            # next observations: based on ...,t-1, to decide t.
            states = self.concatenate_useful_obs(obs = obs, actions = A, t = t)
            obs[0, t, :] = self.step(states = states, errors = np.array([e_G[t, :]]))

            # choose actions based on status. obs = [3, T, N]
            S = self.conc_SA_2_state(obs, A, t-1, multiple_N = True).T #  N * dim
            gene_S[t-4,:,:] = S
            if t == 4:
                self.init_state = S.copy()

            S_norm = (S - self.s_min)/(self.s_max-self.s_min)
            A_t = pi.sample_A(S_norm) # s [N * dx] -> actions [N * 1] -> 1 * N
#             A_t = pi.sample_A(S) # s [N * dx] -> actions [N * 1] -> 1 * N
            A[t, :] = np.squeeze(A_t)
        ##############################

        gene_S = gene_S.transpose(1,0,2)
        gene_S = gene_S.reshape(TN, M, TT, dim_S,order='F')
        #########################################################
        obs, e_G = self.init_MDPs(TT = TT_trajs, NN = TN*M, seed = seed) # obs = [3, T, N]
        A = np.zeros((TT_trajs, M*TN)) # store previous actions+
        gene_S_star = np.zeros((TT,TN*M,dim_S))
        S0 = row_repeat(next_states,M,full_block=True)
        S0_norm = (S0 - self.s_min)/(self.s_max-self.s_min)
        A0 = pi.sample_A(S0_norm) # s [N * dx] -> actions [N * 1] -> 1 * N
#         A0 = pi.sample_A(S0)

        J = int(dim_S/4)
        for i in range(J):
            obs[:,i] = S0[:,(i*4):(i*4)+3].transpose()
            A[i,:] = S0[:,i*4+3]

        obs[:,3] = S0[:,12:].transpose()
        A[3, :] = np.squeeze(A0)
        self.init = [obs.copy(), e_G.copy(), A[:4, :].copy()]
        ##############################
        for t in range(4, TT_trajs):
            # next observations: based on ...,t-1, to decide t.
            states = self.concatenate_useful_obs(obs = obs, actions = A, t = t)
            obs[0, t, :] = self.step(states = states, errors = np.array([e_G[t, :]]))

            # choose actions based on status. obs = [3, T, N]
            S = self.conc_SA_2_state(obs, A, t-1, multiple_N = True).T #  N * dim
            gene_S_star[t-4,:,:] = S
            if t == 4:
                self.init_state = S.copy()

            S_norm = (S - self.s_min)/(self.s_max-self.s_min)
            A_t = pi.sample_A(S_norm) # s [N * dx] -> actions [N * 1] -> 1 * N
#             A_t = pi.sample_A(S) # s [N * dx] -> actions [N * 1] -> 1 * N
            A[t, :] = np.squeeze(A_t)
        ##############################
        gene_S_star = gene_S_star.transpose(1,0,2)
        gene_S_star = gene_S_star.reshape(TN, M, TT, dim_S,order='F')
        return [gene_S,gene_S_star]

    def cal_reward(self, obs, gamma):
        all_rewards = self.Glucose2Reward(obs)
        all_rewards = all_rewards[0]
        all_rewards = np.roll(all_rewards[4:], shift = -1, axis = 0)
        gammas = np.expand_dims(arr([gamma ** j for j in range(all_rewards.shape[0])]), 0)
        discounted_values = np.dot(gammas, all_rewards)
        average_values = np.mean(all_rewards, 0)
        return discounted_values, average_values
    #########################################################################################################

############################################################################################################################################
############################################################################################################################################

    def concatenate_useful_obs(self, obs, actions, t, true_lag = 4):
        # (dim_with_J, N)
        r = np.vstack([
            obs[0, (t - true_lag):t, :], obs[1, (t - true_lag):t, :],
            obs[2, (t - true_lag):t, :], actions[(t - true_lag):t, :]])
        return r

    def conc_SA_2_state(self, obs, actions, t, multiple_N = False, J = 4):
        """ to form a lag-J states from history obs and A
        """
        # dim = (3, T, N)
        N = obs.shape[2]
        dim_obs = 3
        s = np.vstack(([
            obs[:, (t - J + 1 ):t, :],
            actions[(t - J + 1):t, :].reshape((1, J - 1, N))])) # extend_dim for first one
        s = s.reshape(((dim_obs + 1) * (J - 1), N), order = 'F')
        obs_0 = obs[:, t, :] # 3 * N
        s = np.vstack([s, obs_0])
        return s # (dim_with_J, N)

    def sample(self, states, actions):

        trans_mat = np.reshape(np.array(self.coefficients).reshape(4,4),(-1,1),order='F')
        SA = np.hstack([states,actions])
        TN = int(len(SA))
        next_g = SA.dot(trans_mat) +  rnorm(0,self.sd_G,TN).reshape(-1,1) +self.CONST

        e_D = abs(rnorm(self.u_D, self.sd_D, TN))
        e_E = abs(rnorm(self.u_E, self.sd_E, TN))
        next_d = (rbin(1, self.p_D, TN) * e_D).reshape(-1,1)
        next_e = (rbin(1, self.p_E, TN) * e_E).reshape(-1,1)
        ns = np.hstack([next_g,next_d,next_e])

        next_states = np.hstack([SA[:,4:],ns])
        return next_states