# Copyright (c) 2010-2022 Belledonne Communications SARL.
#
# This file is part of Liblinphone 
# (see https://gitlab.linphone.org/BC/public/liblinphone).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.



import abstractapi
import logging
import metaname
import re
import sys


class ParsingError(RuntimeError):
	pass


class UnreleasedNodeError(ValueError):
	pass


class ChildrenList(list):
	def __init__(self, node):
		list.__init__(self)
		self.node = node
	
	def __setitem__(self, key, child):
		if child.parent is not None:
			raise UnreleasedNodeError()
		self[key].parent = None
		list.__setitem__(self, key, child)
		child.parent = self.node
	
	def __delitem__(self, key):
		self[key].parent = None
		list.__delitem__(self, key)
	
	def __iadd__(self, other):
		list.__iadd__(self, other)
		for child in other:
			child.parent = self.node
		return self
	
	def append(self, child):
		list.append(self, child)
		child.parent = self.node
	
	def removeall(self):
		children = []
		while len(self) > 0:
			children.append(self[0])
			del self[0]
		return children


class TreeNode:
	def __init__(self):
		self.parent = None
	
	def find_ancestor(self, ancestorType):
		ancestor = self.parent
		while ancestor is not None and type(ancestor) is not ancestorType:
			ancestor = ancestor.parent
		return ancestor
	
	def find_root(self):
		node = self
		while node.parent is not None:
			node = node.parent
		return node


class SingleChildTreeNode(TreeNode):
	def __init__(self):
		TreeNode.__init__(self)
		self._child = None
	
	def _setchild(self, child):
		if child is not None and child.parent is not None:
			raise UnreleasedNodeError()
		if self._child is not None:
			self._child.parent = None
		self._child = child
		if child is not None:
			child.parent = self
	
	def _getchild(self):
		return self._child
	
	def _delchild(self):
		if self._child is not None:
			self._child.parent = None
		del self._child
	
	child = property(fset=_setchild, fget=_getchild, fdel=_delchild)


class MultiChildTreeNode(TreeNode):
	def __init__(self):
		TreeNode.__init__(self)
		self.children = ChildrenList(self)


class ParagraphPart(TreeNode):
	pass


class TextPart(ParagraphPart):
	def __init__(self, text):
		ParagraphPart.__init__(self)
		self.text = text
	
	def translate(self, docTranslator, **kargs):
		return docTranslator.translate_text(self)


class LanguageKeyword(ParagraphPart):
	def __init__(self, keyword):
		ParagraphPart.__init__(self)
		self.keyword = keyword
	
	def translate(self, docTranslator, **kargs):
		return docTranslator.translate_keyword(self)


class Reference(ParagraphPart):
	def __init__(self, cname):
		ParagraphPart.__init__(self)
		self.cname = cname
		self.relatedObject = None

	@staticmethod
	def make_ref_from_object(cname, obj):
		if isinstance(obj, (abstractapi.Enum, abstractapi.Enumerator, abstractapi.Class, abstractapi.Interface)):
			ref = ClassReference(cname)
		elif isinstance(obj, abstractapi.Method):
			ref = FunctionReference(cname)
		else:
			raise TypeError('cannot create documentation reference from {0}'.format(str(obj)))
		ref.relatedObject = obj
		return ref


class ClassReference(Reference):
	def translate(self, docTranslator, **kargs):
		if self.relatedObject is None:
			raise ReferenceTranslationError(self.cname)
		return docTranslator.translate_class_reference(self, **kargs)

	def resolve(self, api):
		for index in api.enumsIndex, api.enumeratorsIndex, api.classesIndex, api.interfacesIndex:
			if self.cname in index:
				self.relatedObject = index[self.cname]
				break
		if self.relatedObject is None:
			logging.warning('doc reference pointing on an unknown object ({0})'.format(self.cname))


class FunctionReference(Reference):
	def translate(self, docTranslator, **kargs):
		if self.relatedObject is None:
			raise ReferenceTranslationError(self.cname)
		return docTranslator.translate_function_reference(self, **kargs)

	def resolve(self, api):
		try:
			self.relatedObject = api.methodsIndex[self.cname]
		except KeyError:
			logging.warning('doc reference pointing on an unknown object ({0})'.format(self.cname))


