#!/usr/bin/python
# -*- coding: utf-8 -*-

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

'''
Sentsim-pair to narzędzie,  które przyjmuje na  wejściu pojedynczy plik lub 
indeks plików,  w  których zapisane są pary zdań. Zdania zapisywane są 
w formacie CCL. W jednym pliku, znajduje się jedna para zdań. Obliczane jest 
podobieństwo właśnie między takimi zdaniami w jednej parze - wewnątrz jednego 
pliku w formacie CCL. Dodatkowo, jeśli podano przełącznik -O ze wskazaniem 
na plik do zapisu wyników, obliczane będzie podobieństwo między wszystkimi 
zdaniami występującymi w  pliku.  Jeśli nie podano tego, przełacznika  wtedy
obliczane jest podobieństwo tylko  między parą  (pierwszym i drugim zdaniem) 
w tym dokumencie.
'''

try:
  import argcomplete
except ImportError:
  argcomplete = None
import sys
import logging
import corpus2
import grafon_base
from grafon.graphs.basegraph import BaseGraph
from grafon.utils.grafonconfig import GrafonConfig
from grafon.gbuilders.graphbuilder import GraphBuilder

def make_parser(desc = ''):
  """! 
  Makes and returns the parser for arguments 

  @param desc Description of application (default is empty)
  @type desc: String
  """
  aparser = grafon_base.make_parser(desc)
  aparser.add_argument('-f', '--cclfile', dest='ccl_file',
                       required=True, type=str, help=(
                         'File with pair of sentences to calcualte similarity '
                         'between them. If batch mode is enalbed, this file '
                         'contains list of files to run in batch mode. '
                         'One line contains path to ccl file and separated by '
                         'semicolon ";" path to rel.ccl file.'))
  aparser.add_argument('-r', '--relcclfile', dest='relccl_file',
                       required=False, type=str, help=(
                         'Path to file with relation for given CCL file. If '
                         'batch mode is used, then this option is ignored. If '
                         'If batch mode is not used, then this option is '
                         'required.'))
  aparser.add_argument('-b', '--batch', dest='batch', action="store_true", 
                       help=(
                         'If option is used, the batch mode is enabled. Into '
                         'file given by -f option, the list of files to '
                         'process have to be inserted in the format:\n'
                         'file.ccl;file.rel.ccl\n...\n'
                         'fileN.ccl;fileN.rel.ccl'))
  aparser.add_argument('-o', '--out-res-file', dest='out_res_file',
                       required=False, type=str, help=(
                         'If options is given then the results will be '
                         'stored into given filename. If not gvien then the '
                         'result will be stored into standard output.'))
  aparser.add_argument('-O', '--out-res-file-between-all', type=str,
                       dest='out_res_file_all', required=False, help=(
                         'If the file is given then the similarity will be '
                         'measured between all sententences into input file'))
  if argcomplete:
    argcomplete.autocomplete(aparser)
  return aparser

def check_required_arguments(args):
  """!
  The function checks the required arguments.

  For example when the batch mode is not enabled, then the option -r
  is reqiured. If there are not any required parameter, the application 
  will be exited with value equal to 1.

  @param args sentsin_pair arguments
  @type args: program args - from Argparser
  """
  if not args.batch and not args.relccl_file:
    logging.error(
      'If batch mode is not enabled, then the file containing relation '
      '(option -r) is required!')
    exit(1)

def calculate_similarity(measures, bdict, mdict, ofilename = None):
  out = sys.stdout if not ofilename else open(ofilename, 'wt')
  out.write('sid1;sid2;S1;S2')
  for m in measures:
    out.write(';%s' % m)
  out.write('\n')

  for (cclfile, relcclfile), bg in bdict.iteritems():
    mg = mdict.get((cclfile, relcclfile))
    sdict = dict()
    for p in bg.document.paragraphs():
      for s in p.sentences():
        sdict[s.id()] = '"%s"' % (' '.join(t.orth_utf8() for t in s.tokens()))
    id1, g1 = mg[0]
    id2, g2 = mg[1]
    out.write('%s:%s;%s:%s;%s;%s' % (
      cclfile, id1, cclfile, id2, sdict[id1], sdict[id2]))
    for m in measures:
      out.write(';%s' % (str(m.calculate(g1, g2))))
    out.write('\n')
  out.close()

