#!/usr/bin/env python

#  CoverGloobus by BadChoice <guitarboy000@gmail.com>
#  - Reused a lot of code from NowPlaying Screenlet done by Magicrobomonkey and modified by Vrunner
#
# INFO:
# - a simple viewer for currently playing and lyrics searcher
# - Webpage: http://launchpad.net/covergloobus
# - Blog:	 http://gloobus.wordpress.com
# - Donate:  guitarboy000@gmail.com via PayPal
# 
# TODO:
#This program is free software; you can redistribute it and/or
#modify it under the terms of the GNU General Public License
#as published by the Free Software Foundation; either version 2
#of the License, or (at your option) any later version.
#
#This program is distributed in the hope that it will be useful,
#but WITHOUT ANY WARRANTY; without even the implied warranty of
#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#GNU General Public License for more details.
#
#You should have received a copy of the GNU General Public License
#along with this program; if not, write to the Free Software
#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

import sys
#import signal
import gobject
import pango
import pygtk
pygtk.require('2.0')
import gtk
#from gtk import gdk
import cairo
#import os.path
import ConfigParser
import os
import dbus
from dbus.mainloop.glib import DBusGMainLoop

#import subprocess
import config
import WEBServices
#import thread

#from subprocess import Popen

from docky import*
#from colors import *
from ui import *
from about import *
from lyricsearch import LyricSearch
#from widgets import CoverGloobusConfig

import common
from common import c
from common import _
import widgets

sys.path.append(common.ABS_PATH + "/players")
#from GenericPlayer import *

sys.path.append(common.ABS_PATH + "/lyrics")
from lyrics import *

sys.path.append(common.ABS_PATH + "/tabs")
from tabs import *

if gtk.pygtk_version < (2,10,0):
    c.error("PyGtk 2.10.0 or later required")
    raise SystemExit

