package org.bouncycastle.pqc.crypto.sqisign;

/**
 * Left ideal of a maximal order in the quaternion algebra. Java port of
 * {@code src/quaternion/ref/generic/ideal.c}.
 *
 * <p>A {@code QuatLeftIdeal} is a {@link QuatLattice} together with its
 * (integer) reduced norm and a reference to the parent maximal order.</p>
 */
final class QuatLeftIdeal
{
    public final QuatLattice lattice;
    public final Ibz norm;
    public QuatLattice parentOrder;

    public QuatLeftIdeal()
    {
        this.lattice = new QuatLattice();
        this.norm = new Ibz();
    }

    /**
     * {@code quat_lideal_copy}.
     */
    public static void copy(QuatLeftIdeal dst, QuatLeftIdeal src)
    {
        dst.parentOrder = src.parentOrder;
        Ibz.copy(dst.norm, src.norm);
        Ibz.copy(dst.lattice.denom, src.lattice.denom);
        IbzMat.copy4x4(dst.lattice.basis, src.lattice.basis);
    }

    /**
     * {@code quat_lideal_norm}: compute and set the ideal norm from
     * the lattice index in the parent order.
     */
    public static void norm(QuatLeftIdeal lideal)
    {
        QuatLattice.index(lideal.norm, lideal.lattice, lideal.parentOrder);
        Ibz tmp = new Ibz();
        int ok = Ibz.sqrt(tmp, lideal.norm);
        if (ok != 1)
        {
            throw new IllegalStateException("ideal norm: index is not a perfect square");
        }
        Ibz.copy(lideal.norm, tmp);
    }

    /**
     * Principal ideal generated by {@code x} in {@code order}:
     * lattice = order * x; norm = norm(x).
     * Mirrors {@code quat_lideal_create_principal}.
     */
    public static void createPrincipal(QuatLeftIdeal lideal, QuatAlg.Elem x, QuatLattice order, QuatAlg alg)
    {
        Ibz normN = new Ibz();
        Ibz normD = new Ibz();
        QuatLattice.algElemMul(lideal.lattice, order, x, alg);
        QuatLattice.reduceDenom(lideal.lattice, lideal.lattice);
        QuatAlg.norm(normN, normD, x, alg);
        if (Ibz.isOne(normD) != 1)
        {
            throw new IllegalStateException("createPrincipal: non-integral norm");
        }
        Ibz.copy(lideal.norm, normN);
        lideal.parentOrder = order;
    }

    /**
     * Ideal generated by {@code x} and {@code N} together: O*x + O*N.
     * Mirrors {@code quat_lideal_create}.
     */
    public static void create(QuatLeftIdeal lideal, QuatAlg.Elem x, Ibz N, QuatLattice order, QuatAlg alg)
    {
        if (QuatAlg.isZero(x) == 1)
        {
            throw new IllegalArgumentException("create: zero generator");
        }

        QuatLattice ON = new QuatLattice();
        createPrincipal(lideal, x, order, alg);

        IbzMat.scalarMul4x4(ON.basis, N, order.basis);
        Ibz.copy(ON.denom, order.denom);

        QuatLattice.add(lideal.lattice, lideal.lattice, ON);
        lideal.parentOrder = order;
        norm(lideal);
    }

    /**
     * {@code quat_lideal_mul}: right-multiply the ideal by an algebra element.
     */
    public static void mul(QuatLeftIdeal product, QuatLeftIdeal lideal, QuatAlg.Elem alpha, QuatAlg alg)
    {
        Ibz normN = new Ibz();
        Ibz normD = new Ibz();
        QuatLattice.algElemMul(product.lattice, lideal.lattice, alpha, alg);
        product.parentOrder = lideal.parentOrder;
        QuatAlg.norm(normN, normD, alpha, alg);
        Ibz prodNorm = new Ibz();
        Ibz.mul(prodNorm, lideal.norm, normN);
        if (Ibz.divides(prodNorm, normD) != 1)
        {
            throw new IllegalStateException("mul: norm not divisible");
        }
        Ibz remainder = new Ibz();
        Ibz.div(product.norm, remainder, prodNorm, normD);
    }

    /**
     * {@code quat_lideal_add}: ideal sum.
     */
    public static void add(QuatLeftIdeal sum, QuatLeftIdeal i1, QuatLeftIdeal i2)
    {
        if (i1.parentOrder != i2.parentOrder)
        {
            throw new IllegalArgumentException("add: parent orders differ");
        }
        QuatLattice.add(sum.lattice, i1.lattice, i2.lattice);
        sum.parentOrder = i1.parentOrder;
        norm(sum);
    }

    /**
     * {@code quat_lideal_inter}: ideal intersection.
     */
    public static void inter(QuatLeftIdeal inter, QuatLeftIdeal i1, QuatLeftIdeal i2)
    {
        if (i1.parentOrder != i2.parentOrder)
        {
            throw new IllegalArgumentException("inter: parent orders differ");
        }
        QuatLattice.intersect(inter.lattice, i1.lattice, i2.lattice);
        inter.parentOrder = i1.parentOrder;
        norm(inter);
    }

