try:
    from itertools import zip_longest
except ImportError:
    from itertools import izip_longest as zip_longest
import fractions

from .numbertype import *

# strip all copies of elt from the end of the list
def strip(L, elt):
    if len(L) == 0: return L

    i = len(L) - 1
    while i >= 0 and L[i] == elt:
        i -= 1

    return L[:i+1]


# create a polynomial with coefficients in a field; coefficients are in
# increasing order of monomial degree so that, for example, [1,2,3]
# corresponds to 1 + 2x + 3x^2
@memoize
def polynomialsOver(field=fractions.Fraction):

    class Polynomial(DomainElement):
        operatorPrecedence = 2

        @classmethod
        def factory(cls, L):
            return Polynomial([cls.field(x) for x in L])

        def __init__(self, c):
            if type(c) is Polynomial:
                self.coefficients = c.coefficients
            elif isinstance(c, field):
                self.coefficients = [c]
            elif not hasattr(c, '__iter__') and not hasattr(c, 'iter'):
                self.coefficients = [field(c)]
            else:
                self.coefficients = c

            self.coefficients = strip(self.coefficients, field(0))


        def isZero(self): return self.coefficients == []

        def __repr__(self):
            if self.isZero():
                return '0'

            return ' + '.join(['%s x^%d' % (a,i) if i > 0 else '%s'%a
                                 for i,a in enumerate(self.coefficients)])


        def __abs__(self): return len(self.coefficients) # the valuation only gives 0 to the zero polynomial, i.e. 1+degree
        def __len__(self): return len(self.coefficients)
        def __sub__(self, other): return self + (-other)
        def __iter__(self): return iter(self.coefficients)
        def __neg__(self): return Polynomial([-a for a in self])

        def iter(self): return self.__iter__()
        def leadingCoefficient(self): return self.coefficients[-1]
        def degree(self): return abs(self) - 1

        @typecheck
        def __eq__(self, other):
            return self.degree() == other.degree() and all([x==y for (x,y) in zip(self, other)])

        @typecheck
        def __ne__(self, other):
            return self.degree() != other.degree() or any([x!=y for (x,y) in zip(self, other)])

        @typecheck
        def __add__(self, other):
            newCoefficients = [sum(x) for x in zip_longest(self, other, fillvalue=self.field(0))]
            return Polynomial(newCoefficients)


        @typecheck
        def __mul__(self, other):
            if self.isZero() or other.isZero():
                return Zero()

            newCoeffs = [self.field(0) for _ in range(len(self) + len(other) - 1)]

            for i,a in enumerate(self):
                for j,b in enumerate(other):
                    newCoeffs[i+j] += a*b

            return Polynomial(newCoeffs)


        @typecheck
        def __divmod__(self, divisor):
            quotient, remainder = Zero(), self
            divisorDeg = divisor.degree()
            divisorLC = divisor.leadingCoefficient()

            while remainder.degree() >= divisorDeg:
                monomialExponent = remainder.degree() - divisorDeg
                monomialZeros = [self.field(0) for _ in range(monomialExponent)]
                monomialDivisor = Polynomial(monomialZeros + [remainder.leadingCoefficient() / divisorLC])

                quotient += monomialDivisor
                remainder -= monomialDivisor * divisor

            return quotient, remainder


        @typecheck
        def __truediv__(self, divisor):
            if divisor.isZero():
                raise ZeroDivisionError
            return divmod(self, divisor)[0]


        @typecheck
        def __mod__(self, divisor):
            if divisor.isZero():
                raise ZeroDivisionError
            return divmod(self, divisor)[1]

        def __call__(self, x):
            if type(x) is int:
                x = self.field(x)
            assert type(x) is self.field
            if self.isZero():
                return self.field(0)
            y = self.leadingCoefficient()
            for coeff in self.coefficients[-2::-1]:
                y = y * x + coeff
            return y


    def Zero():
        return Polynomial([])


    Polynomial.field = field
    Polynomial.__name__ = '(%s)[x]' % field.__name__
    Polynomial.englishName = 'Polynomials in one variable over %s' % field.__name__
    return Polynomial
