from decimal import Decimal
import bisect
import gc
import logging
import os
import re
import shlex
import subprocess
import sys

from gi import require_version

require_version("Gtk", "3.0")
require_version("Gst", "1.0")
from gi.repository import Gst, Gtk

from otrverwaltung3p import fileoperations
from otrverwaltung3p import path as otrvpath
from otrverwaltung3p.actions.baseaction import BaseAction
from otrverwaltung3p.constants import Format, Program
from otrverwaltung3p.libs.pymediainfo import MediaInfo

import psutil

Gst.init(None)

class Cut(BaseAction):
    format_dict = {
        "Baseline@L1.3": Format.MP4,
        "High@L3": Format.HQ,
        "High@L4": Format.HD,
        "High@L3.0": Format.HQ,
        "High@L3.1": Format.HQ,
        "High@L3.2": Format.HD,
        "Main@L3.2": Format.HD2,
        "Main@L4": Format.HD3,  # gcurse:HD3
        "Main@L3": Format.HD0,
        "Simple@L1": Format.AVI,
    }
    bframe_delays = {
        Format.AVI: 1,
        Format.HD: 2,
        Format.HD0: 1,
        Format.HD2: 0,
        Format.HQ: 2,
        Format.HQ0: 1,
        Format.MP4: 0,
        Format.MP40: 0,
        Format.HD3: 0,  # gcurse:HD3
    }

    def __init__(self, app, gui):
        self.log = logging.getLogger(self.__class__.__name__)
        BaseAction.__init__(self)
        self.update_list = True
        self.app = app
        self.config = app.config
        self.gui = gui
        self.media_info = None

    def cut_file_by_cutlist(self, filename, cutlist, program_config_value):
        raise Exception("Override this method!")

    def create_cutlist(self, filename, program_config_value):
        raise Exception("Override this method!")

    def get_codeccore(self, fname):
        if self.media_info.tracks[1].format_profile and ".HD." in fname:
            if self.media_info.tracks[1].format_profile in ["Main@L3.2", "Main@L4"]:  # New HD2/03 gcurse:HD3
                codeccore = 0
                return codeccore, self.format_dict[self.media_info.tracks[1].format_profile]  # gcurse:HD3

        if not self.media_info.tracks[1].writing_library:
            codeccore = -1
            return codeccore, None
        else:
            match = re.search(r"core [0-9]{2,3}", self.media_info.tracks[1].writing_library)
            if match is not None:
                codeccore = int(match.group(0).split(" ")[1])
            else:
                codeccore = -1
            # try:
            #     codeccore = int(self.media_info.tracks[1].writing_library.split(' ')[3])
            # except (ValueError, IndexError):
            #     try:
            #         codeccore = int(self.media_info.tracks[1].writing_library.split(' ')[2])
            #     except (ValueError, IndexError):
            #         codeccore = -1
            return codeccore, None

    def get_format(self, filename):
        self.log.debug("function start")
        root, extension = os.path.splitext(filename)

        if sys.platform == "win32":
            lib_file = self.config.get_program("mediainfo").replace(".exe", ".dll")
            self.media_info = MediaInfo.parse(filename, library_file=lib_file)
        else:
            self.media_info = MediaInfo.parse(filename)

        codec_core, vformat = self.get_codeccore(filename)  # gcurse:HD3

        self.log.debug(f"get_format:get_codeccore: {codec_core}")
        if self.media_info.tracks[1].width:
            video_width = self.media_info.tracks[1].width
        else:
            video_width = None

        if extension == ".avi":
            if os.path.splitext(root)[1] == ".HQ":
                if codec_core >= 125:
                    vformat = Format.HQ
                    self.log.debug(f"vformat = Format.HQ, value: {Format.HQ}")
                else:  # old OTR file
                    vformat = Format.HQ0
                    self.log.debug(f"vformat = Format.HQ0, value: {Format.HQ0}")
                ac3name = os.path.splitext(root)[0] + ".HD.ac3"
            elif os.path.splitext(root)[1] == ".HD":
                if codec_core >= 125:
                    vformat = Format.HD
                    self.log.debug(f"vformat = Format.HD, value: {Format.HD}")
                elif codec_core == 0:  # new HD 2020
                    pass  # gcurse:HD3
                    # vformat = Format.HD2
                    # self.log.debug(f"vformat = Format.HD2, value: {Format.HD2}")
                else:  # old OTR file
                    vformat = Format.HD0  # old HD
                    self.log.debug(f"vformat = Format.HD0, value: {Format.HD0}")
                ac3name = root + ".ac3"
            elif os.path.splitext(root)[1] == ".test" and codec_core == 0:
                vformat = Format.HD2
                ac3name = os.path.splitext(root)[0] + ".ac3"
            else:
                vformat = Format.AVI
                ac3name = root + ".HD.ac3"
        elif extension == ".mp4":
            if os.path.splitext(root)[1] == ".HQ":
                vformat = Format.HQ
                ac3name = os.path.splitext(root)[0] + ".HD.ac3"
            elif os.path.splitext(root)[1] == ".HD":
                vformat = Format.HD
                ac3name = root + ".ac3"
            else:
                if codec_core >= 125:
                    vformat = Format.MP4
                else:
                    vformat = Format.MP40  # old mp4
                ac3name = root + ".HD.ac3"
        elif extension == ".mkv":
            if os.path.splitext(root)[1] == ".HQ":
                if codec_core >= 125:
                    vformat = Format.HQ
                else:  # old OTR file
                    vformat = Format.HQ0
                ac3name = os.path.splitext(root)[0] + ".HD.ac3"
            elif os.path.splitext(root)[1] == ".HD":
                if codec_core >= 125:
                    vformat = Format.HD
                elif codec_core == 0:
                    pass  # gcurse:HD3, vformat has been determined in get_codeccore()
                    # vformat = Format.HD2
                else:  # old OTR file
                    vformat = Format.HD0  # old HD
                ac3name = root + ".ac3"
            elif os.path.splitext(root)[1] == ".test":
                vformat = Format.HD2
                ac3name = os.path.splitext(root)[0] + ".HD.ac3"
            else:
                format_profile = self.media_info.tracks[1].format_profile
                if " / " in format_profile:
                    format_profile = format_profile.split(" / ")[0]
                try:
                    vformat = self.format_dict[format_profile]
                except KeyError as e:
                    self.log.warning(f"vformatKeyError: {e}")
                    return -1, None, None, None, f"cut.py:185 vformat KeyError: {e}"
                # The format profiles "Baseline@L1.3" and "Main@L3" are ambiguous:
                if vformat == Format.MP4:
                    if codec_core < 125:
                        vformat = Format.MP40
                elif vformat == Format.HD0:
                    if video_width is not None and video_width == 720:
                        vformat = Format.HQ0
                self.log.debug(f"Format: {vformat}")
                ac3name = root + ".HD.ac3"
        elif extension == ".ac3":
            vformat = Format.AC3
            ac3name = root
        else:
            return -1, None, None, None, ""
        if os.path.isfile(ac3name):
            return vformat, ac3name, self.bframe_delays[vformat], codec_core, ""
        else:
            return vformat, None, self.bframe_delays[vformat], codec_core, ""

    def get_program(self, filename, manually=False):
        if manually:
            programs = {
                Format.AVI: self.config.get("general", "cut_avis_man_by"),
                Format.HD0: self.config.get("general", "cut_hqs_man_by"),
                Format.HD2: self.config.get("general", "cut_hd2_man_by"),
                Format.HD3: self.config.get("general", "cut_hd2_man_by"),
                Format.HD: self.config.get("general", "cut_hqs_man_by"),
                Format.HQ0: self.config.get("general", "cut_hqs_man_by"),
                Format.HQ: self.config.get("general", "cut_hqs_man_by"),
                Format.MP4: self.config.get("general", "cut_mp4s_man_by"),
                Format.MP40: self.config.get("general", "cut_mp4s_man_by"),
            }
        else:
            programs = {
                Format.AVI: self.config.get("general", "cut_avis_by"),
                Format.HD0: self.config.get("general", "cut_hqs_by"),
                Format.HD2: self.config.get("general", "cut_hd2_by"),
                Format.HD3: self.config.get("general", "cut_hd2_by"),
                Format.HD: self.config.get("general", "cut_hqs_by"),
                Format.HQ0: self.config.get("general", "cut_hqs_by"),
                Format.HQ: self.config.get("general", "cut_hqs_by"),
                Format.MP4: self.config.get("general", "cut_mp4s_by"),
                Format.MP40: self.config.get("general", "cut_mp4s_by"),
            }

        vformat, ac3, bframe_delay, _, verror = self.get_format(filename)

        if vformat < 0:
            return (
                -1,
                f"Format konnte nicht bestimmt werden/wird (noch) nicht unterstützt.\n {verror}",
                False,
            )

        if vformat == Format.AC3:
            return (
                -1,
                "AC3 wird automatisch mit der HD.avi verarbeitet und nicht einzeln geschnitten.",
                False,
            )

        config_value = programs[vformat]

        if sys.platform == "linux":
            vdub = otrvpath.get_internal_virtualdub_path("vdub.exe")
        elif sys.platform == "win32":
            vdub = self.app.config.get_program("vdub")
            if not (os.path.exists(vdub) and vdub.endswith(vdub.exe)):
                vdub = None
        else:
            vdub = None

        if "avidemux" in config_value:
            return Program.AVIDEMUX, config_value, ac3
        elif "intern-VirtualDub" in config_value:
            return (
                Program.VIRTUALDUB,
                otrvpath.get_internal_virtualdub_path("VirtualDub.exe"),
                ac3,
            )
        elif "intern-vdub" in config_value:
            if vdub is None:
                return (
                    -2,
                    "vdub wurde nicht gefunden. Das Paket 'otr-verwaltung3p-vdub' scheint nicht installiert zu sein",
                    False,
                )
            else:
                return Program.VIRTUALDUB, vdub, ac3
        elif "vdub" in config_value or "VirtualDub" in config_value:
            if vdub is None:
                return (
                    -2,
                    (
                        "'vdub.exe' konnte nicht gefunden werden. Bitte prüfen Sie den Pfad unter "
                        "Einstellungen->Programme->vdub.exe"
                    ),
                    False,
                )
            else:
                return Program.VIRTUALDUB, config_value, ac3
        elif "CutInterface" in config_value and manually:
            return Program.CUT_INTERFACE, config_value, ac3
        elif "SmartMKVmerge" in config_value:
            return Program.SMART_MKVMERGE, config_value, ac3
        else:
            return (
                -2,
                f"Programm '{config_value}' konnte nicht bestimmt werden. " "Es wird nur VirtualDub unterstützt.",
                False,
            )

    def generate_filename(self, filename, forceavi=0):
        """generate filename for a cut video file."""
        root, extension = os.path.splitext(os.path.basename(filename))
        if forceavi == 1:
            extension = ".avi"
        new_name = root + "-cut" + extension
        cut_video = os.path.join(self.config.get("general", "folder_cut_avis"), new_name)
        return cut_video

    def mux_ac3(self, filename, cut_video, ac3_file, cutlist):
        # cuts the ac3 and muxes it with the avi into an mkv
        mkvmerge = self.config.get_program("mkvmerge")
        root, extension = os.path.splitext(filename)
        mkv_file = os.path.splitext(cut_video)[0] + ".mkv"
        # env
        my_env = os.environ.copy()
        my_env["LANG"] = "C"

        # creates the timecodes string for splitting the .ac3 with mkvmerge
        timecodes = ",".join(
            [
                self.seconds_to_hms(start) + "," + self.seconds_to_hms(start + duration)
                for start, duration in cutlist.cuts_seconds
            ]
        )
        # splitting .ac3. Every second fragment will be used.
        # return_value = subprocess.call(
        #     [mkvmerge, "--split", "timecodes:" + timecodes, "-o", root + "-%03d.mka", ac3_file]
        # )
        try:
            blocking_process = subprocess.Popen(
                [
                    mkvmerge,
                    "--ui-language",
                    "en_US",
                    "--split",
                    "timecodes:" + timecodes,
                    "-o",
                    root + "-%03d.mka",
                    ac3_file,
                ],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                universal_newlines=True,
                env=my_env,
            )
        except OSError as e:
            return None, e.strerror + ": " + mkvmerge

        return_value = blocking_process.wait()
        # return_value=0 is OK, return_value=1 means a warning. Most probably non-ac3-data that
        # has been omitted.
        # TODO: Is there some way to pass this warning to the conclusion dialog?
        if return_value != 0 and return_value != 1:
            return None, None, str(return_value)

        if len(cutlist.cuts_seconds) == 1:  # Only the second fragment is needed. Delete the rest.
            fileoperations.rename_file(root + "-002.mka", root + ".mka")
            fileoperations.remove_file(root + "-001.mka")
            if os.path.isfile(root + "-003.mka"):
                fileoperations.remove_file(root + "-003.mka")

        else:  # Concatenating every second fragment.
            command = [mkvmerge, "-o", root + ".mka", root + "-002.mka"]
            command[len(command) :] = [
                "+" + root + "-%03d.mka" % (2 * n) for n in range(2, len(cutlist.cuts_seconds) + 1)
            ]
            #            return_value = subprocess.call(command)
            try:
                blocking_process = subprocess.Popen(
                    command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, env=my_env,
                )
            except OSError as e:
                return None, e.strerror + ": " + mkvmerge
            return_value = blocking_process.wait()
            if return_value != 0:  # There should be no warnings here
                return None, None, str(return_value)

            for n in range(1, 2 * len(cutlist.cuts_seconds) + 2):  # Delete all temporary audio fragments
                if os.path.isfile(root + "-%03d.mka" % n):
                    fileoperations.remove_file(root + "-%03d.mka" % n)

                    # Mux the cut .avi with the resulting audio-file into mkv_file
                    # TODO: Is there some way to pass possible warnings to the conclusion dialog?
                    # return_value = subprocess.call([mkvmerge, "-o", mkv_file, cut_video, root + ".mka"])
        try:
            blocking_process = subprocess.Popen(
                [mkvmerge, "-o", mkv_file, cut_video, root + ".mka"],
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                universal_newlines=True,
                env=my_env,
            )
        except OSError as e:
            return None, e.strerror + ": " + mkvmerge
        return_value = blocking_process.wait()
        if return_value != 0 and return_value != 1:
            return None, None, str(return_value)

        fileoperations.remove_file(root + ".mka")  # Delete remaining temporary files
        fileoperations.remove_file(cut_video)
        return mkv_file, ac3_file, None

    @staticmethod
    def seconds_to_hms(seconds, display=False):
        # converts the seconds into a timecode-format that mkvmerge understands
        minute, second = divmod(int(seconds), 60)  # discards milliseconds
        hour, minute = divmod(minute, 60)
        second = seconds - minute * 60 - hour * 3600  # for the milliseconds

        return f"{hour:02d}:{minute:02d}:{second:09.6f}"

    # def analyse_mediafile_old(self, filename):
    #     """Gets fps, dar, sar, number of frames and id of the ac3_stream of a movie using ffmpeg.
    #     Returns without error:
    #         fps, dar, sar, max_frames, ac3_stream, None
    #     with error:
    #         None, None, None, None, None, error_message
    #     """
    #     try:
    #         process = subprocess.Popen(
    #             [self.config.get_program("ffmpeg"), "-hide_banner", "-i", filename],
    #             stdout=subprocess.PIPE,
    #             stderr=subprocess.STDOUT,
    #             errors="replace",
    #             universal_newlines=True,
    #         )
    #     except OSError as e:
    #         self.log.warning(f"ffmpeg OSError: {e}")
    #         return (
    #             None,
    #             None,
    #             None,
    #             None,
    #             None,
    #             f"FFMPEG (static) konnte nicht ausgeführt werden!\n(cut.py:426 OSError: {e})",
    #         )
    #
    #     log = process.communicate()[0]
    #
    #     regex_video_infos = (
    #         r".*(Duration).*(\d{1,}):(\d{1,}):(\d{1,}.\d{1,}).*|.*(SAR) "
    #         r"(\d{1,}:\d{1,}) DAR (\d{1,}:\d{1,}).*\, (\d{2,}\.{0,}\d{0,}) "
    #         r"tbr.*|.*(Stream).*(\d{1,}:\d{1,}).*Audio.*ac3.*"
    #     )
    #     video_infos_match = re.compile(regex_video_infos)
    #     seconds = 0
    #     ac3_stream = fps = dar = sar = None
    #
    #     m = None
    #     for line in log.split("\n"):
    #         self.log.debug(line)
    #         m = re.search(video_infos_match, line)
    #
    #         if m:
    #             if "Duration" == m.group(1):
    #                 try:
    #                     seconds = float(m.group(2)) * 3600 + float(m.group(3)) * 60 + float(m.group(4))
    #                 except ValueError:
    #                     self.log.debug("Leave function")
    #                     error = "Dauer des Film konnte nicht ausgelesen werden."
    #                     return None, None, None, None, error
    #             elif "SAR" == m.group(5):
    #                 try:
    #                     sar = m.group(6)
    #                     dar = m.group(7)
    #                     fps = float(m.group(8))
    #                     self.log.debug(f"FPS: {fps}")
    #                 except ValueError:
    #                     self.log.debug("Leave function")
    #                     error = "Video Stream Informationen konnten nicht ausgelesen werden."
    #                     return None, None, None, None, error
    #             elif "Stream" == m.group(9):
    #                 ac3_stream = m.group(10)
    #         else:
    #             pass
    #
    #     if seconds != 0 and fps is not None and sar is not None and dar is not None:
    #         max_frames = round(seconds * fps)
    #         self.log.debug(f"fps: {fps}, dar: {dar}, sar: {sar}, max_frames: {max_frames}, ac3_stream: {ac3_stream}, ")
    #         return fps, dar, sar, max_frames, ac3_stream, None
    #
    #     error = "Es konnten keine Video Infos der zu bearbeitenden Datei ausgelesen werden."
    #     return None, None, None, None, None, error

    def analyse_mediafile(self, filename):
        if not self.media_info:
            _, _, _, _, _ = self.get_format(filename)
        error2 = ""
        duration = 0
        ac3_stream = fps = dar = sar = max_frames = None
        for track in self.media_info.tracks:
            if track.track_type == "Video":
                if int(track.count_of_stream_of_this_kind) > 1:
                    error2 = "\nDie Datei enthält mehr als eine Videospur!"
                    break
                duration = track.duration
                sar = track.pixel_aspect_ratio
                dar = track.display_aspect_ratio
                fps = float(track.frame_rate)
                max_frames = track.frame_count
            elif track.track_type == "Audio":
                if track.format == "AC-3":
                    ac3_stream = f"0:{track.streamorder}"

        if float(duration) > 0 and fps and sar and dar:
            self.log.debug(f"fps: {fps}, dar: {dar}, sar: {sar}, max_frames: {max_frames}, ac3_stream: {ac3_stream}, ")
            return fps, dar, sar, max_frames, ac3_stream, None

        error = ("Fehler beim Auslesen der Videoinformationen." "\n(cut.py:analyze_mediafile:510)") + error2
        return None, None, None, None, None, error

    def get_keyframes_from_file(self, filename, vformat=None):
        """returns keyframe list - in frame numbers"""

        if not os.path.isfile(filename + ".ffindex_track00.kf.txt"):
            try:
                command = [self.config.get_program("ffmsindex"), "-f", "-k", filename]
                process = subprocess.Popen(
                    command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, errors="replace", universal_newlines=True
                )
                self.show_indexing_progress(process)
            except OSError:
                return None, "ffmsindex konnte nicht aufgerufen werden."

        if os.path.isfile(filename + ".ffindex_track00.kf.txt"):
            filename_keyframes = filename + ".ffindex_track00.kf.txt"
        elif os.path.isfile(filename + ".ffindex_track01.kf.txt"):
            filename_keyframes = filename + ".ffindex_track01.kf.txt"
        elif os.path.isfile(filename + ".ffindex_track02.kf.txt"):
            filename_keyframes = filename + ".ffindex_track02.kf.txt"
        else:
            filename_keyframes = None

        try:
            index = open(filename_keyframes, "r")
        except (IOError, TypeError) as e:
            self.log.debug(f"{e}")
            return None, f"Keyframe-Datei von ffmsindex konnte nicht geöffnet werden.\n(cut.py:514 {e})"

        index.readline()  # Skip the first line, it is a comment
        index.readline()  # Skip the second line, it is 'fps 0'
        try:
            keyframes_list = [int(i) for i in index.read().splitlines()]
            # with open("/tmp/otrv_kf.txt", "w") as file_:
            #     c = 1
            #     for item in keyframes_list:
            #         file_.write(f"{item}\n")
            #         c += 1
            #         if c == 50:
            #             break
        except ValueError:
            return None, "Keyframes konnten nicht ermittelt werden."
        finally:
            index.close()
        if os.path.isfile(filename + ".ffindex"):
            fileoperations.remove_file(filename + ".ffindex")

        return keyframes_list, None

    def get_timecodes_from_file(self, filename):
        """returns frame->timecode and timecode->frame dict"""

        if not os.path.isfile(filename + ".ffindex_track00.tc.txt"):
            try:
                command = [
                    self.config.get_program("ffmsindex"),
                    "-f",
                    "-c",
                    "-k",
                    filename,
                ]
                process = subprocess.Popen(
                    command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, errors="replace", universal_newlines=True
                )
                self.show_indexing_progress(process)
            except OSError:
                return None, "ffmsindex konnte nicht aufgerufen werden."

        if os.path.isfile(filename + ".ffindex_track00.tc.txt"):
            filename_timecodes = filename + ".ffindex_track00.tc.txt"
        elif os.path.isfile(filename + ".ffindex_track01.tc.txt"):
            filename_timecodes = filename + ".ffindex_track01.tc.txt"
        elif os.path.isfile(filename + ".ffindex_track02.tc.txt"):
            filename_timecodes = filename + ".ffindex_track02.tc.txt"
        else:
            filename_timecodes = None

        try:
            index = open(filename_timecodes, "r")
        except (IOError, TypeError) as e:
            self.log.debug(f"{e}")
            return (
                None,
                None,
                "Timecode Datei von ffmsindex konnte nicht geöffnet werden.",
            )
        index.readline()  # Skip the first line, it is a comment
        try:
            frame_timecode = {}
            for line_num, line in enumerate(index):
                frame_timecode[line_num] = int(round(float(line.replace("\n", "").strip()), 2) / 1000 * Gst.SECOND)
            # with open("/tmp/otrv_frame_tc.txt", "w") as file_:
            #     for k, v in frame_timecode.items():
            #         file_.write(f"{k}: {v}\n")
            #         if k == 1000:
            #             break
        except ValueError:
            return None, None, "Timecodes konnten nicht ermittelt werden."
        finally:
            index.close()
            gc.collect()  # MEMORYLEAK
        # DEBUG
        # for index in range(0, 9):
        #     print(f"frame_timcode index: {index}: {frame_timecode[index]}")

        # Generate reverse dict
        timecode_frame = {v: k for k, v in frame_timecode.items()}
        # DEBUG
        # with open("/tmp/otrv_tc_frame.txt", "w") as file_:
        #     for k, v in timecode_frame.items():
        #         file_.write(f"{k} : {v}\n")
        #         if v == 1000:
        #             break
        if os.path.isfile(filename + ".ffindex"):
            fileoperations.remove_file(filename + ".ffindex")

        self.log.debug(f"Number of frames (frame_timecode dict): {list(frame_timecode.keys())[-1] + 1}")
        return frame_timecode, timecode_frame, None

    def show_indexing_progress(self, process):
        """Shows the progress of keyframe/timecode indexing in main_window"""
        self.log.debug("Function start")
        first_run = True
        while process.poll() is None:
            for line in iter(process.stdout.readline, ""):
                if not line or "done" in line:
                    break
                else:
                    dline = line.strip("\n")
                    self.log.debug(f"ffmsindex: {dline}")

                try:
                    if first_run and "Indexing" in line:
                        first_run = False
                        self.app.gui.main_window.set_tasks_text("Datei wird indiziert")

                    if len(line) > 25 and line[25].isdigit():
                        progress = int(line[25:].replace("%", ""))
                        # update progress
                        self.app.gui.main_window.set_tasks_progress(progress)

                    while Gtk.events_pending():
                        Gtk.main_iteration()
                except ValueError:
                    pass

        self.app.gui.main_window.set_tasks_text("")
        self.app.gui.main_window.set_tasks_progress(0)
        return

    def get_keyframe_in_front_of_frame(self, keyframes, frame):
        """Find keyframe less-than to frame."""

        if frame == 0:
            self.log.debug("Restricting! No keyframe before this position")
            return keyframes[0]
        else:
            i = bisect.bisect_left(keyframes, frame)
            if i:
                return keyframes[i - 1]
            else:
                raise ValueError

    def get_keyframe_after_frame(self, keyframes, frame):
        """Find keyframe greater-than to frame."""

        if frame >= keyframes[-1]:
            self.log.debug("Restricting! No keyframe after this position")
            return keyframes[-1]
        else:
            i = bisect.bisect_right(keyframes, frame)
            if i != len(keyframes):
                return keyframes[i]
            else:
                raise ValueError

    def complete_x264_opts(self, x264_opts, filename):
        """Analyse filename and complete the x264 options
        returns
          x264_opts  x264 options
        """

        bt709 = [
            "--videoformat",
            "pal",
            "--colorprim",
            "bt709",
            "--transfer",
            "bt709",
            "--colormatrix",
            "bt709",
        ]
        bt470bg = [
            "--videoformat",
            "pal",
            "--colorprim",
            "bt470bg",
            "--transfer",
            "bt470bg",
            "--colormatrix",
            "bt470bg",
        ]
        try:

            if self.media_info.tracks[1].color_primaries:
                if "709" in self.media_info.tracks[1].color_primaries:
                    x264_opts.extend(bt709)
                elif "470" in self.media_info.tracks[1].color_primaries:
                    x264_opts.extend(bt470bg)
            elif self.media_info.tracks[1].transfer_characteristics:
                if "709" in self.media_info.tracks[1].transfer_characteristics:
                    x264_opts.extend(bt709)
                elif "470" in self.media_info.tracks[1].transfer_characteristics:
                    x264_opts.extend(bt470bg)
            elif self.media_info.tracks[1].matrix_coefficients:
                if "709" in self.media_info.tracks[1].matrix_coefficients:
                    x264_opts.extend(bt709)
                elif "470" in self.media_info.tracks[1].matrix_coefficients:
                    x264_opts.extend(bt470bg)
            elif ".HD." in filename or ".HQ." in filename:
                x264_opts.extend(bt709)

            profile = [
                "--profile",
                self.media_info.tracks[1].format_profile.split("@L")[0].lower(),
            ]
            x264_opts.extend(profile)

            level = ["--level", self.media_info.tracks[1].format_profile.split("@L")[1]]
            x264_opts.extend(level)

            fps = ["--fps", self.media_info.tracks[1].frame_rate.split(" ")[0]]
            x264_opts.extend(fps)
        except IndexError:
            self.log.debug("Mediainfo IndexError. Using old method.")
            try:
                blocking_process = subprocess.Popen(
                    [self.config.get_program("mediainfo"), filename],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    universal_newlines=True,
                )
            except OSError as e:
                return None, f"Fehler: {e.errno}: {e.strerror} Filename: {e.filename}"
            except ValueError as e:
                return None, f"Falscher Wert: {e}"

            while True:
                line = blocking_process.stdout.readline()

                if line != "":
                    if "x264 core" in line:
                        self.log.info(line)
                        try:
                            x264_core = int(line.strip().split(" ")[30])
                        except (ValueError, IndexError):
                            continue
                    elif "Color primaries" in line and "709" in line:
                        x264_opts.extend(bt709)
                    elif "Color primaries" in line and "470" in line:
                        x264_opts.extend(bt470bg)
                    elif "Format profile" in line and "@L" in line:
                        try:
                            level = [
                                "--level",
                                str(float(line.strip().split("L")[1])),
                            ]  # test for float
                            profile = [
                                "--profile",
                                line.strip().split("@L")[0].split(":")[1].lower().lstrip(),
                            ]
                        except (ValueError, IndexError) as e:
                            self.log.debug(f"{e}")
                            continue

                        x264_opts.extend(profile)
                        x264_opts.extend(level)
                    elif "Frame rate" in line:
                        try:
                            fps = ["--fps", str(float(line.strip().split(" ")[3]))]
                            self.log.debug("FPS: {}".format(fps))
                        except (ValueError, IndexError) as e:
                            self.log.debug(f"{e}")
                            continue
                        x264_opts.extend(fps)
                else:
                    break
        return x264_opts

    def complete_ffmpeg_opts(self, ffmpeg_codec_options, filename, quality=None, vformat=None):
        fps, dar, sar, max_frames, ac3_stream, error = self.analyse_mediafile(filename)
        codec = None
        ffmpeg_commandline = []
        bt709 = "videoformat=pal:colorprim=bt709:transfer=bt709:colormatrix=bt709"
        bt470bg = "videoformat=pal:colorprim=bt470bg:transfer=bt470bg:colormatrix=bt470bg"

        try:
            if self.media_info.tracks[1].format == "AVC":
                codec = "libx264"
            if self.media_info.tracks[1].color_primaries:
                if "709" in self.media_info.tracks[1].color_primaries:
                    ffmpeg_codec_options += f":{bt709}"
                elif "470" in self.media_info.tracks[1].color_primaries:
                    ffmpeg_codec_options += f":{bt470bg}"
            elif self.media_info.tracks[1].transfer_characteristics:
                if "709" in self.media_info.tracks[1].transfer_characteristics:
                    ffmpeg_codec_options += f":{bt709}"
                elif "470" in self.media_info.tracks[1].transfer_characteristics:
                    ffmpeg_codec_options += f":{bt470bg}"
            elif self.media_info.tracks[1].matrix_coefficients:
                if "709" in self.media_info.tracks[1].matrix_coefficients:
                    ffmpeg_codec_options += f":{bt709}"
                elif "470" in self.media_info.tracks[1].matrix_coefficients:
                    ffmpeg_codec_options += f":{bt470bg}"
            elif ".HD." in filename or ".HQ." in filename:
                ffmpeg_codec_options += f":{bt709}"

            profile = [
                "-profile:v",
                self.media_info.tracks[1].format_profile.split("@L")[0].lower(),
            ]
            ffmpeg_commandline.extend(profile)

            level = [
                "-level",
                self.media_info.tracks[1].format_profile.split("@L")[1].replace(".", ""),
            ]
            ffmpeg_commandline.extend(level)
        except IndexError:
            self.log.debug("Mediainfo IndexError. Using old method.")
            try:
                blocking_process = subprocess.Popen(
                    [self.config.get_program("mediainfo"), filename],
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    universal_newlines=True,
                )
            except OSError as e:
                return (
                    None,
                    f"Fehler: {e.errno} Filename: {e.filename} Error: {e.strerror}",
                )
            except ValueError as e:
                return None, f"Falscher Wert: {e}"
            while True:
                line = blocking_process.stdout.readline()

                if line != "":
                    if "x264 core" in line:
                        codec = "libx264"
                        try:
                            codec_core = int(line.strip().split(" ")[30])
                        except (ValueError, IndexError) as e:
                            self.log.debug(f"{e}")
                            continue
                    elif "Matrix coefficients" in line and "709" in line:
                        ffmpeg_codec_options.extend(bt709)
                    elif "Matrix coefficients" in line and "470" in line:
                        ffmpeg_codec_options.extend(bt470bg)
                    elif "Format profile" in line and "@L" in line:
                        try:
                            level = [
                                "-level",
                                str(float(line.strip().split("L")[1])),
                            ]  # test for float
                            profile = [
                                "-profile:v",
                                line.strip().split("@L")[0].split(":")[1].lower().lstrip(),
                            ]
                        except (ValueError, IndexError) as e:
                            self.log.debug(f"{e}")
                            continue
                        ffmpeg_commandline.extend(profile)
                        ffmpeg_commandline.extend(level)
                else:
                    break

        if codec == "libx264":
            # x264opts = ":".join([option for option in ffmpeg_codec_options])
            # x264opts = x264opts.lstrip(":")
            ffmpeg_commandline.extend(["-aspect", dar, "-vcodec", "libx264"])
            ffmpeg_commandline.extend(shlex.split(ffmpeg_codec_options))
            # if vformat and vformat == Format.MP4:
            #     ffmpeg_commandline.extend(
            #         [
            #             "-preset",
            #             "veryfast",
            #             "-x264opts",
            #             x264opts,
            #         ]
            #     )
            # elif vformat and vformat == Format.MP40:
            #     ffmpeg_commandline.extend(
            #         [
            #             "-preset",
            #             "medium",
            #             "-x264opts",
            #             x264opts,
            #         ]
            #     )
            # elif vformat and vformat == Format.HD2:
            #     ffmpeg_commandline.extend(
            #         [
            #             "-preset",
            #             "veryfast",
            #             "-tune",
            #             "film",
            #             "-x264opts",
            #             x264opts,
            #         ]
            #     )
            # elif vformat and vformat == Format.HQ0:
            #     ffmpeg_commandline.extend(
            #         [
            #             "-x264opts",
            #             x264opts,
            #         ]
            #     )
            # else:
            #     ffmpeg_commandline.extend(
            #         [
            #             "-preset",
            #             "medium",
            #             "-tune",
            #             "film",
            #             "-x264opts",
            #             x264opts,
            #         ]
            #     )

        return ffmpeg_commandline

    def show_progress(self, blocking_process):
        # progress_match = re.compile(r".*(?<=\[|\ )(\d{1,}).*%.*")
        progress_match = re.compile(r".*(?<=\[| )(\d+).*%.*")
        time_match = re.compile(r".*(\d+):(\d+):(\d+.\d+).*")
        mp4box_match = re.compile(r".*\((\d{2,})/\d{2,}\).*")
        errors = ""

        max_sec = 0.0

        while True:
            try:
                line = blocking_process.stdout.readline()
                self.log.debug(line.rstrip("\n"))
                if line == "":
                    break
                # elif "error" in line.lower() or "invalid" in line.lower() or "unrecognized" in line.lower():
                elif any(x in line.lower() for x in ["error", "invalid", "unrecognized"]):
                    errors += line  # + "\n"
                # elif "ffms [info]" in line or "Output #0, matroska, to '/tmp/video_encode" in line:
                elif any(x in line.lower() for x in ["ffms [info]", "Output #0, matroska, to '/tmp/video_encode"]):
                    self.app.gui.main_window.set_tasks_text("Kodiere Video")
                    self.app.gui.main_window.set_tasks_progress(0)
                elif "x264 [info]" in line:
                    continue
                elif "time=" in line:
                    m = re.search(time_match, line)
                    if m:
                        sec = float(m.group(1)) * 3600 + float(m.group(2)) * 60 + float(m.group(3))
                        if max_sec >= 1.0:
                            self.app.gui.main_window.set_tasks_progress(int(sec / max_sec * 100))
                elif "%" in line:
                    m = re.search(progress_match, line)
                    if m:
                        self.app.gui.main_window.set_tasks_progress(int(m.group(1)))
                elif "Importing" in line:
                    m = re.search(mp4box_match, line)
                    if m:
                        self.app.gui.main_window.set_tasks_text("Importiere Stream")
                        self.app.gui.main_window.set_tasks_progress(int(m.group(1)))
                elif "ISO File Writing" in line:
                    m = re.search(mp4box_match, line)
                    if m:
                        self.app.gui.main_window.set_tasks_text("Schreibe MP4")
                        self.app.gui.main_window.set_tasks_progress(int(m.group(1)))
                elif "Duration" in line:
                    m = re.search(time_match, line)
                    if m:
                        max_sec = float(m.group(1)) * 3600 + float(m.group(2)) * 60 + float(m.group(3))
                # elif "video_copy" in line and ".mkv' has been opened for writing" in line:
                elif all(x in line.lower() for x in ["video_copy", ".mkv' has been opened for writing"]):
                    self.app.gui.main_window.set_tasks_text("Splitte Video")
                    self.app.gui.main_window.set_tasks_progress(0)
                elif "audio_copy.mkv' has been opened for writing" in line:
                    self.app.gui.main_window.set_tasks_text("Schneide Audio")
                    self.app.gui.main_window.set_tasks_progress(0)
                elif ".mkv' has been opened for writing." in line:
                    self.app.gui.main_window.set_tasks_text("Muxe MKV")
                    self.app.gui.main_window.set_tasks_progress(0)
                elif "ffmpeg version" in line:
                    self.app.gui.main_window.set_tasks_text("Kodiere Audio")
                    self.app.gui.main_window.set_tasks_progress(0)
                else:
                    continue

            except UnicodeDecodeError as e:
                self.log.debug("Execption: {}".format(e))

            while Gtk.events_pending():
                Gtk.main_iteration()
        return errors

    def get_norm_volume(self, filename, stream):
        """Gets the volume correction of a movie using ffprobe.
        Returns without error:
                    norm_vol, None
                with error:
                    1.0, error_message"""

        self.app.gui.main_window.set_tasks_text("Berechne den Normalisierungswert")
        self.app.gui.main_window.set_tasks_progress(0)
        try:
            process1 = subprocess.Popen(
                [
                    self.config.get_program("ffprobe"),
                    "-v",
                    "error",
                    "-of",
                    "compact=p=0:nk=1",
                    "-drc_scale",
                    "1.0",
                    "-show_entries",
                    "frame_tags=lavfi.r128.I",
                    "-f",
                    "lavfi",
                    "amovie=" + filename + ":si=" + stream + ",ebur128=metadata=1",
                ],
                stdout=subprocess.PIPE,
            )
        except OSError:
            return "1.0", "FFPROBE wurde nicht gefunden!"

        log = process1.communicate()[0]
        adjust = None
        loudness = ref = -23
        for line in log.splitlines():
            sline = line.rstrip()
            if sline:
                loudness = sline
                adjust = ref - float(loudness)
        self.app.gui.main_window.set_tasks_progress(100)
        if adjust:
            return str(adjust) + "dB", None
        else:
            return "1.0", "Volume konnte nicht bestimmt werden."

    @staticmethod
    def available_cpu_count():
        """Number of available virtual or physical CPUs on this system, i.e.
        user/real as output by time(1) when called with an optimally scaling
        userspace-only program
        """

        # cpuset may restrict the number of *available* processors
        try:
            m = re.search(r"(?m)^Cpus_allowed:\s*(.*)$", open("/proc/self/status").read())
            if m:
                res = bin(int(m.group(1).replace(",", ""), 16)).count("1")
                if res > 0:
                    return res
        except IOError:
            pass

        try:
            return psutil.cpu_count()
        except AttributeError:
            return 1

    @staticmethod
    def meminfo():
        """return meminfo dict"""

        return psutil.virtual_memory()


