(uiop:define-package :st-buchberger/src/ideal
(:mix :cl)
(:mix-reexport :st-buchberger/src/groebner)
(:export #:ideal #:make-ideal #:basis #:member-p))

(in-package #:st-buchberger/src/ideal)

(defclass ideal ()
 ((ring
   :initarg :ring
   :initform (error "You must specify a ring for the polynomial ideal.")
   :accessor ring)
  (generators
   :initarg :generators
   :initform (error "You must provide a set of generators for the ideal.")
   :accessor generators)
  (cached-basis)))

(defun make-ideal (&rest generators)
 "Create a new ideal generated by the elements contained in the
GENERATORS list."
 (when generators
   (let* ((g (first generators))
          (base-ring (base-ring g)))
     (unless (every (lambda (x) (eq x base-ring))
                    (mapcar #'base-ring (rest generators)))
       (error "Every generator must belong to the same ring."))
     (make-instance 'ideal
                    :ring base-ring
                    :generators (make-array (length generators)
                                            :element-type (class-of g)
                                            :initial-contents generators)))))

(defmethod print-object ((ideal ideal) stream)
 (print-unreadable-object (ideal stream :type t)
   (format stream "~@{~S ~S~^ ~}"
           :ring (slot-value ideal 'ring)
           :generators (slot-value ideal 'generators))))

(defgeneric basis (ideal)
 (:documentation "Returns an array of generators for `ideal'."))

(defmethod basis ((ideal ideal))
 (with-slots (cached-basis) ideal
   (setf cached-basis (reduced-groebner (generators ideal)))
   cached-basis))

(defgeneric member-p (element ideal))

(defmethod member-p ((element ring-element) (ideal ideal))
 (with-slots (generators cached-basis) ideal
   (unless (and (slot-boundp ideal 'cached-basis) cached-basis)
     (setf cached-basis (basis ideal)))
   (ring-zero-p (ring-mod element cached-basis))))