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 json
def permutations(array):
    if len(array) == 0:#沒有元素的時候
        return [[]]
    if len(array) == 1:
        return [array]
    else:
        arr = []
        for i in range(len(array)):
            array[0], array[i] = array[i], array[0]#決定第一個固定的數字
            start = array[0]
            end = permutations(array[1:])#往下切陣列
            for x in end:
                arr.append([start]+x)#頭加尾
    return arr
def BF(input):
    N = len(input)
    a =[]
    l=[]
    sum=0
    min=10000000
    cost=input
    for i in range(N):#產生可能的排列
        a.append(i)
    l=permutations(a)
    for p in range(len(l)):
        sum=0
        for job in range(N):
            sum+=cost[job][l[p][job]]#job 跟 agent 對應,p代表第幾個組合
        if sum<min:#找最小可能
            assign=l[p]
            min=sum
    return min,assign

# main
with open('input.json', 'r') as inputFile:
    data = json.load(inputFile) # load data
    for key in data:
        input = data[key] # load each input

        # Brute Force Algorithm

        min,assignment = BF(input)

        print('Question: ' + str(key))
        print('Assignment:', assignment)
        print('Cost:', min)
        print()


# Third-party libraries:
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.decomposition import PCA
from sklearn.metrics import classification_report
# My libraries:
import dataset.dataset as dtset
from dbclass.dbclass import DBCLASS

# Basic configurations:
plt.rcParams.update({'font.size': 14, 'figure.figsize': (8, 6)})

def cross_validation_trainning(dbclass, ds_train, ds_test, prob_thold_list=[], pca_test=False):
    """
    """

    if prob_thold_list == []:
        prob_thold_list = np.append(np.arange(0, 1, 0.1), 0.999)

    # Metrics arrays:
    accuracy_vec = np.zeros(len(prob_thold_list))
    prob_score_vec = np.zeros(len(prob_thold_list))
    confidence_vec = np.zeros(len(prob_thold_list))
    rejection_vec = np.zeros(len(prob_thold_list))
    # Optimum settings:
    max_train_confidence = 0
    best_prob_thold = 0
    best_metrics = {}

    dbclass.fit(ds_train['data'], ds_train['target'])
    for t_index, prob_thold in enumerate(prob_thold_list):
        y_pred, y_scores = dbclass.predict(ds_test['data'], prob_thold, return_labels=False)
        confusion_matrix = build_confusion_matrix(ds_test['target'], y_pred, labels=dbclass.pgc.keys())
        class_metrics = get_dbclass_metrics(confusion_matrix, ds_test['target'], y_pred, y_scores, prob_thold)

        accuracy_vec[t_index] = class_metrics['accuracy']
        prob_score_vec[t_index] = class_metrics['prob_score']
        confidence_vec[t_index] = class_metrics['confidence']
        rejection_vec[t_index] = class_metrics['rejection']

        if class_metrics['confidence'] > max_train_confidence:
            max_train_confidence = class_metrics['confidence']
            best_prob_thold = prob_thold
            best_metrics = class_metrics

        if not pca_test:
            if len(prob_thold_list) <= 11:
                print("\nProbability score threshold:", prob_thold)
                print('confidence:', class_metrics['confidence'])
                print(confusion_matrix)
            elif int(100*(t_index/len(prob_thold_list))) % 10 == 0:
                print(str(int(100*(t_index/len(prob_thold_list)))) + '% concluído...')

    if not pca_test:
        x_array = prob_thold_list
        Y_arrays = (accuracy_vec, prob_score_vec, confidence_vec, rejection_vec)
        datalabels = ["Accuracy", "Probability score", "Confidence", "Rejection"]
        fig_name = "Classifier trainning metrics"
        plot_train_metris(x_array, Y_arrays, datalabels, fig_name)

    return best_prob_thold, best_metrics

def build_confusion_matrix(y_true, y_pred, labels=[], normalize=True):
    """
    """

    if len(labels) > 0:
        n_classes = len(labels) + 1
    else:
        n_classes = len(np.unique(y_pred)) + 1

    confusion_matrix = np.zeros([n_classes, n_classes])
    for y, y_hat in zip(y_true, y_pred):
        confusion_matrix[y][y_hat] += 1

    if normalize is True:
        percent_convert = 1/len(y_true)
        confusion_matrix = np.round(confusion_matrix*(percent_convert), 2)

    return confusion_matrix

def get_dbclass_metrics(confusion_matrix, y_true, y_pred, y_scores, prob_thold):
    """
    """

    class_metrics = {}
    class_metrics['accuracy'] = np.trace(confusion_matrix)/np.sum(confusion_matrix)
    non_negclass_scores = y_scores[np.where(y_pred != -1)]
    if len(non_negclass_scores) > 0:
        class_metrics['prob_score'] = np.sum(non_negclass_scores)/len(non_negclass_scores)
    else:
        class_metrics['prob_score'] = 1
    if len(np.where(y_true == -1)[0]) > 0 or len(np.where(y_pred == -1)[0]) > 0:
        class_metrics['rejection'] = np.sum(confusion_matrix[:, -1])/np.sum(confusion_matrix)
    else:
        class_metrics['rejection'] = 0

    # Classifier confidence level:
    avg_confidence = (class_metrics['accuracy'] + class_metrics['prob_score']) / 2
    weighted_confidence = prob_thold * class_metrics['accuracy'] + (1 - prob_thold) * class_metrics['prob_score']
    class_metrics['confidence'] = min([avg_confidence, weighted_confidence])

    for metric in class_metrics.keys():
        class_metrics[metric] = round(class_metrics[metric], 6)

    return class_metrics

def get_class_metrics(confusion_matrix):
    """
    """

    class_metrics = {}
    class_metrics['accuracy'] = np.trace(confusion_matrix)/np.sum(confusion_matrix)

    for metric in class_metrics.keys():
        class_metrics[metric] = round(class_metrics[metric], 6)

    return class_metrics

def plot_train_metris(x_array, Y_arrays, datalabels, fig_name):
    '''
    '''
    plt.close(fig_name)
    plt.figure(fig_name)
    for y_array, label in zip(Y_arrays, datalabels):
        if len(x_array) > 11:
            plt.plot(x_array, y_array, label=label, linewidth=2, zorder=5)
        else:
            plt.plot(x_array, y_array, label=label, linewidth=2, linestyle='--', marker='s', zorder=5)
    plt.xlabel('Probability threshold')
    plt.ylabel('Performance')
    plt.xlim([0, 1])
    plt.ylim([0, 1])
    plt.legend(loc='center left', ncol=1, prop={'size': 12})
    plt.grid(True)
    plt.tight_layout()
    plt.show()

