Polynomial.java
package com.acme.math;

import static church.lang.Error.error;
import static church.lang.operators.Arithmetic.*;
import static church.lang.operators.Relational.$$equal;
import static church.lang.operators.Streams.$$encode;
import static com.acme.math.Pair.*;

@SuppressWarnings("unchecked")
public abstract class Polynomial<T> {
    public interface Visitor<T, S> {
        default S get(Polynomial<T> receiver) {
            S result = receiver.accept(this);
            return result == null ? defaultImplementation(receiver) : result;
        }

        default S getOrDefault(Polynomial<T> receiver, S defaultValue) {
            S result = receiver.accept(this);
            return result == null ? defaultValue : result;
        }

        default S constant(T a) {
            return null;
        }

        default S term(T a, int n, Polynomial<T> r) {
            return null;
        }

        default S defaultImplementation(Polynomial<T> e) {
            throw new UnsupportedOperationException();
        }
    }

    public abstract <S> S accept(Visitor<T, S> visitor);

    public static <T> Polynomial<T> constant(T a) {
        return new Polynomial<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.constant(a);
            }
        };
    }

    public static <T> Polynomial<T> term(T a, int n, Polynomial<T> r) {
        return new Polynomial<T>() {
            public <S> S accept(Visitor<T, S> visitor) {
                return visitor.term(a, n, r);
            }
        };
    }

    public static interface $Polynomial<T> {
        public Polynomial<T> polynomial(T a, int n, Polynomial<T> r);
    }

    public static <T> $Polynomial<T> polynomial($$equal<T> $L0, $Additive_identity<T> $L1) {
        return (a, n, r) -> $L0.$equal(a, $L1.additive_identity()) ? r : n == 0 ? constant(a) : term(a, n, r);
    }

    public static <T> T lc(Polynomial<T> $0) {
        return new Visitor<T, T>() {
            public T constant(T a) {
                return a;
            }

            public T term(T a, int n, Polynomial<T> r) {
                return a;
            }

        }
                .get($0);
    }

    public static <T> int deg(Polynomial<T> $0) {
        return new Visitor<T, Integer>() {
            public Integer constant(T a) {
                return 0;
            }

            public Integer term(T a, int n, Polynomial<T> r) {
                return n;
            }

        }
                .get($0);
    }

    public static interface $Red<T> {
        public Polynomial<T> red(Polynomial<T> $0);
    }

    public static <T> $Red<T> red($Additive_identity<Polynomial<T>> $L0) {
        return $0 -> new Visitor<T, Polynomial<T>>() {
            public Polynomial<T> constant(T a) {
                return $L0.additive_identity();
            }

            public Polynomial<T> term(T a, int n, Polynomial<T> r) {
                return r;
            }

        }
                .get($0);
    }

    public static <T, U> $$encode<T, Polynomial<U>> $encode($$encode<T, U> $L0, $$encode<T, String> $L1, $$equal<U> $L2, $Multiplicative_identity<U> $L3, $$encode<T, Integer> $L4, $$equal<Polynomial<U>> $L5, $Additive_identity<Polynomial<U>> $L6) {
        return new $$encode<T, Polynomial<U>>() {
            public T $encode(T stream, Polynomial<U> $0) {
                return new Visitor<U, T>() {
                    public T constant(U a) {
                        return $L0.$encode(stream, a);
                    }

                    public T term(U a, int n, Polynomial<U> r) {
                        T s1 = $L1.$encode($L2.$equal(a, $L3.multiplicative_identity()) ? stream : $L1.$encode($L0.$encode(stream, a), "*"), "x");
                        T s2 = n == 1 ? s1 : $L4.$encode($L1.$encode(s1, "^"), n);
                        return $L5.$equal(r, $L6.additive_identity()) ? s2 : $encode($L1.$encode(s2, " + "), r);
                    }

                }
                        .get($0);
            }
        };
    }

    public static <T> $$equal<Polynomial<T>> $equal($$equal<T> $L0) {
        return new $$equal<Polynomial<T>>() {
            public boolean $equal(Polynomial<T> $0, Polynomial<T> $1) {
                return new Visitor<T, Boolean>() {
                    public Boolean constant(T a1) {
                        return new Visitor<T, Boolean>() {
                            public Boolean constant(T a2) {
                                return $L0.$equal(a1, a2);
                            }

                            public Boolean term(T a2, int n2, Polynomial<T> r2) {
                                return false;
                            }

                        }
                                .get($1);
                    }

                    public Boolean term(T a1, int n1, Polynomial<T> r1) {
                        return new Visitor<T, Boolean>() {
                            public Boolean constant(T a2) {
                                return false;
                            }

                            public Boolean term(T a2, int n2, Polynomial<T> r2) {
                                return (($L0.$equal(a1, a2) && n1 == n2) && $equal(r1, r2));
                            }

                        }
                                .get($1);
                    }

                }
                        .get($0);
            }
        };
    }

    public static <T> $Additive_identity<Polynomial<T>> additive_identity($Additive_identity<T> $L0) {
        return () -> constant($L0.additive_identity());
    }

    public static <T> $$sum<Polynomial<T>> $sum($$sum<T> $L0, $Polynomial<T> $L1) {
        return new $$sum<Polynomial<T>>() {
            public Polynomial<T> $sum(Polynomial<T> $0, Polynomial<T> $1) {
                return new Visitor<T, Polynomial<T>>() {
                    public Polynomial<T> constant(T a) {
                        return new Visitor<T, Polynomial<T>>() {
                            public Polynomial<T> constant(T b) {
                                return Polynomial.constant($L0.$sum(a, b));
                            }

                            public Polynomial<T> term(T a2, int n2, Polynomial<T> r2) {
                                return Polynomial.term(a2, n2, $sum(Polynomial.constant(a), r2));
                            }

                        }
                                .get($1);
                    }

                    public Polynomial<T> term(T a1, int n1, Polynomial<T> r1) {
                        return new Visitor<T, Polynomial<T>>() {
                            public Polynomial<T> constant(T a2) {
                                return Polynomial.term(a1, n1, $sum(r1, Polynomial.constant(a2)));
                            }

                            public Polynomial<T> term(T a2, int n2, Polynomial<T> r2) {
                                return (n1 > n2) ? Polynomial.term(a1, n1, $sum(r1, Polynomial.term(a2, n2, r2))) : (n1 < n2) ? Polynomial.term(a2, n2, $sum(Polynomial.term(a1, n1, r1), r2)) : $L1.polynomial($L0.$sum(a1, a2), n1, $sum(r1, r2));
                            }

                        }
                                .get($1);
                    }

                }
                        .get($0);
            }
        };
    }

    public static <T> $$neg<Polynomial<T>> $neg($$neg<T> $L0) {
        return new $$neg<Polynomial<T>>() {
            public Polynomial<T> $neg(Polynomial<T> $0) {
                return new Visitor<T, Polynomial<T>>() {
                    public Polynomial<T> constant(T a) {
                        return Polynomial.constant($L0.$neg(a));
                    }

                    public Polynomial<T> term(T a, int n, Polynomial<T> r) {
                        return Polynomial.term($L0.$neg(a), n, $neg(r));
                    }

                }
                        .get($0);
            }
        };
    }

    public static <T, U> U map2(church.lang.Functions.Function1<T, church.lang.Functions.Function1<Integer, church.lang.Functions.Function1<U, U>>> f, church.lang.Functions.Function1<T, U> g, Polynomial<T> $0) {
        return new Visitor<T, U>() {
            public U constant(T c) {
                return g.of(c);
            }

            public U term(T a, int n, Polynomial<T> r) {
                return f.of(a).of(n).of(Polynomial.map2(f, g, r));
            }

        }
                .get($0);
    }

    public static <T, U> Polynomial<U> map1(church.lang.Functions.Function1<T, U> f, Polynomial<T> p) {
        return map2(a -> n -> r -> term(f.of(a), n, r), a -> constant(f.of(a)), p);
    }

    public static interface $Scale<T, U> {
        public Polynomial<U> scale(T k, Polynomial<U> p);
    }

    public static <T, U> $Scale<T, U> scale($$prd<T, U> $L0) {
        return (k, p) -> map1(a -> $L0.$prd(k, a), p);
    }

    public static interface $ScaleAndShift<T, U> {
        public Polynomial<U> scaleAndShift(T k, int delta, Polynomial<U> p);
    }

    public static <T, U> $ScaleAndShift<T, U> scaleAndShift($Scale<T, U> $L0, $Polynomial<U> $L1, $$prd<T, U> $L2, $Additive_identity<Polynomial<U>> $L3) {
        return (k, delta, p) -> delta == 0 ? $L0.scale(k, p) : map2(a -> n -> r -> $L1.polynomial($L2.$prd(k, a), (n + delta), r), a -> $L1.polynomial($L2.$prd(k, a), delta, $L3.additive_identity()), p);
    }

    public static <T> $Multiplicative_identity<Polynomial<T>> multiplicative_identity($Multiplicative_identity<T> $L0) {
        return () -> constant($L0.multiplicative_identity());
    }

    public static <T, U> $$prd<Polynomial<T>, Polynomial<U>> $prd($$prd<T, U> $L0, $Polynomial<U> $L1, $$sum<Polynomial<U>> $L2, $ScaleAndShift<T, U> $L3) {
        return new $$prd<Polynomial<T>, Polynomial<U>>() {
            public Polynomial<U> $prd(Polynomial<T> $0, Polynomial<U> $1) {
                return new Visitor<T, Polynomial<U>>() {
                    public Polynomial<U> constant(T a1) {
                        return new Visitor<U, Polynomial<U>>() {
                            public Polynomial<U> constant(U a2) {
                                return Polynomial.constant($L0.$prd(a1, a2));
                            }

                            public Polynomial<U> term(U a2, int n2, Polynomial<U> r2) {
                                return $L1.polynomial($L0.$prd(a1, a2), n2, $prd(Polynomial.constant(a1), r2));
                            }

                        }
                                .get($1);
                    }

                    public Polynomial<U> term(T a1, int n1, Polynomial<T> r1) {
                        return new Visitor<U, Polynomial<U>>() {
                            public Polynomial<U> constant(U a2) {
                                return $L1.polynomial($L0.$prd(a1, a2), n1, $prd(r1, Polynomial.constant(a2)));
                            }

                            public Polynomial<U> term(U a2, int n2, Polynomial<U> r2) {
                                return $L2.$sum($L3.scaleAndShift(a1, n1, Polynomial.term(a2, n2, r2)), $prd(r1, Polynomial.term(a2, n2, r2)));
                            }

                        }
                                .get($1);
                    }

                }
                        .get($0);
            }
        };
    }

    public static interface $DivideOrFail2<T> {
        public Polynomial<T> divideOrFail2(Polynomial<T> p, T v);
    }

    public static <T> $DivideOrFail2<T> divideOrFail2($$dof<T> $L0) {
        return (p, v) -> map1(a -> $L0.$dof(a, v), p);
    }

    public static interface $Divide1<T> {
        public Pair<Polynomial<T>> divide1(Polynomial<T> q, Polynomial<T> r, Polynomial<T> v);
    }

    public static <T> $Divide1<T> divide1($$equal<Polynomial<T>> $L0, $Additive_identity<Polynomial<T>> $L1, $$dof<T> $L2, $Polynomial<T> $L3, $$sub<Polynomial<T>> $L4, $Red<T> $L5, $ScaleAndShift<T, T> $L6) {
        return new $Divide1<T>() {
            public Pair<Polynomial<T>> divide1(Polynomial<T> q, Polynomial<T> r, Polynomial<T> v) {
                if ($L0.$equal(v, $L1.additive_identity())) {
                    return error("Polynomial:: divide by zero. ");
                }
                int delta = (deg(r) - deg(v));
                if (($L0.$equal(r, $L1.additive_identity()) || (delta < 0))) {
                    return pair(q, r);
                }
                T k = $L2.$dof(lc(r), lc(v));
                return divide1($L3.polynomial(k, delta, q), $L4.$sub($L5.red(r), $L6.scaleAndShift(k, delta, $L5.red(v))), v);
            }
        };
    }

    public static interface $Divide2<T> {
        public Pair<Polynomial<T>> divide2(Polynomial<T> u, Polynomial<T> v);
    }

    public static <T> $Divide2<T> divide2($Divide1<T> $L0, $Additive_identity<Polynomial<T>> $L1) {
        return (u, v) -> $L0.divide1($L1.additive_identity(), u, v);
    }

    public static <T> $Quotient<Polynomial<T>> quotient($Divide2<T> $L0) {
        return (u, v) -> first($L0.divide2(u, v));
    }

    public static <T> $$rem<Polynomial<T>> $rem($Divide2<T> $L0) {
        return (u, v) -> second($L0.divide2(u, v));
    }

    public static interface $Content<T> {
        public T content(Polynomial<T> p);
    }

    public static <T> $Content<T> content($Gcd<T> $L0) {
        return p -> map2(a -> n -> r -> $L0.gcd(a, r), c -> c, p);
    }

    public static interface $PrinciplePart<T> {
        public Polynomial<T> principlePart(Polynomial<T> p);
    }

    public static <T> $PrinciplePart<T> principlePart($DivideOrFail2<T> $L0, $Content<T> $L1) {
        return p -> $L0.divideOrFail2(p, $L1.content(p));
    }

}