class AsmuthBloom:
    def __init__(self, n_holders, min_holder, secret, verbose=False):
        self.__verbose = verbose
        self.__sieve = Sieve()
        self.__n_holder = n_holders
        self.__min_holder = min_holder
        self.__holders = [Holder() for _ in range(n_holders)]
        self.__secret = secret
        self.__generateHolders()

    def __generateHolders(self):
        self.__generateSequence()
        self.__generateRandom()

        for i in range(self.__n_holder):
            self.__holders[i].modulo = self.__sequence[i + 1]
            self.__holders[i].secret = self.__y % self.__holders[i].modulo

    def __generateSequence(self):

        initial_multiplier = int(self.__secret / 10)

        # Get first prime in sequence
        first_prime = self.__sieve.getFirstPrimeLargerThan(self.__secret)
        sequenceValid = False

        # Get the rest of sequence
        while not sequenceValid:
            current_sequence = [first_prime]
            temp_prime = self.__sieve.getFirstPrimeLargerThan(
                self.__secret * initial_multiplier)
            for _ in range(self.__n_holder):
                temp_prime = self.__sieve.getFirstPrimeLargerThan(temp_prime)
                current_sequence.append(temp_prime)

            if self.__isSequenceValid(current_sequence):
                sequenceValid = True

            else:
                initial_multiplier = initial_multiplier + 1

        self.__sequence = current_sequence
        self.__M = self.__seqprod(self.__sequence[1:self.__min_holder + 1])

        if self.__verbose:
            print("Sequence : " + str(self.__sequence))
            print("Big-M : " + str(self.__M))

    # Generate a random number
    def __generateRandom(self):

        max = int((self.__M - self.__secret) / self.__sequence[0])

        self.__random_number = random.randint(1, max)
        self.__y = self.__secret + self.__random_number * self.__sequence[0]

        if self.__verbose:
            print("Random number : " + str(self.__random_number))
            print("Y-value : " + str(self.__y))

    ### UTILS
    def __isSequenceValid(self, seq):
        lower_product = self.__seqprod(seq[1:self.__min_holder + 1])
        upper_product = seq[0] * self.__seqprod(
            seq[self.__n_holder - self.__min_holder + 2:])

        return lower_product > upper_product

    def getSequence(self):
        return self.__sequence

    def __seqprod(self, iterable):
        product = 1
        for item in iterable:
            product = product * item

        return product

    def getHolders(self):
        return self.__holders

    ### Solver
    def solve(self):

        chosen_holders = random.sample(self.__holders, self.__min_holder)

        if self.__verbose:
            print("Chosen holders :")
            for holder in chosen_holders:
                print(holder)

        ## Solver CRT
        modulo_list = [holder.modulo for holder in chosen_holders]
        remainder_list = [holder.secret for holder in chosen_holders]

        solution = chinese_remainder(modulo_list, remainder_list)
        if self.__verbose:
            print("CRT Solution : " + str(solution))
        return (solution) % self.__sequence[0]

    # Find inverse
    # Assume coprime
    # Using native methods. We can fix them later
    def __inverse(self, multiplier, modulo):
        for i in range(modulo):
            if ((multiplier * i) % modulo) == 1:
                return i