#!/usr/bin/python3 -u


import gi
import argparse
import dbus
import os

HOME_DIR = os.environ['HOME']
if os.path.exists(HOME_DIR+'/.config/tpConfig.txt'):
    with open(HOME_DIR+'/.config/tpConfig.txt', 'r') as f:
        decodeFlag = f.read()
else:
    decodeFlag = "kylin"

def setEnv():
    LIST_VGA = "lspci | grep VGA"
    output = os.popen(LIST_VGA).read()
    if "AMD" in output: 
        os.putenv("LIBVA_DRIVER_NAME", "radeonsi")
        os.putenv("GST_VAAPI_ALL_DRIVERS", "1")
    elif "Intel" in output:
        os.putenv("LIBVA_DRIVER_NAME", "i965")
    else:
        # have no hardware, execute software decode
        decodeFlag = "kylin"

if decodeFlag == "hard":
    setEnv()
    
gi.require_version('Gst', '1.0')
gi.require_version('Gtk', '3.0')
gi.require_version('GstVideo', '1.0')
gi.require_version('GdkX11', '3.0')

from gi.repository import GObject, Gst, Gtk, Gdk, GLib,GdkPixbuf

# Needed for window.get_xid(), xvimagesink.set_window_handle(), respectively:
from gi.repository import GdkX11, GstVideo

# GObject.threads_init()
Gst.init(None)