class Paragraph(MultiChildTreeNode):
	@property
	def parts(self):
		return self.children
	
	@parts.setter
	def parts(self, parts):
		self.children = parts
	
	def resolve_all_references(self, api):
		for part in self.parts:
			if isinstance(part, Reference):
				part.resolve(api)
			elif isinstance(part, (Section, ParameterList)):
				part.resolve_all_references(api)
	
	def translate(self, docTranslator, **kargs):
		return docTranslator._translate_paragraph(self, **kargs)


class Section(SingleChildTreeNode):
	def __init__(self, kind):
		SingleChildTreeNode.__init__(self)
		self.kind = kind
	
	@property
	def paragraph(self):
		return self.child
	
	@paragraph.setter
	def paragraph(self, paragraph):
		self.child = paragraph
	
	def resolve_all_references(self, api):
		if self.paragraph is not None:
			self.paragraph.resolve_all_references(api)
	
	def translate(self, docTranslator, **kargs):
		return docTranslator._translate_section(self, **kargs)


class ParameterDescription(SingleChildTreeNode):
	def __init__(self, name, desc):
		SingleChildTreeNode.__init__(self)
		self.name = name
		self.child = desc
	
	@property
	def desc(self):
		return self.child
	
	@desc.setter
	def desc(self, desc):
		self.child = desc
	
	def is_self_parameter(self):
		method = self.find_ancestor(Description).relatedObject
		return method.type == abstractapi.Method.Type.Instance and self.name not in [arg.name for arg in method.args]


class ParameterList(MultiChildTreeNode):
	@property
	def parameters(self):
		return self.children
	
	@parameters.setter
	def parameters(self, parameters):
		self.children = parameters
	
	def resolve_all_references(self, api):
		for parameter in self.parameters:
			if parameter.desc is not None:
				parameter.desc.resolve_all_references(api)
	
	def translate(self, docTranslator, **kargs):
		return docTranslator._translate_parameter_list(self, **kargs)


class Description(MultiChildTreeNode):
	def __init__(self):
		MultiChildTreeNode.__init__(self)
		self.relatedObject = None
	
	@property
	def paragraphs(self):
		return self.children
	
	@paragraphs.setter
	def paragraphs(self, paragraphs):
		self.children = paragraphs
	
	def resolve_all_references(self, api):
		for paragraph in self.paragraphs:
			paragraph.resolve_all_references(api)
	
	def translate(self, translator, **kargs):
		return translator.translate_description(self, **kargs)


