from types import StringType, ListType, TupleType
from copy import deepcopy
from Elements import *

DEFAULT_TAB_WIDTH = 720

ParagraphAlignmentMap = { ParagraphPropertySet.LEFT       : 'ql',
						  ParagraphPropertySet.RIGHT      : 'qr',
						  ParagraphPropertySet.CENTER     : 'qc',
						  ParagraphPropertySet.JUSTIFY    : 'qj',
						  ParagraphPropertySet.DISTRIBUTE : 'qd' }

TabAlignmentMap = { TabPropertySet.LEFT    : '',
					TabPropertySet.RIGHT   : 'tqr',
					TabPropertySet.CENTER  : 'tqc',
					TabPropertySet.DECIMAL : 'tqdec' }

TableAlignmentMap = { Table.LEFT   : 'trql',
					  Table.RIGHT  : 'trqr',
					  Table.CENTER : 'trqc' }

CellAlignmentMap = { Cell.ALIGN_TOP            : '', # clvertalt
					 Cell.ALIGN_CENTER         : 'clvertalc',
					 Cell.ALIGN_BOTTOM         : 'clvertalb' }

CellFlowMap = {	Cell.FLOW_LR_TB          : '',           # cltxlrtb, Text in a cell flows from left to right and top to bottom (default)
				Cell.FLOW_RL_TB          : 'cltxtbrl',   # Text in a cell flows right to left and top to bottom
				Cell.FLOW_LR_BT          : 'cltxbtlr',   # Text in a cell flows left to right and bottom to top
				Cell.FLOW_VERTICAL_LR_TB : 'cltxlrtbv',  # Text in a cell flows left to right and top to bottom, vertical
				Cell.FLOW_VERTICAL_TB_RL : 'cltxtbrlv' } # Text in a cell flows top to bottom and right to left, vertical

ShadingPatternMap = { ShadingPropertySet.HORIZONTAL             : 'bghoriz',
					  ShadingPropertySet.VERTICAL               : 'bgvert',
					  ShadingPropertySet.FORWARD_DIAGONAL       : 'bgfdiag',
					  ShadingPropertySet.BACKWARD_DIAGONAL      : 'bgbdiag',
					  ShadingPropertySet.VERTICAL_CROSS         : 'bgcross',
					  ShadingPropertySet.DIAGONAL_CROSS         : 'bgdcross',
					  ShadingPropertySet.DARK_HORIZONTAL        : 'bgdkhoriz',
					  ShadingPropertySet.DARK_VERTICAL          : 'bgdkvert',
					  ShadingPropertySet.DARK_FORWARD_DIAGONAL  : 'bgdkfdiag',
					  ShadingPropertySet.DARK_BACKWARD_DIAGONAL : 'bgdkbdiag',
					  ShadingPropertySet.DARK_VERTICAL_CROSS    : 'bgdkcross',
					  ShadingPropertySet.DARK_DIAGONAL_CROSS    : 'bgdkdcross' }

TabLeaderMap = { TabPropertySet.DOTS		: 'tldot',
				 TabPropertySet.HYPHENS	    : 'tlhyph',
				 TabPropertySet.UNDERLINE	: 'tlul',
				 TabPropertySet.THICK_LINE	: 'tlth',
				 TabPropertySet.EQUAL_SIGN	: 'tleq' }

BorderStyleMap = { BorderPropertySet.SINGLE   : 'brdrs',
				   BorderPropertySet.DOUBLE   : 'brdrth',
				   BorderPropertySet.SHADOWED : 'brdrsh',
				   BorderPropertySet.DOUBLED  : 'brdrdb',
				   BorderPropertySet.DOTTED   : 'brdrdot',
				   BorderPropertySet.DASHED   : 'brdrdash',
				   BorderPropertySet.HAIRLINE : 'brdrhair' }

SectionBreakTypeMap = { Section.NONE   : 'sbknone',
						Section.COLUMN : 'sbkcol',
						Section.PAGE   : 'sbkpage',
						Section.EVEN   : 'sbkeven',
						Section.ODD    : 'sbkodd' }

