"""
    $TYPEDEF

Bivariate Bicycle codes are CSS codes constructed using the *quotient ring*
``R = \\mathbb{F}_2[x, y] / \\langle x^\\ell - 1, y^m - 1 \\rangle``.

# Quotient Ring Formalism

We begin with the polynomial ring ``\\mathbb{F}_2[x, y]``, which is the ring of polynomials in two variables, x and y,
with coefficients in the field ``\\mathbb{F}_2``. The operations are standard polynomial addition and multiplication.

An [ideal](https://en.wikipedia.org/wiki/Ideal_(ring_theory)) is the ring-theoretic analogue of a [normal subgroup](https://en.wikipedia.org/wiki/Normal_subgroup)
from group theory. Just as normal subgroups enable the construction of quotient groups, ideals allow us to form quotient rings.

For a subset I of a ring R to be an ideal, it must satisfy the following properties:
- ``(I, +) \\leq (R, +)``
- ``y \\in R, i \\in I: i \\cdot y \\in I``
- ``x \\in R, i \\in I: x \\cdot i \\in I``

Once we have an ideal, we can construct the quotient ring R/I, whose elements are the cosets themselves. The operations
are defined by adding or multiplying these cosets together. It is important to note that an ideal is not a subring because
it typically does not contain the multiplicative identity. If an ideal contains 1, it must be the entire ring.

We consider the specific ideal generated by two polynomials enforcing periodic boundary conditions:

```math
\\begin{aligned}
J = \\langle x^\\ell - 1, y^m - 1 \\rangle = { p(x,y)(x^\\ell - 1) + q(x,y)(y^m - 1) \\mid p, q \\in \\mathbb{F}_2[x,y]}
\\end{aligned}
```

is a subset of a ring that is closed under addition and under multiplication by any element of the ring (see Definition
10.28 and 10.39) from [ernst2016inquiry](@cite). The set I is the ideal generated by the two polynomials ``x^\\ell - 1``
and ``y^m - 1`` (see Definition 10.45) from [ernst2016inquiry](@cite). This ideal enforces *wrapping around* in both spatial
dimensions: ``x^\\ell - 1`` creates periodic boundary conditions in the x-direction and ``y^m - 1`` creates periodic boundary
conditions in the y-direction.

Formally, to verify that ``J`` satisfies the *ideal* requirements, the properties of J are as follows:

- ``(J, +) \\leq \\mathbb{F}_2[x, y], +)``

For any ``f(x,y) \\in \\mathbb{F}_2[x, y]`` and any ``j(x,y) \\in J``:
- ``f(x,y) \\cdot j(x,y) \\in J``
- ``j(x,y) \\cdot f(x,y) \\in J``

```jldoctest idealsandquotientrings
julia> using Oscar; using QuantumClifford.ECC;

julia> F₂ = GF(2);

julia> R, (x, y) = polynomial_ring(F₂, [:x, :y]);

julia> l, m = 4, 3
(4, 3)

julia> J = ideal(R, [x^l - 1, y^m - 1])
Ideal generated by
  x^4 + 1
  y^3 + 1

julia> x*(x^l - 1) + y*(y^m - 1) ∈ J
true

julia> (x^2 + y)*(x^l - 1) + (x*y + 1)*(y^m - 1) ∈ J
true

julia> 0*(x^l - 1) + 0*(y^m - 1) ∈ J
true
```

The quotient ring ``R = \\mathbb{F}_2[x, y] / I`` is formed by taking the set of [cosets](https://en.wikipedia.org/wiki/Coset)
``f + I``. In this ring, two polynomials are considered the same if their difference is in I. The operations are defined by:

```math
\\begin{aligned}
(f + I) + (g + I) = (f + g) + I, \\quad (f + I)(g + I) = (fg) + I
\\end{aligned}
```

This imposes the relations ``x^\\ell = 1`` and ``y^m = 1``. The operations of addition and multiplication
are performed modulo the ideal I. See Theorem 10.44 of [ernst2016inquiry](@cite) for more details.

```jldoctest idealsandquotientrings
julia> Q, proj = quo(R, J);

julia> x_Q, y_Q = proj(x), proj(y);

julia> zero_Q, one_Q = Q(0), Q(1);

julia> x_Q^l == one_Q
true

julia> y_Q^m == one_Q
true

julia> x_Q^l - one_Q == zero_Q
true

julia> y_Q^m - one_Q == zero_Q
true
```

As a vector space over ``\\mathbb{F}_2``, R has dimension ``\\ell m`` with basis

```math
\\begin{aligned}
{ x^i y^j : 0 \\le i < \\ell, 0 \\le j < m}
\\end{aligned}
```

```jldoctest idealsandquotientrings
julia> basis_elems = [x_Q^i*y_Q^j for i in 0:l-1, j in 0:m-1]
4×3 Matrix{MPolyQuoRingElem{FqMPolyRingElem}}:
 1    y      y^2
 x    x*y    x*y^2
 x^2  x^2*y  x^2*y^2
 x^3  x^3*y  x^3*y^2

julia> length(unique(basis_elems)) == l*m
true
```

There is an injective ring homomorphism from ``R`` to ``\\mathbb{F}_2^{\\ell m \\times \\ell m}``
given by ``x \\mapsto S_\\ell \\otimes I_m`` and ``y \\mapsto I_k \\otimes S_m``, where ``S_k`` is
the cyclic shift matrix of size ``k \\times k`` [eberhardt2024logical](@cite). This homomorphism
allows us to work with either the polynomial quotient ring or explicit circulant matrix formulation.
We implement the polynomial quotient ring formalism.

#### Examples

Here is the `[[54, 8, 6]]` from Table 1 [wang2024coprime](@cite)

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l=3; m=9;

julia> R, (x, y) = polynomial_ring(GF(2), [:x, :y]);

julia> I = ideal(R, [x^l-1, y^m-1]);

julia> S, _ = quo(R, I);

julia> A = S(1 + y^2 + y^4);

julia> B = S(y^3 + x + x^2);

julia> c = BivariateBicycleViaPoly(l, m, A, B);

julia> code_n(c), code_k(c)
(54, 8)
```

Here is the `[[72, 12, 6]]` from Table 3 of [bravyi2024high](@cite)

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l=6; m=6;

julia> R, (x, y) = polynomial_ring(GF(2), [:x, :y]);

julia> I = ideal(R, [x^l-1, y^m-1]);

julia> S, _ = quo(R, I);

julia> A = S(x^3 + y + y^2);

julia> B = S(y^3 + x + x^2);

julia> c = BivariateBicycleViaPoly(l, m, A, B);

julia> code_n(c), code_k(c)
(72, 12)
```

Here is the `[[756, 16, ≤ 34]]` from Table 3 of [bravyi2024high](@cite)

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l=21; m=18;

julia> R, (x, y) = polynomial_ring(GF(2), [:x, :y]);

julia> I = ideal(R, [x^l-1, y^m-1]);

julia> S, _ = quo(R, I);

julia> A = S(x^3 + y^10 + y^17);

julia> B = S(y^5 + x^3 + x^19);

julia> c = BivariateBicycleViaPoly(l, m, A, B);

julia> code_n(c), code_k(c)
(756, 16)
```

Here is the `[[128, 14, 12]]` from Table 1 [eberhardt2024logical](@cite)

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l=8; m=8;

julia> R, (x, y) = polynomial_ring(GF(2), [:x, :y]);

julia> I = ideal(R, [x^l-1, y^m-1]);

julia> S, _ = quo(R, I);

julia> A = S(x^2 + y + y^3 + y^4);

julia> B = S(y^2 + x + x^3 + x^4);

julia> c = BivariateBicycleViaPoly(l, m, A, B);

julia> code_n(c), code_k(c)
(128, 14)
```

# Relationships to other QECCs

This implementation uses the polynomial quotient ring formalism. The same codes can be constructed
using various other approaches:

!!! note
    In the discussion below, we consider the `[[72, 12, 6]]` Bivariate bicycle code as introduced in [bravyi2024high](@cite). For a detailed
    explanation of the *isomorphisms* connecting group algebra of cyclic groups, multivariate quotient rings, and circulant matrix representations,
    see `BivariateBicycleViaCirculantMat`.

- `BivariateBicycleViaCirculantMat`: A generalization of the circulant bivariate bicycle code introduced in [bravyi2024high](@cite),
extending the original three-term polynomial representation up to `l` terms (for `x`-powers) and
`m` terms (for `y`-powers). Instead of polynomials, we provide the *monomial* terms as lists of tuples:

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l = 6;

julia> m = 6;

julia> A = [(:x, 3), (:y, 1), (:y, 2)];

julia> B = [(:y, 3), (:x, 1), (:x, 2)];

julia> c = BivariateBicycleViaCirculantMat(l, m, A, B);

julia> import HiGHS;

julia> code_n(c), code_k(c), distance(c, DistanceMIPAlgorithm(solver=HiGHS))
(72, 12, 6)
```

- `LPCode`: Bivariate Bicycle codes are children of *abelian* [Lifted Product](https://errorcorrectionzoo.org/c/abelian_lifted_product) codes. We provide the ``1 x 1`` matrix of group algebra elements:

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l = 6;

julia> m = 6;

julia> GA = group_algebra(GF(2), abelian_group([l, m]));

julia> x, y = gens(GA);

julia> A = reshape([x^3 + y + y^2], (1, 1));

julia> B = reshape([y^3 + x + x^2], (1, 1));

julia> c = LPCode(A, B);

julia> import HiGHS;

julia> code_n(c), code_k(c), distance(c, DistanceMIPAlgorithm(solver=HiGHS))
(72, 12, 6)
```

- `two_block_group_algebra_code`:  Bivaraite Bicycle codes are a subfamily of *abelian* [2BGA](https://errorcorrectionzoo.org/c/2bga) codes, which themselves are instances of [Lifted Product](https://errorcorrectionzoo.org/c/abelian_lifted_product) codes.

```jldoctest
julia> using Oscar; using QuantumClifford.ECC;

julia> l=6; m=6;

julia> GA = group_algebra(GF(2), abelian_group([l, m]));

julia> x, y = gens(GA);

julia> A = x^3 + y + y^2;

julia> B = y^3 + x + x^2;

julia> c = two_block_group_algebra_code(A,B);

julia> import HiGHS;

julia> code_n(c), code_k(c), distance(c, DistanceMIPAlgorithm(solver=HiGHS))
(72, 12, 6)
```

- `generalized_bicycle_code_as_2bga` and `GeneralizedBicycle`: Bivariate Bicycle codes are siblings of [generalized bicycle](https://errorcorrectionzoo.org/c/generalized_bicycle) codes - both are children of [2BGA](https://errorcorrectionzoo.org/c/2bga) codes.

- `bicycle_code_as_2bga`: The [bicycle](https://errorcorrectionzoo.org/c/bicycle) codes are children of (https://errorcorrectionzoo.org/c/generalized_bicycle) codes.

- `ExtendedGeneralizedBicycle`: These codes are constructed through *algebraic extension* of [generalized bicycle](https://errorcorrectionzoo.org/c/generalized_bicycle) codes.

### Fields
    $TYPEDFIELDS
"""
struct BivariateBicycleViaPoly <: AbstractCSSCode
    """Order of the first abelian group in ``\\mathbb{F}_2[\\mathbb{Z}_\\ell \\times \\mathbb{Z}_m]``"""
    l::Int
    """Order of the second abelian group in ``\\mathbb{F}_2[\\mathbb{Z}_\\ell \\times \\mathbb{Z}_m]``"""
    m::Int
    """First bivariate polynomial in quotient ring ``\\frac{\\mathbb{F}_2[x, y]}{\\langle x^\\ell-1, y^m-1 \\rangle}``"""
    c::MPolyQuoRingElem
    """Second bivariate polynomial in quotient ring ``\\frac{\\mathbb{F}_2[x, y]}{\\langle x^\\ell-1, y^m-1 \\rangle}``"""
    d::MPolyQuoRingElem

    function BivariateBicycleViaPoly(l::Int, m::Int, c::MPolyQuoRingElem, d::MPolyQuoRingElem)
        l > 0 || throw(ArgumentError("l must be positive"))
        m > 0 || throw(ArgumentError("m must be positive"))
        parent(c) == parent(d) || throw(ArgumentError("Polynomials must be in the same quotient ring"))
        Q = parent(c)
        R = base_ring(Q)
        base_ring(R) == GF(2) || throw(ArgumentError("Base ring must be GF(2)"))
        nvars(R) == 2 || throw(ArgumentError("Must be bivariate polynomials"))
        new(l, m, c, d)
    end
