#!/usr/local/bin/python
# coding: iso-8859-1
# Copyright © 2009 by Amos Newcombe
# 
# This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along with this program.  If not, see <http://www.gnu.org/licenses/>.

'''a group of related series, for which statistics can be caklculated and graphs drawn
'''

# ¤ Resources

from mathlib.FiniteSeries.Graph.Axis import Axis

# ¤ Definitions

class FiniteSeries:
	'''a group of related series, for which statistics can be caklculated and graphs drawn
	'''
	
	# ¤ Creation
	
	def __init__(self, ldxData, ddMeta=None, **dMeta):
# 		print '%s(data[:%d], %s, %s)' % (self.__class__.__name__, len(ldxData), ddMeta, dMeta)
		if ddMeta == None: ddMeta = {'_instance': {}}
		ddMeta.setdefault('_instance', {}).update(dMeta)
# 		print '%s(ldx[0:%d], %s)' % (self.__class__.__name__, len(ldxData), ddMeta)
		self.setData(ldxData)
		self.ddMeta = ddMeta
	
	def setData(self, ldxData): self.ldxData = ldxData; self.cData = len(ldxData)
	def getData(self): return self.ldxData
	def getDatum(self, i, s): 
		dx = self.ldxData[i] if isinstance(i, (int, long)) else dict(i)
		return dx[s] if dx[s] != None else ''
	def getFields(self): return self.getData()[-1].keys()
	
	def setMeta(self, sName, oKey, oValue=None):
		self.getMeta(sName).update(
			{oKey: oValue} 
			if isinstance(oKey, str) 
			else dict(oKey)
		)
	
	def getMeta(self, sName, sKey=None, oDefault=None):
		d = self.ddMeta.setdefault(sName, {})
		if sKey == None: return d
		else           : return d.get(sKey, oDefault)
	
	def getSeries(self, sName):
		nOffset = self.getMeta(sName, 'nOffset', 0)
		return [dx[sName] for dx in self.ldxData[nOffset:]]
	
	# ¤ Description
	
	def __str__(self, lsKeys=None):
		if lsKeys == None: lsKeys = self.ldxData[0].keys()
		cch = max([len(s) for s in self.ddMeta['_instance'].keys()])
		return '\n'.join(
			[
				'%-*s: %s' % ((cch,) + tp)
				for tp in self.ddMeta['_instance'].items()
			]                   + \
			['']                + \
			['\t'.join(lsKeys)] + \
			[
				'\t'.join([str(self.getDatum(dx, s)) for s in lsKeys])
				for dx in self.ldxData
			]
		)
	
	# ¤ Operation: creating new series
	
	def AddSeries(self, sName, lx):
# 		print '%s.AddSeries(%s, l[%d])' % (self.__class__.__name__, sName, len(lx))
		for i in range(self.cData): self.ldxData[i][sName] = lx[i]
	
	def Transform(self, sName, f=None, sNameTrans=None):
		if f == None:
			f = lambda x: x
			sCalc = sName + ' copy'
		else:
			try: sf = f.__name__
			except AttributeError: sf = 'f'
			sCalc = '%s(%s)' % (sf, sName)
		if sNameTrans == None: sNameTrans = sCalc
		nOffset = self.getMeta(sName, 'nOffset', 0)
		for i in range(self.cData):
			if i < nOffset:
				self.ldxData[i][sNameTrans] = None
			else:
				self.ldxData[i][sNameTrans] = f(self.getDatum(i, sName))
		self.setMeta(sNameTrans, {'_calc': sCalc, 'nOffset': nOffset})

	def Delta(self, sName, n=1, sNameDelta=None):
# 		print 'Delta(%s, %d, %s)' % (sName, n, sNameDelta)
		sCalc = 'Delta ' + (str(n) if n != 1 else '') + sName
		if sNameDelta == None: sNameDelta = sCalc
		nOffset = self.getMeta(sName, 'nOffset', 0) + n
		for i in range(self.cData):
			if i < nOffset:
				self.ldxData[i][sNameDelta] = None
			else:
				self.ldxData[i][sNameDelta] = self.getDatum(i, sName) - self.getDatum(i-n, sName)
		self.setMeta(sNameDelta, {'_calc': sCalc, 'nOffset': nOffset})
	
	# ¤ Operation: Statistics
	
	def Statistic(self, classStat, tpsFields, sName=None, *tpArgs, **dArgs):
		if isinstance(tpsFields, str): tpsFields = (tpsFields,)
		if sName == None: sName = classStat.__name__
		llxData = [self.getSeries(s) for s in tpsFields]
		lData = lxData[0] if len(lxData) == 1 else llxData
		stat = classStat(lData, *tpArgs, **dArgs)
		for s in tpsFields: self.setMeta(s, sName, stat)
	
	# ¤ÊOperation: graphs

# ¤ÊGraph writers

def WriteGraphObject(path, g, sMsg=None, **dAttr):
	'Write a graph to a local file and return the html object tag that calls it.'
	if not sMsg: sMsg = '[Can\'t show %s]' % path
	open(path, 'w').write(str(g))
	return '<object data="%s"%s>%s</object>' % \
		(path, ''.join([' %s="%s"' % tp for tp in dAttr.items()]), sMsg)

# ¤ File Readers
	
def ReadNumbers(path, **dMeta): 
	'''Assume the first word on each line is a number, and pair it up with its (zero-based) line number.
	
	The field names are t for the line number and x for the number.'''
	dMetaT = {'src': path, '_independent': 't'}
	dMetaT.update(dMeta)
	return (
		[
			{'t': float(i), 'x': float(s.split(None, 1)[0])} 
			for i, s in enumerate(open(path, 'rU'))
		], 
		{'_instance': dMetaT},
	)

if __name__ == '__main__':
	
	import sys
	from math import log
	from Fred import FredTxt
	from mathlib.FiniteSeries.Graph.Graph import Graph
		
	def MakeGraph(fs, lsY, 
		dHScale        , dVScale, 
		gridH=None     , gridV=None, 
		isHDataIn=False, isVDataIn=False, 
	):
		sX = fs.ddMeta['_instance']['_independent']
		g = Graph(fs, **fs.ddMeta['_instance'])
		g.AddAxes(
			Axis('H', g.fsData, [sX], gridH, isHDataIn).setTransform(**dHScale),
			Axis('V', g.fsData, lsY , gridV, isVDataIn).setTransform(**dVScale),
		)
		g.TransformData()
		# BUG: are g.dArgs['width'] and g.dArgs['height'] defined? No.
		g.dArgs.setdefault('width' , g.llpxExtremes[0][1] - g.llpxExtremes[0][0])
		g.dArgs.setdefault('height', g.llpxExtremes[1][1] - g.llpxExtremes[1][0])
# 		print fs.getMeta('_instance')
		g.Assemble()
		return g
	
	FiniteSeries(*ReadNumbers('BookData/WINE.DAT'))
	fs = FiniteSeries(*FredTxt('Data/USRetail/RSAFSNA.txt'))
	fs.Transform('value', log, 'X')
	fs.Delta('X', 12, 'DeltaYrX')
	fs.Delta('DeltaYrX', 1, 'DeltaMoDeltaYrX')
	open('RSAFSNA.txt', 'w').write(str(fs))
# 	FiniteSeries(*FredTxt('Data/ADJRAM.txt'))
