;;  Copyright (c) Jeffrey Straszheim. All rights reserved.  The use and
;;  distribution terms for this software are covered by the Eclipse Public
;;  License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) which can
;;  be found in the file epl-v10.html at the root of this distribution.  By
;;  using this software in any fashion, you are agreeing to be bound by the
;;  terms of this license.  You must not remove this notice, or any other,
;;  from this software.
;;
;;  literals.clj
;;
;;  A Clojure implementation of Datalog -- Literals
;;
;;  straszheimjeffrey (gmail)
;;  Created 25 Feburary 2009


(ns clojure.contrib.datalog.literals
  (:use clojure.contrib.datalog.util)
  (:use clojure.contrib.datalog.database)
  (:use [clojure.set :only (intersection)])
  (:use [clojure.contrib.set :only (subset?)])
  (:use [clojure.contrib.seq-utils :only (flatten)]))


;;; Type Definitions

(defstruct atomic-literal
  :predicate              ; The predicate name
  :term-bindings          ; A map of column names to bindings
  :literal-type)          ; ::literal or ::negated

(derive ::negated ::literal)

(defstruct conditional-literal
  :fun                    ; The fun to call
  :symbol                 ; The fun symbol (for display)
  :terms                  ; The formal arguments
  :literal-type)          ; ::conditional


;;; Basics


(defmulti literal-predicate
  "Return the predicate/relation this conditional operates over"
  :literal-type)

(defmulti literal-columns
  "Return the column names this applies to"
  :literal-type)

(defmulti literal-vars
  "Returns the logic vars used by this literal"
  :literal-type)

(defmulti positive-vars
  "Returns the logic vars used in a positive position"
  :literal-type)

(defmulti negative-vars
  "Returns the logic vars used in a negative position"
  :literal-type)

(defmethod literal-predicate ::literal
  [l]
  (:predicate l))

(defmethod literal-predicate ::conditional
  [l]
  nil)

(defmethod literal-columns ::literal
  [l]
  (-> l :term-bindings keys set))

(defmethod literal-columns ::conditional
  [l]
  nil)

(defmethod literal-vars ::literal
  [l]
  (set (filter is-var? (-> l :term-bindings vals))))

(defmethod literal-vars ::conditional
  [l]
  (set (filter is-var? (:terms l))))

(defmethod positive-vars ::literal
  [l]
  (literal-vars l))

(defmethod positive-vars ::negated
  [l]
  nil)

(defmethod positive-vars ::conditional
  [l]
  nil)

(defmethod negative-vars ::literal
  [l]
  nil)

(defmethod negative-vars ::negated
  [l]
  (literal-vars l))

(defmethod negative-vars ::conditional
  [l]
  (literal-vars l))

(defn negated?
  "Is this literal a negated literal?"
  [l]
  (= (:literal-type l) ::negated))

(defn positive?
  "Is this a positive literal?"
  [l]
  (= (:literal-type l) ::literal))


;;; Building Literals