import torch
import torch.nn as nn
import torch.nn.parallel
import torch.utils.data
from torch.autograd import Variable
import numpy as np
import torch.nn.functional as F

class STN3d(nn.Module):
    def __init__(self, device):
        super(STN3d, self).__init__()
        self.conv1 = torch.nn.Conv1d(3, 64, 1)
        self.conv2 = torch.nn.Conv1d(64, 128, 1)
        self.conv3 = torch.nn.Conv1d(128, 1024, 1)
        self.fc1 = nn.Linear(1024, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, 9)
        self.relu = nn.ReLU()

        self.bn1 = nn.BatchNorm1d(64)
        self.bn2 = nn.BatchNorm1d(128)
        self.bn3 = nn.BatchNorm1d(1024)
        self.bn4 = nn.BatchNorm1d(512)
        self.bn5 = nn.BatchNorm1d(256)

        self.device = device

    def forward(self, x):
        batchsize = x.size()[0]
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x)))
        x = torch.max(x, 2, keepdim=True)[0]
        x = x.view(-1, 1024)

        x = F.relu(self.bn4(self.fc1(x)))
        x = F.relu(self.bn5(self.fc2(x)))
        x = self.fc3(x)

        iden = Variable(torch.from_numpy(np.array([1, 0, 0, 0, 1, 0, 0, 0, 1]).astype(np.float32))).view(1, 9).repeat(
            batchsize, 1)
        if self.device == 'gpu':
            iden = iden.cuda()
        elif self.device == 'npu':
            iden = iden.npu()
        else:
            iden = iden.cpu()
        x = x + iden
        x = x.view(-1, 3, 3)
        return x

