import struct
import numpy
from MatlabFuncs import *
from MatlabProcessPatch import *

def parseresultsfromdisk(filename,iosplit):
	"""
	PARSERESULTSFROMDISK - ...
	 
	    Usage:
	       results=parseresultsfromdisk(filename,iosplit)
	"""

	if iosplit:
		results=parseresultsfromdiskiosplit(filename)
	else:
		results=parseresultsfromdiskioserial(filename)

	#process patch if necessary
	results=MatlabProcessPatch(results)

	return results

def parseresultsfromdiskioserial(filename):    # {{{
	"""
	PARSERESULTSFROMDISK - ...
	 
	    Usage:
	       results=parseresultsfromdiskioserial(filename)
	"""

	#Open file
	try:
		fid=open(filename,'rb')
	except IOError as e:
		raise IOError("loadresultsfromdisk error message: could not open '%s' for binary reading." % filename)

	results={}

	#Read fields until the end of the file.
	result=ReadData(fid)
	while result:
		#Get time and step
		if not result['step'] in results:
			results[result['step']]={}
			results[result['step']]['step']=result['step']
			results[result['step']]['time']=result['time'] 
	
		#Add result
		if result['step'] in results and \
		   result['fieldname'] in results[result['step']] and \
		   not strcmp(result['fieldname'],'SolutionType'):
			results[result['step']][result['fieldname']]=numpy.concatenate((results[result['step']][result['fieldname']],result['field']),axis=0)
		else:
			results[result['step']][result['fieldname']]=result['field']

		#read next result
		result=ReadData(fid)

	fid.close()

	return results
	# }}}

def parseresultsfromdiskiosplit(filename):    # {{{
	"""
	PARSERESULTSFROMDISKIOSPLIT - ...
	 
	    Usage:
	       results=parseresultsfromdiskiosplit(filename)
	"""

	#Open file
	try:
		fid=open(filename,'rb')
	except IOError as e:
		raise IOError("loadresultsfromdisk error message: could not open '%s' for binary reading." % filename)

	results={}

	#if we have done split I/O, ie, we have results that are fragmented across patches, 
	#do a first pass, and figure out the structure of results
	result=ReadDataDimensions(fid)
	while result:

		#Get time and step
		if not result['step'] in results:
			results[result['step']]={}
			results[result['step']]['step']=result['step']
			results[result['step']]['time']=result['time'] 

		#Add result
		if strcmpi(result['fieldname'],'Patch'):
			results[result['step']][result['fieldname']]=[0,result['N']]
		else:
			results[result['step']][result['fieldname']]=float('NaN')

		#read next result
		result=ReadDataDimensions(fid)

	#do a second pass, and figure out the size of the patches
	fid.seek(0)    #rewind
	result=ReadDataDimensions(fid)
	while result:

		#Add result
		if strcmpi(result['fieldname'],'Patch'):
			patchdimensions=results[result['step']][result['fieldname']]
			results[result['step']][result['fieldname']]=[patchdimensions[0]+result['M'],result['N']]

		#read next result
		result=ReadDataDimensions(fid)

	#allocate patches
	for result in results.itervalues():
		if 'Patch' in result:
			result['Patch']=numpy.zeros(shape=(result['Patch'][0],result['Patch'][1]),dtype=float)
			result['counter']=0    #use to index into the patch

	#third pass, this time to read the real information
	fid.seek(0)    #rewind
	result=ReadData(fid)
	while result:

		#Get time and step
		if not result['step'] in results:
			results[result['step']]={}
			results[result['step']]['step']=result['step']
			results[result['step']]['time']=result['time'] 

		#Add result
		if strcmpi(result['fieldname'],'Patch'):
			counter=results[result['step']]['counter']
			counter2=counter+result['field'].shape[0]-1
			results[result['step']][result['fieldname']][counter:counter2,:]=result['field']

			#increment counter: 
			results[result['step']]['counter']=counter2+1
		else:
			results[result['step']][result['fieldname']]=result['field']

		#read next result
		result=ReadData(fid)

	#close file
	fid.close()

	return results
	# }}}

def ReadData(fid):    # {{{
	"""
	READDATA - ...
	 
	    Usage:
	       field=ReadData(fid)
	"""

	#read field
	try:
		length=struct.unpack('i',fid.read(struct.calcsize('i')))[0]

		fieldname=struct.unpack('%ds' % length,fid.read(length))[0][:-1]
		time=struct.unpack('d',fid.read(struct.calcsize('d')))[0]
		step=struct.unpack('i',fid.read(struct.calcsize('i')))[0]

		type=struct.unpack('i',fid.read(struct.calcsize('i')))[0]
		M=struct.unpack('i',fid.read(struct.calcsize('i')))[0]
		if   type==1:
			field=numpy.array(struct.unpack('%dd' % M,fid.read(M*struct.calcsize('d'))),dtype=float)
		elif type==2:
			field=struct.unpack('%ds' % M,fid.read(M))[0][:-1]
		elif type==3:
			N=struct.unpack('i',fid.read(struct.calcsize('i')))[0]
#			field=transpose(fread(fid,[N M],'double'));
			field=numpy.zeros(shape=(M,N),dtype=float)
			for i in xrange(M):
				field[i,:]=struct.unpack('%dd' % N,fid.read(N*struct.calcsize('d')))
		else:
			raise TypeError("cannot read data of type %d" % type)

		result={}
		result['fieldname']=fieldname
		result['time']=time
		result['step']=step
		result['field']=field

	except struct.error as e:
		result={}

	return result
	# }}}

def ReadDataDimensions(fid):    # {{{
	"""
	READDATADIMENSIONS - read data dimensions, step and time, but not the data itself.
	 
	    Usage:
	       field=ReadDataDimensions(fid)
	"""

	#read field
	try:
		length=struct.unpack('i',fid.read(struct.calcsize('i')))[0]

		fieldname=struct.unpack('%ds' % length,fid.read(length))[0][:-1]
		time=struct.unpack('d',fid.read(struct.calcsize('d')))[0]
		step=struct.unpack('i',fid.read(struct.calcsize('i')))[0]

		type=struct.unpack('i',fid.read(struct.calcsize('i')))[0]
		M=struct.unpack('i',fid.read(struct.calcsize('i')))[0]
		N=1    #default
		if   type==1:
			fid.seek(M*8,1)
		elif type==2:
			fid.seek(M,1)
		elif type==3:
			N=struct.unpack('i',fid.read(struct.calcsize('i')))[0]
			fid.seek(N*M*8,1)
		else:
			raise TypeError("cannot read data of type %d" % type)

		result={}
		result['fieldname']=fieldname
		result['time']=time
		result['step']=step
		result['M']=M
		result['N']=N

	except struct.error as e:
		result={}

	return result
	# }}}