class Player(object):
    def __init__(self, **kwargs):

        self.hidc = True


        self.cons_LeftBtnPressed = 0
        self.cons_NoBtnPressed = 1
        self.cons_KeyPressed = 2
        self.cons_KeyReleased = 3

        self.cons_NullBtn = 0
        self.cons_LeftBtn = 1
        self.cons_MidBtn = 2
        self.cons_RightBtn = 4

        self.hid_key_event = 0
        self.hid_mouse_event = 1

        resolution = kwargs.get("resolution")
        if resolution:
            split = resolution.split("x")
            self.width = int(split[0])
            self.height = int(split[1])

        scale = kwargs.get("scale")
        if scale:
            split = scale.split("x")
            self.width = int(split[0])
            self.height = int(split[1])

        self.uibc_enabled = 0
        uibc_enabled = kwargs.get("uibc")
        if uibc_enabled:
            self.uibc_enabled = 1

        debug = kwargs.get("debug")
        if debug:
            Gst.debug_set_active(True)
            Gst.debug_set_threshold_from_string(debug, True)

        port = kwargs.get("port")

        uri = kwargs.get("uri")

        self.window = Gtk.Window()        
        self.window.set_name('MiracastPlayer')
        self.window.set_title('MiracastPlayer')
        # self.window.connect('realize', self.realize_cbk)
        self.window.connect('destroy', self.quit)
        self.window.connect('delete_event', self.confirm)
        # self.window.connect('button-press-event', self.on_double_clicked)
        self.window.connect('button-press-event', self.on_mouse_pressed)
        self.window.connect('button-release-event', self.on_mouse_pressed)
        self.window.connect('motion-notify-event', self.on_mouse_moved)
        self.window.connect('key-press-event', self.on_key_pressed)
        self.window.connect('key-release-event', self.on_key_pressed)
        self.window.connect('scroll-event', self.on_scroll)

        if os.path.exists('/usr/share/icons/ukui-icon-theme-fashion/128x128/apps/kylin-miracast.png'):
            img = GdkPixbuf.Pixbuf.new_from_file('/usr/share/icons/ukui-icon-theme-fashion/128x128/apps/kylin-miracast.png')
            self.window.set_icon(img)
            
        # if hasattr(self,'width') and hasattr(self,'height'):
        self.window.set_default_size(self.width, self.height)

        self.window.fullscreen()
        self.isFullScreen = True
        self.drawingarea = Gtk.DrawingArea()
        self.window.add(self.drawingarea)        
        
        _dbus = dbus.SessionBus()
        proxy = _dbus.get_object('org.gnome.SessionManager', '/org/gnome/SessionManager')
        # self.SessionManagerIface = dbus.Interface(proxy, 'org.gnome.SessionManager')
        self.Inhibit = proxy.get_dbus_method('Inhibit', 'org.gnome.SessionManager')
        self.Uninhibit = proxy.get_dbus_method('Uninhibit', 'org.gnome.SessionManager')
        self.sessmngCookie = 0

        # if hasattr(self,'width') and hasattr(self,'height'):
        self.drawingarea.set_size_request(self.width,self.height)
        half_area_width = self.drawingarea.get_allocation().width / 2
        half_area_height = self.drawingarea.get_allocation().height / 2
        half_def_width = self.width / 2
        half_def_height = self.height / 2
        self.min_hor_pos = half_area_width - half_def_width
        self.max_hor_pos = half_area_width + half_def_width
        self.min_ver_pos = half_area_height - half_def_height
        self.max_ver_pos = half_area_height + half_def_height

        self.drawingarea.add_events(Gdk.EventMask.BUTTON_PRESS_MASK|Gdk.EventMask.BUTTON_RELEASE_MASK) 
        self.drawingarea.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
        self.drawingarea.add_events(Gdk.EventMask.BUTTON1_MOTION_MASK)
        self.drawingarea.add_events(Gdk.EventMask.KEY_PRESS_MASK)
        self.drawingarea.add_events(Gdk.EventMask.SCROLL_MASK)

        audio = kwargs.get("audio")

        self.playbin = None

        self.uibc_x = -1
        self.uibc_y = -1

        #Create GStreamer pipeline
        if uri is not None:
            self.pipeline = Gst.Pipeline()

            # Create GStreamer elements
            self.playbin = Gst.ElementFactory.make('playbin', "source")

            if not uri.startswith("http://") or not uri.startswith("http://") or not uri.startswith("file://"):
                if not uri.startswith("/"):
                    
                    uri = os.path.abspath(uri)
                uri = "file://"+uri

            # Set properties
            self.playbin.set_property('uri', uri)

            # Add playbin to the pipeline
            self.pipeline.add(self.playbin)
        else:
            gstcommand = "udpsrc port="+str(port)+" caps=\"application/x-rtp, media=video\" ! rtpjitterbuffer latency=20 ! rtpmp2tdepay ! tsdemux "

            if audio:
                gstcommand += "name=demuxer demuxer. "

            gstcommand += "! queue name=video_ch max-size-buffers=0 max-size-time=0 ! h264parse ! queue name=dec0 max-size-buffers=0 max-size-time=0 "
            
            if "hard"==decodeFlag:
                gstcommand += "! vaapih264dec "
            else:
                gstcommand += "! avdec_h264 max-threads=0 skip-frame=1 output-corrupt=false "

            gstcommand += "! videoconvert n-threads=0 !"

            if scale:
                gstcommand += "videoscale method=1 ! video/x-raw,width="+str(self.width)+",height="+str(self.height)+" ! "

            gstcommand += " autovideosink force-aspect-ratio=false sync=false async=false "

            if audio:
                gstcommand += "demuxer. ! queue max-size-buffers=0 max-size-time=0 ! aacparse ! avdec_aac ! audioconvert ! audioresample ! autoaudiosink sync=false "

            self.pipeline = Gst.parse_launch(gstcommand)


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

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

        self.success = False

    def start_cursor_timer(self):
        if(self.timer_tag == -1):
            self.timer_tag = GLib.timeout_add(3000, self.on_cursor_timer_out)


    def stop_cursor_timer(self):
        if(-1 != self.timer_tag):
            GLib.source_remove(self.timer_tag)
            self.timer_tag = -1

    def on_cursor_timer_out(self):
        # print("HANDLE TIMER .....")
        self.hide_cursor(self.window)
        
    def hide_cursor(self, window):
        if(self.cursor_hidden is False):
            cursor = Gdk.Cursor(Gdk.CursorType.BLANK_CURSOR)
            window.get_window().set_cursor(cursor)
            self.cursor_hidden = True

        self.stop_cursor_timer()
        # print("hide cursor ............")

    def show_cursor(self, widget):
        if(self.cursor_hidden is True):
            cursor = Gdk.Cursor(Gdk.CursorType.ARROW)
            widget.get_window().set_cursor(cursor)
            self.cursor_hidden = False

        self.stop_cursor_timer()
        self.start_cursor_timer()
        # print("show cursor ............")

    def calc_delta(self, oldval, newval):
        temp = newval - oldval
        if(temp > 127):
            temp = 127
        elif(temp < -127):
            temp = -127

        if(temp < 0):
            temp += 2**8

        return temp

    def on_mouse_moved(self, widget, event):
        self.show_cursor(widget)

        if(self.hidc is True):
            #mouse drag event
            if event.state & Gdk.ModifierType.BUTTON1_MASK:
                type = self.cons_LeftBtnPressed
                button = self.cons_LeftBtn  #mouse left button
            else:
                type = self.cons_NoBtnPressed
                button = self.cons_NullBtn

            uibc_x = int(event.x - self.min_hor_pos)
            uibc_y = int(event.y - self.min_ver_pos)
            delta_x = 0
            delta_y = 0

            if(self.uibc_x >= 0 and self.uibc_y >= 0):
                delta_x = self.calc_delta(self.uibc_x, uibc_x)
                delta_y = self.calc_delta(self.uibc_y, uibc_y)

            self.uibc_x = uibc_x
            self.uibc_y = uibc_y
              
            print('{0},{1},{2},{3},{4}'.format(self.hid_mouse_event, type, button, delta_x, delta_y))

    def on_mouse_pressed(self, widget, event):
        #double-click means quit fullscreen mode
        if event.type == Gdk.EventType._2BUTTON_PRESS:
            self.handle_double_clicked(widget, event)
            return
        
        if(self.hidc is True):
            if event.type == Gdk.EventType.BUTTON_PRESS:
                type = self.cons_LeftBtnPressed
            else:
                type = self.cons_NoBtnPressed
            
            if event.button == 1:
                button = self.cons_LeftBtn
            elif event.button == 2:
                button = self.cons_MidBtn
            if event.button == 3:
                button = self.cons_RightBtn

            # uibc_x = int(event.x - self.min_hor_pos)
            # uibc_y = int(event.y - self.min_ver_pos)
            # delta_x = 0
            # delta_y = 0
            # if(self.uibc_x >= 0 and self.uibc_y >= 0):
            #     delta_x = self.calc_delta(self.uibc_x, uibc_x)
            #     delta_y = self.calc_delta(self.uibc_y, uibc_y)

            # self.uibc_x = uibc_x
            # self.uibc_y = uibc_y

            # print('1,{0},{1},{2},{3}'.format(type, button, delta_x, delta_y))
            print('{0},{1},{2},0,0'.format(self.hid_mouse_event, type, button))
        else:
            #<type>,<count>,<id>,<x>,<y>
            if event.type == Gdk.EventType.BUTTON_PRESS:
                type = 0
            else:
                type = 1

            width = self.drawingarea.get_allocation().width
            height = self.drawingarea.get_allocation().height
            half_area_width = width / 2
            half_area_height = height / 2

            half_def_width = self.width / 2
            half_def_height = self.height / 2

            min_hor_pos = half_area_width - half_def_width
            max_hor_pos = half_area_width + half_def_width
            min_ver_pos = half_area_height - half_def_height
            max_ver_pos = half_area_height + half_def_height

            pos_event_x = event.x
            pos_event_y = event.y

            if self.min_hor_pos <= pos_event_x <= self.max_hor_pos and self.min_ver_pos <= pos_event_y <= self.max_ver_pos:
                uibc_x = int(pos_event_x - self.min_hor_pos)
                uibc_y = int(pos_event_y - self.min_ver_pos)
                print('{0},1,0,{1},{2}'.format(type, uibc_x , uibc_y))

    def on_scroll(self, widget, event):
        if(self.hidc is True):
            #<mouse_type, press/release, button, x, y>
            if event.direction == Gdk.ScrollDirection.DOWN:
                scroll_step = -1
            elif event.direction == Gdk.ScrollDirection.UP:
                scroll_step = 1
            else:
                scroll_step = 0
            
            if scroll_step != 0:                
                print('1,1,4,0,0,{0}'.format(scroll_step))


    def on_key_pressed(self, widget, event):
        if(self.uibc_enabled):
            if(self.hidc is True):
                if event.type == Gdk.EventType.KEY_PRESS:
                    print ("{0},{1},0x{2:04x}" .format(self.hid_key_event, self.cons_KeyPressed, event.keyval))
                elif event.type == Gdk.EventType.KEY_RELEASE:
                    print ("{0},{1},0x{2:04x}" .format(self.hid_key_event, self.cons_KeyReleased, event.keyval))
                #<key_type, pressed/released, button>
            else:
                print("3,0x%04X,0x0000" % event.keyval)
        
        if event.type == Gdk.EventType.KEY_PRESS:
            self.show_cursor(widget)


    def handle_double_clicked(self, widget, event):        
        self.show_cursor(widget)
        if event.button == 1 and event.type == Gdk.EventType._2BUTTON_PRESS:
            if True == self.isFullScreen:
                self.window.unfullscreen()
                self.isFullScreen = False
                # self.SessionManagerIface.Uninhibit(self.sessmngCookie)
                self.Uninhibit(self.sessmngCookie)
                self.sessmngCookie = 0
            else:
                self.window.fullscreen()
                self.isFullScreen = True
                # self.sessmngCookie = self.SessionManagerIface.Inhibit("castplayer", 0, "full screen", 8)
                self.sessmngCookie = self.Inhibit("castplayer", 0, "full screen", 8)

    def on_message(self, bus, message):
        if self.playbin:
            videoPad = self.playbin.emit("get-video-pad", 0)
            if videoPad and not self.success:
                videoPadCapabilities = videoPad.get_current_caps()
                if videoPadCapabilities:
                    (self.success, self.videoWidth) = \
                        videoPadCapabilities.get_structure(0).get_int("width")
                    (self.success, self.videoHeight) = \
                        videoPadCapabilities.get_structure(0).get_int("height")
                    if self.success:
                        print("{0} {1}".format(self.videoWidth, self.videoHeight))
                        self.drawingarea.set_size_request(self.videoWidth, self.videoHeight)

    def run(self):
        self.window.show_all()
        # You need to get the XID after window.show_all().  You shouldn't get it
        # in the on_sync_message() handler because threading issues will cause
        # segfaults there.
        window = self.drawingarea.get_property('window')
        if hasattr(window,'get_xid'):
           self.xid = self.drawingarea.get_property('window').get_xid()

        self.pipeline.set_state(Gst.State.PLAYING)
        # self.sessmngCookie = self.SessionManagerIface.Inhibit("castplayer", 0, "full screen", 8)
        # self.sessmngCookie = self.Inhibit("castplayer", 0, "full screen", 8)
        # self.Uninhibit(0)   
        self.cursor_hidden = False
        self.timer_tag = -1
        self.hide_cursor(self.window)
        Gtk.main()

    def confirm(self, window, event=None):
        dialog = Gtk.MessageDialog(window, 0, Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, "选择\"是\"将关闭窗口，连接会断开！\n选择\"否\"取消当前操作")
        resp = dialog.run()
        ret = False
        if resp == Gtk.ResponseType.NO:
            ret = True
        dialog.destroy()
        return ret

    def quit(self, window):        
        # self.Uninhibit(self.sessmngCookie)
        # self.sessmngCookie = 0
        self.pipeline.set_state(Gst.State.NULL)
        Gtk.main_quit()

    def on_sync_message(self, bus, msg):
        if msg.get_structure().get_name() == 'prepare-window-handle':
            print(self.drawingarea.get_allocation())
            if hasattr(self,'xid'):
               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())


if __name__ == '__main__':

    parser = argparse.ArgumentParser()
    parser.add_argument("uri", nargs="?",                 help="Uri to play")
    parser.add_argument("-v", "--version",                help="Show package version")
    parser.add_argument("--log-level",   metavar="lvl",   help="Maximum level for log messages")
    parser.add_argument("-p", "--port",  type=int, default=1991,  help="Port for rtsp")
    parser.add_argument("-a", "--audio", dest="audio", action="store_true", help="Enable audio support")
    parser.add_argument("-s", "--scale", metavar="WxH",   help="Scale to resolution")
    parser.add_argument("-d", "--debug",                  help="Debug")
    parser.add_argument("--uibc",        type=int, help="Supported uibc-ctl:1 or 0")
    parser.add_argument("--res",         metavar="n,n,n", help="Supported resolutions masks (CEA, VESA, HH)")
    # res
    # "                        default CEA  %08X\n"
    # "                        default VESA %08X\n"
    # "                        default HH   %08X\n"
    parser.add_argument("-r", "--resolution",             help="Resolution")
    parser.set_defaults(audio=True)
    args = parser.parse_args()

    p = Player(**vars(args))
    p.run()