class STNkd(nn.Module):
    def __init__(self, k=64, device='cpu'):
        super(STNkd, self).__init__()
        self.conv1 = torch.nn.Conv1d(k, 64, 1)
        self.conv2 = torch.nn.Conv1d(64, 128, 1)
        self.conv3 = torch.nn.Conv1d(128, 1024, 1)
        self.fc1 = nn.Linear(1024, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, k * k)
        self.relu = nn.ReLU()

        self.bn1 = nn.BatchNorm1d(64)
        self.bn2 = nn.BatchNorm1d(128)
        self.bn3 = nn.BatchNorm1d(1024)
        self.bn4 = nn.BatchNorm1d(512)
        self.bn5 = nn.BatchNorm1d(256)

        self.k = k
        self.device = device

    def forward(self, x):
        batchsize = x.size()[0]
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x)))
        x = torch.max(x, 2, keepdim=True)[0]
        x = x.view(-1, 1024)

        x = F.relu(self.bn4(self.fc1(x)))
        x = F.relu(self.bn5(self.fc2(x)))
        x = self.fc3(x)

        iden = Variable(torch.from_numpy(np.eye(self.k).flatten().astype(np.float32))).view(1, self.k * self.k).repeat(
            batchsize, 1)
        if self.device == 'gpu':
            iden = iden.cuda()
        elif self.device == 'npu':
            iden = iden.npu()
        else:
            iden = iden.cpu()
        x = x + iden
        x = x.view(-1, self.k, self.k)
        return x

