#!/usr/bin/env python
import optparse
import os
import platform
import sys
import codecs
import datetime
from dateutil.parser import parse
import re
import shutil
import time
from decimal import *
from UnicodeCSV import UTF8Recoder
from UnicodeCSV import UnicodeReader
from UnicodeCSV import UnicodeWriter
import csv

import locale
locale.setlocale(locale.LC_ALL, '')

if locale.getlocale()[0]==None:
	locale.setlocale(locale.LC_ALL, 'en_US')

def saveCSV(path,lines):
	names = ['Date','Price','Project','Category','Note']
	f = codecs.open(path, "w","utf-8")
	csvwriter = UnicodeWriter(f,encoding="utf-8")
	csvwriter.writerow(names)
	
	for line in lines:
		newrow = []
		result = ExtractDate(line)
		newrow.append(result['date'])
		rstr = result['replacement']
		if len(rstr.strip())>0:
			line = line.replace(rstr,'').replace('  ',' ').strip()
		result = ExtractPrice(line)
		newrow.append(result['price'])
		rstr = result['replacement']
		if len(rstr.strip())>0:
			line = line.replace(rstr,'').replace('  ',' ').strip()
		result = ExtractProject(line)
		newrow.append(result['project'])
		rstr = result['replacement']
		if len(rstr.strip())>0:
			line = line.replace(rstr,'').replace('  ',' ').strip()
		result = ExtractCategory(line)
		newrow.append(result['category'])
		rstr = result['replacement']
		if len(rstr.strip())>0:
			line = line.replace(rstr,'').replace('  ',' ').strip()
		newrow.append(line)
		csvwriter.writerow(newrow)
	f.close()
	print 'File saved to: ' + path



def ExtractCategory(line):
	pattern = re.compile(u"([@])([^\\s\\.;,:]+)",re.U)
	category = ''
	replacement = ''
	findcategory = pattern.search(line)
	if findcategory != None:
		category = findcategory.group(2).replace('_',' ')
		replacement = findcategory.group(0)
	return {'category':category, 'replacement':replacement}


def ExtractProject(line):
	pattern = re.compile(u"([+])([^\\s\\.;,:]+)",re.U)
	project = ''
	replacement = ''
	findproject = pattern.search(line)
	if findproject != None:
		project = findproject.group(2).replace('_',' ')
		replacement = findproject.group(0)
	return {'project':project, 'replacement':replacement}


def ExtractDate(line):
	datepattern = re.compile("^([\\d\\/-]+)([:]?)",re.U)
	finddate = datepattern.match(line.strip())
	linedate = ''
	replacement = ''
	if finddate != None:
		replacement = finddate.group(0)
		if getDate(finddate.group(1)) != False:
			linedate = getDate(finddate.group(1)).strftime("%Y-%m-%d")
		
	return {'date':linedate, 'replacement':replacement}

def ExtractPrice(line):
	numberpattern = re.compile(u"([-]?)(\\$|\u20AC|\u00A3)([\\d\\.,]+)",re.U)
	findnumber = numberpattern.search(line)
	price = ''
	replacement = ''
	if findnumber != None:
		replacement = findnumber.group(0)
		price = str(findnumber.group(1) +findnumber.group(3))
	return {'price':price, 'replacement':replacement}
	

def saveActiveFile(filestring):
	filestring = fileExists(filestring)	
	if platform.system() == 'Windows':
		savefile = 'expense_cl.ini'
	else:
		savefile = ".expense_cl"
	configfile = os.path.join(os.path.expanduser("~"), savefile)
	f = open(configfile, 'w')
	f.write(filestring)
	f.close()

def getActiveFile():
	if platform.system() == 'Windows':
		savefile = 'expense_cl.ini'
	else:
		savefile = ".expense_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:
			if os.path.isfile(os.path.expanduser(configsetting.strip())):
				return configsetting
			else:
				print 'No active file'
				sys.exit()
		else:
			print 'No active file'
			sys.exit()
	else:
		print 'No active file'
		sys.exit()
	

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


def isReturnFile(myfile):
	if os.path.abspath(os.path.expanduser(myfile.strip())) != False:
		return os.path.abspath(os.path.expanduser(myfile.strip()))
	else:
		print 'You can\'t save to that location'
		sys.exit()

