"""
    $TYPEDEF

The generalized toric code is specified by two [Laurent polynomials](https://en.wikipedia.org/wiki/Laurent_polynomial)
``f(x,y)`` and ``g(x,y)`` over ``\\mathbb{F}_2``. These polynomials encode the local
stabilizers of the X and Z-type operators. The parameters (α, β, γ) define
the geometry of the underlying twisted torus:

- ``\\alpha``: vertical period from lattice vector ``a_1 = (0, \\alpha)``
- ``\\beta``: horizontal extent from lattice vector ``a_2 = (\\beta, \\gamma)``
- ``\\gamma``: vertical shift applied when wrapping horizontally

### Laurent Polynomial Formalism

Any two-dimensional translation-invariant Pauli stabilizer code over ``\\mathbb{Z}_2`` qubits
that satisfies the topological order condition can be transformed via a finite-depth quantum
circuit into a direct sum of Kitaev toric codes ([liang2025generalizedtoriccodestwisted](@cite),
[Liang_2024](@cite)).

[liang2025generalizedtoriccodestwisted](@cite) employ a ring-theoretic approach within the
framework of topological order to study two-dimensional topological CSS codes using [Laurent
polynomial](https://en.wikipedia.org/wiki/Laurent_polynomial) formalism. This technique allows
for efficient construction of new quantum LDPC codes.

!!! note
    The use of *twisted tori* enables stabilizers with more *localized* support compared to prior methods.

By leveraging Laurent polynomial rings, [liang2025generalizedtoriccodestwisted](@cite) derive the
topological order associated with Pauli stabilizer codes. Extending this framework, it introduces a
*ring-theoretic* method that simplifies computations for CSS codes. Specifically, *Gröbner basis* techniques
are utilized to classify [anyons](https://en.wikipedia.org/wiki/Anyon) in these topological orders
[liang2025generalizedtoriccodestwisted](@cite).

For simplicity, [liang2025generalizedtoriccodestwisted](@cite) focus on the *square lattice*. On the
square lattice, a unit cell consisting of two edges, representing their Pauli operators as 4-dimensional vectors:

```math
\\begin{aligned}
X_1 = \\begin{bmatrix} 1 \\\\ 0 \\\\ 0 \\\\ 0 \\end{bmatrix}, \\quad
X_2 = \\begin{bmatrix} 0 \\\\ 1 \\\\ 0 \\\\ 0 \\end{bmatrix}, \\quad
Z_1 = \\begin{bmatrix} 0 \\\\ 0 \\\\ 1 \\\\ 0 \\end{bmatrix}, \\quad
Z_2 = \\begin{bmatrix} 0 \\\\ 0 \\\\ 0 \\\\ 1 \\end{bmatrix}
\\end{aligned}
```

Translated Pauli operators are obtained by multiplying these basis vectors by the monomial
``x^n y^m`` (where n, m may be negative), implementing a shift of n steps in the x-direction
and m steps in the y-direction. In this formalism, the product of two Pauli operators corresponds to
the sum of their 4-dimensional vectors.

The Pauli operator vectors form a [module](https://en.wikipedia.org/wiki/Module_(mathematics)) over the
Laurent polynomial ring ``R = \\mathbb{Z}_2[x, y, x^{-1}, y^{-1}]``.

The translation-invariant ``\\mathbb{Z}_2`` CSS code with stabilizers expressed as:

```math
\\begin{aligned}
A_v = \\begin{bmatrix} f(x, y) \\\\ g(x, y) \\\\ \\hline 0 \\\\ 0 \\end{bmatrix}, \\quad
B_p = \\begin{bmatrix} 0 \\\\ 0 \\\\ \\hline \\overline{g}(x, y) \\\\ \\overline{f}(x, y) \\end{bmatrix}
\\end{aligned}
```

where ``\\overline{x^n y^m} := x^{-n} y^{-m}``. ``A_v`` and ``B_p`` denote the X and Z stabilizer
generators, respectively, forming the stabilizer group ``S``. Specifically, ``S`` is generated by all lattice
translations of ``A_v`` and ``B_p``, i.e., the set of operators ``{x^n y^m A_v, x^n y^m B_p \\mid n, m \\in \\mathbb{Z}}`` [liang2025generalizedtoriccodestwisted](@cite).

Utilizing Laurent polynomial formalism, [liang2025generalizedtoriccodestwisted](@cite) introduces the
**generalized toric code**, a subclass of bivariate bicycle codes, defined by:

```math
\\begin{aligned}
f(x, y) = 1 + x + x^a y^b, \\quad
g(x, y) = 1 + y + x^c y^d
\\end{aligned}
```

### Topological Order Condition

A key condition [liang2025generalizedtoriccodestwisted](@cite) for these polynomials is:

```math
\\begin{aligned}
\\langle f(x, y) \\rangle \\cap \\langle g(x, y) \\rangle = \\langle f(x, y) g(x, y) \\rangle
\\end{aligned}
```

where ``\\langle p(x, y) \\rangle`` denotes the [ideal](https://en.wikipedia.org/wiki/Ideal_(ring_theory))
in R generated by ``p(x, y)``. This implies that ``f(x, y)`` and ``g(x, y)`` are coprime.

Consider the `[[396, 8, ≤ 26]]` Generalized Toric Code from Table IV of [liang2025generalizedtoriccodestwisted](@cite). The
TO condition is proved using ``Oscar.jl`` as follows:

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

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

julia> rels = [x*x⁻¹ - 1, y*y⁻¹ - 1];

julia> R_q = quo(R, rels)[1];

julia> f = 1 + x + x⁻¹*y⁻¹^3;

julia> g = 1 + y + x^3*y⁻¹;

julia> I = ideal(R_q, [f]);

julia> J = ideal(R_q, [g]);

julia> I_cap_J = intersect(I, J);

julia> I_times_J = ideal(R_q, [f*g]);

julia> I_cap_J == I_times_J
true

julia> is_coprime(f, g)
true
```

### Gröbner basis Computation

A useful computational technique involves computing the Gröbner basis for the
polynomials ``f'(x, y) = xf(x, y)`` and ``g'(x, y) = yg(x, y)`` to ensure non-negative
exponents. Alternatively, one may introduce auxiliary variables ``\\overline{x}, \\overline{y}``
to represent ``x^{-1}, y^{-1}`` and include the relations ``\\overline{x}x - 1`` and
``\\overline{y}y - 1`` in the Gröbner basis computation [liang2025generalizedtoriccodestwisted](@cite).

#### Example

Here is an example of computing the maximum logical dimension of Example 5
(−1, −4, 4, −1)-generalized toric code from [liang2025generalizedtoriccodestwisted](@cite).

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

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

julia> f = x*y^4 + x^2*y^4 + 1;

julia> g = y + y^2 + x^4;

julia> I = ideal(R, [f, g]);

julia> G = groebner_basis(I, ordering=lex([x,y])) # Eq. 51
Gröbner basis with elements
  1: y^20 + y^17 + 1
  2: x + y^16 + y^13 + y^12 + y^9 + y^2 + y
with respect to the ordering
  lex([x, y])

julia> H = groebner_basis(I, ordering=lex([y,x])) # Eq. 54
Gröbner basis with elements
  1: x^20 + x^18 + x^2 + x + 1
  2: y + x^18 + x^17 + x^8 + x^4 + 1
with respect to the ordering
  lex([y, x])

julia> a, b = max_xy_exponents(G);

julia> all_monomials = [x^i * y^j for i in 0:a-1 for j in 0:b-1] # Eq. 52
20-element Vector{FqMPolyRingElem}:
 1
 y
 y^2
 y^3
 y^4
 y^5
 y^6
 y^7
 y^8
 y^9
 y^10
 y^11
 y^12
 y^13
 y^14
 y^15
 y^16
 y^17
 y^18
 y^19

julia> k_max = 2*length(all_monomials)
40
```

### Parity Checks

The total number of physical qubits is ``n = 2*\\alpha\\beta``, corresponding to horizontal and
vertical edges in each unit cell. The parity-check matrices ``H_X`` and ``H_Z`` are constructed
following approach in Appendix B of [liang2025generalizedtoriccodestwisted](@cite). Qubits are
indexed row-by-row: for each `y`-coordinate, horizontal edges are listed first, then vertical edges.
Stabilizers are generated by translating the *origin* stabilizer, defined by the monomial supports of
`f` and `g`, across all ``(l_x, l_y)`` in ``0 ≤ l_x < \\beta``, ``0 ≤ l_y < \\alpha``. Translations
obey twisted boundary conditions determined by ``(\\alpha, \\beta, \\gamma)``, ensuring the matrices
retain a generalized cyclic structure.

The notable cases of Generalized toric codes include:

### Kitaev Toric Code

The Kitaev Toric code is defined by the Laurent polynomials ``f(x, y) = 1 + x`` and ``g(x, y) = 1 + y``. The
only independent monomial is 1, as all other monomials ``x^a y^b`` can be expressed as:

```math
\\begin{aligned}
    x^a y^b = a_1 + p(x, y)f(x, y) + q(x, y)g(x, y)
\\end{aligned}
```

where ``a_1 \\in \\mathbb{Z}_2`` and ``p(x, y), q(x, y) \\in R``. For instance: ``x^2 = 1 + (1 + x)(1 + x)``.

#### Example

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

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

julia> f = 1 + x;

julia> g = 1 + y;

julia> α1 = (0, 6);

julia> α2 = (3, 3);

julia> c = GeneralizedToric(f, g, α1, α2);

julia> code_n(c), code_k(c)
(36, 2)
```

### Color Code

The Color code is defined by the Laurent polynomials ``f(x, y) = 1 + x + xy`` and ``g(x, y) = 1 + y + xy``. The
independent monomials are 1 and x. Other monomials follow:

```math
\\begin{aligned}
    x^a y^b = a_1 + a_x x + p(x, y)f(x, y) + q(x, y)g(x, y)
\\end{aligned}
```

The code has ``k_{\\text{max}} = 4``, reflecting its structure as a direct sum of two Kitaev codes.

!!! note
    For simple polynomials, independent monomials can be identified manually. However, a systematic
    approach using **Gröbner basis** is necessary for general cases.

#### Example

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

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

julia> f = 1 + x + x*y;

julia> g = 1 + y + x*y;

julia> α1 = (0, 6);

julia> α2 = (3, 3);

julia> c = GeneralizedToric(f, g, α1, α2);

julia> code_n(c), code_k(c)
(36, 4)
```

### ``(-1, 3, 3, -1)``-Generalized Toric Code

The ``(-1, 3, 3, -1)``-Generalized Toric code corresponds to stabilizers in the Gross code
and ``(3, 3)``-BB code. The Laurent polynomials are:

```math
\\begin{aligned}
    f(x, y) = 1 + x + x^{-1}y^3, \\quad g(x, y) = 1 + y + x^3 y^{-1},
\\end{aligned}
```

#### Example

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

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

julia> f = 1 + x + x^-1*y^3;

julia> g = 1 + y + x^3*y^-1;

julia> α1 = (0, 12);

julia> α2 = (6, 0);

julia> c = GeneralizedToric(f, g, α1, α2);

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

### ``(-1, -3, 3, -1)``-Generalized Toric Code

The ``(-1, -3, 3, -1)``-Generalized Toric code corresponds to the (3, -3)-BB code. The
Laurent polynomials are:

```math
\\begin{aligned}
    f(x, y) = 1 + x + x^{-1}y^{-3}, \\quad g(x, y) = 1 + y + x^3 y^{-1}.
\\end{aligned}
```

#### Example

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

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

julia> f = 1 + x + x^-1*y^-3;

julia> g = 1 + y + x^3*y^-1;

julia> α1 = (0, 12);

julia> α2 = (6, 0);

julia> c = GeneralizedToric(f, g, α1, α2);

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

#### Example from Tables I-IV of [liang2025generalizedtoriccodestwisted](@cite)

Here is an example of `[[396, 8, ≤ 26]]` Generalized Toric Code from Table IV of [liang2025generalizedtoriccodestwisted](@cite).

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

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

julia> f = 1 + x + x^-1*y^-3;

julia> g = 1 + y + x^3*y^-1;

julia> α1 = (0, 66);

julia> α2 = (3, 18);

julia> c = GeneralizedToric(f, g, α1, α2);

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

Here is an example of `[[292, 18, 8]]` Generalized Toric Code from Appendix B of [liang2025generalizedtoriccodestwisted](@cite).

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

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

julia> f = 1 + x + y^2;

julia> g = 1 + y + x^-4*y;

julia> α1 = (0, 73);

julia> α2 = (2, 32);

julia> c = GeneralizedToric(f, g, α1, α2);

julia> code_n(c), code_k(c)
(292, 18)
```

### Fields
    $TYPEDFIELDS
"""
struct GeneralizedToric <: AbstractCSSCode
    """Laurent polynomial over ``\\mathbb{F}_2`` defining horizontal–edge action of the X-type stabilizer"""
    f::LaurentMPolyWrap
    """Laurent polynomial over ``\\mathbb{F}_2`` defining vertical–edge action of the X-type stabilizer"""
    g::LaurentMPolyWrap
    """First lattice vector ``\\mathbf{a}_1 = (p_1, q_1)``, from which ``(\\alpha, \\beta, \\gamma)`` can
    be computed so that ``a_1 = (0, \\alpha)`` represents the same *generic* twisted torus under a unimodular transformation."""
    a1::Tuple{Int,Int}
    """Second lattice vector ``\\mathbf{a}_2 = (p_2, q_2)``, from which ``(\\alpha, \\beta, \\gamma)`` can
    be computed so that ``a_2 = (\\beta, \\gamma)`` represents the same *generic* twisted torus under a unimodular transformation."""
    a2::Tuple{Int,Int}

    function GeneralizedToric(f::LaurentMPolyWrap, g::LaurentMPolyWrap, a1::Tuple{Int,Int}, a2::Tuple{Int,Int})
        if a1[1] != 0
            α, β, γ, _, _ = _twisted_params(a1, a2)
            a1 = (0, α)
            a2 = (β, γ)
            return new(f, g, a1, a2)
        else
            return new(f, g, a1, a2)
        end
    end