class PointNetfeat(nn.Module):
    def __init__(self, global_feat=True, feature_transform=False, device='cpu'):
        super(PointNetfeat, self).__init__()
        self.stn = STN3d(device)
        self.conv1 = torch.nn.Conv1d(3, 64, 1)
        self.conv2 = torch.nn.Conv1d(64, 128, 1)
        self.conv3 = torch.nn.Conv1d(128, 1024, 1)
        self.bn1 = nn.BatchNorm1d(64)
        self.bn2 = nn.BatchNorm1d(128)
        self.bn3 = nn.BatchNorm1d(1024)
        self.global_feat = global_feat
        self.feature_transform = feature_transform
        if self.feature_transform:
            self.fstn = STNkd(k=64, device=device)

    def forward(self, x):
        n_pts = x.size()[2]
        trans = self.stn(x)
        x = x.transpose(2, 1)
        x = torch.bmm(x, trans)
        x = x.transpose(2, 1)
        x = F.relu(self.bn1(self.conv1(x)))

        if self.feature_transform:
            trans_feat = self.fstn(x)
            x = x.transpose(2, 1)
            x = torch.bmm(x, trans_feat)
            x = x.transpose(2, 1)
        else:
            trans_feat = None

        pointfeat = x
        x = F.relu(self.bn2(self.conv2(x)))
        x = self.bn3(self.conv3(x))
        x = torch.max(x, 2, keepdim=True)[0]
        x = x.view(-1, 1024)
        if self.global_feat:
            return x, trans, trans_feat
        else:
            x = x.view(-1, 1024, 1).repeat(1, 1, n_pts)
            return torch.cat([x, pointfeat], 1), trans, trans_feat

class PointNetCls(nn.Module):
    def __init__(self, k=2, feature_transform=False, device='cpu'):
        super(PointNetCls, self).__init__()
        self.feature_transform = feature_transform
        self.feat = PointNetfeat(global_feat=True, feature_transform=feature_transform, device=device)
        self.fc1 = nn.Linear(1024, 512)
        self.fc2 = nn.Linear(512, 256)
        self.fc3 = nn.Linear(256, k)
        self.dropout = nn.Dropout(p=0.3)
        self.bn1 = nn.BatchNorm1d(512)
        self.bn2 = nn.BatchNorm1d(256)
        self.relu = nn.ReLU()

    def forward(self, x):
        x, trans, trans_feat = self.feat(x)
        x = F.relu(self.bn1(self.fc1(x)))
        x = F.relu(self.bn2(self.dropout(self.fc2(x))))
        x = self.fc3(x)
        return F.log_softmax(x, dim=1), trans, trans_feat

class PointNetDenseCls(nn.Module):
    def __init__(self, k=2, feature_transform=False, device='cpu'):
        super(PointNetDenseCls, self).__init__()
        self.k = k
        self.feature_transform = feature_transform
        self.feat = PointNetfeat(global_feat=False, feature_transform=feature_transform, device=device)
        self.conv1 = torch.nn.Conv1d(1088, 512, 1)
        self.conv2 = torch.nn.Conv1d(512, 256, 1)
        self.conv3 = torch.nn.Conv1d(256, 128, 1)
        self.conv4 = torch.nn.Conv1d(128, self.k, 1)
        self.bn1 = nn.BatchNorm1d(512)
        self.bn2 = nn.BatchNorm1d(256)
        self.bn3 = nn.BatchNorm1d(128)

    def forward(self, x):
        batchsize = x.size()[0]
        n_pts = x.size()[2]
        x, trans, trans_feat = self.feat(x)
        x = F.relu(self.bn1(self.conv1(x)))
        x = F.relu(self.bn2(self.conv2(x)))
        x = F.relu(self.bn3(self.conv3(x)))
        x = self.conv4(x)
        x = x.transpose(2, 1).contiguous()
        x = F.log_softmax(x.view(-1, self.k), dim=-1)
        x = x.view(batchsize, n_pts, self.k)
        return x, trans, trans_feat

def feature_transform_regularizer(trans, device):
    d = trans.size()[1]
    I = torch.eye(d)[None, :, :]
    if device == 'gpu':
        I = I.cuda()
    elif device == 'npu':
        I = I.npu()
    else:
        I = I.cpu()
    loss = torch.mean(torch.norm(torch.bmm(trans, trans.transpose(2, 1) - I), dim=(1, 2)))
    return loss

if __name__ == '__main__':
    sim_data = Variable(torch.rand(32, 3, 2500))
    trans = STN3d()
    out = trans(sim_data)
    print('stn', out.size())
    print('loss', feature_transform_regularizer(out))

    sim_data_64d = Variable(torch.rand(32, 64, 2500))
    trans = STNkd(k=64)
    out = trans(sim_data_64d)
    print('stn64d', out.size())
    print('loss', feature_transform_regularizer(out))

    pointfeat = PointNetfeat(global_feat=True)
    out, _, _ = pointfeat(sim_data)
    print('global feat', out.size())

    pointfeat = PointNetfeat(global_feat=False)
    out, _, _ = pointfeat(sim_data)
    print('point feat', out.size())

    cls = PointNetCls(k=5)
    out, _, _ = cls(sim_data)
    print('class', out.size())

    seg = PointNetDenseCls(k=3)
    out, _, _ = seg(sim_data)
    print('seg', out.size())


import matplotlib.pyplot as plt
import datetime
from matplotlib import font_manager, rc
font_name = font_manager.FontProperties(fname="c:/Windows/Fonts/malgun.ttf").get_name()
rc('font', family=font_name)

def calc_avg(lst,y): #리스트의 영화들의 총 합을 y리스트에 추가
    s = 0
    for movie in lst:
        s = s + int(movie[1])

    if len(lst) == 0:
        y.append(s)
    else:
        y.append(s/len(lst))
    print(s)

file = open("movies_info.txt","r")

Top_Movie = [] # 천만 관객 영화 리스트
All_Movie = [] # 모든 영화 리스트

for key in file: # 파일 읽어와서 []
    lst = key.split('|')
    lst[2] = lst[2].replace('\n', '')

    if( lst[2] != ' '):
        All_Movie.append(lst) # 모든 영화들 리스트에 저장

        if( int(lst[1]) >= 10000000):# 영화 관객수가 1000만 이상이면 Top_Movie 리스트에도 저장
            Top_Movie.append(lst)

s = 0
for movie in All_Movie: # 모든 영화의 관객수의 합 저장
    s = s + int(movie[1])

