#!/usr/bin/env python

#    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/>.

#WeatherScreenlet (c) Whise <helder.fraga@hotmail.com>

import re
from urllib import urlopen
import screenlets
from screenlets.options import StringOption, BoolOption
from Numeric import *
import pygtk
pygtk.require('2.0')
import cairo
import pango
import sys
import gobject
import time
import datetime
import math
import gtk
from gtk import gdk
import os
# use gettext for translation
import gettext

_ = screenlets.utils.get_translator(__file__)

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

@tdoc

class WeatherScreenlet(screenlets.Screenlet):
	"""A Weather Screenlet modified from the original to look more clear and to enable the use of icon pack , you can use any icon pack compatible with weather.com , you can find many packs on deviantart.com or http://liquidweather.net/icons.php#iconsets."""
	
	# default meta-info for Screenlets
	__name__ = 'WeatherScreenlet'
	__version__ = '0.5'
	__author__ = 'by Helder Fraga (Whise), robgig1088, and blackhawk'
	__desc__ = _('A weather widget for the Screenlets core suit.')

	# internals
	__timeout = None

	update_interval = 300
	show_error_message = 1

	lasty = 0
	lastx = 0   ## the cursor position inside the window (is there a better way to do this??)
	over_button = 1

	ZIP = "12345"
	use_metric = True
	show_daytemp = True
	mini = False
	
	latest = []          ## the most recent settings we could get...
	latestHourly = []

	updated_recently = 0 ## don't keep showing the error messages until a connection has been established
			     ## and then lost again.
	p_layout = None
	# constructor
	def __init__(self, text="", **keyword_args):
		#call super (and not show window yet)
		screenlets.Screenlet.__init__(self, width=int(220 * self.scale), height=int(150 * self.scale),uses_theme=True, **keyword_args) 
		# set theme
		self.theme_name = "default"
		# add zip code menu item 
		self.add_menuitem("zipcode", _("Zip Code..."))
		self.add_menuitem("mini", _("Toggle mini-view"))
		# init the timeout function
		self.update_interval = self.update_interval
                self.add_options_group(_('Weather'),
                        _('The weather widget settings'))
                self.add_option(StringOption(_('Weather'), _('ZIP'),
                        str(self.ZIP), _('ZIP'), _('The ZIP code to be monitored taken 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 celsius temperature '), 
			_('Use the metric system for measuring values')))
		self.add_option(BoolOption(_('Weather'), 'mini',
			bool(self.mini), _('Use mini-mode'),
			_('Switch to the mini-mode')))
		self.add_option(BoolOption(_('Weather'), 'show_daytemp',
			bool(self.show_daytemp), _('Show 6 day temperature'),
			_('Show 6 day temperature high/low')))

	
	# attribute-"setter", handles setting of attributes
	def __setattr__(self, name, value):
		# call Screenlet.__setattr__ in baseclass (ESSENTIAL!!!!)
		screenlets.Screenlet.__setattr__(self, name, value)
		# check for this Screenlet's attributes, we are interested in:
		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:
				# TODO: raise exception!!!
				pass

	def on_init (self):
		print "Screenlet has been initialized."
		# add default menuitems
		self.add_default_menuitems()	

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


	def update_weather_data(self):
		temp = self.parseWeatherData()
		temp2 = self.parseWeatherDataHourly()
		
		if len(temp) == 0 or 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:
			self.latest = temp
			self.latestHourly = temp2
			self.updated_recently = 1
			self.redraw_canvas()


	def parseWeatherData(self):
		if self.use_metric:
			unit = 'm'
		else:
			unit = 's'

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

			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(12):
				dcstart = data.find('<day d=\"'+str(x))
				dcstop = data.find('</day>',dcstart)   #####10-day forecast
				day = data[dcstart:dcstop]
				forecast.append(self.tokenizeForecast(day))
		except IOError, e:
			print "Error retreiving Weather Data", e

		return forecast


	def parseWeatherDataHourly(self):
		if self.use_metric:
			unit = 'm'
		else:
			unit = 's'

		hforecast = []
		try:
			data = urlopen('http://xoap.weather.com/weather/local/'+self.ZIP+'?cc=*&dayf=10&prod=xoap&par=1003666583&key=4128909340a9b2fc&unit='+unit+'&hbhf=12&link=xoap').read()
			for x in range(8):
				dcstart = data.find('<hour h=\"'+str(x))
				dcstop = data.find('</hour>',dcstart)   ####hourly forecast
				hour = data[dcstart:dcstop]
				hforecast.append(self.tokenizeForecastHourly(hour))
		except IOError, e:
			print "Error retrieving weather data", e

		return hforecast


	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 tokenizeForecastHourly(self, data):
		tokenized = {
		'hour' : self.getBetween(data, 'c=\"','\"'),
		'tmp': self.getBetween(data, '<tmp>','</tmp>'),
		'flik': self.getBetween(data, '<flik>','</flik>'),
		'icon': self.getBetween(data, '<icon>','</icon>')
		}
		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 < 3200:
			weather = str(code)
		
		elif code == 3200:
			weather = "na"
		return weather


	def get_day_or_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
		if dark == 1:
			return "moon"
		else:
			return "sun"


	def on_draw(self, ctx):
		weather = self.latest
		hourly = self.latestHourly
                p_layout = None
                p_layout1 = None
		# set size
		ctx.scale(self.scale, self.scale)
		# draw bg (if theme available)
		ctx.set_operator(cairo.OPERATOR_OVER)
		ctx.translate(15, 15) #icon overhang allowance
		if self.theme:
			if (self.mini == False and weather != []):
				self.theme['weather-bg.svg'].render_cairo(ctx)
			else:
				self.theme['weather-bg-mini.svg'].render_cairo(ctx)
		# draw memory-graph
		if self.theme:
			ctx.set_source_rgba(1, 1, 1, 1)

			if weather == []:
				ctx.save()	
				ctx.translate(20, 25) #no info error message location
				if self.p_layout == None : 
                               		self.p_layout = ctx.create_layout() 
                                else: 
                                 	ctx.update_layout(self.p_layout) 
				p_fdesc = pango.FontDescription()
				p_fdesc.set_family_static("Sans")
				p_fdesc.set_size(6 * pango.SCALE)
				p_fdesc.set_weight(300)
				p_fdesc.set_style(pango.STYLE_NORMAL)   ####render no info message
				self.p_layout.set_font_description(p_fdesc)
				self.p_layout.set_markup('<b>No weather information available</b>')						
				ctx.show_layout(self.p_layout)
				ctx.restore()
			else:
				ctx.save()
				ctx.translate(0, -15) #current weather icon location
				ctx.scale(1.5,1.5) #current weather icon size
				if weather[0]["icon"]=="-": weather[0]["icon"]="48"
				icon = str(self.get_icon(int(weather[0]["icon"])) )
				self.theme.render(ctx,icon)
				ctx.restore()

				ctx.save()
				ctx.translate(140,5) #current temperature location
				degree = unichr(176)
				if self.p_layout == None : 
                               		self.p_layout = ctx.create_layout() 
                                else: 
                                 	ctx.update_layout(self.p_layout)
				p_fdesc = pango.FontDescription()
				p_fdesc.set_family_static("Sans")
				p_fdesc.set_size(20 * pango.SCALE)
				p_fdesc.set_weight(300)
				p_fdesc.set_style(pango.STYLE_NORMAL)
				self.p_layout.set_font_description(p_fdesc)   ######render current temp
				if len(str(weather[0]["temp"])) == 3:
					ctx.translate(-7, 0)
				self.p_layout.set_markup(weather[0]["temp"] + degree)							
				ctx.show_layout(self.p_layout)
				ctx.restore()

				ctx.save()	
				ctx.translate(70,33) # city name location
				if self.p_layout == None : 
                               		self.p_layout = ctx.create_layout() 
                                else: 
                                 	ctx.update_layout(self.p_layout)
				p_fdesc = pango.FontDescription()
				p_fdesc.set_family_static("Sans")
				p_fdesc.set_size(8 * pango.SCALE)
				self.p_layout.set_font_description(p_fdesc)      ### draw location
				self.p_layout.set_width(int((102* pango.SCALE )))
				self.p_layout.set_alignment(pango.ALIGN_RIGHT)
				self.p_layout.set_markup(weather[0]["where"][:weather[0]["where"].find(',')][:15])

				ctx.show_layout(self.p_layout)
				ctx.restore()	
				ctx.save() 
				ctx.restore()
				
				if (self.mini == False):
					ctx.save() 
					ctx.translate(8, 63) #days background bar location
					self.theme['day-bg.svg'].render_cairo(ctx)   ###render the days background
					if self.p_layout == None : 
                               			self.p_layout = ctx.create_layout() 
                                        else: 
                                 		ctx.update_layout(self.p_layout) 
					p_fdesc = pango.FontDescription()
					p_fdesc.set_family_static("Monospace")
					p_fdesc.set_size(7 * pango.SCALE)
					p_fdesc.set_weight(300)    ##### render the days of the week
					p_fdesc.set_style(pango.STYLE_NORMAL)
					self.p_layout.set_font_description(p_fdesc)
					ctx.translate(10,-13)
					self.p_layout.set_markup('<b>' +weather[1]["day"][:3] + '</b>')
					ctx.show_layout(self.p_layout)
					ctx.translate(37, 0)
					self.p_layout.set_markup('<b>' +weather[2]["day"][:3] + '</b>')						
					ctx.show_layout(self.p_layout)
					ctx.translate(37, 0)
					self.p_layout.set_markup('<b>' +weather[3]["day"][:3] + '</b>')	
					ctx.show_layout(self.p_layout)
					ctx.translate(37, 0)
					self.p_layout.set_markup('<b>' +weather[4]["day"][:3] + '</b>')
					ctx.show_layout(self.p_layout)
					ctx.translate(37, 0)
					self.p_layout.set_markup('<b>' +weather[5]["day"][:3] + '</b>')	
					ctx.show_layout(self.p_layout)

					ctx.restore()	

					ctx.save()
					ctx.translate(15, 67)
					ctx.scale(.5, .5)
					self.theme.render(ctx,self.get_icon(int(weather[1]["nighticon"])))				
					ctx.translate(72,0)
					self.theme.render(ctx,self.get_icon(int(weather[2]["dayicon"])))	
					ctx.translate(72,0)
					self.theme.render(ctx,self.get_icon(int(weather[3]["dayicon"])))	
					ctx.translate(72,0)
					self.theme.render(ctx,self.get_icon(int(weather[4]["dayicon"])))				
					ctx.translate(72,0)
					self.theme.render(ctx,self.get_icon(int(weather[5]["dayicon"])))	
					ctx.save()
					ctx.restore()
					ctx.restore()						

					if self.p_layout == None : 
                               			self.p_layout = ctx.create_layout() 
                                        else: 
                                 		ctx.update_layout(self.p_layout)
					p_fdesc = pango.FontDescription()
					p_fdesc.set_family_static("Monospace")
					p_fdesc.set_size(int(6 * pango.SCALE))
					p_fdesc.set_weight(300)    ###note:: this font needs to be set only once for the next few 
					p_fdesc.set_style(pango.STYLE_NORMAL)  #things we need to do, so I'll do it here.
					self.p_layout.set_font_description(p_fdesc)
		
					if self.show_daytemp == True:
						ctx.save()
						ctx.translate(18,97)
						self.p_layout.set_markup('<b>' + weather[1]["high"]+degree+'</b>')
						ctx.show_layout(self.p_layout)
						ctx.translate(0,12)
						self.p_layout.set_markup(weather[1]["low"]+degree)
						ctx.show_layout(self.p_layout)

						ctx.translate(37, -12)
						self.p_layout.set_markup('<b>' + weather[2]["high"]+degree+'</b>')
						ctx.show_layout(self.p_layout)
						ctx.translate(0,12)
						self.p_layout.set_markup(weather[2]["low"]+degree)
						ctx.show_layout(self.p_layout)

						ctx.translate(37, -12)
						self.p_layout.set_markup('<b>' + weather[3]["high"]+degree+'</b>')
						ctx.show_layout(self.p_layout)
						ctx.translate(0,12)
						self.p_layout.set_markup(weather[3]["low"]+degree)
						ctx.show_layout(self.p_layout)

						ctx.translate(37, -12)
						self.p_layout.set_markup('<b>' + weather[4]["high"]+degree+'</b>')
						ctx.show_layout(self.p_layout)
						ctx.translate(0,12)
						self.p_layout.set_markup(weather[4]["low"]+degree)
						ctx.show_layout(self.p_layout)

						ctx.translate(37, -12)
						self.p_layout.set_markup('<b>' + weather[5]["high"]+degree+'</b>')
						ctx.show_layout(self.p_layout)
						ctx.translate(0,12)
						self.p_layout.set_markup(weather[5]["low"]+degree)
						ctx.show_layout(self.p_layout)
						ctx.restore()
						ctx.save()
				
	def on_mouse_down(self,event):
		if event.button == 1:
			os.system('xdg-open weather.com')
			#x = event.x / self.scale
			#y = event.y / self.scale
			#if y >= 75 and x <= 132 and x >= 110:

	def on_draw_shape(self,ctx):
		if self.theme:
			# set size rel to width/height
			self.on_draw(ctx)

	def menuitem_callback(self, widget, id):
		screenlets.Screenlet.menuitem_callback(self, widget, id)
		if id=="zipcode":
			self.show_edit_dialog()
			self.update()
		if id == "mini":
			self.mini = not self.mini
			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 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 __name__ == "__main__":
	import screenlets.session
	screenlets.session.create_session(WeatherScreenlet)
