#!/usr/bin/env python
# -*- coding: utf-8 -*-

#    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 3 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, see <http://www.gnu.org/licenses/>.

#  Widescape Weather Screenlet / vrunner
#	Weather Info : Based on the Default Weather Screenlet
#  UI : Based on Widescape's Weather Konfabulator Widget

from urllib import urlopen
import screenlets
from screenlets.options import ColorOption, FontOption, BoolOption, StringOption
import cairo
import string
import time
import pango
import gtk
import gobject

# use gettext for translation
import gettext

_ = screenlets.utils.get_translator(__file__)

def tdoc(obj):
	obj.__doc__ = _(obj.__doc__)
	return obj

@tdoc
class WidescapeWeatherScreenlet(screenlets.Screenlet):
	"""A Minimal Weather Screenlet that uses Widescape Weather Graphics"""
	
	# default meta-info for Screenlets
	__name__ = 'WidescapeWeatherScreenlet'
	__version__ = '0.1.1'
	__author__ = 'vrunner'
	__desc__ = __doc__

	__timeout = None
	update_interval = 300
	show_error_message = 1

	# editable options
	bgColor = (0.8,0.1,0.1,0.9)
	borderColor = (1,1,1,0.7)
	iconColor = (1,1,1,1)
	textColor = (1,1,1,1)

	bigfont = 'Dejavu Sans Condensed Bold 9'
	smallfont = 'Dejavu Sans Condensed Bold 7'

	showCityTime = True
	showForecast = True
	showTrayIcon = False
	showCityTimeBackground = True
	show24HourClock = False

	screenlet_width = 45
	screenlet_height = 33

	cityName = ""
	ZIP = _("USCA0035")
	use_metric = True

	latest = []
	p_layout = None 	
	# constructor
	def __init__(self, **keyword_args):
		#call super
		screenlets.Screenlet.__init__(self, height=self.screenlet_height, 
			**keyword_args)
		self.enable_buttons = False
		self.draw_buttons = False

		# set theme
		self.theme_name = "default"

		# add zip code menu item
		self.add_menuitem("zipcode", _("Zip Code..."))
		# add default menu items
		self.add_default_menuitems()

		# init the timeout function
		self.update_interval = self.update_interval

		# add option group
		# TODO: Hook it up to LIVE SOURCES
		# TODO: Edit Boxes entering Zipcode, Custom City Name
		#       Check Boxes for toggling Short/Expanded, City/Time display, Tray button
		#       Color selection boxes for Customizing Appearance
		# TODO: Look into Mouse Event Handling - Mouse click/over on widget -> animate open/close
		self.add_options_group(_('Appearance'), _('Adjust Appearance of the Widget'))
		self.add_options_group(_('Layout'), _('Adjust Layout and Behavior of the Widget'))
		self.add_options_group(_('Weather'), _('Settings for fetching Weather Information'))
		self.add_option(ColorOption(_('Appearance'), 'bgColor',
			self.bgColor, _('Background Color'),
			_('Background Color of the Widget')))
		self.add_option(ColorOption(_('Appearance'), 'borderColor',
			self.borderColor, _('Border Color'),
			_('Border Color of the Widget')))
		self.add_option(ColorOption(_('Appearance'), 'iconColor',
			self.iconColor, _('Icon Color'),
			_('Color of the Weather Icons')))
		self.add_option(ColorOption(_('Appearance'), 'textColor',
			self.textColor, _('Text Color'),
			_('Color of the Text')))
		self.add_option(FontOption(_('Appearance'), 'bigfont',
			self.bigfont, _('Bigger Font'),
			_('Font used for City and current temperature')))
		self.add_option(FontOption(_('Appearance'), 'smallfont',
			self.smallfont, _('Smaller Font'),
			_('Font used for forecast temperature')))
		self.add_option(BoolOption(_('Appearance'), 'showCityTimeBackground',
			self.showCityTimeBackground, _('Show Background for City and Time'),
			_('Show City and Time Background Color in the Widget ?')))
		self.add_option(BoolOption(_('Appearance'), 'show24HourClock',
			self.show24HourClock, _('Show 24 Hour Clock'),
			_('Show a 24 Hour Clock ?')))
		self.add_option(BoolOption(_('Layout'), 'showCityTime',
			self.showCityTime, _('Show City and Time'),
			_('Show City and Time in the Widget ?')))
		self.add_option(BoolOption(_('Layout'), 'showForecast',
			self.showForecast, _('Show Forecast'),
			_('Show Forecast in the Widget ?')))
		self.add_option(BoolOption(_('Layout'), 'showTrayIcon',
			self.showTrayIcon, _('Show Tray Icon'),
			_('Show Try Icon')))
		self.add_option(StringOption(_('Weather'), _('ZIP'), 
			str(self.ZIP), _('ZIP'), 
			_('The Zip code from weather.com')), realtime=False)
		self.add_option(BoolOption(_('Weather'), 'show_error_message', 
			bool(self.show_error_message), _('Show error messages'), 
			_('Show an error message on invalid location code')))
		self.add_option(BoolOption(_('Weather'), 'use_metric', 
			bool(self.use_metric), _('Use metric over imperial units'), 
			_('Use the metric system for measuring values')))

	def __setattr__(self, name, value):
		# call Screenlet.__setattr__ in baseclass (ESSENTIAL!!!!)
		screenlets.Screenlet.__setattr__(self, name, value)

		if name in ('bgColor', 'borderColor', 'iconColor', 'textColor'):
			self.redraw_canvas()
		elif name in ('bigfont', 'smallfont', 'showCityTime', 'showForecast', 
			'showTrayIcon', 'showCityTimeBackground', 'show24HourClock'):
			self.update_shape()
			self.redraw_canvas()

		if name == _("ZIP"):
			self.__dict__[name] = value
			gobject.idle_add(self.update_weather_data)
		if name == "update_interval":
			if value > 0:
				self.__dict__['update_interval'] = value
				if self.__timeout:
					gobject.source_remove(self.__timeout)
				self.__timeout = gobject.timeout_add(value * 1000, self.update)
			else:
				pass
		if name == "use_metric":
			self.update()


	def on_draw(self, ctx):
		# if theme is loaded
		if self.theme:
			weather = self.latest
			#self.window.set_keep_below(True)

			# set scale rel. to scale-attribute
			ctx.scale(self.scale, self.scale)

			if weather != []:
				if self.cityName != "":
					fetchedCityName = weather[0]["where"][:weather[0]["where"].find(',')]
					self.cityName = fetchedCityName
			else:
				self.cityName = _("Choose a Zip Code")

			maincellx = 0
			citytimewidth = 0
			maincellwidth = 48
			citywidth = 0
			timewidth = 0
			cityheight = 0

			curtime = ""
			if self.show24HourClock: curtime = time.strftime("%H:%M")
			else: curtime = time.strftime("%l:%M %P")

			if self.showCityTime:
				cx, cy, citywidth, cityheight = self.get_text_extents(ctx, self.cityName, self.bigfont);
				timewidth = self.get_text_width(ctx, curtime, self.smallfont)
				if citywidth > timewidth: citytimewidth = citywidth + 6
				else: citytimewidth = timewidth + 6
				maincellwidth += citytimewidth 

			self.screenlet_width = maincellwidth

			if not self.showCityTimeBackground:
				maincellx = citytimewidth

			self.draw_background(ctx, maincellx, maincellwidth, self.showForecast)

			if self.showCityTime:
				self.draw_text(ctx, self.cityName, citytimewidth-citywidth-3, 1, self.bigfont, self.textColor)
				self.draw_text(ctx, curtime, citytimewidth-timewidth-5, cityheight + 3, self.smallfont, self.textColor)

			# show Current and Today/Tonight's data
			if weather != []:
				self.draw_weather_icon(ctx, self.get_icon(int(weather[0]["icon"])), '', weather[0]["temp"], maincellwidth, 1)
			else:
				self.draw_weather_icon(ctx, "unknown", '', '', maincellwidth, 1)
				

			if self.showForecast:
				self.screenlet_width += 180
				if weather != []:
					if self.is_night(weather):
						self.draw_weather_icon(ctx, self.get_icon(int(weather[1]["nighticon"])), 
							_('Tonight'), weather[1]["low"], maincellwidth, 2)
					else:
						self.draw_weather_icon(ctx, self.get_icon(int(weather[1]["dayicon"])), 
							_('Today'), weather[1]["high"], maincellwidth, 2)
					for x in range(3):
						day = weather[x+2]["day"]
						self.draw_weather_icon(ctx, self.get_icon(int(weather[x+2]["dayicon"])), 
							weather[x+2]["day"], weather[x+2]["high"], maincellwidth, x+3)
				else:
					self.draw_weather_icon(ctx, "unknown", _('Today'), '', maincellwidth, 2)
					self.draw_weather_icon(ctx, "unknown", _('Monday'), '', maincellwidth, 3)
					self.draw_weather_icon(ctx, "unknown", _('Tuesday'), '', maincellwidth, 4)
					self.draw_weather_icon(ctx, "unknown", _('Wednesday'), '', maincellwidth, 5)

			if self.width != self.screenlet_width:
				self.width = self.screenlet_width


	def on_draw_shape(self, ctx):
		#self.on_draw(ctx)
		print self.screenlet_width
		print self.screenlet_height
		ctx.scale(self.scale, self.scale)
		ctx.set_source_rgba(1,1,1,1)
		ctx.rectangle(0,0,self.screenlet_width,self.screenlet_height)
		ctx.paint()

	def on_mouse_down(self, event):
		if event.type == gtk.gdk._2BUTTON_PRESS: 
			self.showForecast = not self.showForecast
			if self.window:
				self.redraw_canvas()
				return True
		return False
	
	def draw_weather_icon(self, ctx, iconname, dayname, temp, w, index):
		# Draw the weather icon
		self.draw_scaled_colorized_pixmap(ctx,iconname+'.png', w + 45*(index-2), 0, 44, 33, self.iconColor)
		# Display the Day
		if dayname != "":
			self.draw_scaled_colorized_pixmap(ctx,_('Day-')+dayname+'.png', w + 45*(index-2), 24, 28, 8, self.textColor)
		# Display the Temperature
		if temp != "": 
			temp = temp+u"° "
			if index == 1:
				width = self.get_text_width(ctx, temp, self.bigfont)
				self.draw_text(ctx, temp, w + 45*(index-1)-22-width, 1, self.bigfont, self.textColor)
			else:
				width = self.get_text_width(ctx, temp, self.smallfont)
				self.draw_text(ctx, temp, w + 45*(index-1)-22-width, 2, self.smallfont, self.textColor)

	def draw_background(self, ctx, x, w, isOpen):
		p = ""
		if self.scale <= 1.0: p = "Small"
		# render Main Cell Background
		self.draw_scaled_colorized_pixmap(ctx,'BackgroundLeft'+p+'.png', x, 0, 1, 33, self.bgColor)
		self.draw_scaled_colorized_pixmap(ctx,'Background'+p+'.png', x+1, 0, w-x-1, 33, self.bgColor)
		self.draw_scaled_colorized_pixmap(ctx,'BackgroundRight'+p+'.png', w-1, 0, 1, 33, self.bgColor)
		# render Forecast Background
		if isOpen:
			self.draw_scaled_colorized_pixmap(ctx,'BackgroundOpen'+p+'.png', w, 0, 180, 33, self.bgColor)
		# render Main Cell Border
		self.draw_scaled_colorized_pixmap(ctx,'BorderLeft'+p+'.png', x, 0, 1, 33, self.borderColor)
		self.draw_scaled_colorized_pixmap(ctx,'Border'+p+'.png', x+1, 0, w-x-1, 33, self.borderColor)
		self.draw_scaled_colorized_pixmap(ctx,'BorderRight'+p+'.png', w-1, 0, 1, 33, self.borderColor)
		# render Forecast Border
		if isOpen:
			self.draw_scaled_colorized_pixmap(ctx,'BorderOpen'+p+'.png', w, 0, 180, 33, self.borderColor)
		# draw Tray Open/Close Button
		if self.showTrayIcon:
			self.draw_tray_button(ctx, w, isOpen)

	def draw_tray_button(self, ctx, w, isOpen):
		status = "closed"
		if isOpen: status = "open"
		self.draw_scaled_colorized_pixmap(ctx,'trayButton-'+status+'.png', w - 10, 22, 9, 9, self.iconColor)
	
	def draw_colorized_pixmap(self, ctx, pixmap, x, y, color):
		#ctx.move_to(x,y);
		#ctx.set_source_surface(self.theme[pixmap], 0, 0)
		#ctx.paint()
		ctx.move_to(x,y)
		ctx.set_source_rgba(color[0], color[1], color[2], color[3])
		ctx.mask_surface(self.theme[pixmap], 0, 0)
		ctx.stroke()

	def draw_scaled_colorized_pixmap(self, ctx, pixmap, x, y, w, h, color):
		# Scale the pixmap
		iw = float(self.theme[pixmap].get_width())
		ih = float(self.theme[pixmap].get_height())
		matrix = cairo.Matrix(xx=iw/w, yy=ih/h)
		matrix.translate(-x, -y)
		pattern = cairo.SurfacePattern(self.theme[pixmap])
		pattern.set_matrix(matrix)
		# Make the pixmap a mask
		ctx.move_to(x,y)
		ctx.set_source_rgba(color[0], color[1], color[2], color[3])
		ctx.mask(pattern)
		ctx.stroke()
	
	def get_text_width(self, ctx, text, font):
		ctx.move_to(0,0)
		if self.p_layout == None :
	
			self.p_layout = ctx.create_layout()
		else:
		
			ctx.update_layout(self.p_layout)
		p_fdesc = pango.FontDescription(font)
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_text(text)
		extents, lextents = self.p_layout.get_pixel_extents()
		return extents[2]

	def get_text_extents(self, ctx, text, font):
		ctx.move_to(0,0)
		if self.p_layout == None :
	
			self.p_layout = ctx.create_layout()
		else:
		
			ctx.update_layout(self.p_layout)
		p_fdesc = pango.FontDescription(font)
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_text(text)
		extents, lextents = self.p_layout.get_pixel_extents()
		return extents

	def draw_text(self, ctx, text, x, y, font, color):
		if self.p_layout == None :
	
			self.p_layout = ctx.create_layout()
		else:
		
			ctx.update_layout(self.p_layout)
		p_fdesc = pango.FontDescription(font)
		self.p_layout.set_font_description(p_fdesc)
		self.p_layout.set_text(text)
		extents, lextents = self.p_layout.get_pixel_extents()

		ctx.move_to(x,y)
		ctx.set_source_rgba(color[0], color[1], color[2], color[3])
		ctx.show_layout(self.p_layout)


	# Functions From Weather Screenlet

	def update(self):
		gobject.idle_add(self.update_weather_data)
		self.update_shape()
		return True


	def update_weather_data(self):
		temp = self.parseWeatherData()
		
		if temp[0]["where"] == '':    ##did we get any data?  if not...
			if self.show_error_message==1 and self.updated_recently == 1:
				self.show_error()
			self.updated_recently = 0
		else:
			#if temp[0]["where"].find(',') > -1:
			#	temp[0]["where"] = temp[0]["where"][:temp[0]["where"].find(',')]
			self.latest = temp
			self.updated_recently = 1
			self.redraw_canvas()


	def parseWeatherData(self):
		if self.use_metric:
			unit = 'm'
		else:
			unit = 's'
		data = urlopen('http://xoap.weather.com/weather/local/'+self.ZIP+'?cc=*&dayf=10&prod=xoap&par=1003666583&key=4128909340a9b2fc&unit='+unit + '&link=xoap').read()
		forecast = []

		dcstart = data.find('<loc ')
		dcstop = data.find('</cc>')     ###### current conditions
		data_current = data[dcstart:dcstop]
		forecast.append(self.tokenizeCurrent(data_current))

		for x in range(10):
			dcstart = data.find('<day d=\"'+str(x))
			dcstop = data.find('</day>',dcstart)   #####10-day forecast
			day = data[dcstart:dcstop]
			forecast.append(self.tokenizeForecast(day))

		return forecast


	def tokenizeForecast(self, data):
		day = self.getBetween(data, '<part p="d">', '</part>')
		daywind = self.getBetween(day, '<wind>', '</wind>')
	
		night = self.getBetween(data, '<part p="n">', '</part>')
		nightwind = self.getBetween(night, '<wind>', '</wind>')

		tokenized = {
		'date': self.getBetween(data, 'dt=\"','\"'),
		'day' : self.getBetween(data, 't=\"','\"'),
		'high': self.getBetween(data, '<hi>','</hi>'),
		'low': self.getBetween(data, '<low>','</low>'),	
		'sunr': self.getBetween(data, '<sunr>','</sunr>'),
		'suns' : self.getBetween(data, '<suns>','</suns>'),		
		'dayicon' : self.getBetween(day, '<icon>','</icon>'), 
		'daystate' : self.getBetween(day, '<t>','</t>'), 
		'daywindspeed' : self.getBetween(daywind, '<s>','</s>'), 
		'daywinddir' : self.getBetween(daywind, '<t>','</t>'), 
		'dayppcp' : self.getBetween(day, '<ppcp>','</ppcp>'), 
		'dayhumid' : self.getBetween(day, '<hmid>','</hmid>'),
		'nighticon' : self.getBetween(night, '<icon>','</icon>'), 
		'nightstate' : self.getBetween(night, '<t>','</t>'), 
		'nightwindspeed' : self.getBetween(nightwind, '<s>','</s>'), 
		'nightwinddir' : self.getBetween(nightwind, '<t>','</t>'), 
		'nightppcp' : self.getBetween(night, '<ppcp>','</ppcp>'), 
		'nighthumid' : self.getBetween(night, '<hmid>','</hmid>'),
		}
		return tokenized

	
	def tokenizeCurrent(self, data):
		wind = self.getBetween(data, '<wind>', '</wind>')
		bar = self.getBetween(data, '<bar>', '</bar>')
		uv = self.getBetween(data, '<uv>', '</uv>')

		tokenized = {
		'where': self.getBetween(data, '<dnam>','</dnam>'),
		'time' : self.getBetween(data, '<tm>','</tm>'),
		'sunr': self.getBetween(data, '<sunr>','</sunr>'),
		'suns' : self.getBetween(data, '<suns>','</suns>'),	
		'date' : self.getBetween(data, '<lsup>','</lsup>'),
		'temp' : self.getBetween(data, '<tmp>','</tmp>'),	
		'flik' : self.getBetween(data, '<flik>','</flik>'), 
		'state' : self.getBetween(data, '<t>','</t>'), 
		'icon' : self.getBetween(data, '<icon>','</icon'),
		'pressure' : self.getBetween(data, '<r>','</r>'),
		'windspeed' : self.getBetween(wind, '<s>','</s>'), 
		'winddir' : self.getBetween(wind, '<t>','</t>'), 
		'humid' : self.getBetween(data, '<hmid>','</hmid>'),
		'vis' : self.getBetween(data, '<vis>','</vis>'),
		'dew' : self.getBetween(data, '<dewp>','</dewp>'),
		}
		return tokenized		


	def getBetween(self, data, first, last):
		x = len(first)
		begin = data.find(first) +x
		end = data.find(last, begin)
		return data[begin:end]


	def get_icon(self, code):
		if code in (0, 3, 4, 17, 35, 47):
			weather = "thunderstorms"
		elif code in (1, 2):
 			weather = "windyshowers"
		elif code in (5, 7):
			weather = "icysnowyrain"
		elif code in (6, 18):
			weather = "sleet"
		elif code == 8:
			weather = "icydrizzle"
		elif code == 9:
			weather = "drizzle"
		elif code ==10:
		   weather = "icyrain"
		elif code in (11, 40):
		   weather = "showers"
		elif code == 12 :
		   weather = "rain"
		elif code == 13 :
		   weather = "lightsnowflurries"
		elif code == 14 :
		   weather = "medsnow"
		elif code == 25 :
 			weather = "fridged"
		elif code in (15,43):
 		   weather = "windysnow"
		elif code in (16,41,42):
		   weather = "normalsnow"
		elif code in (19,20,21,22):
		   weather = "fog"
		elif code in (23, 24):
		   weather = "windy"
		elif code == 26 :
		   weather = "cloudy"
		elif code == 27:
			weather = "mostlycloudynight"
		elif code == 28:
			weather = "mostlycloudyday"
		elif code in (29, 33):
			weather = "partiallycloudynight"
		elif code in (30, 34):
			weather = "partiallycloudyday"
		elif code == 31:
			weather = "clearnight"
		elif code == 32:
			weather = "clearday"
		elif code == 36:
			weather = "hot"
		elif code in (37,38):
			weather = "sunnythunderstorm"
		elif code == 39:
			weather = "sunnyshowers"
		elif code == 44:
			weather = "partiallycloudyday"
		elif code == 45:
			weather = "nightrain"
		elif code == 46:
			weather = "unknown"
		else:
			weather = "unknown"
		return weather
		
	def menuitem_callback(self, widget, id):
		screenlets.Screenlet.menuitem_callback(self, widget, id)
		if id=="zipcode":
			self.show_edit_dialog()
			self.update()

	def show_edit_dialog(self):
		# create dialog
		dialog = gtk.Dialog(_("Zip Code"), self.window)
		dialog.resize(300, 100)
		dialog.add_buttons(gtk.STOCK_OK, gtk.RESPONSE_OK, 
			gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
		entrybox = gtk.Entry()
		entrybox.set_text(str(self.ZIP))
		dialog.vbox.add(entrybox)
		entrybox.show()	
		# run dialog
		response = dialog.run()
		if response == gtk.RESPONSE_OK:
			self.ZIP = entrybox.get_text()
			self.updated_recently = 1
		dialog.hide()
		self.update()


	def is_night(self, weather):
		time = weather[0]["time"].split()[0]
		ampm = weather[0]["time"].split()[1]
		sunset = weather[0]["suns"].split()[0]
		sunrise = weather[0]["sunr"].split()[0]

		hour = time.split(':')[0]
		min = time.split(':')[1]
		risehr = sunrise.split(':')[0]
		risemin = sunrise.split(':')[1]
		sethr = sunset.split(':')[0]
		setmin = sunset.split(':')[1]

		if int(hour) == 12:
			hour = 0
		if ampm == _("AM") :
		        if int(risehr) > int(hour) :
		                dark = 1
		        elif int(risehr) < int(hour) :
				dark = 0
		        else :
		                if int(risemin) > int(min) :
        	                	dark = 1
      		          	elif int(risemin) < int(min) :
       	 	                	dark = 0
         		        else :
           				dark = -1

		elif ampm == _("PM") :
		        if int(sethr) > int(hour) :
		                dark = 0
		        elif int(sethr) < int(hour) :
		                dark = 1
		        else :
		                if int(setmin) > int(min) :
		                        dark = 0
		                elif int(setmin) < int(min) :
		                        dark = 1
		                else :
		                        dark = -1
		return dark


	def show_error(self):

		dialog = gtk.Dialog(_("Zip Code"), self.window)
		dialog.resize(300, 100)
		dialog.add_buttons(gtk.STOCK_OK, gtk.RESPONSE_OK)

		label = gtk.Label(_("Could not reach weather.com.  Check your internet connection and location and try again."))
		dialog.vbox.add(label)
		check = gtk.CheckButton(_("Do not show this again"))
		dialog.vbox.add(check)
		dialog.show_all()
		response = dialog.run()
		if response == gtk.RESPONSE_OK:
			if check.get_active() == True:
				self.show_error_message = 0			
			dialog.hide()

# If the program is run directly or passed as an argument to the python
# interpreter then create a Screenlet instance and show it
if __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(WidescapeWeatherScreenlet)