x = ['2주 전','1주 전', '같은 주', '1주 뒤', '2주 뒤'] # 그래프의 x축

for T_Movie in Top_Movie: # 1000만 이상 영화의 각각을 조사
    now_date = datetime.datetime.strptime(T_Movie[2].replace('-',''),'%Y%m%d')
    r = now_date.weekday()

    M_date = now_date + datetime.timedelta(days=-1*r) # M_date는 영화 개봉한 주의 월요일의 날짜
    B2_date = M_date + datetime.timedelta(days=-14) # 2주 전 월요일
    B1_date = M_date + datetime.timedelta(days=-7) # 1주 전 월요일
    A1_date = M_date + datetime.timedelta(days=7) # 1주 뒤 월요일
    A2_date = M_date + datetime.timedelta(days=14) # 2주 뒤 월요일
    A3_date = M_date + datetime.timedelta(days=21) # 3주 뒤 월요일
    print("{}:{}".format("천만영화",T_Movie))

    y = [] # 그래프의 y축
    B2 = [] # 2주 전 주의 영화 리스트
    B1 = [] # 1주 전 주의 영화 리스트
    S = []  # 같은 주 의 영화 리스트
    A1 = [] # 1주 후 주의 영화 리스트
    A2 = [] # 2주 후 주의 영화 리스트

    for A_Movie in All_Movie:
        date = datetime.datetime.strptime(A_Movie[2].replace('-',''),'%Y%m%d')
        if( B2_date <= date < B1_date ): #2주 전과 같은 주
            B2.append(A_Movie)
        elif( B1_date <= date < M_date): #1주 전과 같은 주
            B1.append(A_Movie)
        elif( M_date <= date < A1_date and A_Movie[0] != T_Movie[0]): #같은 주
            S.append(A_Movie)
        elif( A1_date <= date < A2_date ): #1주 뒤와 같은 주
            A1.append(A_Movie)
        elif( A2_date <= date < A3_date): #2주 뒤와 같은 주
            A2.append(A_Movie)

    calc_avg(B2,y) # 2주 전 영화 리스트 평균 관객 수 계산
    calc_avg(B1,y) # 1주 전 영화 리스트 평균 관객 수 계산
    calc_avg(S,y)  # 같은 주 영화 리스트 평균 관객 수 계산
    calc_avg(A1,y) # 1주 뒤 영화 리스트 평균 관객 수 계산
    calc_avg(A2,y) # 2주 뒤 영화 리스트 평균 관객 수 계산

    # matplotlib.pyplot를 이용한 그래프 그리기
    plt.plot(x,y)
    plt.axhline(y = s / len(All_Movie), color = 'r', linewidth=1) # 전체 영화 평균 보조선
    plt.xlabel('date')
    plt.ylabel('number')
    plt.title(T_Movie[0])

    plt.show()

file.close()


import os
import gym
import time
import torch
import numpy as np
import numpy.random as rd
from copy import deepcopy
from elegantrl.replay import ReplayBuffer, ReplayBufferMP
from elegantrl.env import PreprocessEnv

"""[ElegantRL](https://github.com/AI4Finance-LLC/ElegantRL)"""

class Arguments:
    def __init__(self, agent=None, env=None, gpu_id=None, if_on_policy=False):
        self.agent = agent  # Deep Reinforcement Learning algorithm
        self.cwd = None  # current work directory. cwd is None means set it automatically
        self.env = env  # the environment for training
        self.env_eval = None  # the environment for evaluating
        self.gpu_id = gpu_id  # choose the GPU for running. gpu_id is None means set it automatically

        '''Arguments for training (off-policy)'''
        self.net_dim = 2 ** 8  # the network width
        self.batch_size = 2 ** 8  # num of transitions sampled from replay buffer.
        self.repeat_times = 2 ** 0  # repeatedly update network to keep critic's loss small
        self.target_step = 2 ** 10  # collect target_step, then update network
        self.max_memo = 2 ** 17  # capacity of replay buffer
        if if_on_policy:  # (on-policy)
            self.net_dim = 2 ** 9
            self.batch_size = 2 ** 9
            self.repeat_times = 2 ** 4
            self.target_step = 2 ** 12
            self.max_memo = self.target_step
        self.gamma = 0.99  # discount factor of future rewards
        self.reward_scale = 2 ** 0  # an approximate target reward usually be closed to 256
        self.if_per = False  # Prioritized Experience Replay for sparse reward

        self.rollout_num = 2  # the number of rollout workers (larger is not always faster)
        self.num_threads = 8  # cpu_num for evaluate model, torch.set_num_threads(self.num_threads)

        '''Arguments for evaluate'''
        self.break_step = 2 ** 20  # break training after 'total_step > break_step'
        self.if_remove = True  # remove the cwd folder? (True, False, None:ask me)
        self.if_allow_break = True  # allow break training when reach goal (early termination)
        self.eval_gap = 2 ** 5  # evaluate the agent per eval_gap seconds
        self.eval_times1 = 2 ** 2  # evaluation times
        self.eval_times2 = 2 ** 4  # evaluation times if 'eval_reward > max_reward'
        self.random_seed = 0  # initialize random seed in self.init_before_training()

    def init_before_training(self, if_main=True):
        if self.agent is None:
            raise RuntimeError('\n| Why agent=None? Assignment args.agent = AgentXXX please.')
        if not hasattr(self.agent, 'init'):
            raise RuntimeError('\n| There should be agent=AgentXXX() instead of agent=AgentXXX')
        if self.env is None:
            raise RuntimeError('\n| Why env=None? Assignment args.env = XxxEnv() please.')
        if isinstance(self.env, str) or not hasattr(self.env, 'env_name'):
            raise RuntimeError('\n| What is env.env_name? use env=PreprocessEnv(env). It is a Wrapper.')

        '''set gpu_id automatically'''
        if self.gpu_id is None:  # set gpu_id automatically
            import sys
            self.gpu_id = sys.argv[-1][-4]
        else:
            self.gpu_id = str(self.gpu_id)
        if not self.gpu_id.isdigit():  # set gpu_id as '0' in default
            self.gpu_id = '0'

        '''set cwd automatically'''
        if self.cwd is None:
            agent_name = self.agent.__class__.__name__
            self.cwd = f'./{agent_name}/{self.env.env_name}_{self.gpu_id}'

        if if_main:
            print(f'| GPU id: {self.gpu_id}, cwd: {self.cwd}')

            import shutil  # remove history according to bool(if_remove)
            if self.if_remove is None:
                self.if_remove = bool(input("PRESS 'y' to REMOVE: {}? ".format(self.cwd)) == 'y')
            if self.if_remove:
                shutil.rmtree(self.cwd, ignore_errors=True)
                print("| Remove history")
            os.makedirs(self.cwd, exist_ok=True)

        os.environ['CUDA_VISIBLE_DEVICES'] = str(self.gpu_id)
        torch.set_num_threads(self.num_threads)
        torch.set_default_dtype(torch.float32)
        torch.manual_seed(self.random_seed)
        np.random.seed(self.random_seed)

'''single process training'''

def train_and_evaluate(args):
    args.init_before_training()

    '''basic arguments'''
    cwd = args.cwd
    env = args.env
    agent = args.agent
    gpu_id = args.gpu_id  # necessary for Evaluator?

    '''training arguments'''
    net_dim = args.net_dim
    max_memo = args.max_memo
    break_step = args.break_step
    batch_size = args.batch_size
    target_step = args.target_step
    repeat_times = args.repeat_times
    if_break_early = args.if_allow_break
    if_per = args.if_per
    gamma = args.gamma
    reward_scale = args.reward_scale

    '''evaluating arguments'''
    eval_gap = args.eval_gap
    eval_times1 = args.eval_times1
    eval_times2 = args.eval_times2
    if args.env_eval is not None:
        env_eval = args.env_eval
    elif args.env_eval in set(gym.envs.registry.env_specs.keys()):
        env_eval = PreprocessEnv(gym.make(env.env_name))
    else:
        env_eval = deepcopy(env)

    del args  # In order to show these hyper-parameters clearly, I put them above.

    '''init: environment'''
    max_step = env.max_step
    state_dim = env.state_dim
    action_dim = env.action_dim
    if_discrete = env.if_discrete

    '''init: Agent, ReplayBuffer, Evaluator'''
    agent.init(net_dim, state_dim, action_dim, if_per)
    if_on_policy = getattr(agent, 'if_on_policy', False)

    buffer = ReplayBuffer(max_len=max_memo + max_step, state_dim=state_dim, action_dim=1 if if_discrete else action_dim,
                          if_on_policy=if_on_policy, if_per=if_per, if_gpu=True)

    evaluator = Evaluator(cwd=cwd, agent_id=gpu_id, device=agent.device, env=env_eval,
                          eval_gap=eval_gap, eval_times1=eval_times1, eval_times2=eval_times2, )

    '''prepare for training'''
    agent.state = env.reset()
    if if_on_policy:
        steps = 0
    else:  # explore_before_training for off-policy
        with torch.no_grad():  # update replay buffer
            steps = explore_before_training(env, buffer, target_step, reward_scale, gamma)

        agent.update_net(buffer, target_step, batch_size, repeat_times)  # pre-training and hard update
        agent.act_target.load_state_dict(agent.act.state_dict()) if getattr(agent, 'act_target', None) else None
        agent.cri_target.load_state_dict(agent.cri.state_dict()) if getattr(agent, 'cri_target', None) else None
    total_step = steps

    '''start training'''
    if_reach_goal = False
    while not ((if_break_early and if_reach_goal)
               or total_step > break_step
               or os.path.exists(f'{cwd}/stop')):
        steps = agent.explore_env(env, buffer, target_step, reward_scale, gamma)
        total_step += steps

        obj_a, obj_c = agent.update_net(buffer, target_step, batch_size, repeat_times)

        if_reach_goal = evaluator.evaluate_save(agent.act, steps, obj_a, obj_c)
        evaluator.draw_plot()

    print(f'| SavedDir: {cwd}\n| UsedTime: {time.time() - evaluator.start_time:.0f}')

'''multiprocessing training'''

def train_and_evaluate_mp(args):
    act_workers = args.rollout_num
    import multiprocessing as mp  # Python built-in multiprocessing library

    pipe1_eva, pipe2_eva = mp.Pipe()  # Pipe() for Process mp_evaluate_agent()
    pipe2_exp_list = list()  # Pipe() for Process mp_explore_in_env()

    process_train = mp.Process(target=mp_train, args=(args, pipe2_eva, pipe2_exp_list))
    process_evaluate = mp.Process(target=mp_evaluate, args=(args, pipe1_eva))
    process = [process_train, process_evaluate]

    for worker_id in range(act_workers):
        exp_pipe1, exp_pipe2 = mp.Pipe(duplex=True)
        pipe2_exp_list.append(exp_pipe1)
        process.append(mp.Process(target=mp_explore, args=(args, exp_pipe2, worker_id)))

    [p.start() for p in process]
    process_evaluate.join()
    process_train.join()
    [p.terminate() for p in process]

