class FiniteField(Field):

    def __init__(self, p, k, f):
        self.p, self.k, self.f = p, k, f
        self.field = PolynomialsOverField(Zp(p))

    def cardinality(self):
        return self.p ** self.k

    def div_mod(self, elem1, elem2):

        e1 = copy.deepcopy(elem1)
        e2 = copy.deepcopy(elem2)

        # make both polynomials of degree lower than f's
        e1 = self.rep(e1)
        e2 = self.rep(e2)

        # make sure that elem1 has a higher degree than elem2's.
        # note that adding f is essentially no different from adding 0 in this field.
        # e1 = self.field.add(self.f, e1)

        # result = self.field.div_mod(e1, e2)
        result = self.mul(e1, self.mul_inv(e2))
        # make sure result is returned in proper representation.
        return self.rep(result), self.add_id()

    def add_id(self):
        return Polynomial([0], self.field)

    def mul_id(self):
        return Polynomial([1], self.field)

    def add_inv(self, elem):
        return self.rep(self.field.add_inv(elem))

    def mul_inv(self, elem):
        res = self.field.extended_gcd(elem, self.f)
        return res[1]

    def add(self, elem1, elem2):
        return self.rep(self.field.add(elem1, elem2))

    def mul(self, elem1, elem2):
        return self.rep(self.field.mul(elem1, elem2))

    # elem's equivalence class
    def rep(self, elem):
        return self.field.div_mod(elem, self.f)[1]

    def random_elem(self):
        elem = []
        for i in range(self.k):
            elem.append(random.randrange(self.p))
        return Polynomial(elem, Zp(self.p))

    def __eq__(self, other):
        if isinstance(other, FiniteField):
            return self.p == other.p and self.k == other.k
        return NotImplemented
    def multifactor_hensel_lifting(self, p, f, fr, l):
        zx = ZX()
        r = len(fr)
        # step 1
        if r == 1:
            # extended gcd in ZZ
            if f.get_leading_coef() == 1:
                return [f]

            pl = p**l
            _, w, _ = ZZ().extended_gcd(f.get_leading_coef(), pl)
            return [zx.mod_elem(zx.mul_scalar(f, w), pl)]

        # step 2
        k = floor(r / 2)
        d = ceil(log2(l))

        # step 3
        g = reduce(zx.mul, fr[0:k])
        g = zx.mul_scalar(g, f.get_leading_coef())
        g = zx.mod_elem(g, p)
        h = reduce(zx.mul, fr[k:r])
        h = zx.mod_elem(h, p)

        # step 4: ZZ/<p> is always a field (namely Zp), since p is prime
        zpx = PolynomialsOverField(Zp(p))
        coef, s, t = zpx.extended_gcd(g, h)
        s = zpx.mul_scalar(s, Zp(p).mul_inv(coef.coefs[0]))
        t = zpx.mul_scalar(t, Zp(p).mul_inv(coef.coefs[0]))

        # step 5
        m = p
        for j in range(d):
            # step 6
            g, h, s, t = self.hensel_step(m, f, g, h, s, t)
            m **= 2

        _k1 = self.multifactor_hensel_lifting(p, g, fr[0:k], l)
        _k2 = self.multifactor_hensel_lifting(p, h, fr[k:r], l)
        _k1.extend(_k2)
        return _k1