def get_pca_data(data, n_components, svd_solver='full', whiten=True):
    """
    """

    # Compute a PCA on the face dataset (treated as unlabeled dataset):
    # unsupervised feature extraction / dimensionality reduction
    pca = PCA(n_components=n_components, svd_solver=svd_solver,
              whiten=whiten).fit(data)
    # Projecting the input data on the eigenfaces orthonormal basis:
    pca_data = pca.transform(data)

    return pca_data

def perform_pca_test(ds, pca_limit=200):
    """
    """

    ds_pca = ds.copy()
    n_components_max = min([ds['data'].shape[1], pca_limit])
    pca_step = min([10, int(n_components_max/20)])
    n_components = pca_step
    n_components_best = 0
    max_confidence = 0

    while n_components < n_components_max:
        print('Actual n_components:', n_components, '- max:', n_components_max)
        ds_pca['data'] = get_pca_data(ds['data'], n_components=n_components)
        ds_train, ds_test = dtset.split_data(ds_pca, prop_train=0.8)

        # Trainning the model using cross validation in the trainning dataset:
        dbclass = DBCLASS()
        prob_thold_list = np.arange(0.45, 0.55, 0.01)
        best_prob_thold, class_metrics = cross_validation_trainning(dbclass, ds_train, ds_test, prob_thold_list, pca_test=True)
        if class_metrics['confidence'] > max_confidence:
            max_confidence = class_metrics['confidence']
            n_components_best = n_components

        n_components += pca_step

    return n_components_best, max_confidence

def dbclass_model_test(dbclass, ds, noclass_label="Unknown", cmap='Blues'):
    """
    """

    y_pred, y_scores = dbclass.predict(ds['data'], return_labels=False)
    confusion_matrix = build_confusion_matrix(ds['target'], y_pred, labels=dbclass.pgc.keys())
    class_metrics = get_dbclass_metrics(confusion_matrix, ds['target'], y_pred, y_scores, dbclass.prob_thold)

    ds_pred = print_classification_report(ds, y_pred, noclass_label)

    print('\nConfusion matrix:')
    vmin = min([0, confusion_matrix.min()])
    vmax = max([confusion_matrix.max()])
    plt.figure()
    sns.heatmap(confusion_matrix, annot=np.round(confusion_matrix, 2), cmap=cmap, vmin=vmin, vmax=vmax)
    target_names = [str(x) for x in dbclass.target_names] + [dbclass.unk_class_label]
    target_idxs = [i+0.5 for i in range(len(target_names))]
    plt.xticks(target_idxs, target_names, rotation=-90)
    plt.yticks(target_idxs, target_names, rotation=0)
    plt.tight_layout()
    plt.show()

    print('\nIndex and Target label:')
    for index, tgt_name in enumerate(ds['target_names']):
        print('Index:', index,'Label:', tgt_name)
    print("\nProbability score threshold:", dbclass.prob_thold)
    for metric in class_metrics.keys():
        print(metric, class_metrics[metric])

    dtset.plot_dataset(ds_pred, feat_index=(0, 1), labeled=True, fig_name="model test")

def print_classification_report(ds, y_pred, noclass_label="Unknown"):
    """
    """

    ds_pred = ds.copy()
    if len(np.where(ds['target'] == -1)[0]) > 0 or len(np.where(y_pred == -1)[0]) > 0:
        report_y_true = np.where(ds['target'] == -1, len(ds['target_names']), ds['target'])
        report_y_pred = np.where(y_pred == -1, len(ds['target_names']), y_pred)
        report_target_names = np.append(ds['target_names'], noclass_label)
    else:
        report_y_true = ds['target']
        report_y_pred = y_pred
        report_target_names = ds['target_names']
    report_target_names = np.array([str(name) for name in report_target_names])
    ds_pred['target'] = y_pred
    ds_pred['target_names'] = report_target_names
    print(classification_report(report_y_true, report_y_pred, target_names=report_target_names))

    return ds_pred


import copy
from typing import Any, Literal, Self

import gymnasium as gym
import numpy as np
import torch
import torch.nn.functional as F

from tianshou.data import Batch, to_torch
from tianshou.data.batch import BatchProtocol
from tianshou.data.types import RolloutBatchProtocol
from tianshou.policy import BasePolicy
from tianshou.policy.base import TLearningRateScheduler
from tianshou.utils.net.continuous import VAE
from tianshou.utils.optim import clone_optimizer

