# -*- coding: utf-8 -*-
import numpy as np
from library.python.geohash import decode
from datacloud.dev_utils.geo.addrs_resolve import BadAddrTypes


def distances2features(distances, distance_thresh=500, eps=1e-2):
    """
        Transforms given distances to geo-features.
        100 meters is threshold, which represents whether distance is acceptable.
        Sigmoid like function is applied then and distances lower than threshold would
        rapidly increase feature value up to 1.

        2 / (1 + exp(-2z)) -1 function is used in terms of having thresh / distance (which is positive)
        mapped to (0, 1) space.

        Transform examples:
            distances 9 -> feature 0.9999999995532738
            distances 90 -> feature 0.8044548002984016
            distances 300 -> feature 0.32151273753163445
            distances 1500 -> feature 0.06656807650226271
            distances 5000 -> feature 0.01999733375993107
            distances 30000 -> feature 0.0033333209877091097
    """
    reversed_distances = distance_thresh / (distances + eps)
    features = 2. / (1 + np.exp(-2 * reversed_distances)) - 1

    return features


def distances2features_binary(distances, distance_thresh=500):
    return np.array(distances < distance_thresh, dtype=float)


def haversine(coord1, coord2):
    """
    Calculate the great circle distance between two points
    on the earth (specified in decimal degrees)

    All args must be of equal length.

    """
    R = 6371000  # radius of Earth in meters

    (lon1, lat1), (lon2, lat2) = coord1, coord2
    lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = np.sin(dlat / 2.0) ** 2 + np.cos(lat1) * \
        np.cos(lat2) * np.sin(dlon / 2.0) ** 2

    c = 2 * np.arcsin(np.sqrt(a))
    return R * c  # output distance in meters


def get_features(addrs_points, logs_points, fillna=0, distances_in_group=5):
    decoded_lons = []
    decoded_lats = []
    for point in logs_points:
        decoded_point = decode(point)
        decoded_lons.append(float(decoded_point[1]))
        decoded_lats.append(float(decoded_point[0]))
    decoded_lons = np.array(decoded_lons)
    decoded_lats = np.array(decoded_lats)

    features = [fillna] * (distances_in_group * len(addrs_points))
    bool_features = [1] * len(addrs_points)
    for i, addr_point in enumerate(addrs_points):
        if not isinstance(addr_point, BadAddrTypes):
            distances = np.sort(haversine(
                addr_point,
                (decoded_lons, decoded_lats)
            ))

            left = i * distances_in_group
            right = left + min(distances_in_group, len(distances))

            features[left:right] = distances2features_binary(distances[:right - left])

        elif addr_point == BadAddrTypes.empty_addr:
            bool_features[i] = 0

    return np.hstack((features, bool_features))


def sigmoid(x):
    return 1 / (1 + np.exp(-x))
