#!/usr/bin/env python
import urllib2
import urllib
import optparse
import os
import platform
import sys
import json
import csv
import codecs
import HTMLParser
from TUnicodeCSV.UnicodeCSV import UTF8Recoder
from TUnicodeCSV.UnicodeCSV import UnicodeReader
from TUnicodeCSV.UnicodeCSV import UnicodeWriter
from Tposter.encode import multipart_encode
from Tposter.streaminghttp import register_openers 
import re
from htmlentitydefs import name2codepoint
import TWebBrowser.webbrowser as webbrowser
import shutil
import urlparse

def module_path():
	if hasattr(sys, "frozen"):
		return os.path.dirname(os.path.abspath(unicode(sys.executable, sys.getfilesystemencoding( ))))
	return os.path.dirname(os.path.abspath(unicode(__file__, sys.getfilesystemencoding( ))))


def download(url, fileName=None):
	def getDirectory():
		dirname = os.path.join(os.path.abspath(os.path.expanduser("~")),"Downloads")
		if os.path.isdir(dirname):
			return dirname
		else:
			return os.path.join(os.path.abspath(os.path.expanduser("~")),"Desktop")
	def getFileName(url,openUrl):
		if 'Content-Disposition' in openUrl.info():
			cd = dict(map(lambda x: x.strip().split('=') if '=' in x else (x.strip(),''),openUrl.info()['Content-Disposition'].split(';')))
			if 'filename' in cd:
				filename = cd['filename'].strip("\"'")
				if filename: return filename
		return os.path.basename(urlparse.urlsplit(openUrl.url)[2])
	directoryfound = False
	directory = getDirectory()
	if os.path.isdir(directory):
		directoryfound = True
	else:
		directoryfound = False
		return directoryfound
	try:
		r = urllib2.urlopen(urllib2.Request(url))
		fileName = fileName or getFileName(url,r)
		absfilename = os.path.join(directory,fileName)
		with open(absfilename, 'wb') as f:
			shutil.copyfileobj(r,f)
	finally:
		r.close()
		return True
	return False


def myNewLine():
	if platform.system() == 'Windows':
		return "\r\n"
	else:
		return "\n"

def htmlentitydecode(s):
    return re.sub('&(%s);' % '|'.join(name2codepoint), 
            lambda m: unichr(name2codepoint[m.group(1)]), s)
def saveWebHook(webhook):
	string = ''
	webhook = webhook.strip()
	o = urlparse.urlparse(webhook);
	
	if (o.scheme.lower() == 'http' and webhook.find(' ')==-1) or (o.scheme.lower() == 'https' and webhook.find(' ')==-1):
		string = 'Valid WebHook: ' + webhook
	else:
		print 'Instructed to save invalid WebHook'
		sys.exit()
	
	if platform.system() == 'Windows':
		savefile = 'torch_cl.ini'
	else:
		savefile = ".torch_cl"

	configfile = os.path.join(os.path.expanduser("~"), savefile)
	f = open(configfile, 'w')
	f.write(webhook)
	f.close()
	return string
	
def getWebHook():
	if platform.system() == 'Windows':
		savefile = 'torch_cl.ini'
	else:
		savefile = ".torch_cl"
		
	configfile = os.path.join(os.path.expanduser("~"), savefile)
	
	if os.path.isfile(configfile):
		f = open(configfile, 'r')
		configsetting = f.read()
		f.close()
		
		if len(configsetting)>0:
			return configsetting
		else:
			print 'No WebHook URL saved'
			sys.exit()
	else:
		print 'No WebHook URL saved'
		sys.exit()

def callWebHook(myjson,files=[],mlist=''):
	url = getWebHook()
	values = {}
	
	if len(myjson)>0:
		values['json'] = json.dumps(myjson)
	
	if len(mlist)>0:
		url = url + "&list=" + urllib.quote(mlist)
	
	if len(files)>0:
		register_openers()
		for ins, s in enumerate(files):
			values['file_'+str(ins)] = open(s,'rb')
		
		data, headers = multipart_encode(values)
		req = urllib2.Request(url,data,headers)
	else:
		data = urllib.urlencode(values)
		req = urllib2.Request(url,data)
	
	print 'Sending data...'
	
	try:
		response = urllib2.urlopen(req)
	except URLError, e:
		print 'Something is wrong with that WebHook'
		print e.read()
		sys.exit()
	else:
		the_page = response.read().strip()
		return the_page
		