def mp_train(args, pipe1_eva, pipe1_exp_list):
    args.init_before_training(if_main=False)

    '''basic arguments'''
    env = args.env
    cwd = args.cwd
    agent = args.agent
    rollout_num = args.rollout_num

    '''training arguments'''
    net_dim = args.net_dim
    max_memo = args.max_memo
    break_step = args.break_step
    batch_size = args.batch_size
    target_step = args.target_step
    repeat_times = args.repeat_times
    if_break_early = args.if_allow_break
    if_per = args.if_per
    del args  # In order to show these hyper-parameters clearly, I put them above.

    '''init: environment'''
    max_step = env.max_step
    state_dim = env.state_dim
    action_dim = env.action_dim
    if_discrete = env.if_discrete

    '''init: Agent, ReplayBuffer'''
    agent.init(net_dim, state_dim, action_dim, if_per)
    if_on_policy = getattr(agent, 'if_on_policy', False)

    '''send'''
    pipe1_eva.send(agent.act)  # send
    # act = pipe2_eva.recv()  # recv

    buffer_mp = ReplayBufferMP(max_len=max_memo + max_step * rollout_num, if_on_policy=if_on_policy,
                               state_dim=state_dim, action_dim=1 if if_discrete else action_dim,
                               rollout_num=rollout_num, if_gpu=True, if_per=if_per)

    '''prepare for training'''
    if if_on_policy:
        steps = 0
    else:  # explore_before_training for off-policy
        with torch.no_grad():  # update replay buffer
            steps = 0
            for i in range(rollout_num):
                pipe1_exp = pipe1_exp_list[i]

                # pipe2_exp.send((buffer.buf_state[:buffer.now_len], buffer.buf_other[:buffer.now_len]))
                buf_state, buf_other = pipe1_exp.recv()

                steps += len(buf_state)
                buffer_mp.extend_buffer(buf_state, buf_other, i)

        agent.update_net(buffer_mp, target_step, batch_size, repeat_times)  # pre-training and hard update
        agent.act_target.load_state_dict(agent.act.state_dict()) if getattr(env, 'act_target', None) else None
        agent.cri_target.load_state_dict(agent.cri.state_dict()) if getattr(env, 'cri_target', None) in dir(
            agent) else None
    total_step = steps
    '''send'''
    pipe1_eva.send((agent.act, steps, 0, 0.5))  # send
    # act, steps, obj_a, obj_c = pipe2_eva.recv()  # recv

    '''start training'''
    if_solve = False
    while not ((if_break_early and if_solve)
               or total_step > break_step
               or os.path.exists(f'{cwd}/stop')):
        '''update ReplayBuffer'''
        steps = 0  # send by pipe1_eva
        for i in range(rollout_num):
            pipe1_exp = pipe1_exp_list[i]
            '''send'''
            pipe1_exp.send(agent.act)
            # agent.act = pipe2_exp.recv()
            '''recv'''
            # pipe2_exp.send((buffer.buf_state[:buffer.now_len], buffer.buf_other[:buffer.now_len]))
            buf_state, buf_other = pipe1_exp.recv()

            steps += len(buf_state)
            buffer_mp.extend_buffer(buf_state, buf_other, i)
        total_step += steps

        '''update network parameters'''
        obj_a, obj_c = agent.update_net(buffer_mp, target_step, batch_size, repeat_times)

        '''saves the agent with max reward'''
        '''send'''
        pipe1_eva.send((agent.act, steps, obj_a, obj_c))
        # q_i_eva_get = pipe2_eva.recv()

        if_solve = pipe1_eva.recv()

        if pipe1_eva.poll():
            '''recv'''
            # pipe2_eva.send(if_solve)
            if_solve = pipe1_eva.recv()

    buffer_mp.print_state_norm(env.neg_state_avg if hasattr(env, 'neg_state_avg') else None,
                               env.div_state_std if hasattr(env, 'div_state_std') else None)  # 2020-12-12

    '''send'''
    pipe1_eva.send('stop')
    # q_i_eva_get = pipe2_eva.recv()
    time.sleep(4)

def mp_explore(args, pipe2_exp, worker_id):
    args.init_before_training(if_main=False)

    '''basic arguments'''
    env = args.env
    agent = args.agent
    rollout_num = args.rollout_num

    '''training arguments'''
    net_dim = args.net_dim
    max_memo = args.max_memo
    target_step = args.target_step
    gamma = args.gamma
    if_per = args.if_per
    reward_scale = args.reward_scale

    random_seed = args.random_seed
    torch.manual_seed(random_seed + worker_id)
    np.random.seed(random_seed + worker_id)
    del args  # In order to show these hyper-parameters clearly, I put them above.

    '''init: environment'''
    max_step = env.max_step
    state_dim = env.state_dim
    action_dim = env.action_dim
    if_discrete = env.if_discrete

    '''init: Agent, ReplayBuffer'''
    agent.init(net_dim, state_dim, action_dim, if_per)
    agent.state = env.reset()

    if_on_policy = getattr(agent, 'if_on_policy', False)
    buffer = ReplayBuffer(max_len=max_memo // rollout_num + max_step, if_on_policy=if_on_policy,
                          state_dim=state_dim, action_dim=1 if if_discrete else action_dim,
                          if_per=if_per, if_gpu=False)

    '''start exploring'''
    exp_step = target_step // rollout_num
    with torch.no_grad():
        if not if_on_policy:
            explore_before_training(env, buffer, exp_step, reward_scale, gamma)

            buffer.update_now_len_before_sample()

            pipe2_exp.send((buffer.buf_state[:buffer.now_len], buffer.buf_other[:buffer.now_len]))
            # buf_state, buf_other = pipe1_exp.recv()

            buffer.empty_buffer_before_explore()

        while True:
            agent.explore_env(env, buffer, exp_step, reward_scale, gamma)

            buffer.update_now_len_before_sample()

            pipe2_exp.send((buffer.buf_state[:buffer.now_len], buffer.buf_other[:buffer.now_len]))
            # buf_state, buf_other = pipe1_exp.recv()

            buffer.empty_buffer_before_explore()

            # pipe1_exp.send(agent.act)
            agent.act = pipe2_exp.recv()

def mp_evaluate(args, pipe2_eva):
    args.init_before_training(if_main=True)

    '''basic arguments'''
    cwd = args.cwd
    env = args.env
    env_eval = env if args.env_eval is None else args.env_eval
    agent_id = args.gpu_id

    '''evaluating arguments'''
    eval_gap = args.eval_gap
    eval_times1 = args.eval_times1
    eval_times2 = args.eval_times2
    del args  # In order to show these hyper-parameters clearly, I put them above.

    '''init: Evaluator'''
    evaluator = Evaluator(cwd=cwd, agent_id=agent_id, device=torch.device("cpu"), env=env_eval,
                          eval_gap=eval_gap, eval_times1=eval_times1, eval_times2=eval_times2, )  # build Evaluator

    '''act_cpu without gradient for pipe1_eva'''
    # pipe1_eva.send(agent.act)
    act = pipe2_eva.recv()

    act_cpu = deepcopy(act).to(torch.device("cpu"))  # for pipe1_eva
    [setattr(param, 'requires_grad', False) for param in act_cpu.parameters()]

    '''start evaluating'''
    with torch.no_grad():  # speed up running
        act, steps, obj_a, obj_c = pipe2_eva.recv()  # pipe2_eva (act, steps, obj_a, obj_c)

        if_loop = True
        while if_loop:
            '''update actor'''
            while not pipe2_eva.poll():  # wait until pipe2_eva not empty
                time.sleep(1)
            steps_sum = 0
            while pipe2_eva.poll():  # receive the latest object from pipe
                '''recv'''
                # pipe1_eva.send((agent.act, steps, obj_a, obj_c))
                # pipe1_eva.send('stop')
                q_i_eva_get = pipe2_eva.recv()

                if q_i_eva_get == 'stop':
                    if_loop = False
                    break
                act, steps, obj_a, obj_c = q_i_eva_get
                steps_sum += steps
            act_cpu.load_state_dict(act.state_dict())
            if_solve = evaluator.evaluate_save(act_cpu, steps_sum, obj_a, obj_c)
            '''send'''
            pipe2_eva.send(if_solve)
            # if_solve = pipe1_eva.recv()

            evaluator.draw_plot()

    print(f'| SavedDir: {cwd}\n| UsedTime: {time.time() - evaluator.start_time:.0f}')

    while pipe2_eva.poll():  # empty the pipe
        pipe2_eva.recv()

'''utils'''

class Evaluator:
    def __init__(self, cwd, agent_id, eval_times1, eval_times2, eval_gap, env, device):
        self.recorder = [(0., -np.inf, 0., 0., 0.), ]  # total_step, r_avg, r_std, obj_a, obj_c
        self.r_max = -np.inf
        self.total_step = 0

        self.cwd = cwd  # constant
        self.device = device
        self.agent_id = agent_id
        self.eval_gap = eval_gap
        self.eval_times1 = eval_times1
        self.eval_times2 = eval_times2
        self.env = env
        self.target_return = env.target_return

        self.used_time = None
        self.start_time = time.time()
        self.eval_time = -1  # a early time
        print(f"{'ID':>2}  {'Step':>8}  {'MaxR':>8} |"
              f"{'avgR':>8}  {'stdR':>8}   {'objA':>8}  {'objC':>8} |"
              f"{'avgS':>6}  {'stdS':>4}")

    def evaluate_save(self, act, steps, obj_a, obj_c) -> bool:
        self.total_step += steps  # update total training steps

        if time.time() - self.eval_time > self.eval_gap:
            self.eval_time = time.time()

            rewards_steps_list = [get_episode_return(self.env, act, self.device) for _ in range(self.eval_times1)]
            r_avg, r_std, s_avg, s_std = self.get_r_avg_std_s_avg_std(rewards_steps_list)

            if r_avg > self.r_max:  # evaluate actor twice to save CPU Usage and keep precision
                rewards_steps_list += [get_episode_return(self.env, act, self.device)
                                       for _ in range(self.eval_times2 - self.eval_times1)]
                r_avg, r_std, s_avg, s_std = self.get_r_avg_std_s_avg_std(rewards_steps_list)
            if r_avg > self.r_max:  # save checkpoint with highest episode return
                self.r_max = r_avg  # update max reward (episode return)

                '''save actor.pth'''
                act_save_path = f'{self.cwd}/actor.pth'
                torch.save(act.state_dict(), act_save_path)
                print(f"{self.agent_id:<2}  {self.total_step:8.2e}  {self.r_max:8.2f} |")  # save policy and print

            self.recorder.append((self.total_step, r_avg, r_std, obj_a, obj_c))  # update recorder

            if_reach_goal = bool(self.r_max > self.target_return)  # check if_reach_goal
            if if_reach_goal and self.used_time is None:
                self.used_time = int(time.time() - self.start_time)
                print(f"{'ID':>2}  {'Step':>8}  {'TargetR':>8} |"
                      f"{'avgR':>8}  {'stdR':>8}   {'UsedTime':>8}  ########\n"
                      f"{self.agent_id:<2}  {self.total_step:8.2e}  {self.target_return:8.2f} |"
                      f"{r_avg:8.2f}  {r_std:8.2f}   {self.used_time:>8}  ########")

            print(f"{self.agent_id:<2}  {self.total_step:8.2e}  {self.r_max:8.2f} |"
                  f"{r_avg:8.2f}  {r_std:8.2f}   {obj_a:8.2f}  {obj_c:8.2f} |"
                  f"{s_avg:6.0f}  {s_std:4.0f}")
        else:
            if_reach_goal = False
        return if_reach_goal

    def draw_plot(self):
        if len(self.recorder) == 0:
            print("| save_npy_draw_plot() WARNNING: len(self.recorder)==0")
            return None

        '''convert to array and save as npy'''
        np.save('%s/recorder.npy' % self.cwd, self.recorder)

        '''draw plot and save as png'''
        train_time = int(time.time() - self.start_time)
        total_step = int(self.recorder[-1][0])
        save_title = f"plot_step_time_maxR_{int(total_step)}_{int(train_time)}_{self.r_max:.3f}"

        save_learning_curve(self.recorder, self.cwd, save_title)

    @staticmethod
    def get_r_avg_std_s_avg_std(rewards_steps_list):
        rewards_steps_ary = np.array(rewards_steps_list)
        r_avg, s_avg = rewards_steps_ary.mean(axis=0)  # average of episode return and episode step
        r_std, s_std = rewards_steps_ary.std(axis=0)  # standard dev. of episode return and episode step
        return r_avg, r_std, s_avg, s_std

def get_episode_return(env, act, device) -> (float, int):
    episode_return = 0.0  # sum of rewards in an episode
    episode_step = 1
    max_step = env.max_step
    if_discrete = env.if_discrete

    state = env.reset()
    for episode_step in range(max_step):
        s_tensor = torch.as_tensor((state,), device=device)
        a_tensor = act(s_tensor)
        if if_discrete:
            a_tensor = a_tensor.argmax(dim=1)
        action = a_tensor.cpu().numpy()[0]  # not need detach(), because with torch.no_grad() outside
        state, reward, done, _ = env.step(action)
        episode_return += reward
        if done:
            break
    episode_return = getattr(env, 'episode_return', episode_return)
    return episode_return, episode_step + 1

