#===================================
#		   preferences
#  Copyright 2011 - Nathan Osman
#
#  A web server that allows prefs.
#   to be changed from within the
#         user's browser.
#
#   StackApplet is released under
#		the MIT license
#===================================

# For providing HTML / JS translations
import js_translations
import defaults

import BaseHTTPServer
import os
import platform
import shutil
import sys
import threading
import urlparse
import uuid
import webbrowser

import gobject
gobject.threads_init()

from stackapplet import CAN_USE_MM
import defaults

# Now lets see if we can import the JSON
# module. We try either of these two classes
try:
	import json
except ImportError:
	import simplejson as json

# This is the callback that the main thread would like
# us to use whenever we need to issue a request for data
# from the main thread
main_thread_callback = None

def register_callback(callback):
	
	global main_thread_callback
	main_thread_callback = callback

# Here is a list of files that we can
# serve from the web server
file_list = ['/bg.png',
             '/dark_theme.png',
             '/favicon.ico',
             '/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png',
             '/images/ui-bg_flat_15_cd0a0a_40x100.png',
             '/images/ui-bg_glass_100_e4f1fb_1x400.png',
             '/images/ui-bg_glass_50_3baae3_1x400.png',
             '/images/ui-bg_glass_80_d7ebf9_1x400.png',
             '/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png',
             '/images/ui-bg_highlight-hard_70_000000_1x100.png',
             '/images/ui-bg_highlight-soft_100_deedf7_1x100.png',
             '/images/ui-bg_highlight-soft_25_ffef8f_1x100.png',
             '/images/ui-icons_2694e8_256x240.png',
             '/images/ui-icons_2e83ff_256x240.png',
             '/images/ui-icons_3d80b3_256x240.png',
             '/images/ui-icons_72a7cf_256x240.png',
             '/images/ui-icons_ffffff_256x240.png',
             '/index.html',
             '/jquery.js',
             '/jqueryui.css',
             '/jqueryui.js',
             '/light_theme.png',
             '/main_logo.png',
             '/translations.js']

AUTOSTART_FILE_PATH = os.path.join(os.path.expanduser("~"), ".config/autostart")
AUTOSTART_FILE_NAME = os.path.join(AUTOSTART_FILE_PATH, "stackapplet.desktop")

MM_FILE_PATH = os.path.join(os.path.expanduser("~"), ".config/indicators/messages/applications")
MM_FILE_NAME = os.path.join(MM_FILE_PATH, "stackapplet")

