#
# <one line to give the library's name and an idea of what it does.>
# Copyright (C) 2015  <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/>.
# 
#

import networkx
import structuralsimilaritymeasure

class MetaSimilarityMeasure(
  structuralsimilaritymeasure.StructuralSimilarityMeasure):
  """!
  It is meta-measure which can be initialized with spreading method and
  similarity function.

  If the spreading method is set to None the spreading will not be done.
  Only similarity between two graphs will be measured.
  """

  def __init__(self, options, sim_funcion, spread_method = None,
               spread_max_it = None, spread_max_depth = None,
               init_values = None, str_name = 'MetaSimilarityMeasure'):
    """!
    Store arguments as the members.

    If spreading method is given then arguments spread_max_it and 
    spread_max_depth are required. When are not given, then the excetion
    will be thrown.

    @param options a dictionary containing options for this measure
    @type options: dict

    @param sim_funcion similarity function to calculate similarity between two
           vectors sim(v1, v2) and returning value of similarity
    @type sim_funcion: grafon.measures.similarity

    @param spread_method Spreading method. As default is set to None. 
           When is set to None then spreadin will not be done
    @type spread_method: grafon.spreading

    @param spread_max_it If given spreading method, then maximum number of 
           iterations have to be given
    @type spread_max_it: Int

    @param spread_max_depth If given spreading method, then maximum depth
           have to be given
    @type spread_max_depth: Int

    @param init_values Initial values of nodes into both graphs. 
           As default is none, when is none, then init values will not be used.
    @type init_values:  of values which should be initialized into both graphs

    @param str_name default value same as class name "MetaSimilarityMeasure"
    @type str_name: string
    """
    structuralsimilaritymeasure.StructuralSimilarityMeasure.__init__(
      self, options, str_name)

    self._sim_function = sim_funcion
    self._spread_method = spread_method
    self._spread_max_it = spread_max_it
    self._spread_max_depth = spread_max_depth
    self._init_values = init_values

    if spread_method:
      if not spread_max_it:
        raise Exception("Maximum number of iteration in spread is required!")
      if not spread_max_depth:
        raise Exception("Maximum depth in spread is required!")

  def calculate(self, g1, g2):
    """!
    @param g1 first graph
    @type g1: networgx.DiGraph/networkx.MultiDiGraph

    @param g2 second graph
    @type g1: networgx.DiGraph/networkx.MultiDiGraph

    @return similarity using function initialized by the constructor
    @rtype: double
    """
    if not g1 or not g2:
      return 0

     # makes temporary graphs
    cg1 = networkx.DiGraph()
    cg1.add_edges_from(g1.edges(data = True), data = True)

    cg2 = networkx.DiGraph()
    cg2.add_edges_from(g2.edges(data = True), data = True)

    # set initial values if they are given
    if self._init_values:
      for k, v in self._init_values.iteritems():
        if cg1.node.has_key(k):
          cg1.node[k]['value'] = v
        if cg2.node.has_key(k):
          cg2.node[k]['value'] = v

    # if spreading method is given, then run it with given arguments
    if self._spread_method:
      self._spread_method.spread(
        cg1, 
        max_it=self._spread_max_it, 
        max_depth=self._spread_max_depth)
      self._spread_method.spread(
        cg2, 
        max_it=self._spread_max_it, 
        max_depth=self._spread_max_depth)

    # convert graphs to vectors and calculates similarity betwee them
    vg1g2 = list(set(cg1.nodes() + cg2.nodes()))
    vg1 = [0 for i in vg1g2]
    for n in cg1.nodes():
      vg1[vg1g2.index(n)] = \
        cg1.node[n]['value'] if cg1.node[n].has_key('value') else 0
    vg2 = [0 for i in vg1g2]
    for n in cg2.nodes():
      vg2[vg1g2.index(n)] = \
        cg2.node[n]['value'] if cg2.node[n].has_key('value') else 0

    return self._sim_function.calculate(vg1, vg2)