#!/usr/bin/env python

# This file is part of Window-Switch.
# Copyright (c) 2009-2013 Antoine Martin <antoine@nagafix.co.uk>
# Window-Switch is released under the terms of the GNU GPL v3

from winswitch.util.simple_logger import Logger
logger = Logger("gstreamer_util")
debug_import = logger.get_debug_import()

debug_import("sys, os, commands, thread, subprocess")
import os

debug_import("globals")
from winswitch.globals import OSX, WIN32
debug_import("common")
from winswitch.util.common import csv_list
debug_import("sound_util")
from winswitch.sound.sound_util import has_pa, get_pa_device_options
debug_import("consts")
from winswitch.consts import MODEM_56K_SPEED, DSL_256K_SPEED, DEFAULT_INTERNET_SPEED, DEFAULT_LAN_SPEED, MINIMUM_LAN_SPEED, MAX_SPEED



OSXAUDIO_SINK = "osxaudiosink"
PA_SINK = "pulsesink"
AUTO_SINK = "autoaudiosink"
JACK_SINK = "jackaudiosink"
ESD_SINK = "esdsink"
SDLAUDIO_SINK = "sdlaudiosink"
OSS_SINK = "osssink"
OSS4_SINK = "oss4sink"
DIRECTSOUND_SINK="directsound"

OSXAUDIO_SOURCE = "osxaudiosrc"
PA_SOURCE = "pulsesrc"
AUTO_SOURCE = "autoaudiosrc"
JACK_SOURCE = "jackaudiosrc"
ALSA_SOURCE = "alsasrc"
OSS_SOURCE = "osssrc"
OSS4_SOURCE = "oss4src"

VORBIS = "vorbis"
FLAC = "flac"
AAC = "faa"

THEORA = "theora"
VP8 = "vp8"
XVID = "xvid"
DIVX = "divx"

# {VORBIS : "Free, open, and unpatented format", FLAC : "Free Lossless Audio Codec", AAC : "Advanced Audio Coding"}
# {THEORA : "Free, lossy video compression", VP8 : "Open video compression format from Google", XVID : "Video codec following the MPEG-4 standard", DIVX : "MPEG-4 Version 3 video codec" }
AUDIO_CODECS = [VORBIS, FLAC, AAC]
VIDEO_CODECS = [THEORA, VP8, XVID, DIVX]

has_gst = False
has_tcp_plugins = False
supported_gstaudio_codecs = []
supported_gstvideo_codecs = []

all_plugin_names = []
def has_plugins(*names):
	global all_plugin_names
	for name in names:
		if name not in all_plugin_names:
			#logger.sdebug("missing %s" % name, *names)
			return	False
	return	True
def has_codec(name):
	#ie: vorbisenc,vorbisdec or faac,faad
	return has_plugins("%senc" % codec, "%sdec" % codec) or	has_plugins("%sc" % codec, "%sd" % codec)

def get_encoder(name):
	if has_plugins("%senc" % name):
		return	"%senc" % name
	if has_plugins("%sc" % name):
		return	"%sc" % name
	raise Exception("encoder not found for %s" % name)

def get_decoder(name):
	if has_plugins("%sdec" % name):
		return	"%sdec" % name
	if has_plugins("%sd" % name):
		return	"%sd" % name
	raise Exception("decoder not found for %s" % name)

def get_decoder_options(encoder_options={}):
	return	{}

try:
	debug_import("pygst")
	import pygst
	pygst.require("0.10")
	debug_import("gst")
	import gst
	logger.slog("found gst version %s and pygst version %s" % (gst.version_string(), gst.get_pygst_version()))
	has_gst = True

	registry = gst.registry_get_default()
	all_plugin_names = [el.get_name() for el in registry.get_feature_list(gst.ElementFactory)]
	all_plugin_names.sort()
	logger.sdebug("found the following plugins: %s" % csv_list(all_plugin_names))
	has_tcp_plugins = has_plugins("tcpclientsink", "tcpclientsrc", "tcpserversink", "tcpserversrc")
	for codec in AUDIO_CODECS:
		if has_codec(codec):
			supported_gstaudio_codecs.append(codec)
	for codec in VIDEO_CODECS:
		if has_codec(codec):
			supported_gstvideo_codecs.append(codec)
except ImportError, e:
	logger.serror("(py)gst is missing, sound forwarding and GStreamer video streaming is disabled: %s" % e)



def get_queue2_max_size_time_str(line_speed):
	v = get_queue2_max_size_time(line_speed)
	if v<=0:
		return	""
	return "max-size-time=%s" % v

