#!/usr/bin/env python

import gtk, gtk.gdk, gtk.glade
import gobject
import egg.trayicon
import gnomevfs
import os

# Dbus notification
try:
	import dbus
	import dbus.glib
	import dbus.service
except:
	pass

import sys
		
# p:
#  eX = Exponential Moving Average
#  mX = Moving Average
#  b = Bollinger Bands Overlay
#  v = Volume Overlay
#  p = Parabolic SAR overlay
#  s = Splits Overlay
# q:
#  l = Line
#  c = Candles
#  b = Bars
# l:
#  on = Logarithmic
#  off = Linear
# z:
#  l = Large
#  m = Medium
# t:
#  Xd = X Days
#  Xm = X Months
#  Xy = X Years
# a:
#  fX = MFI X days
#  ss = Slow Stochastic
#  fs = Fast Stochastic
#  wX = W%R X Days
#  mX-Y-Z = MACD X Days, Y Days, Signal
#  pX = ROC X Days
#  rX = RSI X Days
#  v = Volume
#  vm = Volume +MA
# c:
#  X = compare with X
#

BASE_URL = "http://ichart.finance.yahoo.com/z?s=%(s)s&t=%(t)s&q=%(q)s&l=%(l)s&z=%(z)s&p=%(p)s&a=%(a)s%(opt)s"
AUTOREFRESH_TIMEOUT = 20*60*1000 # 15 minutes
IMAGE_CHUNK_SIZE = 512*1024 # 512 KBytes

