#!/usr/bin/python3
#
# first program to learn python and Gtk
# Capture images an create a timelapse using an RPI camera
# requires: ffmpeg, raspistill (optional gthumb for triage)
# antoine@ginies.org
#

import gi
from PIL import Image
import configparser
import subprocess
import sys
import io
import time
import threading
import os.path
import glob
from datetime import datetime

gi.require_version('Gst', '1.0')
gi.require_version('GdkX11', '3.0')
gi.require_version('GstVideo', '1.0')
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gio, GLib, GObject, Gst, GstVideo

Gst.init(None)
video_dev = "/dev/video0"

def read_config():
    config = configparser.ConfigParser()
    STARTCONFIGFILE = '/etc/config.ini'
    if os.path.isfile(STARTCONFIGFILE):
        print("reading /etc/config.ini")
        config.read(STARTCONFIGFILE)
#        config.close()
    else:
        CONFIGFILE = 'config.ini'
        if os.path.isfile('config.ini'):
            print("reading config.ini")
            config.read('config.ini')
            return config
        else:
            print("No config creating one!")
            f = open("config.ini","w+")
            config.read('config.ini')
            if config.has_section("all") != True:
                config.add_section('all')
            if config.has_section("img") != True:
                config.add_section('img')
            if config.has_section("video") != True:
                config.add_section('video')
            config.set('all', 'configfile', 'config.ini')
            config.set('all', 'working_dir', '/tmp/')
            config.set('all', 'live_camera_180', "true")
            config.set('img', 'rotation', '180')
            config.set('img', 'image_name', 'image')
            config.set('img', 'width', '1920')
            config.set('img', 'height', '1080')
            config.set('img', 'quality', '10')
            config.set('img', 'encoding', 'jpg')
            config.set('img', 'timelapse', '3000')
            config.set('img', 'extra', '')
            config.set('video', 'framerate', '30')
            config.set('video', 'setpts', '0.3*PTS')
            config.set('video', 'vcodec', 'libx264')
            config.set('video', 'width', '1920')
            config.set('video', 'height', '1080')
            config.set('video', 'extra', '')
            config.write(f)
            f.close()
            return config

class DisplayVideoConf(Gtk.Window):
    def __init__(self, parent):
        Gtk.Window.__init__(self, title="FFMPEG Video Settings", transient_for=parent)

        self.set_default_size(640, 480)
        self.set_border_width(10)

        def show_video_help(button):
            #self.help_button.set_sensitive(False)
            win = Gtk.Window(title="ffmpeg --help", transient_for=parent)
            win.set_default_size(800, 700)
            win.set_border_width(10)

            # get help
            cmd = "ffmpeg --help"
            proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            rc = proc.wait()
            outs, errs = proc.communicate(timeout=2)
            textview = Gtk.TextView()
            textbuffer = textview.get_buffer()
            textview.set_editable(False)
            scrolled = Gtk.ScrolledWindow()
            scrolled.add(textview)
            end_iter = textbuffer.get_end_iter()
            textbuffer.insert(end_iter, outs.decode("utf8"))

            box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, border_width=12)
            box.pack_start(scrolled, True, True, 0)
            win.add(box)
            win.show_all()

        def on_clicked_ok(button_ok):
            print("saving file")
            fp = open(config.get('all', 'configfile'), 'w')
            config.set('video', 'framerate', entry_framerate.get_text())
            config.set('video', 'setpts', entry_setpts.get_text())
            config.set('video', 'vcodec', entry_vcodec.get_text())
            config.set('video', 'width', entry_vwidth.get_text())
            config.set('video', 'height', entry_vheight.get_text())
            config.set('video', 'extra', entry_vextra.get_text())
            config.write(fp)
            fp.close()
            self.destroy()

        def on_clicked_cancel(button_cancel):
            print("cancel")
            self.destroy()

        #
        # read from config file
        config = read_config()

        self.help_ffmpeg = Gtk.Button(label="ffmpeg Help")
        self.help_ffmpeg.connect("clicked", show_video_help)

        # rotate or not
        box_framerate = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_framerate = Gtk.Entry()
        label_framerate = Gtk.Label("Framerate")
        if config.has_option('video', 'framerate'):
            entry_framerate.set_text(config.get('video', 'framerate'))
        else:
            entry_framerate.set_text("30")

        box_vwidth = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_vwidth = Gtk.Entry()
        label_vwidth = Gtk.Label("Video Width (1920)")
        if config.has_option('video', 'width'):
            entry_vwidth.set_text(config.get('video', 'width'))
        else:
            entry_vwidth.set_text("1920")

        box_vheight = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_vheight = Gtk.Entry()
        label_vheight = Gtk.Label("Video Height (1080)")
        if config.has_option('video', 'height'):
            entry_vheight.set_text(config.get('video', 'height'))
        else:
            entry_vheight.set_text("1080")

        box_steptps = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_setpts = Gtk.Entry()
        label_setpts = Gtk.Label("Video Acceleration (NOT USED)")
        if config.has_option('video', 'setpts'):
            entry_setpts.set_text(config.get('video', 'setpts'))
        else:
            entry_setpts.set_text("0.3*PTS")

        box_vcodec = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_vcodec = Gtk.Entry()
        label_vcodec = Gtk.Label("Video Codec")
        if config.has_option('video', 'vcodec'):
            entry_vcodec.set_text(config.get('video', 'vcodec'))
        else:
            entry_vcodec.set_text('libx264')

        box_vextra = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_vextra = Gtk.Entry()
        label_vextra = Gtk.Label("Extra Options (See Help)")
        if config.has_option('video', 'extra'):
            entry_vextra.set_text(config.get('video', 'extra'))
        else:
            entry_vextra.set_text("")

        box_ok_cancel= Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        button_ok = Gtk.Button.new_with_mnemonic("_Ok")
        button_ok.connect("clicked", on_clicked_ok)
        button_cancel = Gtk.Button.new_with_mnemonic("_Cancel")
        button_cancel.connect("clicked", on_clicked_cancel)

        box_framerate.pack_start(label_framerate, False, False, 0)
        box_framerate.pack_end(entry_framerate, False, False, 0)
        box_vheight.pack_start(label_vheight, False, False, 0)
        box_vheight.pack_end(entry_vheight, False, False, 0)
        box_vwidth.pack_start(label_vwidth, False, False, 0)
        box_vwidth.pack_end(entry_vwidth, False, False, 0)
        box_vcodec.pack_start(label_vcodec, False, False, 0)
        box_vcodec.pack_end(entry_vcodec, False, False, 0)
        box_steptps.pack_start(label_setpts, False, False, 0)
        box_steptps.pack_end(entry_setpts, False, False, 0)
        box_vextra.pack_start(label_vextra, False, False, 0)
        box_vextra.pack_end(entry_vextra, False, False, 0)
        box_ok_cancel.pack_start(button_cancel, False, False, 0)
        box_ok_cancel.pack_end(button_ok, False, False, 0)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vbox.set_spacing(20)
        self.add(vbox)
        vbox.pack_start(self.help_ffmpeg, False, False, 0)

        vboxvideo = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vboxvideo.pack_start(box_framerate, False, False, 0)
        vboxvideo.pack_start(box_vwidth, False, False, 0)
        vboxvideo.pack_start(box_vheight, False, False, 0)
        vboxvideo.pack_start(box_vcodec, False, False, 0)
        vboxvideo.pack_start(box_steptps, False, False, 0)
        framevideo = Gtk.Frame()
        framevideo.add(vboxvideo)
        framevideo.show()

        vboxmore = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vboxmore.pack_start(box_vextra, False, False, 0)
        framemore = Gtk.Frame()
        framemore.add(vboxmore)
        framemore.show()

        vbox.add(framevideo)
        vbox.add(framemore)
        vbox.pack_end(box_ok_cancel, False, False, 0)
        self.show_all()