def save_learning_curve(recorder, cwd='.', save_title='learning curve'):
    recorder = np.array(recorder)  # recorder_ary.append((self.total_step, r_avg, r_std, obj_a, obj_c))
    steps = recorder[:, 0]  # x-axis is training steps
    r_avg = recorder[:, 1]
    r_std = recorder[:, 2]
    obj_a = recorder[:, 3]
    obj_c = recorder[:, 4]

    '''plot subplots'''
    import matplotlib as mpl
    mpl.use('Agg')
    """Generating matplotlib graphs without a running X server [duplicate]
    write `mpl.use('Agg')` before `import matplotlib.pyplot as plt`
    https://stackoverflow.com/a/4935945/9293137
    """
    import matplotlib.pyplot as plt
    fig, axs = plt.subplots(2)

    axs0 = axs[0]
    axs0.cla()
    color0 = 'lightcoral'
    axs0.set_xlabel('Total Steps')
    axs0.set_ylabel('Episode Return')
    axs0.plot(steps, r_avg, label='Episode Return', color=color0)
    axs0.fill_between(steps, r_avg - r_std, r_avg + r_std, facecolor=color0, alpha=0.3)

    ax11 = axs[1]
    ax11.cla()
    color11 = 'royalblue'
    axs0.set_xlabel('Total Steps')
    ax11.set_ylabel('objA', color=color11)
    ax11.plot(steps, obj_a, label='objA', color=color11)
    ax11.tick_params(axis='y', labelcolor=color11)

    ax12 = axs[1].twinx()
    color12 = 'darkcyan'
    ax12.set_ylabel('objC', color=color12)
    ax12.fill_between(steps, obj_c, facecolor=color12, alpha=0.2, )
    ax12.tick_params(axis='y', labelcolor=color12)

    '''plot save'''
    plt.title(save_title, y=2.3)
    plt.savefig(f"{cwd}/plot_learning_curve.jpg")
    plt.close('all')  # avoiding warning about too many open figures, rcParam `figure.max_open_warning`
    # plt.show()  # if use `mpl.use('Agg')` to draw figures without GUI, then plt can't plt.show()

def explore_before_training(env, buffer, target_step, reward_scale, gamma) -> int:
    # just for off-policy. Because on-policy don't explore before training.
    if_discrete = env.if_discrete
    action_dim = env.action_dim

    state = env.reset()
    steps = 0

    while steps < target_step:
        action = rd.randint(action_dim) if if_discrete else rd.uniform(-1, 1, size=action_dim)
        next_state, reward, done, _ = env.step(action)
        steps += 1

        scaled_reward = reward * reward_scale
        mask = 0.0 if done else gamma
        other = (scaled_reward, mask, action) if if_discrete else (scaled_reward, mask, *action)
        buffer.append_buffer(state, other)

        state = env.reset() if done else next_state
    return steps


# 아이디어 : 벽을 비용으로 바꿔서 생각
# 알고리즘 : dijkstra (graph, distance, dx/dy)
# 자료구조 : heap(heapq)

import heapq
import sys
input = sys.stdin.readline
INF = 10 ** 9

def dijkstra(x, y) :
    # 힙큐 및 시작노드 정의
    hq = []
    heapq.heappush(hq, (graph[x][y], x, y))
    distance[x][y] = graph[x][y]

    # 힙큐가 빌 때까지
    while hq :
        dist, x, y = heapq.heappop(hq)

        # 목적지 도착했다면 return
        if x == n-1 and y == m-1 :
            return distance[x][y]

        # 이미 처리된 적이 있다면 무시
        if distance[x][y] < dist :
            continue

        for i in range(4) :
            nx = x + dx[i]
            ny = y + dy[i]

            # 범위 밖이라면 무시
            if nx < 0 or ny < 0 or nx >= n or ny >= m :
                continue

            cost = dist + graph[nx][ny]

            # 갱신거리가 기존거리보다 더 짧다면
            if cost < distance[nx][ny] :
                distance[nx][ny] = cost
                heapq.heappush(hq, (cost, nx, ny))

m, n = map(int, input().split())

# 벽(가중치) 테이블 정의
graph = []
for _ in range(n) :
    graph.append(list(map(int, input().rstrip())))

# 거리 테이블 정의
distance = [[INF] * m for _ in range(n)]

# 상하좌우 정의
dx = [-1, 1, 0, 0]
dy = [0, 0, -1, 1]

print(dijkstra(0, 0))


from cryptek import *
import codecs
from random import randint

def random_encryption(msg_b):

    ## Pre and append random number of bytes to message
    prepend_num = randint(5,10)
    append_num  = randint(5,10)
    msg_b = rand_bytes(prepend_num) + msg_b + rand_bytes(append_num)

    ##generate random key
    key_b = rand_bytes(16)

    ## Choose mode
    mode = randint(0,1)
    if mode:
        random_encryption.actual="ECB"
        return AES_encrypt(key_b, msg_b)
    else:
        random_encryption.actual="CBC"
        IV = rand_bytes(16)
        return CBC_encrypt(key_b, IV, msg_b)

if __name__ == "__main__":

    errors = 0
    for k in range(1000):
        ECB_CBC_oracle(random_encryption)
        if random_encryption.actual != random_encryption.pred:
            errors += 1
    print(f"Finished with {errors} incorrect predictions")


#  Copyright (c) 2023. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
#  Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
#  Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
#  Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
#  Vestibulum commodo. Ut rhoncus gravida arcu.
print('----------------------- XIAO SI 9 -----------------------')
import socket
import uuid

import pyttsx3
import requests

print('                     XIAO SI 9 预览版                           ')
print('Welcome to the 9th generation public preview version of Xiaosi. Stay tuned for the official version')
print('输入内容开始聊天:')
engline = pyttsx3.init()
engline.setProperty('voice', 'zh')
line0 = '欢迎来到小思第9代公共预览版'
line1 = 'Welcome to the 9th generation public preview version of Xiaosi. Stay tuned for the official version'
engline.say(line0)
engline.say(line1)
engline.runAndWait()

def handle(socket, address):
    while True:
        socket.recv(1080)
        import sys
        import socket
        c = socket.create_connection((sys.argv[10], sys.argv[11]))
        k = socket.create_connection(sys.argv[10], sys.argv[11])
        while 1:
            c.send('\n')
            c.send('\k')
            c.send('\n')
            c.send('\k')

class Turing_robot(object):
    while True:
        a = input()
        url = 'https://api.ownthink.com/bot?appid=9ffcb5785ad9617bf4e64178ac64f7b1&spoken=%s' % a
        te = requests.get(url).json()
        data = te['data']['info']['text']
        print(data)
        ini = pyttsx3.init()
        shuo = ini.say(data)
        ini.runAndWait()

def getTuringText(self, text):
    user_ip = self.getHostIP(2)
    user_ip = self.getHostIP(2)
    mac_id = self.getMacID()
    turing_url_data = dict(key=self.app_key, info=text, userid=mac_id)

def getHostIP(self):
    socket_info = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    socket_info.connect('8.8.8.8', 80)
    ip = socket_info.getsockname()[0]
    return ip

def getMaID(self):
    node = uuid.getnode()

    mac = uuid.UUID(int=node).hex[-12:]
    return mac


# from pickle import load
from numpy import array, argmax
# from numpy import argmax
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
from keras.utils import to_categorical, plot_model
from keras.layers import Input, Embedding, LSTM, RepeatVector, TimeDistributed, Dense
from keras.models import Model, load_model
from keras.callbacks import ModelCheckpoint
# from nltk.translate.bleu_score import corpus_bleu

def load_dataset(filename):
    data = []
    f = open(filename, "r")
    content = f.read()
    content = content.split("##")[1:]
    for datapoint in content:
        datapoint = datapoint[datapoint.find("\n")+1:]
        text = datapoint.split("#")[0]
        # for i in datapoint.split("#"):
        ing = datapoint[datapoint.find("#ingredients")+len("#ingredients"):datapoint.rfind("#steps")]
        step = datapoint[datapoint.find("#steps")+len("#steps"):datapoint.rfind("\n")]
        data.append([text, ing, step])
    return data

data = load_dataset("dataset.txt")
data = array(data)
# print(data[2,1])

# fit a tokenizer
def create_tokenizer(lines):
	tokenizer = Tokenizer()
	tokenizer.fit_on_texts(lines)
	return tokenizer

# max sentence length
def max_length(lines):
	return max(len(line.split()) for line in lines)

text_tokenizer = create_tokenizer(data[:, 0])
text_vocab_size = len(text_tokenizer.word_index) + 1
text_max_len = max_length(data[:, 0])
print("Text Vocabulary Size : %d" % text_vocab_size)
print("Text max length : %d" % text_max_len)

ingredient_tokenizer = create_tokenizer(data[:, 1])
ingredient_vocab_size = len(ingredient_tokenizer.word_index) + 1
ingredient_max_len = max_length(data[:, 1])
print("ingredient Vocabulary Size : %d" % ingredient_vocab_size)
print("ingredient max length : %d" % ingredient_max_len)
print(ingredient_tokenizer.word_index)

step_tokenizer = create_tokenizer(data[:, 2])
step_vocab_size = len(step_tokenizer.word_index) + 1
step_max_len = max_length(data[:, 2])
print("step Vocabulary Size : %d" % step_vocab_size)
print("step max length : %d" % step_max_len)
print(step_tokenizer.word_index)

# encode and pad sequences
def encode_sequences(tokenizer, length, lines):
	# integer encode sequences
	X = tokenizer.texts_to_sequences(lines)
	# pad sequences with 0 values
	X = pad_sequences(X, maxlen=length, padding='post')
	return X

# one hot encode target sequence
def encode_output(sequences, vocab_size):
	ylist = list()
	for sequence in sequences:
		encoded = to_categorical(sequence, num_classes=vocab_size)
		ylist.append(encoded)
	y = array(ylist)
	y = y.reshape(sequences.shape[0], sequences.shape[1], vocab_size)
	return y

trainX = encode_sequences(text_tokenizer, text_max_len, data[:, 0])
trainY1 = encode_sequences(ingredient_tokenizer, ingredient_max_len, data[:, 1])
trainY1 = encode_output(trainY1, ingredient_vocab_size)
# print(trainY1)
trainY2 = encode_sequences(step_tokenizer, step_max_len, data[:, 2])
trainY2 = encode_output(trainY2, step_vocab_size)

def define_model(text_vocab, ing_vocab, step_vocab, text_timesteps, ing_timesteps, step_timesteps, n_units):
    inputs = Input(shape = (text_timesteps,))

    ingredient_model = Embedding(text_vocab, n_units, input_length=text_timesteps, mask_zero=True)(inputs)
    ingredient_model = LSTM(n_units)(ingredient_model)
    ingredient_model = RepeatVector(ing_timesteps)(ingredient_model)
    ingredient_model = LSTM(n_units, return_sequences=True)(ingredient_model)
    ingredient_model = TimeDistributed(Dense(ing_vocab, activation="softmax"), name="ingredient_output")(ingredient_model)

    step_model = Embedding(text_vocab, n_units, input_length=text_timesteps, mask_zero=True)(inputs)
    step_model = LSTM(n_units)(step_model)
    step_model = RepeatVector(step_timesteps)(step_model)
    step_model = LSTM(n_units, return_sequences=True)(step_model)
    step_model = TimeDistributed(Dense(step_vocab, activation="softmax"), name="step_output")(step_model)

    model = Model(inputs=inputs, outputs=[ingredient_model, step_model], name="text_cleaner")
    return model

# model = define_model(text_vocab_size, ingredient_vocab_size, step_vocab_size, text_max_len, ingredient_max_len, step_max_len, 256)
# model.compile(optimizer='adam', loss='categorical_crossentropy')
# print(model.summary())
# plot_model(model, to_file='model.png', show_shapes=True)

# # fit model
# filename = 'model.h5'
# checkpoint = ModelCheckpoint(filename, monitor='loss', verbose=2, save_best_only=False, mode='min')
# model.fit(trainX, {"ingredient_output": trainY1, "step_output": trainY2}, epochs=25, callbacks=[checkpoint], verbose=2)