class FinancialChart:
	def __init__(self, ui):
		self.ui = ui
		
		# Window Properties
		win = ui.get_widget("window")
		win.connect("destroy", lambda x: gtk.main_quit())
		win.set_title("Financial Chart")
		
		# Hide progressbar by default
		ui.get_widget("progress").hide()
		
		# Defaut comboboxes values
		for widget in ["t", "q"]:
			ui.get_widget(widget).set_active(0)
		
		# Connect every option widget to its corresponding change signal	
		symbolentry = ui.get_widget("s")
		refresh_chart_callback = lambda w: self.on_refresh_chart()
		
		for widgets, signal in [
			(("pm5","pm10","pm20","pm50","pm100","pm200",
			"pe5","pe10", "pe20","pe50","pe100","pe200",
			"pb","pp","ps","pv",
			"ar","af","ap","aw","am","ass","afs","av","avm"), "toggled"),
			(("t", "q"), "changed"),
			(("s",), "activate"),
		]:
			for widget in widgets:
				ui.get_widget(widget).connect(signal, refresh_chart_callback)
		
		# Setup autocompletion
		completion = gtk.EntryCompletion()
		symbolentry.set_completion(completion)
		
		self.completion_model = gtk.ListStore(gobject.TYPE_STRING)
		completion.set_model(self.completion_model)
		completion.set_text_column(0)
		completion.set_inline_completion(True)
		completion.set_popup_single_match(False)
		completion.connect('match-selected', lambda c, mod, it: self.on_refresh_chart(tickers=mod[it][0]))
		
		# Connect auto-refresh widget
		self.autorefresh_id = 0
		ui.get_widget("autorefresh").connect("toggled", self.on_autorefresh_toggled)
		
		# Tray icon
		tray = egg.trayicon.TrayIcon("Financial Chart")
		img = gtk.Image()
		img.set_from_icon_name("stock_chart", gtk.ICON_SIZE_BUTTON)
		evbox = gtk.EventBox()
		evbox.add(img)
		evbox.connect('button-press-event', self.on_tray_clicked)
		tray.add(evbox)
		tray.show_all()
		
		# Dbus notification
		try:
			bus =  dbus.SessionBus()
			obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
			self.notif = dbus.Interface(obj, 'org.freedesktop.Notifications')
				
			#self.notif.connect_to_signal('ActionInvoked', ??)
			#self.notif.connect_to_signal('NotificationClosed', self.on_closed)
		except Exception, e:
			print >> sys.stderr, 'Failed to display notify bubble', e
					
	def on_tray_clicked(self, evbox, event):
		win = self.ui.get_widget("window")
		if win.get_property('has-toplevel-focus'):
			win.hide()
		else:	
			win.present_with_time(event.time)
			
	def on_refresh_chart(self, from_timer=False, tickers=None):
		if tickers == None:
			tickers = self.ui.get_widget("s").get_text()
			
		if tickers.strip() == "":
			return True
		
		# Record typed string
		self.completion_model.append([tickers])
		
		tickers = [ticker.strip().upper() for ticker in tickers.split(' ') if ticker != ""]
		
		# Update Window Title ------------------------------------------------------
		win = self.ui.get_widget("window")
		title = "Financial Chart - "
		for ticker in tickers:
			title += "%s / " % ticker
		
		win.set_title(title[:-3])

		# Detect Comparison or simple chart ----------------------------------------
		opt = ""
		for ticker in tickers[1:]:
			opt += "&c=%s" % ticker
		
		# Create the overlay string ------------------------------------------------
		p = ""
		for name, param in [
			("pm5",   5),
			("pm10",  10),
			("pm20",  20),
			("pm50",  50),
			("pm100", 100),
			("pm200", 200),
			("pe5",   5),
			("pe10",  10),
			("pe20",  20),
			("pe50",  50),
			("pe100", 100),
			("pe200", 200),
			("pb", ""),
			("pp", ""),
			("ps", ""),
			("pv", ""),
		]:
			if self.ui.get_widget(name).get_active():
				p += "%s%s," % (name[1], param)
			
		# Create the indicators string ---------------------------------------------
		a = ""
		for name, param in [
			("ar",  14),
			("af",  14),
			("ap",  12),
			("aw",  14),
			("am",  "26-12-9"),
			("ass", ""),
			("afs", ""),
			("av",  ""),
			("avm", ""),
		]:
			if self.ui.get_widget(name).get_active():
				a += "%s%s," % (name[1:], param)
		
		# Create the image URL -----------------------------------------------------
		url = BASE_URL % {
			"s": tickers[0],
			"t": self.ui.get_widget("t").get_active_text(),
			"q": self.ui.get_widget("q").get_active_text(),
			"l": "off",
			"z": "l",
			"p": p,
			"a": a,
			"opt": opt,
		}
		
		# Download and display the image -------------------------------------------	
		progress = self.ui.get_widget("progress")
		progress.pulse()
		progress.show()
			
		gnomevfs.async.open(url, self.on_chart_open)

		# Update timer if needed
		self.on_autorefresh_toggled(self.ui.get_widget("autorefresh"))
		
		if from_timer:
			self.show_financial_chart()
		
		return True
	
	def show_financial_chart(self):
		try:
			self.notif.Notify(
				"Financial Chart",
				"stock_chart",
				dbus.UInt32(0),
				"Financial Chart",
				"The chart has been updated",
				[], #actions
				{}, #hints
				5000)
		except Exception, e:
			print >> sys.stderr, 'Failed to display notify bubble', e
			self.ui.get_widget("window").present()
			
	def on_chart_open(self, handle, exc_type):
		progress = self.ui.get_widget("progress")
		progress.pulse()
		
		if not exc_type:
			loader = gtk.gdk.PixbufLoader()
			handle.read(IMAGE_CHUNK_SIZE, self.on_chart_read, loader)
		else:
			handle.close(lambda *args: None)	
			progress.hide()

	def on_chart_read(self, handle, data, exc_type, bytes_requested, loader):
		progress = self.ui.get_widget("progress")
		progress.pulse()
		
		if not exc_type:
			loader.write(data)
		
		if exc_type:
			loader.close()
			handle.close(lambda *args: None)
			self.ui.get_widget("plot").set_from_pixbuf(loader.get_pixbuf())
			progress.hide()
		else:
			handle.read(IMAGE_CHUNK_SIZE, self.on_chart_read, loader)
		
		progress.pulse()
		
	def on_autorefresh_toggled(self, autorefresh):
		if self.autorefresh_id != 0:
			gobject.source_remove(self.autorefresh_id)
			self.autorefresh_id = 0
			
		if autorefresh.get_active():
			self.autorefresh_id = gobject.timeout_add(AUTOREFRESH_TIMEOUT, self.on_refresh_chart, True)
		
if __name__ == "__main__":
	ui = gtk.glade.XML(os.path.join(os.path.dirname(__file__), "financialchart.glade"))
	chart = FinancialChart(ui)
	if len(sys.argv) > 1:
		ui.get_widget("s").set_text(' '.join(sys.argv[1:]))
		chart.on_refresh_chart()

	gtk.main()