class DisplayAllConf(Gtk.Window):
    def __init__(self, parent):
        Gtk.Window.__init__(self, title="Application Settings", transient_for=parent)

        self.set_default_size(640, 480)
        self.set_border_width(10)

        def on_clicked_ok(button_ok):
            print("saving file")
            fp = open(entry_info_config.get_text(), 'w')
            config.set('all', 'configfile', entry_info_config.get_text())
            config.set('all', 'working_dir', entry_working_dir.get_text())
            if self.live_camera_180.get_active() == 1:
                config.set('all', 'live_camera_180', 'true')
            else:
                config.set('all', 'live_camera_180', 'false')
            config.write(fp)
            fp.close()
            self.destroy()

        def on_clicked_cancel(button_cancel):
            print("cancel")
            self.destroy()

        def on_file_clicked(widget):
            dialog = Gtk.FileChooserDialog(
                title="Please choose a file", parent=self, action=Gtk.FileChooserAction.OPEN
            )
            dialog.add_buttons(
                Gtk.STOCK_CANCEL,
                Gtk.ResponseType.CANCEL,
                Gtk.STOCK_OPEN,
                Gtk.ResponseType.OK,
            )

            filter_ini = Gtk.FileFilter()
            filter_ini.set_name("ini file")
            filter_ini.add_pattern("*.ini")
            dialog.add_filter(filter_ini)

            response = dialog.run()
            if response == Gtk.ResponseType.OK:
                print("Open clicked")
                print("File selected: " + dialog.get_filename())
                entry_info_config.set_text(dialog.get_filename())
            elif response == Gtk.ResponseType.CANCEL:
                print("Cancel clicked")

            dialog.destroy()

        def on_button_revert_camera(self, button, live_camera_180):
            if button.get_active():
                state = "on"
                print("Camera upside down")
            else:
                print("Camera normal mode")
                state = "off"

        #
        # read from config file
        config = read_config()

        box_info_config = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_info_config = Gtk.Entry()
        label_info_config = Gtk.Label("Path to config file")
        if config.has_option('all', 'configfile'):
            entry_info_config.set_text(config.get('all', 'configfile'))
        else:
            entry_info_config.set_text("config.ini")
        file_info_config = Gtk.Button(label="Choose File")
        file_info_config.connect("clicked", on_file_clicked)

        box_working_dir = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_working_dir = Gtk.Entry()
        label_working_dir = Gtk.Label("Working Directory")
        if config.has_option('all', 'working_dir'):
            entry_working_dir.set_text(config.get('all', 'working_dir'))
        else:
            entry_working_dir.set_text("/tmp/")

        box_ok_cancel= Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        button_ok = Gtk.Button.new_with_mnemonic("_Ok")
        button_ok.connect("clicked", on_clicked_ok)
        button_cancel = Gtk.Button.new_with_mnemonic("_Cancel")
        button_cancel.connect("clicked", on_clicked_cancel)

        box_live_camera_180= Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        self.label_camera_180 = Gtk.Label()
        self.label_camera_180.set_text("180 Camera Rotation")
        self.live_camera_180 = Gtk.CheckButton()
        if config.get('all', 'live_camera_180') == "true":
            self.live_camera_180.set_active("True")
        else:
            self.live_camera_180.set_active("True")
        self.live_camera_180.connect("toggled", on_button_revert_camera, "1")

        box_info_config.pack_start(label_info_config, False, False, 0)
        box_info_config.pack_start(entry_info_config, False, False, 0)
        box_info_config.pack_end(file_info_config, False, False, 0)
        box_working_dir.pack_start(label_working_dir, False, False, 0)
        box_working_dir.pack_end(entry_working_dir, False, False, 0)
        box_live_camera_180.pack_start(self.label_camera_180, False, False, 0)
        box_live_camera_180.pack_end(self.live_camera_180, False, False, 0)

        box_ok_cancel.pack_start(button_cancel, False, False, 0)
        box_ok_cancel.pack_end(button_ok, False, False, 0)

        vboxconf = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vboxconf.pack_start(box_info_config, False, False, 0)
        vboxconf.pack_start(box_working_dir, False, False, 0)
        vboxconf.pack_start(box_live_camera_180, False, False, 0)
        frameconf = Gtk.Frame()
        frameconf.add(vboxconf)
        frameconf.show()

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vbox.set_spacing(20)
        self.add(vbox)

        vbox.add(frameconf)
        vbox.pack_end(box_ok_cancel, False, False, 0)
        self.show_all()

class DisplayImageConf(Gtk.Window):
    def __init__(self, parent):
        Gtk.Window.__init__(self, title="Rpi Camera Settings", transient_for=parent)

        self.set_default_size(640, 480)
        self.set_border_width(10)

        def show_help(button):
            #self.help_button.set_sensitive(False)
            win = Gtk.Window(title="Raspistill help", transient_for=parent)
            win.set_default_size(800, 700)
            win.set_border_width(10)

            # get help
            cmd = "raspistill --help"
            proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            rc = proc.wait()
            outs, errs = proc.communicate(timeout=2)
            textview = Gtk.TextView()
            textbuffer = textview.get_buffer()
            textview.set_editable(False)
            scrolled = Gtk.ScrolledWindow()
            scrolled.add(textview)
            end_iter = textbuffer.get_end_iter()
            textbuffer.insert(end_iter, outs.decode("utf8"))

            box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10, border_width=12)
            box.pack_start(scrolled, True, True, 0)
            win.add(box)
            win.show_all()

        def on_clicked_ok(button_ok):
            print("saving file")
            fp = open(config.get('all', 'configfile'), 'w')
            config.set('img', 'rotation', entry_rot.get_text())
            config.set('img', 'image_name', entry_image_name.get_text())
            config.set('img', 'width', entry_width.get_text())
            config.set('img', 'height', entry_height.get_text())
            config.set('img', 'quality', entry_quality.get_text())
            config.set('img', 'encoding', combo_encoding.get_active_text())
            config.set('img', 'timelapse', entry_timelapse.get_text())
            config.set('img', 'extra', entry_extra.get_text())
            config.write(fp)
            fp.close()
            self.destroy()

        def on_encoding_changed(combo):
            text = combo.get_active_text()
            if text is not None:
                print("DEBUG Selected: encoding=%s" % text)

        def on_clicked_cancel(button_cancel):
            print("cancel")
            self.destroy()

        #
        # read from config file
        config = read_config()

        self.help_button = Gtk.Button(label="raspistill Help")
        self.help_button.connect("clicked", show_help)

        # rotate or not
        box_rot = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_rot = Gtk.Entry()
        label_rot = Gtk.Label("Image Rotation")
        if config.has_option('img', 'rotation'):
            entry_rot.set_text(config.get('img', 'rotation'))
        else:
            entry_rot.set_text("0")

        box_width = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_width = Gtk.Entry()
        label_width = Gtk.Label("Image Width (1920)")
        if config.has_option('img', 'width'):
            entry_width.set_text(config.get('img', 'width'))
        else:
            entry_width.set_text("1920")

        box_height = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_height = Gtk.Entry()
        label_height = Gtk.Label("Image Height (1080)")
        if config.has_option('img', 'height'):
            entry_height.set_text(config.get('img', 'height'))
        else:
            entry_height.set_text("1080")

        box_timelapse = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_timelapse = Gtk.Entry()
        label_timelapse = Gtk.Label("Time between captures (in ms)")
        if config.has_option('img', 'timelapse'):
            entry_timelapse.set_text(config.get('img', 'timelapse'))
        else:
            entry_timelapse.set_text("3000")

        box_quality = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_quality = Gtk.Entry()
        label_quality = Gtk.Label("Quality")
        if config.has_option('img', 'quality'):
            entry_quality.set_text(config.get('img', 'quality'))
        else:
            entry_quality.set_text("10")

        box_encoding = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        encoding_list = [ "jpg", "bmp", "gif", "png"]
        combo_encoding = Gtk.ComboBoxText()
        combo_encoding.set_entry_text_column(0)
        combo_encoding.connect("changed", on_encoding_changed)
        for data in encoding_list:
            combo_encoding.append_text(data)
        combo_encoding.set_active(0)
        label_encoding = Gtk.Label("Encoding format")
        # jpg bmp gif png
        # TOFIX
        if config.has_option('img', 'encoding'):
            print("IN CONFIG: " + config.get('img', 'encoding'))
            #combo_encoding.set_active_iter(config.get('all', 'encoding'))

        box_image_name = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_image_name = Gtk.Entry()
        label_image_name = Gtk.Label("Image Name (prefix)")
        if config.has_option('img', 'image_name'):
            entry_image_name.set_text(config.get('img', 'image_name'))
        else:
            entry_image_name.set_text('image_')

        box_extra = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        entry_extra = Gtk.Entry()
        label_extra = Gtk.Label("Extra Options (See Help)")
        if config.has_option('img', 'extra'):
            entry_extra.set_text(config.get('img', 'extra'))
        else:
            entry_extra.set_text("")

        box_ok_cancel= Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10)
        button_ok = Gtk.Button.new_with_mnemonic("_Ok")
        button_ok.connect("clicked", on_clicked_ok)
        button_cancel = Gtk.Button.new_with_mnemonic("_Cancel")
        button_cancel.connect("clicked", on_clicked_cancel)

        box_rot.pack_start(label_rot, False, False, 0)
        box_rot.pack_end(entry_rot, False, False, 0)
        box_height.pack_start(label_height, False, False, 0)
        box_height.pack_end(entry_height, False, False, 0)
        box_width.pack_start(label_width, False, False, 0)
        box_width.pack_end(entry_width, False, False, 0)
        box_image_name.pack_start(label_image_name, False, False, 0)
        box_image_name.pack_end(entry_image_name, False, False, 0)
        box_encoding.pack_start(label_encoding, False, False, 0)
        box_encoding.pack_end(combo_encoding, False, False, 0)
        box_ok_cancel.pack_start(button_cancel, False, False, 0)
        box_ok_cancel.pack_end(button_ok, False, False, 0)
        box_timelapse.pack_start(label_timelapse, False, False, 0)
        box_timelapse.pack_end(entry_timelapse, False, False, 0)
        box_extra.pack_start(label_extra, False, False, 0)
        box_extra.pack_end(entry_extra, False, False, 0)

        vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vbox.set_spacing(20)
        self.add(vbox)
        vbox.pack_start(self.help_button, False, False, 0)

        vboximg = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vboximg.pack_start(box_image_name, False, False, 0)
        vboximg.pack_start(box_rot, False, False, 0)
        vboximg.pack_start(box_width, False, False, 0)
        vboximg.pack_start(box_height, False, False, 0)
        vboximg.pack_start(box_encoding, False, False, 0)
        vboximg.pack_start(box_quality, False, False, 0)
        frameimage = Gtk.Frame()
        frameimage.add(vboximg)
        frameimage.show()

        vboxmore = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
        vboxmore.pack_start(box_timelapse, False, False, 0)
        vboxmore.pack_start(box_extra, False, False, 0)
        framemore = Gtk.Frame()
        framemore.add(vboxmore)
        framemore.show()
        vbox.add(frameimage)
        vbox.add(framemore)
        vbox.pack_end(box_ok_cancel, False, False, 0)
        self.show_all()

