(in-package :mt)

;;;
;;; Copyright (c) 2004 -- 2006 Stephan Oepen (oe@csli.stanford.edu)
;;;
;;; This program is free software; you can redistribute it and/or modify it
;;; under the terms of the GNU Lesser General Public License as published by
;;; the Free Software Foundation; either version 2.1 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 Lesser General Public
;;; License for more details.
;;; 

(defun fragment-ep-p (ep)
  (member (mrs:rel-pred ep) *semi-fragment-relations* :test #'equal))

(defun punctuation-ep-p (ep)
  (member (mrs:rel-pred ep) *semi-punctuation-relations* :test #'equal))

(defun token-ep-p (ep)
  (member (mrs:rel-pred ep) *semi-token-relations* :test #'equal))

(defmacro add (n i)
  `(when (numberp ,i) (setf ,n (+ ,n ,i))))

;;;
;;; (LOGON) fragment detection; see comment in `mrsglobals.lisp'.
;;;
(defun fragmentp (mrs)
  (when (mrs::psoa-p mrs)
    (loop
        for ep in (mrs:psoa-liszt mrs)
        when (member (mrs:rel-pred ep) *semi-fragment-relations* :test #'equal)
        count 1 into n
        finally (return (unless (zerop n) n)))))

(defun generate-from-fragmented-mrs (mrs &key signal)
  (declare (ignore signal))

  (labels ((cross-product (sets &key (separator "||"))
             (if (null (rest sets))
               (loop
                   for foo in (first sets) collect foo)
               (loop
                   with rests = (cross-product (rest sets))
                   for foo in (first sets)
                   nconc (loop
                             for bar in rests
                             collect (format
                                      nil
                                      "~a ~a ~a"
                                      foo separator bar))))))
    (let* ((fragments (discriminate-fragments mrs))
           (fragments (loop
                          for fragment in fragments
                          unless (loop
                                     for ep in (mrs:psoa-liszt fragment)
                                     always (punctuation-ep-p ep))
                          collect fragment))
           (tftasks 0) (tetasks 0) (tstasks 0)
           (tunifications 0) (tcopies 0)
           (taedges 0) (tpedges 0)
           (tsubsumptions 0) 
           (tequivalent 0) (tproactive 0) (tretroactive 0)
           outputs statistics)
      (loop
          with lkb::*start-symbol* = lkb::*fragment-start-symbols*
          for fragment in fragments
          for surface = (when (and (mrs:psoa-liszt fragment)
                                   (null (rest (mrs:psoa-liszt fragment)))
                                   (token-ep-p 
                                    (first (mrs:psoa-liszt fragment))))
                          (loop
                              with carg = (mrs::vsym "CARG")
                              with ep = (first (mrs:psoa-liszt fragment))
                              for role in (mrs:rel-flist ep)
                              when (eq (mrs:fvpair-feature role) carg)
                              return (format
                                      nil
                                      "/~a/"
                                      (mrs:fvpair-value role))))
          do
            (multiple-value-bind (strings ftasks etasks stasks
                                  unifications copies aedges pedges)
                (if surface
                  (values (list (list surface)) 0 0 0 0 0 0 0)
                  (lkb::generate-from-mrs fragment :signal nil))
              (push (or strings (list "...")) outputs)
              (add tftasks ftasks) (add tetasks etasks) (add tstasks stasks)
              (add tunifications unifications) (add tcopies copies)
              (add taedges aedges) (add tpedges pedges)
              (add
               tsubsumptions (lkb::statistics-subsumptions lkb::*statistics*))
              (add tequivalent (lkb::statistics-equivalent lkb::*statistics*))
              (add tproactive (lkb::statistics-proactive lkb::*statistics*))
              (add
               tretroactive (lkb::statistics-retroactive lkb::*statistics*))
              ;;
              ;; _fix_me_
              ;; do accumulation of %generator-statistics%, maybe think of a
              ;; way of packaging up fragment combinations as a single edge.
              ;;                                               (18-jul-04; oe)
              (push lkb::%generator-statistics% statistics)))
      (setf lkb::*gen-record* nil)
      (setf lkb::%generator-statistics% nil)
      (setf (lkb::statistics-subsumptions lkb::*statistics*) tsubsumptions)
      (setf (lkb::statistics-equivalent lkb::*statistics*) tequivalent)
      (setf (lkb::statistics-proactive lkb::*statistics*) tproactive)
      (setf (lkb::statistics-retroactive lkb::*statistics*) tretroactive)
      (let* ((strings (cross-product (nreverse outputs)))
             #+:cl-ppcre
             (strings (remove-if
                       #'(lambda (string) (ppcre:scan "^[.|/ ]*$" string))
                       strings)))
        (setf lkb::*gen-record*
          (loop
              for string in strings
              collect (lkb::make-edge :string string)))
        (values
         strings
         tftasks tetasks tstasks tunifications tcopies taedges tpedges)))))

(defun discriminate-fragments (mrs)
  (loop
      with mrss
      for fragments = (find-eps mrs (mrs:psoa-top-h mrs))
      then (find-eps 
            mrs (find-role-value top *semi-fragment-right*))
      for top = (loop for ep in fragments when (fragment-ep-p ep) return ep)
      while fragments
      when (and top (fragment-ep-p top)) do
        (let ((mrs (extract-connected-mrs 
                    mrs 
                    (find-role-value top *semi-fragment-left*))))
          (if mrs
            (push mrs mrss)
            (error
             "discriminate-fragments(): null left daughter (|~a|).~%"
             top)))
      else do
        (push 
         (mrs::make-psoa
          :top-h (mrs::make-var :id (incf %transfer-variable-id%) :type "h")
          :index (mrs::make-var :id (incf %transfer-variable-id%) :type "u")
          :liszt fragments)
         mrss)
         (setf fragments nil)
      finally (return (nreverse mrss))))

;;;
;;; _fix_me_
;;; it appears that in transfer outputs, there can be equivalent but non-eq()
;;; instances of variables, possibly distributed between RELS and HCONS.  in
;;; many contexts, we would not notice, specifically when reading back an MRS
;;; from the string representation (e.g. across processes).     (17-jul-04; oe)
;;;
(defun variable-equal (variable1 variable2)
  (when (and (mrs::var-p variable1) (mrs::var-p variable2))
    (or (eq variable1 variable2)
        (and (equal (mrs:var-type variable1) (mrs:var-type variable2))
             (eql (mrs:var-id variable1) (mrs:var-id variable2))))))

(defun find-role-value (ep role)
  (when (and (mrs::rel-p ep) (symbolp role))
    (loop
        for pair in (mrs:rel-flist ep)
        when (eq (mrs:fvpair-feature pair) role)
        return (mrs:fvpair-value pair))))

(defun find-eps (mrs variable &key role)
  (when (and (mrs::psoa-p mrs) (mrs::var-p variable))
    (loop
        for ep in (mrs:psoa-liszt mrs)
        for key = (if role
                    (find-role-value ep role)
                    (mrs:rel-handel ep))
        when (variable-equal key variable)
        collect ep)))

(defun extract-connected-mrs (mrs variable)
  (when (and (mrs::psoa-p mrs) (mrs::var-p variable))
    (let* ((agenda (list variable))
           variables eps hconss)
      (loop
          for variable = (pop agenda) 
          while variable do
            (push variable variables)
            (loop
                for ep in (mrs:psoa-liszt mrs)
                for label = (mrs:rel-handel ep)
                unless (or (fragment-ep-p ep) (member ep eps :test #'eq))
                do
                  (let ((match (or (variable-equal label variable)
                                   (loop
                                       for pair in (mrs:rel-flist ep)
                                       for value = (mrs:fvpair-value pair)
                                       thereis (variable-equal
                                                value variable)))))
                    (when match
                      (push ep eps)
                      (unless (or (member label variables)
                                  (member label agenda))
                        (push label agenda))
                      (loop
                          for pair in (mrs:rel-flist ep)
                          for value = (mrs:fvpair-value pair)
                          when (and (mrs::var-p value)
                                    (not (member value variables))
                                    (not (member value agenda)))
                          do (push value agenda)))))
            (loop
                for hcons in (mrs:psoa-h-cons mrs)
                for harg = (mrs:hcons-outscpd hcons)
                for larg = (mrs:hcons-scarg hcons)
                when (and (variable-equal harg variable)
                          (not (member larg variables))
                          (not (member larg agenda)))
                do (push larg agenda)
                when (and (variable-equal larg variable)
                          (not (member harg variables))
                          (not (member harg agenda)))
                do (push harg agenda)))
      
      (loop
          for hcons in (mrs:psoa-h-cons mrs)
          when (or (member (mrs:hcons-outscpd hcons) variables)
                   (member (mrs:hcons-scarg hcons) variables))
          do (pushnew hcons hconss :test #'eq))
      
      (mrs::make-psoa
       :top-h (mrs::make-var :id (incf %transfer-variable-id%) :type "h")
       :index (mrs::make-var :id (incf %transfer-variable-id%) :type "u")
       :liszt eps :h-cons hconss))))