import json
import logging
import os
import time

import jsonschema
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtNetwork import (
    QNetworkAccessManager,
    QNetworkReply,
    QNetworkRequest,
)

import client
import fa
import util
from api.replaysapi import ReplaysApiConnector
from config import Settings
from downloadManager import DownloadRequest
from fa.replay import replay
from model.game import GameState
from replays.replayitem import ReplayItem, ReplayItemDelegate
from replays.replayToolbox import ReplayToolboxHandler
from util.gameurl import GameUrl, GameUrlType

logger = logging.getLogger(__name__)

# Replays uses the new Inheritance Based UI creation pattern
# This allows us to do all sorts of awesome stuff by overriding methods etc.

FormClass, BaseClass = util.THEME.loadUiType("replays/replays.ui")

class LiveReplayItem(QtWidgets.QTreeWidgetItem):
    LIVEREPLAY_DELAY = 5 * 60

    def __init__(self, game):
        QtWidgets.QTreeWidgetItem.__init__(self)
        self._game = game
        if game.launched_at is not None:
            self.launch_time = game.launched_at
        else:
            self.launch_time = time.time()
        self._map_dl_request = DownloadRequest()
        self._map_dl_request.done.connect(self._map_preview_downloaded)

        self._game.updated.connect(self._update_game)
        self._set_show_delay()
        self._update_game(self._game)

    def _set_show_delay(self):
        if time.time() - self.launch_time < self.LIVEREPLAY_DELAY:
            self.setHidden(True)
            # Wait until the replayserver makes the replay available
            elapsed_time = time.time() - self.launch_time
            delay_time = self.LIVEREPLAY_DELAY - elapsed_time
            QtCore.QTimer.singleShot(1000 * delay_time, self._show_item)

    def _show_item(self):
        self.setHidden(False)

    def _map_preview_downloaded(self, mapname, result):
        if mapname != self._game.mapname:
            return
        path, is_local = result
        icon = util.THEME.icon(path, is_local)
        self.setIcon(0, icon)

    def _update_game(self, game):
        if game.state == GameState.CLOSED:
            return

        self.takeChildren()     # Clear the children of this item
        self._set_debug_tooltip(game)
        self._set_game_map_icon(game)
        self._set_misc_formatting(game)
        self._set_color(game)
        self._generate_player_subitems(game)

    def _set_debug_tooltip(self, game):
        info = game.to_dict()
        tip = ""
        for key in list(info.keys()):
            tip += "'{}' : '{}'<br/>".format(key, info[key])
        self.setToolTip(1, tip)

    def _set_game_map_icon(self, game):
        if game.featured_mod == "coop":  # no map icons for coop
            icon = util.THEME.icon("games/unknown_map.png")
        else:
            icon = fa.maps.preview(game.mapname)
            if not icon:
                dler = client.instance.map_downloader
                dler.download_preview(game.mapname, self._map_dl_request)
                icon = util.THEME.icon("games/unknown_map.png")
        self.setIcon(0, icon)

    def _set_misc_formatting(self, game):
        self.setToolTip(0, fa.maps.getDisplayName(game.mapname))

        time_fmt = "%Y-%m-%d  -  %H:%M"
        launch_time = time.strftime(time_fmt, time.localtime(self.launch_time))
        self.setText(0, launch_time)

        colors = client.instance.player_colors
        self.setForeground(0, QtGui.QColor(colors.get_color("default")))
        if game.featured_mod == "ladder1v1":
            self.setText(1, game.title)
        else:
            self.setText(1, game.title + "    -    [host: " + game.host + "]")
        self.setForeground(1, QtGui.QColor(colors.get_color("player")))
        self.setText(2, game.featured_mod)
        self.setTextAlignment(2, QtCore.Qt.AlignCenter)

    def _is_me(self, name):
        return client.instance.login == name

    def _is_friend(self, name):
        playerid = client.instance.players.getID(name)
        return client.instance.me.relations.model.is_friend(playerid)

    def _is_online(self, name):
        return name in client.instance.players

    def _set_color(self, game):
        my_game = any(self._is_me(p) for p in game.players)
        friend_game = any(self._is_friend(p) for p in game.players)
        if my_game:
            my_color = "self"
        elif friend_game:
            my_color = "friend"
        else:
            my_color = "player"
        colors = client.instance.player_colors
        self.setForeground(1, QtGui.QColor(colors.get_color(my_color)))

    def _generate_player_subitems(self, game):
        if not game.teams:
            self.setDisabled(True)
            return
        for player in game.playing_players:  # observers don't stream replays
            playeritem = self._create_playeritem(game, player)
            self.addChild(playeritem)

    def _create_playeritem(self, game, name):
        item = QtWidgets.QTreeWidgetItem()
        item.setText(0, name)

        if self._is_me(name):
            player_color = "self"
        elif self._is_friend(name):
            player_color = "friend"
        elif self._is_online(name):
            player_color = "player"
        else:
            player_color = "default"
        colors = client.instance.player_colors
        item.setForeground(0, QtGui.QColor(colors.get_color(player_color)))

        if self._is_online(name):
            item.gurl = self._generate_livereplay_link(game, name)
            item.setToolTip(0, item.gurl.to_url().toString())
            item.setIcon(0, util.THEME.icon("replays/replay.png"))
        else:
            item.setDisabled(True)
        return item

    def _generate_livereplay_link(self, game, name):
        return GameUrl(
            GameUrlType.LIVE_REPLAY, game.mapname,
            game.featured_mod, game.uid, name,
        )

    def __lt__(self, other):
        return self.launch_time < other.launch_time

    def __le__(self, other):
        return self.launch_time <= other.launch_time

    def __gt__(self, other):
        return self.launch_time > other.launch_time

    def __ge__(self, other):
        return self.launch_time >= other.launch_time

class LiveReplaysWidgetHandler(object):
    def __init__(self, liveTree, client, gameset):
        self.liveTree = liveTree
        self.liveTree.itemDoubleClicked.connect(self.liveTreeDoubleClicked)
        self.liveTree.itemPressed.connect(self.liveTreePressed)
        self.liveTree.header().setSectionResizeMode(
            0, QtWidgets.QHeaderView.ResizeToContents,
        )
        self.liveTree.header().setSectionResizeMode(
            1, QtWidgets.QHeaderView.Stretch,
        )
        self.liveTree.header().setSectionResizeMode(
            2, QtWidgets.QHeaderView.ResizeToContents,
        )

        self.client = client
        self.gameset = gameset
        self.gameset.newLiveGame.connect(self._newGame)
        self._addExistingGames(gameset)

        self.games = {}

    def liveTreePressed(self, item):
        if QtWidgets.QApplication.mouseButtons() != QtCore.Qt.RightButton:
            return

        if self.liveTree.indexOfTopLevelItem(item) != -1:
            item.setExpanded(True)
            return

        menu = QtWidgets.QMenu(self.liveTree)

        # Actions for Games and Replays
        actionReplay = QtWidgets.QAction("Replay in FA", menu)
        actionLink = QtWidgets.QAction("Copy Link", menu)

        # Adding to menu
        menu.addAction(actionReplay)
        menu.addAction(actionLink)

        # Triggers
        actionReplay.triggered.connect(
            lambda: self.liveTreeDoubleClicked(item),
        )
        actionLink.triggered.connect(
            lambda: QtWidgets.QApplication.clipboard().setText(
                item.toolTip(0),
            ),
        )

        # Adding to menu
        menu.addAction(actionReplay)
        menu.addAction(actionLink)

        # Finally: Show the popup
        menu.popup(QtGui.QCursor.pos())

    def liveTreeDoubleClicked(self, item):
        """
        This slot launches a live replay from eligible items in liveTree
        """

        if item.isDisabled():
            return

        if (
            self.client.games.party
            and self.client.games.party.memberCount > 1
        ):
            if not self.client.games.leave_party():
                return

        if self.liveTree.indexOfTopLevelItem(item) == -1:
            # Notify other modules that we're watching a replay
            self.client.viewing_replay.emit(item.gurl)
            replay(item.gurl)

    def _addExistingGames(self, gameset):
        for game in gameset.values():
            if game.state == GameState.PLAYING:
                self._newGame(game)

    def _newGame(self, game):
        item = LiveReplayItem(game)
        self.games[game] = item
        self.liveTree.insertTopLevelItem(0, item)
        game.updated.connect(self._check_game_closed)

    def _check_game_closed(self, game):
        if game.state == GameState.CLOSED:
            game.updated.disconnect(self._check_game_closed)
            self._removeGame(game)

    def _removeGame(self, game):
        self.liveTree.takeTopLevelItem(
            self.liveTree.indexOfTopLevelItem(self.games[game]),
        )
        del self.games[game]

class ReplayMetadata:
    def __init__(self, data):
        self.raw_data = data
        self.data = None
        self.is_broken = False
        self.is_incomplete = False

        try:
            self.data = json.loads(data)
        except json.decoder.JSONDecodeError:
            self.is_broken = True
            return

        self._validate_data()

    # FIXME - this is what the widget uses so far, we should define this
    # schema precisely in the future
    def _validate_data(self):
        if not isinstance(self.data, dict):
            self.is_broken = True
            return
        if not self.data.get('complete', False):
            self.is_incomplete = True
            return

        replay_schema = {
            "type": "object",
            "properties": {
                "num_players": {"type": "number"},
                "launched_at": {"type": "number"},
                "game_time": {
                    "type": "number",
                    "minimum": 0,
                },
                "mapname": {"type": "string"},
                "title": {"type": "string"},
                "teams": {
                    "type": "object",
                    "patternProperties": {
                        ".*": {
                            "type": "array",
                            "items": {"type": "string"},
                        },
                    },
                },
                "featured_mod": {"type": "string"},
            },
            "required": [
                "num_players", "mapname", "title", "teams",
                "featured_mod",
            ],
        }
        try:
            jsonschema.validate(self.data, replay_schema)
        except jsonschema.ValidationError:
            self.is_broken = True

    def launch_time(self):
        if 'launched_at' in self.data:
            return self.data['launched_at']
        elif 'game_time' in self.data:
            return self.data['game_time']
        else:
            return time.time()  # FIXME