class LogInterFace(Gtk.Window):
    def __init__(self, command):

        Gtk.Window.__init__(self,
                title="Log Interface",
                default_width=500,
                default_height=400,
                )
        self.cancellable = Gio.Cancellable()

        self.start_button = Gtk.Button(label="Show log")
        self.start_button.connect("clicked", self.on_start_clicked)

        textview = Gtk.TextView()
        self.textbuffer = textview.get_buffer()
        scrolled = Gtk.ScrolledWindow()
        scrolled.add(textview)
        self.command = command
        progress = Gtk.ProgressBar(show_text=True)

        box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, border_width=12)
        box.pack_start(self.start_button, False, True, 0)
        box.pack_start(progress, False, True, 0)
        box.pack_start(scrolled, True, True, 0)

        self.add(box)

    def autoscroll(self, *args):
        adj = self.scrolled.get_vadjustment()
        adj.set_value(adj.get_upper() - adj.get_page_size())

    def append_text(self, text):
        iter_ = self.textbuffer.get_end_iter()
        self.textbuffer.insert(iter_, "[%s] %s\n" % (str(time.time()), text))

class MainBox(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="RPI Camera")

        self.set_default_size(320, 280)
        self.set_border_width(20)

        self.img = Image.open('cover.jpg')
        newimg = self.img.resize((200, 200))
        newimg.save('/tmp/_cover.jpg')
        self.live = Gtk.Image()
        self.live.set_from_file("/tmp/_cover.jpg")
        self.live_on_button = Gtk.Button(label="Live ON")
        self.live_on_button.connect("clicked", self.start_live)
        self.live_off_button = Gtk.Button(label="Live OFF")
        self.live_off_button.set_sensitive(False)
        self.live_off_button.connect("clicked", self.stop_live)
        # vbox contains status and live
        self.vboxlive = Gtk.Box()
        self.vboxlive.set_orientation(Gtk.Orientation.VERTICAL)
        self.vboxlive.set_spacing(10)
        hboxlive = Gtk.Box()
        hboxlive.set_orientation(Gtk.Orientation.HORIZONTAL)
        hboxlive.set_spacing(10)
        hboxlive.pack_start(self.live_on_button, False, False, 0)
        hboxlive.pack_end(self.live_off_button, False, False, 0)

        # Create DrawingArea for video widget
        self.drawingarea = Gtk.DrawingArea()
        #self.drawingarea.set_size_request(400, 300)

        # Needed or else the drawing area will be really small (1px)
        self.drawingarea.set_hexpand(True)
        self.drawingarea.set_vexpand(True)
        #self.vboxlive.add(self.drawingarea)
        self.vboxlive.add(hboxlive)
        self.vboxlive.add(self.live)

        #info
        info = Gtk.Label()
        info.set_text("\nCreate a Timelapse based on RPI camera captures\n")
        self.all_settings_button = Gtk.Button(label="All Settings")
        self.all_settings_button.set_tooltip_text("Configure general options")
        self.all_settings_button.connect("clicked", self.on_set_all_conf)

        # current status
        self.status = Gtk.Label()
        self.status.set_text("Capture OFF")
        self.nb_capture = Gtk.Label()
        self.c_button = Gtk.Button(label="Capture")
        self.c_button.set_tooltip_text("Start capturing timelapse")
        self.c_button.connect("clicked", self.start_capture)
        self.t_button = Gtk.Button(label="Test")
        self.t_button.set_tooltip_text("Test a capture with current setting")
        self.t_button.connect("clicked", self.test_capture)
        self.test_spinner= Gtk.Spinner()
        self.s_button = Gtk.Button(label="Stop")
        self.s_button.set_tooltip_text("Stop to capture timelapse")
        self.s_button.set_sensitive(False)
        self.s_button.connect("clicked", self.stop_capture)

        self.settings_button = Gtk.Button(label="Settings")
        self.settings_button.set_tooltip_text("Configure all options for the Timelapse")
        #https://developer.gnome.org/gnome-devel-demos/stable/tooltip.py.html.en
        # with a tooltip with a given text in the Pango markup language
        #BLAL.set_tooltip_markup("Open an <i>existing</i> file")
        self.settings_button.connect("clicked", self.on_set_image_conf)
        self.settings_button.set_sensitive(True)

        vbox = Gtk.Box()
        vbox.set_orientation(Gtk.Orientation.VERTICAL)
        vbox.set_spacing(10)
        self.add(vbox)

        hboxinfo =Gtk.Box()
        hboxinfo.set_orientation(Gtk.Orientation.HORIZONTAL)
        hboxinfo.set_spacing(10)
        hboxinfo.pack_start(info, True, True, 10)
        hboxinfo.pack_start(self.all_settings_button, True, True, 10)
        frameinfo = Gtk.Frame()
        frameinfo.add(hboxinfo)
        frameinfo.show()

        # vbox contains status and live
        vboxs = Gtk.Box()
        vboxs.set_orientation(Gtk.Orientation.VERTICAL)
        vboxs.set_spacing(10)
        vboxs.pack_start(self.status, True, False, 1)
        vboxs.pack_start(self.test_spinner, False, False, 0)
        self.nb_capture.set_visible(False)
        vboxs.pack_start(self.nb_capture, False, False, 0)

        framestatus = Gtk.Frame()
        framestatus.set_label("Status")
        framestatus.add(vboxs)
        framestatus.show()

        self.framelive = Gtk.Frame()
        self.framelive.set_label("Live (OFF)")
        self.framelive.set_tooltip_text("Live ON/OFF | Capture (ON/OFF)")
        self.framelive.add(self.vboxlive)
        self.framelive.show()

        # create a hboxbut for button line and frame
        hboxbut = Gtk.Box()
        hboxbut.set_spacing(10)
        hboxbut.pack_start(self.settings_button, True, True, 0)
        hboxbut.pack_start(self.t_button, True, True, 0)
        hboxbut.pack_start(self.c_button, True, True, 0)
        hboxbut.pack_end(self.s_button, True, True, 0)
        framebut = Gtk.Frame()
        framebut.set_label("Image Capture Command")
        framebut.add(hboxbut)
        framebut.show()

        self.hboxrender = Gtk.Box()
        self.hboxrender.set_orientation(Gtk.Orientation.HORIZONTAL)
        self.hboxrender.set_spacing(10)
        self.render_button = Gtk.Button(label="Render")
        self.render_button.connect("clicked", self.render_timelapse)
        self.video_settings_button = Gtk.Button(label="Settings")
        self.video_settings_button.connect("clicked", self.on_video_conf)
        self.stop_render_button = Gtk.Button(label="Stop Rendering")
        self.stop_render_button.connect("clicked", self.stop_render)
        self.stop_render_button.set_sensitive(False)
        self.choose_image_button = Gtk.Button(label="Gthumb")
        self.choose_image_button.connect("clicked", self.launch_gthumb)
        self.render_spinner= Gtk.Spinner()
        self.hboxrender.pack_start(self.video_settings_button, False, False, 0)
        self.hboxrender.pack_start(self.render_button, False, False, 0)
        self.hboxrender.pack_start(self.stop_render_button, False, False, 0)
        self.hboxrender.pack_start(self.choose_image_button, False, False, 0)
        self.hboxrender.pack_end(self.render_spinner, True, False, 0)
        self.render_button.set_sensitive(True)
        self.render_button.set_tooltip_text("Create the video based on the capture")
        framerender = Gtk.Frame()
        framerender.set_label("Video Timelapse Command")
        framerender.add(self.hboxrender)
        framerender.show()

        vbox.add(frameinfo)
        vbox.add(framestatus)
        vbox.add(self.framelive)
        vbox.add(framebut)
        vbox.add(framerender)

        self.source_id = 0
        self.count = 0

        if os.path.isfile("/usr/bin/raspistill") == False:
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="/usr/bin/raspistill is not present, please install it.",
            )
            dialog.run()
            dialog.destroy()

    def launch_gthumb(self, button):
        print("Launch Gthumb")
        if os.path.isfile("/usr/bin/gthumb"):
            config = read_config()
            self.working_dir = config.get('all', 'working_dir')
            cmd = "/usr/bin/gthumb " + self.working_dir
            self.sp = subprocess.Popen(cmd, cwd=self.working_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        else:
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="/usr/bin/gthumb is not present, please install it.",
            )
            dialog.run()
            dialog.destroy()

    def start_live(self, button):
       # Create GStreamer pipeline
        self.vboxlive.remove(self.live)
        self.vboxlive.add(self.drawingarea)
        self.framelive.set_label("Live (ON)")
        self.live_on_button.set_sensitive(False)
        self.live_off_button.set_sensitive(True)
        self.c_button.set_sensitive(False)
        config = read_config()
        if config.get('all', 'live_camera_180') == "true":
            self.pipeline = Gst.parse_launch("v4l2src device=" + video_dev + " ! tee name=tee ! queue name=videoqueue ! deinterlace ! videoflip method=2 ! xvimagesink ")
        else:
            self.pipeline = Gst.parse_launch("v4l2src device=" + video_dev + " ! tee name=tee ! queue name=videoqueue ! deinterlace ! xvimagesink ")

        # Create bus to get events from GStreamer pipeline
        bus = self.pipeline.get_bus()
        bus.add_signal_watch()
        bus.connect('message::eos', self.on_eos)
        bus.connect('message::error', self.on_error)

        # This is needed to make the video output in our DrawingArea:
        bus.enable_sync_message_emission()
        bus.connect('sync-message::element', self.on_sync_message)
        self.runvideo()

    def stop_live(self, button):
        self.framelive.set_label("Live (OFF)")
        self.vboxlive.remove(self.drawingarea)
        self.vboxlive.add(self.live)
        self.live_on_button.set_sensitive(True)
        self.live_off_button.set_sensitive(False)
        self.c_button.set_sensitive(True)
        self.pipeline.set_state(Gst.State.NULL)

    def runvideo(self):
        self.show_all()
        self.xid = self.drawingarea.get_property('window').get_xid()
        self.pipeline.set_state(Gst.State.PLAYING)
        #Gtk.main()

    def on_sync_message(self, bus, msg):
        if msg.get_structure().get_name() == 'prepare-window-handle':
            #print('prepare-window-handle')
            msg.src.set_property('force-aspect-ratio', True)
            msg.src.set_window_handle(self.xid)

    def on_eos(self, bus, msg):
        print('on_eos(): seeking to start of video')
        self.pipeline.seek_simple( Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0)

    def on_error(self, bus, msg):
        print('on_error():', msg.parse_error())

    def Update_test_rendering(self):
        if self.sptest.poll() is None:
            print("Capture in progress... ")
            return True
        else:
            print("Capture Finished")
            print(self.sptest.stdout)
            print(self.sptest.stderr)
            self.t_button.set_sensitive(True)
            self.test_spinner.stop()
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="Capture available: " + self.working_dir + "/test.jpg",
            )
            imagename= self.working_dir + "/test." + self.encoding
            if os.path.isfile(imagename):
                self.img = Image.open(imagename)
                sizeh = int(600/self.ratio)
                newimg = self.img.resize((600, sizeh))
                newimg.save(self.working_dir + "/_test." + self.encoding)
                self.live.set_from_file(self.working_dir + "/_test." + self.encoding)

            self.c_button.set_sensitive(True)
            self.t_button.set_sensitive(True)
            self.render_button.set_sensitive(True)
            self.settings_button.set_sensitive(True)
            self.live_on_button.set_sensitive(True)
            self.status.set_text("Capture OFF")
            self.test_spinner.stop()
            dialog.run()
            dialog.destroy()
            return False

    def Update_rendering(self):
        if self.spvideo.poll() is None:
            print("Rendering in progress... ")
            return True
        else:
            print("Rendering Finished")
            print(self.spvideo.stdout)
            print(self.spvideo.stderr)
            self.render_button.set_sensitive(True)
            self.render_spinner.stop()
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="Video available: " + self.working_dir + "/output.mp4",
            )
            dialog.run()
            dialog.destroy()
            self.stop_render_button.set_sensitive(False)
            return False

    def render_timelapse(self, button):
        #ffmpeg -r 10 -pattern_type glob -i "*.jpg" -s 1920x1080 -vcodec libx264 output.mp4
        print('try to do rendering!')
        config = read_config()
        self.working_dir = config.get('all', 'working_dir')
        self.image_name = config.get('img', 'image_name')
        encoding = config.get('img', 'encoding')
        framerate = config.get('video', 'framerate')
        setpts = config.get('video', 'setpts')
        vcodec = config.get('video', 'vcodec')
        vwidth = config.get('video', 'width')
        vheight = config.get('video', 'height')
        vextra = config.get('video', 'extra')

        #cmd = "ffmpeg -y -r " + framerate + " -filter:v \"setpts=" + setpts + "\" " + " -pattern_type glob -i \"" + self.working_dir + "/" + self.image_name + "*." + encoding + "\" " + " -s " + vwidth + "x" + vheight + " -vcodec " + vcodec + " " + self.working_dir + "/output.mp4"
        cmd = "ffmpeg -y -r " + framerate + " -pattern_type glob -i \"" + self.working_dir + "/" + self.image_name + "*." + encoding + "\" " + " -s " + vwidth + "x" + vheight + " -vcodec " + vcodec + " " + self.working_dir + "/output.mp4"
        if os.path.isfile("/usr/bin/ffmpeg"):
            #num_files = len([f for f in os.listdir(self.working_dir)if os.path.isfile(os.path.join(self.working_dir, f))])
            os.chdir(self.working_dir)
            num_files = len(glob.glob("*." + encoding))
            dialogr = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.YES_NO,
                text="Are you Sure you want to render the video?\n  (" + str(num_files) + " images found)\n\n" + cmd,
            )
            response = dialogr.run()
            if response == Gtk.ResponseType.YES:
                # enable rendering
                print(cmd)
                self.spvideo = subprocess.Popen(cmd, cwd=self.working_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                self.render_button.set_sensitive(False)
                self.stop_render_button.set_sensitive(True)
                self.c_button.set_sensitive(False)
                self.render_spinner.start()
                self.source_id = GLib.timeout_add(3000, self.Update_rendering)
            elif response == Gtk.ResponseType.NO:
                print("Cancel")
            dialogr.destroy()

        else:
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="/usr/bin/ffmpeg is not present, please install it.",
            )
            dialog.run()
            dialog.destroy()

    def stop_render(self, button):
        dialog = Gtk.MessageDialog(
            transient_for=self,
            flags=0,
            message_type=Gtk.MessageType.WARNING,
            buttons=Gtk.ButtonsType.YES_NO,
            text="Are you Sure you want to stop Rendering in Video?",
        )
        response = dialog.run()
        if response == Gtk.ResponseType.YES:
            GLib.source_remove(self.source_id)
            # TOFIX
            pid = self.spvideo.pid
            #pid += 1
            print(pid)
            #cmd = "kill -9 " + str(pid)
            cmd = "killall ffmpeg"
            proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            rc = proc.wait()
            try:
                outs, errs = proc.communicate(timeout=2)
            except TimeoutExpired:
                proc.kill()
                outs, errs = proc.communicate()
            # enable rendering
            self.c_button.set_sensitive(True)
            self.render_button.set_sensitive(True)
            self.stop_render_button.set_sensitive(False)
            self.render_spinner.stop()
        elif response == Gtk.ResponseType.NO:
            print("Cancel")
        dialog.destroy()

    def Update_info(self, timer):
      c = timer.count + 1
      timer.count = c
      # number of capture start from 1
      nb = c + 1
      print("Update to image image_" + str(c).zfill(4) + '.jpg')
      self.nb_capture.set_text("Number of Captures (every " + str(int(self.timelapse)/1000) +  "s) : " + str(nb))
      imagename= self.working_dir + "/" + self.image_name + str(c).zfill(4) + "." + self.encoding
      if os.path.isfile(imagename):
          self.img = Image.open(imagename)
          sizeh = int(600/self.ratio)
          newimg = self.img.resize((600, sizeh))
          newimg.save(self.working_dir + "/_live_record_rpi." + self.encoding)
          self.live.set_from_file(self.working_dir + "/_live_record_rpi." + self.encoding)
      else:
          self.status.set_text("Cant grab any Images....")
          print("image not ready... bypassing")
      return True

    def test_capture(self, button):
        config = read_config()
        rot= config.get('img', 'rotation')
        self.image_name = config.get('img', 'image_name')
        width = config.get('img', 'width')
        height = config.get('img', 'height')
        quality = config.get('img', 'quality')
        self.working_dir = config.get('all', 'working_dir')
        self.encoding = config.get('img', 'encoding')
        extra = config.get('img', 'extra')
        # be sure working dir exist
        if not os.path.exists(self.working_dir):
            os.makedirs(self.working_dir)
        self.live_on_button.set_sensitive(False)
        self.live_off_button.set_sensitive(False)
        self.vboxlive.remove(self.drawingarea)
        self.vboxlive.add(self.live)
        self.c_button.set_sensitive(False)
        self.settings_button.set_sensitive(False)
        self.t_button.set_sensitive(False)
        self.ratio = float(int(width)/int(height))
        print('Start Capturing a test')
        command = "raspistill" + " -rot " + rot + " -o " + self.working_dir + "/test." + self.encoding + " --width " + width + " --height " + height + " --quality " + quality +  " --encoding " + self.encoding + " -a " + datetime.now().strftime("\"%d/%m/%Y %H:%M:%S\"") + " " + extra
        print(command)
        self.status.set_text("Testing a Capture")
        self.sptest = subprocess.Popen(command, cwd=self.working_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        self.test_spinner.start()
        self.source_id = GLib.timeout_add(2000, self.Update_test_rendering)

    def start_capture(self, button):
        if self.status.get_text() == "Capture ON":
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.INFO,
                buttons=Gtk.ButtonsType.OK,
                text="Capture is already on going...",
                )
            dialog.run()
            dialog.destroy()
        else:
            config = read_config()
            rot= config.get('img', 'rotation')
            #raspistill = config.get('all', 'raspistill')
            self.image_name = config.get('img', 'image_name')
            width = config.get('img', 'width')
            height = config.get('img', 'height')
            quality = config.get('img', 'quality')
            self.encoding = config.get('img', 'encoding')
            self.timelapse = config.get('img', 'timelapse')
            self.working_dir = config.get('all', 'working_dir')
            extra = config.get('img', 'extra')
            # be sure working dir exist
            if not os.path.exists(self.working_dir):
                os.makedirs(self.working_dir)

            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.YES_NO,
                text="All previous images will be deleted from this directory. Are you Sure you want to do this ?\n\n (Current working_dir is: " + self.working_dir + ")" + "\n\n If not please change the working_dir setting to another directory",
            )
            response = dialog.run()
            if response == Gtk.ResponseType.YES:
                cmd = "rm -vf " + self.image_name + "*." + self.encoding
                proc = subprocess.Popen(cmd, cwd=self.working_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                rc = proc.wait()
                try:
                    outs, errs = proc.communicate(timeout=2)
                except TimeoutExpired:
                    proc.kill()
                    outs, errs = proc.communicate()

                self.live_on_button.set_sensitive(False)
                self.live_off_button.set_sensitive(False)
                self.vboxlive.remove(self.drawingarea)
                self.vboxlive.add(self.live)
                self.c_button.set_sensitive(False)
                self.settings_button.set_sensitive(False)
                self.t_button.set_sensitive(False)
                self.s_button.set_sensitive(True)
                self.nb_capture.set_visible(True)
                print('Start Capturing')
                #command = "raspistill" + " -rot " + rot + " --timelapse " + self.timelapse + " -o " + self.image_name + str(chr(37)) +"04d." + self.encoding + " --width " + width + " --height " + height + " --quality " + quality +  " -t 0" + " --encoding " + self.encoding + " -a " + datetime.now().strftime("\"%d/%m/%Y %H:%M:%S\"") + " " + extra
                command = "raspistill" + " -rot " + rot + " --timelapse " + self.timelapse + " -o " + self.image_name + str(chr(37)) +"04d." + self.encoding + " --width " + width + " --height " + height + " --quality " + quality +  " -t 0" + " --encoding " + self.encoding + " -a " + datetime.now().strftime("\"%d/%m/%Y\"") + " " + extra
                self.status.set_text("Capture ON\n")
                #self.status.set_text("Capture ON\n" + command)
                print(command)
                self.sp = subprocess.Popen(command, cwd=self.working_dir, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                self.ratio = float(int(width)/int(height))
                # wait for first image
                loadingimg = "/usr/share/icons/gnome/48x48/status/image-loading.png"
                if os.path.isfile(loadingimg):
                    self.live.set_from_file(loadingimg)
                else:
                    print("Missing image:" + loadingimg)

                self.framelive.set_label("Capture (ON)")
                # wait for first image...
                time.sleep(2)
                self.source_id = GLib.timeout_add(int(self.timelapse), self.Update_info, self)

            elif response == Gtk.ResponseType.NO:
                print("cancel")

            dialog.destroy()

    def stop_capture(self,button):
        if self.status.get_text() == "Capture OFF":
            print("Noting to do")
        else:
            print('Stop')
            dialog = Gtk.MessageDialog(
                transient_for=self,
                flags=0,
                message_type=Gtk.MessageType.WARNING,
                buttons=Gtk.ButtonsType.YES_NO,
                text="Are you Sure you want to stop Timelapse Recording?",
            )
            response = dialog.run()
            if response == Gtk.ResponseType.YES:
                GLib.source_remove(self.source_id)
                #pid = self.sp.pid
                #print("Capture Pid: " + str(pid))
                #cmd = "kill -9 " + str(pid) + " " + str(int(pid + 1))
                cmd = "killall raspistill"
                proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
                rc = proc.wait()
                try:
                    outs, errs = proc.communicate(timeout=2)
                except TimeoutExpired:
                    proc.kill()
                    outs, errs = proc.communicate()
                self.status.set_text("Capture OFF")
                self.framelive.set_label("Live (OFF)")
                self.c_button.set_sensitive(True)
                self.settings_button.set_sensitive(True)
                self.s_button.set_sensitive(False)
                self.t_button.set_sensitive(True)
                self.live_on_button.set_sensitive(True)
                # enable rendering
                self.render_button.set_sensitive(True)
                self.stop_render_button.set_sensitive(False)
            elif response == Gtk.ResponseType.NO:
                print("Cancel")

            dialog.destroy()

    def on_set_image_conf(self, widget):
        dialog = DisplayImageConf(self)

    def on_set_all_conf(self, widget):
        dialog = DisplayAllConf(self)

    def on_video_conf(self, widget):
        print("plop")
        dialog = DisplayVideoConf(self)

# MAIN
window = MainBox()
window.connect("destroy", Gtk.main_quit)
window.show_all()
Gtk.main()


'''Go-cqhttp 事件模型。
WindowsSov8 Anon Bot 自用 Adapter
'''
# -*- coding: utf-8 -*-
# !/usr/bin/python3
from pydantic import BaseModel
from typing import Optional, Literal

from .message import Message

# 所有事件上报的基类
class Event(BaseModel):
    '''所有事件上报的基类'''
    time: int
    '''事件发生的unix时间戳'''
    self_id: int
    '''收到事件的机器人的 QQ 号'''
    post_type: Literal[
        'message', # 消息, 例如, 群聊消息
        'message_sent', # 消息发送，例如，bot发送在群里的消息
        'request', # 请求, 例如, 好友申请
        'notice', # 通知, 例如, 群成员增加
        'meta_event' # 元事件, 例如, go-cqhttp 心跳包
    ]
    '''表示该上报的类型, 消息, 消息发送, 请求, 通知, 或元事件'''

# 消息上报的基类
class MessageEvent(Event):
    '''消息上报的基类'''
    message_type: Literal[
        'private', # 私聊消息
        'group' # 群消息
    ]
    '''消息类型'''
    sub_type: Literal[
        'friend', # 好友
        'normal', # 群聊
        'anonymous', # 匿名
        'group_self', # 群中自身发送
        'group', # 群临时会话
        'notice' # 系统提示
    ]
    '''表示消息的子类型'''
    message_id: int
    '''消息 ID'''
    user_id: int
    '''发送者 QQ 号'''
    message: Message
    '''一个消息链'''
    raw_message: str
    '''CQ 码格式的消息'''
    font: int = 0
    '''字体'''
    sender: 'Sender'
    '''发送者信息'''

    # 表示消息发送者的信息
    class Sender(BaseModel):
        '''表示消息发送者的信息'''
        # 以下是必定存在的信息
        user_id: int
        '''发送者 QQ 号'''
        nickname: str
        '''昵称'''
        sex: Literal[
            'male',
            'female',
            'unknown'
        ]
        '''性别, `male` 或 `female` 或 `unknown`'''
        age: int
        '''年龄'''

        # 当私聊类型为群临时会话时的额外字段
        group_id: Optional[int] = None
        '''临时群消息来源群号'''

        # 如果是群聊
        card: Optional[str] = None
        '''群名片／备注'''
        area: Optional[str] = None
        '''地区'''
        level: Optional[str] = None
        '''成员等级'''
        role: Optional[
            Literal[
                'owner',
                'admin',
                'member'
            ]
        ] = None
        '''角色, `owner` 或 `admin` 或 `member`'''
        title: Optional[str] = None
        '''专属头衔'''

        # 判断是否拥有管理权限
        @property
        def is_admin(self) -> bool:
            '''是否拥有管理权限'''
            return any((self.role == 'admin', self.role == 'owner'))

    # 重定义转换字符串方法
    def __str__(self) -> str:
        '''重定义转换字符串方法'''
        message_string = ''
        if self.message_type == 'group': # 群消息
            message_string += f'[{self.sender.role}]'
            if self.sender.title != '': # 如果有头衔
                message_string += f'[{self.sender.title}]'
            if self.sender.card != '': # 如果有群昵称
                message_string += f'{self.sender.card}({self.sender.nickname})'
            else:
                message_string += self.sender.nickname
            message_string += '(来自群组{})'.format(getattr(self, 'group_id', None))
        else: # 私聊消息
            message_string += f'[{self.sub_type}]{self.sender.nickname}'
        message_string += f'({self.user_id}): {self.raw_message}'

        return message_string

    # 定义配置
    class Config:
        arbitrary_types_allowed = True

# 请求上报的基类
class RequestEvent(Event):
    '''请求上报的基类'''
    request_type: Literal[
        'friend', # 好友请求
        'group' # 群请求
    ]
    '''请求类型'''

# 通知上报的基类
class NoticeEvent(Event):
    '''通知上报的基类'''
    notice_type: Literal[
        'group_upload', # 群文件上传
        'group_admin', # 群管理员变更
        'group_decrease', # 群成员减少
        'group_increase', # 群成员增加
        'group_ban', # 群成员禁言
        'friend_add', # 好友添加
        'group_recall', # 群消息撤回
        'friend_recall', # 好友消息撤回
        'group_card', # 群名片变更
        'offline_file', # 离线文件上传
        'client_status', # 客户端状态变更
        'essence', # 精华消息
        'notify' # 系统通知
    ]
    '''通知类型'''

# 元事件上报的基类
class MetaEvent(Event):
    '''元事件上报的基类'''
    meta_event_type: Literal[
        'lifecycle', # 生命周期
        'heartbeat' # 心跳包
    ]
    '''元事件类型'''

# 私聊消息
class PrivateMessageEvent(MessageEvent):
    '''私聊消息'''
    target_id: int
    '''接收者 QQ 号'''
    temp_source: Optional[int] = None
    '''临时会话来源'''

# 群消息
class GroupMessageEvent(MessageEvent):
    '''群消息'''
    group_id: int
    '''群号'''
    anonymous: Optional['Anonymous']
    '''匿名信息, 如果不是匿名消息则为 null'''

    # 匿名信息
    class Anonymous(BaseModel):
        '''匿名信息'''
        id: int
        '''匿名用户 ID'''
        name: str
        '''匿名用户名称'''
        flag: str
        '''匿名用户 flag, 在调用禁言 API 时需要传入'''

# 私聊消息撤回
class FriendRecallEvent(NoticeEvent):
    '''私聊消息撤回'''
    user_id: int
    '''好友 QQ 号'''
    message_id: int
    '''被撤回的消息 ID'''

# 群消息撤回
class GroupRecallEvent(NoticeEvent):
    '''群消息撤回'''
    group_id: int
    '''群号'''
    user_id: int
    '''消息发送者 QQ 号'''
    operator_id: int
    '''操作者 QQ 号'''
    message_id: int
    '''被撤回的消息 ID'''

# 群成员增加
class GroupIncreaseEvent(NoticeEvent):
    '''群成员增加'''
    sub_type: Literal[
        'approve',
        'invite'
    ]
    '''事件子类型, 分别表示管理员已同意入群、管理员邀请入群'''
    group_id: int
    '''群号'''
    operator_id: int
    '''操作者 QQ 号'''
    user_id: int
    '''加入者 QQ 号'''

# 群成员减少
class GroupDecreaseEvent(NoticeEvent):
    '''群成员减少'''
    sub_type: Literal[
        'leave',
        'kick',
        'kick_me'
    ]
    '''事件子类型, 分别表示主动退群、成员被踢、登录号被踢'''
    group_id: int
    '''群号'''
    operator_id: int
    '''操作者 QQ 号 ( 如果是主动退群, 则和 `user_id` 相同 )'''
    user_id: int
    '''离开者 QQ 号'''

# 群管理员变动
class GroupAdminEvent(NoticeEvent):
    '''群管理员变动'''
    sub_type: Literal[
        'set',
        'unset'
    ]
    '''事件子类型, 分别表示设置和取消管理员'''
    group_id: int
    '''群号'''
    user_id: int
    '''管理员 QQ 号'''

# 群文件上传
class GroupUploadEvent(NoticeEvent):
    '''群文件上传'''
    group_id: int
    '''群号'''
    user_id: int
    '''发送者 QQ 号'''
    file: 'File'
    '''文件信息'''

    # 文件信息
    class File(BaseModel):
        '''文件信息'''
        id: str
        '''文件 ID'''
        name: str
        '''文件名'''
        size: int
        '''文件大小 ( 字节数 )'''
        busid: int
        '''busid ( 目前不清楚有什么作用 )'''

# 群禁言
class GroupBanEvent(NoticeEvent):
    '''群禁言'''
    sub_type: Literal[
        'ban',
        'lift_ban'
    ]
    '''事件子类型, 分别表示禁言、解除禁言'''
    group_id: int
    '''群号'''
    operator_id: int
    '''操作者 QQ 号'''
    user_id: int
    '''被禁言 QQ 号 (为全员禁言时为`0`)'''
    duration: int
    '''禁言时长, 单位秒 (为全员禁言时为`-1`)'''

# 好友添加
class FriendAddEvent(NoticeEvent):
    '''好友添加'''
    user_id: int
    '''新添加好友 QQ 号'''

# 系统通知上报
class NotifyEvent(NoticeEvent):
    '''系统通知上报'''
    sub_type: Literal[
        'poke'
    ]
    '''提示类型'''

# 戳一戳（双击头像）
class PokeEvent(NotifyEvent):
    '''戳一戳（双击头像）'''
    sender_id: Optional[int]
    '''发送者 QQ 号'''
    group_id: Optional[int]
    '''群号'''
    user_id: int
    '''发送者 QQ 号'''
    target_id: int
    '''被戳者 QQ 号'''

# 接收到离线文件
class OfflineFileEvent(NoticeEvent):
    '''接收到离线文件'''
    user_id: int
    '''发送者id'''
    file: 'File'
    '''文件数据'''

    # 文件数据
    class File(BaseModel):
        '''文件数据'''
        name: str
        '''文件名'''
        size: int
        '''文件大小'''
        url: str
        '''下载链接'''

# 加好友请求
class FriendRequestEvent(RequestEvent):
    '''加好友请求'''
    user_id: int
    '''发送请求的 QQ 号'''
    comment: str
    '''验证信息'''
    flag: str
    '''请求 flag, 在调用处理请求的 API 时需要传入'''

# 加群请求/邀请
class GroupRequestEvent(RequestEvent):
    '''加群请求/邀请'''
    sub_type: Literal[
        'add',
        'invite'
    ]
    '''请求子类型, 分别表示加群请求、邀请登录号入群'''
    group_id: int
    '''群号'''
    user_id: int
    '''发送请求的 QQ 号'''
    comment: str
    '''验证信息'''
    flag: str
    '''请求 flag, 在调用处理请求的 API 时需要传入'''

# 心跳包
class HeartbeatEvent(MetaEvent):
    '''心跳包'''
    status: 'Status'
    '''应用程序状态'''
    interval: int
    '''距离上一次心跳包的时间(单位是毫秒)'''

    # 应用程序状态
    class Status(BaseModel):
        '''应用程序状态'''
        app_initialized: bool
        '''程序是否初始化完毕'''
        app_enabled: bool
        '''程序是否可用'''
        plugins_good: Optional[bool]=None
        '''插件正常(可能为 null)'''
        app_good: bool
        '''程序正常'''
        online: bool
        '''是否在线'''
        stat: 'StatusStatistics'
        '''统计信息'''

        # 统计信息
        class StatusStatistics(BaseModel):
            '''统计信息'''
            packet_received: int
            '''收包数'''
            packet_sent: int
            '''发包数'''
            packet_lost: int
            '''丢包数'''
            message_received: int
            '''消息接收数'''
            message_sent: int
            '''消息发送数'''
            disconnect_times: int
            '''连接断开次数'''
            lost_times: int
            '''连接丢失次数'''
            last_message_time: int
            '''最后一次消息时间'''

# 生命周期
class LifecycleEvent(MetaEvent):
    '''生命周期'''
    sub_type: Literal[
        'enable', # 启用
        'disable', # 禁用
        'connect' # 连接
    ]
    '''子类型'''


import torch.nn as nn
import torch
import torch.optim as optim
from torch.autograd import Variable
import gc
from scipy.special import expit
from sklearn.preprocessing import MultiLabelBinarizer

from sklearn.metrics import fbeta_score
import numpy as np
from .model import loadModel, countParams, checkpointModel, softmargin_jaccard_loss_2
from utils.constants import LABEL_LIST, LABEL_WEIGHTS, THRESHOLDS
import pandas as pd
import os
from prettytable import PrettyTable

#TBD - feed in a single tensor into the get_label_strings_from_tensor, rather than doing it per batch
def predict(model, config, loader, dataset = ""):
    config.log("Predicting on {}".format(dataset))
    model.eval()
    print_every = 100

    num_examples = 0

    if dataset is 'train':
        num_examples = config.num_train
    elif dataset is 'val':
        num_examples = config.num_val
    elif dataset is 'test':
        num_examples = loader.dataset.num_examples

    preds_var = Variable(torch.FloatTensor(num_examples,17).type(config.dtype), volatile=True)
    subm = None
    image_list = []

    if dataset is not "test":
        subm = pd.DataFrame(columns=('image_name', 'tags', 'labels'))
        labels_var = Variable(torch.FloatTensor(num_examples,17).type(config.dtype), volatile=True)
        for t, (x, image_names, y) in enumerate(loader):
            if t%print_every == 0:
                print(t)
            x_var = Variable(x.type(config.dtype), volatile=True)
            scores = model(x_var)
            preds_var.data[t*loader.batch_size:t*loader.batch_size+x.size()[0]] = scores.data #tbd - verify this is good
            labels_var.data[t*loader.batch_size:t*loader.batch_size+x.size()[0]] = y
            image_list.extend(image_names)
        #hacky
        labels_var[labels_var>.5] = 1
        labels_var[labels_var<.5] = 0
        labels = get_label_strings_from_tensor(labels_var.data)

        subm['labels'] = labels

    else: #dataset is 'test'..
        subm = pd.DataFrame(columns=('image_name', 'tags'))
        for t, (x, image_names, _) in enumerate(loader):
            if t%print_every == 0:
                print(t)
            x_var = Variable(x.type(config.dtype), volatile=True)
            scores = model(x_var)
            preds_var.data[t*loader.batch_size:t*loader.batch_size+x.size()[0]] = scores.data #tbd - verify this is good
            image_list.extend(image_names)

    preds_var = nn.functional.sigmoid(preds_var)
    preds_var[preds_var>0.5] = 1
    preds_var[preds_var<=0.5] = 0
    preds = get_label_strings_from_tensor(preds_var.data)

    subm['image_name'] = image_list
    subm['tags'] = preds
    submission_name = os.path.join(config.log_dest, "submission_tt_v3_{}.csv".format(dataset))
    subm.to_csv(submission_name, index=False)
    config.log("Done. Made csv: {}".format(submission_name))

def get_label_strings_from_tensor(pred_labels_tensor):
    mlb = MultiLabelBinarizer(classes = LABEL_LIST)
    mlb = mlb.fit(None) #what hte fuck
    pred_labels_cpu = pred_labels_tensor.cpu().numpy()
    pred_labels_str = mlb.inverse_transform(pred_labels_cpu)
    pred_labels = [" ".join(pred_labels_str[i]) for i in range(pred_labels_cpu.shape[0])]
    return pred_labels

# Function: check_accuracy
#
# Evaluates the model on a dataset
# Always takes a model in TRAIN and returns a model in TRAIN
# ----
# Args:
#   model: the model object
#   loader: DataLoader in pytorch
#
def eval_performance(model, config, loader, f2 = True, recall = True, acc = True, label = "", print_probabilities = False):
    #thresholds = torch.FloatTensor(THRESHOLDS).cuda() if config.use_gpu else torch.FloatTensor(THRESHOLDS)
    #thresholds = Variable(thresholds)
    sum_f2 = 0.0
    num_samples_f2 = 0
    num_correct_recall = 0
    num_samples_recall = 0
    num_correct_acc = 0
    num_samples_acc = 0
    class_probabilities = torch.zeros(1, 17).cuda() if config.use_gpu else torch.zeros(1,17)
    model.eval()
    for x, _, y in loader:
        y = y.type(torch.cuda.ByteTensor) if config.use_gpu else y.type(torch.ByteTensor)
        x_var = Variable(x.type(config.dtype), volatile=True)
        scores = model(x_var)
        #scores = expit(scores.data.cpu().numpy())
        scores = nn.functional.sigmoid(scores)
        if print_probabilities:
            class_probabilities += scores.data.sum(0)
        #preds = scores > thresholds.expand(scores.size(0), 17)
        preds = scores > 0.2
        if f2:
            sum_f2 += fbeta_score(preds.data.cpu().numpy(), y.cpu().numpy(), beta=2, average='samples')*y.size(0)
            num_samples_f2 += y.size(0)
        if recall:
            num_correct_recall += (preds.data == y).sum()
            num_samples_recall += preds.size(0)*17
        if acc:
            #num_correct_acc += np.sum([1 for i in range(preds.size(0)) if np.array_equal(preds[i].data.cpu().numpy(), y.cpu().numpy()[i])])
            num_correct_acc += (((preds.data == y).sum(1)) == 17).sum()
            num_samples_acc += preds.size(0)
    if f2:
        f2_score = float(sum_f2)/num_samples_f2
        config.log('F2 score {%s} : Got %.2f' % (label, 100.0 * f2_score))
    if recall:
        recall = float(num_correct_recall) / num_samples_recall
        config.log('Global recall {%s} : Got %d / %d correct (%.2f)' % (label, num_correct_recall, num_samples_recall, 100.0 * recall))
    if acc:
        acc = float(num_correct_acc) / num_samples_acc
        config.log('All or none acc {%s} : Got %d / %d correct (%.2f)' % (label, num_correct_acc, num_samples_acc, 100 * acc))
    model.train()
    if print_probabilities:
        class_probabilities /= num_samples_f2
        class_probabilities = class_probabilities.cpu().numpy()
        table = PrettyTable(['Class', 'Average Probability'])
        for i, x in enumerate(LABEL_LIST):
            table.add_row([x, class_probabilities[0, i]])
        config.log(table)

    return f2_score, recall, acc

def check_per_class_accuracy(model, config, loader, label = ""):
    num_correct = np.zeros((17,))
    num_samples = 0
    model.eval()
    for x, _, y in loader:
        x_var = Variable(x.type(config.dtype), volatile=True)
        scores = model(x_var)
        scores = expit(scores.data.cpu().numpy())
        # sigmoid

        preds = scores > 0.5
        num_correct += (preds == y.cpu().numpy()).sum(axis=0)
        num_samples += preds.shape[0]
    acc = num_correct / num_samples
    # TODO: Not printing this right now because it would print 17 scores at every step.
    #config.log('{} : Got %d / %d correct (%.2f)' % (label, num_correct, num_samples, 100 * acc))
    model.train()
    return acc

###############
###############
###############
# Function: train
#
# Evaluates the model on a dataset
# Always takes a model in TRAIN and returns a model in TRAIN
# ----
# Args:
#   model: the model object
#   loader: DataLoader in pytorch
#
def train(model, config, loss_fn = None, optimizer = None, weight_decay = 0, lr_decay = 0):
    if not loss_fn:
        loss_fn = nn.MultiLabelSoftMarginLoss(weight = None).type(config.dtype) # TODO: should the loss function run on the CPU or GPU?
    if not optimizer:
        optimizer = optim.SGD(model.parameters(), lr = config.lr, momentum = 0.9, weight_decay = weight_decay)

    step_1, step_2, step_3 = False, False, False
    lr = config.lr
    best_f2 = 0.0
    loss_history = [] # per iteration
    train_f2_history = []
    val_f2_history = []
    train_all_or_none_acc_history = [] # per epoch
    val_all_or_none_acc_history = [] # per epoch
    # train_per_class_acc_history = [] # TODO
    # val_per_class_acc_history = []  # TODO
    train_global_recall_history = []
    val_global_recall_history = []

    if config.checkpoint:
        loadModel(model, config, optimizer)
        if config.predict:
            config.log("Skipping training. Predicting only.")
            return None

    countParams(model, config)

    model.train()
    for epoch in range(config.epochs):
        config.log('\nStarting epoch %d / %d with learning rate: %.3E' % (epoch + 1, config.epochs, lr))
        loss_total = 0.0
        grad_magnitude = 0.0
        for t, (x, _, y) in enumerate(config.train_loader):
            # Train
            x_var = Variable(x.type(config.dtype))
            y_var = Variable(y.type(config.dtype)) # removed .long() ?
            scores = model(x_var)
            #loss = softmargin_jaccard_loss_2(loss_fn, scores, y_var, config)
            loss = loss_fn(scores, y_var)
            loss_history.append(loss.data[0])
            loss_total += loss.data[0]

            # Backprop
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            grad_magnitude_t = [(x.grad.data.sum(), torch.numel(x.grad.data)) for x in model.parameters() if x.grad.data.sum() != 0.0]
            grad_magnitude += sum([abs(x[0]) for x in grad_magnitude_t]) #/ sum([x[1] for x in grad_magnitude])

            # Print Loss
            if config.print_every and (t + 1) % config.print_every == 0:
                #grad_magnitude = [(x.grad.data.sum(), torch.numel(x.grad.data)) for x in model.parameters() if x.grad.data.sum() != 0.0]
                #grad_magnitude = sum([abs(x[0]) for x in grad_magnitude]) #/ sum([x[1] for x in grad_magnitude])
                config.log('t = %d, avg_loss = %.4f, grad_mag = %.4f' % (t + 1, loss_total / (t+1), grad_magnitude / (t+1)))
        if epoch >= 10 and not step_1:
            lr = 1e-1 * lr
            optimizer = optim.SGD(model.parameters(), lr = lr, momentum = 0.9, weight_decay = weight_decay)
            step_1 = True
        if epoch >= 20 and not step_2:
            lr = 1e-2 * lr
            optimizer = optim.SGD(model.parameters(), lr = lr, momentum = 0.9, weight_decay = weight_decay)
            step_2 = True
        if epoch >= 25 and not step_3:
            lr = lr / 2.0
            optimizer = optim.SGD(model.parameters(), lr = lr, momentum = 0.9, weight_decay = weight_decay)
            step_3 = True

            gc.collect()

        config.log("Finished Epoch {}/{}".format(epoch + 1, config.epochs))
        config.log("Evaluating...")
        if config.train_loader:
            f2, recall, acc = eval_performance(model, config, config.train_loader, label = "train")
            train_f2_history.append(f2)
            train_global_recall_history.append(recall)
            train_all_or_none_acc_history.append(acc)
            # train_f2_history.append(f2_score(model, config, config.train_loader, "train"))
            # train_all_or_none_acc_history.append(check_all_or_none_accuracy(model, config, config.train_loader, "train"))
            # train_global_recall_history.append(check_global_recall(model, config, config.train_loader, "train"))
        if config.val_loader:
            f2, recall, acc = eval_performance(model, config, config.val_loader, label = "val")
            val_f2_history.append(f2)
            val_global_recall_history.append(recall)
            val_all_or_none_acc_history.append(acc)
            # val_f2_history.append(f2_score(model, config, config.val_loader, "val"))
            # val_all_or_none_acc_history.append(check_all_or_none_accuracy(model, config, config.val_loader, "val"))
            # val_global_recall_history.append(check_global_recall(model, config, config.val_loader, "val"))

        is_best = False
        if float(val_f2_history[-1]) > float(best_f2):
            best_f2 = val_f2_history[-1]
            is_best = True

        if config.save_every or is_best:
            stats = {
                'loss': loss_history[-1],
                'train_f2': train_f2_history[-1],
                'train_acc': train_all_or_none_acc_history[-1],
                'train_recall': train_global_recall_history[-1],
            }
            if config.val_loader:
                stats['val_f2'] = val_f2_history[-1]
                stats['val_acc'] = val_all_or_none_acc_history[-1]
                stats['val_recall'] = val_global_recall_history[-1]
            if not config.no_save:
                checkpointModel(model, config, optimizer, epoch, stats, is_best)
        gc.collect()

    print("\nFinished training.")

    results_dict = {
        'train_loss': loss_history,
        'train_f2': train_f2_history,
        'train_all_or_none': train_all_or_none_acc_history,
        'train_global_recall': train_global_recall_history,
        'val_f2': val_f2_history,
        'val_all_or_none': val_all_or_none_acc_history,
        'val_global_recall': val_global_recall_history
    }
    return results_dict


from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
data = pd.read_csv('IOT_data_2.csv')

print(data)

plt.xlabel("pressure")
plt.ylabel("id")

#train data 및 test data 나누기
x = data[["pressure"]]
y = data[["id"]]
x_train, x_test, y_train, y_test = train_test_split(x,y,train_size=0.8, test_size=0.2)

#예측 모델 생성
lr = LinearRegression()
lr.fit(x_train,y_train)

#정확도 측정
accuracy = lr.score(x_train,y_train)
print(accuracy)

#예측값 구하기
y_predicted = lr.predict(x_test)
mse = mean_squared_error(y_test, y_predicted)
print(mse)
print('w : ',lr.coef_,lr.intercept_)

#산포도 구해보기
plt.scatter(y_test,y_predicted, alpha=0.5)
plt.show()

21-1 산업인공지능개론/과제/산업현장의 IoT데이터 수집 및 예측 분석 결과/10W-1.py


from itertools import combinations_with_replacement, combinations, product
from collections import defaultdict
import sqlite3
from math import factorial

main_base = sqlite3.connect('D://crag3.db')
# main_base.execute('create table crag_retro (p0 INTEGER, p1 INTEGER, q0 INTEGER, q1 INTEGER, '
#                   'ftm INTEGER, value REAL, PRIMARY KEY (p0, p1, q0, q1, ftm))')
#
# main_base.commit()

main_base.execute('delete from crag_retro')
main_base.commit()
scores_cats = [50, 26, 25, 20, 20, 20, 20, 1, 2, 3, 4, 5, 6]
scores_maxes = [50, 26, 25, 20, 20, 20, 20, 3, 6, 9, 12, 15, 18]

categories = {}
categories['crag'] = 0
categories['thirteen'] = 1
categories['three-of-a-kind'] = 2
categories['low straight'] = 3
categories['high straight'] = 4
categories['odd straight'] = 5
categories['even straight'] = 6
categories['6'] = 7
categories['5'] = 8
categories['4'] = 9
categories['3'] = 10
categories['2'] = 11
categories['1'] = 12

def scores(dices):
    _scores = defaultdict(int)
    cts = defaultdict(int)
    for d in dices:
        cts[d] += 1
    if sum(dices) == 13:
        _scores[1] = 1
        if set(cts.values()) == {1,2}:
            if 2 in cts.values():
                _scores[0] = 1
    if len(cts) == 1: _scores[2] = 1
    if dices == (1,2,3): _scores[3] = 1
    if dices == (4,5,6): _scores[4] = 1
    if dices == (1,3,5): _scores[5] = 1
    if dices == (2,4,6): _scores[6] = 1
    for i in range(1, 7):
        if i in cts:
            _scores[6+i] = cts[i]
    return _scores

memo = {}

def tp_to_num2(tp):
    _num = 0
    for i in range(len(tp)):
        if tp[i] != 'NULL':
            _num += pow(3, i)*(tp[i]+1)
    return _num

def tp_to_num3(tp):
    _num = 0
    for i in range(len(tp)):
        if tp[i] != 'NULL':
            _num += pow(4, i)*(tp[i]+1)
    return _num

def expectiminimax(state, ftm):
    memo_local = {}
    ev = 0
    for dices in combinations_with_replacement([i for i in range(1, 7)], 3):
        multiplier = 6
        cts = defaultdict(int)
        for el in dices: cts[el] += 1
        for el in cts: multiplier //= factorial(cts[el])
        saved_dices_set = set()
        for r in range(0, 4):
            for comb in combinations(dices, r):
                saved_dices_set.add(comb)
        if ftm: _ev = float('-inf')
        else: _ev = float('inf')
        for sd in saved_dices_set:
            n = 3 - len(sd)
            __em = 0
            __ct = 0
            for comb in combinations_with_replacement([i for i in range(1, 7)], n):
                _multiplier = factorial(len(comb))
                _cts = defaultdict(int)
                for el in comb: _cts[el] += 1
                for el in _cts: _multiplier //= factorial(_cts[el])
                new_state_dices = tuple(list(sorted(sd + comb)))
                if new_state_dices in memo_local:
                    __em += memo_local[new_state_dices]
                else:
                    if ftm: _temp_minimax_score = float('-inf')
                    else: _temp_minimax_score = float('inf')
                    curr_scores = scores(new_state_dices)
                    possible_actions = []
                    for i in range(13*(int(ftm)^1), 13 + 13*(int(ftm)^1)):
                        if state[i] == 'NULL': possible_actions.append(i)
                    for i in possible_actions:
                        _temp_next_state = list(state)
                        _temp_next_state[i] = curr_scores[i%13]
                        _temp_score = get_score(_temp_next_state, ftm)
                        if ftm:_temp_minimax_score = max(_temp_minimax_score, _temp_score)
                        else: _temp_minimax_score = min(_temp_minimax_score, _temp_score)
                    __em += _temp_minimax_score
                    memo_local[new_state_dices] = _temp_minimax_score
                __ct += _multiplier
            if ftm: _ev = max(_ev, __em/__ct)
            else: _ev = min(_ev, __em/__ct)
        ev += _ev*multiplier
    return ev/pow(6,3)

def get_score(state, ftm):
    sc = tp_to_num2(state[:7]) + tp_to_num3(state[7:13]) + \
                                        tp_to_num2(state[13:20]) + tp_to_num3(state[20:26])
    if state.count('NULL') == 0:
        _pool = get_pool(state)
        if _pool[0] > _pool[1]: return 1
        elif _pool[0] == _pool[1]: return 0
        else: return -1
    if (sc, ftm) in memo:
        return memo[(sc, ftm)]
    else:
        result = main_base.execute('select value from crag_retro where '
                                   'p0 = ' + str(state[0]) + ' and p1 = ' + str(state[1]) + ' and q0 = ' + str(state[2])
                                   + ' and q1 = ' + str(state[3]) + ' and ftm = ' + str(state[4])).fetchone()
        memo[(sc, ftm)] = result[0]
        if len(memo) > pow(10,7): memo.clear()
        return result

def compute(state, pool, ftm):
    state_conversed = (tp_to_num2(state[:7]),) + (tp_to_num3(state[7:13]),) + \
                      (tp_to_num2(state[13:20]),) + (tp_to_num3(state[20:26]),)
    #print(state_conversed)
    if pool[0] > pool[1] + pool[3]:
        score = 1
    elif pool[1] > pool[0] + pool[2]:
        score = -1
    else:
        score = expectiminimax(state, ftm)
    add_score(state_conversed, ftm, score)

def add_score(state_conversed, ftm, score):
    sc = state_conversed
    #print(sc)
    s = 'insert into crag_retro values (' + str(sc[0]) + ', ' + str(sc[1]) + ', ' + str(sc[2]) + ', ' + str(sc[3]) + ', ' + str(int(ftm)) + ', ' + str(score) + ')'
    #print(s)
    main_base.execute(s)
    main_base.commit()

def get_pool(state):
    _pool = [0, 0, 244, 244]
    for i in range(13):
        if state[i] != 'NULL':
            _pool[0] += state[i]*scores_cats[i]
            _pool[2] -= state[i]*scores_maxes[i]
    for i in range(13, 26):
        if state[i] != 'NULL':
            _pool[1] += state[i]*scores_cats[i-13]
            _pool[3] -= state[i]*scores_maxes[i-13]
    return _pool

#s = (0,)*13 + ('NULL',) + (0,)*12
#print(expectiminimax(s, False))

prev = ()
ftm = False
for r in range(13, -1, -1):
    curr = ()
    for comb in combinations([i for i in range(r)], r):
        _comb = set(comb)
        twos = []
        threes = []
        for el in comb:
            if el < 7: twos.append(el)
            else: threes.append(el)
        for num_upp in range(8):
            num_low = r - num_upp
            if num_upp <= 7 and num_low <= 6:
                for prod_upp in product([0,1], repeat=num_upp):
                    print(r, num_upp, prod_upp, comb, len(prev), len(curr))
                    for prod_low in product([0,1,2], repeat=num_low):
                        merged_prod = prod_upp + prod_low
                        state = ['NULL']*13
                        ind_merged_prod = 0
                        for i in range(13):
                            if i in _comb:
                                state[i] = merged_prod[ind_merged_prod]
                                ind_merged_prod += 1
                        state = tuple(state)
                        if r == 13: prev += (state,)
                        else: curr += (state,)
    if len(curr) > 0 and len(prev) > 0:
        ind = 0
        print('hehe')
        ftm = False
        for p in prev:
            for c in curr:
                pool = get_pool(p + c)
                #print(p, c)
                compute(p+c, pool, ftm)
                if ind % 10 == 0: print(ind)
                ind += 1
        ind = 0
        ftm = True
        for c1 in curr:
            for c2 in curr:
                pool = get_pool(c1 + c2)
                compute(c1+c2, pool, ftm)
                if ind % 10 == 0: print(ind)
                ind += 1
    if len(curr) != 0: prev = curr