import os,sys,math
import numpy as np

factorial = math.factorial
comb = math.comb
perm = math.perm
#print (math.comb(n, k))

"""
    Coefficients used in this library are  [ a0, a1, ..., an ] for y = a0 + a1*x + ... an*x**n
    Otherwise I'd go crazy reversing indices everywhree.
    Polynomial order n is length(poly)-1

"""


def poly_inputscreen(poly):
    """
    regularizes the input for a polynomial coefficient array
    """

    poly = np.array(poly)
    poly = np.reshape(poly,[np.prod(poly.shape)])
    if(poly.size==0):
        poly = np.float32([0])

    return poly

def poly_string(poly):
    """
    Returns a string representation of the polynomial, 
    omitting zero coefficeints.
    """
    poly = poly_inputscreen(poly)
    s = ""
    poly = poly_inputscreen(poly)

    if(poly.shape[0]==0):
        s = "0.0"
        return s
    if(poly.shape[0]==1):
        s = "{}".format(poly[0])
        return s

    for I in range(0,poly.shape[0]):
        skip = np.abs(poly[I])<1E-15 #skip zero coefficients
        if(not skip):
            if(I==0):
                s = s + "{}".format(poly[I])
            else:
                s = s + "{}*x^{}".format(poly[I],I)
            if(I<poly.shape[0]-1):
                s = s + " + "
            
    return s

def poly_print(poly):
    """
    Prints the string representation of the polynomial
    """
    print(poly_string(poly))
    return

def polyval(x,poly):
    poly = poly_inputscreen(poly)
    x = np.array(x)

    y = np.zeros(x.shape)

    for I in range(0,poly.shape[0]):
        y = y + poly[I]*x**I
        
    return y


def poly_trimorder(poly):
    """
    Trims the polynomial array size to the leading nonzero order
    """
    poly = poly_inputscreen(poly)

    ind = poly.shape[0]
    for I in range(ind-1,-1,-1):
        if(np.abs(poly[I])>1E-15):
            ind = I+1
            break
    poly = poly[0:ind]

    return poly

def poly_mult(poly1, poly2):
    """
    Multiplies two polynomials together, returning
     poly1*poly2
    [1,1] * [-1,1] = [-1,0,1]
    [1,1] * [1,1] = [1,2,1]
    """
    poly1 = poly_inputscreen(poly1)
    poly2 = poly_inputscreen(poly2)

    N1 = poly1.shape[0]
    N2 = poly2.shape[0]

    poly3 = np.zeros([N1+N2],dtype=np.float64)

    for I in range(0,N1):
        for J in range(0,N2):
            poly3[I+J] = poly3[I+J] + poly1[I]*poly2[J]

    poly3 = poly_trimorder(poly3)
    return poly3


def poly_derivative(poly,N=1):
    """
    derivative of polynomial, of positive integer order
    returns d^N/dx^N(poly)
    """
    poly = poly_inputscreen(poly)

    N = int(N)
    if(N<=0):
        return poly

    for J in range(0,N):
        for I in range(0,poly.shape[0]-1):
            poly[I] = poly[I+1]*(I+1)
        poly[poly.shape[0]-1] = 0

    poly = poly_trimorder(poly)
    return poly

def poly_integral(poly,N=1):
    """
    Returns indef. integral of polynomial, compounded N times
    integral(poly(x)*dx)
    """
    poly = poly_inputscreen(poly)

    N = int(N)
    if(N<=0):
        return poly

    N1 = poly.shape[0]
    N2 = N1 + N
    poly1 = np.zeros([N2])
    poly1[0:N1] = poly[:]

    for J in range(0,N):
        for I in range(poly1.shape[0]-1,0,-1):
            poly1[I] = poly1[I-1]*1.0/(I)
        poly1[I-1] = 0

    poly1 = poly_trimorder(poly1)
    return poly1

def poly_pow(poly,N):
    """
    Raises polynomial to Nth positive integer power
    poly(x)^N
    
    naive algorithm for now
    can make a faster one by squaring, but ...
    """

    N = int(N)
    if(N<0):
        return np.array([0])
    if(N==0):
        return np.array([1])
    
    poly = poly_inputscreen(poly)
    N1 = poly.shape[0]
    N2 = (N1-1)*N+1
    poly1 = np.zeros([N2])
    poly2 = np.zeros([N2])

    poly2[0:N1] = poly[:]

    for I in range(1,N):
        #swap addresses
        tmp = poly1
        poly1 = poly2
        poly2 = tmp
        poly2[:] = 0
        for J in range(0,N1):
            for K in range(0,N2-N1+1):
                poly2[J+K] = poly2[J+K] + poly1[K]*poly[J]
    
    poly2 = poly_trimorder(poly2)
    return poly2