class LocalReplayItem(QtWidgets.QTreeWidgetItem):
    def __init__(self, replay_file, metadata=None):
        QtWidgets.QTreeWidgetItem.__init__(self)
        self._replay_file = replay_file
        self._metadata = metadata
        self._map_dl_request = DownloadRequest()
        self._map_dl_request.done.connect(self._map_preview_downloaded)
        self._setup_appearance()

    def replay_path(self):
        return os.path.join(util.REPLAY_DIR, self._replay_file)

    def _setup_appearance(self):
        if self._metadata is None:
            self._setup_no_metadata_appearance()
        elif self._metadata.is_broken:
            self._setup_broken_appearance()
        elif self._metadata.is_incomplete:
            self._setup_incomplete_appearance()
        else:
            self._setup_complete_appearance()

    def _setup_no_metadata_appearance(self):
        self.setText(1, self._replay_file)
        self.setIcon(0, util.THEME.icon("replays/replay.png"))
        colors = client.instance.player_colors
        self.setForeground(0, QtGui.QColor(colors.get_color("default")))

    def _setup_broken_appearance(self):
        self.setIcon(0, util.THEME.icon("replays/broken.png"))
        self.setText(1, self._replay_file)
        # FIXME: Needs to come from theme
        self.setForeground(1, QtGui.QColor("red"))
        self.setForeground(2, QtGui.QColor("gray"))

        self.setText(2, "(replay parse error)")

    def _setup_incomplete_appearance(self):
        self.setIcon(0, util.THEME.icon("replays/replay.png"))
        self.setText(1, self._replay_file)
        self.setText(2, "(replay doesn't have complete metadata)")
        # FIXME: Needs to come from theme
        self.setForeground(1, QtGui.QColor("yellow"))

    def _setup_complete_appearance(self):
        data = self._metadata.data
        launch_time = time.localtime(self._metadata.launch_time())
        try:
            game_time = time.strftime("%H:%M", launch_time)
        except ValueError:
            game_time = "Unknown"

        icon = fa.maps.preview(data['mapname'])
        if icon:
            self.setIcon(0, icon)
        else:
            dler = client.instance.map_downloader
            dler.download_preview(data['mapname'], self._map_dl_request)
            self.setIcon(0, util.THEME.icon("games/unknown_map.png"))

        self.setToolTip(0, fa.maps.getDisplayName(data['mapname']))
        self.setText(0, game_time)
        self.setForeground(
            0,
            QtGui.QColor(client.instance.player_colors.get_color("default")),
        )
        self.setText(1, data['title'])
        self.setToolTip(1, self._replay_file)

        playerlist = []
        for players in list(data['teams'].values()):
            playerlist.extend(players)
        self.setText(2, ", ".join(playerlist))
        self.setToolTip(2, ", ".join(playerlist))

        self.setText(3, data['featured_mod'])
        self.setTextAlignment(3, QtCore.Qt.AlignCenter)

    def replay_bucket(self):
        if self._metadata is None:
            return "legacy"
        if self._metadata.is_broken:
            return "broken"
        if self._metadata.is_incomplete:
            return "incomplete"
        try:
            t = time.localtime(self._metadata.launch_time())
            return time.strftime("%Y-%m-%d", t)
        except ValueError:
            return "broken"

    def _map_preview_downloaded(self):
        self._setup_appearance()

class LocalReplayBucketItem(QtWidgets.QTreeWidgetItem):
    def __init__(self, kind, children):
        QtWidgets.QTreeWidgetItem.__init__(self)
        self._setup_appearance(kind, children)

    def _setup_appearance(self, kind, children):
        if kind == "broken":
            self._setup_broken_appearance()
        elif kind == "incomplete":
            self._setup_incomplete_appearance()
        elif kind == "legacy":
            self._setup_legacy_appearance()
        else:
            self._setup_date_appearance()

        self.setIcon(0, util.THEME.icon("replays/bucket.png"))
        self.setText(0, kind)
        self.setText(3, "{} replays".format(len(children)))
        self.setForeground(
            3,
            QtGui.QColor(client.instance.player_colors.get_color("default")),
        )

        for item in children:
            self.addChild(item)

    def _setup_broken_appearance(self):
        # FIXME: Needs to come from theme
        self.setForeground(0, QtGui.QColor("red"))

        self.setText(1, "(not watchable)")
        self.setForeground(
            1,
            QtGui.QColor(client.instance.player_colors.get_color("default")),
        )

    def _setup_incomplete_appearance(self):
        # FIXME: Needs to come from theme
        self.setForeground(0, QtGui.QColor("yellow"))

        self.setText(1, "(watchable)")
        self.setForeground(
            1,
            QtGui.QColor(client.instance.player_colors.get_color("default")),
        )

    def _setup_legacy_appearance(self):
        self.setForeground(
            0,
            QtGui.QColor(client.instance.player_colors.get_color("default")),
        )
        self.setForeground(
            1,
            QtGui.QColor(client.instance.player_colors.get_color("default")),
        )
        self.setText(1, "(old replay system)")

    def _setup_date_appearance(self):
        self.setForeground(
            0,
            QtGui.QColor(client.instance.player_colors.get_color("player")),
        )

class LocalReplaysWidgetHandler(object):
    def __init__(self, myTree):
        self.myTree = myTree
        self.myTree.itemDoubleClicked.connect(self.myTreeDoubleClicked)
        self.myTree.itemPressed.connect(self.myTreePressed)
        self.myTree.header().setSectionResizeMode(
            0, QtWidgets.QHeaderView.ResizeToContents,
        )
        self.myTree.header().setSectionResizeMode(
            1, QtWidgets.QHeaderView.ResizeToContents,
        )
        self.myTree.header().setSectionResizeMode(
            2, QtWidgets.QHeaderView.Stretch,
        )
        self.myTree.header().setSectionResizeMode(
            3, QtWidgets.QHeaderView.ResizeToContents,
        )
        self.myTree.modification_time = 0

        replay_cache = os.path.join(util.CACHE_DIR, "local_replays_metadata")
        self.replay_files = LocalReplayMetadataCache(
            util.REPLAY_DIR, replay_cache,
        )

    def myTreePressed(self, item):
        if QtWidgets.QApplication.mouseButtons() != QtCore.Qt.RightButton:
            return

        if item.isDisabled():
            return

        if self.myTree.indexOfTopLevelItem(item) != -1:
            return

        menu = QtWidgets.QMenu(self.myTree)

        # Actions for Games and Replays
        actionReplay = QtWidgets.QAction("Replay", menu)
        actionExplorer = QtWidgets.QAction("Show in Explorer", menu)

        # Adding to menu
        menu.addAction(actionReplay)
        menu.addAction(actionExplorer)

        # Triggers
        actionReplay.triggered.connect(lambda: self.myTreeDoubleClicked(item))
        actionExplorer.triggered.connect(
            lambda: util.showFileInFileBrowser(item.replay_path()),
        )

        # Adding to menu
        menu.addAction(actionReplay)
        menu.addAction(actionExplorer)

        # Finally: Show the popup
        menu.popup(QtGui.QCursor.pos())

    def myTreeDoubleClicked(self, item):
        if item.isDisabled():
            return

        if self.myTree.indexOfTopLevelItem(item) == -1:
            replay(item.replay_path())

    def updatemyTree(self):
        modification_time = os.path.getmtime(util.REPLAY_DIR)
        if self.myTree.modification_time == modification_time:
            return  # nothing changed -> don't redo
        self.myTree.modification_time = modification_time
        self.myTree.clear()

        # We put the replays into buckets by day first, then we add them to the
        # treewidget.
        buckets = {}

        if not self.replay_files.cache_loaded:
            self.replay_files.load_cache()

        # Iterate
        for infile in os.listdir(util.REPLAY_DIR):
            if infile.endswith(".scfareplay"):
                metadata = None
            elif infile.endswith(".fafreplay"):
                metadata = self.replay_files[infile]
            else:
                continue
            item = LocalReplayItem(infile, metadata)
            bucket = item.replay_bucket()
            buckets.setdefault(bucket, [])
            buckets[bucket].append(item)

        self.replay_files.save_cache()
        # Now, create a top level treeWidgetItem for every bucket, and put the
        # bucket's contents into them
        for bucket, items in buckets.items():
            bucket_item = LocalReplayBucketItem(bucket, items)
            self.myTree.addTopLevelItem(bucket_item)

class LocalReplayMetadataCache:
    CACHE_DIFF_THRESHOLD = 20

    def __init__(self, cache_dir, cache_file):
        self._cache_dir = cache_dir
        self._cache_file = cache_file
        self._cache = {}
        self._new_cache_entries = set()
        self._used_cache_entries = set()
        self.cache_loaded = False

    def load_cache(self):
        if os.path.exists(self._cache_file):
            with open(self._cache_file, "rt") as fh:
                for line in fh:
                    filename, metadata = line.split(':', 1)
                    self._cache[filename] = ReplayMetadata(metadata)
        self.cache_loaded = True

    def save_cache(self):
        if not self._cache_differs_much_from_files():
            return
        with open(self._cache_file, "wt") as fh:
            for filename in self._used_cache_entries:
                fh.write(filename + ":" + self._cache[filename].raw_data)

    def _cache_differs_much_from_files(self):
        new_entries = len(self._new_cache_entries)
        all_entries = len(self._cache)
        all_used_entries = len(self._used_cache_entries)
        unused_entries = all_entries - all_used_entries
        return new_entries + unused_entries > self.CACHE_DIFF_THRESHOLD

    def __getitem__(self, filename):
        if filename not in self._cache:
            try:
                target_file = os.path.join(self._cache_dir, filename)
                with open(target_file, "rt") as fh:
                    metadata = fh.readline()
                    self._cache[filename] = ReplayMetadata(metadata)
                self._new_cache_entries.add(filename)
            except IOError:
                raise KeyError

        self._used_cache_entries.add(filename)
        return self._cache[filename]

