#
# <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 os, sys
import logging
import networkx
import semgraph
import unicodedata
import grafon.constants as constants

class PlWNGraph(semgraph.SemGraph):
  """!
  This class is a child of SemGraph. 
  
  Represents the semantic graph - plWordNet graph. The graph is read from
  database or read from the pickle. The inherited method build_graph throws
  exception becouse to built the graph the method read_graph should be used
  """

  def __init__(self):
    """!
    Constructor calls parents constructor and makes directed graph
    in the argument. The graph is networkx.DiGraph()
    """
    super(PlWNGraph, self).__init__(networkx.DiGraph())
    
  def build_graph(self):
    """!
    Inherited method, but only throws the exception. The graph must be 
    built using read_graph method.
    """
    raise Exception("For PlWNGraph method read_graph should be used!")

  def read_graph(self, db_config_file, out_filename):
    """!
    Reads graph from database or from file.

    If a given graph file exists in path out_filename then the graph is only
    unpickled, if does not exists, then based on the parametres in config 
    db_config_file makes the graph. For building graph uses method 
    _build_graph_from_db(db_config_file).

    @param db_config_file: Config file used while the graph is built based
                           on the database connection
    @param out_filename: Path to file when the graph will be stored or from 
                         what will be read if exists
    @return The built graph
    """
    if os.path.exists(out_filename):
      logging.info("\n  ~ Reading graph from file...")
      self._g = networkx.read_gpickle(out_filename)
    else:
      self._build_graph_from_db(db_config_file)
      networkx.write_gpickle(self._g, out_filename)
    self._undirected_g = self._g.to_undirected()
    return self._g

  def _build_graph_from_db(self, db_config_file):
    """!
    Method do built graph from db connection. It uses npsemrel.carrot.db
    module to connect to database.
    
    @param db_config_file: path to config file in WordNetLoom format

    @returns built graph with type networkx.DiGraph()
    """
    logging.info("\n  ~ Building graph from database...")

    from npsemrel.carrot.db import db
    dbcon = db.DB()
    dbconnection = dbcon.connect(db_config_file)
    if not dbconnection:
      return None

    cursor = dbconnection.cursor()
    # gets synset relations
    sql_query = (
      "SELECT DISTINCT "
      "   SR.PARENT_ID, SR.CHILD_ID, RT.shortcut "
      " FROM synsetrelation SR "
      "  JOIN unitandsynset UAS ON (SR.PARENT_ID = UAS.SYN_ID)"
      "  JOIN lexicalunit LU ON (LU.ID = UAS.LEX_ID) "
      "  JOIN relationtype RT ON (RT.ID = SR.REL_ID) "
      " WHERE "
      "   LU.pos < 5 AND "
      "   RT.shortcut NOT LIKE '%_ap' AND "
      "   RT.shortcut NOT LIKE '%_pa';")
    
    logging.info("\n    ~ Using synset relations...")
    cursor.execute(sql_query)
    while True:
      db_res = cursor.fetchone()
      if not db_res:
          break
      pid, chid, shrtct = db_res
      # Partnet
      if not self._g.has_node(pid):
        lu_label = self._get_syn_str(dbconnection, pid)
        #lu_label = unicodedata.normalize('NFD', lu_label).encode('ascii', 'ignore')
        self._g.add_node(pid, attr_dict = { 
          constants.NODE_TOKENS       : [],
          constants.NODE_COUNT        : 1,
          constants.NODE_PLWN_STR     : lu_label,
          constants.NODE_SOURCES      : [constants.NODE_FROM_PLWN]})
          #constants.NODE_LABEL : lu_label})
        #print "S(P):", pid, lu_label, self._g.node[pid]
      # Child
      if not self._g.has_node(chid):
        lu_label = self._get_syn_str(dbconnection, chid)
        #lu_label = unicodedata.normalize('NFD', lu_label).encode('ascii', 'ignore')
        self._g.add_node(chid, attr_dict = { 
          constants.NODE_TOKENS       : [],
          constants.NODE_COUNT        : 1,
          constants.NODE_PLWN_STR     : lu_label,
          constants.NODE_SOURCES      : [constants.NODE_FROM_PLWN]})
        #print "S(CH):", chid, lu_label, self._g.node[chid]
      # Add edge between parent and child
      self._g.add_edge(
        pid, chid, 
        attr_dict={
          constants.EDGE_LABEL: shrtct,
          constants.EDGE_COUNT: 1})

    # gets lexical units relations
    sql_query = (
    " SELECT "
    " UAS_P.SYN_ID, UAS_CH.SYN_ID, RT.shortcut"
    " FROM lexicalrelation LR "
    "   JOIN unitandsynset UAS_P  ON (LR.PARENT_ID = UAS_P.LEX_ID)"
    "   JOIN unitandsynset UAS_CH ON (LR.CHILD_ID = UAS_CH.LEX_ID)"
    "   JOIN relationtype RT ON (RT.ID = LR.REL_ID);")
    logging.info("\n    ~ Using lexical unit relations...")
    cursor.execute(sql_query)
    while True:
      db_res = cursor.fetchone()
      if not db_res:
          break
      pid, chid, shrtct = db_res
      # Parent
      if not self._g.has_node(pid):
        lu_label = self._get_lu_str(dbconnection, pid)
        # lu_label = unicodedata.normalize('NFD', lu_label).encode('ascii', 'ignore')
        self._g.add_node(pid, attr_dict = { 
          constants.NODE_TOKENS       : [],
          constants.NODE_COUNT        : 1,
          constants.NODE_PLWN_STR     : lu_label,
          constants.NODE_SOURCES      : [constants.NODE_FROM_PLWN]})
        #print "L(P):", pid, lu_label, self._g.node[pid]
      # Child
      if not self._g.has_node(chid):
        lu_label = self._get_lu_str(dbconnection, chid)
        # lu_label = unicodedata.normalize('NFD', lu_label).encode('ascii', 'ignore')
        self._g.add_node(chid, attr_dict = { 
          constants.NODE_TOKENS       : [],
          constants.NODE_COUNT        : 1,
          constants.NODE_PLWN_STR     : lu_label,
          constants.NODE_SOURCES      : [constants.NODE_FROM_PLWN]})
        #print "L(CH):", chid, lu_label, self._g.node[chid]
      # Add edge between parent and child
      shrtct = unicodedata.normalize('NFD', shrtct).encode('ascii', 'ignore')
      self._g.add_edge(
        pid, chid, 
        attr_dict={
          constants.EDGE_LABEL: shrtct,
          constants.EDGE_COUNT: 1})
    return self._g

  def _get_syn_str(self, dbconnection, synid):
    # return ""
    curr = dbconnection.cursor()
    sql_query = (
      "SELECT LU.lemma, LU.variant "
      " FROM unitandsynset UAS "
      "   JOIN lexicalunit LU ON (LU.ID = UAS.LEX_ID) "
      " WHERE UAS.SYN_ID = {} AND UAS.unitindex=0".format(synid))
    curr.execute(sql_query)

    while True:
      db_res = curr.fetchone()
      if not db_res:
        break;
      return "{}.{}".format(db_res[0].encode('utf8'), int(db_res[1])).decode('utf8')
    return ''

  def _get_lu_str(self, dbconnection, luid):
    curr = dbconnection.cursor()
    sql_query = (
      "SELECT LU.lemma, LU.variant "
      " FROM unitandsynset UAS "
      "   JOIN lexicalunit LU ON (LU.ID = UAS.LEX_ID) "
      " WHERE LU.ID = {} AND UAS.unitindex=0".format(luid))
    curr.execute(sql_query)

    while True:
      db_res = curr.fetchone()
      if not db_res:
        break;
      return "{}.{}".format(db_res[0].encode('utf8'), int(db_res[1])).decode('utf8')
    return ''