;;; ein-classes.el --- Classes and structures.      -*- lexical-binding:t -*-

;; Copyright (C) 2017 John M. Miller

;; Author: John M Miller <millejoh at mac dot com>

;; This file is NOT part of GNU Emacs.

;; ein-classes.el 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.

;; ein-classes.el 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 ein-worksheet.el.  If not, see <http://www.gnu.org/licenses/>.

;;; Commentary:

;;; Content
(require 'eieio)

(cl-defstruct ein:$content
  "Content returned from the Jupyter notebook server:
`ein:$content-url-or-port'
  URL or port of Jupyter server.

`ein:$content-name'
  The name/filename of the content. Always equivalent to the last
  part of the path field

`ein:$content-path'
 The full file path. It will not start with /, and it will be /-delimited.

`ein:$content-type'
 One of three values: :directory, :file, :notebook.

`ein:$content-writable'
  Indicates if requester has permission to modified the requested content.

`ein:$content-created'

`ein:$content-last-modified'

`ein:$content-mimetype'
  Specify the mime-type of :file content, null otherwise.

`ein:$content-raw-content'
  Contents of resource as returned by Jupyter.  Depending on content-type will hold:
    :directory : JSON list of models for each item in the directory.
    :file      : Text of file as a string or base64 encoded string if mimetype
                 is other than 'text/plain'.
    :notebook  : JSON structure of the file.

`ein:$content-format'
  Value will depend on content-type:
    :directory : :json.
    :file      : Either :text or :base64
    :notebook  : :json.

"
  url-or-port
  notebook-api-version
  name
  path
  type
  writable
  created
  last-modified
  mimetype
  raw-content
  format
  session-p)
;;; Websockets

(cl-defstruct ein:$websocket
  "A wrapper object of `websocket'.

`ein:$websocket-ws'               : an instance returned by `websocket-open'
`ein:$websocket-kernel'           : kernel at the time of instantiation
`ein:$websocket-closed-by-client' : t/nil'
"
  ws
  kernel
  closed-by-client)

;;; Notebook
(cl-defstruct ein:$notebook
  "Hold notebook variables.

`ein:$notebook-url-or-port'
  URL or port of IPython server.

`ein:$notebook-notebook-id' : string
  uuid string (as of ipython 2.0 this is the same is notebook-name).

`ein:$notebook-notebook-path' : string
  Path to notebook.

`ein:$notebook-kernel' : `ein:$kernel'
  `ein:$kernel' instance.

`ein:$notebook-kernelspec' : `ein:$kernelspec'
  Jupyter kernel specification for the notebook.

`ein:$notebook-kernelinfo' : `ein:kernelinfo'
  `ein:kernelinfo' instance.

`ein:$notebook-pager'
  Variable for `ein:pager-*' functions. See ein-pager.el.

`ein:$notebook-dirty' : boolean
  Set to `t' if notebook has unsaved changes.  Otherwise `nil'.

`ein:$notebook-metadata' : plist
  Notebook meta data (e.g., notebook name).

`ein:$notebook-name' : string
  Notebook name.

`ein:$notebook-nbformat' : integer
  Notebook file format version.

`ein:$notebook-nbformat-minor' : integer
  Notebook file format version.

`ein:$notebook-events' : `ein:$events'
  Event handler instance.

`ein:$notebook-worksheets' : list of `ein:worksheet'
  List of worksheets.

`ein:$notebook-scratchsheets' : list of `ein:worksheet'
  List of scratch worksheets.

`ein:$notebook-api-version' : integer
   Major version of the IPython notebook server we are talking to.
"
  url-or-port
  notebook-id ;; In IPython-2.0 this is "[:path]/[:name].ipynb"
  notebook-path
  kernel
  kernelinfo
  kernelspec
  pager
  dirty
  metadata
  notebook-name
  nbformat
  nbformat-minor
  events
  worksheets
  scratchsheets
  api-version)


;;; Worksheet
(defclass ein:worksheet ()
  ((nbformat :initarg :nbformat :type integer)
   (notebook-path :initarg :notebook-path :type function
		  :accessor ein:worksheet--notebook-path)
   (saved-cells :initarg :saved-cells :initform nil
                :accessor ein:worksheet--saved-cells
                :documentation
                "Slot to cache cells for worksheet without buffer")
   (dont-save-cells :initarg :dont-save-cells :initform nil :type boolean
                    :accessor ein:worksheet--dont-save-cells-p
                    :documentation "Don't cache cells when this flag is on.")
   (ewoc :initarg :ewoc :type ewoc :accessor ein:worksheet--ewoc)
   (kernel :initarg :kernel :type ein:$kernel :accessor ein:worksheet--kernel)
   (dirty :initarg :dirty :type boolean :initform nil :accessor ein:worksheet--dirty-p)
   (metadata :initarg :metadata :initform nil :accessor ein:worksheet--metadata)
   (events :initarg :events :accessor ein:worksheet--events)))

;;; Kernel
(cl-defstruct ein:$kernelspec
  "Kernel specification as return by the Jupyter notebook server.

`ein:$kernelspec-name' : string
  Name used to identify the kernel (like python2, or python3).

`ein:$kernelspec-display-name' : string
  Name used to display kernel to user.

`ein:$kernelspec-language' : string
  Programming language supported by kernel, like 'python'.

`ein:$kernelspec-resources' : plist
  Resources, if any, used by the kernel.

`ein:$kernelspec-spec' : plist
  How the outside world defines kernelspec:
  https://ipython.org/ipython-doc/dev/development/kernels.html#kernelspecs
"
  name
  display-name
  resources
  spec
  language)

(cl-defstruct ein:$kernel
  "Should be named ein:$session.  We glom session and kernel as
defined by the server as just ein:$kernel in the client."
  url-or-port
  path
  kernelspec
  events
  api-version
  session-id
  kernel-id
  shell-channel
  iopub-channel
  websocket                             ; For IPython 3.x+
  base-url                              ; /api/kernels/
  kernel-url                            ; /api/kernels/<KERNEL-ID>
  ws-url                                ; ws://<URL>[:<PORT>]
  username
  msg-callbacks
  oinfo-cache
  after-start-hook
  after-execute-hook)

;;; Cells

(defclass ein:basecell ()
  ((cell-type :initarg :cell-type :type string :accessor ein:cell-type)
   (read-only :initarg :read-only :initform nil :type boolean)
   (ewoc :initarg :ewoc :type ewoc :accessor ein:basecell--ewoc)
   (element :initarg :element :initform nil :type list
            :documentation "ewoc nodes")
   (element-names :initarg :element-names)
   (input :initarg :input :type string
          :documentation "Place to hold data until it is rendered via `ewoc'.")
   (outputs :initarg :outputs :initform nil :type list)
   (metadata :initarg :metadata :initform nil :type list :accessor ein:cell-metadata)
   (events :initarg :events :type ein:events)
   (cell-id :initarg :cell-id :initform (ein:utils-uuid) :type string
            :accessor ein:cell-id))
  "Notebook cell base class")

(defclass ein:codecell (ein:basecell)
  ((traceback :initform nil :initarg :traceback :type list)
   (cell-type :initarg :cell-type :initform "code")
   (kernel :initarg :kernel :type ein:$kernel :accessor ein:cell-kernel)
   (element-names :initform '(:prompt :input :output :footer))
   (input-prompt-number :initarg :input-prompt-number
                        :documentation "\
Integer or \"*\" (running state).
Implementation note:
Typed `:input-prompt-number' becomes a problem when reading a
notebook that saved "*".  So don't add `:type'!")
   (collapsed :initarg :collapsed :initform nil :type boolean)
   (running :initarg :running :initform nil :type boolean)))

(defclass ein:textcell (ein:basecell)
  ((cell-type :initarg :cell-type :initform "text")
   (element-names :initform '(:prompt :input :footer))))

(defclass ein:htmlcell (ein:textcell)
  ((cell-type :initarg :cell-type :initform "html")))

(defclass ein:markdowncell (ein:textcell)
  ((cell-type :initarg :cell-type :initform "markdown")))

(defclass ein:rawcell (ein:textcell)
  ((cell-type :initarg :cell-type :initform "raw")))

;;; Notifications

(defclass ein:notification-status ()
  ((status :initarg :status :initform nil)
   (message :initarg :message :initform nil)
   (s2m :initarg :s2m))
  "Hold status and its string representation (message).")

(defclass ein:notification-tab ()
  ((get-list :initarg :get-list :type function)
   (get-current :initarg :get-current :type function))
  ;; These "methods" are for not depending on what the TABs for.
  ;; Probably I'd want change this to be a separated Emacs lisp
  ;; library at some point.
  "See `ein:notification-setup' for explanation.")

(defclass ein:notification ()
  ((buffer :initarg :buffer :type buffer :document "Notebook buffer")
   (tab :initarg :tab :type ein:notification-tab)
   (execution-count
    :initform "y" :initarg :execution-count
    :documentation "Last `execution_count' sent by `execute_reply'.")
   (notebook
    :initarg :notebook
    :initform
    (ein:notification-status
     "NotebookStatus"
     :s2m
     '((notebook_saving.Notebook       . "Saving notebook...")
       (notebook_saved.Notebook        . "Notebook saved")
       (notebook_save_failed.Notebook  . "Failed saving notebook!")))
    :type ein:notification-status)
   (kernel
    :initarg :kernel
    :initform
    (ein:notification-status
     "KernelStatus"
     :s2m
     '((status_idle.Kernel . nil)
       (status_busy.Kernel . "Kernel busy...")
       (status_restarting.Kernel . "Kernel restarting...")
       (status_restarted.Kernel . "Kernel restarted")
       (status_dead.Kernel . "Kernel requires restart \\<ein:notebook-mode-map>\\[ein:notebook-restart-session-command-km]")
       (status_reconnecting.Kernel . "Kernel reconnecting...")
       (status_reconnected.Kernel . "Kernel reconnected")
       (status_disconnected.Kernel . "Kernel requires reconnect \\<ein:notebook-mode-map>\\[ein:notebook-reconnect-session-command-km]")))
    :type ein:notification-status))
  "Notification widget for Notebook.")

;;; Events

(defclass ein:events ()
  ((callbacks :initarg :callbacks :type hash-table
              :initform (make-hash-table :test 'eq)))
  "Event handler class.")


(provide 'ein-classes)

;;; ein-classes.el ends here