class ReplayVaultWidgetHandler(object):
    # connect to save/restore persistence settings for checkboxes & search
    # parameters
    automatic = Settings.persisted_property(
        "replay/automatic", default_value=False, type=bool,
    )
    spoiler_free = Settings.persisted_property(
        "replay/spoilerFree", default_value=True, type=bool,
    )
    hide_unranked = Settings.persisted_property(
        "replay/hideUnranked", default_value=False, type=bool,
    )
    match_username = Settings.persisted_property(
        "replay/matchUsername", default_value=True, type=bool,
    )

    def __init__(self, widget, dispatcher, client, gameset, playerset):
        self._w = widget
        self._dispatcher = dispatcher
        self.client = client
        self._gameset = gameset
        self._playerset = playerset

        self.onlineReplays = {}
        self.selectedReplay = None
        self.apiConnector = ReplaysApiConnector(self._dispatcher)
        self.client.lobby_info.replayVault.connect(self.replayVault)
        self.replayDownload = QNetworkAccessManager()
        self.replayDownload.finished.connect(self.onDownloadFinished)
        self.toolboxHandler = ReplayToolboxHandler(
            self, widget, dispatcher, client, gameset, playerset,
        )

        self.showLatest = True
        self.searching = False
        self.searchInfo = "<font color='gold'><b>Searching...</b></font>"
        self.defaultSearchParams = {
            "page[number]": 1,
            "page[size]": 100,
            "sort": "-startTime",
            "include": (
                "featuredMod,mapVersion,mapVersion.map,playerStats,"
                "playerStats.player"
            ),
        }

        _w = self._w
        _w.onlineTree.setItemDelegate(ReplayItemDelegate(_w))
        _w.onlineTree.itemDoubleClicked.connect(self.onlineTreeDoubleClicked)
        _w.onlineTree.itemPressed.connect(self.onlineTreeClicked)

        _w.searchButton.pressed.connect(self.searchVault)
        _w.playerName.returnPressed.connect(self.searchVault)
        _w.mapName.returnPressed.connect(self.searchVault)
        _w.automaticCheckbox.stateChanged.connect(self.automaticCheckboxchange)
        _w.matchUsernameCheckbox.stateChanged.connect(
            self.matchUsernameCheckboxChange,
        )
        _w.showLatestCheckbox.stateChanged.connect(
            self.showLatestCheckboxchange,
        )
        _w.spoilerCheckbox.stateChanged.connect(self.spoilerCheckboxchange)
        _w.hideUnrCheckbox.stateChanged.connect(self.hideUnrCheckboxchange)
        _w.RefreshResetButton.pressed.connect(self.resetRefreshPressed)

        # restore persistent checkbox settings
        _w.matchUsernameCheckbox.setChecked(self.match_username)
        _w.automaticCheckbox.setChecked(self.automatic)
        _w.spoilerCheckbox.setChecked(self.spoiler_free)
        _w.hideUnrCheckbox.setChecked(self.hide_unranked)

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.stopSearchVault)

    def showToolTip(self, widget, msg):
        """
        Default tooltips are too slow and disappear when user starts typing
        """

        position = widget.mapToGlobal(
            QtCore.QPoint(0 + widget.width(), 0 - widget.height() / 2),
        )
        QtWidgets.QToolTip.showText(position, msg)

    def stopSearchVault(self):
        self.searching = False
        self._w.searchInfoLabel.clear()
        self._w.advSearchInfoLabel.clear()
        self.timer.stop()

    def searchVault(
        self,
        minRating=None,
        mapName=None,
        playerName=None,
        leaderboardId=None,
        modListIndex=None,
        quantity=None,
        reset=None,
        exactPlayerName=None,
    ):
        w = self._w
        timePeriod = None

        if self.searching:
            QtWidgets.QMessageBox.critical(
                None,
                "Replay vault",
                "Please, wait for previous search to finish.",
            )
            return

        if reset:
            w.minRating.setValue(0)
            w.mapName.setText("")
            w.playerName.setText("")
            w.leaderboardList.setCurrentIndex(0)
            w.modList.setCurrentIndex(0)
            w.quantity.setValue(100)
            w.showLatestCheckbox.setChecked(True)
        else:
            if minRating is not None:
                w.minRating.setValue(minRating)
            if mapName is not None:
                w.mapName.setText(mapName)
            if playerName is not None:
                w.playerName.setText(playerName)
            if leaderboardId is not None:
                w.leaderboardList.setCurrentIndex(leaderboardId)
            if modListIndex is not None:
                w.modList.setCurrentIndex(modListIndex)
            if quantity is not None:
                w.quantity.setValue(quantity)
            if not self.showLatest:
                timePeriod = []
                timePeriod.append(
                    w.dateStart.dateTime().toUTC().toString(QtCore.Qt.ISODate),
                )
                timePeriod.append(
                    w.dateEnd.dateTime().toUTC().toString(QtCore.Qt.ISODate),
                )

        filters = self.prepareFilters(
            w.minRating.value(),
            w.mapName.text(),
            w.playerName.text(),
            w.leaderboardList.currentIndex(),
            w.modList.currentText(),
            timePeriod,
            exactPlayerName,
        )

        # """ search for some replays """
        self._w.onlineTree.clear()
        self._w.searchInfoLabel.setText(self.searchInfo)
        self._w.searchInfoLabel.setVisible(True)
        self._w.advSearchInfoLabel.setVisible(False)
        self.searching = True

        parameters = self.defaultSearchParams.copy()
        parameters["page[size]"] = w.quantity.value()

        if filters:
            parameters["filter"] = filters

        self.apiConnector.requestData(parameters)
        self.timer.start(90000)

    def prepareFilters(
        self,
        minRating,
        mapName,
        playerName,
        leaderboardId,
        modListIndex,
        timePeriod=None,
        exactPlayerName=None,
    ):
        '''
        Making filter string here + some logic to exclude "heavy" requests
        which may overload database (>30 sec searches). It might looks weak
        (and probably it is), but hey, it works! =)
        '''

        filters = []

        if self.hide_unranked:
            filters.append('validity=="VALID"')

        if leaderboardId:
            filters.append(
                'playerStats.ratingChanges.leaderboard.id=="{}"'
                .format(leaderboardId),
            )

            if minRating and minRating > 0:
                if leaderboardId == 1:
                    filters.append(
                        'playerStats.player.globalRating.rating=ge="{}"'
                        .format(minRating),
                    )
                elif leaderboardId == 2:
                    filters.append(
                        'playerStats.player.ladder1v1Rating.rating=ge="{}"'
                        .format(minRating),
                    )
                else:
                    filters.append(
                        'playerStats.ratingChanges.meanBefore=ge="{}"'
                        .format(minRating + 300),
                    )
        else:
            if minRating and minRating > 0:
                if modListIndex == "ladder1v1":
                    filters.append(
                        'playerStats.player.ladder1v1Rating.rating=ge="{}"'
                        .format(minRating),
                    )
                else:
                    filters.append(
                        'playerStats.player.globalRating.rating=ge="{}"'
                        .format(minRating),
                    )

        if mapName:
            filters.append(
                'mapVersion.map.displayName=="*{}*"'.format(mapName),
            )

        if playerName:
            if self.match_username or exactPlayerName:
                filters.append(
                    'playerStats.player.login=="{}"'.format(playerName),
                )
            else:
                filters.append(
                    'playerStats.player.login=="*{}*"'.format(playerName),
                )

        if modListIndex and modListIndex != "All":
            filters.append(
                'featuredMod.technicalName=="{}"'.format(modListIndex),
            )

        if timePeriod:
            filters.append('startTime=ge="{}"'.format(timePeriod[0]))
            filters.append('startTime=le="{}"'.format(timePeriod[1]))
        elif len(filters) > 0:
            months = 3
            if playerName:
                months = 6

            startTime = (
                QtCore.QDateTime.currentDateTimeUtc()
                .addMonths(-months)
                .toString(QtCore.Qt.ISODate)
            )
            filters.append('startTime=ge="{}"'.format(startTime))

        if len(filters) > 0:
            return "({})".format(";".join(filters))

        return None

    def reloadView(self):
        if not self.searching:
            # refresh on Tab change or only the first time
            if self.automatic or self.onlineReplays == {}:
                self.searchVault(reset=True)

    def onlineTreeClicked(self, item):
        if QtWidgets.QApplication.mouseButtons() == QtCore.Qt.RightButton:
            if isinstance(item.parent, ReplaysWidget):      # FIXME - hack
                item.pressed(item)
        else:
            self.selectedReplay = item
            if hasattr(item, "moreInfo"):
                if item.moreInfo is False:
                    item.infoPlayers()
                elif item.spoiled != self._w.spoilerCheckbox.isChecked():
                    self._w.replayInfos.clear()
                    self._w.replayInfos.setHtml(item.replayInfo)
                    item.resize()
                else:
                    self._w.replayInfos.clear()
                    item.generateInfoPlayersHtml()
            if self.toolboxHandler.mapPreview:
                self.toolboxHandler.updateMapPreview()

    def onlineTreeDoubleClicked(self, item):
        if (
            self.client.games.party
            and self.client.games.party.memberCount > 1
        ):
            if not self.client.games.leave_party():
                return

        if hasattr(item, "duration"):  # it's a game not a date separator
            if "playing" in item.duration:  # live game will not be in vault
                # search result isn't updated automatically - so game status
                # might have changed
                if item.uid in self._gameset:  # game still running
                    game = self._gameset[item.uid]
                    if not game.launched_at:  # we frown upon those
                        return
                    if game.has_live_replay:  # live game over 5min
                        for name in game.players:  # find a player ...
                            if name in self._playerset:  # still logged in
                                self._startReplay(name)
                                break
                    else:
                        delta = time.gmtime(
                            game.LIVE_REPLAY_DELAY_SECS
                            - (time.time() - game.launched_at),
                        )
                        wait_str = time.strftime('%M Min %S Sec', delta)
                        QtWidgets.QMessageBox.information(
                            client.instance,
                            "5 Minute Live Game Delay",
                            (
                                "It is too early to join the Game.\n"
                                "You have to wait {} to join.".format(wait_str)
                            ),
                        )
                else:  # game ended - ask to start replay
                    if QtWidgets.QMessageBox.question(
                        client.instance,
                        "Live Game ended",
                        "Would you like to watch the replay from the vault?",
                        QtWidgets.QMessageBox.Yes,
                        QtWidgets.QMessageBox.No,
                    ) == QtWidgets.QMessageBox.Yes:
                        req = QNetworkRequest(QtCore.QUrl(item.url))
                        req.setAttribute(
                            QNetworkRequest.FollowRedirectsAttribute, True,
                        )
                        self.replayDownload.get(req)

            else:  # start replay
                if hasattr(item, "url"):
                    req = QNetworkRequest(QtCore.QUrl(item.url))
                    req.setAttribute(
                        QNetworkRequest.FollowRedirectsAttribute, True,
                    )
                    self.replayDownload.get(req)

    def _startReplay(self, name):
        if name is None or name not in self._playerset:
            return
        player = self._playerset[name]

        if not player.currentGame:
            return
        replay(player.currentGame.url(player.id))

    def matchUsernameCheckboxChange(self, state):
        self.match_username = state

    def automaticCheckboxchange(self, state):
        self.automatic = state

    def spoilerCheckboxchange(self, state):
        self.spoiler_free = state
        # if something is selected in the tree to the left
        if self.selectedReplay:
            # and if it is a game
            if isinstance(self.selectedReplay, ReplayItem):
                # then we redo it
                self.selectedReplay.generateInfoPlayersHtml()

    def showLatestCheckboxchange(self, state):
        self.showLatest = state
        if state:  # disable date edit fields if True
            self._w.dateStart.setEnabled(False)
            self._w.dateEnd.setEnabled(False)
        else:  # enable date edit and set current date
            self._w.dateStart.setEnabled(True)
            self._w.dateEnd.setEnabled(True)

            date = QtCore.QDate.currentDate()
            self._w.dateStart.setDate(date)
            self._w.dateEnd.setDate(date)

    def hideUnrCheckboxchange(self, state):
        self.hide_unranked = state

    def resetRefreshPressed(self):
        # reset search parameter and reload recent Replays List
        if not self.searching:
            self.searchVault(reset=True)

    def onDownloadFinished(self, reply):
        if reply.error() != QNetworkReply.NoError:
            QtWidgets.QMessageBox.warning(
                self._w, "Network Error", reply.errorString(),
            )
        else:
            faf_replay = QtCore.QFile(
                os.path.join(util.CACHE_DIR, "temp.fafreplay"),
            )
            faf_replay.open(
                QtCore.QIODevice.WriteOnly
                | QtCore.QIODevice.Truncate,
            )
            faf_replay.write(reply.readAll())
            faf_replay.flush()
            faf_replay.close()
            replay(os.path.join(util.CACHE_DIR, "temp.fafreplay"))

    def replayVault(self, message):
        action = message["action"]
        self.stopSearchVault()
        self._w.replayInfos.clear()
        if action == "search_result":
            self.onlineReplays = {}
            replays = message["replays"]
            for replay_item in replays:
                uid = int(replay_item["id"])
                if uid not in self.onlineReplays:
                    self.onlineReplays[uid] = ReplayItem(uid, self._w)
                self.onlineReplays[uid].update(replay_item, self.client)
            self.updateOnlineTree()

            if len(message["replays"]) == 0:
                self._w.searchInfoLabel.setText(
                    "<font color='gold'><b>No replays found</b></font>",
                )
                self._w.advSearchInfoLabel.setText(
                    "<font color='gold'><b>No replays found</b></font>",
                )

    def updateOnlineTree(self):
        self.selectedReplay = None  # clear, it won't be part of the new tree
        self._w.replayInfos.clear()
        self._w.onlineTree.clear()
        buckets = {}
        for uid in self.onlineReplays:
            bucket = buckets.setdefault(self.onlineReplays[uid].startDate, [])
            bucket.append(self.onlineReplays[uid])

        for bucket in buckets.keys():
            bucket_item = QtWidgets.QTreeWidgetItem()
            self._w.onlineTree.addTopLevelItem(bucket_item)

            bucket_item.setIcon(0, util.THEME.icon("replays/bucket.png"))
            bucket_item.setText(
                0, "<font color='white'>{}</font>".format(bucket),
            )
            bucket_len = len(buckets[bucket])
            bucket_item.setText(
                1, "<font color='white'>{} replays</font>".format(bucket_len),
            )

            for replay_item in buckets[bucket]:
                bucket_item.addChild(replay_item)
                replay_item.setFirstColumnSpanned(True)
                replay_item.setIcon(0, replay_item.icon)

            bucket_item.setExpanded(True)

