import time

import numpy as np
import pandas as pd

import trilateration
from models import get_xcols


def multi_predict(model, test_set):
    """Use model with input size 1 to predict on greater dimension data.

    Return predictions in the same shape as calling predict() on
    multidimensional model.

    :param model: model used for predictions
    :param test_set: testing dataset
    :return: model's predictions on test_set
    """
    predictions = []

    for col in test_set.columns.values:
        predictions.append(model.predict(test_set[[col]]))

    # list of lists of lists -> list of lists; concatenating the innermost lists
    predictions = list(map(lambda x: [z for y in x for z in y], predictions))

    return np.array(list(zip(*predictions)))


def score(test_set, predictions, k, log=False):
    """Compute the mean error of predictions.

    Takes coordinates of cell towers from test_set and predicted distances to
    find coordinates using trilateration. Computes the error based on true
    measurement locations. Returns the mean error in meters.

    :param test_set: testing dataset
    :param prediction: a model's predictions
    :param k: output size of the model
    :param log: if True, log ditances vector to file
    :return: mean error in meters
    """
    coordinates = [test_set["cell_coord_" + str(i)] for i in range(k)]
    coordinates = pd.Series(list(zip(*coordinates)))

    locations = np.vectorize(trilateration.trilateration)\
                            (pd.Series(predictions.tolist()), coordinates)
    locations = pd.Series(list(zip(*locations)))

    distances = np.vectorize(trilateration.haversine_distance)\
                            (test_set.meas_coord_0, locations)

    d = pd.Series(distances)

    if log:
        d.to_csv("tmp/" + str(int(time.time())))

    return d.mean()


def evaluate(model, test_set, k, debug=False, log=False):
    """Evaluate given model on test_set.

    Predict values on the test_set. If k=1, the model has to predict multiple
    times on each sample. Calculate the error of predictions and return the mean
    error in meters.

    :param model: model to be evaluated
    :param test_set: testing dataset
    :param k: output size of the model
    :param log: if True, log result vector to file
    :return: mean error in meters
    """
    xcols = get_xcols(test_set)

    if k == 1:
        predictions = multi_predict(model, test_set[xcols])
    else:
        predictions = model.predict(test_set[xcols])

    result = score(test_set, predictions, k, log=log)

    if debug:
        print()
        print("TEST", test_set[xcols][:10])
        print("PRED", predictions[:10])

        try:
            print("PARAM", model.get_params())
        except:
            pass
        try:
            print("PARAM", model.get_config())
        except:
            pass
        print("RESULT", result)
        print()

    return result
