#!/opt/local/Library/Frameworks/Python.framework/Versions/2.7/Resources/Python.app/Contents/MacOS/Python
import optparse
import os
import platform
import sys
import codecs
import datetime
from dateutil.parser import parse
from dateutil.relativedelta import *
import calendar
from decimal import *

class DepreciationSchedule:
	"""Class to define depreciation schedule for assets"""
	def __init__(self, typofdepreciation='straight line', periods=1, cost=0, residual=0):
		if typofdepreciation.lower().strip() == 'ddb' or typofdepreciation.lower().strip() == 'double':
			self.typofdepreciation = 'ddb'
		elif typofdepreciation.lower().strip() == 'sum-of-years' or typofdepreciation.lower().strip() == 'sum':
			self.typofdepreciation = 'sum'
		elif typofdepreciation.lower().strip() == 'mixed' or typofdepreciation.lower().strip() == 'hybrid':
			self.typofdepreciation = 'mixed'
		else:
			self.typofdepreciation = 'straight'
		
		if self.is_number(periods) and periods>0:
			self.periods = Decimal(str(periods))
		else:
			return False
		
		if self.is_number(cost):
			self.cost = Decimal(str(cost))
		else:
			return False
			
		if self.is_number(residual):
			self.residual = Decimal(str(residual))
		else:
			return False
	
	def is_number(self, s):
		try:
			Decimal(str(s))
			if s>=0:
				return True
			else:
				return False
		except ValueError:
			return False	
	
	def Straight(self):
		cost = Decimal(self.cost-self.residual)
		perperiodcost = round(cost/self.periods, 2)
		lastperiod = round(cost-Decimal(str(perperiodcost))*(self.periods-Decimal('1')), 2)
		schedule = []
		i = 0
		
		while i < int(self.periods):
			i=i+1
			if i == int(self.periods):
				if lastperiod>0:
					schedule.append(lastperiod)
			else:
				if perperiodcost>0:
					schedule.append(perperiodcost)
		
		return schedule
		
	def DDBMixed(self):
		cost = self.cost
		balance = cost
		adjcost = balance - self.residual
		periods = self.periods
		start = Decimal('1.0')
		end = Decimal('2.0')
		percentage = Decimal(end*start/periods)

		schedule = []
		i = 0
		newperiods = int(round(periods/end))
		remainginperiods = int(Decimal(periods - Decimal(newperiods)));
		while i < newperiods:
			i=i+1
			expense = round(balance*percentage, 2)
			if i == int(newperiods) and int(remainginperiods) <= 0:
				expense = round(adjcost, 2)
			elif Decimal(str(expense)) > adjcost and adjcost>0:
				expense = round(adjcost, 2)
			elif Decimal(str(expense)) > adjcost:
				expense = 0			
			if expense>0:
				schedule.append(expense)
				balance = balance - Decimal(str(expense))	
				adjcost = adjcost - Decimal(str(expense))	
		
		if remainginperiods > 0:
			sl = DepreciationSchedule('straight line', remainginperiods, balance, self.residual)
			sld = sl.Schedule()
			schedule.extend(sld)
		return schedule

	def DDB(self):
		cost = self.cost
		balance = cost
		adjcost = balance - self.residual
		periods = self.periods
		start = Decimal('1.0')
		end = Decimal('2.0')
		percentage = end*start/periods
		
		schedule = []
		i = 0
		
		while i < int(periods):
			i=i+1
			expense = round(balance*percentage, 2)
			if i == int(periods):
				expense = round(adjcost, 2)
			elif Decimal(str(expense)) > adjcost and adjcost>0:
				expense = round(adjcost, 2)
			elif Decimal(str(expense)) > adjcost:
				expense = 0			
			if expense>0:
				schedule.append(expense)
				balance = balance - Decimal(str(expense))	
				adjcost = adjcost - Decimal(str(expense))			
		return schedule
	
	def SumYears(self):
		sumofyears = 0
		cost = self.cost-self.residual
		balance = cost
		i = 1;
		cperiod = int(self.periods);
		while i < int(self.periods)+1:
			sumofyears = sumofyears+i
			i=i+1
		sumofyears = Decimal(sumofyears)
		schedule = []
		
		while cperiod > 0:
			percentage = Decimal(cperiod)/sumofyears
			cperiod = cperiod - 1
			expense = round(percentage*cost, 2)
			if cperiod==0:
				expense = round(balance, 2)
			elif Decimal(str(expense)) > balance and balance>0:
				expense = round(balance, 2)
			elif Decimal(str(expense)) > balance:
				expense = 0
			
			if expense>0:
				schedule.append(expense)
				balance = balance - Decimal(str(expense))

		return schedule
	
	def Schedule(self):
		if self.typofdepreciation=='ddb':
			return self.DDB()
		elif self.typofdepreciation=='sum':
			return self.SumYears()
		elif self.typofdepreciation=='mixed':
			return self.DDBMixed()
		else:
			return self.Straight()





currencyvar = "$"

def checkDate(mstr):
    try:
        return parse(mstr).strftime('%Y/%m/%d').strip()
    except Exception as inst:
        return datetime.date.today().isoformat().replace('-','/').strip()

def getDate(mstr):
	try:
		return parse(mstr)
	except Exception as inst:
		return datetime.date.today()


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 SaveFile(transaction,myfile):
	mf = isReturnFile(myfile)
	if os.path.isfile(mf):
		fout = codecs.open(mf, "a", "utf-8")
		fout.write(unicode(myNewLine(), "utf-8"))
	else:
		fout = codecs.open(mf, "w", "utf-8")
	fout.write(unicode(transaction,"utf-8"))
	fout.close()
	print 'Saved File!'