class preference_server(BaseHTTPServer.BaseHTTPRequestHandler):

	nonce      = ''
	auth_token = ''
	
	@staticmethod
	def reset_tokens():
		
		# Generate a nonce and a authorization token
		preference_server.nonce      = uuid.uuid4().hex
		preference_server.auth_token = uuid.uuid4().hex
	
	# Initializes the list of actions available to connecting clients
	def init_actions(self):
		
		# This list is a mapping between request actions
		# and member functions
		self.data_list = [ [ '/get_settings',     self.process_get_settings     ],
		                   [ '/get_translations', self.process_get_translations ],
		                   [ '/add_account',      self.process_add_account      ],
		                   [ '/remove_account',   self.process_remove_account   ],
		                   [ '/reorder',          self.process_reorder          ],
		                   [ '/set_rate',         self.process_set_rate         ],
		                   [ '/set_startup',      self.process_set_startup      ],
		                   [ '/set_mm',           self.process_set_mm           ],
		                   [ '/set_theme',        self.process_set_theme        ],
		                   [ '/set_language',     self.process_set_language     ] ]
	
	def get_action_function(self, action):
	
		for entry in self.data_list:
		
			if action == entry[0]:
				return entry[1]
		
		return None
	
	# Ensures the user is authorized to access the page
	def check_authorization(self):
		
		# Check to see if the user has already received the auth token
		if not self.nonce == '':
			# Get the cookies set by the user
			cookies = self.headers.getheader('Cookie', '').split('; ')
			
			# Check for the stackapplet_auth cookie
			if 'stackapplet_auth=' + self.auth_token in cookies:
				return True
			else:
				self.send_reply(403, "Invalid authorization credentials supplied. Please click on the preferences menu item in the application to view this page.")
				return False
		else:
			self.send_reply(403, "Your session has expired. Please click on the preferences menu item in the application to view this page.")
	
	# There are basically two types of requests
	# that we process - GET requests (for files)
	# and POST requests (for actions).
	def do_GET(self):
		
		# Log the request
		print '[GET]: ' + self.path
		
		# Check to see if the user is authenticating
		if not self.nonce == '' and self.path == '/auth?nonce=' + self.nonce:
			
			# It can only be used once, so drop it
			self.nonce = ''
			
			# Redirect the client to the '/' page
			self.send_response(302)
			
			# Set the auth token as a cookie to the user
			self.send_header('Set-Cookie', 'stackapplet_auth=' + self.auth_token)
			self.send_header('Location', '/')
			
			return
		
		# Make sure the user is authorized
		if not self.check_authorization():
			return
		
		# If no page was specified, default to index.html
		if self.path == '/':
			self.path = '/index.html'
		
		# self.path contains the path that we want
		# to deal with. Check to see if it's one of the
		# pages that we serve static content from.
		global file_list
		
		if self.path in file_list:
			self.process_file()
		else:
			self.send_reply(404, "The page you have requested does not exist on this server.")
	
	def do_POST(self):
		
		# Log the request
		print '[POST]: ' + self.path
		
		# Make sure the user is authorized
		if not self.check_authorization():
			return
		
		self.init_actions()
		
		# Get the POST data
		length = int(self.headers.getheader('content-length'))
		self.post_vars = urlparse.parse_qs(self.rfile.read(length))
		
		function = self.get_action_function(self.path)
		if not function == None:
			function()
		else:
			self.send_reply(404, "The specified command is not recognized by StackApplet.")
	
	def send_reply(self, status, reply, content_type = 'text/plain'):
		
		self.send_response(status)
		self.send_header('Content-length', str(len(reply)))
		self.send_header('Content-type', content_type)
		self.end_headers()
		self.wfile.write(reply)
	
	def process_file(self):
		
		# Now we need to simply construct the path
		# to the file and grab the contents. First check
		# to see if the file is in the html folder.
		filename = os.path.join(sys.path[0], "html" + self.path)
		
		if not os.path.isfile(filename):
			
			if platform.system() == 'Windows':
				filename = os.path.join(os.path.dirname(sys.executable), "html" + self.path)
			else:
				filename = os.path.join(sys.path[0], "../html" + self.path)
		
		f = open(filename, 'rb')
		data = f.read()
		
		if filename.endswith('.png'):
			self.send_reply(200, data, 'image/png')
		elif filename.endswith('.css'):
			self.send_reply(200, data, 'text/css')
		elif filename.endswith('.js'):
			self.send_reply(200, data, 'text/javascript')
		else:
			self.send_reply(200, data, 'text/html')
	
	def process_get_settings(self):
		
		# We need to get the current list of accounts.
		# This means sending a request to the main thread
		# and waiting for a reply with the list of accounts.
		global main_thread_callback
		
		# Now have this message processed in the main
		# thread BUT then wait for it to be signaled
		# once processing is complete
		event = threading.Event()
		data = {}
		json_account = []
		
		gobject.idle_add(main_thread_callback, 'get_settings', event, data)
		event.wait()
		
		# Account list should now have our data
		for account in data['accounts']:
			
			json_account.append({ 'site_name': account['site_name'] })
		
		# Get startup data
		global AUTOSTART_FILE_NAME, MM_FILE_NAME
		
		if os.path.isfile(AUTOSTART_FILE_NAME):
			startup = 1
		else:
			startup = 0
		
		if os.path.isfile(MM_FILE_NAME):
			mm = 1
		else:
			mm = 0
		
		# Determine if we are on Windows
		on_windows = 0
		
		if platform.system() == 'Windows':
			on_windows = 1
		
		# Determine if we have a messaging menu
		can_use_mm = 0
		
		if CAN_USE_MM:
			can_use_mm = 1
		
		response = json.dumps({ 'accounts'    : json_account,
		                        'theme'       : data['theme'],
		                        'refresh_rate': data['refresh_rate'],
		                        'language'    : data['language'],
		                        'startup'     : startup,
		                        'mm'          : mm,
		                        'on_windows'  : on_windows,
		                        'can_use_mm'  : can_use_mm })
		
		self.send_reply(200, response, 'application/json')
	
	def process_get_translations(self):
		
		global main_thread_callback
		data = {}
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'get_translation', event, data)
		event.wait()
		
		# Refresh the translations and return them
		js_translations.refresh(data['translation'])
		json_result = json.dumps(js_translations.translation_keys);
		
		self.send_reply(200, json_result, 'application/json')
	
	def process_add_account(self):
		
		global main_thread_callback
		
		# Get the variables for this account
		site      = self.post_vars['site'][0]
		site_name = self.post_vars['site_name'][0]
		user_id   = self.post_vars['user_id'][0]
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'add_account', event, { 'site'     : site,
		                                                               'site_name': site_name,
		                                                               'user_id'  : user_id })
		event.wait()
		
		self.send_reply(200, '', 'application/json')
	
	def process_remove_account(self):
		
		global main_thread_callback
		
		account_index = self.post_vars['index'][0]
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'remove_account', event, account_index)
		event.wait()
		
		self.send_reply(200, '', 'application/json')
	
	def process_reorder(self):
		
		global main_thread_callback
		
		old_index = self.post_vars['old_index'][0]
		new_index = self.post_vars['new_index'][0]
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'reorder', event, { 'old_index': old_index,
		                                                           'new_index': new_index })
		event.wait()
		
		self.send_reply(200, '', 'application/json')
	
	def process_set_rate(self):
		
		global main_thread_callback
		
		rate = self.post_vars['rate'][0]
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'set_rate', event, rate)
		event.wait()
		
		self.send_reply(200, '', 'application/json')

	def process_set_startup(self):
		
		# We actually don't need the main thread to manipulate
		# this feature.
		global AUTOSTART_FILE_PATH, AUTOSTART_FILE_NAME
		
		# See if the path exists
		if not os.path.isdir(AUTOSTART_FILE_PATH):
			os.mkdir(AUTOSTART_FILE_PATH)
		
		# Now see if we're adding or removing
		value = self.post_vars['value'][0]
		
		if value == '0':
			
			# We're removing
			if os.path.isfile(AUTOSTART_FILE_NAME):
				os.remove(AUTOSTART_FILE_NAME)
		
		else:
		
			# We're copying it there.
			shutil.copyfile('/usr/share/applications/stackapplet.desktop', AUTOSTART_FILE_NAME)
		
		self.send_reply(200, '', 'application/json')
	
	def process_set_mm(self):
		
		# We actually don't need the main thread to manipulate
		# this feature.
		global MM_FILE_PATH, MM_FILE_NAME
		
		# See if the path exists
		if not os.path.isdir(MM_FILE_PATH):
			os.makedirs(MM_FILE_PATH)
		
		# Now see if we're adding or removing
		value = self.post_vars['value'][0]
		
		if value == '0':
			
			# We're removing
			if os.path.isfile(MM_FILE_NAME):
				os.remove(MM_FILE_NAME)
		
		else:
		
			# We're creating the file
			f = open(MM_FILE_NAME, 'w')
			f.write("/usr/share/applications/stackapplet.desktop")
			f.close()
		
		self.send_reply(200, '', 'application/json')
	
	def process_set_theme(self):
		
		global main_thread_callback
		
		theme = self.post_vars['theme'][0]
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'set_theme', event, theme)
		event.wait()
		
		self.send_reply(200, '', 'application/json')
	
	def process_set_language(self):
		
		global main_thread_callback
		
		if 'language' in self.post_vars:
			language = self.post_vars['language'][0]
		else:
			language = ''
		
		event = threading.Event()
		gobject.idle_add(main_thread_callback, 'set_language', event, language)
		event.wait()
		
		self.send_reply(200, '', 'application/json')
	
	# This is here to supplant those annoying
	# status messages that display whenever a client
	# views a page. We provide our own logging facilities ATM.
	def log_message(self, a, *b):
		
		pass

# The port we're listening on
current_port = defaults.__default_port__

# Create the server
while True:
	try:
		httpd = BaseHTTPServer.HTTPServer(('localhost', current_port), preference_server)
		break
	except:
		current_port += 1

# No create a special thread just for the server
thread = threading.Thread(target=httpd.serve_forever)

# Start it
thread.start()

# The handler
def trigger():
	global current_port
	preference_server.reset_tokens()
	webbrowser.open("http://localhost:" + str(current_port) + '/auth?nonce=' + preference_server.nonce)
