import random

class Autograder:
    def check_individual(self, func):
        length = 15
        ind = func(length)
        assert len(ind) == length, f"Expected length {length}, got {len(ind)}"
        assert all(bit in [0, 1] for bit in ind), "Individual must only contain 0s and 1s"
        ind2 = func(length)
        print("✅ create_individual looks good!")

    def check_population(self, func, ind_func):
        pop_size = 10
        length = 15
        pop = func(pop_size, length)
        assert len(pop) == pop_size, f"Expected population size {pop_size}, got {len(pop)}"
        assert len(pop[0]) == length, "Individuals in population have wrong length"
        print("✅ create_population looks good!")

    def check_fitness(self, func):
        test_items = [
            {"weight": 10, "value": 50},
            {"weight": 20, "value": 100},
            {"weight": 30, "value": 50}
        ]
        limit = 25

        ind_valid = [1, 0, 0]
        ind_over = [1, 1, 0]
        ind_super_over = [1, 0, 1]

        actual_valid = func(ind_valid, test_items, limit)
        actual_over = func(ind_over, test_items, limit)
        actual_super = func(ind_super_over, test_items, limit)

        assert actual_valid == 50, f"Failed on valid cargo. Expected 50, got {actual_valid}"
        assert actual_over == 100, f"Failed penalty on overweight cargo. Expected 100, got {actual_over}"
        assert actual_super == 0, f"Failed negative fitness check. Expected 0, got {actual_super}. Remember to use max(0, fitness)!"

        print("✅ calculate_fitness looks good!")

    def check_selection(self, func):
        population = [[0,0], [0,1], [1,1]]
        fitness_scores = [10, 50, 100]
        parent1, parent2 = func(population, fitness_scores)
        assert parent1 == [1,1] and parent2 == [1,1], "Selection did not pick the fittest individual!"
        print("✅ tournament_selection looks good!")

    def check_crossover(self, func):
        p1 = [0, 0, 0, 0]
        p2 = [1, 1, 1, 1]
        random.seed(42)
        c1, c2 = func(p1, p2)
        assert len(c1) == 4 and len(c2) == 4, "Children must be same length as parents"
        assert c1 != p1 and c2 != p2, "Crossover did not occur!"
        assert sum(c1) > 0 and sum(c2) > 0, "Genes were not swapped properly"
        print("✅ crossover looks good!")

    def check_mutation(self, func):
        ind = [0, 0, 0, 0, 0]
        mutated = func(ind.copy(), 1.0)
        assert mutated == [1, 1, 1, 1, 1], "Bit-flip mutation did not work correctly"
        print("✅ mutate looks good!")