def get_queue2_max_size_time(line_speed):
	if line_speed<=MODEM_56K_SPEED:
		return	2000000000l		#2s
	if line_speed<=DSL_256K_SPEED:
		return	1600000000l		#1.6s
	if line_speed<=DEFAULT_INTERNET_SPEED:
		return	1200000000l		#1.2s
	if line_speed<=MINIMUM_LAN_SPEED:
		return	 600000000l		#0.6s
	if line_speed<=DEFAULT_LAN_SPEED:
		return	 200000000l		#0.2s
	if line_speed<=MAX_SPEED:
		return	 80000000l		#0.08s
	return	 5000000l			#0.05s


# These are not accessible through pygst?
GST_QUEUE_NO_LEAK = 0			#Not Leaky
GST_QUEUE_LEAK_UPSTREAM = 1		#Leaky on Upstream
GST_QUEUE_LEAK_DOWNSTREAM = 2	#Leaky on Downstream

def _get_receiver_pipeline(mode, host, port, sound_sink_module, sound_sink_options, decoder_plugin, decoder_options):
	#ie: gst-launch tcpclientsrc host=192.168.42.100 port=5000 ! gdpdepay ! vorbisdec ! queue ! pulsesink
	return ["tcp%ssrc host=%s port=%s" % (mode, host, port),
			"gdpdepay",
			"%s %s" % (decoder_plugin, format_options(decoder_options)),
			"queue leaky=%s" % GST_QUEUE_NO_LEAK,
			"%s %s" % (sound_sink_module, format_options(sound_sink_options)),
			]

def _get_sender_pipeline(mode, host, port, sound_src_module, sound_src_options, encoder_plugin, encoder_options):
	#ie: gst-launch pulsesrc ! vorbisenc ! gdppay ! queue ! tcpserversink host=192.168.42.100 port=5000
	return ["%s %s" % (sound_src_module, format_options(sound_src_options)),
			"%s %s" % (encoder_plugin, format_options(encoder_options)),
			"gdppay",
			"queue leaky=%s" % GST_QUEUE_LEAK_DOWNSTREAM,
			"queue leaky=%s" % GST_QUEUE_NO_LEAK,
			"tcp%ssink host=%s port=%s " % (mode, host, port)]

def make_pipeline_str(pipeline_list):
	return	" ! ".join(pipeline_list)


def start_gst_sound_pipe(daemon, start_callback, terminated_callback, log_filename, env, name, in_or_out, mode, host, port, gst_mod, gst_mod_opts, codec, codec_options):
	logger.sdebug(None, daemon, start_callback, terminated_callback, log_filename, env, name, in_or_out, mode, host, port, gst_mod, gst_mod_opts, codec, codec_options)
	if mode!="client" and mode!="server":
		raise Exception("mode must be client or server!")
	if in_or_out:
		pipeline = _get_receiver_pipeline(mode, host, port, gst_mod, gst_mod_opts, decoder_plugin=get_decoder(codec), decoder_options=codec_options)
	else:
		pipeline = _get_sender_pipeline(mode, host, port, gst_mod, gst_mod_opts, encoder_plugin=get_encoder(codec), encoder_options=codec_options)
	gst_exec_args = get_hidden_wrapper_command(in_or_out)
	start_gst_pipeline(gst_exec_args, daemon, start_callback, terminated_callback, log_filename, env, name, mode, pipeline)


def start_gst_pipeline(gst_exec_args, daemon, start_callback, terminated_callback, log_filename, env, name, mode, pipeline):
	args = get_launch_wrapper_command_for_pipeline(gst_exec_args, daemon, log_filename, name, pipeline, env)
	return _exec_launch_wrapper(daemon, start_callback, terminated_callback, args, env)

def get_launch_wrapper_command_for_pipeline(args_list, daemon, log_filename, name, pipeline, env, dock_icon=None):
	assert pipeline
	assert len([x for x in pipeline if x is None])==0, "found empty item in pipeline: %s" % pipeline
	assert name
	def add_arg(arg_name, arg_value=None):
		if OSX:
			""" On OSX we can't pass arguments to open(GST_Launcher.app) so we have to use the environment instead. YUK!
				If there is a non crazy way of doing this I haven't found it.
				Having to bundle another Info.plist just to set LSBackgroundOnly=1 was bad enough, this is much much worse!
			"""
			env_name = arg_name.replace("-", "_")
			if arg_value is not None:
				env["__%s" % env_name] = arg_value
			else:
				env["__%s" % env_name] = "True"
		else:
			if arg_value is not None:
				args_list.append("--%s=%s" % (arg_name, arg_value))
			else:
				args_list.append("--%s" % arg_name)
	add_arg("session-name", name)
	add_arg("pipeline", make_pipeline_str(pipeline))
	""" on win32 and osx, we run embedded - so dont bother with the daemon stuff """
	if daemon and not WIN32 and not OSX:
		add_arg("daemon")
		add_arg("print-pid")
		add_arg("quiet")
	if log_filename:
		add_arg("log-file", log_filename)
	if dock_icon and OSX:
		add_arg("dock-icon", dock_icon)
	logger.sdebug("=%s" % str(args_list), daemon, log_filename, name, pipeline, env, dock_icon)
	return	args_list