end

function _twisted_params(a1::Tuple{Int,Int}, a2::Tuple{Int,Int})
    M = matrix(ZZ, 2, 2, [a1[1], a1[2], a2[1], a2[2]])
    H, U = hnf_with_transform(M)
    @assert(H == U*M)
    α = H[2,2]
    β = H[1,1]
    γ = H[1,2]
    return α, β, γ, H, U
end

function _monomial_terms(poly)
    coeffs = coefficients(poly)
    exps   = exponent_vectors(poly)
    terms = Tuple{Int,Int}[]
    for (c,e) in zip(coeffs, exps)
        if c != 0
            push!(terms, (Int(e[1]), Int(e[2])))
        end
    end
    return terms
end

function _move_index(i::Int, dx::Int, dy::Int, β::Int, γ::Int, n::Int, α::Int)
    dy_mod = mod(dy, α)
    i = mod(i + 2*β*dy_mod, n)
    if dx > 0
        for _ = 1:dx
            if mod(i,β) == (β-1)
                i = mod(i+1-(2*γ+1)*β, n)
            else
                i = mod(i+1,n)
            end
        end
    elseif dx < 0
        for _ = 1:(-dx)
            if mod(i,β) == 0
                i = mod(i-1+(2*γ+1)*β, n)
            else
                i = mod(i-1, n)
            end
        end
    end
    return i