class ReplaysWidget(BaseClass, FormClass):
    def __init__(self, client, dispatcher, gameset, playerset):
        super(BaseClass, self).__init__()

        self.setupUi(self)

        self.liveManager = LiveReplaysWidgetHandler(
            self.liveTree, client, gameset,
        )
        self.localManager = LocalReplaysWidgetHandler(self.myTree)
        self.vaultManager = ReplayVaultWidgetHandler(
            self, dispatcher, client, gameset, playerset,
        )

        logger.info("Replays Widget instantiated.")

    def set_player(self, name, leaderboardName=None):
        self.setCurrentIndex(2)  # focus on Online Fault
        if leaderboardName is not None:
            leaderboardId = self.leaderboardList.findText(leaderboardName)
            self.vaultManager.searchVault(
                0, "", name, leaderboardId, 0, 100, exactPlayerName=True,
            )
        else:
            self.vaultManager.searchVault(
                0, "", name, 0, 0, 100, exactPlayerName=True,
            )

    def focusEvent(self, event):
        self.localManager.updatemyTree()
        self.vaultManager.reloadView()
        return BaseClass.focusEvent(self, event)

    def showEvent(self, event):
        self.localManager.updatemyTree()
        self.vaultManager.reloadView()
        return BaseClass.showEvent(self, event)


import sys
from pathlib import Path
from typing import List, Dict, Union

import pydantic
import yaml
import colorama
from colorama import Fore
import click
import git

VERSION = "0.2.2"
README_URL = "https://github.com/laorange/stoken"
BASE_DIR = Path.cwd().resolve()
colorama.just_fix_windows_console()

class Config(pydantic.BaseModel):
    suffix: List[str]
    token: Dict[str, str]

class Stoken:
    def __init__(self, mode: str, encoding: str, variable_prefix: str, variable_suffix: str, debug: bool, no_git: bool):
        self.mode = mode
        self.encoding = encoding
        self.variable_prefix = variable_prefix
        self.variable_suffix = variable_suffix
        self.debug = debug
        self.no_git = no_git

        if mode == "debug":
            self.debug = True
            self.mode = "auto"

        self.config = self.get_config()
        self.git_repo = None if no_git else self.get_git_repo()

    @staticmethod
    def quit_with_info(info: str = None):
        info = info + '\n\n' if info else ''
        input(f"{info}Press ENTER to exist: ")
        sys.exit(1)

    @staticmethod
    def get_git_repo() -> Union[git.repo.base.Repo, None]:
        git_dev = BASE_DIR.resolve()
        while 1:
            if (git_dev / ".git").exists():
                try:
                    repo = git.repo.base.Repo(git_dev)
                    print(f"Found git root: {repo.git_dir}. If there's no need to detect ignored files in git, use the option `--no-git`.")
                    return repo
                except:
                    return None

            if git_dev.parent == git_dev:
                return None

            git_dev = git_dev.parent

    @staticmethod
    def get_config() -> Config:
        YAML_PATH = BASE_DIR / "stoken.yaml"
        YAML_PATH = YAML_PATH if YAML_PATH.exists() else BASE_DIR / "stoken.yml"
        if not YAML_PATH.exists():
            Stoken.quit_with_info(
                f"{Fore.RED}ERROR: Can't find configuration file! Create `stoken.yaml` in `{BASE_DIR}`. "
                f"Please refer to {README_URL}.{colorama.Style.RESET_ALL}"
            )
        else:
            print(f"{Fore.GREEN}stoken: read {YAML_PATH}{Fore.RESET}")

        with YAML_PATH.open("rt", encoding="utf-8") as yaml_file:
            try:
                config = Config(**yaml.load(yaml_file, Loader=yaml.SafeLoader))
                return config
            except Exception as e:
                Stoken.quit_with_info(
                    f"{Fore.RED}{e}\n\nFail to read configuration file. Please refer to {README_URL} and modify the `{YAML_PATH}`.{colorama.Style.RESET_ALL}"
                )

    def execute_for_file(self, file: Path):
        text_of_this_file = ""
        change_num = 0

        for line_num, line in enumerate(file.open(encoding=self.encoding)):
            content_of_this_line = line

            for key, token in self.config.token.items():
                variable = f"{self.variable_prefix}{key}{self.variable_suffix}"

                if self.mode == "auto":
                    if token in line:
                        self.mode = "hide"
                    elif variable in line:
                        self.mode = "restore"

                if self.mode == "hide":
                    if token in line:
                        content_of_this_line = content_of_this_line.replace(token, variable)
                        change_num += 1

                elif self.mode == "restore":
                    if variable in line:
                        content_of_this_line = content_of_this_line.replace(variable, token)
                        change_num += 1

            if content_of_this_line != line:
                print(f"{file.relative_to(BASE_DIR)} line-{line_num + 1}: {Fore.RED}{line.strip()} {Fore.RESET}-> {Fore.GREEN}{content_of_this_line.strip()}{Fore.RESET}")

            text_of_this_file += content_of_this_line

        if change_num:
            if not self.debug:
                with file.open("wt", encoding=self.encoding) as f:
                    f.write(text_of_this_file)
        else:
            if self.debug:  # print no-token files‘ names only in debug mode
                print(f"{file.relative_to(BASE_DIR)}: No tokens were found.")

    def execute(self, path: Path = None):
        if path is None:
            path = BASE_DIR

        if self.git_repo is not None:
            if Path(self.git_repo.git_dir) == path or self.git_repo.ignored(path):
                if path.is_dir() or path.suffix in self.config.suffix:
                    print(f"{path.relative_to(BASE_DIR)} has been ignored.")
                return

        if path.is_dir():
            for file in path.iterdir():
                self.execute(file)
        else:
            file = path
            if file.suffix in self.config.suffix:
                self.execute_for_file(file)