class Parser:
	def __init__(self):
		self.constants_regex = re.compile('(?:^|\W)(TRUE|FALSE|NULL)(?:\W|$)')
	
	def parse_description(self, node):
		if node is None:
			return None
		
		desc = Description()
		for paraNode in node.findall('./para'):
			desc.paragraphs += self._parse_paragraph(paraNode)
		return desc
	
	def _parse_paragraph(self, node):
		paragraphs = []
		paragraph = Paragraph()
		
		text = node.text
		if text is not None:
			paragraph.parts += self._parse_text(text)
		
		for partNode in node.findall('*'):
			if partNode.tag == 'ref':
				ref = self._parse_reference(partNode)
				if ref is not None:
					paragraph.parts.append(ref)
			elif partNode.tag == 'simplesect':
				paragraphs.append(paragraph)
				paragraph.parts.append(self._parse_simple_section(partNode))
				paragraph = Paragraph()
			elif partNode.tag == 'xrefsect':
				paragraphs.append(paragraph)
				paragraph.parts.append(self._parse_xref_section(partNode))
				paragraph = Paragraph()
			elif partNode.tag == 'parameterlist' and partNode.get('kind') == 'param':
				paragraphs.append(paragraph)
				paragraphs.append(self._parse_parameter_list(partNode))
				paragraph = Paragraph()
			elif partNode.tag == 'bctbxlist':
				pass
			else:
				text = partNode.text
				if text is not None:
					paragraph.parts += self._parse_text(text)
			
			text = partNode.tail
			if text is not None:
				text = text.strip('\n')
				if len(text) > 0:
					paragraph.parts += self._parse_text(text)
		
		paragraphs.append(paragraph)
		return [x for x in paragraphs if type(x) is not Paragraph or len(x.parts) > 0]
	
	def _parse_text(self, text):
		parts = []
		lastIndex = 0
		
		match = self.constants_regex.search(text)
		while match is not None:
			if match.start(1) == 0 and lastIndex == 0:
				# Special case when text starts by constant
				parts.append(self._parse_constant(text[match.start(1):match.end(1)]))
			elif match.start(1)-lastIndex > 0:
				parts.append(TextPart(text[lastIndex:match.start(1)]))
				parts.append(self._parse_constant(text[match.start(1):match.end(1)]))
			lastIndex = match.end(1)
			match = self.constants_regex.search(text, lastIndex)
		
		if lastIndex < len(text):
			parts.append(TextPart(text[lastIndex:]))
		
		return parts
	
	def _parse_constant(self, token):
		if token == 'TRUE':
			return LanguageKeyword(abstractapi.Boolean(True))
		elif token == 'FALSE':
			return LanguageKeyword(abstractapi.Boolean(False))
		elif token == 'NULL':
			return LanguageKeyword(abstractapi.Nil())
		else:
			raise ValueError("invalid C constant token '{0}'".format(token))
	
	def _parse_simple_section(self, sectionNode):
		section = Section(sectionNode.get('kind'))
		para = sectionNode.find('./para')
		paragraphs = self._parse_paragraph(para)
		section.paragraph = paragraphs[0] if len(paragraphs) > 0 else None
		return section
	
	def _parse_parameter_list(self, paramListNode):
		paramList = ParameterList()
		for paramItemNode in paramListNode.findall('./parameteritem'):
			name = metaname.ArgName()
			name.from_snake_case(paramItemNode.find('./parameternamelist/parametername').text)
			desc = self.parse_description(paramItemNode.find('parameterdescription'))
			paramList.parameters.append(ParameterDescription(name, desc))
		return paramList
	
	def _parse_xref_section(self, sectionNode):
		sectionId = sectionNode.get('id')
		if sectionId.startswith('deprecated_'):
			section = Section('deprecated')
			description = self.parse_description(sectionNode.find('./xrefdescription'))
			paras = description.paragraphs.removeall()
			section.paragraph = paras[0] if len(paras) > 0 else None
			return section
		else:
			raise ParsingError('unknown xrefsect type ({0})'.format(sectionId))
	
	def _parse_reference(self, node):
		if node.text.endswith('()'):
			return FunctionReference(node.text[0:-2])
		else:
			return ClassReference(node.text)


class TranslationError(Exception):
	pass


class ReferenceTranslationError(TranslationError):
	def __init__(self, refName):
		Exception.__init__(self, refName)
	
	def msg(self):
		return '{0} reference could not been translated'.format(self.args[0])