end

function parity_matrix_xz(c::GeneralizedToric)
    α = c.a1[2]
    β = c.a2[1]
    γ = c.a2[2]
    f_terms = _monomial_terms(c.f)
    g_terms = _monomial_terms(c.g)
    n = 2*α*β
    cols = α*β
    base_horiz = 0
    base_vert  = β
    origin_av = Int[]
    for (ex,ey) in f_terms
        idx = _move_index(base_horiz, ex, ey, β, γ, n, α)
        push!(origin_av, idx)
    end
    for (ex,ey) in g_terms
        idx = _move_index(base_vert, ex, ey, β, γ, n, α)
        push!(origin_av, idx)
    end
    origin_av = unique(origin_av)
    origin_bp = Int[]
    for (ex,ey) in g_terms
        idx = _move_index(base_horiz, -ex, -ey, β, γ, n, α)
        push!(origin_bp, idx)
    end
    for (ex,ey) in f_terms
        idx = _move_index(base_vert, -ex, -ey, β, γ, n, α)
        push!(origin_bp, idx)
    end
    origin_bp = unique(origin_bp)
    HX = zeros(Int, n, cols)
    HZ = zeros(Int, n, cols)
    col = 1
    for lᵧ in 0:(α-1)
        for lₓ in 0:(β-1)
            for q in origin_av
                newq = _move_index(q, lₓ, lᵧ, β, γ, n, α)
                @assert 0 <= newq < n
                HX[newq+1, col] = 1
            end
            for q in origin_bp
                newq = _move_index(q, lₓ, lᵧ, β, γ, n, α)
                @assert 0 <= newq < n
                HZ[newq+1, col] = 1
            end
            col += 1
        end
    end
    return HX', HZ'