def retreaveData(value):
	if len(value.strip())>0 and os.path.isfile(os.path.expanduser(value.strip())):
		f = codecs.open(os.path.expanduser(value.strip()),'r', "utf-8")
		value = f.read()
		f.close()
		return value
	else:
		return value

def fileList(slist):
	flist = slist.strip().split(',')
	listout = []
	for s in flist:
		if len(s.strip())>0 and os.path.isfile(os.path.expanduser(s.strip())):
			if os.path.getsize(os.path.expanduser(s.strip())) < 10240000:
				listout.append(os.path.expanduser(s.strip()))
			else:
				print "File \"" + os.path.expanduser(s.strip()) + "\" not included because it exceeds file limits"
	
	return listout
	
def fileShort(slist):
	listout = []
	if len(slist.strip())>0 and os.path.isfile(os.path.expanduser(slist.strip())):
		if os.path.getsize(os.path.expanduser(slist.strip())) < 10240000:
			listout.append(os.path.expanduser(slist.strip()))
		else:
			print "File \"" + os.path.expanduser(slist.strip()) + "\" not included because it exceeds file limits"
	return listout
	
def createMessage(data):
	mjson = {}
	mjson['title'] = retreaveData(data.title.strip())
	mjson['body'] = retreaveData(data.body.strip())	
	if data.simulate == True:
		mjson['simulate'] = 'true'
		return callWebHook(mjson,fileList(data.file))
	else:
		return callWebHook(mjson,fileList(data.file))
	
def createTime(data):
	mjson = {}
	mjson['note'] = retreaveData(data.note.strip())
	mjson['category'] = retreaveData(data.category.strip())	
	mjson['timestamp'] = retreaveData(data.timestamp.strip())
	mjson['hours'] = str(data.hours).strip()
	return callWebHook(mjson)
	
def createExpense(data):
	mjson = {}
	mjson['note'] = retreaveData(data.note.strip())
	mjson['category'] = retreaveData(data.category.strip())	
	mjson['timestamp'] = retreaveData(data.timestamp.strip())
	mjson['price'] = str(data.price).strip()
	return callWebHook(mjson,fileShort(data.onefile))

def saveString(path,value):
	f = codecs.open(path, 'w', "utf-8")
	f.write(htmlentitydecode(unicode(value, "utf-8")))
	f.close()
	print 'File saved to: ' + path
	
def saveCSV(path,value):
	names = []
	f = codecs.open(path, "w","utf-8")
	csvwriter = UnicodeWriter(f,encoding="utf-8")
	try:
		s = value[0].keys()
	except:
		try:		
			for name in value.keys():
				names.append(htmlentitydecode(name))
			csvwriter.writerow(names)
		
			newrow = []
			for name in value.keys():
				newrow.append(value[name])
			csvwriter.writerow(newrow)
		except:
			f.write(unicode(value, "utf-8"))				
	else:
		for row in value:
			for name in row.keys():
				try:
					names.index(htmlentitydecode(name))
				except:
					names.append(htmlentitydecode(name))
		csvwriter.writerow(names)

		for row in value:
			newrow = []
			for name in names:
				newrow.append(htmlentitydecode(row.get(name,'')))
			csvwriter.writerow(newrow)
	f.close()
	print 'File saved to: ' + path

def saveTXT(path,value):
	svalue = '';
	for row in value:
		title = ''
		mdate = ''
		project = ''
		category = ''
		for name in row.keys():
			if name.lower() == 'date':
				mdate = row.get(name,'').strip()
			elif name.lower() == 'title':
				title = row.get(name,'').strip()
			elif name.lower() == 'projectname':
				project = row.get(name,'')
			elif name.lower() == 'category':
				category = row.get(name,'')
		if len(mdate)>0:
			title = title + ' on ' + mdate
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(category)>0:
			category = '@' + category.strip().replace(' ','_') + ' '
		svalue = svalue + project + category + title + myNewLine()
	f = codecs.open(path, 'w', "utf-8")
	f.write(htmlentitydecode(svalue.strip()))
	f.close()
	print 'File saved to: ' + path

