# this is an example of an example class to make into a cdef class
# this would be much easier if sage polyonomilas were used

def chebyshev_polynomial(n):
    if n == 0:
        return [1]
    elif n == 1:
        return [0,1]
    else:
        f = [0] + [2*a for a in chebyshev_polynomial(n-1)]
        g = chebyshev_polynomial(n-2)
        return poly_sum(f, g, -1)

def poly_eval(coeffs, x):
    res = coeffs[-1]
    for a in reversed(coeffs[:-1]):
        res = res*x + a
    return res

def poly_sum(f, g, a=1):
    """
    Compute f+a*g
    """
    dlen = len(f) - len(g)
    if dlen > 0:
        g = g + [0]*dlen
    else:
        f = f + [0]*(-dlen)
    return [fi + a*gi for fi, gi in zip(f,g)]
    

class ChebyshevApproximation:
    def __init__(self, f, degree=None):
        if degree is None:
            self._poly = list(f)
            return
        N = degree+1
        coeffs = []
        T = [chebyshev_polynomial(k) for k in range(N+1)]
        xs = [math.cos((k+.5)*math.pi/N) for k in range(N)]
        fx = [float(f(x)) for x in xs]
        for j in range(degree+1):
            c = 0
            for k in range(N):
                c += fx[k]*poly_eval(T[j], xs[k])
            coeffs.append(c*2/N)
        poly = []
        for k, c in enumerate(coeffs):
            poly = poly_sum(poly, T[k], c)
        poly[0] = float(f(0))
        self._poly = poly

    def __repr__(self):
        return "%s + %s*x + %s" % (self._poly[0], 
                                   self._poly[1], 
                                   " + ".join("%s*x^%s"%(c,k+2) for k,c in enumerate(self._poly[2:])))
    
    def __call__(self, x):
        return poly_eval(self._poly, x)

    def eval(self, x):
        return poly_eval(self._poly, x)

    def differentiate(self):
        return ChebyshevApproximation([c*(n+1) for n,c in enumerate(self._poly[1:])])

    def integrate(self):
        return ChebyshevApproximation([0] + [c/(n+1) for n,c in enumerate(self._poly)])