def fileExists(value):
	if os.path.isfile(os.path.expanduser(value.strip())):
		return os.path.abspath(os.path.expanduser(value.strip()))
	else:
		print "I can't find the source file"
		sys.exit()


def tryDecimal(mstr):
	mstr = mstr.replace(',','').strip()
	try:
		mdec = Decimal(mstr)
		return mdec
	except Exception as inst:
		return Decimal('0')

def getDate(mstr):
	try:
		mdate = parse(mstr.strip())
		return mdate
	except Exception as inst:
		return False
		
def errorMessage(mstr):
	print mstr
	sys.exit()

def searchTool(operator,first,second):
	if operator == "=":
		if first == second:
			return True
	elif operator == "<":
		if first < second:
			return True
	else:
		if first > second:
			return True
	return False



def getNumber(line):
	numberpattern = re.compile(u"([-]?)(\\$|\u20AC|\u00A3)([\\d\\.,]+)",re.U)
	findnumber = numberpattern.search(line)
	if findnumber != None:
		return tryDecimal(str(findnumber.group(1)) + str(findnumber.group(3)))
	else:
		return Decimal('0')

def searchProcessor(searchterm,line):
	datepattern = re.compile("^([\\d\\/-]+)",re.U)
	numberpattern = re.compile(u"([-]?)(\\$|\u20AC|\u00A3)([\\d\\.,]+)",re.U)
	result=re.match(u"([><=])([-]?)(\\$|\u20AC|\u00A3)?([\\d\\.,]+)([\\s]|$)",searchterm.strip(),re.U)
	result2=re.match("([><=])([\\d\\/-]+)",searchterm.strip(),re.U)
	if result != None:
		operator = result.group(1)
		foundnumber = tryDecimal(str(result.group(2))+str(result.group(4)))
		searchtype = 'number'	
	elif result2 != None:
		operator = result2.group(1)
		founddate = getDate(result2.group(2))
		if founddate == False:
			errorMessage("Sorry, that's not a valid date to use for search")			
		searchtype = 'date'
	elif len(searchterm.strip())>0:
		searchpattern = re.compile(re.escape(searchterm.strip()),re.I|re.U)
		searchtype = 'string'
	else:
		searchtype = 'none'
		
	if searchtype == 'number':
		findnumber = numberpattern.search(line)
		if findnumber != None:
			linenumber = tryDecimal(str(findnumber.group(1)) + str(findnumber.group(3)))
			if searchTool(operator,linenumber,foundnumber) == True:
				return True
	elif searchtype == 'date':
		finddate = datepattern.match(line.strip())
		if finddate != None:
			linedate = getDate(finddate.group(1))
			if linedate:
				if searchTool(operator,linedate,founddate):
					return True
	elif searchtype == 'string':
		if searchpattern.search(line) != None:
			return True
	return False

def searchAndAlg(search, line):
	andconditions = search.lower().split(' and ')
	for subsearch in andconditions:
		if searchProcessor(subsearch,line) != True and len(subsearch.strip())>0:
			return False
	return True
	
def searchOrAlg(searchstring, line):
	orconditions = searchstring.lower().split(' or ')
	for subsearch in orconditions:
		if searchAndAlg(subsearch,line) and len(subsearch.strip())>0:
			return True
	return False


def ParseFile(mainfile,searchterm):
	totalvalue = Decimal('0')
	resultlist = []
	rf = codecs.open(mainfile, "r", "utf-8")
	for line in rf:
		if len(searchterm.strip())==0 or searchOrAlg(searchterm, line) == True:
			totalvalue = getNumber(line) + totalvalue
			resultlist.append(line)
	rf.close()
	return {'total':totalvalue, 'lines':resultlist}

def PrintList(mlist):
	for line in mlist:
		if len(line.strip())>0:
			print line.strip()
			
def WriteFile(savefile,lines):
	writenewline = False
	if os.path.isfile(savefile):
 		fout = codecs.open(savefile, "a+", "utf-8")
		if os.path.getsize(savefile)>0:
			fout.seek(-1, os.SEEK_END)
			test = fout.read(1)
		else:
			test = "\n"		
		if test != "\n":
			writenewline = True
	else:
		fout = codecs.open(savefile, "w", "utf-8")
	for line in lines:
		if len(line.strip())>0:
			if writenewline:
				fout.write(unicode(myNewLine()))
				writenewline = False
			fout.write(line)
	fout.close()
	return True