class BCQPolicy(BasePolicy):
    """Implementation of BCQ algorithm. arXiv:1812.02900.

    :param actor_perturbation: the actor perturbation. `(s, a -> perturbed a)`
    :param actor_perturbation_optim: the optimizer for actor network.
    :param critic: the first critic network.
    :param critic_optim: the optimizer for the first critic network.
    :param critic2: the second critic network.
    :param critic2_optim: the optimizer for the second critic network.
    :param vae: the VAE network, generating actions similar to those in batch.
    :param vae_optim: the optimizer for the VAE network.
    :param device: which device to create this model on.
    :param gamma: discount factor, in [0, 1].
    :param tau: param for soft update of the target network.
    :param lmbda: param for Clipped Double Q-learning.
    :param forward_sampled_times: the number of sampled actions in forward function.
        The policy samples many actions and takes the action with the max value.
    :param num_sampled_action: the number of sampled actions in calculating target Q.
        The algorithm samples several actions using VAE, and perturbs each action to get the target Q.
    :param observation_space: Env's observation space.
    :param action_scaling: if True, scale the action from [-1, 1] to the range
        of action_space. Only used if the action_space is continuous.
    :param action_bound_method: method to bound action to range [-1, 1].
        Only used if the action_space is continuous.
    :param lr_scheduler: if not None, will be called in `policy.update()`.

    .. seealso::

        Please refer to :class:`~tianshou.policy.BasePolicy` for more detailed explanation.
    """

    def __init__(
        self,
        *,
        actor_perturbation: torch.nn.Module,
        actor_perturbation_optim: torch.optim.Optimizer,
        critic: torch.nn.Module,
        critic_optim: torch.optim.Optimizer,
        action_space: gym.Space,
        vae: VAE,
        vae_optim: torch.optim.Optimizer,
        critic2: torch.nn.Module | None = None,
        critic2_optim: torch.optim.Optimizer | None = None,
        # TODO: remove? Many policies don't use this
        device: str | torch.device = "cpu",
        gamma: float = 0.99,
        tau: float = 0.005,
        lmbda: float = 0.75,
        forward_sampled_times: int = 100,
        num_sampled_action: int = 10,
        observation_space: gym.Space | None = None,
        action_scaling: bool = False,
        action_bound_method: Literal["clip", "tanh"] | None = "clip",
        lr_scheduler: TLearningRateScheduler | None = None,
    ) -> None:
        # actor is Perturbation!
        super().__init__(
            action_space=action_space,
            observation_space=observation_space,
            action_scaling=action_scaling,
            action_bound_method=action_bound_method,
            lr_scheduler=lr_scheduler,
        )
        self.actor_perturbation = actor_perturbation
        self.actor_perturbation_target = copy.deepcopy(self.actor_perturbation)
        self.actor_perturbation_optim = actor_perturbation_optim

        self.critic = critic
        self.critic_target = copy.deepcopy(self.critic)
        self.critic_optim = critic_optim

        critic2 = critic2 or copy.deepcopy(critic)
        critic2_optim = critic2_optim or clone_optimizer(critic_optim, critic2.parameters())
        self.critic2 = critic2
        self.critic2_target = copy.deepcopy(self.critic2)
        self.critic2_optim = critic2_optim

        self.vae = vae
        self.vae_optim = vae_optim

        self.gamma = gamma
        self.tau = tau
        self.lmbda = lmbda
        self.device = device
        self.forward_sampled_times = forward_sampled_times
        self.num_sampled_action = num_sampled_action

    def train(self, mode: bool = True) -> Self:
        """Set the module in training mode, except for the target network."""
        self.training = mode
        self.actor_perturbation.train(mode)
        self.critic.train(mode)
        self.critic2.train(mode)
        return self

    def forward(
        self,
        batch: RolloutBatchProtocol,
        state: dict | BatchProtocol | np.ndarray | None = None,
        **kwargs: Any,
    ) -> Batch:
        """Compute action over the given batch data."""
        # There is "obs" in the Batch
        # obs_group: several groups. Each group has a state.
        obs_group: torch.Tensor = to_torch(batch.obs, device=self.device)
        act_group = []
        for obs_orig in obs_group:
            # now obs is (state_dim)
            obs = (obs_orig.reshape(1, -1)).repeat(self.forward_sampled_times, 1)
            # now obs is (forward_sampled_times, state_dim)

            # decode(obs) generates action and actor perturbs it
            act = self.actor_perturbation(obs, self.vae.decode(obs))
            # now action is (forward_sampled_times, action_dim)
            q1 = self.critic(obs, act)
            # q1 is (forward_sampled_times, 1)
            max_indice = q1.argmax(0)
            act_group.append(act[max_indice].cpu().data.numpy().flatten())
        act_group = np.array(act_group)
        return Batch(act=act_group)

    def sync_weight(self) -> None:
        """Soft-update the weight for the target network."""
        self.soft_update(self.critic_target, self.critic, self.tau)
        self.soft_update(self.critic2_target, self.critic2, self.tau)
        self.soft_update(self.actor_perturbation_target, self.actor_perturbation, self.tau)

    def learn(self, batch: RolloutBatchProtocol, *args: Any, **kwargs: Any) -> dict[str, float]:
        # batch: obs, act, rew, done, obs_next. (numpy array)
        # (batch_size, state_dim)
        batch: Batch = to_torch(batch, dtype=torch.float, device=self.device)
        obs, act = batch.obs, batch.act
        batch_size = obs.shape[0]

        # mean, std: (state.shape[0], latent_dim)
        recon, mean, std = self.vae(obs, act)
        recon_loss = F.mse_loss(act, recon)
        # (....) is D_KL( N(mu, sigma) || N(0,1) )
        KL_loss = (-torch.log(std) + (std.pow(2) + mean.pow(2) - 1) / 2).mean()
        vae_loss = recon_loss + KL_loss / 2

        self.vae_optim.zero_grad()
        vae_loss.backward()
        self.vae_optim.step()

        # critic training:
        with torch.no_grad():
            # repeat num_sampled_action times
            obs_next = batch.obs_next.repeat_interleave(self.num_sampled_action, dim=0)
            # now obs_next: (num_sampled_action * batch_size, state_dim)

            # perturbed action generated by VAE
            act_next = self.vae.decode(obs_next)
            # now obs_next: (num_sampled_action * batch_size, action_dim)
            target_Q1 = self.critic_target(obs_next, act_next)
            target_Q2 = self.critic2_target(obs_next, act_next)

            # Clipped Double Q-learning
            target_Q = self.lmbda * torch.min(target_Q1, target_Q2) + (1 - self.lmbda) * torch.max(
                target_Q1,
                target_Q2,
            )
            # now target_Q: (num_sampled_action * batch_size, 1)

            # the max value of Q
            target_Q = target_Q.reshape(batch_size, -1).max(dim=1)[0].reshape(-1, 1)
            # now target_Q: (batch_size, 1)

            target_Q = (
                batch.rew.reshape(-1, 1) + (1 - batch.done).reshape(-1, 1) * self.gamma * target_Q
            )

        current_Q1 = self.critic(obs, act)
        current_Q2 = self.critic2(obs, act)

        critic1_loss = F.mse_loss(current_Q1, target_Q)
        critic2_loss = F.mse_loss(current_Q2, target_Q)

        self.critic_optim.zero_grad()
        self.critic2_optim.zero_grad()
        critic1_loss.backward()
        critic2_loss.backward()
        self.critic_optim.step()
        self.critic2_optim.step()

        sampled_act = self.vae.decode(obs)
        perturbed_act = self.actor_perturbation(obs, sampled_act)

        # max
        actor_loss = -self.critic(obs, perturbed_act).mean()

        self.actor_perturbation_optim.zero_grad()
        actor_loss.backward()
        self.actor_perturbation_optim.step()

        # update target network
        self.sync_weight()

        return {
            "loss/actor": actor_loss.item(),
            "loss/critic1": critic1_loss.item(),
            "loss/critic2": critic2_loss.item(),
            "loss/vae": vae_loss.item(),
        }


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

from tornado.web import RequestHandler, Application
from tornado.ioloop import IOLoop
import pickle
import os
import bz2

from search.snippeter import Snippeter
import search.manifest as manifest