(def negation-symbol 'not!)
(def conditional-symbol 'if)

(defmulti build-literal
  "(Returns an unevaluated expression (to be used in macros) of a
   literal."
  first)

(defn build-atom
  "Returns an unevaluated expression (to be used in a macro) of an
   atom."
  [f type]
  (let [p (first f)
        ts (map #(if (is-var? %) `(quote ~%) %) (next f))
        b (if (seq ts) (apply assoc {} ts) nil)]
    `(struct atomic-literal ~p ~b ~type)))

(defmethod build-literal :default
  [f]
  (build-atom f ::literal))

(defmethod build-literal negation-symbol
  [f]
  (build-atom (rest f) ::negated))

(defmethod build-literal conditional-symbol
  [f]
  (let [symbol (fnext f)
        terms (nnext f)
        fun `(fn [binds#] (apply ~symbol binds#))]
    `(struct conditional-literal
             ~fun
             '~symbol
             '~terms
             ::conditional)))


;;; Display

(defmulti display-literal
  "Converts a struct representing a literal to a normal list"
  :literal-type)

(defn- display
  [l]
  (conj (-> l :term-bindings list* flatten) (literal-predicate l)))

(defmethod display-literal ::literal
  [l]
  (display l))

(defmethod display-literal ::negated
  [l]
  (conj (display l) negation-symbol))

(defmethod display-literal ::conditional
  [l]
  (list* conditional-symbol (:symbol l) (:terms l)))


;;; Sip computation

(defmulti get-vs-from-cs
  "From a set of columns, return the vars"
  :literal-type)

(defmethod get-vs-from-cs ::literal
  [l bound]
  (set (filter is-var?
               (vals (select-keys (:term-bindings l)
                                  bound)))))

(defmethod get-vs-from-cs ::conditional
  [l bound]
  nil)


(defmulti get-cs-from-vs
  "From a set of vars, get the columns"
  :literal-type)

(defmethod get-cs-from-vs ::literal
  [l bound]
  (reduce conj
          #{}
          (remove nil? 
                  (map (fn [[k v]] (if (bound v) k nil))
                       (:term-bindings l)))))

(defmethod get-cs-from-vs ::conditional
  [l bound]
  nil)


(defmulti get-self-bound-cs
  "Get the columns that are bound withing the literal."
  :literal-type)

(defmethod get-self-bound-cs ::literal
  [l]
  (reduce conj
          #{}
          (remove nil?
                  (map (fn [[k v]] (if (not (is-var? v)) k nil))
                       (:term-bindings l)))))

(defmethod get-self-bound-cs ::conditional
  [l]
  nil)


(defmulti literal-appropriate?
  "When passed a set of bound vars, determines if this literal can be
   used during this point of a SIP computation."
  (fn [b l] (:literal-type l)))

(defmethod literal-appropriate? ::literal
  [bound l]
  (not (empty? (intersection (literal-vars l) bound))))

(defmethod literal-appropriate? ::negated
  [bound l]
  (subset? (literal-vars l) bound))

(defmethod literal-appropriate? ::conditional
  [bound l]
  (subset? (literal-vars l) bound))


(defmulti adorned-literal
  "When passed a set of bound columns, returns the adorned literal"
  (fn [l b] (:literal-type l)))

(defmethod adorned-literal ::literal
  [l bound]
  (let [pred (literal-predicate l)
        bnds (intersection (literal-columns l) bound)]
    (if (empty? bound)
      l
      (assoc l :predicate {:pred pred :bound bnds}))))

(defmethod adorned-literal ::conditional
  [l bound]
  l)


(defn get-adorned-bindings
  "Get the bindings from this adorned literal."
  [pred]
  (:bound pred))

(defn get-base-predicate
  "Get the base predicate from this predicate."
  [pred]
  (if (map? pred)
    (:pred pred)
    pred))


;;; Magic Stuff

(defn magic-literal
  "Create a magic version of this adorned predicate."
  [l]
  (assert (-> l :literal-type (isa? ::literal)))
  (let [pred (literal-predicate l)
        pred-map (if (map? pred) pred {:pred pred})
        bound (get-adorned-bindings pred)
        ntb (select-keys (:term-bindings l) bound)]
    (assoc l :predicate (assoc pred-map :magic true) :term-bindings ntb :literal-type ::literal)))

(defn literal-magic?
  "Is this literal magic?"
  [lit]
  (let [pred (literal-predicate lit)]
    (when (map? pred)
      (:magic pred))))
      
(defn build-seed-bindings
  "Given a seed literal, already adorned and in magic form, convert
   its bound constants to new variables."
  [s]
  (assert (-> s :literal-type (isa? ::literal)))
  (let [ntbs (map-values (fn [_] (gensym '?_gen_)) (:term-bindings s))]
    (assoc s :term-bindings ntbs)))


;;; Semi-naive support

(defn negated-literal
  "Given a literal l, return a negated version"
  [l]
  (assert (-> l :literal-type (= ::literal)))
  (assoc l :literal-type ::negated))

(defn delta-literal
  "Given a literal l, return a delta version"
  [l]
  (let [pred* (:predicate l)
        pred (if (map? pred*) pred* {:pred pred*})]
    (assoc l :predicate (assoc pred :delta true))))

        
;;; Database operations

(defn- build-partial-tuple
  [lit binds]
  (let [tbs (:term-bindings lit)
        each (fn [[key val :as pair]]
               (if (is-var? val)
                 (if-let [n (binds val)]
                   [key n]
                   nil)
                 pair))]
    (into {} (remove nil? (map each tbs)))))

(defn- project-onto-literal
  "Given a literal, and a materialized tuple, return a set of variable
   bindings."
  [lit tuple]
  (let [step (fn [binds [key val]]
               (if (and (is-var? val)
                        (contains? tuple key))
                 (assoc binds val (tuple key))
                 binds))]
    (reduce step {} (:term-bindings lit))))
  

(defn- join-literal*
  [db lit bs fun]
  (let [each (fn [binds]
               (let [pt (build-partial-tuple lit binds)]
                 (fun binds pt)))]
    (when (contains? db (literal-predicate lit))
      (apply concat (map each bs)))))

(defmulti join-literal
  "Given a database (db), a literal (lit) and a seq of bindings (bs),
   return a new seq of bindings by joining this literal."
  (fn [db lit bs] (:literal-type lit)))

(defmethod join-literal ::literal
  [db lit bs]
  (join-literal* db lit bs (fn [binds pt]
                             (map #(merge binds %)
                                  (map (partial project-onto-literal lit)
                                       (select db (literal-predicate lit) pt))))))

(defmethod join-literal ::negated
  [db lit bs]
  (join-literal* db lit bs (fn [binds pt]
                             (if (any-match? db (literal-predicate lit) pt)
                               nil
                               [binds]))))

(defmethod join-literal ::conditional
  [db lit bs]
  (let [each (fn [binds]
               (let [resolve (fn [term]
                               (if (is-var? term)
                                 (binds term)
                                 term))
                     args (map resolve (:terms lit))]
                 (if ((:fun lit) args)
                   binds
                   nil)))]
    (remove nil? (map each bs))))
                 
(defn project-literal
  "Project a stream of bindings onto a literal/relation. Returns a new
   db."
  ([db lit bs] (project-literal db lit bs is-var?))
  ([db lit bs var?]
     (assert (= (:literal-type lit) ::literal))
     (let [rel-name (literal-predicate lit)
           columns (-> lit :term-bindings keys)
           idxs (vec (get-adorned-bindings (literal-predicate lit)))
           db1 (ensure-relation db rel-name columns idxs)
           rel (get-relation db1 rel-name)
           step (fn [rel bindings]
                  (let [step (fn [t [k v]]
                               (if (var? v)
                                 (assoc t k (bindings v))
                                 (assoc t k v)))
                        tuple (reduce step {} (:term-bindings lit))]
                    (add-tuple rel tuple)))]
       (replace-relation db rel-name (reduce step rel bs)))))


;; End of file