def AddToFile(savefile,line):
	writenewline = False
	if os.path.isfile(savefile):
 		fout = codecs.open(savefile, "a+", "utf-8")
		if os.path.getsize(savefile)>0:
			fout.seek(-1, os.SEEK_END)
			test = fout.read(1)
		else:
			test = "\n"
		if test != "\n":
			writenewline = True
	else:
		fout = codecs.open(savefile, "w", "utf-8")
	if len(line.strip())>0:
		if writenewline:
			fout.write(unicode(myNewLine()))
			writenewline = False
		fout.write(line+unicode(myNewLine()))
	fout.close()
	return True


def ImportFile(activefile,importfile):
	fout = codecs.open(importfile, "r", "utf-8")
	importlist = fout.readlines()
	return WriteFile(activefile,importlist)

def ImportCSV(activefile,importfile):
	f = open(importfile, 'rb')
	reader = csv.DictReader(f)
	lines = []
	for item in reader:
		line = ProcessCSVItem(item) + unicode(myNewLine())
		lines.append(line)
	return WriteFile(activefile,lines)

def ProcessCSVItem(item):
	project = ''
	category = ''
	price = ''
	note = ''
	date = ''
	for ei in item.keys():
		kval = ei.lower().strip()
		if kval == 'date' and len(item.get(ei,'').strip())>0:
			date = item.get(ei,'').strip() + ': '
		elif kval == 'note' and len(item.get(ei,'').strip())>0:
			note = item.get(ei,'').strip() + ' '
		elif kval == 'price' and len(item.get(ei,'').strip())>0:
			price = item.get(ei,'').strip() + ' '
			if price[0] != locale.localeconv()['currency_symbol'].strip():
				price = locale.localeconv()['currency_symbol'].strip() + price
		elif kval == 'project' and len(item.get(ei,'').strip())>0:
			project = '+' + item.get(ei,'').strip().replace(' ','_') + ' '
		elif kval == 'category' and len(item.get(ei,'').strip())>0:
			category = '@' + item.get(ei,'').strip().replace(' ','_') + ' '
	finalstring = date + note + price + project + category
	return finalstring.strip()


def main():
	desc = 'Expense.txt is a tool to analyze and keep track your expenses'
	p = optparse.OptionParser(description=desc)
	
	utilities = optparse.OptionGroup(p, 'Utility Options')
	utilities.add_option('--file', '-f', dest="file", help="Define the active file to analyze", default='', metavar='"<File Path>"')
	utilities.add_option('--save', '-s', dest="savefile", help="Save output to a file", default='', metavar='"<File Path>"')
	p.add_option_group(utilities)
	
	addops = optparse.OptionGroup(p, 'Creation Options')
	addops.add_option('--import', '-i', dest="importfile", help="Define a file to import into the active file", default='', metavar='"<File Path>"')
	addops.add_option('--add', '-a', dest="add", help="Add a line to the active file", default='', metavar="'4-19-11:Expense for work $100 @category +project'")
	p.add_option_group(addops)
	
	(options, arguments) = p.parse_args()
	if sys.stdout.encoding != None:
		argumentstring = ' '.join( map( str, arguments ) ).decode(sys.stdout.encoding)
	else:
		argumentstring = ' '.join( map( str, arguments ) )
	if len(options.file.strip())>0:
		mystring = saveActiveFile(options.file.strip())
	if len(options.savefile.strip())>0:
		savefile = isReturnFile(options.savefile.strip())
	else:
		savefile = ''
	if len(options.importfile.strip())>0:
		importfile = fileExists(options.importfile.strip())
	else:
		importfile = ''
	
	activefile = getActiveFile()
	
	
	if len(options.add.strip())>0:
		AddToFile(activefile,options.add.strip())
	
	if len(importfile)>0 and importfile.lower()[-4:]=='.csv':
		ImportCSV(activefile,importfile)
	elif len(importfile)>0:
		ImportFile(activefile,importfile)
		
	results = ParseFile(activefile,argumentstring)
	
	if len(savefile)>0 and savefile.lower()[-4:]=='.csv':
		saveCSV(savefile,results['lines'])
	elif len(savefile)>0:
		WriteFile(savefile,results['lines'])
		print 'File saved to: ' + savefile
	else:	
		PrintList(results['lines'])
		print ""
		print "Total expenses: " + locale.currency(results['total'], grouping=True)

if __name__ == '__main__':
	main()