class Settings( list ) :
	def __init__( self ) :
		super( Settings, self ).__init__()
		self._append = super( Settings, self ).append

	def append( self, value, mask=None, fallback=None ) :
		if (value is not 0) and value in [ False, None, '' ] :
			if fallback : self._append( self, fallback )

		else :
			if mask :
				if value is True :
					value = mask
				else :
					value = mask % value
			self._append( value )

	def Join( self ) :
		if self : return r'\%s' % '\\'.join( self )
		return ''

	def __repr__( self ) :
		return self.Join()

class Renderer :
	def __init__( self, write_custom_element_callback=None ) :
		self.character_style_map = {}
		self.paragraph_style_map = {}
		self.WriteCustomElement  = write_custom_element_callback

	#
	#	All of the Rend* Functions populate a Settings object with values
	#
	def _RendPageProperties( self, section, settings, in_section ) :
		#  this one is different from the others as it takes the settings from a
		if in_section :
			#paper_size_code   = 'psz%s'
			paper_width_code  = 'pgwsxn%s'
			paper_height_code = 'pghsxn%s'
			landscape         = 'lndscpsxn'
			margin_suffix     = 'sxn'

		else :
			#paper_size_code   = 'psz%s'
			paper_width_code  = 'paperw%s'
			paper_height_code = 'paperh%s'
			landscape         = 'landscape'
			margin_suffix     = ''

		#settings.append( section.Paper.Code,   paper_size_code  )
		settings.append( section.Paper.Width,  paper_width_code  )
		settings.append( section.Paper.Height, paper_height_code )

		if section.Landscape :
			settings.append( landscape )

		if section.FirstPageNumber :
			settings.append( section.FirstPageNumber, 'pgnstarts%s' )
			settings.append( 'pgnrestart' )

		self._RendMarginsPropertySet( section.Margins, settings, margin_suffix )

	def _RendShadingPropertySet( self, shading_props, settings, prefix='' ) :
		if not shading_props : return

		settings.append( shading_props.Shading, prefix + 'shading%s' )
		settings.append( ShadingPatternMap.get( shading_props.Pattern, False ) )

		settings.append( self._colour_map.get( shading_props.Foreground, False ), prefix + 'cfpat%s' )
		settings.append( self._colour_map.get( shading_props.Background, False ), prefix + 'cbpat%s' )

	def _RendBorderPropertySet( self, edge_props, settings ) :
		settings.append( BorderStyleMap[ edge_props.Style ] )
		settings.append( edge_props.Width                                , 'brdrw%s'  )
		settings.append( self._colour_map.get( edge_props.Colour, False ), 'brdrcf%s' )
		settings.append( edge_props.Spacing or False                     , 'brsp%s'   )

	def _RendFramePropertySet( self, frame_props, settings, tag_prefix='' ) :
		if not frame_props : return

		if frame_props.Top :
			settings.append( tag_prefix + 'brdrt' )
			self._RendBorderPropertySet( frame_props.Top, settings )

		if frame_props.Left :
			settings.append( tag_prefix + 'brdrl' )
			self._RendBorderPropertySet( frame_props.Left, settings )

		if frame_props.Bottom :
			settings.append( tag_prefix + 'brdrb' )
			self._RendBorderPropertySet( frame_props.Bottom, settings )

		if frame_props.Right :
			settings.append( tag_prefix + 'brdrr' )
			self._RendBorderPropertySet( frame_props.Right, settings )

	def _RendMarginsPropertySet( self, margin_props, settings, suffix='' ) :
		if not margin_props : return

		settings.append( margin_props.Top,    'margt' + suffix + '%s' )
		settings.append( margin_props.Left,   'margl' + suffix + '%s' )
		settings.append( margin_props.Bottom, 'margb' + suffix + '%s' )
		settings.append( margin_props.Right,  'margr' + suffix + '%s' )

	def _RendParagraphPropertySet( self, paragraph_props, settings ) :
		if not paragraph_props : return
		settings.append( ParagraphAlignmentMap[ paragraph_props.Alignment ] )

		settings.append( paragraph_props.SpaceBefore, 'sb%s' )
		settings.append( paragraph_props.SpaceAfter,  'sa%s' )

		#	then we have to find out all of the tabs
		width = 0
		for tab in paragraph_props.Tabs :
			settings.append( TabAlignmentMap[ tab.Alignment ]   )
			settings.append( TabLeaderMap.get( tab.Leader, '' ) )

			width += tab.Width or DEFAULT_TAB_WIDTH
			settings.append( 'tx%s' % width             )

		settings.append( paragraph_props.PageBreakBefore, 'pagebb' )

		settings.append( paragraph_props.FirstLineIndent, 'fi%s'   )
		settings.append( paragraph_props.LeftIndent,      'li%s'   )
		settings.append( paragraph_props.RightIndent,     'ri%s'   )

		if paragraph_props.SpaceBetweenLines :
			if paragraph_props.SpaceBetweenLines < 0 :
				settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult0' )
			else :
				settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult1' )

	def _RendTextPropertySet( self, text_props, settings ) :
		if not text_props : return

		if text_props.Expansion :
			settings.append( text_props.Expansion, 'expndtw%s' )

		settings.append( text_props.Bold,            'b'    )
		settings.append( text_props.Italic,          'i'    )
		settings.append( text_props.Underline,       'ul'   )
		settings.append( text_props.DottedUnderline, 'uld'  )
		settings.append( text_props.DoubleUnderline, 'uldb' )
		settings.append( text_props.WordUnderline,   'ulw'  )

		settings.append( self._font_map.get( text_props.Font, False ), 'f%s' )
		settings.append( text_props.Size, 'fs%s' )
		settings.append( self._colour_map.get( text_props.Colour, False ), 'cf%s' )

		if text_props.Frame :
			frame = text_props.Frame
			settings.append( 'chbrdr' )
			settings.append( BorderStyleMap[ frame.Style ] )
			settings.append( frame.Width                                , 'brdrw%s' )
			settings.append( self._colour_map.get( frame.Colour, False ), 'brdrcf%s' )

	#
	#	All of the Write* functions will write to the internal file object
	#
	#	the _ ones probably don't need to be used by anybody outside
	#	but the other ones like WriteTextElement could be used in the Custom
	#	callback.
	def Write( self, document, fout ) :
		#  write all of the standard stuff based upon the first document
		self._doc  = document
		self._fout = fout
		self._WriteDocument  ()
		self._WriteColours   ()
		self._WriteFonts     ()
		self._WriteStyleSheet()

		settings = Settings()
		self._RendPageProperties( self._doc.Sections[ 0 ], settings, in_section=False )
		self._write( repr( settings ) )

		#  handle the simplest case first, we don't need to do anymore mucking around
		#  with section headers, etc we can just rip the document out
		if len( document.Sections ) == 1 :
			self._WriteSection( document.Sections[ 0 ],
								is_first   = True,
								add_header = False )

		else :
			for section_idx, section in enumerate( document.Sections ) :
				is_first       = section_idx == 0
				add_header     = True
				self._WriteSection( section, is_first, add_header )

		self._write( '}' )

		del self._fout, self._doc, self._CurrentStyle

	def _write( self, data, *params ) :
		#----------------------------------
		# begin modification
		# by Herbert Weinhandl
		# to convert accented characters
		# to their rtf-compatible form
		#for c in range( 128, 256 ) :
		#	data = data.replace( chr(c), "\'%x" % c)
		# end modification
		#
		#  This isn't the right place for this as it is going to do
		#  this loop for all sorts of writes, including settings, control codes, etc.
		#
		#  I will create a def _WriteText (or something) method that is used when the
		#  actual string that is to be viewed in the document is written, this can then
		#  do the final accented character check.
		#
		#  I left it here so that I remember to do the right thing when I have time
		#----------------------------------

		if params : data = data % params
		self._fout.write( data )

	def _WriteDocument( self ) :
		settings = Settings()

		assert Languages.IsValid   ( self._doc.DefaultLanguage )
		assert ViewKind.IsValid    ( self._doc.ViewKind        )
		assert ViewZoomKind.IsValid( self._doc.ViewZoomKind    )
		assert ViewScale.IsValid   ( self._doc.ViewScale       )

		settings.append( self._doc.DefaultLanguage, 'deflang%s'   )
		settings.append( self._doc.ViewKind       , 'viewkind%s'  )
		settings.append( self._doc.ViewZoomKind   , 'viewzk%s'    )
		settings.append( self._doc.ViewScale      , 'viewscale%s' )

		self._write( "{\\rtf1\\ansi\\ansicpg1252\\deff0%s\n" % settings )

	def _WriteColours( self ) :
		self._write( r"{\colortbl ;" )

		self._colour_map = {}
		offset = 0
		for colour in self._doc.StyleSheet.Colours :
			self._write( r'\red%s\green%s\blue%s;', colour.Red, colour.Green, colour.Blue )
			self._colour_map[ colour ] = offset + 1
			offset += 1
		self._write( "}\n" )

	def _WriteFonts( self ) :
		self._write( r'{\fonttbl' )

		self._font_map = {}
		offset = 0
		for font in self._doc.StyleSheet.Fonts :
			pitch     = ''
			panose    = ''
			alternate = ''
			if font.Pitch     : pitch     = r'\fprq%s'    % font.Pitch
			if font.Panose    : panose    = r'{\*\panose %s}' % font.Panose
			if font.Alternate : alternate = r'{\*\falt %s}'   % font.Alternate.Name

			self._write( r'{\f%s\f%s%s\fcharset%s%s %s%s;}',
						 offset,
						 font.Family,
						 pitch,
						 font.CharacterSet,
						 panose,
						 font.Name,
						 alternate )

			self._font_map[ font ] = offset
			offset += 1

		self._write( "}\n" )

	def _WriteStyleSheet( self ) :
		self._write( r"{\stylesheet" )

		#	TO DO: character styles, does anybody actually use them?

		offset_map = {}
		for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) :
			offset_map[ style ] = idx

		#	paragraph styles
		self.paragraph_style_map = {}
		for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) :

			if idx == 0 :
				default = style
			else :
				self._write( '\n' )

			settings = Settings()

			#	paragraph properties
			self._RendParagraphPropertySet( style.ParagraphPropertySet, settings )
			self._RendFramePropertySet    ( style.FramePropertySet,     settings )
			self._RendShadingPropertySet  ( style.ShadingPropertySet,   settings )

			#	text properties
			self._RendTextPropertySet   ( style.TextStyle.TextPropertySet,     settings )
			self._RendShadingPropertySet( style.TextStyle.ShadingPropertySet,  settings )

			#	have to take
			based_on = '\\sbasedon%s' % offset_map.get( style.BasedOn, 0 )
			next     = '\\snext%s'    % offset_map.get( style.Next,    0 )

			inln = '\\s%s%s' % ( idx, settings )
			self._write( "{%s%s%s %s;}", inln, based_on, next, style.Name )

			self.paragraph_style_map[ style ] = inln

		#	if now style is specified for the first paragraph to be written, this one
		#	will be used
		self._CurrentStyle = self.paragraph_style_map[ default ]

		self._write( "}\n" )

	def _WriteSection( self, section, is_first, add_header ) :

		def WriteHF( hf, rtfword ) :
			#if not hf : return 
			
			#  if we don't have anything in the header/footer then include
			#  a blank paragraph, this stops it from picking up the header/footer
			#  from the previous section
			# if not hf :	hf = [ Paragraph( '' ) ]
			if not hf :	hf = []

			self._write( '{\\%s' % rtfword )
			self._WriteElements( hf )
			self._write( '}\n' )

		settings = Settings()

		if not is_first :
			#  we need to finish off the preceding section
			#  and reset all of our defaults back to standard
			settings.append( 'sect'  )

		#  reset to our defaults
		settings.append( 'sectd' )

		if add_header :
			settings.append( SectionBreakTypeMap[ section.BreakType ] )
			self._RendPageProperties( section, settings, in_section=True )

		settings.append( section.HeaderY, 'headery%s' )
		settings.append( section.FooterY, 'footery%s' )

		#  write all of these out now as we need to do a write elements in the
		#  next section
		self._write( repr( settings ) )

		#	finally after all that has settled down we can do the
		#	headers and footers
		if section.FirstHeader or section.FirstFooter :
			#  include the titlepg flag if the first page has a special format
			self._write( r'\titlepg' )
			WriteHF( section.FirstHeader, 'headerf' )
			WriteHF( section.FirstFooter, 'footerf' )

		WriteHF( section.Header, 'header' )
		WriteHF( section.Footer, 'footer' )

		#	and at last the contents of the section that actually appear on the page
		self._WriteElements( section )

	def _WriteElements( self, elements ) :
		new_line = ''
		for element in elements :
			self._write( new_line )
			new_line = '\n'

			clss = element.__class__

			if clss == Paragraph :
				self.WriteParagraphElement( element )

			elif clss == Table :
				self.WriteTableElement( element )

			elif clss == StringType :
				self.WriteParagraphElement( Paragraph( element ) )

			elif clss in [ RawCode, Image ] :
				self.WriteRawCode( element )

			#elif clss == List  :
			#	self._HandleListElement( element )

			elif self.WriteCustomElement :
				self.WriteCustomElement( self, element )

			else :
				raise Exception( "Don't know how to handle elements of type %s" % clss )

	def WriteParagraphElement( self, paragraph_elem, tag_prefix='', tag_suffix=r'\par', opening='{', closing='}' ) :

		#	the tag_prefix and the tag_suffix take care of paragraphs in tables.  A
		#	paragraph in a table requires and extra tag at the front (intbl) and we
		#	don't want the ending tag everytime.  We want it for all paragraphs but
		#	the last.

		overrides = Settings()
		self._RendParagraphPropertySet( paragraph_elem.Properties, overrides )
		self._RendFramePropertySet    ( paragraph_elem.Frame,      overrides )
		self._RendShadingPropertySet  ( paragraph_elem.Shading,    overrides )

		#	when writing the RTF the style is carried from the previous paragraph to the next,
		#	so if the currently written paragraph has a style then make it the current one,
		#	otherwise leave it as it was
		self._CurrentStyle = self.paragraph_style_map.get( paragraph_elem.Style, self._CurrentStyle )

		self._write( r'%s\pard\plain%s %s%s ' % ( opening, tag_prefix, self._CurrentStyle, overrides ) )

		for element in paragraph_elem :

			if isinstance( element, StringType ) :
				self._write( element )

			elif isinstance( element, RawCode ) :
				self._write( element.Data )

			elif isinstance( element, Text ) :
				self.WriteTextElement( element )

			elif isinstance( element, Inline ) :
				self.WriteInlineElement( element )

			elif element == TAB :
				self._write( r'\tab ' )

			elif element == LINE :
				self._write( r'\line ' )

			elif self.WriteCustomElement :
				self.WriteCustomElement( self, element )

			else :
				raise Exception( 'Don\'t know how to handle %s' % element )

		self._write( tag_suffix + closing )

	def WriteRawCode( self, raw_elem ) :
		self._write( raw_elem.Data )

	def WriteTextElement( self, text_elem ) :
		overrides = Settings()

		self._RendTextPropertySet   ( text_elem.Properties, overrides )
		self._RendShadingPropertySet( text_elem.Shading,    overrides, 'ch' )

		#	write the wrapper and then let the custom handler have a go
		if overrides : self._write( '{%s ' % repr( overrides ) )

		#	if the data is just a string then we can now write it
		if isinstance( text_elem.Data, StringType ) :
			self._write( text_elem.Data or '' )

		elif text_elem.Data == TAB :
			self._write( r'\tab ' )

		else :
			self.WriteCustomElement( self, text_elem.Data )

		if overrides : self._write( '}' )

	def WriteInlineElement( self, inline_elem ) :
		overrides = Settings()

		self._RendTextPropertySet   ( inline_elem.Properties, overrides )
		self._RendShadingPropertySet( inline_elem.Shading,    overrides, 'ch' )

		#	write the wrapper and then let the custom handler have a go
		if overrides : self._write( '{%s ' % repr( overrides ) )

		for element in inline_elem :
			#	if the data is just a string then we can now write it
			if isinstance( element, StringType ) :
				self._write( element )

			elif isinstance( element, RawCode ) :
				self._write( element.Data )

			elif element == TAB :
				self._write( r'\tab ' )

			elif element == LINE :
				self._write( r'\line ' )

			else :
				self.WriteCustomElement( self, element )

		if overrides : self._write( '}' )

	def WriteText( self, text ) :
		self._write( text or '' )

	def WriteTableElement( self, table_elem ) :

		vmerge = [ False ] * table_elem.ColumnCount
		for height, cells in table_elem.Rows :

			#	calculate the right hand edge of the cells taking into account the spans
			offset   = table_elem.LeftOffset or 0
			cellx    = []
			cell_idx = 0
			for cell in cells :
				cellx.append( offset + sum( table_elem.ColumnWidths[ : cell_idx + cell.Span ] ) )
				cell_idx += cell.Span

			self._write( r'{\trowd' )

			settings = Settings()

			#	the spec says that this value is mandatory and I think that 108 is the default value
			#	so I'll take care of it here
			settings.append( table_elem.GapBetweenCells or 108, 'trgaph%s' )
			settings.append( TableAlignmentMap[ table_elem.Alignment ] )
			settings.append( height, 'trrh%s' )
			settings.append( table_elem.LeftOffset, 'trleft%s' )

			width = table_elem.LeftOffset or 0
			for idx, cell in enumerate( cells ) :
				self._RendFramePropertySet  ( cell.Frame,   settings, 'cl' )

				#  cells don't have margins so I don't know why I was doing this
				#  I think it might have an affect in some versions of some WPs.
				#self._RendMarginsPropertySet( cell.Margins, settings, 'cl' )

				#  if we are starting to merge or if this one is the first in what is 
				#  probably a series of merges then start the vertical merging
				if cell.StartVerticalMerge or (cell.VerticalMerge and not vmerge[ idx ]) :
					settings.append( 'clvmgf' )
					vmerge[ idx ] = True

				elif cell.VerticalMerge :
					#..continuing a merge
					settings.append( 'clvmrg' )

				else :
					#..no merging going on so make sure that it is off
					vmerge[ idx ] = False					

				#  for any cell in the next row that is covered by this span we
				#  need to run off the vertical merging as we don't want them
				#  merging up into this spanned cell
				for vmerge_idx in range( idx + 1, idx + cell.Span - 1 ) :
					vmerge[ vmerge_idx ] = False

				settings.append( CellAlignmentMap[ cell.Alignment ] )
				settings.append( CellFlowMap[ cell.Flow ] )

				#  this terminates the definition of a cell and represents the right most edge of the cell from the left margin
				settings.append( cellx[ idx ], 'cellx%s' )

			self._write( repr( settings ) )

			for cell in cells :
				if len( cell ) :				
					last_idx = len( cell ) - 1
					for element_idx, element in enumerate( cell ) :
						#	wrap plain strings in paragraph tags
						if isinstance( element, StringType ) :
							element = Paragraph( element )

						#	don't forget the prefix or else word crashes and does all sorts of strange things
						if element_idx == last_idx :
							self.WriteParagraphElement( element, tag_prefix=r'\intbl', tag_suffix='', opening='', closing='' )

						else :
							self.WriteParagraphElement( element, tag_prefix=r'\intbl', opening='', closing='' )

					self._write( r'\cell' )

				else :
					self._write( r'\pard\intbl\cell' )

			self._write( '\\row}\n' )
