


#define POLY p1
#define X p2
#define A p3
#define B p4
#define C p5
#define Y p6

Eval_roots = ->
	# A == B -> A - B

	p2 = cadr(p1)

	if (car(p2) == symbol(SETQ) || car(p2) == symbol(TESTEQ))
		push(cadr(p2))
		Eval()
		push(caddr(p2))
		Eval()
		subtract()
	else
		push(p2)
		Eval()
		p2 = pop()
		if (car(p2) == symbol(SETQ) || car(p2) == symbol(TESTEQ))
			push(cadr(p2))
			Eval()
			push(caddr(p2))
			Eval()
			subtract()
		else
			push(p2)

	# 2nd arg, x

	push(caddr(p1))
	Eval()
	p2 = pop()
	if (p2 == symbol(NIL))
		guess()
	else
		push(p2)

	p2 = pop()
	p1 = pop()

	if (!ispoly(p1, p2))
		stop("roots: 1st argument is not a polynomial")

	push(p1)
	push(p2)

	roots()


hasImaginaryCoeff = ->

	polycoeff = tos

	push(p1)
	push(p2)
	k = coeff()

	imaginaryCoefficients = false
	h = tos
	for i in [k...0] by -1
		#console.log "hasImaginaryCoeff - coeff.:" + stack[tos-i].toString()
		if iscomplexnumber(stack[tos-i])
			imaginaryCoefficients = true
			break
	tos -= k
	return imaginaryCoefficients


roots = ->
	h = 0
	i = 0
	n = 0
	h = tos - 2

	roots2()

	n = tos - h
	if (n == 0)
		stop("roots: the polynomial is not factorable, try nroots")
	if (n == 1)
		return
	sort_stack(n)
	save()
	p1 = alloc_tensor(n)
	p1.tensor.ndim = 1
	p1.tensor.dim[0] = n
	for i in [0...n]
		p1.tensor.elem[i] = stack[h + i]
	tos = h
	push(p1)
	restore()

roots2 = ->
	save()

	p2 = pop()
	p1 = pop()

	push(p1)
	push(p2)

	if !hasImaginaryCoeff()
		factorpoly()
		p1 = pop()
	else
		pop()
		pop()


	if (car(p1) == symbol(MULTIPLY))
		p1 = cdr(p1)
		while (iscons(p1))
			push(car(p1))
			push(p2)
			roots3()
			p1 = cdr(p1)
	else
		push(p1)
		push(p2)
		roots3()

	restore()

roots3 = ->
	save()
	p2 = pop()
	p1 = pop()
	if (car(p1) == symbol(POWER) && ispoly(cadr(p1), p2) && isposint(caddr(p1)))
		push(cadr(p1))
		push(p2)
		mini_solve()
	else if (ispoly(p1, p2))
		push(p1)
		push(p2)
		mini_solve()
	restore()

#-----------------------------------------------------------------------------
#
#	Input:		stack[tos - 2]		polynomial
#
#			stack[tos - 1]		dependent symbol
#
#	Output:		stack			roots on stack
#
#						(input args are popped first)
#
#-----------------------------------------------------------------------------