@click.command()
@click.option("--mode", type=click.Choice(['auto', 'hide', 'restore', 'debug'], case_sensitive=False), help='The mode of operation. Default: auto', default="auto")
@click.option("-e", "--encoding", default="utf-8", help='The encoding used to decode the file. Default: utf-8')
@click.option("-p", "--variable-prefix", default="#{{", help="The prefix of variable placeholder. Default: #{{")
@click.option("-s", "--variable-suffix", default="}}#", help="The suffix of variable placeholder. Default: }}#")
@click.option('--debug', is_flag=True, help="In debug mode, `stoken` won't modify files, only detect tokens.")
@click.option('--no-git', is_flag=True, help="By default, the program will detect if there is a git directory, and if so, it will ignore the files in .gitignore. Activate this option to detect all the files.")
@click.option("-v", "--version", is_flag=True, help="To get the current version of stoken.")
def main(mode: str, encoding: str, variable_prefix: str, variable_suffix: str, debug: bool, no_git: bool, version: bool):
    if version:
        return print(f"v{VERSION}")

    stoken = Stoken(mode, encoding, variable_prefix, variable_suffix, debug, no_git)
    stoken.execute()
    stoken.quit_with_info(f"{Fore.GREEN}stoken: finished!{Fore.RESET}")

if __name__ == '__main__':
    main()


import openai
import time
import re

# OpenAI API 키 설정
def set_openai_api_key(api_key):
    openai.api_key = api_key

def check_korean_ratio(text):
    korean_pattern = re.compile("[ㄱ-ㅎㅏ-ㅣ가-힣]")
    special_pattern = re.compile("[\W]")
    numeric_pattern = re.compile("[\d]")

    korean_count = 0
    special_count = 0
    numeric_count = 0
    total_count = len(text)

    for char in text:
        if korean_pattern.match(char):
            korean_count += 1
        elif special_pattern.match(char):
            special_count += 1
        elif numeric_pattern.match(char):
            numeric_count += 1

    korean_ratio = korean_count / total_count
    special_ratio = special_count / total_count
    numeric_ratio = numeric_count / total_count
    total_ratio = korean_ratio + special_ratio + numeric_ratio

    return total_ratio

def check_english_ratio(text):
    english_pattern = re.compile("[A-Za-z]")
    special_pattern = re.compile("[\W]")
    numeric_pattern = re.compile("[\d]")

    english_count = 0
    special_count = 0
    numeric_count = 0
    total_count = len(text)

    for char in text:
        if english_pattern.match(char):
            english_count += 1
        elif special_pattern.match(char):
            special_count += 1
        elif numeric_pattern.match(char):
            numeric_count += 1

    english_ratio = english_count / total_count
    special_ratio = special_count / total_count
    numeric_ratio = numeric_count / total_count

    total_ratio = english_ratio +  special_ratio +  numeric_ratio
    print(total_ratio)

    return total_ratio

def call_openai_api(model, msg):

   try:
       response = openai.ChatCompletion.create(
           model=model,
           messages=msg
       )
       return response
   except openai.error.APIError as e:
       print("API 호출 오류:", e)
       print("다시 시도합니다...")
       time.sleep(2)
       return call_openai_api(model, msg)
   except openai.error.RateLimitError as e:
        print("API 호출 오류:", e)
        print("다시 시도합니다...")
        time.sleep(20)  # 20초 대기
        return call_openai_api(model, msg)

def get_response(input_text):
   model = "gpt-3.5-turbo"

   #print("123456789")
   #print(input_text)

   english_pattern = re.compile("[A-Za-z]+")
   if check_korean_ratio(input_text) > 0.75:

      input_pmt = f"translate {input_text} into english, Show only translated results. No korean in the result"
      print(input_pmt)

      messages = [
         {
               "role": "system",
               "content": "You are a helpful assistant who is good at translating."
         },
         {
               "role": "user",
               "content": input_pmt
         }

      ]

      #print("1")
      #print(input_pmt)

      korean_pattern = re.compile("[\uac00-\ud7a3]+")

      while True:
          try:
              response = call_openai_api(model, messages)

              translate_pmt = response['choices'][0]['message']['content']
              print(translate_pmt)

              if check_english_ratio(translate_pmt) > 0.75:
                 break
              else:
                 print("한글이 탐지되었습니다.")
          except openai.error.RateLimitError as e:
              print("Rate limit reached. Waiting for 20 seconds...")
              time.sleep(20)
   else:
      #print("2")
      translate_pmt = input_text

   #print(translate_pmt)

   time.sleep(20)

   # 원래 내용
   original_prompt = """Please create a text adventure story, following the rules listed below:

presentation rules:

1. The story output will always show 'title', 'theme' ,'world view', 'story summary' and 'etc'

2. Wrap all story output in code blocks.

3. The subject is {translate_pmt}, please write the subject in a little more detail you want so that it can be used as a text adventure game story.

4. The world view of the topic must be written within 2 lines

5. The Story Summary of the topic must be written within 6 lines"""

   original_prompt = original_prompt.replace('{translate_pmt}', translate_pmt)

   messages = [
       {
           "role": "system",
           "content": original_prompt
       },
       {
           "role": "user",
           "content": "start create."
       }
   ]

   #print("2")
   # OpenAI API 호출
   while True:
       try:
           response = call_openai_api(model, messages)

           return_response = response['choices'][0]['message']['content']
           #print(return_response)
           break
       except openai.error.RateLimitError as e:
           print("Rate limit reached. Waiting for 20 seconds...")
           time.sleep(20)
       except openai.error.APIError as e:
          print("API 호출 오류:", e)
          print("다시 시도합니다...")
          time.sleep(2)

   if not('Story Summary' in return_response or 'STORY SUMMARY' in return_response):
      print("No Summary. Sending original prompt again.")
      time.sleep(20)
      return_response = get_response(translate_pmt)
      return return_response

   if not('Theme' in return_response or 'THEME' in return_response):
      print("No Theme. Sending original prompt again.")
      time.sleep(20)
      return_response = get_response(translate_pmt)
      return return_response

   if not('Worldview' not in return_response or 'WORLDVIEW' not in return_response):
      print("No Worldview. Sending original prompt again.")
      time.sleep(20)
      return_response = get_response(translate_pmt)
      return return_response

   #print("3")
   return return_response

def categorize_text(text):
    # #, :, 등의 특수문자 제거
    text = text.replace('#', '')

    categorized_dict = {}

    # Theme 추출
    theme_start = text.lower().find("theme")
    theme_end = text.lower().find("world view")
    theme = text[theme_start + len("theme"):theme_end].strip()
    theme = theme.replace(':', '').strip()
    categorized_dict["Theme"] = theme

    # World View 추출
    worldview_start = text.lower().find("world view")
    worldview_end = text.lower().find("story summary")
    worldview = text[worldview_start + len("world view"):worldview_end].strip()
    worldview = worldview.replace(':', '').strip()
    categorized_dict["World View"] = worldview

    # Story Summary 추출
    summary_start = text.lower().find("story summary")
    if 'etc'in text.lower():
        summary_end = text.lower().find("etc")
        summary = text[summary_start + len("story summary"):summary_end].strip()
    else:
        summary = text[summary_start + len("story summary"):].strip()

    categorized_dict["Sample Story"] = summary

    return categorized_dict


import random as rd
list1 = []
list2 = []
print("Nhap n:")
n = int(input())
for i in range(n):
    list1.append(rd.randint(1, 100))
print(list1)
print("Nhap x:")
x = int(input())
for j in list1:
    if(j <= x):
        list2.append(j)
print("Day nho hon hoac bang x la:")
print(list2)


#user enters a statement
USER_INPUT = input('Enter a statement in lower case: ')

#program returns the character that appears the most frequently
each_count = {}

for letter in USER_INPUT:
    if letter.isalpha(): #check if the letters in the statement is a letter
        if letter in each_count: #if it is a letter
            each_count[letter] +=1 #takes an account of each character + adds each time there
        else:
            each_count[letter] = 1

frequent_character = None
highest_count = 0
for letter, count in each_count.items():
    if count > highest_count:
        frequent_character = letter
        highest_count = count

print(f'The most frequent character is "{frequent_character}" and it appears {highest_count} times.')


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

# ## TensorFlow MNIST问题实践优化

# 选择环境：Anaconda Python 3.5.2
# 安装Tensorflow：Python 3.5环境下运行pip install --upgrade --ignore-installed tensorflow
# 参考书籍：《TensorFlow实战Google深度学习框架（第2版）》
# ipynb格式：点击阅读原文github

# ### 5.5 TensorFlow最佳实践样例程序

# 解决MNIST问题的重构代码拆成3个程序：
# mnist_inference.py：定义前向传播过程以及神经网络中的参数
# mnist_train.py：定义神经网络的训练过程
# mnist_eval.py：定义测试过程

# #### 1. mnist_inference.py

# In[1]:

import tensorflow as tf

# 1. 定义神经网络结构相关的参数。

# In[2]:

INPUT_NODE = 784
OUTPUT_NODE = 10
LAYER1_NODE = 500

# 2. 通过tf.get_variable函数来获取变量。

# In[4]:

def get_weight_variable(shape, regularizer):
    weights = tf.get_variable(
        "weights", shape,
        initializer=tf.truncated_normal_initializer(stddev=0.1))
    if regularizer != None:
        tf.add_to_collection('losses', regularizer(weights))
    return weights

# 3. 定义神经网络的前向传播过程。

# In[5]:

def inference(input_tensor, regularizer):
    # 第一层神经网络及前向传播
    with tf.variable_scope('layer1'):
        weights = get_weight_variable(
            [INPUT_NODE, LAYER1_NODE], regularizer)
        biases = tf.get_variable(
            "biases", [LAYER1_NODE],
            initializer=tf.constant_initializer(0.0))
        layer1 = tf.nn.relu(tf.matmul(input_tensor, weights) + biases)

    # 第二层神经网络及前向传播
    with tf.variable_scope('layer2'):
        weights = get_weight_variable(
            [LAYER1_NODE, OUTPUT_NODE], regularizer)
        biases = tf.get_variable(
            "biases", [OUTPUT_NODE],
            initializer=tf.constant_initializer(0.0))
        layer2 = tf.matmul(layer1, weights) + biases

    #返回最后前向传播的结果
    return layer2