class Translator:
	def __init__(self, langCode):
		self.langCode = langCode
		self.textWidth = 80
		self.nameTranslator = metaname.Translator.get(langCode)
		self.langTranslator = abstractapi.Translator.get(langCode)
		self.refNameTranslator = self.nameTranslator
		self.displaySelfParam = True if langCode == 'C' else False
	
	def translate_description(self, description, tagAsBrief=False):
		if description is None:
			return None
		
		paras = self._translate_description(description)
		
		lines = self._paragraphs_to_lines(paras)
		
		if tagAsBrief:
			self._tag_as_brief(lines)
		
		lines = self._crop_text(lines, self.textWidth)
		
		translatedDoc = {'lines': []}
		for line in lines:
			translatedDoc['lines'].append({'line': line})
		
		return translatedDoc
	
	def translate_reference(self, ref, absName=False, namespace=None):
		if ref.relatedObject is None:
			raise ReferenceTranslationError(ref.cname)
		if absName:
			commonName = None
		else:
			if namespace is None:
				description = ref.find_root()
				namespaceTypes = (abstractapi.Namespace, abstractapi.Class)
				namespace = description.relatedObject
				if type(namespace) not in namespaceTypes:
					namespace = description.relatedObject.find_first_ancestor_by_type(*namespaceTypes)
			if namespace is abstractapi.GlobalNs:
				commonName = None
			elif namespace.name == ref.relatedObject.name:
				commonName = namespace.name.prev
			elif namespace.name.is_prefix_of(ref.relatedObject.name):
				commonName = namespace.name
			else:
				commonName = metaname.Name.find_common_parent(ref.relatedObject.name, namespace.name)
		return ref.relatedObject.name.translate(self.refNameTranslator, recursive=True, topAncestor=commonName)
	
	def translate_keyword(self, keyword):
		return keyword.keyword.translate(self.langTranslator)
	
	def translate_text(self, textpart):
		return textpart.text
	
	def _translate_description(self, desc):
		paras = []
		for para in desc.paragraphs:
			paras.append(para.translate(self))
		return [para for para in paras if para != '']
	
	def _translate_paragraph(self, para):
		strPara = ''
		for part in para.parts:
			try:
				if isinstance(part, str):
					strPara += part
				else:
					strPara += part.translate(self)
			except ReferenceTranslationError:
				strPara += part.cname
		
		return strPara
	
	def _paragraphs_to_lines(self, paragraphs):
		lines = []
		for para in paragraphs:
			if para is not paragraphs[0]:
				lines.append('')
			lines += para.split('\n')
		return lines
	
	def _crop_text(self, inputLines, width):
		outputLines = []
		for line in inputLines:
			outputLines += self._split_line(line, width)
		return outputLines
	
	def _split_line(self, line, width, indent=False):
		firstNonTab = next((c for c in line if c != '\t'), None)
		tabCount = line.index(firstNonTab) if firstNonTab is not None else 0
		linePrefix = ('\t' * tabCount)
		line = line[tabCount:]
		
		lines = []
		while len(line) > width:
			cutIndex = line.rfind(' ', 0, width)
			if cutIndex >= 0:
				if self.langCode == 'Java':
					# Do not break a line in the middle of a { }
					while (not line[0:cutIndex].count('{') == line[0:cutIndex].count('}')) and (not line[cutIndex:].count('{') == line[cutIndex:].count('}')):
						cutIndex += 1
				if self.langCode == 'CSharp':
					# Do not break a line in the middle of a xml tag
					while not line[0:cutIndex].count('<') == line[0:cutIndex].count('>'):
						cutIndex += 1
				if line[cutIndex] == ' ':
					# Don't keep a whitespace at the start of the next line if you cut on one
					lines.append(line[0:cutIndex])
					line = line[cutIndex+1:]
				else:
					lines.append(line[0:cutIndex])
					line = line[cutIndex:]
			else:
				# Don't break http links
				cutIndex = len(line) if 'http://' or 'https://' in line else width
				lines.append(line[0:cutIndex])
				line = line[cutIndex:]
		if line:
			lines.append(line)
		
		if indent:
			lines = [line if line is lines[0] else '\t' + line for line in lines]
		
		return [linePrefix + line for line in lines]
	
	def _tag_as_brief(self, lines):
		pass


class DoxygenTranslator(Translator):
	def _tag_as_brief(self, lines):
		if len(lines) > 0:
			lines[0] = '@brief ' + lines[0]

	def translate_class_reference(self, ref, **kargs):
		return '@ref ' + super().translate_reference(ref)

	def translate_function_reference(self, ref, **kargs):
		return super().translate_reference(ref) + '()'
	
	def _translate_section(self, section):
		return '@{0} {1}'.format(
			section.kind,
			self._translate_paragraph(section.paragraph)
		)
	
	def _translate_parameter_list(self, parameterList):
		text = ''
		for paramDesc in parameterList.parameters:
			if self.displaySelfParam or not paramDesc.is_self_parameter():
				desc = self._translate_description(paramDesc.desc)
				desc = desc[0] if len(desc) > 0 else ''
				text += ('@param {0} {1}\n'.format(paramDesc.name.translate(self.nameTranslator), desc))
		return text