# note that for many quadratic and cubic polynomials we don't
# actually end up using the quadratic and cubic formulas in here,
# since there is a chance we factored the polynomial and in so
# doing we found some solutions and lowered the degree.
mini_solve = ->
	#console.log "mini_solve >>>>>>>>>>>>>>>>>>>>>>>> tos:" + tos
	n = 0

	save()

	p2 = pop()
	p1 = pop()

	push(p1)
	push(p2)

	n = coeff()

	# AX + B, X = -B/A

	if (n == 2)
		#console.log "mini_solve >>>>>>>>> 1st degree"
		p3 = pop()
		p4 = pop()
		push(p4)
		push(p3)
		divide()
		negate()
		restore()
		return

	# AX^2 + BX + C, X = (-B +/- (B^2 - 4AC)^(1/2)) / (2A)

	if (n == 3)
		#console.log "mini_solve >>>>>>>>> 2nd degree"
		p3 = pop() # A
		p4 = pop() # B
		p5 = pop() # C

		# B^2
		push(p4)
		push(p4)
		multiply()

		# 4AC
		push_integer(4)
		push(p3)
		multiply()
		push(p5)
		multiply()

		# B^2 - 4AC
		subtract()

		#(B^2 - 4AC)^(1/2)
		push_rational(1, 2)
		power()

		#p6 is (B^2 - 4AC)^(1/2)
		p6 = pop()
		push(p6);

		# B
		push(p4)
		subtract() # -B + (B^2 - 4AC)^(1/2)

		# 1/2A
		push(p3)
		divide()
		push_rational(1, 2)
		multiply()
		# tos - 1 now is 1st root: (-B + (B^2 - 4AC)^(1/2)) / (2A)

		push(p6);
		# tos - 1 now is (B^2 - 4AC)^(1/2)
		# tos - 2: 1st root: (-B + (B^2 - 4AC)^(1/2)) / (2A)

		# add B to tos
		push(p4)
		add()
		# tos - 1 now is  B + (B^2 - 4AC)^(1/2)
		# tos - 2: 1st root: (-B + (B^2 - 4AC)^(1/2)) / (2A)

		negate()
		# tos - 1 now is  -B -(B^2 - 4AC)^(1/2)
		# tos - 2: 1st root: (-B + (B^2 - 4AC)^(1/2)) / (2A)

		# 1/2A again
		push(p3)
		divide()
		push_rational(1, 2)
		multiply()
		# tos - 1: 2nd root: (-B - (B^2 - 4AC)^(1/2)) / (2A)
		# tos - 2: 1st root: (-B + (B^2 - 4AC)^(1/2)) / (2A)

		restore()
		return

	if (n == 4)
		#console.log ">>>>>>>>>>>>>>>> actually using cubic formula <<<<<<<<<<<<<<< "
		p3 = pop() # A
		p4 = pop() # B
		p5 = pop() # C
		p6 = pop() # D

		#console.log ">>>> A:" + p3.toString()
		#console.log ">>>> B:" + p4.toString()
		#console.log ">>>> C:" + p5.toString()
		#console.log ">>>> D:" + p6.toString()

		# C - only related calculations
		push(p5)
		push(p5)
		multiply()
		R_c2 = pop()

		push(R_c2)
		push(p5)
		multiply()
		R_c3 = pop()

		# B - only related calculations
		push(p4)
		push(p4)
		multiply()
		R_b2 = pop()

		push(R_b2)
		push(p4)
		multiply()
		R_b3 = pop()

		push(R_b3)
		push(p6)
		push_integer(-4)
		multiply()
		multiply()
		R_m4_b3_d = pop()


		push(R_b3)
		push_integer(2)
		multiply()
		R_2_b3 = pop()

		# A - only related calculations
		push_integer(3)
		push(p3)
		multiply()
		R_3_a = pop()

		push(R_3_a)
		push_integer(9)
		multiply()
		push(p3)
		multiply()
		push(p6)
		multiply()
		R_27_a2_d = pop()

		push(R_27_a2_d)
		push(p6)
		multiply()
		negate()
		R_m27_a2_d2 = pop()

		push(R_3_a)
		push_integer(2)
		multiply()
		R_6_a = pop()

		# mixed calculations
		push(p3)
		push(p5)
		multiply()
		R_a_c = pop()

		push(R_a_c)
		push(p4)
		multiply()
		R_a_b_c = pop()

		push(R_a_c)
		push_integer(3)
		multiply()
		R_3_a_c = pop()

		push_integer(-4)
		push(p3)
		push(R_c3)
		multiply()
		multiply()
		R_m4_a_c3 = pop()

		push(R_a_b_c)
		push_integer(9)
		multiply()
		negate()
		R_m9_a_b_c = pop()

		push(R_m9_a_b_c)
		push(p6)
		push_integer(-2)
		multiply()
		multiply()
		R_18_a_b_c_d = pop()

		push(R_b2)
		push(R_3_a_c)
		subtract()
		R_DELTA0 = pop()

		push(R_b2)
		push(R_c2)
		multiply()
		R_b2_c2 = pop()

		push(R_DELTA0)
		push_integer(3)
		power()
		push_integer(4)
		multiply()
		R_4_DELTA03 = pop()

		push(R_DELTA0)
		simplify()
		Eval()
		yyfloat()
		Eval(); # normalize
		absval()
		R_DELTA0_toBeCheckedIfZero = pop()
		#console.log "D0 " + R_DELTA0_toBeCheckedIfZero.toString()
		#if iszero(R_DELTA0_toBeCheckedIfZero)
		#	console.log " *********************************** D0 IS ZERO"


		# DETERMINANT
		push(R_18_a_b_c_d)
		push(R_m4_b3_d)
		push(R_b2_c2)
		push(R_m4_a_c3)
		push(R_m27_a2_d2)
		add()
		add()
		add()
		add()
		simplify()
		Eval()
		yyfloat()
		Eval(); # normalize
		absval()
		R_determinant = pop()
		#console.log "DETERMINANT: " + R_determinant.toString()

		# R_DELTA1
		push(R_2_b3)
		push(R_m9_a_b_c)
		push(R_27_a2_d)
		add()
		add()
		R_DELTA1 = pop()

		# R_Q
		push(R_DELTA1)
		push_integer(2)
		power()
		push(R_4_DELTA03)
		subtract()
		push_rational(1, 2)
		power()
		R_Q = pop()

		push(p4)
		negate()
		push(R_3_a)
		divide()
		R_m_b_over_3a = pop()

		if iszero(R_determinant)
			if iszero(R_DELTA0_toBeCheckedIfZero)
				#console.log " *********************************** DETERMINANT IS ZERO and delta0 is zero"
				push(R_m_b_over_3a) # just same solution three times
				restore()
				return
			else
				#console.log " *********************************** DETERMINANT IS ZERO and delta0 is not zero"
				push(p3)
				push(p6)
				push_integer(9)
				multiply()
				multiply()
				push(p4)
				push(p5)
				multiply()
				subtract()
				push(R_DELTA0)
				push_integer(2)
				multiply()
				divide() # first solution
				root_solution = pop()
				push(root_solution) # pushing two of them on the stack
				push(root_solution)

				# second solution here
				# 4abc
				push(R_a_b_c)
				push_integer(4)
				multiply()

				# -9a*a*d
				push(p3)
				push(p3)
				push(p6)
				push_integer(9)
				multiply()
				multiply()
				multiply()
				negate()

				# -9*b^3
				push(R_b3)
				negate()

				# sum the three terms
				add()
				add()

				# denominator is a*delta0
				push(p3)
				push(R_DELTA0)
				multiply()

				# build the fraction
				divide()

				restore()
				return



		C_CHECKED_AS_NOT_ZERO = false
		flipSignOFQSoCIsNotZero = false
		
		# C will go as denominator, we have to check
		# that is not zero
		while !C_CHECKED_AS_NOT_ZERO

			# R_C
			push(R_Q)
			if flipSignOFQSoCIsNotZero
				negate()
			push(R_DELTA1)
			add()
			push_rational(1, 2)
			multiply()
			push_rational(1, 3)
			power()
			R_C = pop()

			push(R_C)
			simplify()
			Eval()
			yyfloat()
			Eval(); # normalize
			absval()
			R_C_simplified_toCheckIfZero = pop()
			#console.log "C " + R_C_simplified_toCheckIfZero.toString()
			if iszero(R_C_simplified_toCheckIfZero)
				#console.log " *********************************** C IS ZERO flipping the sign"
				flipSignOFQSoCIsNotZero = true
			else
				C_CHECKED_AS_NOT_ZERO = true


		push(R_C)
		push(R_3_a)
		multiply()
		R_3_a_C = pop()

		push(R_3_a_C)
		push_integer(2)
		multiply()
		R_6_a_C = pop()


		# imaginary parts calculations
		push(imaginaryunit)
		push_integer(3)
		push_rational(1, 2)
		power()
		multiply()
		i_sqrt3 = pop()
		push_integer(1)
		push(i_sqrt3)
		add()
		one_plus_i_sqrt3 = pop()
		push_integer(1)
		push(i_sqrt3)
		subtract()
		one_minus_i_sqrt3 = pop()


		push(R_C)
		push(R_3_a)
		divide()
		R_C_over_3a = pop()

		# first solution
		push(R_m_b_over_3a) # first term
		push(R_C_over_3a)
		negate() # second term
		push(R_DELTA0)
		push(R_3_a_C)
		divide()
		negate() # third term
		# now add the three terms together
		add()
		add()
		simplify()

		# second solution
		push(R_m_b_over_3a) # first term
		push(R_C_over_3a)
		push(one_plus_i_sqrt3)
		multiply()
		push_integer(2)
		divide() # second term
		push(one_minus_i_sqrt3)
		push(R_DELTA0)
		multiply()
		push(R_6_a_C)
		divide() # third term
		# now add the three terms together
		add()
		add()
		simplify()

		# third solution
		push(R_m_b_over_3a) # first term
		push(R_C_over_3a)
		push(one_minus_i_sqrt3)
		multiply()
		push_integer(2)
		divide() # second term
		push(one_plus_i_sqrt3)
		push(R_DELTA0)
		multiply()
		push(R_6_a_C)
		divide() # third term
		# now add the three terms together
		add()
		add()
		simplify()

		restore()
		return

	tos -= n

	restore()

