import numpy as np
from scipy import pi, exp

from ..Contour import Contour
from ..Paths import ComplexLine, ComplexArc

class AnnulusSector(Contour):
	"""
	A sector of an annulus in the complex plane.
	
	Parameters
	----------
	center : complex
		The center of the annulus sector.
	radii : tuple
		Tuple of length two of the form (inner_radius, outer_radius)
	phiRange : tuple
		Tuple of length two of the form (phi0, phi1).
		The segment of the contour containing inner and outer circular 
		arcs will be joined, counter clockwise from phi0 to phi1.

	Examples
	--------
	.. plot::
		:include-source:

		from numpy import pi
		from cxroots import AnnulusSector
		annulusSector = AnnulusSector(center=0.2, radii=(0.5, 1.25), phiRange=(-pi/4, pi/4))
		annulusSector.show()

	.. plot::
		:include-source:

		from numpy import pi
		from cxroots import AnnulusSector
		annulusSector = AnnulusSector(center=0.2, radii=(0.5, 1.25), phiRange=(pi/4, -pi/4))
		annulusSector.show()	
	"""
	def __init__(self, center, radii, phiRange):
		self.center = center
		self.axisName = ('r', 'phi')

		if phiRange[0] > phiRange[1]:
			phiRange = (phiRange[0], phiRange[1]+2*pi)

		phi0, phi1 = self.phiRange = phiRange

		# r > 0
		r0, r1 = self.radii = radii
		if r0 < 0 or r1 <= 0:
			raise ValueError('Radius > 0')

		# verticies [[radius0,phi0],[radius0,phi1],[radius1,phi1],[radius0,phi1]] 
		self.z1 = z1 = center + r0*exp(1j*phi0)
		self.z2 = z2 = center + r1*exp(1j*phi0)
		self.z3 = z3 = center + r1*exp(1j*phi1)
		self.z4 = z4 = center + r0*exp(1j*phi1)

		segments = [ComplexLine(z1,z2),
					ComplexArc(center,r1,phi0,phi1-phi0),
					ComplexLine(z3,z4),
					ComplexArc(center,r0,phi1,phi0-phi1)]

		super(AnnulusSector, self).__init__(segments)

	def __str__(self):
		return 'Annulus sector: center={center.real:.3f}{center.imag:+.3f}i, r0={radii[0]:.3f}, r1={radii[1]:.3f}, phi0={phiRange[0]:.3f}, phi1={phiRange[1]:.3f}'.format(center=self.center, radii=self.radii, phiRange=self.phiRange)
	
	@property
	def centralPoint(self):
		# get the central point within the contour
		r = (self.radii[0] + self.radii[1])/2
		phi = (self.phiRange[0] + self.phiRange[1])/2
		return r*exp(1j*phi)

	@property
	def area(self):
		return (self.radii[1]**2 - self.radii[0]**2)*abs(self.phiRange[1] - self.phiRange[0])%(2*pi)/2

	def contains(self, z):
		""" Returns True if the point z lies within the contour, False if otherwise """
		angle = np.angle(z - self.center)%(2*pi) # np.angle maps to [-pi,pi]
		radiusCorrect = self.radii[0] < abs(z - self.center) < self.radii[1]
		
		phi = np.mod(self.phiRange, 2*pi)
		if phi[0] > phi[1]:
			angleCorrect = phi[0] < angle <= 2*pi or 0 <= angle < phi[1]
		else:
			angleCorrect = phi[0] < angle < phi[1]

		return radiusCorrect and angleCorrect

	def subdivide(self, axis, divisionFactor=0.5):
		"""
		Subdivide the contour

		Parameters
		----------
		axis : str, can be either 'r' or 'phi'
			The axis along which the line subdividing the contour is a constant.
		divisionFactor : float in range (0,1), optional
			Determines the point along 'axis' at which the line dividing the box is placed

		Returns
		-------
		box1 : AnnulusSector
			If axis is 'r' then phiRange and the inner radius is the same as original AnnulusSector
			with the outer radius determined by the divisionFactor.
			If axis is 'phi' then the radii and phiRange[0] is the same as the original AnnulusSector
			with phiRange[1] determined by the divisionFactor.
		box2 : AnnulusSector
			If axis is 'r' then phiRange and the outer radius is the same as original AnnulusSector
			with the inner radius determined equal to the outer radius of box1.
			If axis is 'phi' then the radii and phiRange[1] is the same as the original AnnulusSector
			with phiRange[0] equal to phiRange[1] of box1.
		"""
		r0, r1 = self.radii
		phi0, phi1 = self.phiRange
		if axis == 0 or axis == self.axisName[0]:
			divisionPoint = r0 + divisionFactor*(r1-r0)
			box1 = AnnulusSector(self.center, [r0, divisionPoint], self.phiRange)
			box2 = AnnulusSector(self.center, [divisionPoint, r1], self.phiRange)

			# reuse line segments from original box where possible
			# this allows the cached integrals to be used
			box1.segments[3] = self.segments[3]
			box2.segments[1] = self.segments[1]
			box1.segments[1]._reversePath = box2.segments[3]
			box2.segments[3]._reversePath = box1.segments[1]

		elif axis == 1 or axis == self.axisName[1]:
			divisionPoint = phi0 + divisionFactor*(phi1-phi0)
			box1 = AnnulusSector(self.center, self.radii, [phi0, divisionPoint])
			box2 = AnnulusSector(self.center, self.radii, [divisionPoint, phi1])

			box1.segments[0] = self.segments[0]
			box2.segments[2] = self.segments[2]
			box1.segments[2]._reversePath = box2.segments[0]
			box2.segments[0]._reversePath = box1.segments[2]

		for box in [box1, box2]:
			box._createdBySubdivisionAxis = axis
			box._parentBox = self
		self._childBoxes = [box1, box2]

		return box1, box2

	def randomPoint(self):
		"""Returns a random point inside the contour of the AnnulusSector."""
		r = np.random.uniform(*self.radii)
		phiRange = np.mod(self.phiRange, 2*pi)
		if phiRange[0] > phiRange[1]:
			phi = random.choice([np.random.uniform(phiRange[0], 2*pi),
								 np.random.uniform(0, phiRange[1])])
		else:
			phi = np.random.uniform(*phiRange)

		return r*exp(1j*phi) + self.center