def get_hidden_wrapper_command(in_or_out, hidden=True):
	"""
	Utility method so we can use the hidden version (with its own Info.plist on osx)
	"""
	from winswitch.util.commands_util import GST_CAPTURE_COMMAND, GST_PLAYBACK_COMMAND, GST_LAUNCHER_APP
	if OSX and hidden:
		return	["open", "-n", "-W", GST_LAUNCHER_APP]
	if in_or_out:
		return	[GST_PLAYBACK_COMMAND]
	else:
		return	[GST_CAPTURE_COMMAND]

def _exec_launch_wrapper(daemon, start_callback, terminated_callback, args, env):
	"""
	Executes the command,
	using _exec_embedded (when daemon is False or on win32 and osx)
	using _do_exec_daemon otherwise
	"""
	if not daemon or WIN32 or OSX:
		""" On win32 we can't daemonize the process, but since the server should die with the client, that's OK.
		osx runs the server embedded so we also run the sound pipes embedded """
		return _exec_embedded(start_callback, terminated_callback, args, env)
	else:
		_do_exec_daemon(start_callback, terminated_callback, args, env)
		return	None

def _exec_embedded(start_callback, terminated_callback, args, env):
	"""
	Run the sound process wrapper in a SimpleLineProcess so we get notified of start/end.
	"""
	from winswitch.util.process_util import SimpleLineProcess
	from winswitch.util.commands_util import GST_LAUNCHER_APP
	cwd = GST_LAUNCHER_APP or os.getcwd()
	def line_handler(line):
		logger.sdebug(None, line)
	logger.slog("cwd=%s" % cwd, start_callback, terminated_callback, args, env)
	proc = SimpleLineProcess(args, env, cwd, line_handler, start_callback=start_callback, exit_callback=terminated_callback, log_full_command=True)
	proc.start()
	return	proc

def _do_exec_daemon(start_callback, terminated_callback, args_list, env):
	from winswitch.util.process_util import twisted_exec
	def daemon_running(stdoutdata):
		pid = int(stdoutdata.replace("\n", "").replace("\r", ""))
		from winswitch.util.process_util import DaemonPidWrapper
		real_proc = DaemonPidWrapper(args_list, pid)
		start_callback(real_proc)
	def daemon_failed(error):
		logger.serror(None, error)
		terminated_callback(None)
	twisted_exec(args_list, ok_callback=daemon_running, err_callback=daemon_failed, env=env)










def parse_options(options):
	"""
	Used to parse a map from the command line in the following format: a:1,b:2,c:3 etc..
	"""
	opts = {}	#{"device": "alsa_output.0.analog-stereo.monitor"}
	opts_str = str(options)
	if opts_str and len(opts_str)>0:
		for option in opts_str.split(","):
			s = option.split("=", 1)
			if len(s)<2:
				continue
			opts[s[0]] = s[1]
	return opts

def format_options(options):
	"""
	Formats a dict as a string that can be used on the command line and parsed by parse_options() above.
	"""
	if not options or len(options)==0:
		return	""
	opts_str = []
	for k,v in options.items():
		opts_str.append("%s=%s" % (k,v))
	return csv_list(opts_str, quote=None, sep=' ', before="", after="")




def filter_plugins(*descr_plugins):
	d = []
	for descr,plugin in descr_plugins:
		if has_plugins(plugin):
			d.append((descr,plugin))
	return	d


def get_default_gst_sound_sink_module_options():
	"""
	The list of sound sink modules we can choose from
	"""
	return	filter_plugins(("OSX Audio", OSXAUDIO_SINK),
							("Direct Sound", DIRECTSOUND_SINK),
							("PulseAudio", PA_SINK),
							("Auto", AUTO_SINK),
							("SDL", SDLAUDIO_SINK),
							("OSS v4", OSS4_SINK),
							("OSS", OSS4_SINK),
							("Jack", JACK_SINK),
							("ESD", ESD_SINK)
							)