class QueryHandler(RequestHandler):
    def initialize(self, docs, snippeter):
        self.docs = docs
        self.snippeter = snippeter

    def _gen_results(self, docids, q):
        docids = docids.split(",")
        docids = [int(x) for x in docids]
        results = []

        snippets = self.snippeter.snippet_batch(docids, q)

        for docid in docids:
            results.append({
                "doc_id" : docid,
                "title" : self.docs[docid]["metadata"]["title"],
                "url" : self.docs[docid]["metadata"]["url"],
                "snippet" : snippets[docid],
            })

        return results

    def get(self):
        docids = self.get_argument("id")
        q = self.get_argument("q")
        self.write({
            "results" : self._gen_results(docids, q)
        })

def make_doc_app(doc_f, tfidf_f, snippet_len):
    docs = pickle.load(bz2.open(doc_f, "rb"))
    tfidf = pickle.load(open(tfidf_f, "rb"))
    snippeter = Snippeter(tfidf, docs, snippet_len)
    app = Application([
        (r'/doc', QueryHandler, dict(docs=docs, snippeter=snippeter))
    ])

    return app

'''
import re

def get_port(url):
    return int(re.findall(r':([0-9]+)', url)[0])

if __name__ == "__main__":
    import sys
    srv_id = int(sys.argv[1])
    app = make_doc_app(srv_id)
    app.listen(get_port(manifest.DOC_SRV[srv_id]))
    IOLoop.current().start()
'''


import itertools
from typing import Union, List, Tuple

from allennlp.data.dataset_readers import DatasetReader
from allennlp.data import Vocabulary
from torch.utils.data import Dataset
from allennlp.data.dataset import Batch
from allennlp.data.instance import Instance

class AllennlpDataset(Dataset):

    def __init__(self,
                 vocab: Vocabulary,
                 iterable: List=None,
                 reader: DatasetReader=None,
                 dataset_path: Union[str, List[str], Tuple[str]]=None
                 ):

        self.vocab = vocab
        self.reader = reader
        self.dataset_path = dataset_path
        if iterable is not None:
            self.iterable = iterable
        else:
            assert reader is not None and dataset_path is not None
            if type(dataset_path) in [list, tuple]:
                self.iterable = list(itertools.chain(*[self.reader.read(x) for x in dataset_path]))
            else:
                self.iterable = self.reader.read(dataset_path)
        self.iterator = (x for x in self.iterable)

        self._length = None

    def __len__(self):
        """
        This is gross but will go away in the next pytorch release,
        as they are introducing an `IterableDataset` class which doesn't
        need to have a length:
        https://pytorch.org/docs/master/data.html#torch.utils.data.IterableDataset
        """
        if self._length is None:
            self._length = 0
            for i in self.iterator:
                self._length += 1
            self.iterator = (x for x in self.iterable)
        return self._length

    def __getitem__(self, idx) -> Instance:
        get_next = next(self.iterator, None)
        if get_next is None:
            self.iterator = (x for x in self.iterable)
            get_next = next(self.iterator)
        return get_next

    # Function to tell the torch DataLoader how to batch up our custom data, i.e Instances
    def allennlp_collocate(self, batch):
        batch = Batch(batch)
        batch.index_instances(self.vocab)
        return batch.as_tensor_dict(batch.get_padding_lengths())


import numpy as np
from six import string_types, integer_types
from synge.shape import _intradistance_aux, _aux_rms

def _validate_shape(shape):
    if not (
        hasattr(shape, "__iter__") and (len(shape) == 2 or len(shape) == 1)
    ) and not isinstance(shape, integer_types):
        raise ValueError('Error! "shape" must be an integer or a tuple with size 2!')
    return True

def _validate_shape_intradistance(shape):
    if not (hasattr(shape, "__iter__") and len(shape) == 2):
        raise ValueError('Error! "shape" must be a tuple with size 2!')
    return True

distributions_list = {
    "uniform": lambda shape, param1, param2: np.random.uniform(
        param1 - param2, param1 + param2, shape
    ),
    "gaussian": lambda shape, param1, param2: np.random.normal(param1, param2, shape),
    "logistic": lambda shape, param1, param2: np.random.logistic(param1, param2, shape),
    # "triangular": lambda shape, param1,param2: np.random.triangular(-param1, 0, param2, shape),
    # "gamma": lambda shape, param1,param2: np.random.gamma(
    #     2 + 8 * np.random.rand(), param1 / 5, shape
    # ),
    # "gap": lambda shape, param1,param2: gap(shape, param1),
    # "binaire": lambda shape, param1,param2: np.random.choice([0, 1], shape),
}
"""List of distributions for which you can just provide a string as input."""

# Aliases for distributions should be put here.
distributions_list["normal"] = distributions_list["gaussian"]

valid_distributions = distributions_list.keys()
"""List of valid strings for distributions."""

class Distribution(object):
    def __init__(self, f, **kwargs):
        self.f = f
        self.kwargs = kwargs

    def __call__(self, shape, intra_distance, *args, **kwargs):
        new_kwargs = self.kwargs.copy()
        new_kwargs.update(kwargs)  # add keyword arguments given in __init__
        if intra_distance:
            assert _validate_shape_intradistance(shape)
            out = _intradistance_aux(shape)
            return out * self.f((shape[0], 1), *args, **new_kwargs)
        else:
            assert _validate_shape(shape)
            return self.f(shape, *args, **new_kwargs)

def gap(shape, param):
    out = np.zeros(shape)
    try:
        for j in range(shape[1]):
            new_shape = (2 * shape[0], 1)
            aux = np.random.normal(0, param, new_shape)
            med_aux = _aux_rms(aux)
            median = np.median(med_aux)
            out[:, j] = aux[med_aux > median][: shape[0]]
    except BaseException:
        j = 0
        new_shape = (2 * shape[0], 1)
        aux = np.random.normal(0, param, new_shape)
        med_aux = _aux_rms(aux)
        median = np.median(med_aux)
        out[:, j] = aux[med_aux > median][: shape[0]]
    return out

def get_dist_function(d):
    """
    Transforms distribution name into respective function.
    Args:
        d (str or function): Input distribution str/function.
    Returns:
        function: Actual function to compute the intended distribution.
    """
    if isinstance(d, Distribution):
        return d
    elif hasattr(d, "__call__"):
        return Distribution(d)
    elif isinstance(d, string_types):
        try:
            return Distribution(distributions_list[d])
        except KeyError:
            raise ValueError(
                'Invalid distribution name "'
                + d
                + '". Available names are: '
                + ", ".join(distributions_list.keys())
            )
    else:
        raise ValueError("Invalid distribution input!")