end

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

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

"""
    $TYPEDEF

Given a Gröbner basis `G` of polynomials over a finite field multivariate polynomial ring in variables
including `x` and `y`. It computes the maximum exponents of `x` and `y` appearing in any polynomial in
`G`. It assumes `G` is a vector of polynomials and that variables `x` and `y` exist in the parent ring.

#### Example

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

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

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

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

julia> I = ideal(R, [f, g]);

julia> W = groebner_basis(I, ordering=lex([x, y]), complete_reduction=true)
Gröbner basis with elements
  1: y^7 + y^6 + y^4 + y^2 + y
  2: x*y^2 + x*y + x + y^6 + y^3
  3: x^2 + x + y^3
with respect to the ordering
  lex([x, y])

julia> a, b = max_xy_exponents(W)
(2, 7)
```
"""
function max_xy_exponents(G::IdealGens{FqMPolyRingElem})
    vs = gens(parent(first(G)))
    xpos = findfirst(==(vs[1]), vs)
    ypos = findfirst(==(vs[2]), vs)
    nvs = length(vs)
    exps = []
    for p in G
        for e in exponent_vectors(p)
            evec = collect(e)
            while length(evec) < nvs
                push!(evec, 0)
            end
            push!(exps, (evec[xpos], evec[ypos]))
        end
    end
    max_x_exp = maximum(t[1] for t in exps)
    max_y_exp = maximum(t[2] for t in exps)
    return max_x_exp, max_y_exp
end
