Fourier.java
package com.acme.fourier;

import church.lang.Array;
import church.lang.Array.$CreateArray;
import church.lang.JavaClasses.$Java_class;
import church.lang.operators.Arithmetic.*;
import church.math.Complex;
import church.primitives.Integers;

import static church.lang.Array.length;
import static church.lang.operators.Arithmetic.*;
import static church.math.Complex.complex;
import static java.lang.Math.*;

@SuppressWarnings("unchecked")
public class Fourier {
    private static final $$rem<Integer>                $S0 = Integers::$rem;
    private static final $Java_class<Complex<Double>>  $S1 = () -> (Class<Complex<Double>>) (Object) Complex.class;
    private static final $CreateArray<Complex<Double>> $S2 = Array.createArray($S1);

    public static interface $Evens<T> {
        public T[] evens(T[] a);
    }

    public static <T> $Evens<T> evens($CreateArray<T> $L0) {
        return a -> $L0.createArray((length(a) / 2), i -> a[(2 * i)]);
    }

    public static interface $Odds<T> {
        public T[] odds(T[] a);
    }

    public static <T> $Odds<T> odds($CreateArray<T> $L0) {
        return a -> $L0.createArray(((length(a) + 1) / 2), i -> a[((2 * i) + 1)]);
    }

    public static interface $Transform_rec<T, U> {
        public U[] transform_rec(church.lang.Functions.Function1<Integer, T> exp, U[] vec, int stride);
    }

    public static <T, U> $Transform_rec<T, U> transform_rec($Evens<U> $L0, $Odds<U> $L1, $CreateArray<U> $L2, $$sum<U> $L3, $$prd<T, U> $L4) {
        return new $Transform_rec<T, U>() {
            public U[] transform_rec(church.lang.Functions.Function1<Integer, T> exp, U[] vec, int stride) {
                int N = length(vec);
                if (N == 1) {
                    return vec;
                }
                U[] evens0 = transform_rec(exp, $L0.evens(vec), (2 * stride));
                U[] odds0  = transform_rec(exp, $L1.odds(vec), (2 * stride));
                int halfN  = (N / 2);
                return $L2.createArray(vec.length, i -> {
                    int k = $S0.$rem(i, halfN);
                    return $L3.$sum(evens0[k], $L4.$prd(exp.of((stride * i)), odds0[k]));
                });
            }
        };
    }

    public static Complex<Double> exp_2PI_i(double i, double N) {
        double theta = (((2.0 * PI) * i) / N);
        return complex(cos(theta), sin(theta));
    }

    public static interface $Transform<T> {
        public T[] transform(T[] v);
    }

    public static <T> $Transform<T> transform($Transform_rec<Complex<Double>, T> $L0) {
        return v -> {
            int               N        = length(v);
            Complex<Double>[] expCache = $S2.createArray(N, i -> exp_2PI_i(i, N));
            return $L0.transform_rec(i -> expCache[$S0.$rem(i, N)], v, 1);
        };
    }

}