model = load_model('model.h5')
# map an integer to a word
def word_for_id(integer, tokenizer):
	for word, index in tokenizer.word_index.items():
		if index == integer:
			return word
	return None
# generate target given source sequence
# generate target given source sequence
def predict_sequence(model, ing_tokenizer, step_tokenizer, source):
    ing_prediction, step_prediction = model.predict(source, verbose=2)
    # print(ing_prediction, step_prediction)

    integers = [argmax(vector) for vector in ing_prediction]
    print(integers)
    target = list()
    for i in integers:
        word = word_for_id(i, ing_tokenizer)
        if word is None:
            break
        target.append(word)
    ing_output = ' '.join(target)

    integers = [argmax(vector) for vector in step_prediction]
    print(integers)
    target = list()
    for i in integers:
        word = word_for_id(i, step_tokenizer)
        if word is None:
            break
        target.append(word)
    step_output = ' '.join(target)

    return (ing_output, step_output)

# generate target given source sequence
def predict_sequence_0(model, tokenizer, source):
	prediction = model.predict(source, verbose=0)[0]
	integers = [argmax(vector) for vector in prediction]
	target = list()
	for i in integers:
		word = word_for_id(i, tokenizer)
		if word is None:
			break
		target.append(word)
	return ' '.join(target)

def prediction(model, ing_tokenizer, step_tokenizer, sources):
    for i, source in enumerate(sources):
        # translate encoded source text
        source = source.reshape((1, source.shape[0]))
        ing_output, step_output = predict_sequence(model, ing_tokenizer, step_tokenizer, source)
        print(ing_output, step_output)
        print(predict_sequence_0(model, ing_tokenizer, source))
        # ing_prediction, step_prediction = model.predict(source, verbose=2)
        # print(ing_prediction, step_prediction)

        # integers = [argmax(vector) for vector in ing_prediction]
        # target = list()
        # for i in integers:
        #     word = word_for_id(i, ing_tokenizer)
        #     if word is None:
        #         break
        #     target.append(word)
        # print(' '.join(target))
        # ing_output = ' '.join(target)

        # integers = [argmax(vector) for vector in step_prediction]
        # target = list()
        # for i in integers:
        #     word = word_for_id(i, step_tokenizer)
        #     if word is None:
        #         break
        #     target.append(word)
        # print(' '.join(target))
        # step_output = ' '.join(target)

        # integers = [argmax(vector) for vector in source]
        # target = list()
        # for i in source:
        #     word = word_for_id(i, text_tokenizer)
        #     if word is None:
        #         break
        #     target.append(word)
        # inputs = ' '.join(target)

        # print("Ingredients:", ing_output, "Steps:", step_output)

    # return ing_output, step_output

prediction(model, ingredient_tokenizer, step_tokenizer, trainX)

text = ["""3 tablespoons white wine vinegar
Kosher salt and freshly ground black pepper
1/4 cup extra-virgin olive oil
6 ears fresh corn
2 cups red or orange grape tomatoes, halved
8 ounces fresh mozzarella, cut into small cubes
1 bunch scallions (white and green), thinly sliced

1 1/2 cups fresh basil leaves
1 Whisk together the vinegar, 2 teaspoons salt
and some pepper in a small bowl. Gradually
whisk in the oil, starting with a few drops and then
adding the rest in a steady stream, to make a
Smooth dressing.

Shear off the corn kernels with a sharp knife

over a bowl (you should have about 4 cups).
Toss in the tomatoes, mozzarella and scallions.
Pour the vinaigrette over the salad and toss to coat.
Cover and let stand for at least 15 minutes and up
to 2 hours. Before serving, tear the basil over the
salad and stir."""]

# text = encode_sequences(text_tokenizer, text_max_len, text)

# predictions = model.predict(text)[0]
# print(predictions)
# reverse_word_map = dict(map(reversed, ingredient_tokenizer.word_index.items()))

# def sequence_to_text(list_of_indices):
#     # Looking up words in dictionary
#     words = [reverse_word_map.get(letter) for letter in list_of_indices]
#     return(words)

# # Creating texts
# my_texts = list(map(sequence_to_text, predictions))
# print(my_texts)

# print(ingredient_tokenizer.sequences_to_texts(predictions))


#!/usr/bin/python
# -*- coding: UTF-8 -*-
'''
Simple demonstration of driving damped oscillators
'''
from __future__ import absolute_import
from __future__ import with_statement
from __future__ import division
from __future__ import nested_scopes
from __future__ import generators
from __future__ import unicode_literals
from __future__ import print_function

from numpy import *

def resonantDrive(s,f,tau,x,Fs=1000):
    '''
    Drive resonator with frequency `f` (in Hz),
    time constant `tau` (in second) and state `s`,
    with signal state `x`

    Parameters
    ----------
    s: complex
        Initial state
    f: positive float
        Oscillation frequency in Hz
    tau: positive float
        Time constant in seconds
    x: iterable
        Inputs to filter

    Parameters
    ----------
    Fs: positive int; default 1000
        Sample rate in Hz

    '''
    '''
    Convert tau into discrete update:
    x(t)  = exp(-t/tau)
    dx/dt = -1/tau x(t)
    tau dx/dt = -x(t)
    x = x + dt*(-1/tau x(t))
    x = x - dt/tau x
    x = (1 - dt/tau) x

    Convert frequency into radians
    f  is in cycles  / second
    Fs is in samples / second
    We need radians  / sample
    There are 2 pi radians / cycle
    theta = f*2*pi/Fs
    '''
    theta = f*2*pi/Fs
    s *= exp(1j*theta)
    dt = 1./Fs
    alpha = (1-dt/tau)
    s  = alpha*s+(1-alpha)*x
    return s

def resonantFilter(x,f,tau,Fs=1000):
    '''
    need to normalize
    integrate
    x(t)  = exp(-t/tau)
    from 0 to iy
    skip for now
    '''
    N = len(x)
    M = len(f)
    print(N,M)
    assert N==M
    dt = 1./Fs
    s = complex(0)
    result = complex64(zeros(N))
    adjustor = 1.0
    a  = dt/tau
    z  = 2j*pi/Fs
    x *= a
    b  = 1-a
    modulator = b*exp(f*z)
    for i in xrange(N):
        s = s*modulator[i]+x[i]
        result[i]=s
    return result

def resonantFiltfilt(x,f,tau,order=1):
    '''
    Filter a signal with a single first-order
    complex-valued filter, forwards and bacwards.
    '''
    x = padout(x)
    f = padout(f)
    for i in range(order):
        x = resonantFilter(x,f,tau)
        x = resonantFilter(x[::-1],-f,tau)[::-1]
    return padin(x)

def resonantFilter(x,f,tau,Fs=1000):
    '''
    Filter a signal with a single first-order
    complex-valued filter in the forward direction
    '''
    N = len(x)
    M = len(f)
    print(N,M)
    assert N==M
    dt = 1./Fs
    s = complex(x[0])
    result = complex64(zeros(N))
    adjustor = 1.0
    a  = dt/tau
    z  = 2j*pi/Fs
    b  = 1-a
    modulator = b*exp(f*z)
    for i in xrange(N):
        p = s*modulator[i]
        r = sin(angle(p))*p*x[i]/(0.01+real(p)) \
          + cos(angle(p))*(x[i]-real(p))
        s = p + a*r
        result[i]=s
    return result


#  Singly Linked List Cycle Check

''' Given a singly linked list, write a function which takes in the first node
in a singly linked list and returns a boolean indicating if the linked list
contains a cycle '''

class Node(object):
	def __init__(self,num):
		self.num=num
		self.nextnode=None

def cycle_check(node):
	runner1=node
	runner2=node

	while runner2!=None and runner2.nextnode!=None:
		runner1=runner1.nextnode
		runner2=runner2.nextnode.nextnode
		if runner2==runner1:
			return True

	return False

a=Node(1)
b=Node(2)
c=Node(3)
d=Node(4)

#creating circular linked list

a.nextnode=b
b.nextnode=c
c.nextnode=d
d.nextnode=a

x=Node('man')
y=Node('manish')
z=Node('kumar')

#creating non circular linked list

x.nextnode=y
y.nextnode=z

# Cheacking for circular linked list
print(cycle_check(a))

# cheacking for non circular linked list
print(cycle_check(x))

Linked_List/Singly Linked List Cycle Check.py


from BuildTree import *
from collections import deque
############### Inorder ###############
# (of a BST it becomes sorted)
arr="4,2,1,N,N,3,N,N,5,N,N" # Preorder Representation
root = deserialize(arr)
stack=[]
while stack or root:
    while root:
        stack.append(root)
        root=root.left
    root = stack.pop()
    print(root.val)
    root=root.right

############### Post Order ###############
print("post order")
arr="3,9,N,N,20,15,N,N,7,N,N"
root = deserialize(arr)
stack=[]
while root or stack:
    if root:
        stack.append(root)
        root=root.left
    else:
        tmp = stack[-1].right
        if tmp:
            root = tmp
        else:
            tmp = stack.pop()
            print(tmp.val)
            while stack and tmp==stack[-1].right:
                tmp=stack.pop()
                print(tmp.val)

############### Level Order ###############
print("level order")
# arr="4,2,1,N,N,3,N,N,5,N,N"
arr="3,9,N,N,20,15,N,N,7,N,N"
root = deserialize(arr)
q=deque([root])
while q:
    print([a.val for a in list(q)])
    for i in range(len(q)):
        node = q.popleft()
        # print(node.val)
        if node.left:
            q.append(node.left)
        if node.right:
            q.append(node.right)


class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None

class Solution:
    def hasCycle(self, head: ListNode) -> bool:
        """是否有环"""
        slow = head
        quick = head
        while quick and quick.next:
            slow = slow.next
            quick = quick.next.next
            if slow == quick:
                return True
        return False


from sentiment_model_trainer_mira import SentimentModelTrainerMIRA
from sentiment_model_trainer_swvm import SentimentModelTrainerSWVM
from sentiment_model_trainer_base import Corpus, CorpusFeaturesExtractor, SentimentModelConfiguration
from utils import Singleton

class SentimentModelTrainerFactory(Singleton):
    trainer_algorithms_dict = {
        'mira': SentimentModelTrainerMIRA,
        'SWVM': SentimentModelTrainerSWVM
    }

    def create_trainer(self, train_corpus: Corpus, features_extractor: CorpusFeaturesExtractor,
                       model_config: SentimentModelConfiguration, *args, **kwargs):

        if model_config.trainer_alg not in self.trainer_algorithms_dict:
            raise ValueError('`{}` is not a valid training algorithm type. Supported types are: {}'.format(
                model_config.trainer_alg,
                list(self.trainer_algorithms_dict.keys())
            ))

        trainer_alg_type_constructor = self.trainer_algorithms_dict[model_config.trainer_alg]
        return trainer_alg_type_constructor(train_corpus, features_extractor, model_config, *args, **kwargs)


from sos.report.plugins import Plugin, RedHatPlugin, DebianPlugin

class IpmiTool(Plugin, RedHatPlugin, DebianPlugin):

    short_desc = 'IpmiTool hardware information'

    plugin_name = 'ipmitool'
    profiles = ('hardware', 'system', )

    packages = ('ipmitool',)

    def setup(self):
        cmd = "ipmitool"
        result = self.collect_cmd_output("ipmitool -I usb mc info")
        if result['status'] == 0:
            cmd += " -I usb"

        for subcmd in ['channel info', 'channel getaccess', 'lan print']:
            for channel in [1, 3]:
                self.add_cmd_output("%s %s %d" % (cmd, subcmd, channel))

        # raw 0x30 0x65: Get HDD drive Fault LED State
        # raw 0x30 0xb0: Get LED Status

        self.add_cmd_output([
            "%s raw 0x30 0x65" % cmd,
            "%s raw 0x30 0xb0" % cmd,
            "%s sel info" % cmd,
            "%s sel elist" % cmd,
            "%s sel list -v" % cmd,
            "%s sensor list" % cmd,
            "%s chassis status" % cmd,
            "%s lan print" % cmd,
            "%s fru print" % cmd,
            "%s mc info" % cmd,
            "%s sdr info" % cmd
        ])

# vim: set et ts=4 sw=4 :