"""Traduction and optimisation of A.Malkassian's code on R (2010)
Marjorie ROSCIAN M2 CCI : Avril-Sept 2018

This file defines functions created by A.Malkassian for his data analysis

libraries :
numPy
math
rpy2
scipy

"""


# Packages #
from Analysis.FourierBasis import *
from scipy.spatial.distance import euclidean

import rpy2.robjects as robjects
from rpy2.robjects.packages import importr
import rpy2.robjects.numpy2ri
rpy2.robjects.numpy2ri.activate()


# Import from R via rpy2 #
utils = importr("utils")
base = importr("base")
utils.install_packages("fda")
utils.install_packages("stats")
fda = importr("fda")
c = robjects.r('c')
stats = importr('stats')
rplot = robjects.r('plot')


def data_to_function(data_matrix, nb_bases, nb_col):
    """This function tranforms a set of data (profil, discrete values) to functional data the fda package from R.
    If needed, you can use M.Roscian python's version : longer but efficient.
    In order to optimize duration of data processing we choose to use rpy2.

    #input : data_matrix is the full array of data
             nb_bases is the number of bases wanted to make fourier series
             nb_col is the number of columns to transorfm in the dataset

    #output : result is the array resulting of fourier coefficients

    #other : seq is a vector of length of data
             basis is the Fourier basis created from fda
             data_cell is the matrix of the running cell
             data_running is the vector corresponding to the running profil
             smoothed_data returns evenly spaced numbers over 1 to length of dataset
             data_spline is a vector of smooth_spline fit on our current dataset
             new_fda computes the Data2fd function
             data_funct adds each profil in an array

    # Caution : index starts at 0
    # M version corresponding to the lines you have to add/uncomment
    # if you want to use the other Fourier Basis fucntion in python code : FourierBasis.py
    """

    result = []
    seq = np.arange(0, data_matrix.size)
    basis = fda.create_fourier_basis(rangeval=c(0, 1), nbasis=nb_bases)

    # Start listing every cell
    for cell in seq:

        data_cell = data_matrix[cell]
        coeff = np.zeros((nb_bases, nb_col))

        for column in np.arange(0, nb_col):
            data_running = np.array(data_cell[:, column])
            smoothed_data = np.linspace(1, len(data_running), num=1001)
            data_spline = stats.smooth_spline(x=data_running, spar=0.5)
            new_data = stats.predict(object=data_spline, new_data=smoothed_data).rx2('y')
            new_fda = fda.Data2fd(argvals=new_data, basisobj=basis)
            coeff[:, column - 1] = np.ndarray.flatten(np.array(new_fda.rx2('coefs')))

            # M version
            # x = np.array(np.arange(0, data_running.size))
            # data_spline = scipy.interpolate.splrep(x, data_running, s=0.5)
            # new_data = scipy.interpolate.splev(smoothed_data, data_spline)
            # data_funct[:, column-1] = np.array(fourierbasis(new_data, nb_bases))
            # coeff[:, column-1] = np.array(fourierbasis(new_data, nb_bases))

        result.append(coeff)
    print('DONE Fourier')
    return result


def dist_inv(vector_1, vector_to_inv):
    """ This function compares two vector and keep the min euclidean distance between these two vectors or
    one and the inverse other

    This function aims to calculate the invariant orientation distance. If the cell is analysed in a peculiar position
    and then in another, this will switch to the good side.

    #input : vector_1 is the first vector to compare
             vector_to_inv is the second vector you have to test

    #output : returns the minimal distance beetween the two vectors

    #orther : dist is the euclidean distance between the two input vectors
              dist2 is the euclidean distance between the first vector and the flipped other one

    """
    inv_vector_2 = np.flip(vector_to_inv, axis=0)

    dist = euclidean(vector_1, vector_to_inv)

    dist2 = euclidean(vector_1, inv_vector_2)

    return np.minimum(dist, dist2)