def get_default_gst_sound_sink_module():
	d = get_default_gst_sound_sink_module_options()
	if len(d)==0:
		return	None
	_,plugin = d[0]
	return	plugin

def get_default_gst_sound_sink_options_options(module):
	if module==PA_SINK:
		sinks = get_pa_device_options(False, False)						#ie: {"alsa_card.pci-0000_00_14.2" : "Internal Audio"}
		if len(sinks)>0:
			return	{"device":sinks}
	return	{}
def get_default_gst_sound_sink_options(module):							#default options for module selected
	options_list = get_default_gst_sound_sink_options_options(module)	#ie: {device:[list of devices], other_option:[list_of_possible_values]}
	options_selected = {}
	if len(options_list)>0:
		for option,values in options_list.items():						#ie: device,{"alsa_card.pci-0000_00_14.2" : "Internal Audio"}
			if values and len(values)>0:
				name,_ = values.items()[0]
				options_selected[option] = name							#select first value from list as default
	return	options_selected



def get_default_gst_sound_source_module_options():
	return	filter_plugins(("OSX Audio", OSXAUDIO_SOURCE),
							("PulseAudio", PA_SOURCE),
							("Auto", AUTO_SOURCE),
							("Alsa", ALSA_SOURCE),
							("OSS v4", OSS4_SOURCE),
							("OSS", OSS_SOURCE),
							("Jack", JACK_SOURCE)
							)

def get_default_gst_sound_source_module():
	d = get_default_gst_sound_source_module_options()
	if len(d)==0:
		return	None
	_,plugin = d[0]
	return	plugin

def get_default_gst_sound_source_options_options(module, monitors=False):
	if module==PA_SOURCE:
		sources = get_pa_device_options(monitors, True)						#ie: {"alsa_card.pci-0000_00_14.2" : "Internal Audio"}
		if len(sources)>0:
			return	{"device":sources}
	return	{}
def get_default_gst_sound_source_options(module, monitors=False):
	options_list = get_default_gst_sound_source_options_options(module, monitors)	#ie: {device:[list of devices], other_option:[list_of_possible_values]}
	options_selected = {}
	if len(options_list)>0:
		for option,values in options_list.items():
			if values and len(values)>0:
				name,_ = values.items()[0]
				options_selected[option] = name							#select first value from list as default
	return	options_selected

#CLONE: only pulseaudio supported for now
def get_default_gst_sound_clone_module_options():
	opt = []
	if has_pa():
		opt.append(("PulseAudio", PA_SOURCE))
	return	opt
def get_default_gst_clone_module():
	if has_pa():
		return	PA_SOURCE
	return	None
def get_default_gst_sound_clone_options(module):
	return	get_default_gst_sound_source_options(module, monitors=True)





def get_default_gst_video_sink_module_options():
	if OSX:			#All native gstreamer video sinks are just broken at the moment on osx...
		return	filter_plugins(
								("SDL", "sdlvideosink"),
								("X11", "ximagesink")
							)
	return	filter_plugins(
							("OSX native", "osxvideosink"),
							("Direct Draw", "directdrawsink"),
							("Auto", "autovideosink"),
							("OpenGL", "glimagesink"),
							("SDL", "sdlvideosink"),
							("X11", "ximagesink"),
							("X11 Xv extension", "xvimagesink"),
							("GConf system default", "gconfvideosink"),
							("GSettings default", "gsettingsvideosink"),
							("DirectFB", "dfbvideosink"),
							("Linux Framebuffer", "fbdevsink")
							)
	#other options: ximagesink (*nix), xvimagesink (*nix with XVideo), directdrawsink (win32), directshowsink (win32), d3dvideosink (win32)

def get_default_gst_video_sink_module():
	d = get_default_gst_video_sink_module_options()
	if len(d)==0:
		return	None
	_,plugin = d[0]
	return	plugin


def get_default_gst_video_source_module_options():
	if OSX:			#All gstreamer video sources are just broken at the moment on osx...
		return	[]
	return	filter_plugins(
							("OSX native", "osxvideosrc"),
							("X11", "ximagesrc"),
							("GConf system default", "gconfvideosrc"),
							("GSettings default", "gsettingsvideosrc"),
							("Auto", "autovideosrc"),
						)

def get_default_gst_video_source_module():
	d = get_default_gst_video_source_module_options()
	if len(d)==0:
		return	None
	_,plugin = d[0]
	return	plugin
