def generate_initial_population(num_population):
    # [learning_rate, hidden_size, number_of_layers, fitness]
    learning_rate = np.empty([num_population, 1], dtype=np.float32)
    hidden_size = np.empty([num_population, 1], dtype=np.int32)
    number_of_layers = np.empty([num_population, 1], dtype=np.int32)
    fitness = np.zeros([num_population, 1], dtype=np.float32)

    for i in range(num_population):
    
        learning_rate[i] = round(random.uniform(0.001, 0.1), 3)
        hidden_size[i] = int(random.randrange(1, 20, step= 1))
        number_of_layers[i] = int(random.uniform(1, 6))

    population = np.concatenate((learning_rate, hidden_size, number_of_layers, fitness), axis= 1)
    return population

def fitness(population):
    total = len(population)
    i = 1
    for config in population:

        # Train MLP model using current hyperparameter combinations, add calculate fitness
        # print("Training" , i , "of", total)
        config[3] = round(training_run(Configuration(config[0], 12, int(config[1]), int(config[2]), 200), test=True), 2)
        i = i + 1
   

        # Split training set into development, and validation set?
        # Use only subset of training data for model selection?

def selection(population):
    
    population_count = population.shape[0]
    number_to_remove = int(population.shape[0]/2)
    to_remove = []
    for i in range(number_to_remove):
        index = population_count - i -1
        to_remove.append(index)

    # Sort population by fitness, config[3] -> fitness
    population = population[population[:, 3].argsort()]

    # Removing half of the worst performers from current generation
    population = np.delete(population, to_remove, 0)
    return population

def crossover(population):
    # Single-point crossover, two-point crossover, k-fold

    num_hyperparameters = population[0].shape[0] -1

    # crossover_index = np.random(1, num_hyperparameters)
    crossover_index = random.randint(1, num_hyperparameters-1)
    print("crossover_index", crossover_index)
    children = np.array([])
    for parent in range(len(population) -1):
        if parent % 2 == 0:
            
            if crossover_index == 1:
                children = np.append(children, np.array([ population[parent][0], population[parent+1][1], population[parent+1][2], 0 ]))
                children = np.append(children, np.array([ population[parent+1][0], population[parent][1], population[parent][2], 0 ]))
            
            if crossover_index == 2:
                children = np.append(children, np.array([ population[parent][0], population[parent][1], population[parent+1][2], 0 ]))
                children = np.append(children, np.array([ population[parent+1][0], population[parent+1][1], population[parent][2], 0 ]))

            # children = np.append(children, np.array([ population[parent][0], population[parent+1][1], population[parent+1][2], 0 ]))
            # children = np.append(children, np.array([ population[parent+1][0], population[parent][1], population[parent][2], 0 ]))

    children = children.reshape(len(population), num_hyperparameters + 1)
    return children

def mutation(population):
    # Select random elements from the population
    # Mutate a single gene (hyperparameter)

    mutation_rate = 0.4
    parameters_to_change = int(mutation_rate * (len(population)*2))

    for change in range(parameters_to_change):
        i = random.randint(0, len(population)-1)
        param = random.randint(0, population[0].shape[0]-1-1)

        if param == 0:
            gene_edit = round(random.uniform(0.001, 0.1), 3)
        elif param == 1:
            gene_edit = random.randint(1, 100)
        elif param == 2:
            gene_edit = random.randint(1, 12)

        population[i][param] = gene_edit

    return population