def showExpenses(value):
	svalue = '';
	for row in value:
		note = ''
		mdate = ''
		project = ''
		category = ''
		cost = ''
		for name in row.keys():
			if name.lower() == 'note':
				note = row.get(name,'').strip()
			elif name.lower() == 'projectname':
				project = row.get(name,'')
			elif name.lower() == 'category':
				category = row.get(name,'')
			elif name.lower() == 'cost':
				cost = str(round(float(row.get(name,0)),2)) + ": "
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(category)>0:
			category = '@' + category.strip().replace(' ','_') + ' '
		svalue = svalue + project + category + cost + note + myNewLine()
	print htmlentitydecode(svalue.strip())

def showTime(value):
	svalue = '';
	for row in value:
		note = ''
		project = ''
		category = ''
		hours = ''
		for name in row.keys():
			if name.lower() == 'note':
				note = row.get(name,'').strip()
			elif name.lower() == 'projectname':
				project = row.get(name,'')
			elif name.lower() == 'category':
				category = row.get(name,'')
			elif name.lower() == 'hours':
				hours = str(round(float(row.get(name,0)),2)) + ": "
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(category)>0:
			category = '@' + category.strip().replace(' ','_') + ' '
		svalue = svalue + project + category + hours + note + myNewLine()
	print htmlentitydecode(svalue.strip())

def showPayments(value):
	svalue = '';
	for row in value:
		note = ''
		project = ''
		invoicename = ''
		amount = ''
		for name in row.keys():
			if name.lower() == 'note':
				note = row.get(name,'').strip()
			elif name.lower() == 'projectname':
				project = row.get(name,'')
			elif name.lower() == 'invoicename':
				invoicename = row.get(name,'')
			elif name.lower() == 'amount':
				amount = str(round(float(row.get(name,0)),2)) + ": "
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(invoicename)>0:
			invoicename = '@' + invoicename.strip().replace(' ','_') + ' '
		svalue = svalue + project + invoicename + amount + note + myNewLine()
	print htmlentitydecode(svalue.strip())

def showInvoices(value):
	svalue = '';
	for row in value:
		n = ''
		project = ''
		category = ''
		amount = ''
		for name in row.keys():
			if name.lower() == 'name':
				n = row.get(name,'').strip()
			elif name.lower() == 'project':
				project = row.get(name,'')
			elif name.lower() == 'state':
				state = row.get(name,'')
			elif name.lower() == 'total':
				total = str(round(float(row.get(name,0)),2)) + ": "
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(state)>0:
			state = '@' + state.strip().replace(' ','_') + ' '
		svalue = svalue + project + state + total + n + myNewLine()
	print htmlentitydecode(svalue.strip())
	
	
def	showDrive(value):
	svalue = '';
	for row in value:
		title = ''
		url = ''
		project = ''
		category = ''
		for name in row.keys():
			if name.lower() == 'title':
				title = row.get(name,'').strip()
			elif name.lower() == 'url':
				url = row.get(name,'').strip()
			elif name.lower() == 'projectname':
				project = row.get(name,'')
			elif name.lower() == 'category':
				category = row.get(name,'')
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(category)>0:
			category = '@' + category.strip().replace(' ','_') + ' '
		svalue = svalue + project + category + title + myNewLine() + url + myNewLine()
	print htmlentitydecode(svalue.strip())	

def showTXT(value):
	svalue = '';
	for row in value:
		title = ''
		mdate = ''
		project = ''
		category = ''
		for name in row.keys():
			if name.lower() == 'date':
				mdate = row.get(name,'').strip()
			elif name.lower() == 'title':
				title = row.get(name,'').strip()
			elif name.lower() == 'projectname':
				project = row.get(name,'')
			elif name.lower() == 'category':
				category = row.get(name,'')
		if len(mdate)>0:
			title = title + ' on ' + mdate
		if len(project)>0:
			project = '+' + project.strip().replace(' ','_') + ' '
		if len(category)>0:
			category = '@' + category.strip().replace(' ','_') + ' '
		svalue = svalue + project + category + title + myNewLine()
	print htmlentitydecode(svalue.strip())


def tryJsonError(string):
	try:
		return json.loads(string)
	except:
		return string

def searchLists(search):
	jdata = callWebHook('',[],search.strip())
	return tryJsonError(jdata)