import os.path
import time

from androguard.core.bytecodes.apk import APK
from androguard.misc import AnalyzeAPK
import util
from util import logger

def get_features(file_path):
    time_start = time.time()
    if os.path.isdir(file_path):
        return
    file_output_parts = os.path.splitext(file_path)[0].split("/")
    file_output_parts[-1] += ".json"
    file_output_parts.insert(len(file_output_parts)-1, "json")
    file_output_path = ""
    for part in file_output_parts:
        file_output_path = os.path.join(file_output_path, part)
    file_output_dir = file_output_path[:file_output_path.rfind("\\")]
    if not os.path.exists(file_output_dir):
        os.mkdir(file_output_dir)
    if os.path.exists(file_output_path):
        logger.info("the APK " + file_path + " already abstracted")
        return

    logger.info("start to analyze the APK " + file_path + "...")
    a = APK(file_path)
    data = {}
    declared_xml_pms = set()
    declared_pms = set()
    activities = set()
    services = set()
    providers = set()
    receivers = set()
    hardware = set()

    for pms in a.get_permissions():
        declared_xml_pms.add(pms)
    for pms in a.get_declared_permissions():
        declared_pms.add(pms)
    for activity in a.get_activities():
        activities.add(activity)
    for service in a.get_services():
        services.add(service)
    for provider in a.get_providers():
        providers.add(provider)
    for receiver in a.get_receivers():
        receivers.add(receiver)
    for feature in a.get_features():
        hardware.add(feature)
    data['declared_xml_pms'] = declared_xml_pms
    data['declared_pms'] = declared_pms
    data['activities'] = activities
    data['services'] = services
    data['providers'] = providers
    data['receivers'] = receivers
    data['hardware'] = hardware

    sensitive_apis = set()
    android_suspicious_apis = ["getExternalStorageDirectory", "getSimCountryIso", "execHttpRequest",
                               "sendTextMessage", "getSubscriberId", "getDeviceId", "getPackageInfo",
                               "getSystemService", "getWifiState",
                               "setWifiEnabled", "setWifiDisabled", "Cipher"]
    other_suspicious_apis = ["Ljava/net/HttpURLconnection;->setRequestMethod(Ljava/lang/String;)",
                             "Ljava/net/HttpURLconnection",
                             "Lorg/apache/http/client/methods/HttpPost",
                             "Landroid/telephony/SmsMessage;->getMessageBody",
                             "Ljava/io/IOException;->printStackTrace", "Ljava/lang/Runtime;->exec"]
    a, d, dx = AnalyzeAPK(file_path)
    for method in dx.get_methods():
        api = str(method.get_method())
        for other_suspicious_api in other_suspicious_apis:
            if other_suspicious_api in api:
                sensitive_apis.add(api)
        if (";->" not in api) or (not api.startswith("Landroid")):
            continue
        api_parts = api.split(";->")
        api_class = api_parts[0].strip()
        api_name = api_parts[1].split("(")[0].strip()
        if api_name in android_suspicious_apis:
            sensitive_apis.add(api_class + ";->" + api_name)
    data['sensitive_apis'] = sensitive_apis

    util.get_json(file_output_path, data)
    time_final = time.time()
    logger.info(file_path + " analyzed successfully in " + str(time_final - time_start) + "s")

def solution(*paths):
    apk_list = []
    for path in paths:
        apk_list.extend(util.get_file_list(path))

    for path in apk_list:
        get_features(path)


import pickle
import matplotlib.pyplot as plt
import numpy as np

res = pickle.load(open("/local/results/sb3.p", 'rb'))
dia = res.dmat_dia
hst = plt.hist(dia, 100)

maxx = np.max(hst[1])
maxy = np.max(hst[0])

perc = res.params.st2['memory']['smoothing_percentile']
x = np.percentile(dia, perc)

plt.plot((x,x), (0, maxy), 'r')

res.plot_delta()


#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Fri Dec  1 10:07:10 2017