    /**
     * Inverse lattice without HNF: conjugate of the ideal's lattice, scaled
     * so the lattice has denominator = old_denom * norm. Mirrors C
     * {@code quat_lideal_inverse_lattice_without_hnf}.
     */
    public static void inverseLatticeWithoutHnf(QuatLattice inv, QuatLeftIdeal lideal)
    {
        QuatLattice.conjugateWithoutHnf(inv, lideal.lattice);
        Ibz.mul(inv.denom, inv.denom, lideal.norm);
    }

    /**
     * {@code quat_lideal_right_transporter}: transporter from I1 to I2.
     */
    private static void rightTransporter(QuatLattice trans, QuatLeftIdeal lideal1, QuatLeftIdeal lideal2, QuatAlg alg)
    {
        QuatLattice inv = new QuatLattice();
        inverseLatticeWithoutHnf(inv, lideal1);
        QuatLattice.mul(trans, inv, lideal2.lattice, alg);
    }

    /**
     * {@code quat_lideal_right_order}: right order of the ideal.
     */
    private static void rightOrder(QuatLattice order, QuatLeftIdeal lideal, QuatAlg alg)
    {
        rightTransporter(order, lideal, lideal, alg);
    }

    /**
     * {@code quat_lideal_class_gram}: gram matrix divided by norm * denom².
     * Returns the rationality-preserving Gram matrix of the ideal class.
     */
    public static void classGram(Ibz[][] G, QuatLeftIdeal lideal, QuatAlg alg)
    {
        QuatLattice.gram(G, lideal.lattice, alg);
        Ibz divisor = new Ibz();
        Ibz remainder = new Ibz();
        Ibz.mul(divisor, lideal.lattice.denom, lideal.lattice.denom);
        Ibz.mul(divisor, divisor, lideal.norm);
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j <= i; j++)
            {
                Ibz.div(G[i][j], remainder, G[i][j], divisor);
                if (Ibz.isZero(remainder) != 1)
                {
                    throw new IllegalStateException("classGram: division remainder non-zero");
                }
            }
        }
        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j <= i - 1; j++)
            {
                Ibz.copy(G[j][i], G[i][j]);
            }
        }
    }

    /**
     * {@code quat_lideal_conjugate_without_hnf}: conjugate ideal whose parent
     * order is the right order of the input.
     */
    public static void conjugateWithoutHnf(QuatLeftIdeal conj, QuatLattice newParentOrder,
                                           QuatLeftIdeal lideal, QuatAlg alg)
    {
        rightOrder(newParentOrder, lideal, alg);
        QuatLattice.conjugateWithoutHnf(conj.lattice, lideal.lattice);
        conj.parentOrder = newParentOrder;
        Ibz.copy(conj.norm, lideal.norm);
    }

    // ---- generator search ---------------------------------------------------

    /**
     * Search for a small algebra element {@code gen} that generates the ideal
     * (i.e. its image in the lattice has norm equal to the ideal's norm,
     * coprime to the ideal-norm quotient). Mirrors {@code quat_lideal_generator}.
     * <p>
     * Iterates over integer 4-tuples (a, b, c, d) in expanding L1-norm balls
     * and accepts the first primitive one whose induced element satisfies the
     * norm-coprimality test.
     */
    public static int generator(QuatAlg.Elem gen, QuatLeftIdeal lideal, QuatAlg alg)
    {
        Ibz normInt = new Ibz();
        Ibz normDenom = new Ibz();
        Ibz gcd = new Ibz();
        Ibz r = new Ibz();
        Ibz q = new Ibz();
        Ibz[] vec = IbzVec.init4();

        int intNorm = 0;
        while (true)
        {
            intNorm++;
            for (int a = -intNorm; a <= intNorm; a++)
            {
                for (int b = -intNorm + Math.abs(a); b <= intNorm - Math.abs(a); b++)
                {
                    for (int c = -intNorm + Math.abs(a) + Math.abs(b); c <= intNorm - Math.abs(a) - Math.abs(b); c++)
                    {
                        int d = intNorm - Math.abs(a) - Math.abs(b) - Math.abs(c);
                        IbzVec.set4(vec, a, b, c, d);
                        IbzVec.content4(gcd, vec);
                        if (Ibz.isOne(gcd) != 1)
                        {
                            continue;
                        }
                        IbzMat.eval4x4(gen.coord, lideal.lattice.basis, vec);
                        Ibz.copy(gen.denom, lideal.lattice.denom);
                        QuatAlg.norm(normInt, normDenom, gen, alg);
                        if (Ibz.isOne(normDenom) != 1)
                        {
                            continue;
                        }
                        Ibz.div(q, r, normInt, lideal.norm);
                        if (Ibz.isZero(r) != 1)
                        {
                            continue;
                        }
                        Ibz.gcd(gcd, lideal.norm, q);
                        if (Ibz.cmp(gcd, Ibz.ONE) == 0)
                        {
                            return 1;
                        }
                    }
                }
            }

            // Defensive cap to avoid runaway search in pathological inputs.
            if (intNorm > 40)
            {
                return 0;
            }
        }
    }

}
