;;; javaimp-util.el --- javaimp util  -*- lexical-binding: t; -*-

;; Copyright (C) 2019  Free Software Foundation, Inc.

;; Author: Filipp Gunbin <fgunbin@fastmail.fm>
;; Maintainer: Filipp Gunbin <fgunbin@fastmail.fm>

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.


;;; Code:

(require 'xml)
(require 'cl-lib)
(require 'seq)

(defcustom javaimp-cygpath-program
  (if (eq system-type 'cygwin) "cygpath")
  "Path to the `cygpath' program (Cygwin only).  Customize it if
the program is not on `exec-path'."
  :group 'javaimp
  :type 'string)

(defconst javaimp-debug-buf-name "*javaimp-debug*")

(defconst javaimp--basedir (file-name-directory load-file-name))

;; Structs

(cl-defstruct javaimp-node
  parent children contents)

(cl-defstruct javaimp-module
  id parent-id
  file
  file-orig
  final-name                           ;may be relative (to build-dir)
  source-dirs build-dir
  dep-jars
  load-ts
  dep-jars-path-fetcher)

(cl-defstruct javaimp-id
  group artifact version)

(cl-defstruct javaimp-cached-jar
  file read-ts classes)


(defun javaimp--xml-children (xml-tree child-name)
  "Returns list of children of XML-TREE filtered by CHILD-NAME"
  (seq-filter (lambda (child)
		(and (consp child)
		     (eq (car child) child-name)))
	      (cddr xml-tree)))

(defun javaimp--xml-child (name el)
  "Returns a child of EL named by symbol NAME"
  (assq name (cddr el)))

(defun javaimp--xml-first-child (el)
  "Returns a first child of EL"
  (car (cddr el)))


(defun javaimp--get-file-ts (file)
  (nth 5 (file-attributes file)))

(defun javaimp-print-id (id)
  (format "%s:%s:%s"
          (javaimp-id-artifact id)
          (javaimp-id-group id)
          (javaimp-id-version id)))

(defun javaimp--get-jdk-jars ()
  (and javaimp-java-home
       (file-accessible-directory-p javaimp-java-home)
       (let ((lib-dir
	      (concat (file-name-as-directory javaimp-java-home)
		      (file-name-as-directory "jre")
		      (file-name-as-directory "lib"))))
	 (directory-files lib-dir t "\\.jar\\'"))))


;; TODO use functions `cygwin-convert-file-name-from-windows' and
;; `cygwin-convert-file-name-to-windows' when they are available
;; instead of calling `cygpath'.  See
;; https://cygwin.com/ml/cygwin/2013-03/msg00228.html

(defun javaimp-cygpath-convert-maybe (path &optional mode is-really-path)
  "On Cygwin, converts PATH using cygpath according to MODE and
IS-REALLY-PATH.  If MODE is `unix' (the default), adds -u switch.
If MODE is `windows', adds -m switch.  If `is-really-path' is
non-nil, adds `-p' switch.  On other systems, PATH is returned
unchanged."
  (if (and path (eq system-type 'cygwin))
      (progn
	(unless mode (setq mode 'unix))
	(let (args)
	  (push (cond ((eq mode 'unix) "-u")
		      ((eq mode 'windows) "-m")
		      (t (error "Invalid mode: %s" mode)))
		args)
	  (and is-really-path (push "-p" args))
	  (push path args)
	  (car (apply #'process-lines javaimp-cygpath-program args))))
    path))


(defun javaimp--call-build-tool (program handler &rest args)
  "Runs PROGRAM with ARGS, then calls HANDLER in the temporary
buffer and returns its result"
  (message "Calling %s on args: %s" program args)
  (with-temp-buffer
    (let ((status (let ((coding-system-for-read
                         (if (eq system-type 'cygwin) 'utf-8-dos)))
                    ;; TODO check 
 in output on Gnu/Linux
                    (apply #'process-file program nil t nil args)))
	  (buf (current-buffer)))
      (with-current-buffer (get-buffer-create javaimp-debug-buf-name)
	(erase-buffer)
	(insert-buffer-substring buf))
      (or (and (numberp status) (= status 0))
	  (error "\"%s\" failed with status \"%s\"" program status))
      (goto-char (point-min))
      (funcall handler))))

(defun javaimp--split-native-path (path)
  (when path
    (let ((converted (javaimp-cygpath-convert-maybe path 'unix t))
	  (sep-regex (concat "[" path-separator "\n" "]+")))
      (split-string converted sep-regex t))))

(defun javaimp--build-tree (this parent-node all)
  (message "Building tree for module: %s" (javaimp-print-id (javaimp-module-id this)))
  (let ((children
	 ;; more or less reliable way to find children is to look for
	 ;; modules with "this" as the parent
	 (seq-filter (lambda (m)
		       (equal (javaimp-module-parent-id m) (javaimp-module-id this)))
		     all)))
    (let* ((this-node (make-javaimp-node
		       :parent parent-node
		       :children nil
		       :contents this))
	   ;; recursively build child nodes
	   (child-nodes
	    (mapcar (lambda (child)
		      (javaimp--build-tree child this-node all))
		    children)))
      (setf (javaimp-node-children this-node) child-nodes)
      this-node)))

(provide 'javaimp-util)
