#
# <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/>.
# 
#

import corpus2
import networkx, os
import grafon.constants as constants

class GraphBuilder:
    """!
    Basic class for building simple graph, without edges. Represents the graphs
    for each sentences from analysed text.

    The built graph contains only nodes. In the nodes the "words" and tokens 
    from the text are stored. One node may represents many tokens which are 
    stored into node attribute named as tokens = []. While tokens are added
    to graph always the skip_token method is calls to check that the token can
    be represented into graph. Also for each token converter is run to converts
    the token to its key represetation into new/existing node.
    """

    def __init__(self):
      """!
      Constructor - doing nothing ;)
      """
      pass

    def build_graph_from_file(self, base_graph, cclfile, relcclfile, tagset):
      """!
      From given text file builds the basic grah containing only nodes.

      For read cclfile (cclfile and relcclfile) this method builds the graph
      only with nodes. The nodes are built based on the converter methods
      and skipping these nodes which should be skipped. The created node contains
      into the its attribute named as token list of tokens which belong to this
      node. This method build graphs for all sentences from given text and store
      its into graph dictionary which is a member of base_graph (graph_dict).
      As a key is used sentence identifier and as the value is set the built 
      graph for this sentence. Also the tagset of base_graph is set with
      given as the argument. There the ccl document and relccl are read and
      the apropriate members are set into base_graph object. If any excetion
      will be caught then raise it.

      Additionaly this method handle the case when the MWE should be extracted
      and used into graphs. If the mwe_dict_path is given, then from this path
      dictionary of the MWE will be read. In the process of building the graph
      the MWEs will be extraced and put into graph as single node. 

      @param graph_dict object of BaseGraph
      @type graph_dict: BaseGraph
      @param cclfile path to ccl file
      @type cclfile: String
      @param relcclfile path to relations of givem ccl file
      @type relcclfile: String
      @param tagset Tagset used while reading text
      @type tagset: corpus2.Tagset
      @param mwe_dict_path (Optional) Path to MWE dictionary
      @type mwe_dict_path: String
      """
      try:
        cclreader = corpus2.CclRelReader(tagset, cclfile, relcclfile)
        base_graph.tagset = tagset
        base_graph.document = cclreader.read()
        for paragraph in base_graph.document.paragraphs(): 
          for sentence in paragraph.sentences():
            base_graph.graph_dict[sentence.id()] = \
              self.build_graph_from_sentence(sentence, base_graph)
      except Exception, e:
          raise e

    def build_graph_for_rel_sentences(self, base_graph, cclfile, 
                                      relcclfile, tagset, rel_names):
      """
      This method can be used f.e. in entailment task to build the graphs
      only from sentences between given relations exists. 

      In general, method builds graphs 
      @param graph_dict object of BaseGraph
      @type graph_dict: BaseGraph
      @param cclfile path to ccl file
      @type cclfile: String
      @param relcclfile path to relations of givem ccl file
      @type relcclfile: String
      @param tagset Tagset used while reading text
      @type tagset: corpus2.Tagset
      """
      try:
        cclreader = corpus2.CclRelReader(tagset, cclfile, relcclfile)
        base_graph.tagset = tagset
        base_graph.document = cclreader.read()

        # makes sentence dictionary - key is sentence id, valu is sentence
        sdict = {}
        for paragraph in base_graph.document.paragraphs(): 
          for sentence in paragraph.sentences():
            sdict[sentence.id()] = sentence
        for r in base_graph.document.relations():
          if r.rel_name() in rel_names:
            if not base_graph.graph_dict.has_key(r.rel_from().sentence_id()):
              base_graph.graph_dict[r.rel_from().sentence_id()] = \
                self.build_graph_from_sentence(
                  sdict[r.rel_from().sentence_id()], base_graph)
            if not base_graph.graph_dict.has_key(r.rel_to().sentence_id()):
              base_graph.graph_dict[r.rel_to().sentence_id()] = \
                self.build_graph_from_sentence(
                  sdict[r.rel_to().sentence_id()], base_graph)
      except Exception, e:
          raise e

    def load_graps_for_rel_sentences(self, base_graph, cclfile, 
                                      relcclfile, tagset, rel_names,
                                      graphs_dir):
      gdict = {}
      try:
        cclreader = corpus2.CclRelReader(tagset, cclfile, relcclfile)
        base_graph.tagset = tagset
        base_graph.document = cclreader.read()
        for r in base_graph.document.relations():
          if r.rel_name() in rel_names:
            # loads "from" sentence graph
            sid = r.rel_from().sentence_id().strip(). \
              replace('ner-iob-', ''). \
              replace('xml:s', 'xml#s')
            gpath = os.path.join(graphs_dir, '{}.pickle'.format(sid))
            try:
              if not base_graph.graph_dict.has_key(r.rel_from().sentence_id()):
                base_graph.graph_dict[r.rel_from().sentence_id()] = \
                  networkx.read_gpickle(gpath)
                print >> sys.stderr, 'Loaded {}'.format(gpath)
            except IOError, e:
              print >> sys.stderr, e
            # loads "to" sentence graph
            sid = r.rel_to().sentence_id().strip(). \
              replace('ner-iob-', ''). \
              replace('xml:s', 'xml#s')
            gpath = os.path.join(graphs_dir, '{}.pickle'.format(sid))
            try:
              if not base_graph.graph_dict.has_key(r.rel_to().sentence_id()):
                base_graph.graph_dict[r.rel_to().sentence_id()] = \
                  networkx.read_gpickle(gpath)
                print >> sys.stderr, 'Loaded {}'.format(gpath)
            except IOError, e:
              print >> sys.stderr, e
      except Exception, e:
          raise e

    def build_graph_from_sentence(self, sentence, base_graph):
      """!
      Build the graph for the given sentence.

      This function uses the properties of base_graphs (such as 
      node_converter, information aboud skip the tokens etc.) to build
      the single graph for the sentence. This graph contains only nodes
      extracted from the sentence (the graph do not contains the edges)

      This function also try to find NE into 

      @param sentence Sentence to build the graph
      @type sentence: corpus2.Sentence
      @param base_graph Base graph to use the information
      @type base_graph grafon.BaseGraph
      @return Graph build from the sentence.
      @rtype networkx.MultiDiGraph()
      """
      g = networkx.MultiDiGraph()
      for token in sentence.tokens():
        if base_graph.skip_token(token):
          continue
        # The token can produce multiple key, such as mapping one token
        # to many SUMO concepts, so there the all possible keys should be
        # handled
        for k in base_graph.node_converter.key(token):
          if k:
            if not g.has_node(k):
              g.add_node(k, attr_dict = { 
                constants.NODE_TOKENS       : [token], 
                constants.NODE_COUNT        : 1,
                constants.NODE_IS_SINGLETOK : False,
                constants.NODE_NE_ANNOTS    : [],
                constants.NODE_SOURCES      : [constants.NODE_FROM_TEXT]})
            else:
              g.node[k][constants.NODE_COUNT] += 1
              g.node[k][constants.NODE_TOKENS].append(token)
      return g