"""Select samples using Kennard-Stone algorithm.

    > The KENNARDSTONE method selects a subset of samples from x which provide uniform coverage over the data set and includes samples on the boundary of the data set. The method begins by finding the two samples which are farthest apart using geometric distance. To add another sample to the selection set the algorithm selects from the remaining samples that one which has the greatest separation distance from the selected samples. The separation distance of a candidate sample from the selected set is the distance from the candidate to its closest selected sample. This most separated sample is then added to the selection set and the process is repeated until the required number of samples, k, have been added to the selection set. In practice this produces a very uniformly distributed network of selected points over the data set and includes samples along the boundary of the dataset. The method performs efficiently because it calculates the inter-sample distances matrix only once.

    ([Source](http://wiki.eigenvector.com/index.php?title=Kennardstone))
"""
import numpy as np


def kennardstonealgorithm(x_variables, k):
    """Run the Kennard-Stone algorithm.

    Args:
        x_variables (dataset): data to select samples from
        k (int): number of samples to be selected

    Returns:
        list: The indexes of selected samples (training data).
        list: The indexes of remaining samples (test data).
    """
    x_variables = np.array(x_variables)
    original_x = x_variables
    distance_to_average = (
        (x_variables - np.tile(x_variables.mean(axis=0), (x_variables.shape[0], 1))) ** 2).sum(axis=1)
    max_distance_sample_number = np.where(
        distance_to_average == np.max(distance_to_average))
    max_distance_sample_number = max_distance_sample_number[0][0]
    selected_sample_numbers = list()
    selected_sample_numbers.append(max_distance_sample_number)
    remaining_sample_numbers = np.arange(0, x_variables.shape[0], 1)
    x_variables = np.delete(x_variables, selected_sample_numbers, 0)
    remaining_sample_numbers = np.delete(
        remaining_sample_numbers, selected_sample_numbers, 0)
    for iteration in range(1, k):
        selected_samples = original_x[selected_sample_numbers, :]
        min_distance_to_selected_samples = list()
        for min_distance_calculation_number in range(0, x_variables.shape[0]):
            distance_to_selected_samples = ((selected_samples - np.tile(x_variables[min_distance_calculation_number, :],
                                                                        (selected_samples.shape[0], 1))) ** 2).sum(axis=1)
            min_distance_to_selected_samples.append(
                np.min(distance_to_selected_samples))
        max_distance_sample_number = np.where(
            min_distance_to_selected_samples == np.max(min_distance_to_selected_samples))
        max_distance_sample_number = max_distance_sample_number[0][0]
        selected_sample_numbers.append(
            remaining_sample_numbers[max_distance_sample_number])
        x_variables = np.delete(x_variables, max_distance_sample_number, 0)
        remaining_sample_numbers = np.delete(
            remaining_sample_numbers, max_distance_sample_number, 0)

    return selected_sample_numbers, remaining_sample_numbers