@author: eduardopoleo
"""

# Building the CNN
from keras.models import Sequential
from keras.layers import Conv2D # images are 2D
from keras.layers import MaxPooling2D
from keras.layers import Flatten # conver the feature map into the vector to feed the NN
from keras.layers import Dense

# Initializing the CNN

classifier = Sequential()

# Convolution
#args:
# number of feature detectors ( the more the better usually )
# number of columns per feature detector
# number of rows per feature detector
# input_shape(dim1, dim2, number_of_channels)
classifier.add(Conv2D(32, (3, 3), input_shape = (32, 32, 3), activation = 'relu')) # tensorflow backend order

# Pooling
# pool_size: size of the subtable that slides over the feature table to choose the max feature
classifier.add(MaxPooling2D(pool_size = (2,2)))

# Train Acc: 0.80, Test Acc: 0.77
# Adding an additional layer to improve Acc results:

classifier.add(Conv2D(32, (3, 3), activation = 'relu'))
classifier.add(MaxPooling2D(pool_size = (2,2)))

# Flattening
classifier.add(Flatten())

# Full connection
classifier.add(Dense(units = 128, activation = 'relu'))
classifier.add(Dense(units = 1, activation = 'sigmoid'))

# Compiling the CNN
classifier.compile(optimizer = 'adam', loss = 'binary_crossentropy', metrics = ['accuracy'])

# Fitting the CNN to the images taken from (https://keras.io/preprocessing/image/)

from keras.preprocessing.image import ImageDataGenerator

# pre proccessing the image to prevent overfitting
train_datagen = ImageDataGenerator(
        rescale=1./255,
        shear_range=0.2,
        zoom_range=0.2,
        horizontal_flip=True)

# just need to scale the test set not process it cuz it's used on predictions
test_datagen = ImageDataGenerator(rescale=1./255)

training_set = train_datagen.flow_from_directory(
        'dataset/training_set',
        target_size=(32, 32),
        batch_size=32,
        class_mode='binary')

# the dimensions to which all images found will be resized.
# Default: (256, 256). Do not know if you can go higher
# In general higher better cuz we have more information about the pixels but it's more
# computational intensive.
test_set = test_datagen.flow_from_directory(
        'dataset/test_set',
        target_size=(32, 32),
        batch_size=32,
        class_mode='binary')

history = classifier.fit_generator(
        training_set,
        steps_per_epoch=8000/32, # number of batches to be considered on the training set
        epochs=1) # number of batches to bbe considered on the test set.

# Part 3, Making new prediction

import numpy as np
from keras.preprocessing import image

test_image = image.load_img('dataset/single_prediction/cat_or_dog_2.jpg', target_size=(32, 32))
test_image = image.img_to_array(test_image) # add the color dimention => 3
test_image = np.expand_dims(test_image, axis = 0)
result = classifier.predict_generator(test_image, steps = 2000/32)

training_set.class_indices


import math
import sys
import cv2
import numpy as np
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.uic import loadUi
from matplotlib import pyplot as plt

class ShowImage(QMainWindow):
    def __init__(self):
        #A1 - A8
        super(ShowImage, self).__init__()
        loadUi('untitled.ui', self)
        self.Image = None
        self.button_loadCitra.clicked.connect(self.fungsi)
        self.button_prosesCitra.clicked.connect(self.grayscale)
        self.actionOperasi_Pencerahan.triggered.connect(self.brightness)
        self.actionSimple_Contrast.triggered.connect(self.contrast)
        self.actionContrast_Stretching.triggered.connect(self.stretching)
        self.actionNegative_Image.triggered.connect(self.negative)
        self.actionBinary_Image.triggered.connect(self.binary)

        #A9 - C2
        self.actionHistogram_Grayscale.triggered.connect(self.histogram)
        self.actionHistogram_RGB.triggered.connect(self.rgbhist)
        self.actionHistogram_Equalization.triggered.connect(self.equalizehist)

        #B1 - B4
        self.actionTranslasi.triggered.connect(self.translasi)
        self.action90_Derajat.triggered.connect(self.rotasi90d)
        self.action_90_Derajat.triggered.connect(self.rotasi_90d)
        self.action45_Derajat.triggered.connect(self.rotasi45d)
        self.action_45_Derajat.triggered.connect(self.rotasi_45d)
        self.action180_Derajat.triggered.connect(self.rotasi180d)
        self.actionTranspose.triggered.connect(self.transpose)
        self.action2x.triggered.connect(self.zoomIn2x)
        self.action3x.triggered.connect(self.zoomIn3x)
        self.action4x.triggered.connect(self.zoomIn4x)
        self.action1_2.triggered.connect(self.zoomOuthalf)
        self.action1_4.triggered.connect(self.zoomOutquarter)
        self.action3_4.triggered.connect(self.zoomOut75)
        self.actionCrop.triggered.connect(self.crop)

        #C1-C2
        self.actionAdd.triggered.connect(self.adder)
        self.actionSub.triggered.connect(self.subtractor)
        self.actionMul.triggered.connect(self.multipler)
        self.actionDiv.triggered.connect(self.divider)
        self.actionAnd.triggered.connect(self.operasiAnd)
        self.actionOr.triggered.connect(self.operasiOr)
        self.actionXor.triggered.connect(self.operasiXor)

    #A1 - A8
    def fungsi(self):
        self.Image = cv2.imread('3.jpeg')
        self.displayImage(1)

    def grayscale(self):
        H, W = self.Image.shape[:2]
        gray = np.zeros((H, W), np.uint8)
        for i in range(H):
            for j in range(W):
                gray[i, j] = np.clip(0.299 * self.Image[i, j, 0] +
                                     0.587 * self.Image[i, j, 1] +
                                     0.114 * self.Image[i, j, 2], 0, 255)
        self.Image = gray
        self.displayImage(2)

    def brightness(self):
        try:
            self.Image = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
        except:
            pass

        H, W = self.Image.shape[:2]
        brightness = 50
        for i in range(H):
            for j in range(W):
                a = self.Image.item(i, j)
                b = np.clip(a + brightness, 0, 255)

                self.Image.itemset((i, j), b)

        self.displayImage(2)

    def contrast(self):
        try:
            self.Image = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
        except:
            pass

        H, W = self.Image.shape[:2]
        contrast = 1.7
        for i in range(H):
            for j in range(W):
                a = self.Image.item(i, j)
                b = np.clip(a * contrast, 0, 255)

                self.Image.itemset((i, j), b)

        self.displayImage(2)

    def stretching(self):
        try:
            self.Image = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
        except:
            pass

        H, W = self.Image.shape[:2]
        minV = np.min(self.Image)
        maxV = np.max(self.Image)

        for i in range(H):
            for j in range(W):
                a = self.Image.item(i, j)
                b = float(a - minV) / (maxV - minV) * 255

                self.Image.itemset((i, j), b)

        self.displayImage(2)

    def negative(self):
        try:
            self.Image = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
        except:
            pass

        H, W = self.Image.shape[:2]
        for i in range(H):
            for j in range(W):
                a = self.Image.item(i, j)
                b = math.ceil(255 - a)

                self.Image.itemset((i, j), b)

        self.displayImage(2)

    def binary(self):
        try:
            self.Image = cv2.cvtColor(self.Image, cv2.COLOR_BGR2GRAY)
        except:
            pass

        H, W = self.Image.shape[:2]
        for i in range(H):
            for j in range(W):
                a = self.Image.item(i, j)
                if a < 89:
                    b = 0
                else:
                    b = 255

                self.Image.itemset((i, j), b)

        self.displayImage(2)

    def displayImage(self, windows=1):
        qformat = QImage.Format_Indexed8

        if len(self.Image.shape) == 3:
            if (self.Image.shape[2]) == 4:
                qformat = QImage.Format_RGBA8888
            else:
                qformat = QImage.Format_RGB888

        img = QImage(self.Image, self.Image.shape[1], self.Image.shape[0], self.Image.strides[0], qformat)

        img = img.rgbSwapped()

        if windows == 1:
            self.imgLabel.setPixmap(QPixmap.fromImage(img))

            self.imgLabel.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
            self.imgLabel.setScaledContents(True)

        if windows == 2:
            self.hasilLabel.setPixmap(QPixmap.fromImage(img))

            self.hasilLabel.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter)
            self.hasilLabel.setScaledContents(True)

    #A9 - A11
    def histogram(self):
        H, W = self.Image.shape[:2]
        gray = np.zeros((H, W), np.uint8)
        for i in range(H):
            for j in range(W):
                gray[i, j] = np.clip(0.299 * self.Image[i, j, 0] +
                                     0.587 * self.Image[i, j, 1] +
                                     0.114 * self.Image[i, j, 2], 0, 255)
        self.Image = gray
        self.displayImage(2)

        plt.hist(self.Image.ravel(), 255, [0, 255])
        plt.show()

    def rgbhist(self):
        color = ('b', 'g', 'r')
        for i, col in enumerate(color):
            histo = cv2.calcHist([self.Image], [i], None, [256], [0, 256])
            plt.plot(histo, color=col)
            plt.xlim([0, 256])
        self.displayImage(2)
        plt.show()

    def equalizehist(self):
        hist, bins = np.histogram(self.Image.flatten(), 256, [0, 256])
        cdf = hist.cumsum()
        cdf_normalized = cdf * hist.max() / cdf.max()
        cdf_m = np.ma.masked_equal(cdf, 0)
        cdf_m = (cdf_m - cdf_m.min()) * 255 / (cdf_m.max() - cdf_m.min())
        cdf = np.ma.filled(cdf_m, 0).astype('uint8')
        self.Image = cdf[self.Image]
        self.displayImage(2)

        plt.plot(cdf_normalized, color='b')
        plt.hist(self.Image.flatten(), 256, [0, 256], color='r')
        plt.xlim([0, 256])
        plt.legend(('cdf', 'histogram'), loc='upper left')
        plt.show()

    # B1 - B4
    def translasi(self):
        h, w = self.Image.shape[:2]
        quarter_h, quarter_w = h / 4, w / 4
        T = np.float32([[1, 0, quarter_w], [0, 1, quarter_h]])
        img = cv2.warpAffine(self.Image, T, (w, h))
        self.Image = img
        self.displayImage(2)

    def rotasi(self, degree):

        h, w = self.Image.shape[:2]
        rotationMatrix = cv2.getRotationMatrix2D((w / 2, h / 2), degree, .7)
        cos = np.abs(rotationMatrix[0, 0])
        sin = np.abs(rotationMatrix[0, 1])
        nW = int((h * sin) + (w * cos))
        nH = int((h * cos) + (w * sin))
        rotationMatrix[0, 2] += (nW / 2) - w / 2
        rotationMatrix[1, 2] += (nH / 2) - h / 2
        rot_image = cv2.warpAffine(self.Image, rotationMatrix, (h, w))
        self.Image = rot_image
        self.displayImage(2)

    def rotasi90d(self):
        self.rotasi(90)
    def rotasi_90d(self):
        self.rotasi(-90)
    def rotasi45d(self):
        self.rotasi(45)
    def rotasi_45d(self):
        self.rotasi(-45)
    def rotasi180d(self):
        self.rotasi(180)

    def transpose(self):
        img = cv2.transpose(self.Image)
        #plt.imshow(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
        cv2.imshow('Original', self.Image)
        cv2.imshow('Transposed', img)
        cv2.waitKey()

    def zoomIn2x(self):
        scale=2
        resize_img = cv2.resize(self.Image, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
        cv2.imshow('Original', self.Image)
        cv2.imshow('Zoom In', resize_img)
        cv2.waitKey()

    def zoomIn3x(self):
        scale=3
        resize_img = cv2.resize(self.Image, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
        cv2.imshow('Original', self.Image)
        cv2.imshow('Zoom In', resize_img)
        cv2.waitKey()

    def zoomIn4x(self):
        scale=4
        resize_img = cv2.resize(self.Image, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
        cv2.imshow('Original', self.Image)
        cv2.imshow('Zoom In', resize_img)
        cv2.waitKey()

    def zoomOuthalf(self):
        scale=0.5
        resize_img = cv2.resize(self.Image, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
        cv2.imshow('Original', self.Image)
        cv2.imshow('Zoom Out', resize_img)
        cv2.waitKey()

    def zoomOutquarter(self):
        scale=0.25
        resize_img = cv2.resize(self.Image, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
        cv2.imshow('Original', self.Image)
        cv2.imshow('Zoom Out', resize_img)
        cv2.waitKey()

    def zoomOut75(self):
        scale=0.75
        resize_img = cv2.resize(self.Image, None, fx=scale, fy=scale, interpolation=cv2.INTER_CUBIC)
        cv2.imshow('Original', self.Image)
        cv2.imshow('Zoom Out', resize_img)
        cv2.waitKey()

    def crop(self):
        h, w = self.Image.shape[:2]
        start_x=1
        start_y=1
        end_x=100
        end_y=100
        img = self.Image
        crop_img = img[start_x:end_x, start_y:end_y]
        cv2.imshow('Original', self.Image)
        cv2.imshow('Cropped', crop_img)
        cv2.waitKey()

    #C1-C2
    def adder(self):
        img1 = cv2.imread('1.jpeg', 0)
        img2 = cv2.imread('2.jpeg', 0)
        add = img1 + img2
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('Added', add)
        cv2.waitKey()

    def subtractor(self):
        img1 = cv2.imread('1.jpeg', 0)
        img2 = cv2.imread('2.jpeg', 0)
        subtract = img1 - img2
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('Subtracted', subtract)
        cv2.waitKey()

    def multipler(self):
        img1 = cv2.imread('1.jpeg', 0)
        img2 = cv2.imread('2.jpeg', 0)
        mul = img1 * img2
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('Multiplied', mul)
        cv2.waitKey()

    def divider(self):
        img1 = cv2.imread('1.jpeg', 0)
        img2 = cv2.imread('2.jpeg', 0)
        div = img1 / img2
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('Divided', div)
        cv2.waitKey()

    def operasiAnd(self):
        img1 = cv2.imread('1.jpeg', 1)
        img2 = cv2.imread('2.jpeg', 1)
        img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
        img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
        and_op = cv2.bitwise_and(img1, img2)
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('And result', and_op)
        cv2.waitKey()

    def operasiOr(self):
        img1 = cv2.imread('1.jpeg', 1)
        img2 = cv2.imread('2.jpeg', 1)
        img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
        img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
        or_op = cv2.bitwise_or(img1, img2)
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('Or result', or_op)
        cv2.waitKey()

    def operasiXor(self):
        img1 = cv2.imread('1.jpeg', 1)
        img2 = cv2.imread('2.jpeg', 1)
        img1 = cv2.cvtColor(img1, cv2.COLOR_BGR2RGB)
        img2 = cv2.cvtColor(img2, cv2.COLOR_BGR2RGB)
        xor_op = cv2.bitwise_xor(img1, img2)
        cv2.imshow('Image 1 Original', img1)
        cv2.imshow('Image 2 Original', img2)
        cv2.imshow('Xor result', xor_op)
        cv2.waitKey()

app = QtWidgets.QApplication(sys.argv)
window = ShowImage()
window.setWindowTitle('Pertemuan 2')
window.show()
sys.exit(app.exec_())

# image = cv2.imread('dodge.jpg', cv2.IMREAD_COLOR)
# cv2.imshow('Gambar 1', image)
# cv2.waitKey(0)
# cv2.destroyAllWindows()


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from vk_fetching import GetGroupInfoById

def GetBarPlot(measure, agg, l):
    fig, ax = plt.subplots()
    ax.bar(l.index, l[measure][agg], color = 'red')
    fig.set_figwidth(16)
    fig.set_figheight(6)
    ax.set_xlabel('Месяцы')
    if measure == 'post_views':
        if agg == 'mean':
            ax.set_ylabel('Среднее кол-во просмотров')
        else:
            ax.set_ylabel('Суммарное кол-во просмотров')
    elif measure == 'post_likes':
        if agg == 'mean':
            ax.set_ylabel('Среднее кол-во лайков')
        else:
            ax.set_ylabel('Суммарное кол-во лайков')
    elif measure == 'post_comments':
        if agg == 'mean':
            ax.set_ylabel('Среднее кол-во комментариев')
        else:
            ax.set_ylabel('Суммарное кол-во комментариев')
    else:
        if agg == 'mean':
            ax.set_ylabel('Среднее кол-во репостов')
        else:
            ax.set_ylabel('Суммарное кол-во репостов')
    plt.savefig('png/'+measure+agg+'.png')
    # plt.show(block=False)

def get_group_stats(group):

    post_data = GetGroupInfoById(group)

    df = pd.DataFrame(post_data)

    df['post_dt'] = pd.to_datetime(df['post_dt'])
    df['post_date'] = df['post_dt'].map(lambda x: x.strftime('%Y-%m-%d'))
    df['post_ym'] = df['post_dt'].map(lambda x: x.strftime('%Y-%m'))

    mean_day_posts = round(df.groupby(['post_date'])['post_id'].count().mean())
    mean_month_posts = round(df.groupby(['post_ym'])['post_id'].count().mean())
    mean_likes = round(df['post_likes'].mean())
    mean_reposts = round(df['post_reposts'].mean())
    mean_comments = round(df['post_comments'].mean())
    mean_views = round(df['post_views'].mean())
    conv_views_to_likes = round((mean_likes/mean_views)*100, 2)

    # print('------------------------')
    # print('Среднее кол-во постов в день:',mean_day_posts)
    # print('Среднее кол-во постов в месяц:',mean_month_posts)
    # print('Среднее кол-во лайков под постом:',mean_likes)
    # print('Среднее кол-во репостов под постом:',mean_reposts)
    # print('Среднее кол-во комментариев под постом:',mean_comments)
    # print('Среднее кол-во просмотров под постом:',mean_views)
    # print('Конверсия просмотров к лайкам:',conv_views_to_likes)

    l = df.groupby(['post_ym']).agg({'post_views':['sum', 'mean'], 'post_likes':['sum', 'mean'], 'post_comments':['sum', 'mean'], 'post_reposts':['sum', 'mean']})

    return_list = {
        'mean_day_posts':mean_day_posts,
        'mean_month_posts':mean_month_posts,
        'mean_likes':mean_likes,
        'mean_reposts':mean_reposts,
        'mean_comments':mean_comments,
        'mean_views':mean_views,
        'conv_views_to_likes':conv_views_to_likes
    }

    return return_list

    # GetBarPlot('post_views', 'mean')
    # GetBarPlot('post_views', 'sum')
    # GetBarPlot('post_comments', 'mean')
    # GetBarPlot('post_comments', 'sum')
    # GetBarPlot('post_likes', 'mean')
    # GetBarPlot('post_likes', 'sum')
    # GetBarPlot('post_reposts', 'mean')
    # GetBarPlot('post_reposts', 'sum')
    # print('Графики сохранены в папку png/')


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

import json
import requests
from urllib.parse import urljoin
from urllib import request

BASE_URL = 'http://192.168.26.67:8000'
AUTH = ('admin', 'admin')

def test_get_user_list():
    rsp = requests.get(urljoin(BASE_URL, '/snippets/'), auth=AUTH, headers={
        'Accept': 'application/json'
    })
    return rsp

def test_post_user_list():
    json_data = dict(
        title='zhangsan',
        code='oo',
        linenos='true'
    )
    rsp = requests.post(urljoin(BASE_URL, '/snippets/'), auth=AUTH, headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    }, data=json.dumps(json_data))
    return rsp

def test_get_user():
    rsp = requests.get(urljoin(BASE_URL, '/snippets/17'), auth=AUTH, headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    })
    return rsp

def test_put_user():
    json_data = dict(
        title='zhangsan',
        code='oo',
        linenos='true'
    )
    # 注意最后的 /
    rsp = requests.put(urljoin(BASE_URL, '/snippets/1/'), auth=AUTH, headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    }, data=json.dumps(json_data),
                       )
    return rsp

def test_patch_user():
    json_data = dict(
        title='aaaaaaaaaaaaaaaaaaaa',
    )
    rsp = requests.patch(urljoin(BASE_URL, '/snippets/1/'), auth=AUTH, headers={
        'Accept': 'application/json',
        'Content-Type': 'application/json',
    }, data=json.dumps(json_data),
                         )
    return rsp

def call(body, url):
    headers = {}

    # 这里有个细节，如果body需要json形式的话，需要做处理
    # 可以是data = json.dumps(body)
    response = requests.post(url, data=json.dumps(body), headers=headers)
    # 也可以直接将data字段换成json字段，2.4.3版本之后支持
    # response  = requests.post(url, json = body, headers = headers)

    # 返回信息
    print(response.text)
    # 返回响应头
    print(response.status_code)
    return "回调发送成功"

if __name__ == "__main__":
    url = "http://192.168.0.128:8080/api/register/result"
    data = '[{"bugid": "12260", "type": "success", "bugname": "firefox"}, {"bugid": "12261", "type": "success", "bugname": "xulrunner"}, {"bugid": "12262", "type": "failed", "bugname": "xulrunner-devel"}]'
    call(data, url)
    request.urlopen('http://www.sina.com.cn')
    # r = requests.get("http://localhost:9999/helloworld/yourname")
    # print(r.text)