
(require :asdf)

(pushnew #P"/usr/local/asdf-install/site-systems/" asdf:*central-registry*)

(handler-bind ((style-warning #'muffle-warning))
  (asdf:operate 'asdf:load-op 'cffi-grovel :verbose nil)
  (asdf:operate 'asdf:load-op 'alexandria :verbose nil)
  (asdf:operate 'asdf:load-op 'cffi :verbose nil)
  (asdf:operate 'asdf:load-op 'babel :verbose nil)
  (asdf:operate 'asdf:load-op 'png :verbose nil))

(defpackage #:approx-ft
  (:use "COMMON-LISP"))
(in-package #:approx-ft)

(defun read-png ( input-pathname )
  (with-open-file (input input-pathname
			 :direction :input
			 :element-type '(unsigned-byte 8))
    (png:decode input)))

(defun write-png ( png output-pathname )
  (with-open-file (output output-pathname
			  :direction :output
			  :if-exists :supersede
			  :if-does-not-exist :create
			  :element-type '(unsigned-byte 8))
    (png:encode png output)))

(defun calculate-entropy ( png )
  (let ((counters (make-array '(256)
			      :initial-element 0
			      :element-type 'fixnum))
	(total (* (png:image-height png) (png:image-width png))))
    (dotimes (jj (png:image-height png))
      (dotimes (ii (png:image-width png))
	(incf (aref counters (aref png jj ii 0)))))
    (flet ((entropy ( value )
	     (if (zerop value)
		 0.0d0
		 (let ((pp (/ value total 1.0)))
		   (- (* pp (log pp)))))))
      (loop :for ii :from 0 :below 256
	 :summing (entropy (aref counters ii))))))

(defun calculate-rms ( png )
  (let ((sum 0))
    (dotimes (jj (png:image-height png))
      (dotimes (ii (png:image-width png))
	(let ((vv (- (mod (+ (aref png jj ii 0) 128) 256) 128)))
	  (incf sum (* vv vv)))))
    (sqrt (/ sum (* (png:image-height png) (png:image-width png))))))

(defun box-muller (&optional (random-state *random-state*))
  (let ((u1 (random 1.0 random-state))
        (u2 (random 1.0 random-state)))
    (let ((rr (sqrt (* -2.0 (log u1))))
          (aa (* 2.0 pi u2)))
      (values (coerce (* rr (cos aa)) 'double-float)
              (coerce (* rr (sin aa)) 'double-float)))))

(defun float-to-fixed ( ff )
  (max (min (round (+ (* ff 1024.0) 32768)) 65535) 0))

(defun fixed-to-float ( ff )
  (/ (- ff 32768) 1024.0))

(defparameter +param-count+ 4)
(defparameter +scale+ 0.02d0)
(defparameter +wave-scale+ 1.5d0)

(defun create-random-gene ( count &optional (random-state *random-state*) )
  (let ((nn (* count +param-count+)))
    (let ((ret (make-array (list nn) :element-type '(unsigned-byte 16))))
      (loop :for ii :from 0 :below nn :by 2
	 :do (multiple-value-bind (aa bb)
		 (box-muller random-state)
	       (setf (aref ret (+ ii 0)) (float-to-fixed aa))
	       (when (< (+ ii 1) nn)
		 (setf (aref ret (+ ii 1)) (float-to-fixed bb)))))
      ret)))

(defun gene-to-params ( gg )
  (loop :for ii :from 0 :below (length gg) :by +param-count+
     :collecting (loop :for jj :from 0 :below +param-count+
		    :collecting (fixed-to-float (aref gg (+ ii jj))))))

(defun param-amplitude ( pp )
  (* (first pp) +scale+))
(defun param-frequency ( pp )
  (* (second pp) +wave-scale+))
(defun param-angle ( pp )
  (* (third pp) pi))
(defun param-offset ( pp )
  (* (fourth pp) +wave-scale+))

(defun subtract-image ( orig buf res )
  (let ((ih (png:image-height orig))
	(iw (png:image-width orig)))
    (assert (= (png:image-height orig) (png:image-height buf)))
    (assert (= (png:image-width orig) (png:image-width buf)))
    (assert (= (png:image-channels orig) (png:image-channels buf)))
    (assert (= (png:image-height orig) (png:image-height res)))
    (assert (= (png:image-width orig) (png:image-width res)))
    (assert (= (png:image-channels orig) (png:image-channels res)))
    (loop :for jj :from 0 :below ih
       :do (loop :for ii :from 0 :below iw
	      :do (loop :for kk :from 0 :below (png:image-channels orig)
		     :do (setf (aref res jj ii kk)
			       (mod (- (aref orig jj ii kk)
				       (aref res jj ii kk)) 256)))))
    res))

(defun make-gene-image ( gg buf )
  (let ((params (gene-to-params gg))
	(ih (png:image-height buf))
	(iw (png:image-width buf)))
    (labels ((clip (vv)
	       (declare (type fixnum vv))
	       (cond
		 ((minusp vv) 0)
		 ((< 255 vv) 255)
		 (t vv)))
	     (calc (ii jj params &optional (sum 0.5d0))
	       (declare (type double-float ii jj sum)
			(optimize (speed 3)))
	       (cond
		 ((null params) (clip (coerce (round (* sum 255.0d0))
					      'fixnum)))
		 (t (let ((pp (first params)))
		      (declare (type list pp))
		      (let ((amp (param-amplitude pp))
			    (freq (param-frequency pp))
			    (ang (param-angle pp))
			    (off (param-offset pp)))
			(declare (type double-float amp freq ang off))
			(let ((cos (coerce (cos ang) 'double-float))
			      (sin (coerce (sin ang) 'double-float)))
			  (declare (type double-float cos sin))
			  (let ((xx (+ (* cos ii) (* sin jj))))
			    (declare (type double-float xx))
			    (let ((raw (coerce (cos (* 2.0d0 pi
						       (+ (* xx freq) off)))
					       'double-float)))
			      (declare (type double-float raw))
			      (calc ii jj
				    (rest params)
				    (+ sum (* amp raw))))))))))))
      (loop :for jj :from 0 :below ih
	 :do (loop :with fj = (- (/ jj ih 0.5d0) 1.0d0)
		:for ii :from 0 :below iw
		:do (loop :with fi = (- (/ ii iw 0.5d0) 1.0d0)
		       :for kk :from 0 :below (png:image-channels buf)
		       :do (setf (aref buf jj ii kk)
				 (calc fi fj params))))))
    buf))

(defun subtract-gene-image (gg orig buffer)
  (make-gene-image gg buffer)
  (subtract-image orig buffer buffer))

(defun evaluate-gene ( gg orig &optional buffer )
  (let ((buf (subtract-gene-image gg orig (or buffer (png:copy-image orig)))))
    (* (calculate-rms buf)
       #+nope (calculate-entropy buf))))

(defun read-random-state ( pathname )
  (or (ignore-errors
	(with-open-file (in pathname
			    :direction :input)
	  (read in nil nil)))
      *random-state*))

(defun write-random-state ( pathname rs )
  (with-open-file (out pathname
		       :direction :output
		       :if-exists :supersede
		       :if-does-not-exist :create)
    (write rs :stream out)))

(defun make-population ( genes count evaluator &optional (rs *random-state*) )
    (let ((scored (mapcar #'(lambda (gg)
			      (cons (funcall evaluator gg) gg))
			  (loop :for ii :from 1 :to genes
			     :collecting (create-random-gene count rs)))))
      (make-array genes :initial-contents scored)))

(defun breed (population aa bb dst evaluator &optional (rs *random-state*))
  (let ((bits (* (length (cdr (aref population aa))) 16)))
    (let ((start (random bits rs))
	  (end (random (1- bits) rs))
	  (primary (cdr (aref population aa)))
	  (secondary (cdr (aref population bb)))
	  (gene (cdr (aref population dst))))
      (if (<= start end)
	  (incf end)
	  (rotatef start end))
      (labels ((make-mask (bb &optional (oo 0))
		 (let* ((bb (min (max bb 0) 16))
		       (oo (min (max oo 0) (- 16 bb))))
		   (ash (1- (ash 1 bb)) oo)))
	       (high-bits (vv bb)
		 (logand vv (make-mask bb (- 16 bb))))
	       (mid-bits (vv ss len)
		 (logand vv (make-mask ss (- len ss))))
	       (low-bits (vv bb)
		 (logand vv (make-mask bb)))
	       (combine-word (aa bb ii start end)
		 (let ((start-bit (* ii 16)))
		   (cond
		     ((or (<= (+ start-bit 16) start)
			  (<= end start-bit)) aa)
		     ((and (<= start start-bit)
			   (<=  (+ start-bit 16) end) bb))
		     (t (let ((high (high-bits aa (- start-bit start)))
			      (mid  (mid-bits  bb (- start-bit start)
					          (- end start)))
			      (low  (low-bits  aa (- end (+ start-bit 16)))))
			  (logior high mid low)))))))
	(loop :for word :from 0 :below (/ bits 16)
	   :do (setf (elt gene word)
		     (combine-word (elt primary word)
				   (elt secondary word)
				   word start end))))
      (setf (car (aref population dst))
	    (funcall evaluator gene)))))

(defun mutate (population src dst evaluator &optional (rs *random-state*))
  (let ((in (cdr (aref population src)))
	(out (cdr (aref population dst))))
    (loop :for word :from 0 :below (length in)
       :do (setf (aref out word) (aref in word)))
    (loop :for mutation :from 0 :below (1+ (round (* 3 (abs (box-muller)))))
       :do (let ((bit (random (* (length in) 16) rs)))
	     (let* ((word (floor bit 16))
		    (digit (- bit (* word 16))))
	       (setf (aref out word)
		     (logxor (aref out word) (ash 1 digit))))))
    (setf (car (aref population dst))
	  (funcall evaluator out))))

(defun replace-duplicates (population count evaluator
			   &optional (rs *random-state*))
  (labels ((genes-equal (aa bb &optional (ii -1))
	     (cond
	       ((minusp ii) (= (length aa) (length bb)))
	       ((= ii (length aa)) t)
	       ((= (aref aa ii) (aref bb ii)) (genes-equal aa bb (1+ ii)))
	       (t nil)))
	   (has-duplicate ( population gg ii )
	     (cond
	       ((minusp ii) t)
	       ((genes-equal (cdr (aref population ii)) gg) nil)
	       (t (has-duplicate population gg (1- ii))))))
    (loop :for ii :from 1 :below (length population)
       :do (let ((cons (aref population ii)))
	     (when (has-duplicate population (cdr cons) (1- ii))
	       (let ((gene (create-random-gene count rs)))
		 (setf (car cons) (funcall evaluator gene)
		       (cdr cons) gene)))))))
	
(defun do-generation (population count evaluator &optional (rs *random-state*))
  (let ((nn (floor (/ (length population) 4))))
    (assert (< 1 nn))
    (loop :for ii :from 0 :below nn
       :do (let ((aa (random (* 2 nn)))
		 (bb (random (1- (* 2 nn))))
		 (cc (random nn)))
	     (when (<= aa bb)
		 (incf bb))
	     (breed population aa bb (+ ii nn nn) evaluator rs)
	     (mutate population cc (+ ii nn nn nn) evaluator rs)))
    (replace-duplicates population count evaluator rs)))

(defun make-evaluator ( orig buf )
  (let ((buf (or buf (png:copy-image orig))))
    (lambda (gene)
      (evaluate-gene gene orig buf))))

(defun evolve-image (input-pathname output-pathname
		     &key (population 30)
		     (count 10)
		     (generations 100)
		     (gene-pathname nil)
		     (gene-image-pathname nil))
  (let ((rs (read-random-state #P"random-seed.lisp")))
    (unwind-protect
	 (let* ((png (read-png input-pathname))
		(buf (png:copy-image png))
		(evaluator (make-evaluator png buf))
		(population (make-population population count evaluator rs)))
	   (stable-sort population #'< :key #'car)
	   (dotimes (ii generations)
	     (do-generation population count evaluator rs)
	     (stable-sort population #'< :key #'car)
	     (when gene-pathname
	       (with-open-file (out gene-pathname
				    :direction :output
				    :if-exists :supersede
				    :if-does-not-exist :create)
		 (write (cdr (aref population 0)) :stream out)))
	     (subtract-gene-image (cdr (aref population 0)) png buf)
	     (write-png buf output-pathname)
	     (when gene-image-pathname
	       (make-gene-image (cdr (aref population 0)) buf)
	       (write-png buf (format nil "out-~4,'0D.png" ii))
	       (write-png buf gene-image-pathname))
	     (format t "~S: ~S~%" ii (car (aref population 0)))
	     (force-output))
	   (car (aref population 0)))
      (write-random-state #P"random-seed.lisp" rs))))

(defun do-it ()
  (evolve-image "in.png" "out.png"
		:population 100 :count 50
		:generations (ash 1 12)
		:gene-pathname "gene.lisp"
		:gene-image-pathname "gene.png"))

(do-it)
