(in-package :maxima)

(use-package "CL-SLOOP")

(defvar $todd_coxeter_state nil)
(proclaim '(type (vector (t))  $todd_coxeter_state))
;;  To turn on debug printing set to T
(defvar *debug* nil)
;;  When *debug* is not nil, this holds the multiplications for
;; the current row.
(defvar *this-row* nil)

(eval-when
    #+gcl (compile eval)
    #-gcl (:compile-toplevel :execute)
    (defmacro nvars () '(aref $todd_coxeter_state 0))
    (defmacro ncosets() '(aref $todd_coxeter_state 1))
    (defmacro multiply-table () '(aref $todd_coxeter_state 2))
    (defmacro relations () '(aref $todd_coxeter_state 3))
    (defmacro subgroup-generators () '(aref $todd_coxeter_state 4))
    (defmacro row1-relations () '(aref $todd_coxeter_state 5))
    (defmacro with-multiply-table (&body body)
      `(let ((nvars (nvars))(multiply-table (multiply-table)))
	 (declare (fixnum nvars) (type (vector (t)) multiply-table))
	 ,@ body)) 

    (defmacro  undef (s) `(eql 0 ,s))

    ;; Multiply coset K times variable R
    (defmacro mult (k r) `(the coset (aref (table ,r) ,k)))

					; Force  k . r = s and  k = s . r^-1
    (defmacro define-mult (k r s)
      `(progn (setf (mult ,k ,r) ,s)
	      (setf (mult ,s (- ,r)) ,k)))

    ;; cosets M < N are to be made equal
    (defmacro push-todo (m n)
      `(progn (vector-push-extend ,m *todo*)
	      (vector-push-extend ,n *todo*)))

    (defmacro f+ (a b) `(the fixnum (+ (the fixnum ,a) (the fixnum ,b))))
    (defmacro f- (a &optional b) `(the fixnum (- (the fixnum ,a)
						 ,@ (if b `((the fixnum ,b))))))
  
    ;; The multiplication table for variable I 
    (defmacro table (i)
      `(the (vector (coset)) (aref  multiply-table (f+ ,i nvars))))

    ;; Some optional declarations of functions.
    (proclaim '(ftype (function (fixnum) t) doing-row)) 
    (proclaim '(ftype (function (t t t) t) set-up todd-coxeter )) 
    (proclaim
     '(ftype (function nil t) fill-in-inverses dprint-state next-coset
       dcheck-tables replace-coset-in-multiply-table)) 
    (proclaim
     '(ftype (function (t) t) $todd_coxeter coerce-rel has-repeat)) 
    (proclaim '(ftype (function (t t) t) my-print))
    (proclaim '(ftype (function (t *) t) $todd_coxeter))) ;; end of the macros and proclamations.

(eval-when
    #+gcl (compile eval load)
    #-gcl (:compile-toplevel :execute :load-toplevel)
    (deftype coset nil 'fixnum)
    (proclaim '(type (vector (coset)) *todo*)))

;; The data type we use to enumerate cosets.


(defvar *todo* (make-array 10 :element-type 'coset :fill-pointer 0 :adjustable t))

;; NVARS is the number of of variables.   It should be the maximum
;; of the absolute values of the entries in the relations RELS.
;; The format of the relations is variables X,Y,.. correspond to
;; numbers 1,2,.. and X^^-1, Y^^-1 .. are -1,-2,...   RELS is
;; a list of lists in these variables.
;; Thus rels = '((1 -2 -1) (2 2 3) ..)  (ie [x1.x2^^-1 . x1^^-1, x2.x2.x3,.. ))
;; SUBGP is also a list of lists.
;; Returns order of G/H, where G is Free/(rels), and H is
;; This is the main entry point at lisp level.
;; Example: (TODD-COXETER 2 '((1 1) (1 2 1 2 1 2) (2 2)))
;; returns 6.   In (multiply-table) we find the current
;; state of the action of the variables on the cosets G/H.
;; For computing the symmetric group using the relations
;; p(i,j) :=concat(x,i).concat(x,j);
;; symet(n):=create_list(if (j - i) = 1 then (p(i,j))^^3 else
;;     if (not i = j) then (p(i,j))^^2 else p(i,i) , j,1,n-1,i,1,j);
;; todd_coxeter(symet(n)) == n!
;; the running time of the first version of this code is observed to be quadratic
;; in the number of cosets.  On a rios it is approx 5*10^-5 * (ncosets)^2.

(defun todd-coxeter (nvars rels subgp &aux (i 1) (c 0 ))
  (declare (fixnum i c))
  (set-up nvars rels subgp)
  (sloop while (>= (ncosets) i)
     do  (incf c) ;; count how many row tries.. 
     (cond
      ;; row still being done
      ((doing-row i) (replace-coset-in-multiply-table))
      ;; row finished but there is work to do
      ((> (the fixnum (fill-pointer *todo*)) 0)
       (incf i) (replace-coset-in-multiply-table))
      ;; row finished -- no work
      (t (incf i))))
  (format t "~%Rows tried ~a" c) 
  (ncosets))

;; Store the data in $todd_coxeter_state, and build multiply-table.
(defun set-up (nvars rels subgp)
  (setf $todd_coxeter_state  (make-array 6))
  (setf (nvars) nvars)
  (setf (ncosets) 1)
  (setf (relations) rels)
  (setf (subgroup-generators) subgp)
  (setf (row1-relations) (append (subgroup-generators)  (relations)))
  (setf (fill-pointer *todo*) 0)
  (setf (multiply-table) (make-array (* 2 (1+ (nvars)))))
  (with-multiply-table
   (sloop for rel in (row1-relations) do
      (sloop for v in rel
	 do (or (<= 1 (abs v)  nvars)
		(error
		 "Vars must be integers with absolute value between 1 and ~a"
		 nvars))))
   (sloop for i from (f- nvars)  to nvars
      when (not (zerop i))
      do (setf (table i) (make-array 10 :adjustable t :element-type 'coset)))
   ))

;; Starts multiplying coset i times the relations.  Basic fact is i . rel = i.
;; This gives a condition on the multiplication table.  Once we have made it all
;; the way through the relations for a given coset i, and NOT had any
;; incosistency in our current multiplication table, then we go on the the next
;; coset.  The coset 1 denotes H.  so for generators h of H we we have 1 . h = 1.
;; So when we do row 1, we add to the relations the generators of H.

;; When we do find an inconsistency eg: 7 . y = 1 and 4 . y = 1 or 7 = 1 . y^^-1
;; and 4 . y = 1, then we would know that 4 and 7 represent the same coset, and
;; so we put 4 and 7 in the *todo* vector and return t so that
;; replace-coset-in-multiply-table will identify them.  While we are running
;; inside doing-row, the multiply-table is accurate, up to our current state of
;; knowledge.  Note that once we find such a nonpermutation action of y, we could
;; not maintain the consistency of (table i) and (table -i).  We exit doing-row
;; with value t, to indicate replacements should be done, and that we must
;; return to complete row i.  (Actually we return t even in the case we were
;; finished the row and found the duplicate in the last step).

(defun doing-row ( i &aux (j 0) (k 0) (r 0)(s 0) *this-row* relations)
  (declare (fixnum j  k i r s))
  (setf relations (if (eql i 1) (row1-relations) (relations)))
  (with-multiply-table
   (sloop for rel in relations
      for v on relations
      do
      (setq k i)
      (sloop 
	 do
	 (setq r (car rel))
	 (setq s (mult  k r))
	 (cond
	  ((undef s)
	   (cond ((cdr rel)
		  (setq s  (next-coset))
		  (define-mult k r s))
		 (t (setq s (mult i  (- r)))
		    (cond ((undef s) (define-mult k r i))
			  ((< k s) (push-todo k s)(return-from doing-row (cdr v)))
			  ((> k s) (push-todo s k)(return-from doing-row (cdr v))))
		    (loop-finish)))))
	 (cond ((setq rel (cdr rel))
		(when *debug* 
		  (push s *this-row*)
		  (my-print (reverse *this-row*) i))
		(setq k s)
		(incf j))
	       ((< i s) (push-todo i s) (return-from doing-row (cdr v)))
	       ((> i s) (push-todo s i) (return-from doing-row (cdr v)))
	       (t			;rel is exhausted and it matched 
		(loop-finish))))))
  (when *debug*  (dcheck-tables) 
	(my-print (reverse *this-row*) i))
  nil)

;; FILL-IN-INVERSES not only completes the (table i) for i < 0
;; but at the same time checks that (table i) for i > 0
;; does not have repeats.   eg if  5 . y = 3 and 7 . y = 3,
;; then this would show up when we go to build the inverse.
;; if it does we add 5 and 7 to the *todo* vector.

(defun fill-in-inverses (&aux (s 0) (sp 0))
  (declare (fixnum s sp))
  ;(format t "~%In fillThere are now ~a cosets" (ncosets))
 (with-multiply-table
  (sloop for i from 1 to nvars
     do (let ((ta1 (table i))
	      (ta2 (table (- i))))
	  (declare (type (vector (coset)) ta1 ta2))
	  (sloop for j from 1 to (ncosets) do (setf (aref ta2 j) 0))
	  (sloop for j from 1 to (ncosets)  do
	     (setf s (aref ta1 j))
	     when (not (eql 0 s))
	     do
	     (setf sp (aref ta2 s))
	     (cond ((eql 0 sp) (setf (aref ta2 s) j))
		   (t	       ;; there's a duplicate!
		    (push-todo sp j)
		    (return-from fill-in-inverses t))))))))
		

;; set n (vector-pop *todo*) , m (vector-pop *todo*)
;; and replace n by m in multiply-table and in *todo*.
;; The replacement is done carefully so as not to lose ANY
;; information from multiply-table, without recording it in
;; *todo*.   It finishes by doing FILL-IN-INVERSES which may
;; in turn cause entries to be added to *todo*.

(defun replace-coset-in-multiply-table (&aux (m 0) (n 0) (s 0) (s2 0) )
  (declare (Fixnum m n s s2 ))
  (with-multiply-table	   
   (tagbody
    AGAIN
    (setf n (vector-pop *todo*))
    (setf m (vector-pop *todo*))
    (unless (eql m n)
	    (dprint-state)
	    (if *debug* (format t "     ~a --> ~a " n m))
	   
	    (sloop for i from 1 to nvars
	       do
	       (let ((ta (table i)))
		 (declare (type  (vector (coset)) ta))
		 (setq s2 (mult n i))
		 (unless (undef s2)
			 (setq s (mult m i))
			 (cond
			  ((undef s) (setf (mult m i) s2))
			  ((< s s2) (push-todo s s2))
			  ((> s s2)(push-todo s2 s))))
		 (sloop for  j downfrom (f- n 1) to 1
		    do (setq s (aref ta j))
		    (cond ((>  s n) (setf (aref ta j) (f- s 1)))
			  ((eql s n) (setf (aref ta j) m) )))
		 (sloop for  j from n below (ncosets)
		    do (setq s (aref ta (f+ j 1)))
		    (cond ((>  s n) (setf (aref ta j) (f- s 1)))
			  ((eql s n) (setf (aref ta j) m) )
			  (t  (setf (aref ta j) s))))))
		
	    (sloop for i downfrom (f- (fill-pointer *todo*) 1) to 0
	       do (setf s (aref *todo* i))
	       (cond ((> s n) (setf (aref *todo* i) (f- s 1)))
		     ((eql s n)(setf (aref *todo* i)  m))))
	    (decf (ncosets))
	    (dprint-state)
	    (progn nil)
	    )

    (if (>  (the fixnum (fill-pointer *todo*)) 0) (go AGAIN))
    ;;(format t "~%There are now ~a cosets" (ncosets))
    ;; check for new duplicates introduced!!
    (if (fill-in-inverses) (go AGAIN))
    )))

;; Get the next coset number, making sure the multiply-table will
;; have room for it, and is appropriately cleaned up.
(defun next-coset ()
  (let* ((n (f+ 1 (ncosets)))
	 (m 0))
    (declare (fixnum n))
    (with-multiply-table    
     (let ((ta (table 1)))
       (unless (> (the fixnum (array-total-size ta)) (f+ n 1))
	       (setf m (f+ n  (ash n -1)))
	       (sloop for i from (f- 0 nvars) to nvars 
		  when (not (eql i 0))
		  do (setf ta (table i))
		  (setf (table i) (adjust-array   ta m ))))
       (sloop for i from 1  to nvars
	  do (setf (aref (table i) n) 0)
	  (setf (aref (table (f- i)) n) 0))))
    (setf (ncosets) n)))



;;  $todd_coxeter parses maxima args
;; todd_coxeter(rels, subgrp) computes the
;; order of G/H where G = Free(listofvars(rels))/subgp_generated(rels));
;; and H is generated by subgp.   Subgp defaults to [].
;; todd_coxeter([x^^3,y.x.y^^-1 . x^^-1],[])  gives 6  the order of the symmetric group
;; on 3 elements.

(defun $todd_coxeter (rels &optional (subgp '((mlist))))
  (let ((vars ($sort ($listofvars rels)))
	(neg 1))
    (declare (special neg vars))
    (todd-coxeter ($length vars) (mapcar 'coerce-rel (cdr rels))
			  (mapcar 'coerce-rel (cdr subgp))
			  )))

(defun coerce-rel (rel )
      (declare (special vars neg))
  (cond ((atom rel)(list (* neg (position rel vars))))
	(t (case (caar rel)
	     (mnctimes (apply 'append (mapcar 'coerce-rel (cdr rel))))
	     (mncexpt
	      (let* ((n  (meval* (third rel)))
		     (neg (signum n))
		     (v (coerce-rel (second rel))))
		(declare (special neg))
		(sloop for i below (abs (third rel))
		   append v)))
	     (otherwise (error "bad rel"))))))

;; The following functions are for debugging purposes, and
;; for displaying the rows as they are computed.   They are
;; not required for correct running.
;#+debug
(progn
(defvar *names* '(nil x y z))
(defun my-print (ro i  &aux relations)
  (when *debug*
  (fresh-line)
;  (print ro)
  (format t "Row ~a " i)
  (setq relations (if (eql i 1) (row1-relations) (relations)))
  (sloop for rel in relations
     do 
     (sloop for v on rel
	do (format t
		   (if (> (car v) 0) "~a" "~(~a~)")
		   (nth (abs (car v)) *names*))
	(cond ((null ro) (return-from my-print)))
	(if (cdr v) (princ (pop ro))
	  (format t "~a | ~a" i i))))))
     
(defun has-repeat (ar &aux (j (+ 1 (ncosets)))  ans tem)
  (sloop for k from 1 to (ncosets)
     do (setq tem (aref ar k))
     (cond ((and  (not (eql tem 0))
		  (find tem ar :start (+ k 1) :end j))
	    (pushnew tem ans))))
     ans)

(defun dcheck-tables ( &aux tem )
  (when *debug*
    (with-multiply-table
    (sloop for i from 1 to (nvars)
       do (if (setq tem (has-repeat (table i )) )
	      (format t "~%Table ~a has repeat ~a " i tem))))))
     
(defun dprint-state ()
  (when *debug*
    (with-multiply-table
	  (format t "~%Ncosets = ~a, *todo*=" (ncosets) *todo*)
	  (sloop for i from 1 to (nvars) do
	     (format t "~%~a:~a" (nth i *names*)
		     (subseq (table i ) 1  (+ 1 (ncosets)))))
	  (my-print (reverse *this-row*) 0)
	  ))
))
