""" The method QuadraticForm::basis_of_short_vectors currently doesn't return 
    a basis all the time, the following can be used to correct this defect.

    An example of this problem is the E8 lattice:
        Q = QuadraticForm( matrix( [[2,0,0,0,0,0,0,1],
                                    [0,2,1,1,1,1,1,1],
                                    [0,1,2,1,1,1,1,1],
                                    [0,1,1,2,1,1,1,1],
                                    [0,1,1,1,2,1,1,1],
                                    [0,1,1,1,1,2,1,1],
                                    [0,1,1,1,1,1,2,0],
                                    [1,1,1,1,1,1,0,2]] ))
        B = Q.basis_of_short_vectors()
        matrix(B).det()
    the result of this will be -2, which indicates this is not a basis.
    We can use the following code to correct this by:
        B = fix_basis(Q, matrix(B).transpose()).columns()
        # B now is a basis
        matrix(B).det()
    The determinant will be -1.
    We remark that the code may not return an optimally short solution.
"""

def find_missed_1(M,L):
    """
        Look for vector v in L (shortest) such that v is not in the image of M
        and there is a substitution of v for a column of M such that the image
        of M' would is strictly larger than that of M
    """
    M2 = M.inverse()
    for Li in L:
        for v in Li:
            v2 = M2*vector(v)
            div = lcm([ x.denominator() for x in v2])
            if div != 1:
                for x in (M2*vector(v)):
                    if x.denominator() == div and x.numerator().abs() == 1:
                        return v
    return 0

def find_missed_2(M,L):
    """
        As above, except only require the image of M' to have smaller index
        than that of M
    """
    M2 = M.inverse()
    for Li in L:
        for v in Li:
            v2 = M2*vector(v)
            div = lcm([ x.denominator() for x in v2])
            if div != 1:
                for x in (M2*vector(v)):
                    if x.denominator() == div and x.numerator().abs() < div:
                        return v
    return 0

def fix_basis(Q,M):
    """
        Iteratively attempt to convert the matrix M into a change of basis
        matrix of Q by substituting small vectors
    """
    new = M
    depth = 1
    while new.det().abs() != 1:
        # Outer loop simply controls generating too many vectors to test
        depth = depth + 1
        L = Q.short_vector_list_up_to_length(depth)
        while new.det().abs() != 1:
            # Inner loop iterates over possible substitutions
            v = find_missed_1(new,L)
            C = new.columns();
            if v == 0:
                # We didn't find a clean substitute, look for a non-clean one
                v = find_missed_2(new,L)
                if v == 0:
                    break
                div = lcm([ x.denominator() for x in (new.inverse()*v)])
                v2 = new.inverse()*v*div
                for i in range(Q.dim()):
                    if v2[i].abs() < div:
                        C[i] = v
                        new = (matrix(C)).transpose()
                        break
            else:
                div = lcm([ x.denominator() for x in (new.inverse()*v)])
                v2 = new.inverse()*v*div
                for i in range(Q.dim()):
                    if v2[i].abs() == 1:
                        C[i] = v
                        new = (matrix(C)).transpose()
                        break
    return new

