;;;
;;; | png-to-text.lisp
;;; | What it says on the tin
;;; +------------------------
;;;
;;; ./png-to-text.lisp <FILE_NAME> <WIDTH> <HEIGHT>
;;;
;;; Author: Andres V.
;;; License: GPL V3
;;; Anyone and everyone is allowed to use and change this,
;;;  do whatever you want.
;;; Notes: This only supports PNG files.

(ql:quickload "png")

(defun flatten (l)
 (cond ((null l) nil)
       ((atom l) (list l))
       (t (loop for a in l appending (flatten a)))))

(defun average (l) (/ (apply #'+ l) (list-length l)))

(defun normalize-pixel (image x y)
 "Gray-scale-ifies the pixel to be a single value"
 (let ((values (loop for c from 0 to (1- (png:image-channels image))
                     :collect (if (and (< x (png:image-width image))
                                       (< y (png:image-height image)))
                                  (aref image y x c)
                                  0))))
   (average values)))

(defun average-brightness (image x y w h)
 "Returns the average brightness in a scale from 0 to 1 of the pixel group"
 (let ((values (flatten (loop for i from x to (1- (+ x w))
                              :collect
                              (loop for j from y to (1-(+ y h))
                                    :collect (normalize-pixel image i j))))))
   (/ (average values) 256)))

(defvar color-blocksy
 (list '(4/5 "█")
       '(3/5 "▓")
       '(2/5 "▒")
       '(1/5 "░")
       '(0/5 " ")))

(defun selector (number selectors)
  "Takes a number to compare to and a selectors list composed of a number
to compare to and a return value"
 (second (find-if (lambda (x) (>= number (first x))) selectors)))

(defun select-block (brightness) (selector brightness color-blocksy))

(defun blockify-image (image w h)
 (let* ((sw (floor (png:image-width image) w)) (sh (floor (png:image-height image) h)))
   (loop for i from 0 to (1- w)
         :collect
         (loop for j from 0 to (1- h) :collect
               (select-block (average-brightness image (* i sw) (* j sh) sw sh))))))

(defun print-blocky-image (stream blocky-image)
 (dolist (line blocky-image)
       (format stream "~{~A~}~%" line)))

(defun rotate-list (l w h)
 (loop for j from 0 to (1- h)
       :collect (loop for i from 0 to (1- w)
                      :collect
                      (nth j (nth (- (1- w) i) l)))))

(defun flip-list (l)
 (loop for i in l :collect (reverse i)))

(defun process-file (filename w h)
 (with-open-file (in filename :element-type '(unsigned-byte 8))
   (let* ((image (png:decode in))
          (blocks (blockify-image image w h)))
     (print-blocky-image t (flip-list (rotate-list blocks w h)))
     )))

(defun denil (x) (if (eq x 'nil) "" x))

(defun main ()
 (let* ((fname (nth 1 *posix-argv*))
        (widths  (nth 2 *posix-argv*))
        (heights (nth 3 *posix-argv*))
        (width  (parse-integer (denil widths)  :junk-allowed t))
        (height (parse-integer (denil heights) :junk-allowed t)))
   (if (or (eq width 'nil) (eq height 'nil) (eq fname 'nil))
       (format t "Usage:~%$png-to-text <FILE_NAME> <WIDTH> <HEIGHT>~%")
       (process-file fname width height))))