class JavaDocTranslator(Translator):
	class ReferenceTranslator(metaname.JavaTranslator):
		def __init__(self):
			super().__init__()
			self.classMemberSep = '#'

		def translate_method_name(self, name, recursive, topAncestor):
			res = super().translate_method_name(name, recursive, topAncestor)
			# Mehtods that are relative to the current class must start with '#'
			if name.prev is None or not recursive or name.prev is topAncestor:
				res = '#' + res
			return res

	def __init__(self):
		super().__init__('Java')
		self.refNameTranslator = JavaDocTranslator.ReferenceTranslator()

	def translate_class_reference(self, ref, **kargs):
		return '{@link ' + super().translate_reference(ref) + '}'

	def translate_function_reference(self, ref, **kargs):
		return '{@link ' + super().translate_reference(ref) + '}'
	
	def _translate_section(self, section):
		if section.kind == 'see':
			return 'see: {0}'.format(self._translate_paragraph(section.paragraph))
		if section.kind == 'note':
			return 'note: {0}'.format(self._translate_paragraph(section.paragraph))
		if section.kind == 'warning':
			return 'warning: {0}'.format(self._translate_paragraph(section.paragraph))
			
		return '@{0} {1}'.format(
			section.kind,
			self._translate_paragraph(section.paragraph)
		)

	def _translate_parameter_list(self, parameterList):
		text = ''
		for paramDesc in parameterList.parameters:
			if self.displaySelfParam or not paramDesc.is_self_parameter():
				desc = self._translate_description(paramDesc.desc)
				desc = desc[0] if len(desc) > 0 else ''
				text += ('@param {0} {1}\n'.format(paramDesc.name.translate(self.nameTranslator), desc))
		return text


class SwiftDocTranslator(Translator):
	def __init__(self):
		super().__init__('Swift')

	def translate_class_reference(self, ref, **kargs):
		return '``{0}``'.format(super().translate_reference(ref))

	def translate_function_reference(self, ref, **kargs):
		params = []
		for arg in ref.relatedObject.args:
			argName = arg.name.translate(self.refNameTranslator)
			params.append(argName + ':')
		return '``{0}({1})``'.format(super().translate_reference(ref), ''.join(params)).replace('.', '/')

	def _translate_section(self, section):
		if section.kind == 'return':
			section.kind = 'Returns'
		elif section.kind == 'warning':
			section.kind = 'Warning'
		elif section.kind == 'note':
			section.kind = 'Note'
		elif section.kind == 'see':
			section.kind = 'See also'
		else:
			logging.warning('doc translate section pointing on an unknown object ({0})'.format(section.kind))

		return '- {0}: {1}'.format(
			section.kind,
			self._translate_paragraph(section.paragraph)
		)

	def _translate_parameter_list(self, parameterList):
		text = ''
		for paramDesc in parameterList.parameters:
			if self.displaySelfParam or not paramDesc.is_self_parameter():
				desc = self._translate_description(paramDesc.desc)
				desc = desc[0] if len(desc) > 0 else ''
				text += ('- Parameter {0}: {1}\n'.format(paramDesc.name.translate(self.nameTranslator), desc))
		return text

