#
# $Id: contour.py,v 1.23 2008/01/29 04:08:31 mrnolta Exp $
#
# Copyright (C) 2001 Mike Nolta <mrnolta@users.sourceforge.net>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA  02111-1307, USA.
#

from biggles import \
	_series, _message, \
	_LineComponent,  _PathObject, _PlotComponent, BigglesError
from geometry import *
import _biggles

import numpy

def _span( a, b, n ):
	return a + float(b - a)*numpy.arange( 0, n, 1, numpy.Float )/(n-1)

def _pop2( x, i, j ):
	if i < j:
		b = x.pop( j )
		a = x.pop( i )
	elif i > j:
		a = x.pop( i )
		b = x.pop( j )
	return a, b

def _unzip( line ):
	x = []
	y = []
	for x0,y0 in line:
		x.append( x0 )
		y.append( y0 )
	return x, y

class Contour( _LineComponent ):

	def __init__( self, x, y, z, z0, **kw ):
		_LineComponent.__init__( self )
		self.kw_init( kw )
		self.x = x
		self.y = y
		self.z = z
		self.z0 = z0

	def limits( self ):
		p = min(self.x), min(self.y)
		q = max(self.x), max(self.y)
		return BoundingBox( p, q )

	def _get_contours( self ):
		segs = _biggles.contour_segments( \
			self.x, self.y, self.z, self.z0 )
		open = [[ segs[0][0], segs[0][1] ]]
		closed = []
		for a,b in segs[1:]:
			xxx = []
			for i in range(len(open)):
				begin = open[i][0]
				end = open[i][-1]
				if a == begin:
					xxx.append( (i,0,b) )
				elif a == end:
					xxx.append( (i,1,b) )
				if b == begin:
					xxx.append( (i,0,a) )
				elif b == end:
					xxx.append( (i,1,a) )
			if len(xxx) == 0:
				open.append( [a,b] )
			elif len(xxx) == 1:
				i,end,pt = xxx[0]
				if end == 0:
					open[i].insert( 0, pt )
				else:
					open[i].append( pt )
			elif len(xxx) == 2:
				i0,end0,pt0 = xxx[0]
				i1,end1,pt1 = xxx[1]
				if i0 == i1:
					# closed
					l0 = open.pop( i0 )
					l0.append( l0[0] )
					closed.append( l0 )
				else:
					l0, l1 = _pop2( open, i0, i1 )
					m = None
					if end0==1 and end1==0:
						m = l0 + l1
					elif  end0==0 and end1==1:
						m = l1 + l0
					elif end0==0 and end1==0:
						l0.reverse()
						m = l0 + l1
					elif end0==1 and end1==1:
						l1.reverse()
						m = l0 + l1
					if m is not None:
					 	open.append( m )
					else:
						_message( "contour: m is None" )
			elif len(xxx) > 2:
				_message( "contour: len(xxx) > 2" )
		return open + closed

	def make( self, context ):
		lines = self._get_contours()
		for line in lines:
			x, y = _unzip( line )
			u, v = context.geom.call_vec( x, y )
			self.add( _PathObject(u, v) )

def _func_color_black( i, n, z0, z_min, z_max ):
	return 0x000000

def _func_linetype_dotneg( i, n, z0, z_min, z_max ):
	if z0 < 0:
		return "dotted"
	return "solid"

def _func_linewidth_placeholder( i, n, z0, z_min, z_max ):
	return 1

class Contours( _PlotComponent ):

	_named_func_color = {
		"black"			: _func_color_black,
	}

	_named_func_linetype = {
		"dotted-negative"	: _func_linetype_dotneg,
	}

	_named_func_linewidth = {
		"placeholder"		: _func_linewidth_placeholder,
	}

	def __init__( self, z, x=None, y=None, zrange=None, **kw ):
		_PlotComponent.__init__( self )
		#apply( self.conf_setattr, ("Contours",), kw )
		self.conf_setattr( "Contours" )
		self.kw_init( kw )
		self.z = z
		self.x = x
		self.y = y
		self.zrange = zrange

	def _get_coords( self ):
		dim = self.z.shape
		x = self.x
		if x is None:
			x = range(dim[0])
		y = self.y
		if y is None:
			y = range(dim[1])
		return x, y, self.z

	def limits( self ):
		x, y, z = self._get_coords()
		return BoundingBox( (min(x),min(y)), (max(x),max(y)) )

	def make( self, context ):
		self.clear()

		x, y, z = self._get_coords()
		limits = self.limits()
		xr = limits.xrange()
		yr = limits.yrange()

		zr = self.zrange
		if zr is None:
			zr = _biggles.range( z )

		levels = self.levels
		if type(levels) == type(0):
			levels = _series( 1, self.levels, \
				float(zr[1]-zr[0])/(self.levels+1), zr[0] )

		colorfunc = self.func_color
		if type(colorfunc) == type(""):
			colorfunc = self._named_func_color[colorfunc]

		linefunc = self.func_linetype
		if type(linefunc) == type(""):
			linefunc = self._named_func_linetype[linefunc]

		widthfunc = self.func_linewidth
		if type(widthfunc) == type(""):
			widthfunc = self._named_func_linewidth[widthfunc]

		nlevels = len(levels)
		for i in range(nlevels):
			kw = {}
			z0 = levels[i]
			args = i, nlevels, z0, zr[0], zr[1]
			if colorfunc is not None:
				color = apply( colorfunc, args )
				if color is not None:
					kw["color"] = color
			if linefunc is not None:
				linetype = apply( linefunc, args )
				if linetype is not None:
					kw["linetype"] = linetype
			if widthfunc is not None:
				linewidth = apply( widthfunc, args )
				if linewidth is not None:
					kw["linewidth"] = linewidth
			c = apply( Contour, (x, y, z, z0), kw )
			self.add( c )

	def make_key( self, bbox ):
		xr = bbox.xrange()
		y = bbox.center()[1]
		p = xr[0], y
		q = xr[1], y
		return apply( _LineObject, (p,q), self.kw_style )

