#
# <one line to give the library's name and an idea of what it does.>
# Copyright (C) 2014    <copyright holder> <email>
# 
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program.    If not, see <http://www.gnu.org/licenses/>.
# 
#

"""!
@package This package contains methods to calculate the similarity between 
two vectors.

There is abstract Class Similarity which should be implemented in each 
SimilarityMeasure.
"""

import abc
import math

class Similarity(object):
  """!
  Abstract class of any similarity measure.

  Similiarity is calculates between two vectors. Each child class have to
  implement metod to calculate similarity.
  """
  __metaclass__ = abc.ABCMeta

  def __init__(self, str_name):
    """
    Constructor takes name of similiary measure and set it. 

    @param str_name: name of similarity measure
    """
    self._name = str_name

  @abc.abstractmethod
  def calculate(self, v1, v2):
    """
    Abstract method which must be implemented in any child. This method
    is called always when the similarity between two vectors have to be
    calculated.

    @param v1: first vector
    @param v2: second vector
    @return Value of similarity between v1 and v2
    """
    pass

  def __str__(self):
    """
    Makes the object string like represented
    """
    return self.name()

  def name(self):
    """
    Name of similarity
    """
    return self._name


class CosineSimilarity(Similarity):
  """
  Cosine similarity measure. Calculates the cosine ismilarity between
  two vectors. Name of mneasure is set to 'cosine' Cosine is calcualted as:
  http://en.wikipedia.org/wiki/Cosine_similarity
  """

  def __init__(self):
    """
    Constructor only sets the name of measure to 'cosine' and calls parent.
    """
    super(CosineSimilarity, self).__init__('cosine')

  def calculate(self, v1, v2):
    """
    Calculates cosine similarity between two vectors.

    This method calculates cosine similarity between two given vevtors. 
    Lenght of these vectors must be same, if is not then assertion is calls.
    If the vector has same length then similarity is calculated.

    @param v1: first vector
    @param v2: secon vector
    @retun cosine(v1,v2)
    """
    assert len(v1) == len(v2), 'The both vectors have the different size!'

    dot = 0
    for i in range(0, len(v1)):
        dot += v1[i] * v2[i]
    v1_len = 0
    for i in range(0, len(v1)):
        v1_len += (v1[i] * v1[i])
    v1_len = math.sqrt(v1_len)
    v2_len = 0
    for i in range(0, len(v2)):
        v2_len += (v2[i] * v2[i])
    v2_len = math.sqrt(v2_len)
    d = v1_len * v2_len
    return 0 if d == 0 else (float(dot) / d)


class BhattacharyyaSimilarity(Similarity):
  """!
  Bhattacharyya similarity measure. Calculates the smilarity between
  two vectors. Name of mneasure is set to 'bhattacharyya'.
  Measure is calcualted as follow:
  http://en.wikipedia.org/wiki/Bhattacharyya_distance
  """

  def __init__(self):
    """!
    Constructor only sets the name of measure to 'bhattacharyya' 
    and calls parent.
    """
    super(BhattacharyyaSimilarity, self).__init__('bhattacharyya')

  def calculate(self, v1, v2):
    """!
    Calculates Bhattacharyya similarity between two vectors.

    This method calculates Bhattacharyya similarity between two given vevtors. 
    Lenght of these vectors must be same, if is not then assertion is calls.
    If the vector has same length then similarity is calculated.

    @param v1: first vector
    @param v2: secon vector
    @retun cosine(v1,v2)
    """
    assert len(v1) == len(v2), 'The both vectors have the different size!'

    dot = 0
    for i in range(0, len(v1)):
        dot += math.sqrt(abs(v1[i]) * abs(v2[i]))
    v1_len = 0
    for i in range(0, len(v1)):
        v1_len += abs(v1[i])
    v2_len = 0
    for i in range(0, len(v2)):
        v2_len += abs(v2[i])
    d = math.sqrt(v1_len * v2_len)
    return 0 if d == 0 else (float(dot) / d)