(begin-module 'enum)
(module-export '(
  new
  make
  extend
  foreach
  find-name
  find-value
  max-value
))


define new(name)
  assert {symbol? name} "enum name should be a symbol"
  assert {false? ::SYSTEM:global?(name (::SYSTEM:current-namespace))}
    string-append("duplicate enum: " symbol->string(name))
  define nspace ::namespace:new(name)
  ::SYSTEM:global-set-r/o! name nspace (::SYSTEM:current-namespace)
  nspace


define max-value(nspace)
  if ::SYSTEM:global?('_MAX_VALUE_ nspace)
    ::SYSTEM:global-ref('_MAX_VALUE_ nspace)
    0

define extend
  case-lambda
    (nspace list)
      extend nspace list max-value(nspace)
    (nspace list idx)
      define unq-val(val)
        if {{pair? val} and {car(val) eq? 'quote}}
          cadr(val)
          val
      define item-name
      while {pair? list}
        {item-name := car(list)}
        if {not {symbol? item-name}} error("enum element should be a symbol")
        {list := cdr(list)}
        if {lax-car(list) eq? '=}
          then
            {idx := unq-val(cadr(list))}
            if {not {integer? idx}} error("enum element index should be a fixnum")
            {list := cddr(list)}
        if {not {item-name eq? '_}}
          then
            ::SYSTEM:global-set-r/o! item-name idx nspace
            ::SYSTEM:global-export-r/o item-name nspace
        inc! idx
      ::SYSTEM:global-set-r/o! `_MAX_VALUE_ idx nspace
      ::SYSTEM:global-export-r/o `_MAX_VALUE_ nspace
      idx

;; return first unused index
;; `_` name means "do not create this element"
;; `name = value` means "restart at this value".
;; note that `value` should be a number (possibly a quoted one).
;; automatically sets "_MAX_VALUE_"
define make
  case-lambda
    (name list)
      extend new(name) list 0
    (name list idx)
      extend new(name) list idx


;; proc: (name value)
define foreach(nspace proc)
  define it ::namespace:iter:new(nspace)
  while ::namespace:iter:next(it)
    if {not {::namespace:iter:name(it) eq? '_MAX_VALUE_}}
      proc ::namespace:iter:name(it) ::namespace:iter:value(it)


;; return (name . value) or #f
define find-name(nspace name)
  if ::SYSTEM:global?(name nspace)
    cons name ::SYSTEM:global-ref(name nspace)
    #f


;; return name symbol or #f
define find-value(nspace value)
  define it ::namespace:iter:new(nspace)
  while ::namespace:iter:next(it)
    if {not {::namespace:iter:name(it) eq? '_MAX_VALUE_}}
      if {::namespace:iter:value(it) = value}
        ::SYSTEM:return ::namespace:iter:name(it)
  #f

(end-module 'enum)

#|
(enum:make 'etest '(a b c d = 69 e _ f))
(enum:foreach etest (lambda (key value) (printf "key=%o; value=%o\n" key value)))
(printf "max=%o\n" (enum:max-value etest))
(enum:extend etest '(more0 more1 more666 = 666))
(enum:foreach etest (lambda (key value) (printf "key=%o; value=%o\n" key value)))
(printf "max=%o\n" (enum:max-value etest))

(printf "res=%o\n" (enum:find-name etest 'b))
(printf "res=%o\n" (enum:find-name etest 'z))

(printf "res=%o\n" (enum:find-value etest 69))
(printf "res=%o\n" (enum:find-value etest 42))
|#