# 无论是训练时还是测试时，都可以直接调用inference这个函数，而不用关心具体的神经网络结构。


import collections
from util import *
import torch
import warnings
import copy
import random
import numpy as np

# [reference] https://github.com/matthiasplappert/keras-rl/blob/master/rl/memory.py

# This is to be understood as a transition: Given `current_state`, performing `ActionNoise`
# yields `reward` and results in `next_state`, which might be `terminal`.
Experience = collections.namedtuple('Experience', 'current_state, act, reward, next_state, terminal1')

def sample_batch_indexes(low, high, size):
    if high - low >= size:
        b_ids = random.sample(range(low, high), size)
    else:
        # Not enough data. Help ourselves with sampling from the range, but the same index
        # can occur multiple times. This is not good and should be avoided by picking a
        # large enough warm-up phase.
        warnings.warn('needs more warm up steps')
        b_ids = np.random.random_integers(low, high - 1, size=size)
    assert len(b_ids) == size
    return b_ids

class RingBuffer(object):
    def __init__(self, maxlen):
        self.maxlen = maxlen
        self.start = 0
        self.length = 0
        self.data = [None for _ in range(maxlen)]

    def __len__(self):
        return self.length

    def __getitem__(self, index):
        if index < 0 or index >= self.length:
            raise KeyError()
        return self.data[(self.start + index) % self.maxlen]

    def append(self, v):
        if self.length < self.maxlen:
            self.length += 1
        elif self.length == self.maxlen:
            self.start = (self.start + 1) % self.maxlen
        else:
            raise RuntimeError()
        self.data[(self.start + self.length - 1) % self.maxlen] = v

def zero_observ(ob):
    if hasattr(ob, 'shape'):  # 判断是否包含 shape 属性
        return np.zeros(ob.shape)
    elif hasattr(ob, '__iter__'):  # 可迭代？
        out = list()
        for i in ob:
            out.append(zero_observ(i))
        return out
    else:
        return 0.

class BasicMemory(object):
    def __init__(self, window_length, ignore_episode_boundaries=False):
        self.window_length = window_length
        self.ignore_episode_boundaries = ignore_episode_boundaries

        self.recent_observations = collections.deque(maxlen=window_length)
        self.recent_terminals = collections.deque(maxlen=window_length)

    def append(self, ob, terminal, training=True):
        self.recent_terminals.append(terminal)
        self.recent_observations.append(ob)

# BasicMemory 中维护 recent_observations and  recent_terminals
class SequentialMemory(BasicMemory):
    def __init__(self, limit, **kwargs):
        super(SequentialMemory, self).__init__(
            **kwargs)  # initialize kwargs as a dict，  store window_length and ignore_episode_boundaries

        self.limit = limit
        self.actions = RingBuffer(limit)  # actions rewards terminals observations ring buffers
        self.rewards = RingBuffer(limit)  # maxlen == limit
        self.terminals = RingBuffer(limit)
        self.observations = RingBuffer(limit)

    def sample(self, bsize, b_ids=None):  # 返回experience存储s0 s1 ActionNoise reward terminal， state为一串observation序列
        if b_ids is None:
            b_ids = sample_batch_indexes(0, len(self.observations) - 1, size=bsize)  # 从observation里随机抽取batch_size个idxs
        b_ids = np.array(b_ids) + 1
        assert np.min(b_ids) >= 1
        assert np.max(b_ids) < len(self.observations)
        assert len(b_ids) == bsize

        # Create experiences
        experiences = list()
        for index in b_ids:
            terminal0 = self.terminals[index - 2] if index >= 2 else False
            while terminal0:
                index = sample_batch_indexes(1, len(self.observations), size=1)[
                    0]  # nb_entries == len(self.observations) == 当前长度 ??
                terminal0 = self.terminals[index - 2] if index >= 2 else False  # 随机抽取idx，直到上一步 terminal 为 false
            assert 1 <= index < len(self.observations)

            # This code is slightly complicated by the fact that subsequent observations might be
            # from different episodes. We ensure that an experience never spans multiple episodes.
            # This is probably not that important in practice but it seems cleaner.
            current_state = [self.observations[index - 1]]
            for offset in range(0, self.window_length - 1):
                current_idx = index - 2 - offset  # 上一步observation 对应s0
                current_terminal = self.terminals[
                    current_idx - 1] if current_idx - 1 > 0 else False  # 用于判断是否完成一个 trajectory ？
                if current_idx < 0 or (not self.ignore_episode_boundaries and current_terminal):
                    # The previously handled ob was terminal, don't add the current one.
                    # Otherwise we would leak into a different episode.
                    break
                current_state.insert(0, self.observations[current_idx])  # 导入 index 之前 window_length 内的 ob
            while len(current_state) < self.window_length:
                current_state.insert(0, zero_observ(current_state[0]))  # 用0填充 功能和Memory中的get_recent_state类似

            # Okay, now we need to create the follow-up state. This is current_state shifted on timestep
            # to the right. Again, we need to be careful to not include an ob from the next
            # episode if the last state is terminal.
            next_state = [np.copy(x) for x in current_state[1:]]  # 复制state0[1:], next_state 比 current_state 滞后一个observation
            next_state.append(self.observations[index])

            assert len(current_state) == self.window_length
            assert len(next_state) == len(current_state)
            experiences.append(Experience(current_state=current_state, act=self.actions[index - 1],
                                          reward=self.rewards[index - 1],
                                          next_state=next_state,
                                          terminal1=self.terminals[index - 1]))
        assert len(experiences) == bsize
        return experiences

    def sample_and_split(self, bsize, b_ids=None):  # 拆分sample获得的experience， 分别写入buffer
        experiences = self.sample(bsize, b_ids)
        state0_batch = list()
        reward_batch = list()
        action_batch = list()
        terminal1_batch = list()
        state1_batch = list()
        for experience in experiences:
            state0_batch.append(experience.current_state)
            state1_batch.append(experience.next_state)
            reward_batch.append(experience.reward)
            action_batch.append(experience.act)
            if experience.terminal1:
                terminal1_batch.append(0.)
            else:
                terminal1_batch.append(1.)  # 0代表terminal为true， 为false时写入1

        # Prepare and validate parameters.
        state0_batch = np.array(state0_batch).reshape(bsize, -1)
        state1_batch = np.array(state1_batch).reshape(bsize, -1)
        terminal1_batch = np.array(terminal1_batch).reshape(bsize, -1)
        reward_batch = np.array(reward_batch).reshape(bsize, -1)
        action_batch = np.array(action_batch).reshape(bsize, -1)
        return state0_batch, action_batch, reward_batch, state1_batch, terminal1_batch

    def append(self, ob, act, reward, terminal, training=True):
        super(SequentialMemory, self).append(ob, terminal, training=training)  # 更新recent_observations

        if training:
            self.observations.append(ob)  # 环形缓冲定义的append， 会改变start保证数据的顺序性
            self.actions.append(act)
            self.rewards.append(reward)
            self.terminals.append(terminal)


def Cpg(self):
        r'''Gas-phase heat capacity of the mixture at its current temperature ,
        and composition in units of [J/kg/K]. For calculation of this property at
        other temperatures or compositions, or specifying manually the method
        used to calculate it, and more - see the object oriented interface
        :obj:`thermo.heat_capacity.HeatCapacityGasMixture`; each Mixture
        instance creates one to actually perform the calculations. Note that
        that interface provides output in molar units.

        Examples
        --------
        >>> Mixture(['oxygen', 'nitrogen'], ws=[.4, .6], T=350, P=1E6).Cpg
        995.8911053614883
        '''
        Cpgm = self.HeatCapacityGasMixture(self.T, self.P, self.zs, self.ws)
        if Cpgm:
            return property_molar_to_mass(Cpgm, self.MW)
        return None


from  pyftpdlib.filesystems import AbstractedFS
import os
import time
from pyftpdlib._compat import PY3, u, unicode

import mobyle.common
from mobyle.common.connection import connection
from mobyle.common.project import ProjectData, Project

from mobyle.data.manager.objectmanager import ObjectManager

from bson import ObjectId

from pyftpdlib.filesystems import _months_map, FilesystemError

try:
    from stat import filemode as _filemode  # PY 3.3
except ImportError:
    from tarfile import filemode as _filemode

import logging