#=============================================================================#
#=						 COVER GLOOBUS MAIN CLASS							==#
#=============================================================================#
class CoverGloobus:

	win 			= None
	config 			= None
	UIManager 		= None
	dbus_iface 		= None
	player 			= None
	configprcs  	= None
	dockyManager 	= None
	statusIcon 		= None
	menu 			= None
	lyrics          = None
	
	handler_play_id = None
	handler_prev_id = None
	handler_next_id = None
	
	track = common.Track()

	docky_show = None

	conf_window = None

	# ================================ MAIN ================================ #
	## Main function
	## Runs initialization methods, sets SIGUSR1 handler and
	## then runs the main gtk bucle.
	def main(self):
		self.set_pid()
		self.init_dbus() # Start dbus
		self.init() #Init configurations
		#signal.signal(signal.SIGUSR1, self.SIGUSR1_handler)
		#signal.signal(signal.SIGUSR2, self.SIGUSR1_handler)
		
		return gtk.main()
		#try: gtk.main()
		#except KeyboardInterrupt: print 'terminated'
		#return True

	def set_pid(self):
		f = open('/tmp/covergloobus.pid', 'w')
		f.truncate()
		f.write(str(os.getpid()))
		f.close()
	
	# =============================== INIT  ================================ #	
	def init(self):
		self.disconnect_player_controls()
		self.config = common.config_load()
		self.k_ctrl = False
		self.ontop = self.config.getboolean("StartPosition","ontop")
		self.track = common.Track()
		self.draw_window()
		self.init_player()
		self.init_docky()
		self.init_lyrics()
		self.change_song()
		self.win.set_keep_above(self.ontop)
		self.set_player_control_callbacks()
		self.create_menu()
		self.show_status_icon()
		self.win.queue_draw()
		c.debug('Init done')
	
	# ============================== INIT DBUS ============================= #	
	def init_dbus(self):
		c.debug("Initializing dbus")
		DBUS_NAME = "org.freedesktop.DBus"
		DBUS_OBJECT = "/org/freedesktop/DBus"

		dbus.set_default_main_loop(DBusGMainLoop(set_as_default=True)) #SET Global dbus main loop
		session_bus = dbus.SessionBus()
		dbus_object = session_bus.get_object(DBUS_NAME, DBUS_OBJECT)
		self.dbus_iface = dbus.Interface(dbus_object, DBUS_NAME)
	
	# ============================== INIT PLAYER =========================== #	
	def init_player(self):
		p_name = self.config.get("Player","name")
		if self.player == None or p_name != self.player.get_player_name():
			if self.player != None:
				c.debug("Removing old player")
				self.player.close()
			c.debug("Loading player")
			exec "from "+ p_name + " import *"	#Import player
			exec "self.player = " + p_name + "Player(self.dbus_iface)"	#Create the player 
			self.player.set_handler(self.state_handler)
			c.debug("Player loaded: " + p_name)
		self.player.last_song = None
		self.player.set_config(self.config)
		if self.player.is_active():
			self.player.active = self.player.connect()
			state = self.player.get_state()
			if state != 'stopped':
				self.track = self.player.get_track()
				if state == 'playing' and self.UIManager.playercontrols_item:
					self.UIManager.playercontrols_item.change_state('playing')
			#self.change_song()
    
	# ============================== INIT DOCKY ============================ #
	def init_docky(self):
		# If docky, show in taskbar
		if self.config.getboolean("Docky","value"):
			if not self.dockyManager:
				self.dockyManager = docky(self, c)
				if self.dockyManager.is_active():
					gobject.timeout_add (2000, self.docky_connect)
				else:
					self.dockyManager = None
			self.win.set_property("skip-taskbar-hint", False)
		else:
			if self.dockyManager:
				self.dockyManager.disconnect()
				self.dockyManager = None
			self.win.set_property("skip-taskbar-hint", True)

	# ============================== INIT LYRICS ============================ #
	def init_lyrics(self):
		if not self.lyrics:
			self.lyrics = LyricSearch(self.config)
		else:
			self.lyrics.set_config(self.config)
		

	# ============================== Connect with Docky ==================== #
	def docky_connect(self):
		self.dockyManager.connect()
		self.dockyManager.create_menu()
		self.dockyManager.bind_signals()
		self.player.last_song = ''
		self.change_song()
		return False

	# ============================== DRAW WINDOW =========================== #        
	def draw_window(self):
		if self.docky_show == None:
			self.docky_show = self.config.getboolean("Docky","value")
		elif self.docky_show != self.config.getboolean("Docky","value"):
			self.docky_show = self.config.getboolean("Docky","value")
			new_window = True
		else:
			new_window = False
		theme = self.config.get("Theme","path")
		c.debug("Theme: " + theme)
		if self.win == None or self.UIManager == None or self.UIManager.theme_path != theme or new_window:
			if self.win != None:
				c.debug("Removing old window")
				self.win.disconnect(self.win.gloobus_expose_id)
				self.win.destroy()
			c.debug("Drawing window")
			self.win = gtk.Window() 
			#self.win.set_type_hint(gtk.gdk.WINDOW_TYPE_HINT_DOCK)
			self.win.set_icon_from_file(common.ICON_PATH)
			self.win.set_property	("skip-taskbar-hint", True)
			self.win.set_property	("skip-pager-hint", True)
			self.win.set_keep_below(True)
			self.win.set_decorated(False)
			self.win.stick()

			self.UIManager = UI(self.win,c)
			if not os.path.exists(theme + "/skin.xml"):
				c.warning(_('Theme not found, setting default theme'))
				c.debug('Theme not found: ' + theme)
				default_conf = common.config_default()
				theme = default_conf.get("Theme","path")
				self.config.set('Theme', 'name', default_conf.get('Theme','name'))
				self.config.set('Theme', 'path', theme)
			self.UIManager.load_xml(theme + "/skin.xml")
			c.debug("Theme name: "+ self.config.get("Theme","name"))
			c.debug("Theme path: "+ self.config.get("Theme","path"))
			
			self.win.set_title('CoverGloobus')
			self.win.set_position(gtk.WIN_POS_CENTER)
			self.win.set_default_size(self.UIManager.get_width(), 
				self.UIManager.get_height()) #Window Size


			#self.win.connect			('delete-event', gtk.main_quit)
			self.win.set_app_paintable	(True)
			self.win.gloobus_expose_id = self.win.connect			('expose-event', self.expose)
			self.win.add_events			( gtk.gdk.BUTTON_PRESS_MASK )
			self.win.connect			('button-press-event', self.clicked)
			self.win.add_events			( gtk.gdk.KEY_PRESS_MASK )
			self.win.connect			('key-press-event', self.keypressed)
			
			self.win.set_colormap	(self.win.get_screen().get_rgba_colormap()) #Set Alpha Screen
			#self.win.set_keep_above(int(self.config.get("StartPosition","ontop"))) #Always on top
			
			self.win.show_all()
		self.win.move(int(self.config.get("StartPosition","x")),
            self.config.getint("StartPosition","y"))
	
	# ========================= SHOW STATUS ICON =========================== #
	# Show icon in notification area
	def show_status_icon(self):
		show = self.config.getboolean("StatusIcon","value")
		c.debug('Showing status icon: ' + str(show))
		if self.statusIcon == None and show:
			self.statusIcon = gtk.status_icon_new_from_file(common.ICON_PATH)
			self.statusIcon.set_tooltip("CoverGloobus")
			self.statusIcon.connect('popup-menu', self.status_icon_menu)
		elif self.statusIcon != None:
			self.statusIcon.set_visible(show)
	
	# ============================ UPDATE CONFIG =========================== #
	# It saves the configuration of the positions
	def update_config(self):
		save_pos = self.win.get_position()
		self.config.set('StartPosition', 'x', str(save_pos[0]))
		self.config.set('StartPosition', 'y', str(save_pos[1]))
		self.config.set('StartPosition', 'ontop', str(int(self.ontop)))
	
	# ========================= CLICKED ==================================== #
	## 
	## @param widget Main Application window
	## @param event Clicked Event
	## @return None
	##
	## This function mask the buttons from mouse
	def clicked(self, widget, event):
		c.debug("Button Pressed: " + str(event.button))
		if event.button == 1:
			if event.type == gtk.gdk.BUTTON_PRESS:
				stick = self.config.getboolean("DesktopStick", "value")
				if not stick:
					c.debug("Moving")
					self.win.begin_move_drag(event.button, int(event.x_root), 
						int(event.y_root), event.time)
				else:
					c.debug("Sticked to desktop, can't move!")
			elif event.type == gtk.gdk._2BUTTON_PRESS:
				if self.track.cover:
					widgets.CoverWindow(self.track)
		elif event.button == 2:
			self.lyrics.load_lyrics(self.track)
		elif event.button == 3:
			c.debug("Right Button Pressed, Launch menu")
			self.menu.popup(None,None,None,3,event.get_time(),0)
			
		if not self.ontop:
			self.win.set_keep_below(True)
	
	#======================== STATUS ICON MENU ============================= #			
	def status_icon_menu(self, icon, button, time):
		c.debug("Status icon right-clicked")
		if button == 3:
			self.menu.popup(None,None,gtk.status_icon_position_menu,3,time,
				self.statusIcon)

	# ========================= PRESSED ==================================== #
	## 
	## @param widget Main Application window
	## @param event Pressed Event
	## @return None
	##
	## This function mask the key pressed by keyboard
	def keypressed(self, widget, event):
			if event.keyval == 65507:
				c.debug("Keypress: CONTROL")
				self.k_ctrl=True
			if event.keyval == 65307:
				self.menu_option_quit(None,None)
			elif event.keyval == 116 or event.keyval == 84:
				#t or T
				c.debug("Keypress: t or T")
				self.ontop=not self.ontop
				self.config.set('StartPosition', 'ontop', str(int(self.ontop)))
				self.win.set_keep_above(self.ontop)
				self.k_ctrl=False

	# ============================= MENU =================================== #
	## @return None
	##
	## This function creates and shows the menu
	def create_menu(self):
		if self.menu:
			self.menu.destroy()
		self.menu 	= gtk.Menu();
		lyrics 	= gtk.MenuItem(_("Show Lyrics"))
		self.menu.append (lyrics);
		tabs 	= gtk.MenuItem(_("Show Tabs"))
		self.menu.append (tabs);
		config 	= gtk.MenuItem(_("Configure"))
		self.menu.append (config);
		stick 	= gtk.CheckMenuItem(_("Stick to desktop"))
		self.menu.append (stick);
		ontop 	= gtk.CheckMenuItem(_("Always on top"))
		self.menu.append (ontop);
		blog 	= gtk.MenuItem(_("Blog"))
		self.menu.append (blog);
		donate 	= gtk.MenuItem(_("Donate"))
		self.menu.append (donate);
		bug 	= gtk.MenuItem(_("Report a Bug"))
		self.menu.append (bug);
		about 	= gtk.MenuItem(_("About"))
		self.menu.append (about);
		quit 	= gtk.MenuItem(_("Quit"))
		self.menu.append (quit)
		
		stick.set_active(self.config.getboolean("DesktopStick","value"))
		ontop.set_active(self.config.getboolean("StartPosition","ontop"))
        
		lyrics.connect 	('button-press-event', self.menu_option_lyrics);
		tabs.connect 	('button-press-event', self.menu_option_tabs);
		config.connect 	('button-press-event', self.menu_option_config);
		stick.connect   ("activate"          , self.menu_option_stick)
		ontop.connect   ("activate"          , self.menu_option_ontop)
		blog.connect	('button-press-event', self.menu_option_blog);
		donate.connect	('button-press-event', self.menu_option_donate);
		bug.connect		('button-press-event', self.menu_option_bug);
		about.connect	('button-press-event', self.menu_option_about);
		quit.connect	('button-press-event', self.menu_option_quit);

		self.menu.show_all()

	# ========================== MENU OPTION================================ #
	##
	def menu_option_lyrics(self,widget,event):
		c.debug("MENU OPTION: show lyrics")
		self.lyrics.load_lyrics(self.track)

	def menu_option_tabs(self,widget,event):
		c.debug("MENU OPTION: show tabs")
		self.lyrics.load_tabs(self.track)

	def menu_option_config(self,widget,event):
		#if self.conf_window:
		#	if self.conf_window.poll() == None:
		#		g.warning('Configuration allready running')
		#		return
		#	
		#g.debug("MENU OPTION: config")
		#self.update_config()
		#common.config_save(self.config)
		#command = 'python ' + common.ABS_PATH + '/covergloobus-config.py ' + str(os.getpid())
		#if debug:
		#	command = command + ' --debug'
		#self.conf_window = Popen(command, shell=True)
		#command = command + ' &'
		#os.system(command);
		c.debug("MENU OPTION: configure")
		if not self.conf_window:
			self.conf_window = widgets.CoverGloobusConfig(self)
			self.conf_window.connect('destroy', self.clear_conf_window)
		else:
			c.warning(_('Configuration already running'))

	def clear_conf_window(self, widget):
		self.conf_window = None

	def menu_option_stick(self,widget):
		c.debug("MENU OPTION: stick to desktop")
		if widget.active:
			self.config.set('DesktopStick', 'value', '1')
		else:
			self.config.set('DesktopStick', 'value', '0')
			
	def menu_option_ontop(self,widget):
		c.debug("MENU OPTION: always on top")
		self.ontop = bool(widget.active)
		self.config.set('StartPosition', 'ontop', str(int(self.ontop)))
		self.win.set_keep_above(self.ontop)
		if not self.ontop:
			self.win.set_keep_below(True)

	def menu_option_blog(self,widget,event):
		c.debug("MENU OPTION: blog")
		os.system("gnome-open http://gloobus.wordpress.com/");

	def menu_option_donate(self,widget,event):
		c.debug("MENU OPTION: donate")
		os.system("gnome-open \"https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=guitarboy000%40gmail%2ecom&item_name=Gloobus%2c%20A%20Quicklook%20for%20linux&no_shipping=0&no_note=1&tax=0&currency_code=EUR&lc=US&bn=PP%2dDonationsBF&chars\"");

	def menu_option_bug(self,widget,event):
		c.debug("MENU OPTION: report bug")
		os.system("gnome-open https://bugs.launchpad.net/covergloobus");

	def menu_option_about(self,widget,event):
		c.debug("MENU OPTION: about")
		a = about()
		a.create()

	def menu_option_quit(self,widget,event):
		c.debug("MENU OPTION: quit")
		self.update_config()
		common.config_save(self.config)
		#c.debug("Position saved")

		#if self.conf_window and self.conf_window.poll() == None:
		#	self.conf_window.kill()

		os.remove('/tmp/covergloobus.pid')
		
		gtk.main_quit()
	

	# ================================ EXPOSE ============================== #
	## 
	## @param widget Main Application window
	## @param event Expose Event
	## @return None
	##
	## This function draws the theme
	## It draws a black background then, gets the new information and finally draws each item in the theme config file
	## 
	def expose(self,widget, event=None):
		cr = widget.window.cairo_create()	#Get a cairo context
		cr.set_operator(cairo.OPERATOR_CLEAR)
		cr.paint()
		cr.set_operator(cairo.OPERATOR_OVER)
		
		if self.config.getboolean('Background', 'show'):
			(width, height) = widget.get_size() #Get window size
			self.draw_nice_background(cr,width,height) #Draw the nice background
		
		for i in self.UIManager.items[:]:	#Draw each interface item
		    i.draw(cr)
		    
		return False

	# ===================================== Draw Nice Backgroun ===================================== #
	## 
	## @param cr Cairo context
	## @param w width of the rectangle to draw
	## @param h height of the rectangle to draw
	## @return None
	##
	## This function draws a nice semiblack background using cairo, this function is very useful to see the dimensions
	## of the window when creating a theme
	##
	def draw_nice_background(self,cr,w,h):
		x = 0
		y = 0
		r = 15
		
		alpha = self.config.getfloat('Background', 'opacity')
		cr.set_operator(cairo.OPERATOR_OVER);
		cr.set_source_rgba (0, 0, 0, alpha);		# Background opacity
		
		cr.move_to	( x+r,y )                     	# Move to A
		cr.line_to	( x+w-r,y )                    	# Straight line to B
		cr.curve_to	( x+w-r,y,x+w,y,x+w,y+r )		# Curve to C
		cr.line_to	( x+w,y+h-r )                	# Move to D
		cr.curve_to	( x+w,y+h-r,x+w,y+h,x+w-r,y+h )	# Curve to E
		cr.line_to	( x+r,y+h )                  	# Line to F
		cr.curve_to	( x+r,y+h,x,y+h,x,y+h-r )   	# Curve to G
		cr.line_to	( x,y+r )                      	# Line to H
		cr.curve_to	( x,y+r,x,y,x+r,y)      		# Curve to G
		cr.fill		( )
		
	def change_song(self):
		if self.track.title != self.player.last_song:
			self.player.last_song = self.track.title
			self.lyrics.song_changed(self.track)
			if self.dockyManager:
				self.dockyManager.set_waiting()
			c.debug("COVER PATH:"+ self.track.cover)
			if (self.track.cover == "" and self.player.active) or not os.path.exists(self.track.cover):
				c.debug("No cover given")
				if self.config.getboolean("AmazonCovers", "value"):
					c.debug("Try to download cover")
					self.track.cover = WEBServices.amazon_download_cover(self.track.artist,
						self.track.album, c)
				else:
					c.debug("No download (in config)")
					self.track.cover = ""
			if self.UIManager.albumcover_item:					
				self.UIManager.albumcover_item.set_image(self.track.cover)

			if self.UIManager.titlename_item:
				self.UIManager.titlename_item.set_text(self.track.title)

			if self.UIManager.albumname_item:
				self.UIManager.albumname_item.set_text(self.track.album)
		
			if self.UIManager.artistname_item:
				self.UIManager.artistname_item.set_text(self.track.artist)

			if self.UIManager.rating_item:
				self.UIManager.rating_item.set_rating(self.track.rating)
			
			if self.dockyManager:
				#self.dockyManager.connect() #If its still not connected :D
				self.dockyManager.set_cover(self.track.cover)
				self.dockyManager.set_title(self.track.artist, self.track.title)
				self.dockyManager.unset_waiting()
			self.win.window.invalidate_rect((0,0,self.UIManager.get_width(),
				self.UIManager.get_height()),False)
			c.info(_("Song Changed") + ': ' + self.track.title)
			
	def check_song(self):
		"""
			Checks if player is active or not
			if active load current song, controlls if it is different...
		"""
		active = self.player.is_active()
		if active:
			if not self.player.active: 
				c.info("Player Active")
				self.player.active = self.player.connect()
			
			track = self.player.get_track()
			if self.track.title != track.title:
				self.track = track
		else:
			c.debug("Player not active")
			self.player.active = False
			self.track = common.Track()
			self.win.window.invalidate_rect((0,0,self.UIManager.get_width(),self.UIManager.get_height()),False)
		self.change_song()
		return False

	
	# ============================ Set Callbacks =========================== #
	def set_player_control_callbacks(self):
		c.debug('Connecting player controls')
		if self.UIManager.playercontrols_item:
			if self.UIManager.playercontrols_item.prev_button:
				self.handler_prev_id = self.UIManager.playercontrols_item.prev_button.connect('button-press-event',self.player.previous)

			if self.UIManager.playercontrols_item.play_pause_button:
				self.handler_play_id = self.UIManager.playercontrols_item.play_pause_button.connect('button-press-event',self.player.play)

			if self.UIManager.playercontrols_item.next_button:
				self.handler_next_id = self.UIManager.playercontrols_item.next_button.connect('button-press-event',self.player.next)
	
	# ========================== Disconnect Callbacks ====================== #			
	## Disconetc the player controls so we can connect the new ones if player changed
	def disconnect_player_controls(self):
		c.debug('Disconnecting player controls')
		if self.handler_prev_id:
			self.UIManager.playercontrols_item.prev_button.disconnect(self.handler_prev_id)
			self.handler_prev_id = None
				
		if self.handler_play_id:
			self.UIManager.playercontrols_item.play_pause_button.disconnect(self.handler_play_id)
			self.handler_play_id = None
				
		if self.handler_next_id:
			self.UIManager.playercontrols_item.next_button.disconnect(self.handler_next_id)
			self.handler_next_id = None
    
	# ============================ SIGUSR1_Handler ========================= #
	# When configuration changes itself and saved, sends the SIGUSR1 signal to covergloobus
	# We handle it and init covergloobus with the new configuration    
	#def SIGUSR1_handler(self, signum, frame):
		#g.debug('SIGUSR1 catched')
		#self.init()

	def state_handler(self, state=None, track=None, time=None):
		#states: started, closed, playing, paused, stopped
		if state:
			self.player_state = state
			c.debug('STATE: ' + state)
			if state == 'started':
				self.player.active = self.player.connect()
			elif state == 'closed':
				if self.player.active:
					gobject.timeout_add(200, self.check_song)
					self.player.active = False
			elif state == 'playing':
				if self.track.title == '':
					self.check_song()
				if self.UIManager.playercontrols_item:
					self.UIManager.playercontrols_item.change_state(state)
			elif state == 'paused':
				if self.UIManager.playercontrols_item:
					self.UIManager.playercontrols_item.change_state(state)
			elif state == 'stopped':
				self.track = common.Track()
				self.change_song()
			else:
				c.warning('UNKNOWN STATE: ' + state)
				
		if track:
			if track != True:
				self.track = track
				self.change_song()
			else:
				self.check_song()
		
	def player_handler(self, action):
		pass

if __name__ == '__main__':

	#TRANSLATIONS
	#gettext.bindtextdomain('covergloobus', config.prefix+'/share/locale/') ##TODO GET IT FROM CONFIG
	#gettext.textdomain('covergloobus')
	#_ = gettext.gettext

	#g = colors()
	#debug = False
	#for a in sys.argv:
	#	if a == '--debug':
	#		debug = True
	#g.set_debug(debug)

	pid = common.get_pid()
	if pid:
		c.warning('CoverGloobus already running.')
		sys.exit(1)
    	
	cg = CoverGloobus()
	sys.exit(cg.main())  