def checkVersion():
	f = urllib.urlopen("https://www.mytorch.net/CL/version.txt")
	s = f.read().strip()
	eq = s.rfind('=')
	onlineversion = ""
	if eq>-1 and eq != False:
		onlineversion = s[eq:].strip()
	f.close()
	scriptdir = module_path()
	versionfile = os.path.join(scriptdir, 'version.txt')
	try:		
		curversion = open(versionfile, 'r').read().strip()
	except IOError as e:
		print "Version file has been removed, are you using MacPorts to keep TorchCL up-to-date?"
		return False
		curversion = ""
	eq = curversion.rfind('=')
	if eq>-1 and eq != False:
		curversion = curversion[eq:].strip()
	if curversion == onlineversion and len(curversion)>0:
		print "Version up to date"
	else:
		if platform.system() == 'Windows':
			print "Version out of date, opening web browser"
			webbrowser.open("http://torch.wbpsystems.com/cl.php#Setup")
		else:
			print "Version out of date, attempting to download update to Downloads folder"
			downloadoutcome = download("https://www.mytorch.net/CL/torchCL.tar.gz")
			if downloadoutcome != True:
				print "Couldn't download file, opening web browser"
				webbrowser.open("http://torch.wbpsystems.com/cl.php#Setup")
			else:
				print "File downloaded"
		

def saveTemplateToDisk(path,value):
	svalue = '';
	for row in value:
		subject = ''
		body = ''
		for name in row.keys():
			if name.lower() == 'subject':
				subject = row.get(name,'').strip()
			elif name.lower() == 'body':
				body = row.get(name,'').strip()
		svalue = svalue + "*" + subject + "*" + myNewLine() + myNewLine() + body + myNewLine() + myNewLine()
	f = codecs.open(path, 'w', "utf-8")
	f.write(htmlentitydecode(svalue.strip()))
	f.close()

def showTemplateToDisk(value):
	svalue = '';
	listlen = len(value)
	for row in value:
		subject = ''
		body = ''
		for name in row.keys():
			if name.lower() == 'subject':
				subject = row.get(name,'').strip()
			elif name.lower() == 'body':
				body = row.get(name,'').strip()		
		if listlen>1:
			svalue = svalue + subject + myNewLine()
		else:
			svalue = svalue + "*" + subject + "*" + myNewLine() + myNewLine() + body + myNewLine() + myNewLine()
	print htmlentitydecode(svalue.strip())
	
