import math

from Math.VectorSizeMismatch import VectorSizeMismatch


cdef class Vector(object):

    def __init__(self,
                 valuesOrSize=None,
                 initial=None):
        """
        A constructor of Vector class which takes a list values as an input. Then, initializes
        values list and size variable with given input and its size.

        PARAMETERS
        ----------
        valuesOrSize
            list input or size.
        initial
            initial value for each element
        """
        if valuesOrSize is None:
            self.__values = []
            self.__size = 0
        elif isinstance(valuesOrSize, list):
            self.__values = valuesOrSize.copy()
            self.__size = len(valuesOrSize)
        else:
            self.initAllSame(valuesOrSize, initial)

    cpdef initAllSame(self,
                      int size,
                      double x):
        """
        Another constructor of Vector class which takes integer size and double x as inputs. Then, initializes size
        variable with given size input and creates new values list and adds given input x to values list.

        PARAMETERS
        ----------
        size : int
            list size.
        x : double
            item to add values list.
        """
        cdef int i
        self.__size = size
        self.__values = []
        for i in range(size):
            self.__values.append(x)

    cpdef initAllZerosExceptOne(self,
                                int size,
                                int index,
                                double x):
        """
        Another constructor of Vector class which takes integer size, integer index and double x as inputs.
        Then, initializes size variable with given size input and creates new values list and adds 0.0 to
        values list. Then, sets the item of values list at given index as given input x.

        PARAMETERS
        ----------
        size : int
            list size.
        index : int
            to set a particular item.
        x : double
            item to add values list's given index.
        """
        cdef int i
        self.__size = size
        self.__values = []
        for i in range(size):
            self.__values.append(0.0)
        self.__values[index] = x

    cpdef Vector biased(self):
        """
        The biased method creates a list result, add adds each item of values list into the result list.
        Then, insert 1.0 to 0th position and return result list.

        RETURNS
        -------
        list
            result list.
        """
        cdef Vector result
        cdef double value
        result = Vector()
        for value in self.__values:
            result.add(value)
        result.insert(0, 1.0)
        return result

    cpdef add(self, double x):
        """
        The add method adds given input to the values {@link ArrayList} and increments the size variable by one.

        PARAMETERS
        ----------
        x : double
            input to add values list.
        """
        self.__values.append(x)
        self.__size = self.__size + 1

    cpdef insert(self,
                 int pos,
                 double x):
        """
        The insert method puts given input to the given index of values list and increments the size variable by one.

        PARAMETERS
        ----------
        pos : int
            index to insert input.
        x : double
            input to insert to given index of values list.
        """
        self.__values.insert(pos, x)
        self.__size = self.__size + 1

    cpdef remove(self, int pos):
        """
        The remove method deletes the item at given input position of values list and decrements the size variable by
        one.

        PARAMETERS
        ----------
        pos : int
            index to remove from values list.
        """
        self.__values.pop(pos)
        self.__size = self.__size - 1

    cpdef clear(self):
        """
        The clear method sets all the elements of values list to 0.
        """
        cdef int i
        for i in range(len(self.__values)):
            self.__values[i] = 0

    cpdef double sumOfElements(self):
        """
        The sumOfElements method sums up all elements in the vector.

        RETURNS
        -------
        double
            Sum of all elements in the vector.
        """
        cdef double total
        cdef int i
        total = 0
        for i in range(self.__size):
            total += self.__values[i]
        return total

    cpdef int maxIndex(self):
        """
        The maxIndex method gets the first item of values list as maximum item, then it loops through the indices
        and if a greater value than the current maximum item comes, it updates the maximum item and returns the final
        maximum item's index.

        RETURNS
        -------
        int
            final maximum item's index.
        """
        cdef int index, i
        cdef double maxValue
        index = 0
        maxValue = self.__values[0]
        for i in range(1, self.__size):
            if self.__values[i] > maxValue:
                maxValue = self.__values[i]
                index = i
        return index

    cpdef sigmoid(self):
        """
        The sigmoid method loops through the values list and sets each ith item with sigmoid function, i.e
        1 / (1 + Math.exp(-values.get(i))), i ranges from 0 to size.
        """
        cdef int i
        for i in range(self.__size):
            self.__values[i] = 1 / (1 + math.exp(-self.__values[i]))

    cpdef tanh(self):
        """
        The tanh method loops through the values list and sets each ith item with tanh function.
        """
        cdef int i
        for i in range(self.__size):
            self.__values[i] = math.tanh(self.__values[i])

    cpdef relu(self):
        """
        The relu method loops through the values list and sets each ith item with relu function.
        """
        cdef int i
        for i in range(self.__size):
            if self.__values[i] < 0:
                self.__values[i] = 0.0

    cpdef reluDerivative(self):
        """
        The reluDerivative method loops through the values list and sets each ith item with the derivative of the
        relu function.
        """
        cdef int i
        for i in range(self.__size):
            if self.__values[i] > 0:
                self.__values[i] = 1.0
            else:
                self.__values[i] = 0.0

    cpdef Vector skipVector(self,
                            int mod,
                            int value):
        """
        The skipVector method takes a mod and a value as inputs. It creates a new result Vector, and assigns given input
        value to i. While i is less than the size, it adds the ith item of values {@link ArrayList} to the result and
        increments i by given mod input.

        PARAMETERS
        ----------
        mod : int
            integer input.
        value : int
            integer input.

        RETURNS
        -------
        Vector
            result Vector.
        """
        cdef Vector result
        cdef int i
        result = Vector()
        i = value
        while i < self.__size:
            result.add(self.__values[i])
            i += mod
        return result

    cpdef addVector(self, Vector v):
        """
        The add method takes a Vector v as an input. It sums up the corresponding elements of both given vector's
        values list and values list and puts result back to the values list.

        PARAMETERS
        ----------
        v : Vector
            Vector to add.
        """
        cdef int i
        if self.__size != v.__size:
            raise VectorSizeMismatch
        for i in range(self.__size):
            self.__values[i] = self.__values[i] + v.__values[i]

    cpdef subtract(self, Vector v):
        """
        The subtract method takes a Vector v as an input. It subtracts the corresponding elements of given vector's
        values list from values list and puts result back to the values list.

        PARAMETERS
        ----------
        v : Vector
            Vector to subtract from values list.
        """
        cdef int i
        if self.__size != v.__size:
            raise VectorSizeMismatch
        for i in range(self.__size):
            self.__values[i] = self.__values[i] - v.__values[i]

    cpdef Vector difference(self, Vector v):
        """
        The difference method takes a Vector v as an input. It creates a new Vector result, then
        subtracts the corresponding elements of given vector's values list from values list and puts
        result back to the result.

        PARAMETERS
        ----------
        v : Vector
            Vector to find difference from values list.

        RETURNS
        -------
        Vector
            new Vector with result list.
        """
        cdef int i
        if self.__size != v.__size:
            raise VectorSizeMismatch
        result = Vector()
        for i in range(self.__size):
            result.add(self.__values[i] - v.__values[i])
        return result

    cpdef double dotProduct(self, Vector v):
        """
        The dotProduct method takes a Vector v as an input. It creates a new double variable result, then
        multiplies the corresponding elements of given vector's values list with values list and assigns
        the multiplication to the result.

        PARAMETERS
        ----------
        v : Vector
            Vector to find dot product.

        RETURNS
        -------
        double
            result.
        """
        cdef double result
        cdef int i
        if self.__size != v.__size:
            raise VectorSizeMismatch
        result = 0
        for i in range(self.__size):
            result += self.__values[i] * v.__values[i]
        return result

    cpdef double dotProductWithSelf(self):
        """
        The dotProduct method creates a new double variable result, then squares the elements of values list and assigns
        the accumulation to the result.

        RETURNS
        -------
        double
            result.
        """
        cdef double result
        cdef int i
        result = 0
        for i in range(self.__size):
            result += self.__values[i] * self.__values[i]
        return result

    cpdef Vector elementProduct(self, Vector v):
        """
        The elementProduct method takes a Vector v as an input. It creates a new Vector result, then
        multiplies the corresponding elements of given vector's values list with values list and adds
        the multiplication to the result list.

        PARAMETERS
        ----------
        v : Vector
            Vector to find dot product.

        RETURNS
        -------
        Vector
            with result list.
        """
        cdef Vector result
        cdef int i
        if self.__size != v.__size:
            raise VectorSizeMismatch
        result = Vector()
        for i in range(self.__size):
            result.add(self.__values[i] * v.__values[i])
        return result

    cpdef divide(self, double value):
        """
        The divide method takes a double value as an input and divides each item of values list with given value.

        PARAMETERS
        ----------
        value : double
            is used to divide items of values list.
        """
        cdef int i
        for i in range(self.__size):
            self.__values[i] = self.__values[i] / value

    cpdef multiply(self, double value):
        """
        The multiply method takes a double value as an input and multiplies each item of values list with given value.

        PARAMETERS
        ----------
        value : double
            is used to multiply items of values list.
        """
        cdef int i
        for i in range(self.__size):
            self.__values[i] = self.__values[i] * value

    cpdef Vector product(self, double value):
        """
        The product method takes a double value as an input and creates a new result {@link Vector}, then multiplies
        each item of values list with given value and adds to the result {@link Vector}.

        PARAMETERS
        ----------
        value : double
            is used to multiply items of values list.

        RETURNS
        -------
        Vector
            Vector result.
        """
        cdef Vector result
        cdef int i
        result = Vector()
        for i in range(self.__size):
            result.add(self.__values[i] * value)
        return result

    cpdef l1Normalize(self):
        """
        The l1Normalize method is used to apply Least Absolute Errors, it accumulates items of values list and sets
        each item by dividing it by the summation value.
        """
        cdef double total
        cdef int i
        total = 0
        for i in range(self.__size):
            total += self.__values[i]
        for i in range(self.__size):
            self.__values[i] = self.__values[i] / total

    cpdef double l2Norm(self):
        """
        The l2Norm method is used to apply Least Squares, it accumulates second power of each items of values list
        and returns the square root of this summation.

        RETURNS
        -------
        double
            square root of this summation.
        """
        cdef double total
        cdef int i
        total = 0
        for i in range(self.__size):
            total += self.__values[i] ** 2
        return math.sqrt(total)

    cpdef double cosineSimilarity(self, Vector v):
        """
        The cosineSimilarity method takes a Vector v as an input and returns the result of dotProduct(v)
        / l2Norm() / v.l2Norm().

        PARAMETERS
        ----------
        v : Vector
            input.

        RETURNS
        -------
        double
            dotProduct(v) / l2Norm() / v.l2Norm()
        """
        if self.__size != v.__size:
            raise VectorSizeMismatch
        return self.dotProduct(v) / self.l2Norm() / v.l2Norm()

    cpdef int size(self):
        """
        The size method returns the size of the values list

        RETURNS
        -------
        int
            size of the values list
        """
        return len(self.__values)

    cpdef double getValue(self, int index):
        """
        Getter for the item at given index of values list

        PARAMETERS
        ----------
        index : int
            index used to get an item.

        RETURNS
        -------
        item
            the item at given index.
        """
        return self.__values[index]

    cpdef setValue(self,
                   int index,
                   double value):
        """
        Setter for the setting the value at given index of values list.

        PARAMETERS
        ----------
        index : int
            index to set.
        value : item
            is used to set the given index
        """
        self.__values[index] = value

    cpdef addValue(self,
                   int index,
                   double value):
        """
        The addValue method adds the given value to the item at given index of values list.

        PARAMETERS
        ----------
        index : int
            index to add the given value.
        value : item
            value to add to given index.
        """
        self.__values[index] += value

    def __repr__(self):
        return f"{self.__values}"