def TestDateRanage(ptype,periods,activedate):
	try:
		if ptype.lower().strip() == 'months' or ptype.lower().strip() == 'month':
			relativedatem = relativedelta(months=+periods)
		elif ptype.lower().strip() == 'days' or ptype.lower().strip() == 'day':
			relativedatem = relativedelta(days=+periods)
		else:
			relativedatem = relativedelta(years=+periods)
		testdate = activedate+relativedatem
		return True;
	except Exception as inst:
		print "Sorry, that date is out of range"
		sys.exit()
		return False;

def GetTransactions(method,cost,residual,asset,expense,payment,periods,dt,note,ptype):
	global currencyvar
	transactions = ""
	transactionobj = DepreciationSchedule(method,periods,cost,residual)
	if transactionobj == False:
		print "Sorry, something about those numbers doesn't make sense!"
		sys.exit()
	else:
		transactionlist = transactionobj.Schedule()
		
	
	startline = checkDate(dt) + " * " + note.strip()
	debitline = "\t" + asset.strip() + "\t\t" + currencyvar.strip() + str(round(cost, 2))
	creditline = "\t" + payment.strip()
	transactions = startline + myNewLine() + debitline + myNewLine() + creditline
	activedate = getDate(dt)
	
	if ptype.lower().strip() == 'months' or ptype.lower().strip() == 'month':
		relativedate = relativedelta(months=+1)
	elif ptype.lower().strip() == 'days' or ptype.lower().strip() == 'day':
		relativedate = relativedelta(days=+1)
	else:
		relativedate = relativedelta(years=+1)
	
	TestDateRanage(ptype.lower().strip(),periods,activedate)
	
	for s in transactionlist:
		activedate=activedate+relativedate
		startline = activedate.strftime('%Y/%m/%d').strip() + " * " + note.strip()
		debitline = "\t" + expense.strip() + "\t\t" + currencyvar.strip() + str(s)
		creditline = "\t" + asset.strip() + ":Accumulated Depreciation"
		transactions = transactions + myNewLine() + startline + myNewLine() + debitline + myNewLine() + creditline
	return transactions
		

def main():
	desc = 'This tool allows you to create a depreciation schedule for Ledger'
	p = optparse.OptionParser(description=desc)
	global currencyvar
	todaysdate = datetime.date.today().isoformat().replace('-','/').strip()
	accounting = optparse.OptionGroup(p, 'Accounting Options')
	accounting.add_option('--method', '-m', dest="method", help="Define the depreciation methodology to use.  Accepts, double declining balance (DDB), sum of the years digits (SUM) or straight-line (straight).  Additionally, you can specify 'mixed' which will use DDB for the first half of the periods and straight-line for the remainder.", default='straight', metavar='"straight"')
	accounting.add_option('--cost', '-c', dest="cost", help="Cost of the asset you want to depreciate", type='float',default=0, metavar='"1000"')
	accounting.add_option('--periods', '-p', dest="periods", help="The number of periods you wish to depreciate over", type='int', default=10, metavar='"10"')
	accounting.add_option('--periodtype', '-t', dest="ptype", help="The type of periods to use (days, months, years)", default='years', metavar='"years"')
	accounting.add_option('--residual', '-r', dest="residual", help="The residual (or scrap) value of the asset", type='float', default=0, metavar='"100"')
	accounting.add_option('--asset', '-a', dest="asset", help="Asset account", default='Assets:Default Asset', metavar='"Assets:Computers"')
	accounting.add_option('--expense', '-e', dest="expense", help="Expense account", default='Equity:Expenses:Depreciation Expense', metavar='"Equity:Expenses:Depreciation Expense"')
	accounting.add_option('--payment', '-o', dest="payment", help="Account that you used to buy the asset", default='Assets:Checking', metavar='"Assets:Checking"')
	accounting.add_option('--date', '-d', dest="date", help="Date of asset purchase", default=todaysdate, metavar='"' + todaysdate + '"')
	accounting.add_option('--note', '-n', dest="note", help="Memo line note", default='Asset Purchase', metavar='"Asset Purchase"')
	
	
	output = optparse.OptionGroup(p, 'Output Options')
	output.add_option('--currency', dest="currency", help="Currency character in data", default='', metavar='"$"')
	output.add_option('--save', '-s', dest="savefile", help="Save output to a file", default='', metavar='"<File Path>"')
	
	p.add_option_group(accounting)
	p.add_option_group(output)
	(options, arguments) = p.parse_args();
	
	if len(options.currency.strip())>0:
		currencyvar = options.currency.strip()
	
	if len(options.asset.strip())>0 and len(options.expense.strip())>0 and len(options.payment.strip())>0 and options.cost>0:
		transactions = GetTransactions(options.method.strip(),options.cost,options.residual,options.asset.strip(),options.expense.strip(),options.payment.strip(),options.periods,options.date.strip(),options.note.strip(),options.ptype.strip())
	else:
		print "You have to specify at least a cost of the asset"
		sys.exit()
	
	if len(options.savefile.strip())>0:
		SaveFile(transactions,options.savefile.strip())
		sys.exit()
	else:
		print unicode(transactions,"utf-8")
		sys.exit()


if __name__ == '__main__':
	main()