import itertools
import numpy as np

class Vector:
    """
    This class represents a vector in Z2^n and has the following methods:
        __init__(self, *args): This method initializes the vector with the values passed as arguments.
        __add__(self, other): This method returns the sum of two vectors.
        __mul__(self, value): This method returns the product of a vector and a scalar.
        __str__(self): This method returns the string representation of the vector.
        __repr__(self): This method returns the string representation of the vector.
        dereference(self): This method returns the values of the vector.
    """
    def __init__(self, *args):
        self.values = args
    def __add__(self, other):
        return Vector(*[(x + y)%2 for x, y in zip(self.values, other.values)])
    def __mul__(self, value):
        if value == 0 or value == 1:
            return Vector(*[x * value for x in self.values])
        else:
            raise ValueError("The value must be 0 or 1")
    def __str__(self):
        return str(self.values)
    def __repr__(self):
        return str(self.values)
    def __eq__(self, __o: object) -> bool:
        for i in range(len(self.values)):
            if self.values[i] != __o.values[i]:
                return False
        return True
    def dereference(self):
        return self.values

def generate_all_vectors(n):
    """
    :params:
        n: int
    :return:
        list of Vector objects
    This function generates all possible vectors in Z2^n and returns a list of all possible vectors.
    """
    return [Vector(*x) for x in itertools.product([0, 1], repeat=n)]

def generate_all_bases(n):
    """
    :params:
        n: int
    :return:
        list of lists of Vector objects
    This function generates all possible bases of Z2^n and returns a list of all possible bases.
    """
    possible_bases = list(itertools.product(generate_all_vectors(n), repeat=n))
    bases = []
    for base in possible_bases:
        if is_linearly_independent(base):
            bases.append(base)
    return bases

def is_linearly_independent(vectors):
    """
    :params:
        vectors: list of Vector objects
    :return:
        bool
    This function checks if the vectors passed as arguments are linearly independent or not. It returns True if they are linearly independent and False otherwise.
    """
    z2=[0,1]
    for scalar in list(itertools.product(z2, repeat=len(vectors))):
        if scalar == tuple([0 for i in range(len(vectors))]):
            continue
        vector = Vector(*[0 for i in range(len(vectors[0].dereference()))])
        for i in range(len(vectors)):
            vector += vectors[i] * scalar[i]
        if vector == Vector(*[0 for i in range(len(vectors[0].dereference()))]):
            return False
    return True

def main():
    n = int(input("Enter the dimension of the vector space: "))
    bases = generate_all_bases(n)
    print("The bases are: {}".format(bases))
    print("The number of bases for the vector space is: {}".format(len(bases)))

def file_handle(n):
    """
    :params:
        n: int
    This function generates all possible bases of Z2^n and writes them to a file.
    The file is named output_n_n.txt where n is the dimension of the vector space.
    """
    bases = generate_all_bases(n)
    with open(f"output_n_{n}.txt", "w") as f:
        f.write("The number of bases for the vector space Z2^{} is: {}\n".format(n,len(bases)))
        f.write("The bases are:\n")
        for base in bases:
            f.write(str(base) + "\n")

if __name__ == "__main__":
    file_handle(1)
    file_handle(2)
    file_handle(3)
    file_handle(4)
    # main()