def calculate_similarity_between_all(measures, bdict, mdict, ofilename):
  out = open(ofilename, 'wt')
  '''
  out.write('sid1;sid2;S1;S2')
  for m in measures:
    out.write(';%s' % m)
  out.write('\n')
  '''

  rdict = dict()
  sdict = dict()
  for (cclfile, relcclfile), bg in bdict.iteritems():
    mg = mdict.get((cclfile, relcclfile))
    id1, g1 = mg[0]
    id2, g2 = mg[1]
    rdict[(cclfile, relcclfile, id1)] = g1
    rdict[(cclfile, relcclfile, id2)] = g2
    for p in bg.document.paragraphs():
      for s in p.sentences():
        sdict[(cclfile, relcclfile, s.id())] = '"%s"' % (
          ' '.join(t.orth_utf8() for t in s.tokens()))

  resdict = dict()
  for (cclfile1, relcclfile1, sid1), g1 in rdict.iteritems():
    for (cclfile2, relcclfile2, sid2), g2 in rdict.iteritems():
      '''
      out.write('%s:%s;%s:%s;%s;%s' % (
        cclfile1, sid1, cclfile2, sid2, 
        sdict[(cclfile1, relcclfile1, sid1)], 
        sdict[(cclfile2, relcclfile2, sid2)]))
      '''
      for m in measures:
        vres = m.calculate(g1, g2)
        if not resdict.has_key((cclfile1, relcclfile1, sid1)):
          resdict[(cclfile1, relcclfile1, sid1)] = []
        resdict[(cclfile1, relcclfile1, sid1)].append(
          [vres, m, cclfile2, relcclfile2, sid2])
        #out.write(';%s' % (str(vres)))
      #out.write('\n')

  for (ccl, rel, sid), rank in resdict.iteritems():
    out.write("%s:%s;" % (ccl, sid)) 
    for [vres, m, cl, rl, sd] in sorted(rank, key=lambda e: e[0], reverse=True):
      if ccl == cl and rel == rel and sid == sd:
        continue
      out.write("%s:%s(%s, %s);" % (cl, sd, str(vres), str(m)))
    out.write('\n')
  out.close()

def main(argv=None):
  p = make_parser(__doc__)
  args = p.parse_args(argv)
  check_required_arguments(args)

  cfg = GrafonConfig(args.config)
  tagset = cfg.tagset()
  node = cfg.node(tagset)
  filters = cfg.filters(tagset)

  # empty base graph will be used into mergers only
  bg = BaseGraph(node, filters)
  smerger = cfg.sumo_merger(bg)
  pmerger = cfg.plwn_merger(bg)
  # same graph builder will be used to build all graphs
  bgbuilder = GraphBuilder()

  # read required graphs and graph reducers
  sgraph    = grafon_base.read_sumo_graph(cfg) if smerger else None
  plwngraph = grafon_base.read_plwn_graph(cfg) if pmerger else None
  reducers  = cfg.reducers() if cfg.reducers() else None

  files_list = []
  if args.batch:
    with open(args.ccl_file) as batchfile:
      for line in batchfile:
        files_list.append(line.strip().split(';'))
  else:
    files_list.append((args.ccl_file, args.relccl_file))

  # for each file from files build the graph and put it into dictionary
  # where as the key the pair of (cclfilepath, relcclfilepath) is used
  # and the built base graph is a value into this key.
  f_bg_dict = dict()
  for (cclfile, relcclfile) in files_list:
    f_bg_dict[(cclfile, relcclfile)] = grafon_base.build_graph_for_file(
      cclfile, relcclfile, bgbuilder, cfg.tagset(), node, filters)
  
  # for each graph add the edges
  for (cclfile, relcclfile), f_bg in f_bg_dict.iteritems():
    logging.info('Adding edges into graph {}'.format(cclfile))
    if args.verbose_mode:
      print >> sys.stderr, 'Adding edges into graph %s' % (cclfile)
    for ebuilder in cfg.ebuilders():
      if args.verbose_mode:
        print >> sys.stderr, '\tbuilder %s' % (ebuilder)
      logging.info('\tbuilder {}'.format(ebuilder))
      ebuilder.add_edges(f_bg)
    for rebuilder in cfg.rebuilders():
      if args.verbose_mode:
        print >> sys.stderr, 'Rebuilding graph usingr ebuilder', rebuilder
      logging.info('Rebuilding graph usingr ebuilder {}'.format(rebuilder))
      rebuilder.rebuild_base_graph(f_bg)

  # for each graph try to mege with sumo/plwn graph
  # the key is same as in f_bg_dict and as the value is used pair 
  # of {sentence id, merged graph} if nothing will be merged, then as
  # the "merged" graph will be used "not merged" graph (a base graph)
  f_merged_dict = dict()
  for (cclfile, relcclfile), f_bg in f_bg_dict.iteritems():
    f_merged_dict[(cclfile, relcclfile)] = grafon_base.merge_graphs(
      f_bg, smerger, sgraph, pmerger, plwngraph, reducers)

  # Filter graph by the min number of nodes and/or edges
  if cfg.min_edge_count() or cfg.min_node_count():
    grafon_base.filter_n_e_by_count(cfg, f_merged_dict.values())

  # calculate similarity between pairs
  calculate_similarity(cfg.measures(), f_bg_dict, f_merged_dict, 
                       args.out_res_file)
  # calculate similarity between all files
  if args.out_res_file_all:
    calculate_similarity_between_all(
      cfg.measures(), f_bg_dict, f_merged_dict, args.out_res_file_all)

  # if user would like to store graphs into file, so lets do it!
  if args.visualise_dir:
    for (cclfile, relcclfile), f_bg in f_bg_dict.iteritems():
      grafon_base.vis_graphs_into_files(
        f_bg, f_merged_dict[(cclfile, relcclfile)], 
        args.visualise_dir, oformat=args.visualise_format)

  logging.info('Grafon - Everything has been done ;>')

if __name__ == '__main__':
  main()