class MobyleFileSystem(AbstractedFS):
    """Represents the Mobyle filesystem.

    """

    def __init__(self, root, cmd_channel):
        """
         - (str) root: the user "real" home directory (e.g. '/home/user')
         - (instance) cmd_channel: the FTPHandler class instance
        """
        super(MobyleFileSystem, self).__init__(root, cmd_channel)
        self._uid = root
        self._root = u('/')

    def validpath(self, path):
            # validpath was used to check symlinks escaping user home
            # directory; this is no longer necessary.
            return True

    def chdir(self, path):
        # No change possible for the moment
        #self._cwd = path
        if path == '/':
            path = u(path)
        self._cwd = u(self.fs2ftp(path))
        logging.warn("chdir to " + self._cwd)

    def mkdir(self, path):
        # Not possible
        return False

    def rmdir(self, path):
        # Not possible
        return False

    def isfile(self, path):
        return True

    def islink(self, path):
        return False

    def isdir(self, path):
        """If path is a user id, then it is root dir"""
        elts = path.split('/')
        try:
            #user = connection.User.find_one({ '_id' : ObjectId(self._uid) })
            # User root
            #if len(elts) == 1:
            if path == '/':
                return True
            # User project
            elif len(elts) == 2:
                return True
            # Next, should check if a sub dataset
        except Exception:
            return False
        return False

    def get_list_dir(self, path):
        """"Return an iterator object that yields a directory listing
        in a form suitable for LIST command.
        """
        logging.debug("cur dir is " + self._cwd)
        logging.debug("get_list_dir: " + path)
        if self.isdir(path):
            logging.warn("list a directory")
            listing = self.listdir(path)
            try:
                listing.sort()
            except UnicodeDecodeError:
                # (Python 2 only) might happen on filesystem not
                # supporting UTF8 meaning os.listdir() returned a list
                # of mixed bytes and unicode strings:
                # http://goo.gl/6DLHD
                # http://bugs.python.org/issue683592
                pass
            return self.format_list(path, listing)
        # if path is a file or a symlink we return information about it
        else:
            logging.debug("list a file: " + path)
            # Should of course get fakedatas of user only
            data = connection.ProjectData.find_one({'_id': ObjectId(path)})

            return self.format_list(path, [data])

    def listdir(self, path):
        """List the content ie all fakedatas."""

        files = []
        # Should of course get fakedatas/projects of user only
        elts = path.split('/')
        logging.debug("listdir: path= " + str(elts))
        if path == '/':
            # Root dir, list projects
            projects = connection.Project.find({"users": {"$elemMatch": {'user.$id': ObjectId(self._uid)}}})
            #projects = connection.FakeProject.find()
            for project in projects:
                files.append(project)
        else:
            project = elts[1].split('_')
            projectdata = connection.ProjectData.find({"project": ObjectId(project[0])})
            #fakedata = connection.ProjectData.find({ 'project' : project[1] })
            for data in projectdata:
                #files.append(data['uid'])
                files.append(data)
        logging.warn("list files " + str(files))
        return files

    def getsize(self, path):
        paths = path.split('/')
        filename = paths[2]
        filename = filename.split('_')[0]
        fakedata = connection.ProjectData.find_one({'_id': filename})
        if fakedata is not None:
            return fakedata['size']
        return None

    def getmtime(self, path):
        paths = path.split('/')
        paths = path.split('/')
        filename = paths[2]
        filename = filename.split('_')[0]
        mngr = ObjectManager()
        filename = mngr.get_file_path(filename)
        return os.path.getmtime(filename)

    def realpath(self, path):
        return path

    def lexists(self, path):
        return True

    def open(self, filename, mode):
        """Open a file returning its handler."""
        logging.debug('open file ' + filename)
        paths = filename.split('/')
        filename = paths[2]
        filename = filename.split('_')[0]
        mngr = ObjectManager()
        filename = mngr.get_file_path(filename)
        logging.debug("## open " + filename)
        return open(filename, mode)

    def format_list(self, basedir, listing, ignore_err=True):
        """Return an iterator object that yields the entries of given
        directory emulating the "/bin/ls -lA" UNIX command output.

         - (str) basedir: the absolute dirname.
         - (list) listing: the names of the entries in basedir
         - (bool) ignore_err: when False raise exception if os.lstat()
         call fails.

        On platforms which do not support the pwd and grp modules (such
        as Windows), ownership is printed as "owner" and "group" as a
        default, and number of hard links is always "1". On UNIX
        systems, the actual owner, group, and number of links are
        printed.

        This is how output appears to client:

        -rw-rw-rw-   1 owner   group    7045120 Sep 02  3:47 music.mp3
        drwxrwxrwx   1 owner   group          0 Aug 31 18:50 e-books
        -rw-rw-rw-   1 owner   group        380 Sep 02  3:40 module.py
        """
        if self.cmd_channel.use_gmt_times:
            timefunc = time.gmtime
        else:
            timefunc = time.localtime
        SIX_MONTHS = 180 * 24 * 60 * 60
        #readlink = getattr(self, 'readlink', None)
        now = time.time()
        for basename in listing:
            if not isinstance(basename, Project):
                try:
                    mngr = ObjectManager()
                    st = self.lstat(mngr.get_file_path(str(basename['_id'])))
                except (OSError, FilesystemError):
                    if ignore_err:
                        continue
                    raise
                size = basename['data']['size']  # file size
                perms = _filemode(st.st_mode)  # permissions
            else:
                # This is a project, fake a directory
                size = 0
                st = self.lstat(os.path.realpath(__file__))
                basename['_id'] = str(basename['_id'])
                perms = "drwxrwxrwx"
            nlinks = st.st_nlink  # number of links to inode
            if not nlinks:  # non-posix system, let's use a bogus value
                nlinks = 1
            size = st.st_size  # file size
            mtime = timefunc(st.st_mtime)

            uname = "mobyle"
            gname = "mobyle"

            # if modification time > 6 months shows "month year"
            # else "month hh:mm";  this matches proftpd format, see:
            # http://code.google.com/p/pyftpdlib/issues/detail?id=187
            if (now - st.st_mtime) > SIX_MONTHS:
                fmtstr = "%d  %Y"
            else:
                fmtstr = "%d %H:%M"
            try:
                mtimestr = "%s %s" % (_months_map[mtime.tm_mon],
                                      time.strftime(fmtstr, mtime))
            except ValueError:
                # It could be raised if last mtime happens to be too
                # old (prior to year 1900) in which case we return
                # the current time as last mtime.
                mtime = timefunc()
                mtimestr = "%s %s" % (_months_map[mtime.tm_mon],
                                      time.strftime("%d %H:%M", mtime))

            # formatting is matched with proftpd ls output
            line = "%s %3s %-8s %-8s %8s %s %s\r\n" % \
                (perms, nlinks, uname, gname, size, mtimestr,
                    str(basename['_id']) + "_" + basename['name'])
            logging.debug("out list: " + line)
            yield line.encode('utf8', self.cmd_channel.unicode_errors)


'''
-Hard-
*DP*
*Greedy*

There is a one-dimensional garden on the x-axis. The garden starts at the point 0 and ends at
the point n. (i.e The length of the garden is n).

There are n + 1 taps located at points [0, 1, ..., n] in the garden.

Given an integer n and an integer array ranges of length n + 1 where ranges[i] (0-indexed) means
the i-th tap can water the area [i - ranges[i], i + ranges[i]] if it was open.

Return the minimum number of taps that should be open to water the whole garden, If the garden
cannot be watered return -1.

Example 1:

Input: n = 5, ranges = [3,4,1,1,0,0]
Output: 1
Explanation: The tap at point 0 can cover the interval [-3,3]
The tap at point 1 can cover the interval [-3,5]
The tap at point 2 can cover the interval [1,3]
The tap at point 3 can cover the interval [2,4]
The tap at point 4 can cover the interval [4,4]
The tap at point 5 can cover the interval [5,5]
Opening Only the second tap will water the whole garden [0,5]
Example 2:

Input: n = 3, ranges = [0,0,0,0]
Output: -1
Explanation: Even if you activate all the four taps you cannot water the whole garden.
Example 3:

Input: n = 7, ranges = [1,2,1,0,2,1,0,1]
Output: 3
Example 4:

Input: n = 8, ranges = [4,0,0,0,0,0,0,0,4]
Output: 2
Example 5:

Input: n = 8, ranges = [4,0,0,0,4,0,0,0,4]
Output: 1

Constraints:

1 <= n <= 10^4
ranges.length == n + 1
0 <= ranges[i] <= 100

'''

class Solution(object):
    def minTaps(self, n, ranges):
        """
        :type n: int
        :type ranges: List[int]
        :rtype: int
        """
        # 所有喷头覆盖的区间
        maxRanges = [0]*(n+1)
        # 循环每一个喷头
        for i in range(len(ranges)):
            # 喷头覆盖区间的左下标
            left = i-ranges[i]
            # 左下标小于0时，设为0
            if left < 0: left = 0
            # 喷头覆盖区间的右下标
            right = i+ranges[i]
            # 右下标大于n时，设为n
            if right > n: right = n
            # 将区间存入maxRanges
            # maxRanges[left]代表左下标为left的区间最远可以到达的坐标
            maxRanges[left] = max(maxRanges[left], right)
        # 找出能够覆盖整个区间的最少区间个数
        # 第一个区间从0开始，maxRanges[0]结束
        #print(maxRanges)
        l, r = 0, maxRanges[0]
        res = 1 # 返回结果
        # 当右区间小于n时循环
        while r < n:
            # 找到下一个区间，下一个区间的left在当前区间内
            # 并且下一个区间的right大于当前区间right
            # 找到符合上述要求的一个right最大区间
            maxLeft, maxRight = l, r
            for i in range(l, r+1):
                if maxRanges[i] > maxRight:
                    maxRight = maxRanges[i]
                    maxLeft = i
            # 如果最大right不能大于当前right，返回-1
            if maxRight == r: return -1
            # 更新下一个区间的left和right
            l, r = maxLeft+1, maxRight
            res += 1
        return res

if __name__ == "__main__":
    print(Solution().minTaps(n = 5, ranges = [3,4,1,1,0,0]))


from slam import process
from display import Display
from pointmap import PointMap
import cv2
import open3d as o3d
import depthai as dai

# Start defining a pipeline
pipeline = dai.Pipeline()

# Define a source - color camera
camRgb = pipeline.createColorCamera()
camRgb.setBoardSocket(dai.CameraBoardSocket.RGB)
# camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_1080_P)  		#1080
# camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_12_MP)		#3040
camRgb.setResolution(dai.ColorCameraProperties.SensorResolution.THE_4_K)			#2160
camRgb.setVideoSize(1000, 1000) ## CUSTOMIZE SIZE
camRgb.setInterleaved(False)
# camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.BGR)
camRgb.setColorOrder(dai.ColorCameraProperties.ColorOrder.RGB)

# Create output
xoutRgb = pipeline.createXLinkOut()
xoutRgb.setStreamName("rgb")
camRgb.preview.link(xoutRgb.input)
xoutRgb1 = pipeline.createXLinkOut()
xoutRgb1.setStreamName("video")
xoutRgb1.input.setBlocking(False)
xoutRgb1.input.setQueueSize(1)
camRgb.video.link(xoutRgb1.input)

pmap = PointMap()
display = Display()

pcd = o3d.geometry.PointCloud()
visualizer = o3d.visualization.Visualizer()
visualizer.create_window(window_name="3D plot", width=960, height=540)