end

function _poly_to_coeff_mat(poly::MPolyQuoRingElem, l::Int, m::Int)
    mat = zeros(Int, l, m)
    for term in terms(lift(poly))
        coeffᵥₐₗ = coeff(term, 1)
        coeffᵢₙₜ = iszero(coeffᵥₐₗ) ? 0 : 1
        monom = monomial(term, 1)
        exps = exponent_vector(monom, 1)
        i = length(exps) >= 1 ? exps[1] : 0
        j = length(exps) >= 2 ? exps[2] : 0
        i = mod(i, l)
        j = mod(j, m)
        if i < l && j < m
            mat[i+1, j+1] = coeffᵢₙₜ
        end
    end
    return mat
end

function _bivariate_circulant(coeff_mat::Matrix{Int})
    l, m = size(coeff_mat)
    n = l*m
    circ_mat = zeros(Int, n, n)
    for i₁ in 0:l-1, j₁ in 0:m-1
        row_idx = i₁*m+j₁+1
        for i₂ in 0:l-1, j₂ in 0:m-1
            i_mod = mod(i₁+i₂, l)
            j_mod = mod(j₁+j₂, m)
            col_idx = i_mod*m+j_mod+1
            circ_mat[row_idx, col_idx] += coeff_mat[i₂+1, j₂+1]
        end
    end
    return circ_mat
end

function parity_matrix_xz(c::BivariateBicycleViaPoly)
    A_coeff = _poly_to_coeff_mat(c.c, c.l, c.m)
    B_coeff = _poly_to_coeff_mat(c.d, c.l, c.m)
    A = _bivariate_circulant(A_coeff)
    B = _bivariate_circulant(B_coeff)
    hx, hz = hcat(A, B), hcat(transpose(B), transpose(A))
    return hx, hz
end

parity_matrix_x(c::BivariateBicycleViaPoly) = parity_matrix_xz(c)[1]

parity_matrix_z(c::BivariateBicycleViaPoly) = parity_matrix_xz(c)[2]

code_n(c::BivariateBicycleViaPoly) = 2*c.l*c.m
