(require 'org-element)
(require 'string-inflection)

(setq *test-tags* (json-read-file "TAGS2"))
(setq *keys* (delete-dups (mapcar (lambda (x) (mapcar (lambda (y) (car y)) x)) *test-tags*)))

(defun classes-with-members (tags-data)
 (delete nil (delete-dups
              (mapcar
               (lambda (x)
                 (if (string-equal (alist-get 'scopeKind x) "class")
                     (alist-get 'scope x)))
               tags-data))))

(defun find-class (cls tags)
 (delete
  nil (mapcar (lambda (x) (let ((val (alist-get 'name x)))
                            (if (string-equal val cls) x)))
              tags)))

(defun find-class-members (cls tags)
 ;;; These are only like "m_*" members, not like member
 ;;; functions or getters or anything like that.
 (delete
  nil (mapcar (lambda (x)
                (if
                    (and (string-equal (alist-get 'scopeKind x) "class")
                         (string-equal (alist-get 'kind x) "member")
                         (string-equal (alist-get 'scope x) cls))
                    x))
              tags)))


(defun tag-apply-predicates (tag-record conditions)
   (mapcar
    (lambda (condition)
      (pcase-let ((`(,func ,p ,q) condition))
        (let ((x (apply func (list (alist-get p tag-record) q))))
          (if (equal nil x) nil
            tag-record))))
    conditions))

(defun find-tags (tags-data conditions)
 ;;; `conditions` is a list with a comparison function,
 ;;;  an alist key, and a value for comparison.
 (delete
  nil
  (mapcan (lambda (tag-record)
            (let ((candidate (tag-apply-predicates tag-record conditions)))
              (if (member nil candidate) nil
                candidate)))
          tags-data)))

(defun select-from-tags (tags-data keys &rest conditions)
 ;;; This one is fit for public consumption.
 (delete-dups
  (mapcar
   (lambda (x) (mapcar (lambda (k) (alist-get k x)) keys))
   (find-tags tags-data conditions))))


(defun tag-namespaced-name (tag)
 (let ((has-namespacep (alist-get 'scopeKind tag)))
   (if (string-equal has-namespacep "namespace")
       (string-join (list (alist-get 'scope tag) "::" (alist-get 'name tag)))
     (alist-get 'name tag))))


(setq *prototypes* (select-from-tags
                   *test-tags*
                   '(name access scope signature)
                   '(string-equal kind "prototype")))

(setq *render-window*
     '("sf::RenderWindow::RenderWindow" "public" "sf::RenderWindow"
       "(WindowHandle handle,const ContextSettings & settings=ContextSettings ())"))

(defun tag-wildcard (x y) t)
(setq *tag-wildcard* '(tag-wildcard i j))

(defun get-header-type-signature-info (tags-data header-path)
 (let* ((prototype-keys '(path name access typeref scope scopeKind signature))
        (parameter-keys '(name typeref))
        (prototypes
         (mapcar (lambda (x) (seq-mapn 'cons prototype-keys x))
                 (select-from-tags
                  tags-data
                  prototype-keys
                  '(string-equal kind "prototype")
                  `(string-equal path ,header-path)))))
   (mapcar (lambda (x)
             (let* ((parameter-info-list (select-from-tags
                                          tags-data parameter-keys
                                          `(string-equal scope ,(alist-get 'name x))
                                          `(string-equal kind "parameter")))
                    (parameter-info-alist (mapcar
                                           (lambda (p) (seq-mapn 'cons parameter-keys p))
                                           parameter-info-list)))
               (cons `(parameters . ,parameter-info-alist) x)))
           prototypes)))

(defun get-all-header-type-signature-info (tags-data)
 (mapcar (lambda (h) (get-header-type-signature-info tags-data h))
         (mapcar
          'car
          (select-from-tags tags-data '(path) *tag-wildcard*))))

(defun get-signatures (tags) (delete nil (get-all-header-type-signature-info tags)))
(defun get-qualified-signatures (tags)
 (let ((qualified (mapcar (lambda (s)
                           (seq-filter
                            (lambda (x) (and
                                         (string-match-p "sf::.+" (alist-get 'name x))
                                         (string-equal "public" (alist-get 'access x))))
                            s))
                          (get-signatures tags))))
   (seq-filter (lambda (x) (not (equal x nil))) qualified)))
(defun get-paths (tags)
 (seq-sort
  'string-collate-lessp
  (mapcar (lambda (x) (alist-get 'path (car x)))
          (get-qualified-signatures tags))))


(setq *signatures* (get-signatures *test-tags*))
     ;;(delete nil (get-all-header-type-signature-info *test-tags*)))
(setq *qualified-signatures* (get-qualified-signatures *test-tags*))
;; (let ((qualified (mapcar (lambda (s)
;;                          (seq-filter
;;                           (lambda (x) (and
;;                                        (string-match-p "sf::.+" (alist-get 'name x))
;;                                        (string-equal "public" (alist-get 'access x))))
;;                           s))
;;                        *signatures*)))
;;   (seq-filter (lambda (x) (not (equal x nil))) qualified)))
(setq *paths* (get-paths *test-tags*))
     ;(seq-sort
;              'string-collate-lessp
;              (mapcar (lambda (x) (alist-get 'path (car x))) *qualified-signatures*)))

(defun constructor-p (prototype)
 (let ((scope (split-string (alist-get 'scope prototype) "::"))
       (name (split-string (alist-get 'name prototype) "::")))
   (equal (last scope) (last name))))

(defun destructor-p (prototype)
 (let ((name (split-string (alist-get 'name prototype) "::")))
   (if (string-match-p "^~.+" (car (last name))) t nil)))

(defun constructor-type (prototype)
 (if (or (destructor-p prototype) (constructor-p prototype)) (alist-get 'scope prototype) nil))

(defun find-param-symbol (param) (car (last (split-string param " "))))

(defun find-param-type (param)
 (let* ((param-tokens (split-string param " "))
        (token-count (length param-tokens)))
   (string-join (seq-take param-tokens (- token-count 1)) " ")))

(defun prepare-signature (sig)
 (let* ((sig-list (split-string (string-trim sig "(" ")") ","))
        (type-symbol-pairs (mapcar
                            (lambda (x) `(,(find-param-type x) ,(find-param-symbol x)))
                            sig-list))

        ;; In most cases, the first parameter should be the object. Then, the predicate
        ;; can be called like `render_window_draw(RenderWindow, Drawable, RenderStates).`

        ;; Other clause orders could be like `render_window_some_static_method(X,Y,Z).`
        ;; for static methods; and `render_window_create(RenderWindow, H, W, Etc).`
        ;; The order matters for currying and sequential application and stuff -- probly
        ;; need to adhere to some standard conventions to keep from getting confused.

        (param-enum (number-sequence 1 (length type-symbol-pairs))))
   (seq-mapn (lambda (i j) (cons (number-to-string i) j)) param-enum type-symbol-pairs)))

(defun format-parameter-rows (sig)
 (let ((row-data (mapcar (lambda (x) (string-join x " | ")) (prepare-signature sig))))
   (string-join (mapcar (lambda (x) (format "| %s | | |" x)) row-data) "\n")))

(defun parameter-table-columns ()
 (let ((columns '("Argv Idx"    "C++ type"    "C++ symbol"
                  "Prolog term" "Prolog mode" "Prolog type")))
   (format "| %s |" (string-join columns " | "))))

(defun make-parameter-table (sig)
 (format "%s\n|-\n%s"
         (parameter-table-columns) (format-parameter-rows sig)))

(defun insert-parameter-table (sig)
 (insert (make-parameter-table sig))
 (org-table-align))

(defun upcase-p (c)
 (let ((s (char-to-string c)))
   (and (string-match "[A-Z]" s)
        (string-equal s (upcase s)))))

(defun look-ahead (char-list i) (nth (+ i 1) char-list))

(defun camel-to-snake (s) (string-inflection-underscore-function s))
(defun camel-to-pascal (s) (string-inflection-pascal-case-function s))


;%(defun camel-to-snake (s)
;%  (string-match-

(defun get-module-name (prototype)
 (let* ((path (alist-get 'path prototype))
        (module-name (last (split-string path "/"))))
   (camel-to-snake (car (split-string (car module-name) ".hpp")))))

(setq *sprite* (car (seq-filter
                    (lambda (x) (string-equal (alist-get 'path (car x))
                                              "include/SFML/Graphics/Sprite.hpp"))
                    *qualified-signatures*)))


;; Argv Idx, SFML type, C++ Parameter, Prolog term, Prolog type, Prolog mode

'(:namespace :module :fn-name :return-type :caller-type :argv-idx :parameter-type)

(defun get-return-type (prototype)
 (cond ((constructor-p prototype) (alist-get 'scope prototype))
       ((destructor-p prototype) "void")
       (t (string-remove-prefix "typename:" (alist-get 'typeref prototype)))))


(defvar *constructor* "constructor")
(defvar *destructor* "destructor")
(defvar *member-function* "member-function")
(defvar *member-data* "member-data")

(defun get-prototype-role (prototype)
 (cond ((constructor-p prototype) *constructor*)
       ((destructor-p prototype) *destructor*)
       (t *member-function*)))


(defun prototype-information (namespace prototype)
 `((:namespace . ,namespace)
   (:module . ,(get-module-name prototype))
   (:fn-name . ,(alist-get 'name prototype))
   (:return-type . ,(get-return-type prototype))
   (:caller-type . ,(alist-get 'scope prototype))
   (:role . ,(get-prototype-role prototype))))

(defun parameter-information (argv-idx parameter)
 `((:argv-idx   . ,argv-idx)
   (:param-type . ,(string-remove-prefix "typename:" (alist-get 'typeref parameter)))
   (:param-sym  . ,(alist-get 'name parameter))))

(defun parameter-list-information (prototype-information parameter-list)
 (let ((caller-info `((:argv-idx . 1)
                      (:param-type . ,(alist-get :caller-type prototype-information))
                      (:param-sym . "<-this"))))
   (cond ((not parameter-list) (list caller-info))
         ((= 1 (length parameter-list)) (append caller-info
                                              `(,(parameter-information 2 (car parameter-list)))))
         (t (let ((argv-index (number-sequence 0 (- (length parameter-list) 1))))
              (append
               `(,caller-info)
               (mapcar
                (lambda (idx) (let ((param (nth idx parameter-list)))
                                (parameter-information (+ 2 idx) param)))
                argv-index)))))))

(defun get-tabular-prototype-signature (namespace prototype)
 (let ((prototype-info (prototype-information namespace prototype)))
   (mapcar
    (lambda (x) (append prototype-info x))
    (parameter-list-information prototype-info (alist-get 'parameters prototype)))))


(setq *sprite-info* (prototype-information "graphics" (car *sprite*)))
(setq *sprite-params* (parameter-list-information *sprite-info* (alist-get 'parameters (car *sprite*))))


(defun prototype-signature-row-infer-prolog-functor (tabular-signature-row)
 (let* ((qualified-name (split-string (alist-get :fn-name tabular-signature-row) "::"))
        (name-parts-count (length qualified-name))
        (name-parts (seq-take (reverse qualified-name) (- name-parts-count 1)))
        (role (alist-get :role tabular-signature-row)))
   (camel-to-snake
    (cond ((string-equal role *constructor*)
           (concat (string-join (delete-dups name-parts) "_") "_create"))
          ((string-equal role *destructor*) (concat (string-join (delete-dups name-parts) "_") "_delete"))
          ((string-equal role *member-function*) (string-join name-parts "_"))))))

(defun prototype-signature-infer-prolog-functor (tabular-signature)
 (prototype-signature-row-infer-prolog-functor (car tabular-signature)))

(defvar *<-this* "<-this")

(defun infer-prolog-term (tabular-signature-row)
 (let ((cpp-sym (alist-get :param-sym tabular-signature-row)))
   (cond ((string-equal cpp-sym *<-this*) (camel-to-pascal
                                           (alist-get :module tabular-signature-row)))
         (t (camel-to-pascal cpp-sym)))))

(defun prototype-signature-row-infer-prolog-information (tabular-signature-row)
 `((:prolog-functor . ,(prototype-signature-row-infer-prolog-functor tabular-signature-row))
   (:prolog-term . ,(infer-prolog-term tabular-signature-row))))  ;;;,(camel-to-pascal (alist-get :param-sym tabular-signature-row)))))

(defun prototype-signature-infer-prolog-information (tabular-signature)
 (mapcar 'prototype-signature-row-infer-prolog-information tabular-signature))