class SphinxTranslator(Translator):
	def __init__(self, langCode):
		Translator.__init__(self, langCode)
		if langCode == 'C':
			self.domain = 'cpp'
			self.classDeclarator = 'type'
			self.interfaceDeclarator = self.classDeclarator
			self.methodDeclarator = 'function'
			self.enumDeclarator = 'enum'
			self.enumeratorDeclarator = 'enumerator'
			self.methodReferencer = 'func'
		elif langCode == 'Cpp':
			self.domain = 'cpp'
			self.classDeclarator = 'class'
			self.interfaceDeclarator = self.classDeclarator
			self.methodDeclarator = 'function'
			self.enumDeclarator = 'enum'
			self.enumeratorDeclarator = 'enumerator'
			self.namespaceDeclarator = 'namespace'
			self.methodReferencer = 'func'
		elif langCode == 'CSharp':
			self.domain = 'csharp'
			self.classDeclarator = 'class'
			self.interfaceDeclarator = self.classDeclarator
			self.methodDeclarator = 'method'
			self.enumDeclarator = 'enum'
			self.enumeratorDeclarator = 'value'
			self.namespaceDeclarator = 'namespace'
			self.classReferencer = 'type'
			self.interfaceReferencer = self.classReferencer
			self.enumReferencer = 'type'
			self.enumeratorReferencer = 'enum'
			self.methodReferencer = 'meth'
		elif langCode == 'Java':
			self.domain = 'java'
			self.classDeclarator = 'type'
			self.interfaceDeclarator = self.classDeclarator
			self.methodDeclarator = 'method'
			self.enumDeclarator = 'type'
			self.enumeratorDeclarator = 'field'
			self.namespaceDeclarator = 'package'
			self.methodReferencer = 'meth'
		elif langCode == 'Swift':
			self.domain = 'swift'
			self.classDeclarator = 'class'
			self.interfaceDeclarator = self.classDeclarator
			self.methodDeclarator = 'class_method'
			self.enumDeclarator = 'enum'
			self.enumeratorDeclarator = 'enum_case'
		else:
			raise ValueError('invalid language code: {0}'.format(langCode))
	
	def get_declarator(self, typeName):
		try:
			attrName = typeName + 'Declarator'
			declarator = getattr(self, attrName)
			return '{0}:{1}'.format(self.domain, declarator)
		except AttributeError:
			raise ValueError("'{0}' declarator type not supported".format(typeName))
	
	def get_referencer(self, typeName):
		try:
			attrName = typeName + 'Referencer'
			if attrName in dir(self):
				referencer = getattr(self, attrName)
				return '{0}:{1}'.format(self.domain, referencer)
			else:
				return self.get_declarator(typeName)
		except AttributeError:
			raise ValueError("'{0}' referencer type not supported".format(typeName))

	def translate_class_reference(self, ref, label=None, namespace=None):
		refStr = Translator.translate_reference(self, ref, absName=True)
		tag = self._sphinx_ref_tag(ref)

		return ':{tag}:`{label} <{ref}>`'.format(
			tag=tag,
			label=label if label is not None else Translator.translate_reference(self, ref, namespace=namespace),
			ref=tag.split(':')[1] + " " + refStr if self.domain == 'swift' else refStr
		)

	def translate_function_reference(self, ref, label=None, useNamespace=True, namespace=None):
		if self.domain == 'csharp':
			refStr = ref.relatedObject.name.translate(self.nameTranslator, **abstractapi.Translator._namespace_to_name_translator_params(namespace))
		else:
			refStr = ref.relatedObject.translate_as_prototype(self.langTranslator,
				hideArguments=self.domain not in ['java', 'swift'],
				hideArgNames=self.domain == 'java',
				hideArgTypes=self.domain == 'swift',
				hideReturnType=True,
				stripDeclarators=True,
				namespace=namespace
			)
		return ':{tag}:`{label} <{ref}>`'.format(
			tag   = self._sphinx_ref_tag(ref),
			label = label if label is not None else '{0}()'.format(Translator.translate_reference(self, ref, namespace=namespace)),
			ref   = "static " + refStr if self.domain == 'swift' and ref.relatedObject.type == abstractapi.Method.Type.Class else refStr
		)
	
	def translate_keyword(self, keyword):
		translatedKeyword = Translator.translate_keyword(self, keyword)
		return '``{0}``'.format(translatedKeyword)
	
	def _translate_section(self, section):
		strPara = self._translate_paragraph(section.paragraph)
		if section.kind == 'deprecated':
			return '**Deprecated:** {0}\n'.format(strPara)
		else:
			if section.kind == 'see':
				kind = 'seealso'
			else:
				kind = section.kind
			
			if section.kind == 'return':
				return ':return: {0}'.format(strPara)
			else:
				return '.. {0}::\n\t\n\t{1}\n\n'.format(kind, strPara)
	
	def _translate_parameter_list(self, parameterList):
		text = ''
		for paramDesc in parameterList.parameters:
			if self.displaySelfParam or not paramDesc.is_self_parameter():
				desc = self._translate_description(paramDesc.desc)
				desc = desc[0] if len(desc) > 0 else ''
				text += (':param {0}: {1}\n'.format(paramDesc.name.translate(self.nameTranslator), desc))
		text += '\n'
		return text
	
	def _sphinx_ref_tag(self, ref):
		typeName = type(ref.relatedObject).__name__.lower()
		return self.get_referencer(typeName)
	
	isParamDescRegex = re.compile('\t*:(?:param\s+\w+|return):')
	
	def _split_line(self, line, width):
		if SphinxTranslator.isParamDescRegex.match(line) is not None:
			lines = Translator._split_line(self, line, width, indent=True)
			return lines
		else:
			return Translator._split_line(self, line, width)


