;; POV-Mode.el -- A major mode for editing POV-Ray source files.
;;
;; Author: Zastai <zastai@hotmail.com>
;; Keywords: languages raytracing
;;

;;; Commentary:

;; Features: 
;;   * Fontification (with distinction between v2.x and v3.x keywords)
;;   * Ability to render the current buffer.
;;   * Ability to get help about the word at point.
;;
;; Wish list:
;;   * Huge wish: Syntax checking (ay caramba!)
;;   * Smaller wish: indentation a la cc-mode 
;;      (still a bit too complicated for a newbie like me)
;;
;; Associate .pov and .inc files with this mode.

;;; Code:

(require 'thingatpt)
(require 'font-lock)

;; We need custom (or our own version of it)
;; This bit of code was shamefully stolen from po-mode.el
(eval-and-compile
  (condition-case ()
      (require 'custom)
    (error nil))
  (if (and (featurep 'custom) (fboundp 'custom-declare-variable))
      nil
    (defmacro defgroup (&rest args)
      nil)
    (defmacro defcustom (var value doc &rest args)
      (` (defvar (, var) (, value) (, doc))))))

(defgroup pov-mode nil
  "Major mode for editing source code for the raytracer POV-Ray."
  :tag "POV-Ray Mode"
  :group 'languages
)

(defcustom pov-mode-hook nil
  "*Hook run at startup by `pov-mode'."
  :type 'hook
  :tag "POV-Ray Mode Hook"
  :group 'pov-mode
  )

(defcustom pov-pre-render-hook nil
  "*Hook run by `pov-render' before it calls the renderer."
  :type 'hook
  :tag "POV-Ray Pre-Render Hook"
  :group 'pov-mode
  )

(defcustom pov-post-render-hook nil
  "*Hook run by `pov-render' after the renderer returns."
  :type 'hook
  :tag "POV-Ray Post-Render Hook"
  :group 'pov-mode
  )

(defcustom pov-include-path-v2 nil
  "*Path where include files (like colors.inc) for POV-Ray 2.x reside.
Nil means that no include files exist/are necessary."
  :type '(repeat (directory :tag "Include"))
  :tag "POV-Ray Include Path [2.x]"
  :group 'pov-mode
)

(defcustom pov-include-path-v3 nil
  "*Path where include files (like colors.inc) for POV-Ray 3.x reside.
Nil means that no include files exist/are necessary.
`pov-include-path-v2', if set, will also be searched."
  :type '(repeat (directory :tag "Include"))
  :tag "POV-Ray Include Path [3.x]"
  :group 'pov-mode
)

(defcustom pov-renderer-v2 nil
  "*Program used for rendering version 2.x scenes.
Nil means no usable renderer exists; if a renderer is set for version 3.x
 scenes, it will be tried using the version compatibility option."
  :type '(choice (const :tag "None" nil)
	  (file :tag "Program" :value "povray"))
  :tag "POV-Ray Rendering Program [v2.x]"
  :group 'pov-mode
)

(defcustom pov-renderer-v3 nil
  "*Program used for rendering version 3.x scenes.
Nil means no usable renderer exists."
  :type '(choice (const :tag "None" nil)
	  (file :tag "Program" :value "povray"))
  :tag "POV-Ray Rendering Program [v3.x]"
  :group 'pov-mode
)

(defcustom pov-renderer-options-v2 nil
  "*Options to pass to the raytrace engine when rendering version 2.x scenes.
Note that `pov-render' gets the type and name of the output file from the
 variables `pov-output-file-type' and `pov-output-file-name', respectively."
  :type '(repeat (string :tag "Option"))
  :tag "POV-Ray Render Options [v2.x]"
  :group 'pov-mode
)

(defcustom pov-renderer-options-v3 nil
  "*Options to pass to the raytrace engine when rendering version 3.x scenes.
These can be command-line options like '+Q9', or .ini file statements (such
 as 'Quality = 9'.
Note that `pov-render'  gets the type and name of the output file from the
 variables `pov-output-file-type' and `pov-output-file-name', respectively."
  :type '(repeat (string :tag "Option"))
  :tag "POV-Ray Render Options [v3.x]"
  :group 'pov-mode
)

(defcustom pov-helper-v2 nil
  "*Program used to get help for version 2.x of POV-Ray.
Nil means no such program is available."
  :type '(choice (const :tag "None" nil)
	  (file :tag "Program" :value "povhelp"))
  :tag "POV-Ray Help Program [v2.x]"
  :group 'pov-mode
)

(defcustom pov-helper-v3 nil
  "*Program used to get help for version 3.x of POV-Ray.
Nil means no such program is available."
  :type '(choice (const :tag "None" nil)
	  (file :tag "Program" :value "povhelp"))
  :tag "POV-Ray Help Program [v3.x]"
  :group 'pov-mode
)

(defcustom pov-output-file-type t
  "*Character identifying the file type used for the rendered output.
Used as an argument to POV-Ray's +F option. If nil, don't do file output.
If t, use the compilation default.
For a list of available types, consult the POV-Ray documentation."
  :type '(choice (const  :tag "No File Output" nil)
		 (const  :tag "POV-Ray Compilation Default" t)
		 (string :tag "User-Defined" :value "T"))
  :tag "POV-Ray Type Of Output File"
  :group 'pov-mode
)

(defcustom pov-output-directory nil
  "*Directory to place rendered scenes in.
If nil, use the default directory (ie the current directory or the
 directory specified in povray.ini).
If no directory is specified here or elsewhere in POV-Ray's options, the
 output file will probably end up in `pov-temporary-directory'."
  :type '(choice (const     :tag "Default Directory" nil)
		 (directory :tag "Fixed Location" :value "~/Scenes/"))
  :tag "POV-Ray Output Directory"
  :group 'pov-mode
)

(defcustom pov-output-filename nil
  "*Filename used for rendered scenes.
To prevent confusion, its extension should match the file type set by
 `pov-output-file-type'.
If nil, let POV-Ray decide the filename."
  :type '(choice (const :tag "Let POV-Ray decide" nil)
		 (file  :tag "Fixed Name" :value "scene.out"))
  :tag "POV-Ray Output File Name"
  :group 'pov-mode
)

(defcustom pov-default-version 3
  "*Version of POV-Ray you are using.
Use `pov-set-version' to switch version modes."
  :type '(choice (const :tag "POV-Ray v2.x" 2)
		 (const :tag "POV-Ray v3.x" 3))
  :tag "POV-Ray Version"
  :group 'pov-mode
)

(defcustom pov-output-buffer-kb 128
  "*Size, in kilobytes, of the file output buffer when rendering scenes.
Note that this ALWAYS overrides any default value."
  :type '(choice (const   :tag "None             "  nil)
		 (const   :tag "Default     (128)"  128)
		 (const   :tag "Large       (256)"  256)
		 (const   :tag "Very Large  (512)"  512)
		 (const   :tag "Huge       (1024)" 1024)
		 (integer :tag "Custom Size"  :value 0))
  :tag "POV-Ray Output-Buffer Size"
  :group 'pov-mode
)

(defcustom pov-temporary-directory nil
  "*Name of the temporary directory where temporary files are placed."
  :type '(choice (const     :tag "Default"          nil)
		 (directory :tag "Custom Directory" "~/.POV-Ray.tmp/"))
  :tag "POV-Ray Temporary Directory"
  :group 'pov-mode
)

(defcustom pov-temporary-ini-file nil
  "*Name of the temporary ini/def file used to render the scene.
Note that this is ALWAYS created in `pov-temporary-directory', and is
 ALWAYS removed after rendering is complete."
  :type '(choice (const :tag "Default" nil)
		 (file  :tag "Custom File" "_emacs_.ini"))
  :tag "POV-Ray Temporary Definition File"
  :group 'pov-mode
)

(defcustom pov-temporary-output-file "_emacs_.out"
  "*Name of the temporary file used to hold output from the renderer.
Note that this is ALWAYS created in `pov-temporary-directory', and is
 ALWAYS removed after rendering is complete (although its contents will be
 piped into the '*POV-Ray Output*' buffer."
  :type '(choice (const :tag "Default"     nil)
		 (file  :tag "Custom File" :value "_emacs_.out"))
  :tag "POV-Ray Temporary Output File"
  :group 'pov-mode
)

(defvar pov-mode-syntax-table nil
  "The syntax table that is used in POV-Mode.")
(if pov-mode-syntax-table ()
  (setq pov-mode-syntax-table (make-syntax-table))
  (modify-syntax-entry ?_ "w" pov-mode-syntax-table)
  (modify-syntax-entry ?/ ". 124b" pov-mode-syntax-table)
  (modify-syntax-entry ?* ". 23" pov-mode-syntax-table)
  (modify-syntax-entry ?\n "> b" pov-mode-syntax-table)
)

(defvar pov-mode-map nil
  "The keymap and menus used in POV-Mode.")
(if pov-mode-map ()
  (setq pov-mode-map (make-sparse-keymap))
  
  (define-key pov-mode-map "\C-c\C-h" 'pov-help)
  (define-key pov-mode-map "\C-c\C-r" 'pov-render)
  (define-key pov-mode-map "\C-c\C-v" 'pov-set-version)

  (define-key pov-mode-map [menu-bar] (make-sparse-keymap))
  (define-key pov-mode-map [menu-bar pov-mode] 
    (cons "POV-Ray" (make-sparse-keymap "POV-Ray")))
  (define-key pov-mode-map [menu-bar pov-mode help] 
    '("Run POV-Help" . pov-help))
  (define-key pov-mode-map [menu-bar pov-mode render] 
    '("Render Scene" . pov-render))
  (define-key pov-mode-map [menu-bar pov-mode version] 
    '("Change Version" . pov-set-version))
)

(defconst pov-keywords-v2
  (concat ""
	  ;; A -- adaptive agate agate_turb all alpha ambient area_light
	  "a\\(daptive\\|gate\\(_turb\\|\\)\\|l\\(l\\|pha\\)\\|"
	  "mbient\\|rea_light\\)\\|"
	  ;; B -- background bicubic_patch blob blue bounded_by box bozo
	  ;;      brilliance bumps bump_map bump_size
	  "b\\(ackground\\|icubic_patch\\|l\\(ob\\|ue\\)\\|o\\(unded_by\\|"
	  "x\\|zo\\)\\|rilliance\\|ump\\(s\\|_map\\|_size\\)\\)\\|"
	  ;; C -- camera checker clipped_by clock colo(u)r colo(u)r_map
	  ;;      component composite cone crand cubic cylinder
	  "c\\(amera\\|hecker\\|l\\(ipped_by\\|ock\\)\\|o\\(l\\(o[u]?r"
	  "\\(_map\\|\\)\\)\\|mpo\\(nent\\|site\\)\\|ne\\)\\|rand\\|ubic\\|"
	  "ylinder\\)\\|"
	  ;; D -- default dents difference diffuse direction disc distance dump
	  "d\\(ents\\|i\\(ff\\(erence\\|use\\)\\|rection\\|s\\(c\\|tance\\)\\)"
	  "\\|ump\\)\\|"
	  ;; E -- (none)
	  ""
	  ;; F -- falloff filter finish flatness fog frequency
	  "f\\(alloff\\|i\\(lter\\|nish\\)\\|latness\\|og\\|requency\\)\\|"
	  ;; G -- gif gradient granite green
	  "g\\(if\\|r\\(a\\(dient\\|nite\\)\\|een\\)\\)\\|"
	  ;; H -- height_field hexagon
	  "he\\(ight_field\\|xagon\\)\\|"
	  ;; I -- iff image_map interpolate intersection inverse ior
	  "i\\(ff\\|mage_map\\|n\\(ter\\(polate\\|section\\)\\|verse\\)\\|"
	  "or\\)\\|"
	  ;; J -- jitter
	  "jitter\\|"
	  ;; K -- (none)
	  ""
	  ;; L -- lambda leopard light_source location looks_like look_at
	  "l\\(ambda\\|eopard\\|ight_source\\|o\\(cation\\|ok\\(s_like\\|"
	  "_at\\)\\)\\)\\|"
	  ;; M -- mandel map_type marble material_map merge metallic
	  "m\\(a\\(ndel\\|p_type\\|rble\\|terial_map\\)\\|e\\(rge\\|"
	  "tallic\\)\\)\\|"
	  ;; N -- normal no_shadow
	  "no\\(rmal\\|_shadow\\)\\|"
	  ;; O -- object octaves omega once onion open
	  "o\\(bject\\|ctaves\\|mega\\|n\\(ce\\|ion\\)\\|pen\\)\\|"
	  ;; P -- phase phong phong_size pigment plane point_at poly pot
	  "p\\(h\\(ase\\|ong\\(_size\\|\\)\\)\\|igment\\|lane\\|o\\("
	  "int_at\\|ly\\|t\\)\\)\\|"
	  ;; Q -- quadric quartic quick_colo(u)r
	  "qu\\(a\\(dric\\|rtic\\)\\|ick_colo[u]?r\\)\\|"
	  ;; R -- radial radius raw red reflection refraction rgb rgbf right
	  ;;      ripples rotate roughness
	  "r\\(a\\(di\\(al\\|us\\)\\|w\\)\\|e\\(d\\|f\\(lection\\|raction\\)"
	  "\\)\\|gb\\(f\\|\\)\\|i\\(ght\\|pples\\)\\|o\\(tate\\|ughness\\)"
	  "\\)\\|"
	  ;; S -- scale sky smooth smooth_triangle specular sphere spotlight
	  ;;      spotted sturm
	  "s\\(cale\\|ky\\|mooth\\(_triangle\\|\\)\\|p\\(ecular\\|here\\|"
	  "ot\\(ted\\|light\\)\\)\\|turm\\)\\|"
	  ;; T -- texture tga threshold tightness tile2 tiles torus translate
	  ;;      triangle turbulence type
	  "t\\(exture\\|ga\\|hreshold\\|i\\(ghtness\\|le[2s]\\)\\|orus\\|"
	  "r\\(anslate\\|iangle\\)\\|urbulence\\|ype\\)\\|"
	  ;; U -- union up use_colo(u)r use_index u_steps
	  "u\\(nion\\|p\\|se_\\(colo[u]?r\\|index\\)\\|_steps\\)\\|"
	  ;; V -- v_steps, W -- waterlevel waves wood wrinkles
	  "v_steps\\|w\\(a\\(ter_level\\|ves\\)\\|ood\\|rinkles\\)\\|"
	  ;; X -- x, Y -- y, Z -- z
	  "[xyz]"
  )
  "Regexp which expands to all keywords used in POV-Ray 2.x:

A -- adaptive agate agate_turb all alpha ambient area_light
B -- background bicubic_patch blob blue bounded_by box bozo
     brilliance bumps bump_map bump_size
C -- camera checker clipped_by clock colo(u)r colo(u)r_map
     component composite cone crand cubic cylinder
D -- dents difference diffuse direction disc distance dump
E -- (none)
F -- falloff filter finish flatness fog frequency
G -- gif gradient granite green
H -- height_field hexagon
I -- iff image_map interpolate intersection inverse ior
J -- jitter
K -- (none)
L -- lambda leopard light_source location looks_like look_at
M -- mandel map_type marble material_map merge metallic
N -- normal no_shadow
O -- object octaves omega once onion open
P -- phase phong phong_size pigment plane point_at poly pot
Q -- quadric quartic quick_colo(u)r
R -- radial radius raw red reflection refraction rgb rgbf right
     ripples rotate roughness
S -- scale sky smooth smooth_triangle specular sphere spotlight
     spotted sturm
T -- texture tga threshold tightness tile2 tiles torus translate
     triangle turbulence type
U -- union up use_colo(u)r use_index u_steps
V -- v_steps
W -- water_level waves wood wrinkles
X -- x
Y -- y
Z -- z
")

(defconst pov-keywords-v3
  (concat pov-keywords-v2
	  ;; A
	  "\\|a\\(a_\\(level\\|threshold\\)\\|dc_bailout\\|mbient_light\\|"
	  "ngle\\|perture\\|rc_angle\\|ssumed_gamma\\|t\\(mospher\\(e\\|"
	  "ic_attenuation\\)\\|tenuating\\)\\|verage\\)\\|"
	  ;; B
	  "b\\(l\\(ack_hole\\|ur_samples\\)\\|ox_mapping\\|r\\(ick\\("
	  "_size\\|\\)\\|ightness\\)\\|umpy[123]\\)\\|"
	  ;; C
	  "c\\(austics\\|o\\(n\\(fidence\\|ic_sweep\\|stant\\|trol[01]\\)\\|"
	  "unt\\)\\|rackle\\|ub\\(e\\|ic_spline\\)\\|ylindrical_mapping\\)\\|"
	  ;; D
	  "d\\(istance_maximum\\|ust\\(_type\\|\\)\\)\\|"
	  ;; E
	  "e\\(ccentricity\\|mitting\\|rror_bound\\|xponent\\)\\|"
	  ;; F
	  "f\\(a\\(de_\\(distance\\|power\\)\\|l\\(loff_angle\\|se\\)\\)\\|"
	  "isheye\\|lip\\|o\\(cal_point\\|g_\\(alt\\|offset\\|type\\)\\)\\)\\|"
	  ;; G
	  "g\\(lo\\(bal_settings\\|wing\\)\\|ray_threshold\\)\\|"
	  ;; H
	  "h\\(alo\\|f_gray_16\\|ierarchy\\|ollow\\|ypercomplex\\)\\|"
	  ;; I
	  "i\\(ncidence\\|rid\\(_wavelength\\|\\)\\)\\|"
	  ;; J
	  "julia_fractal\\|"
	  ;; L
	  "l\\(athe\\|inear\\(_s\\(pline\\|weep\\)\\|\\)\\|ow_error_factor\\)"
	  "\\|"
	  ;; M
	  "m\\(a\\(trix\\|x_\\(i\\(ntersections\\|teration\\)\\|trace_level\\|"
	  "value\\)\\)\\|esh\\|inimum_reuse\\|ortar\\)\\|"
	  ;; N
	  "n\\(earest_count\\|o\\(rmal_map\\|\\)\\|umber_of_waves\\)\\|"
	  ;; O
	  "o\\(ff\\(set\\|\\)\\|mnimax\\|n\\|trhographic\\)\\|"
	  ;; P
	  "p\\(a\\(noramic\\|ttern[123]\\)\\|erspective\\|gm\\|i\\(gment_map"
	  "\\|\\)\\|lanar_mapping\\|ng\\|olygon\\|pm\\|r\\(ecision\\|ism\\)"
	  "\\|wr\\)\\|"
	  ;; Q
	  "qu\\(a\\(dratic_spline\\|ternion\\)\\|ilted\\)\\|"
	  ;; R
	  "r\\(a\\(diosity\\|inbow\\|mp_wave\\)\\|e\\(c\\(iprocal\\|"
	  "ursion_limit\\)\\|peat\\)\\|gb[f]?t\\)\\|"
	  ;; S
	  "s\\(amples\\|ca\\(llop_wave\\|ttering\\)\\|hadowless\\|"
	  "ine_wave\\|ky_sphere\\|l\\(ice\\|ope_map\\)\\|or\\|p\\("
	  "herical_mapping\\|iral[12]?\\)\\|trength\\|uper_ellipsoid\\|"
	  "ys\\)\\|"
	  ;; T
	  "t\\(e\\(st_camera_[1234]\\|xt\\(ure_map\\|\\)\\)\\|hickness\\|r\\("
	  "a\\(ck\\|ns\\(form\\|mit\\)\\)\\|iangle_wave\\|ue\\)\\|tf\\|"
	  "urb_depth\\|\\)\\|"
	  ;; U
	  "u\\(ltra_wide_angle\\|\\)\\|"
	  ;; V
	  "v\\(ariance\\|ol\\(ume_\\(object\\|rendered\\)\\|_with_light\\)\\|"
	  "\\)\\|"
	  ;; W
	  "w\\(arp\\|idth\\)\\|"
	  ;; Y
	  "yes"
  )
  "Regexp which expands to all keywords used in POV-Ray 3.x:

A -- aa_level aa_threshold adaptive adc_bailout agate agate_turb all alpha
     ambient ambient_light angle aperture arc_angle area_light assumed_gamma
     atmosphere atmospheric_attenuation attenuating average
B -- background bicubic_patch blob black_hole blue blur_samples bounded_by 
     box box_mapping bozo brick brick_size brightness brilliance bumps 
     bumpy1 bumpy2 bumpy3 bump_map bump_size
C -- camera caustics checker clipped_by clock colo(u)r colo(u)r_map component
     composite cone confidence conic_sweep constant control0 control1 count
     crackle crand cube cubic cubic_spline cylinder cylindrical_mapping
D -- dents difference diffuse direction disc distance distance_maximum dump
     dust dust_type
E -- eccentricity emitting error_bound exponent
F -- fade_distance fade_power falloff falloff_angle false filter finish
     fisheye flatness focal_point fog fog_alt fog_offset fog_type frequency
G -- gif global_settings glowing gradient granite gray_threshold green
H -- halo height_field hexagon hf_gray_16 hierarchy hollow hypercomplex
I -- iff image_map incidence interpolate intersection inverse ior irid
     irid_wavelength
J -- jitter julia_fractal
K -- (none)
L -- lambda lathe leopard light_source linear linear_spline linear_sweep
     location looks_like look_at low_error_factor
M -- mandel map_type marble material_map matrix max_intersections max_iteration
     max_trace_level max_value merge mesh metallic minimum_reuse mortar
N -- nearest_count no normal normal_map no_shadow
O -- object octaves off offset omega omnimax on once onion open orthographic
P -- panoramic pattern1 pattern2 pattern3 perspective pgm phase phong
     phong_size pi pigment pigment_map planar_mapping plane png point_at poly
     polygon pot ppm precision prism pwr
Q -- quadratic_spline quadric quartic quaternation quick_colo(u)r quilted
R -- radial radiosity radius rainbow ramp_wave raw reciprocal recursion_limit
     red reflection refraction repeat rgb rgbf rgbft rgbt right ripples rotate
     roughness
S -- samples scale scallop_wave scattering shadowless sine_wave sky sky_sphere
     slice slope_map smooth smooth_triangle sor specular sphere
     spherical_mapping spiral spiral1 spiral2 spotlight spotted strength sturm
     superellipsoid sys
T -- t test_camera_1 test_camera_2 test_camera_3 test_camera_4 text texture
     texture_map tga thickness threshold tightness tile2 tiles torus track
     transform translate transmit triangle triangle_wave true ttf turbulence
     turb_depth type
U -- u ultra_wide_angle union up use_colo(u)r use_index u_steps
V -- v variance volume_object volume_rendered vol_with_light v_steps
W -- warp water_level waves width wood wrinkles
X -- x
Y -- y yes
Z -- z
")

(defconst pov-directives-v2
  (concat "\\(de\\(clare\\|fault\\)\\|include\\|max_\\(intersections\\|"
	  "trace_level\\)\\|version\\)"
  )
  "Regexp which expands to all directives used in POV-Ray 2.x:
#declare
#default
#include
#max_intersections
#max_trace_level
#version

Note: The `#' is optional.
")

(defconst pov-directives-v3
  (concat "\\(break\\|case\\|de\\(bug\\|clare\\|fault\\)\\|"
	  "e\\(lse\\|nd\\|rror\\)\\|fatal\\|i\\(f\\(def\\|\\)\\|nclude\\)\\|"
	  "max_\\(intersections\\|trace_level\\)\\|r\\(ange\\|ender\\)\\|"
	  "s\\(tatistics\\|witch\\)\\|version\\|warning\\)"
  )
  "Regexp which expands to all directives used in POV-Ray 3.x:

#break               #case
#debug               #declare
#default             #else
#end                 #error
#fatal               #if
#ifdef               #include
#range               #render
#statistics          #switch
#version             #warning
#while
#max_intersections  | these should not be used
#max_trace_level    |   (they should be in global_settings)

Note that unlike in version 2.x, the '#' isn't really optional; although
 omitting it only generates a warning, it is likely to disrupt further parsing.
")

(defconst pov-functions
  (concat ""
	  "a\\(bs\\|cos[h]?\\|sc\\|sin[h]?\\|tan[2h]?\\)\\|"
	  "c\\(eil\\|hr\\|o\\(ncat\\|s[h]?\\)\\)\\|"
	  "d\\(egrees\\|iv\\)\\|"
	  "exp\\|"
	  "f\\(ile_exists\\|loor\\)\\|"
	  "int\\|"
	  "log\\|"
	  "m\\(ax\\|in\\|od\\)\\|"
	  "pow\\|"
          "radians\\|"
	  "s\\(in[h]?\\|qr[t]\\|tr\\(cmp\\|len\\|lwr\\|upr\\|\\)\\|ubstr\\)\\|"
	  "tan[h]?\\|"
	  "v\\(al\\|axis_rotate\\|cross\\|dot\\|length\\|normalize\\|rotate\\)"
  )
  "Regexp which expands to all functions introduced in version 3.0 of POV-Ray:

A - abs, acos, acosh, asc, asin, asinh, atan, atanh, atan2
C - ceil, chr, concat, cos, cosh
D - degrees, div
E - exp
F - file_exists, floor
I - int
L - log
M - max, min, mod
P - pow
R - radians
S - sin, sinh, sqr, sqrt, str, strcmp, strlen, strlwr, strupr, substr
T - tan, tanh
V - val, vaxis_rotate, vcross, vdot, vlength, vnormalize, vrotate
")

(defconst pov-font-lock-keywords-v2
  (list
   ;; Assignments
   '("\\([^ \t\n]*\\)[ \t\n]*=" 1 font-lock-variable-name-face)
   ;; Brackets are functions :)
   '("[{}()<>]\\|\\[\\|\\]" . font-lock-function-name-face)
   ;; Directives
   (cons (concat "^\\([ \t]*#\\)?[ \t]*" pov-directives-v2)
	 '((2 font-lock-reference-face)))
   (cons (concat "\\<\\(" pov-keywords-v2 "\\)\\>") 
	 'font-lock-keyword-face)
   )
"List of expressions to fontify in POV-Mode for POV-Ray version 2.x.")

(defconst pov-font-lock-keywords-v3
  (list
   ;; Assignments
   '("\\([^ \t\n]*\\)[ \t\n]*[\+-/\*]?=" 1 font-lock-variable-name-face)
   ;; Brackets are functions :)
   '("[{}()<>]\\|\\[\\|\\]" . font-lock-function-name-face)
   
   ;; Directives
   (cons (concat "^[ \t]*#[ \t]*" pov-directives-v3)
	 '((1 font-lock-reference-face)))
   ;; Keywords
   (cons (concat "\\<\\(" pov-keywords-v3 "\\)\\>") 
	 'font-lock-keyword-face)
   ;; Functions
   (cons (concat "\\<\\(" pov-functions "\\)\\>(.*)") 
	 'font-lock-function-name-face)
   )
"List of expressions to fontify in POV-Mode for POV-Ray version 3.x.")

(defvar pov-renderer nil
  "Used internally by `pov-mode'.
Do not set this variable yourself; set `pov-renderer-v2' and 
 `pov-renderer-v3' instead.")

(defvar pov-renderer-options nil
  "Used internally by `pov-mode'.
Do not set this variable yourself; set `pov-renderer-options-v2' and 
 `pov-renderer-options-v3' instead.")

(defvar pov-include-path nil
  "Used internally by `pov-mode'.
Do not set this variable yourself; set `pov-include-path-v2' and 
 `pov-include-path-v3' instead.")

(defvar pov-helper nil
  "Used internally by `pov-mode'.
Do not set this variable yourself; set `pov-helper-v2' and 
 `pov-helper-v3' instead.")

(defvar pov-version pov-default-version
  "*Version of POV-Ray you are using.
Use `pov-set-version' to switch version modes.")

;;;###autoload
(defun pov-mode ()
  "Major mode for editing POV-Ray source code.
The hook variable `pov-mode-hook' is run at startup.

Key Bindings for POV-Mode:
\\{pov-mode-map}
"
  (interactive)
  (kill-all-local-variables)
  (set-syntax-table pov-mode-syntax-table)
  (use-local-map pov-mode-map)
  (make-local-variable 'font-lock-defaults)
  (make-local-variable 'pov-version)
  (let ((version (get 'pov-version 'new-version-set)))
    (if (not version)
	(save-excursion
	  (goto-char (point-min))
	  ;; Look for "#version x.y'
	  (if (re-search-forward
	       "^[ 	]*#[ 	]*version[ 	]+\\([0-9]+\\)\\.[0-9]+" nil t)
	      (setq pov-version (string-to-number (match-string 1)))
	    (setq pov-version pov-default-version)))
      (setq pov-version version)
      (put 'pov-version 'new-version-set nil)))
  (setq major-mode 'pov-mode mode-name 
	(concat "POV-Ray v" (number-to-string pov-version) ".x"))

  (make-local-variable 'pov-renderer)
  (make-local-variable 'pov-renderer-options)
  (make-local-variable 'pov-include-path)
  (make-local-variable 'pov-helper)
  (cond ((eq pov-version 2)
	 (setq font-lock-defaults '(pov-font-lock-keywords-v2))
	 (setq pov-include-path     pov-include-path-v2)
	 (setq pov-renderer-options pov-renderer-options-v2)
	 (setq pov-helper           pov-helper-v2)
	 (setq pov-renderer         pov-renderer-v2)
	 )
	((eq pov-version 3)
	 (setq font-lock-defaults '(pov-font-lock-keywords-v3))
	 (setq pov-include-path     pov-include-path-v3)
	 (setq pov-renderer-options pov-renderer-options-v3)
	 (setq pov-renderer         pov-renderer-v3)
	 (setq pov-helper           pov-helper-v3)
         )
  )
  ;; Add file's directory to include path
  (if (buffer-file-name)
      (setq pov-include-path
	    (append pov-include-path (make-list 1 (file-name-directory
						   (buffer-file-name))))))

  (make-local-variable 'comment-start)
  (make-local-variable 'comment-end)
  (make-local-variable 'comment-column)
  (make-local-variable 'comment-start-skip)
  (make-local-variable 'comment-multi-line)

  ;; minimize excessive tabbing
  (setq tab-width 2)

  (setq comment-start "// "
 	comment-end   ""
	comment-column 32
	comment-start-skip "/\\*+ *\\|// *"
 	comment-multi-line nil)
  (run-hooks 'pov-mode-hook)
)

(defun pov-help ()
  "Get help for the POV-Ray keyword at point.
Uses the help program as set in either `' or `', depending on what version
 of POV-Ray is being used."
  (interactive)
  ;; Only complain if it's not a string
  (if (eq pov-helper nil) (error "No POV-Ray help program set."))
  (unwind-protect
      (call-process pov-helper nil (get-buffer-create "*POV-Ray Output*")
		    nil
		    ;; Using the actual width as parameter causes serious
		    ;; trouble with the display
		    (concat "-h" (number-to-string (- (frame-height) 4)))
		    (concat "-w" (number-to-string (- (frame-width)  2)))
		    (if (eq (word-at-point) nil) "" (word-at-point)))
    (redraw-display)
    )
)

(defun pov-build-ini-file (scene ini out)
  "Used internally by `pov-render'. It builds a temporary .ini file, which
is then used to render the scene."
  (let ((buf (get-buffer-create "*POV-Ray Definition Buffer*"))
	(ver pov-version)              (bufsize pov-output-buffer-kb)
	(odir pov-output-directory)    (ofile pov-output-filename)
	(otype pov-output-file-type)
	(pathlist pov-include-path)    (options pov-renderer-options)
	)
    (set-buffer buf)
    (delete-region 1 (+ (buffer-size) 1))
    ;; I don't know if pov-ray 2.x supported comments in its definition files
    (if (eq ver 3)
	(insert ";; Temporary .ini file " ini " for scene " scene ?\n
		";; Generated by pov-mode.el -- DO NOT EDIT\n"))
    ;; User options go first so they don't override things
    (while options
      (insert (car-safe options) ?\n)
      (setq options (cdr-safe options)))
    ;; In- and Output
    (insert (if (eq ver 2) "+I" "Input_File_Name=") scene ?\n)
    (insert (if (eq ver 2) "+O" "OutPut_File_Name=")
	    (or odir "") (or ofile "") ?\n)
    (insert (if (eq ver 2) "+GA" "All_File=") out ?\n)
    ;; File Type
    (if (eq otype nil)
	(insert (if (eq ver 2) "-F" "Output_to_File=Off") ?\n)
      (insert (if (eq ver 2) "+F" "Output_to_File=On\n"))
      (insert (if (eq otype t) "" (concat "Output_File_Type=" otype "\n"))))
    ;; Buffer Size
    (if (> (or bufsize 0) 0)
	(insert (if (eq ver 2) "+B" "Buffer_Output=True\nBuffer_Size=")
		(number-to-string bufsize) ?\n)
      (insert (if (eq ver 2) "-B" "Buffer_Output=False") ?\n))
    ;; Library Paths
    (let ((counter 0))
      (while pathlist
	(insert (if (eq ver 2) "+L" "Library_Path=")
		(car-safe pathlist) ?\n)
	;; Only 10 paths are supported
	(setq counter (+ counter 1))
	(setq pathlist (if (< counter 10) (cdr-safe pathlist) nil))
      ))
    ;; Write it!
    (write-region 1 (+ (buffer-size) 1) ini)
  )
)

(defun pov-render ()
  "Renders the POV-Ray scene in the current buffer."
  (interactive)
  ;; For version 2, we can use the version 3 renderer.
  (if (eq pov-renderer-v3 nil) (error "No renderer available."))
  (let* ((dir (file-name-directory
	       (expand-file-name
		(or pov-temporary-directory "~/.POV-Ray.tmp/"))))
        (ini  (concat dir (file-name-nondirectory (or pov-temporary-ini-file
						      "_emacs_.ini"))))
	(out  (concat dir (file-name-nondirectory
			   (or pov-temporary-output-file "_emacs_.out"))))
	(scene (concat dir (file-name-nondirectory (buffer-file-name))))
	(buf   (get-buffer-create "*POV-Ray Output*"))
	(ver   pov-version) (renderer pov-renderer))
    (if (not (file-exists-p dir))
	(condition-case nil (make-directory dir) (error nil)))
    (if (not (file-exists-p dir))
	(error "Couldn't create temporary directory (%s)." dir))
    (if (file-exists-p ini) (error "Temporary file (%s) exists." ini))
    (if (file-exists-p out) (error "Temporary file (%s) exists." scene))
    (if (file-exists-p out) (error "Temporary file (%s) exists." out))
    (write-region 1 (+ (buffer-size) 1) scene)
    (pov-build-ini-file scene ini out)
    (run-hooks pov-pre-render-hook)
    (unwind-protect
	(if (and (eq ver 2) (eq renderer nil))
	    (call-process pov-renderer-v3 nil buf t "+MV2.0" ini)
	  ;; Do we need a param to specify it's a definition file?
	  (call-process renderer nil buf t ini))
      (redraw-display)
      (delete-file ini)
      (delete-file scene)
      (run-hooks pov-post-render-hook)
      (if (not (file-exists-p out)) ()
	(set-buffer buf)
	(insert-file-contents out nil nil nil t)
	(if (eq (buffer-size) 0) (message "Renderer returned without output.")
	  (switch-to-buffer buf)
	  (message "Rendering complete."))
	(delete-file out))
      (condition-case nil (delete-directory dir) (error nil)))
    )
  )

(defun pov-set-version (newversion)
"Set the version of POV-Ray you are using.
This function also refontifies the buffer to accomodate the syntax changes
and new/lost keywords.
"
  (interactive "NSwitch to version: (2 or 3) ")
  (if (not (eq major-mode 'pov-mode))
      (error "This only makes sense in a POV-Ray buffer!"))
  (if (not (or (eq newversion 2) (eq newversion 3)))
      (error "Only versions 2.x and 3.x are supported!"))
  (put 'pov-version 'new-version-set newversion)
  (pov-mode)
  (font-lock-fontify-buffer)
)

;;;###autoload
(defun pov-render-file (scenefile)
  "Renders a POV-Ray scene."
  (interactive "fScene file to render: ")
  (if (eq scenefile nil) (error "No scene file given."))
  (if (not (file-exists-p scenefile)) (error "Scene file doesn't exist."))
  (let ((buf (find-file-noselect scenefile)))
    (set-buffer buf)
    (pov-mode) ; Make sure the pov-* vars get set
    (unwind-protect (pov-render) (kill-buffer buf)))
)

(provide 'pov-mode)
