#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
Generic formatting filter: creates proper indentation for each tree node,
placing "%s" placeholder where the actual output should be. You can use
this filter to preformat tree and then replace %s placeholder to whatever you
need. This filter should't be called directly from editor as a part 
of abbreviation.
@author Sergey Chikuyonok (serge.che@gmail.com)
@link http://chikuyonok.ru
"""
import re
from zencoding import zen_core as zen_coding

alias = '_format'
"Filter name alias (if not defined, ZC will use module name)"

child_token = '${child}'
placeholder = '%s'

def get_newline():
	return zen_coding.get_newline()


def get_indentation():
	return zen_coding.get_indentation()

def has_block_sibling(item):
	"""
	Test if passed node has block-level sibling element
	@type item: ZenNode
	@return: bool
	"""
	return item.parent and item.parent.has_block_children()

def is_very_first_child(item):
	"""
	Test if passed itrem is very first child of the whole tree
	@type tree: ZenNode
	"""
	return item.parent and not item.parent.parent and not item.previous_sibling

def should_break_line(node, profile):
	"""
	Need to add line break before element
	@type node: ZenNode
	@type profile: dict
	@return: bool
	"""
	if not profile['inline_break']:
		return False
		
	# find toppest non-inline sibling
	while node.previous_sibling and node.previous_sibling.is_inline():
		node = node.previous_sibling
	
	if not node.is_inline():
		return False
		
	# calculate how many inline siblings we have
	node_count = 1
	node = node.next_sibling
	while node:
		if node.is_inline():
			node_count += 1
		else:
			break
		node = node.next_sibling
	
	return node_count >= profile['inline_break']

def should_break_child(node, profile):
	"""
	 Need to add newline because <code>item</code> has too many inline children
	 @type node: ZenNode
	 @type profile: dict
	 @return: bool
	"""
	# we need to test only one child element, because 
	# has_block_children() method will do the rest
	return node.children and should_break_line(node.children[0], profile)

def process_snippet(item, profile, level=0):
	"""
	Processes element with <code>snippet</code> type
	@type item: ZenNode
	@type profile: dict
	@param level: Depth level
	@type level: int
	"""
	data = item.source.value;
		
	if not data:
		# snippet wasn't found, process it as tag
		return process_tag(item, profile, level)
		
	item.start = placeholder
	item.end = placeholder
	
	padding = item.parent.padding if item.parent else get_indentation() * level 
	
	if not is_very_first_child(item):
		item.start = get_newline() + padding + item.start
	
	# adjust item formatting according to last line of <code>start</code> property
	parts = data.split(child_token)
	lines = zen_coding.split_by_lines(parts[0] or '')
	padding_delta = get_indentation()
		
	if len(lines) > 1:
		m = re.match(r'^(\s+)', lines[-1])
		if m:
			padding_delta = m.group(1)
	
	item.padding = padding + padding_delta
	
	return item

def process_tag(item, profile, level=0):
	"""
	Processes element with <code>tag</code> type
	@type item: ZenNode
	@type profile: dict
	@param level: Depth level
	@type level: int
	"""
	if not item.name:
		# looks like it's a root element
		return item
	
	item.start = placeholder
	item.end = placeholder
	
	is_unary = item.is_unary() and not item.children
		
	# formatting output
	if profile['tag_nl'] is not False:
		padding = item.parent.padding if item.parent else get_indentation() * level
		force_nl = profile['tag_nl'] is True
		should_break = should_break_line(item, profile)
		
		# formatting block-level elements
		if ((item.is_block() or should_break) and item.parent) or force_nl:
			# snippet children should take different formatting
			if not item.parent or (item.parent.type != 'snippet' and not is_very_first_child(item)):
				item.start = get_newline() + padding + item.start
				
			if item.has_block_children() or should_break_child(item, profile) or (force_nl and not is_unary):
				item.end = get_newline() + padding + item.end
				
			if item.has_tags_in_content() or (force_nl and not item.has_children() and not is_unary):
				item.start += get_newline() + padding + get_indentation()
			
		elif item.is_inline() and has_block_sibling(item) and not is_very_first_child(item):
			item.start = get_newline() + padding + item.start
		
		item.padding = padding + get_indentation()
	
	return item

def process(tree, profile, level=0):
	"""
	Processes simplified tree, making it suitable for output as HTML structure
	@type item: ZenNode
	@type profile: dict
	@param level: Depth level
	@type level: int
	"""
	
	for item in tree.children:
		if item.type == 'tag':
			item = process_tag(item, profile, level)
		else:
			item = process_snippet(item, profile, level)
		
		if item.content:
			item.content = zen_coding.pad_string(item.content, item.padding)
			
		process(item, profile, level + 1)
	
	return tree