#!/usr/bin/env python
import cgi
import md5
import time
import urllib
import cgitb; cgitb.enable()
import cStringIO
import copy
from math import *
import simplejson
import sys
import tiles


dowURI = "http://irc.peeron.com/xkcd/map/data"
print "Content-type: text/html\n"

def hex2fract(hexStr):
	"""transform a hex string into a fraction...so 0.hexStr"""
	fRet = 0
	hexList = list(hexStr)
	hexList.reverse()
	for x in hexList:
		fRet += int(x,16)
		fRet /= 16.0
	
	return fRet

def geoHash(date,dow,(xLoc,yLoc)):
	"""Work out the geohash for a given geoHashStr and current global location"""
	# Make the string and clean it up
	geohashStr = "%s-%s"%(date,dow)
	geohashStr = geohashStr.strip()
	
	# md5
	fullHex=md5.md5(geohashStr).hexdigest()
	
	# split 'n' float-inate
	h1 = str(hex2fract(fullHex[len(fullHex)/2:]))
	h2 = str(hex2fract(fullHex[:len(fullHex)/2]))
	
	# dance
	xBit = "%+f"%(xLoc)
	yBit = "%+f"%(yLoc)
	
	xBit = xBit[0:xBit.index(".")]
	yBit = yBit[0:yBit.index(".")]
	return {"long":float("%s%s"%(xBit,h1[1:])),"lat":float("%s%s"%(yBit,h2[1:]))}

def validityInfo(location , threshold=4.0):
	"""
	Get some validity information, trys to calssify the place located and tell you if its a valid place for a meet
	This is a fairly trivial validity check, we should add more! :)
	"""
	# Put your google key here (this is the one for 127.0.0.1)
	#googleMapsKey = "ABQIAAAA5-a-EvcnxDG8-DGOekgl-RRi_j0U6kJrkFvY4-OX2XYmEAa76BTEPzhg5GwgdRaPj8_XtPBIyG6xXQ"
	googleMapsKey = "ABQIAAAA5-a-EvcnxDG8-DGOekgl-RTmTTfHc8sMlwr5QmS9HetPY_a1hBSqdG_gqZKbkU_vujE0Fp_W7SX3aA"
	
	
	# Get the tile XY from the lat long
	z = 10 #the zoom level
	x,y = tiles.tileXY(location['lat'],location['long'],z)
	s,w,n,e = tiles.tileEdges(x,y,z)
	w,n = tiles.tileXY(w,n,z)
	mapURI = tiles.tileURL(x,y,z,"mapnik")
	x,y = abs(int(x-w)),abs(int(y-y))
	mapURI = "http://maps.google.com/staticmap?center=%s,%s&zoom=15&size=300x300&key=%s"%(location['lat'],location['long'],googleMapsKey)
	x,y = 150,150
	visualMapURI = "http://maps.google.com/staticmap?center=%s,%s&zoom=8&size=300x300&key=%s"%(location['lat'],location['long'],googleMapsKey)
	visualMapURI = "%s&markers=%s,%s,blues"%(visualMapURI,location['lat'],location['long'])
	visualURL = "http://maps.google.co.uk/maps?q=%s,%s"%(location['lat'],location['long'])
	try:
		from PIL import Image
	except ImportError:
		return {"name":"DUNNO, no imaging library, im blind!","valid":-1,"map":visualMapURI}
	
	
	# Big fat list of colours and their validity, only water i can think of for now
	types = {
	"WATER":{"colour":(152, 179, 204),"valid":0}
	}
	
	# Some debugging stuff 
	
	#print mapURI
	#print """<img src="%s"/>"""%mapURI
	
	# I use the PIL library to make with the image go
	mapURL = urllib.urlopen(mapURI)
	im = cStringIO.StringIO(mapURL.read()) # constructs a StringIO holding the image
	img = Image.open(im).convert("RGB")
	
	# Grab the middle pixel
	m = img.getpixel((x,y))
	
	# Default is that we don't know
	possibleType = "NOT WATER"
	previousBest = None
	# For every type of terrain find the euclidian distance and see if its below the threshold AND the previous best, if so classify
	for tName in types:
		t = types[tName]['colour']
		
		dist = sqrt(sum([(t[i] - m[i])**2 for i in range(len(t))]))
		#print "Here is the pixel colour i'm testing: %s and distance %s to %s<br/>"%(list(t),dist,tName)
		
		if(dist < threshold and (previousBest == None or previousBest > dist)):
			possibleType = tName
	
	types['NOT WATER'] = {"colour":m,"valid":1}
	return {"name":possibleType,
			"valid":types[possibleType]["valid"],
			"colour":"%s"%(list(types[possibleType]["colour"])),
			"colourURL":mapURI,
			"map":visualMapURI,
			"mapURL":visualURL}