def main():
	didsomething = False
	mystring = ' '
	mycsv = ''
	desc = 'TorchCL allows you to interact with your project management software directly from the command line.  Use the following options to create messages, time entries and expenses.  Additionally, use the list features to retrieve data and optionally save it to a file.'
	p = optparse.OptionParser(description=desc)
	utilities = optparse.OptionGroup(p, 'Utility Options')
	utilities.add_option('--webhook', '-w', dest="webhook", help="Define the webhook to use for all transactions", default='', metavar='"<Valid WebHook>"')
	utilities.add_option('--save', '-s', dest="savefile", help="Save output to a file", default='', metavar='"<File Path>"')
	utilities.add_option('--text', dest="stext", action="store_true", default=False, help="Change screen output to text")
	utilities.add_option('--version', dest="version", action="store_true", default=False, help="Check if this is the current version of TorchCL")
	
	messages = optparse.OptionGroup(p, 'Inserting Messages Options')
	messages.add_option('--title', '-t', dest="title", help="When defining a message, set the title", default='', metavar='"<Title or File Path>"')
	messages.add_option('--body', '-b', dest="body", help="When defining a message, set the body", default='', metavar='"<Body or File Path>"')
	messages.add_option('--files', '-f', dest="file", help="Comma seperated list of files to attach when creating a message", default='', metavar='"<File Path>, <File Path>"')
	messages.add_option("--simulate", action="store_true", dest="simulate", default=False, help="Evaluates the message body for email commands but doesn't create the final message")
	
	hours = optparse.OptionGroup(p, 'Inserting Time and Expenses')
	hours.add_option('--hours', dest="hours", type="float", help="When inserting time, the number of hours worked", default=0, metavar='"<Number>"')
	hours.add_option('--price', dest="price", type="float", help="When entering an expense, the price of the item", default=0, metavar='"<Number>"')
	hours.add_option('--note', '-n', dest="note", help="When entering an expense or time the note about the item", default='', metavar='"<Note or File Path>"')
	hours.add_option('--category', '-c', dest="category", help="When entering an expense or time the category of the item", default='', metavar='"<Category or File Path>"')
	hours.add_option('--date', '-d', dest="timestamp", help="When entering an expense or time the date of the item", default='', metavar='"<Date or File Path>"')
	hours.add_option('--file',  dest="onefile", help="When entering an expense attach a receipt", default='', metavar='"<File Path>"')
	
	search = optparse.OptionGroup(p, 'Search and Retrieve Data')
	search.add_option('--list', dest="lists", help="Request a list, currently supports 'time', 'expenses', 'invoices', 'events', 'allevents', 'completeevents', 'messages', 'docs' or 'payments'; you can also specify a project name to see a list only from that project", default='',  metavar='"<List Item>"')
	
	p.add_option_group(utilities)
	p.add_option_group(messages)
	p.add_option_group(hours)
	p.add_option_group(search)
	
	(options, arguments) = p.parse_args()
	
	if len(options.webhook.strip())>0:
		didsomething = True
		mystring = saveWebHook(options.webhook.strip())
	
	if len(options.title.strip())>0:	
		didsomething = True
		mystring = createMessage(options)
	
	if len(options.note.strip())>0 and options.hours>0:
		didsomething = True
		mystring = createTime(options)
	
	if len(options.note.strip())>0 and options.price>0:
		didsomething = True
		mystring = createExpense(options)
		
	if len(options.lists.strip())>0:
		didsomething = True
		mycsv = searchLists(options.lists.strip())
			
	if len(mystring.strip())>0 and len(options.savefile.strip())>0 and os.path.abspath(os.path.expanduser(options.savefile.strip())) != False:
		saveString(os.path.abspath(os.path.expanduser(options.savefile.strip())),mystring)
	elif len(mystring.strip())>0:
		print mystring
		
	if len(mycsv)>0 and len(options.savefile.strip())>0 and os.path.abspath(os.path.expanduser(options.savefile.strip())) != False and options.savefile.strip().lower()[-4:]=='.txt' and (options.lists.strip().lower()[0:6]=='events' or options.lists.strip().lower()[0:9]=='allevents' or options.lists.strip().lower()[0:14]=='completeevents'):
		saveTXT(os.path.abspath(os.path.expanduser(options.savefile.strip())),mycsv)
	elif len(mycsv)>0 and len(options.savefile.strip())>0 and os.path.abspath(os.path.expanduser(options.savefile.strip())) != False and options.savefile.strip().lower()[-4:]=='.txt' and options.lists.strip().lower()[0:8]=='messages':
		saveTemplateToDisk(os.path.abspath(os.path.expanduser(options.savefile.strip())),mycsv)
	elif len(mycsv)>0 and len(options.savefile.strip())>0 and os.path.abspath(os.path.expanduser(options.savefile.strip())) != False:
		saveCSV(os.path.abspath(os.path.expanduser(options.savefile.strip())),mycsv)
	elif len(mycsv)>0 and options.stext==True and (options.lists.strip().lower()[0:6]=='events' or options.lists.strip().lower()[0:9]=='allevents' or options.lists.strip().lower()[0:14]=='completeevents'):
		showTXT(mycsv)
	elif len(mycsv)>0 and options.stext==True and options.lists.strip().lower()[0:8]=='messages':
		showTemplateToDisk(mycsv)
	elif len(mycsv)>0 and options.stext==True and options.lists.strip().lower()[0:4]=='time':
		showTime(mycsv)
	elif len(mycsv)>0 and options.stext==True and options.lists.strip().lower()[0:8]=='expenses':
		showExpenses(mycsv)
	elif len(mycsv)>0 and options.stext==True and options.lists.strip().lower()[0:8]=='payments':
		showPayments(mycsv)
	elif len(mycsv)>0 and options.stext==True and options.lists.strip().lower()[0:8]=='invoices':
		showInvoices(mycsv)
	elif len(mycsv)>0 and options.stext==True and (options.lists.strip().lower()[0:4]=='docs' or options.lists.strip().lower()[0:5]=='drive'):
		showDrive(mycsv)
	elif len(mycsv)>0:
		print mycsv
	
	if options.version == True:
		didsomething = True
		checkVersion()
		
	if didsomething==False:
		print "Please specify at least a few parameters, otherwise I can't do much"

if __name__ == '__main__':
	main()