class SandCastleTranslator(Translator):
	def __init__(self, langCode):
		super().__init__(langCode)
		self.isEndTagPlaced = False

	def translate_text(self, textpart):
		text = super().translate_text(textpart)
		xmlSpecialCharDict = {'<': '&lt;',
			'>': '&gt;',
			"'": '&apos;',
			'"': '&quot;',
			'&': '&amp;'}
		if sys.version_info[0] >= 3:
			xmlTranslationTable = str.maketrans(xmlSpecialCharDict)
		else:
			import string
			xmlTranslationTable = string.maketrans(xmlSpecialCharDict)
		return text.translate(xmlTranslationTable)

	def translate_description(self, description, tagAsBrief=False):
		self.isEndTagPlaced = False
		translatedDoc = super().translate_description(description, tagAsBrief)
		if not tagAsBrief:
			if not self.isEndTagPlaced:
				translatedDoc['lines'].append({'line': '</para>'})
				translatedDoc['lines'].append({'line': '</summary>'})
				self.isEndTagPlaced = True		
		return translatedDoc

	def translate_function_reference(self, ref):
		refStr = Translator.translate_reference(self, ref, absName=True)
		subnResult = re.subn('(\.Get\(\))', '.Instance' , refStr)
		if subnResult[1] > 0:
			return '<see cref="{0}">{0}</see>'.format(subnResult[0])
		subnResult = re.subn('(\.Get|\.Set)', '.' , subnResult[0])
		if subnResult[1] > 0:
			return '<see cref="{0}">{0}</see>'.format(subnResult[0])
		return '<see cref="{0}()">{0}()</see>'.format(subnResult[0])
		# In every cases we write the same value in the "see" tag value
		# than in the cref value so that even if the cref is broken the
		# text is displayed

	def translate_class_reference(self, ref):
		refStr = Translator.translate_reference(self, ref, absName=True)
		return '<see cref="{0}">{0}</see>'.format(refStr)
	

	def _translate_parameter_list(self, parameterList):
		text = ''
		if not self.isEndTagPlaced:
			text += '</para>\n'
			text += '</summary>\n'
			self.isEndTagPlaced = True

		for paramDesc in parameterList.parameters:
			if self.displaySelfParam or not paramDesc.is_self_parameter():
				desc = self._translate_description(paramDesc.desc)
				desc = desc[0] if len(desc) > 0 else ''
				text += ('<param name="{0}">{1}</param>\n'.format(paramDesc.name.translate(self.nameTranslator), desc))
		return text
	
	def _translate_section(self, section):
		text =''
		if not self.isEndTagPlaced:
			text += '</para>\n'
			text += '</summary>\n'
			self.isEndTagPlaced = True

		if section.kind == 'return':
			section.kind = '<returns>{0}</returns>'
		elif section.kind == 'warning':
			section.kind = '<remarks>Warning : {0}</remarks> '
		elif section.kind == 'note':
			section.kind = '<remarks>Note : {0}</remarks>'
		elif section.kind == 'see':
			section.kind = '<remarks>See : {0}</remarks>'
		else:
			section.kind = section.kind + " : {0}"
			logging.warning('SandCastle doc translate section pointing on an unknown object ({0})'.format(section.kind))

		text += section.kind.format(self._translate_paragraph(section.paragraph))

		return text
