#! /usr/bin/env python
# encoding: utf-8
# Thomas Nagy, 2005 (ita)

import os
from stat import *
import Deptree
import Params
from Params import debug, error, trace, fatal

class Node:
	def __init__(self, name, parent):

		self.m_name     = name
		self.m_parent   = parent

		# contents of this node (filesystem structure)
		self.m_dirs     = []
		self.m_files    = []

		# debugging only - a node is supposed to represent exactly one folder
		#if os.sep in name: print "error in name ? "+name

		# timestamp or hash of the file (string hash or md5) - we will use the timestamp by default
		self.m_tstamp   = None

		# IMPORTANT:
		# Some would-be class properties are stored in Deptree: nodes to depend on, signature, flags, ..
		# In fact, unused class members increase the .dblite file size sensibly with lots of objects 
		#   eg: the m_tstamp is used for every node, while the signature is computed only for build files

	def __str__(self):
		return "<%s>"%self.abspath()

	def __repr__(self):
		return "<%s>"%self.abspath()

	# tells if this node triggers a rebuild
	def haschanged(self):
		return (self.m_oldstamp != self.m_newstamp)

	# size of the subtree
	def size(self):
		l_size=1
		for i in self.m_dirs: l_size += i.size()
		l_size += len(self.m_files)
		return l_size

	def get_sig(self):
		try: return Params.g_build.m_tree.m_sigs[self]
		except: return self.m_tstamp

	# uses a cache, so calling height should have no overhead
	def height(self):
		try:
			return Params.g_build.m_height_cache[self]
		except:
			if not self.m_parent: val=0
			else:                 val=1+self.m_parent.height()
			Params.g_build.m_height_cache[self]=val
			return val

	# flag a subtree
	def tag(self, val):
		for i in self.m_files:
			i.m_flag = val
		for i in self.m_dirs:
			i.m_flag = val
			i.tag(val)

	def child_of_name(self, name):
		for d in self.m_dirs:
			trace('child of name '+d.m_name)
			if d.m_name == name:
				return d
		# perhaps we should throw an exception
		return None

	def ensure_scan(self):
		if not self in Params.g_build.m_scanned_folders:
			# TODO: a little bit inefficient, but code factorization will come later
			folder = '/'.join( Params.srcnode().difflst(self) )
			Params.g_build.m_tree.scanner_mirror(folder)
		Params.g_build.m_scanned_folders.append(self)

	# list of file names that separate a node from a child
	def difflst(self, child):
		if not child: error('Node difflst takes a non-null parameter!')
		lst=[]
		node = child
		while child != self:
			lst.append(child.m_name)
			child=child.m_parent
		lst.reverse()
		return lst

	## ------------ TODO : the following may need to be improved
	# list of paths up to the root
	# cannot remember where it is used (ita)
	def path_list(self):
		if not self.m_parent: return []
		return self.m_parent.pathlist().append(self.m_name)


	# returns a list that can be reduced to the absolute path
	# make sure to reverse it (used by abspath)
	def pathlist2(self):
		if not self.m_parent: return [Params.g_rootname]
		return [self.m_name, os.sep]+self.m_parent.pathlist2()

	# absolute path
	def abspath(self):
		## 1. stupid method
		# if self.m_parent is None: return ''
		# return self.m_parent.abspath()+os.sep+self.m_name
		#
		## 2. without a cache
		# return ''.join( self.pathlist2() )
		#
		## 3. with the cache
		try:
			return Params.g_build.m_abspath_cache[self]
		except:
			lst=self.pathlist2()
			lst.reverse()
			val=''.join(lst)
			Params.g_build.m_abspath_cache[self]=val
			return val

	# TODO : make it procedural, not recursive
	# find a node given an existing node and a list like ['usr', 'local', 'bin']
	def find_node(self, lst):
		#print self, lst
		if not lst: return self
		name=lst[0]

		if name == '.':  return self.find_node( lst[1:] )
		if name == '..': return self.m_parent.find_node( lst[1:] )

		for d in self.m_dirs+self.m_files:
			if d.m_name == name:
				return d.find_node( lst[1:] )

		trace('find_node returns nothing '+str(self)+' '+str(lst))
		return None


	## ===== BEGIN relpath-related methods  ===== ##

	# returns the list of names to the node
	# make sure to reverse it (used by relpath)
	def pathlist3(self, node):
		if self is node: return ['.']
		return [self.m_name, os.sep]+self.m_parent.pathlist3(node)
	
	# same as pathlist3, but do not append './' at the beginning
	def pathlist4(self, node):
		if self.m_parent is node: return [self.m_name]
		return [self.m_name, os.sep]+self.m_parent.pathlist4(node)
	
	# path relative to a direct parent
	def relpath(self, parent):
		#print "relpath", self, parent
		try:
			return Params.g_build.m_relpath_cache[self][parent]
		except:
			lst=self.pathlist3(parent)
			lst.reverse()
			val=''.join(lst)

			try:
				Params.g_build.m_relpath_cache[self][parent]=val
			except:
				Params.g_build.m_relpath_cache[self]={}
				Params.g_build.m_relpath_cache[self][parent]=val
			return val

	# path relative to the src directory (useful for building targets : almost - ita)
	def srcpath(self):
		node = Params.srcnode()
		if not node: error("BUG in srcpath")
		return self.relpath(node)

	# path used when building targets
	def bldpath(self):
		try:
			orig = Params.g_build.m_tree.m_bld_to_src[self]
			return orig.relpath_gen(Params.g_bldnode)
		except KeyError:
			pass
		except:
			print self
			raise
		if not Params.g_bldnode: error("BUG in bldpath")
		#return self.relpath(Params.g_bldnode)
		return self.relpath_gen(Params.g_bldnode)

	# find a common ancestor for two nodes - for the shortest path in hierarchy
	def find_ancestor(self, node):
		dist=self.height()-node.height()
		if dist<0: return node.find_ancestor(self)
		# now the real code
		cand=self
		while dist>0:
			cand=cand.m_parent
			dist=dist-1
		if cand is node: return cand
		cursor=node
		while cand.m_parent:
			cand   = cand.m_parent
			cursor = cursor.m_parent
			if cand is cursor: return cand

	# prints the amount of "../" between two nodes
	def invrelpath(self, parent):
		lst=[]
		cand=self
		while cand is not parent:
			cand=cand.m_parent
			lst+=['..',os.sep]
		return lst

	# TODO: do this in a single function (this one uses invrelpath, find_ancestor and pathlist4)
	# string representing a relative path between two nodes, we are at relative_to
	def relpath_gen(self, going_to):
		if self is going_to: return '.'
		if going_to.m_parent is self: return '..'

		# up_path is '../../../' and down_path is 'dir/subdir/subdir/file'
		ancestor  = self.find_ancestor(going_to)
		up_path   = going_to.invrelpath(ancestor)
		down_path = self.pathlist4(ancestor)
		down_path.reverse()
		return "".join( up_path+down_path )

	# TODO look at relpath_gen - it is certainly possible to get rid of find_ancestor
	def relpath_gen2(self, going_to):
		if self is going_to: return '.'
		ancestor = Params.srcnode()
		up_path   = going_to.invrelpath(ancestor)
		down_path = self.pathlist4(ancestor)
		down_path.reverse()
		return "".join( up_path+down_path )

	## ===== END relpath-related methods  ===== ##

	def debug(self):
		print "========= debug node ============="
		print "dirs are ", self.m_dirs
		print "files are", self.m_files
		print "======= end debug node ==========="

	def unlink(self):
		ret = os.unlink( self.abspath() )
		print ret

	def is_child_of(self, node):
		if self.m_parent:
			if self.m_parent is node: return 1
			else: return self.m_parent.is_child_of(node)
		return 0

	# returns the folder in the build dir for reaching this node
	def cd_to(self):
		reldir = Params.g_build.m_tree.m_bldnode.difflst(self)
		reldir = reldir[:len(reldir)-1]
		reldir = os.sep.join(reldir)
		return reldir