# Connect to the device
with dai.Device() as device:
	# Print out available cameras
	print('Connected cameras: ', device.getConnectedCameras())
	# Start pipeline
	device.startPipeline(pipeline)

	# Output queue will be used to get the rgb frames from the output defined above
	# qRgb = device.getOutputQueue(name="rgb", maxSize=4, blocking=False)
	qRgb = device.getOutputQueue(name="rgb", maxSize=30, blocking=False)
	out = device.getOutputQueue(name="video", maxSize=1, blocking=False)

	# while cap.isOpened():
	while True:
		frame = qRgb.get()
		# frame = frame.getCvFrame()
		output = out.get()
		frame = output.getCvFrame()

		img, tripoints, kpts, matches = process(frame)
		xyz = pmap.collect_points(tripoints)

		if kpts is not None or matches is not None:
			display.display_points2d(frame, kpts, matches)
		else:
			pass
		display.display_vid(frame)

		if xyz is not None:
			display.display_points3d(xyz, pcd, visualizer)
		else:
			pass
		if cv2.waitKey(1) == 27:
			break

	cv2.destroyAllWindows()


import glob
import numpy as np
from kmeans import kmeans, avg_iou

ANNOTATIONS_PATH = "source\\yolov3-pytorch\\datasets\\train\\Annotations"
CLUSTERS = 6

def load_dataset(path):
    dataset = []
    height = 480
    width = 640
    for txt_file in glob.glob("{}\\*.txt".format(path)):

        with open(txt_file,"r") as file:
            for i in file.readlines():
                txt_split = i[:-2].split(" ")
                ds=[float(txt_split[3])*width, float(txt_split[4])*height]
                dataset.append(ds)

    return np.array(dataset)

data = load_dataset(ANNOTATIONS_PATH)
out = kmeans(data, k=CLUSTERS)
print("Accuracy: {:.2f}%".format(avg_iou(data, out) * 100))
print("Boxes:\n {}".format(out))

ratios = np.around(out[:, 0] / out[:, 1], decimals=2).tolist()
print("Ratios:\n {}".format(sorted(ratios)))


#! python
from collections import Counter

data = open('day2.csv', 'r').read().splitlines()
sorted_data = sorted(data)

# part 1
twos, threes = 0, 0
for i in data:
    if 2 in Counter(i).values():
        twos += 1
    if 3 in Counter(i).values():
        threes += 1

check_sum =  twos * threes
print('Part 1 Answer: {}'.format(check_sum))

# part 2
def checker(lst):
    temp_list = lst[:]
    while len(temp_list) > 1:
        check = temp_list.pop()
        for i in temp_list:
            if len(check) == len(i):
                diffs, common = 0, ''
                for z in zip(check,i):
                    if z[0] != z[1]:
                        diffs += 1
                    else:
                        common += z[0]
                if diffs < 2:
                    print(('Part 2 Answer: {} === may be partner id to === {}\n'
                            'Common chars are ===> {}'.format(check, i, common)))

checker(sorted_data)


from keras.models import load_model
import os
from scipy import ndimage, misc
import re
from numpy.random import shuffle
from numpy import array
from matplotlib import pyplot as plt

images = []
for root, dirnames, filenames in os.walk("./flowers_to_predict"):
    for filename in filenames:
        if re.search("\.(jpg|jpeg|png|bmp|tiff|gif)$", filename):
            filepath = os.path.join(root, filename)
            image = ndimage.imread(filepath, mode="RGB")
            images.append(misc.imresize(image, (32, 32)))

shuffle(images)
images = array(images)

print ('loading model')
model = load_model('fine_tune-ep249-loss0.275-BEST_MODEL.h5')
print ('finished loading')

predictions = model.predict(images)

for i in range(len(images)):
    plt.imshow(images[i])
    plt.pause(0.05)
    print('{0} with prediction of {1}'.format(('Flower' if predictions[i] > 0.8 else 'Not flower'), predictions[i]))


import datetime
import os
import random
def clear():            #to make the screen cleared
    os.system('CLS')
    return()
clear()
a=datetime.datetime.now()
print("""
                WELCOME TO FERRY TICKETING SYSTEM

                                                    {}

                                    """.format(a.strftime("Date:%Y-%m-%d| %H:%m")))

print("press Enter to continue....")
input()
clear()
month=a.strftime("%b")
day=a.strftime("%d")
year=a.strftime("%Y")
FID="FID"+"00"+str(random.randint(1,8)) #to generate random ferry ID
seat_Business=[0]*10                #Assigns seat for business class passsenger
seat_Economy=[0]*40                 #Assigns seat for Economy class passengers
class tickets:                      #class to create objects
    def __init__(self,locat,seat,name,age,address,FID,x):
        self.destination=locat
        self.seat=seat
        self.name=name
        self.age=age
        self.address=address
        self.FID=FID
        self.Class= "Business Class" if (x=='1') else "Economy Class"
    def bookTicket(self):           #method to write tickets in a file
        #to save the ticket in the file
        TicketFile=open("ticket.txt","w")   #opens the file in writing mode
        a=datetime.datetime.now()
        b=a.strftime("Date:%Y-%m-%d | %H:%M")   #extracts current time
        TicketFile.write("""
        *********************************************
        *               {}      *
        *********************************************
        {} | Ferry ID:{}
        Seat Number :{}
        Name        :{}
        Age         :{}
        Address     :{}
        Class Type  :{}
        *********************************************""".format(self.destination,b,self.seat,
        self.FID,self.name,self.age,self.address,self.Class))
        TicketFile.close()
        #to get the ticket
    def getTicket(self):
        data=''
        TicketFile=open("ticket.txt","r")
        for x in range(10):
            data=data+TicketFile.readline()
        return(data)
class ViewSeat:                 #class for viewing seat arranement
    def __init__(self):
        self.arrangement="""        ******************************************************
        *               SEATING ARRANGMENT                   *
        ******************************************************
        *  Ferry ID: {}              Date: {} {} {}    *
        ******************************************************
        *  BUSINESS CLASS                                    *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *  ECONOMY CLASS                                     *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************
        *   {}    *     {}    *     {}     *     {}    *     {}   *
        ******************************************************"""
    def viewArrangement(self):
        fileView=open("seat.txt","w")
        fileView.write(self.arrangement.format(FID,day,month,year,*seat_Business,*seat_Economy))
        fileView.close()
    def getArrangement(self):
        fileView=open("seat.txt","r")
        return(fileView.read())
def menu():
    print("""        ******************************************************")
        *                FERRY TICKETING SYSTEM              *
        *                       MAIN MENU                    *
        *         P - to purchase ticket                     *
        *         V - to View seeting arrangement            *
        *         Q - to Quit the system                     *
        ******************************************************""")
    choice=input("Please enter your choice")
    def alert1():
        print("Wrong choice. Kindly choose as specified")
        return menu()
    func_dict={
        'P':purchase,
        "V":view,
        "Q":exit,
        'p':purchase,
        "v":view,
        "q":exit}
    clear()
    func_dict.get(choice,alert1)()

def purchase():
    print("""       **************************************************
        *               PURCHASING MODULE                *
        *                    SUB MENU                    *
        *     B - to purchase ticket for Business class  *
        *     E - to purchase ticket for economy class   *
        *     M - to return to Main Menu                 *
        **************************************************""")
    choice1=input("select your choice")
    def alert2():
        print("Wrong choice. Kindly choose as specified")
        return purchase()
    func_dict1={
    'B':Business,
    "E":Economy,
    "M":menu,
    'b':Business,
    "e":Economy,
    "m":menu}
    clear()
    func_dict1.get(choice1,alert2)()
def view():
    a=ViewSeat()
    a.viewArrangement()
    print(a.getArrangement())
    input("Press Enter to go back to main menu")
    clear()
    return menu()
def Business():
    for x in seat_Business:
        if x==0:
            break   #to ensure if the business class seat is vacant
    else:
        print("Sorry! the business class seat is full")
        print("do you want to book economy class?")
        alter=input("enter Y or N")
        if alter=="Y" or alter=="y":
            Economy()
            return
        elif alter=="N" or alter=="n":
            print("Next trip leaves in next one hour.")
            return
        else:
            clear()
            print ("You entered wrong choice. Please try again")
            return Business()
    try:
        SeatNo=int(input("please enter your desired seat number"))

    except:
        print("Please enter a number between 1 and 10")
        return Business()
    if SeatNo>10 or SeatNo<1:
        print("Please choose between 1 to 10")
        return Business()
    elif seat_Business[SeatNo-1]==0:
        seat_Business[SeatNo-1]=1
    else:
        print("sorry! the seat is already reserved. Choose another seat number")
        return Business()
    SeatReserve(SeatNo,1)
    return menu()
def SeatReserve(SeatNo,x):  #function to get the deatil of the passenger
    try:
        age=int(input("Enter your age"))
    except:
        print("Enter your age in numbers")
        return SeatReserve(SeatNo,x)
    print("Enter your destination\n1.Penang to Langkawi\n2.Langkawi to Penang")
    location=int(input("Enter 1 or 2"))
    if location==1:
        Loc="PENANG TO LANGKAWI"
    else:
        Loc="LANGKAWI TO PENANG"
    name=input("Enter your name")
    address=input("Enter your address")
    myTicket=tickets(Loc,SeatNo,name,age,address,FID,x)
    myTicket.bookTicket()
    clear()
    print("You have successfully purchased ticket.\n")
    input("press enter to view your ticket")
    clear()
    print(myTicket.getTicket())
    input("Press enter to return to Main menu")
def Economy():
    for x in seat_Economy:
        if x==0:
            break   #to ensure if the economy class seat is vacant
    else:
        print("Sorry! this deck is completely full")
        if 0 in seat_Business:
            print("Do you like to book business class ticket?(Y/N)")
            alter=input("Enter either Y or N")
            if alter=='Y' or alter=='y':
                return Business()
            else:
                print("Please wait till the next trip?")
    try:
        SeatNo=int(input("please enter your desired seat number"))
    except:
        print("Please enter a number between 11 and 50")
    if SeatNo>50 or SeatNo<11:
        print("Please choose between 11 to 50")
        return Economy()
    elif seat_Economy[SeatNo-11]==0:
        seat_Economy[SeatNo-11]=1
    else:
        print("sorry! the seat is already reserved. Choose another seat number")
        return Economy()
    SeatReserve(SeatNo,0)
    clear()
    return menu()
menu()