def closestValid((oX,oY),loc):
	"""Find the closes valid location from the original.
	   - Checks all around in a 3x3 square centered at the original coords
	   - Uses a euclidian distance
	   - Performs foo to guarantee no stinky Lat/Long based overflow"""
	locations = [
	(-1,-1),( 0,-1),( 1,-1),
	(-1, 0),( 0, 0),( 1, 0),
	(-1, 1),( 0, 1),( 1, 1)
	]
	(rX,rY) = (abs(loc['long']-int(loc['long'])),abs(loc['lat']-int(loc['lat'])))
	sD = None
	s = None
	for (x,y) in locations:
		# euclidian distance
		xBit = "%+f"%(oX+x)
		yBit = "%+f"%(oY+y)
		xBit = "%s%s"%(xBit[0:xBit.index(".")],("%s"%rX)[1:])
		yBit = "%s%s"%(yBit[0:yBit.index(".")],("%s"%rY)[1:])
		xBit = float(xBit)
		yBit = float(yBit)
		d = sqrt( ((xBit - oX)**2) + ((yBit - oY)**2) )
		locationInfo = validityInfo({"long":xBit,"lat":yBit})
		# If its the smallest distance and a valid location 
		if((sD == None or sD > d ) and not locationInfo['valid'] == 0):
			
			sD = d
			s = copy.deepcopy(locationInfo)
			s['location'] = {"long":xBit,"lat":yBit}
	
	return s

fields =  cgi.FieldStorage()

# Grab the xloc/yloc
# Defaults to southampton england
xLoc = -2
yLoc = 50

if(fields.has_key("xloc")):
	xLoc = fields['xloc'].value

if(fields.has_key("yloc")):
	yLoc = fields['yloc'].value

originalLoc = (float(xLoc),float(yLoc))


# Grab the date, use it to get the DOW from the XKCD cache
# Defaults to today

date = time.localtime()
if(fields.has_key("date")):
	date = time.strptime(fields['date'].value,"%Y-%m-%d")

if(xLoc >= -30):
	# Make it W30 compatible!
	date = time.localtime(time.mktime(date) - (60*60*24))

year = str(date[0])
month = "%02d"%date[1]
day = "%02d"%date[2]

date = "%s-%s-%s"%(year,month,day)
#dowURI = "%s"%"/".join([dowURI,year,month,day])
dowURI = "http://staticfree.info/geohash/dji/%s-%s-%s"%(year,month,day)
dow = urllib.urlopen(dowURI)
dow = dow.read()

try:
	float(dow)
except Exception:
	output = {"failed": True, "reason": "No DOW index for date: %s. Sorry :("%date}
	print simplejson.dumps(output)
	sys.exit()


# Grab the location, information about it and search for the alternative "best" location
location = geoHash(date,dow,originalLoc)
info = validityInfo(location)
closesValid = closestValid(originalLoc,location)

output = {"failed": False,"location":location,"date":date,"information":info,"best":closesValid}
print simplejson.dumps(output)
sys.exit()

