;; ==========================================================================
;; eval.sx — Reference SX evaluator written in SX
;;
;; This is the canonical specification of SX evaluation semantics.
;; A thin bootstrap compiler per target reads this file and emits
;; a native evaluator (JavaScript, Python, Rust, etc.).
;;
;; The evaluator is written in a restricted subset of SX:
;;   - defcomp, define, defmacro, lambda/fn
;;   - if, when, cond, case, let, do, and, or
;;   - map, filter, reduce, some, every?
;;   - Primitives: list ops, string ops, arithmetic, predicates
;;   - quote, quasiquote/unquote/splice-unquote
;;   - Pattern matching via (case (type-of expr) ...)
;;
;; Platform-specific concerns (DOM rendering, async I/O, HTML emission)
;; are declared as interfaces — each target provides its own adapter.
;; ==========================================================================


;; --------------------------------------------------------------------------
;; 1. Types
;; --------------------------------------------------------------------------
;;
;; The evaluator operates on these value types:
;;
;;   number    — integer or float
;;   string    — double-quoted text
;;   boolean   — true / false
;;   nil       — singleton null
;;   symbol    — unquoted identifier (e.g. div, ~card, map)
;;   keyword   — colon-prefixed key (e.g. :class, :id)
;;   list      — ordered sequence (also used as code)
;;   dict      — string-keyed hash map
;;   lambda    — closure: {params, body, closure-env, name?}
;;   macro     — AST transformer: {params, rest-param, body, closure-env}
;;   component — UI component: {name, params, has-children, body, closure-env}
;;   thunk     — deferred eval for TCO: {expr, env}
;;
;; Each target must provide:
;;   (type-of x)        → one of the strings above
;;   (make-lambda ...)   → platform Lambda value
;;   (make-component ..) → platform Component value
;;   (make-macro ...)    → platform Macro value
;;   (make-thunk ...)    → platform Thunk value
;;
;; These are declared in platform.sx and implemented per target.
;; --------------------------------------------------------------------------


;; --------------------------------------------------------------------------
;; 2. Trampoline — tail-call optimization
;; --------------------------------------------------------------------------

(define trampoline
  (fn (val)
    ;; Iteratively resolve thunks until we get an actual value.
    ;; Each target implements thunk? and thunk-expr/thunk-env.
    (let ((result val))
      (do
        ;; Loop while result is a thunk
        ;; Note: this is pseudo-iteration — bootstrap compilers convert
        ;; this tail-recursive form to a while loop.
        (if (thunk? result)
          (trampoline (eval-expr (thunk-expr result) (thunk-env result)))
          result)))))


;; --------------------------------------------------------------------------
;; 3. Core evaluator
;; --------------------------------------------------------------------------

(define eval-expr
  (fn (expr env)
    (case (type-of expr)

      ;; --- literals pass through ---
      "number"  expr
      "string"  expr
      "boolean" expr
      "nil"     nil

      ;; --- symbol lookup ---
      "symbol"
        (let ((name (symbol-name expr)))
          (cond
            (env-has? env name)     (env-get env name)
            (primitive? name)       (get-primitive name)
            (= name "true")        true
            (= name "false")       false
            (= name "nil")         nil
            :else                   (error (str "Undefined symbol: " name))))

      ;; --- keyword → its string name ---
      "keyword" (keyword-name expr)

      ;; --- dict literal ---
      "dict"
        (map-dict (fn (k v) (trampoline (eval-expr v env))) expr)

      ;; --- list = call or special form ---
      "list"
        (if (empty? expr)
          (list)
          (eval-list expr env))

      ;; --- anything else passes through ---
      :else expr)))


;; --------------------------------------------------------------------------
;; 4. List evaluation — dispatch on head
;; --------------------------------------------------------------------------

(define eval-list
  (fn (expr env)
    (let ((head (first expr))
          (args (rest expr)))

      ;; If head isn't a symbol, lambda, or list → treat as data list
      (if (not (or (= (type-of head) "symbol")
                   (= (type-of head) "lambda")
                   (= (type-of head) "list")))
        (map (fn (x) (trampoline (eval-expr x env))) expr)

        ;; Head is a symbol — check special forms, then function call
        (if (= (type-of head) "symbol")
          (let ((name (symbol-name head)))
            (cond
              ;; Special forms
              (= name "if")          (sf-if args env)
              (= name "when")        (sf-when args env)
              (= name "cond")        (sf-cond args env)
              (= name "case")        (sf-case args env)
              (= name "and")         (sf-and args env)
              (= name "or")          (sf-or args env)
              (= name "let")         (sf-let args env)
              (= name "let*")        (sf-let args env)
              (= name "lambda")      (sf-lambda args env)
              (= name "fn")          (sf-lambda args env)
              (= name "define")      (sf-define args env)
              (= name "defcomp")     (sf-defcomp args env)
              (= name "defmacro")    (sf-defmacro args env)
              (= name "defstyle")    (sf-defstyle args env)
              (= name "defkeyframes") (sf-defkeyframes args env)
              (= name "defhandler")  (sf-define args env)
              (= name "begin")       (sf-begin args env)
              (= name "do")          (sf-begin args env)
              (= name "quote")       (sf-quote args env)
              (= name "quasiquote")  (sf-quasiquote args env)
              (= name "->")          (sf-thread-first args env)
              (= name "set!")        (sf-set! args env)

              ;; Higher-order forms
              (= name "map")          (ho-map args env)
              (= name "map-indexed")  (ho-map-indexed args env)
              (= name "filter")       (ho-filter args env)
              (= name "reduce")       (ho-reduce args env)
              (= name "some")         (ho-some args env)
              (= name "every?")       (ho-every args env)
              (= name "for-each")     (ho-for-each args env)

              ;; Macro expansion
              (and (env-has? env name) (macro? (env-get env name)))
                (let ((mac (env-get env name)))
                  (make-thunk (expand-macro mac args env) env))

              ;; Fall through to function call
              :else (eval-call head args env)))

          ;; Head is lambda or list — evaluate as function call
          (eval-call head args env))))))


;; --------------------------------------------------------------------------
;; 5. Function / lambda / component call
;; --------------------------------------------------------------------------

(define eval-call
  (fn (head args env)
    (let ((f (trampoline (eval-expr head env)))
          (evaluated-args (map (fn (a) (trampoline (eval-expr a env))) args)))
      (cond
        ;; Native callable (primitive function)
        (and (callable? f) (not (lambda? f)) (not (component? f)))
          (apply f evaluated-args)

        ;; Lambda
        (lambda? f)
          (call-lambda f evaluated-args env)

        ;; Component
        (component? f)
          (call-component f args env)

        :else (error (str "Not callable: " (inspect f)))))))


(define call-lambda
  (fn (f args caller-env)
    (let ((params (lambda-params f))
          (local  (env-merge (lambda-closure f) caller-env)))
      (if (!= (len args) (len params))
        (error (str (or (lambda-name f) "lambda")
                    " expects " (len params) " args, got " (len args)))
        (do
          ;; Bind params
          (for-each
            (fn (pair) (env-set! local (first pair) (nth pair 1)))
            (zip params args))
          ;; Return thunk for TCO
          (make-thunk (lambda-body f) local))))))


(define call-component
  (fn (comp raw-args env)
    ;; Parse keyword args and children from unevaluated arg list
    (let ((parsed  (parse-keyword-args raw-args env))
          (kwargs  (first parsed))
          (children (nth parsed 1))
          (local   (env-merge (component-closure comp) env)))
      ;; Bind keyword params
      (for-each
        (fn (p) (env-set! local p (or (dict-get kwargs p) nil)))
        (component-params comp))
      ;; Bind children if component accepts them
      (when (component-has-children? comp)
        (env-set! local "children" children))
      ;; Return thunk — body evaluated in local env
      (make-thunk (component-body comp) local))))


(define parse-keyword-args
  (fn (raw-args env)
    ;; Walk args: keyword + next-val → kwargs dict, else → children list
    (let ((kwargs (dict))
          (children (list))
          (i 0))
      ;; Iterative parse — bootstrap converts to while loop
      (reduce
        (fn (state arg)
          (let ((idx     (get state "i"))
                (skip    (get state "skip")))
            (if skip
              ;; This arg was consumed as a keyword value
              (assoc state "skip" false "i" (inc idx))
              (if (and (= (type-of arg) "keyword")
                       (< (inc idx) (len raw-args)))
                ;; Keyword: evaluate next arg and store
                (do
                  (dict-set! kwargs (keyword-name arg)
                    (trampoline (eval-expr (nth raw-args (inc idx)) env)))
                  (assoc state "skip" true "i" (inc idx)))
                ;; Positional: evaluate and add to children
                (do
                  (append! children (trampoline (eval-expr arg env)))
                  (assoc state "i" (inc idx)))))))
        (dict "i" 0 "skip" false)
        raw-args)
      (list kwargs children))))


;; --------------------------------------------------------------------------
;; 6. Special forms
;; --------------------------------------------------------------------------

(define sf-if
  (fn (args env)
    (let ((condition (trampoline (eval-expr (first args) env))))
      (if (and condition (not (nil? condition)))
        (make-thunk (nth args 1) env)
        (if (> (len args) 2)
          (make-thunk (nth args 2) env)
          nil)))))


(define sf-when
  (fn (args env)
    (let ((condition (trampoline (eval-expr (first args) env))))
      (if (and condition (not (nil? condition)))
        (do
          ;; Evaluate all but last for side effects
          (for-each
            (fn (e) (trampoline (eval-expr e env)))
            (slice args 1 (dec (len args))))
          ;; Last is tail position
          (make-thunk (last args) env))
        nil))))


(define sf-cond
  (fn (args env)
    ;; Detect scheme-style: first arg is a 2-element list
    (if (and (= (type-of (first args)) "list")
             (= (len (first args)) 2))
      ;; Scheme-style: ((test body) ...)
      (sf-cond-scheme args env)
      ;; Clojure-style: test body test body ...
      (sf-cond-clojure args env))))

(define sf-cond-scheme
  (fn (clauses env)
    (if (empty? clauses)
      nil
      (let ((clause (first clauses))
            (test   (first clause))
            (body   (nth clause 1)))
        (if (or (and (= (type-of test) "symbol")
                     (or (= (symbol-name test) "else")
                         (= (symbol-name test) ":else")))
                (and (= (type-of test) "keyword")
                     (= (keyword-name test) "else")))
          (make-thunk body env)
          (if (trampoline (eval-expr test env))
            (make-thunk body env)
            (sf-cond-scheme (rest clauses) env)))))))

(define sf-cond-clojure
  (fn (clauses env)
    (if (< (len clauses) 2)
      nil
      (let ((test   (first clauses))
            (body   (nth clauses 1)))
        (if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
                (and (= (type-of test) "symbol")
                     (or (= (symbol-name test) "else")
                         (= (symbol-name test) ":else"))))
          (make-thunk body env)
          (if (trampoline (eval-expr test env))
            (make-thunk body env)
            (sf-cond-clojure (slice clauses 2) env)))))))


(define sf-case
  (fn (args env)
    (let ((match-val (trampoline (eval-expr (first args) env)))
          (clauses   (rest args)))
      (sf-case-loop match-val clauses env))))

(define sf-case-loop
  (fn (match-val clauses env)
    (if (< (len clauses) 2)
      nil
      (let ((test   (first clauses))
            (body   (nth clauses 1)))
        (if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
                (and (= (type-of test) "symbol")
                     (or (= (symbol-name test) "else")
                         (= (symbol-name test) ":else"))))
          (make-thunk body env)
          (if (= match-val (trampoline (eval-expr test env)))
            (make-thunk body env)
            (sf-case-loop match-val (slice clauses 2) env)))))))


(define sf-and
  (fn (args env)
    (if (empty? args)
      true
      (let ((val (trampoline (eval-expr (first args) env))))
        (if (not val)
          val
          (if (= (len args) 1)
            val
            (sf-and (rest args) env)))))))


(define sf-or
  (fn (args env)
    (if (empty? args)
      false
      (let ((val (trampoline (eval-expr (first args) env))))
        (if val
          val
          (sf-or (rest args) env))))))


(define sf-let
  (fn (args env)
    (let ((bindings (first args))
          (body     (rest args))
          (local    (env-extend env)))
      ;; Parse bindings — support both ((name val) ...) and (name val name val ...)
      (if (and (= (type-of (first bindings)) "list")
               (= (len (first bindings)) 2))
        ;; Scheme-style
        (for-each
          (fn (binding)
            (let ((vname (if (= (type-of (first binding)) "symbol")
                           (symbol-name (first binding))
                           (first binding))))
              (env-set! local vname (trampoline (eval-expr (nth binding 1) local)))))
          bindings)
        ;; Clojure-style
        (let ((i 0))
          (reduce
            (fn (acc pair-idx)
              (let ((vname (if (= (type-of (nth bindings (* pair-idx 2))) "symbol")
                             (symbol-name (nth bindings (* pair-idx 2)))
                             (nth bindings (* pair-idx 2))))
                    (val-expr (nth bindings (inc (* pair-idx 2)))))
                (env-set! local vname (trampoline (eval-expr val-expr local)))))
            nil
            (range 0 (/ (len bindings) 2)))))
      ;; Evaluate body — last expression in tail position
      (for-each
        (fn (e) (trampoline (eval-expr e local)))
        (slice body 0 (dec (len body))))
      (make-thunk (last body) local))))


(define sf-lambda
  (fn (args env)
    (let ((params-expr (first args))
          (body        (nth args 1))
          (param-names (map (fn (p)
                              (if (= (type-of p) "symbol")
                                (symbol-name p)
                                p))
                            params-expr)))
      (make-lambda param-names body env))))


(define sf-define
  (fn (args env)
    (let ((name-sym (first args))
          (value    (trampoline (eval-expr (nth args 1) env))))
      (when (and (lambda? value) (nil? (lambda-name value)))
        (set-lambda-name! value (symbol-name name-sym)))
      (env-set! env (symbol-name name-sym) value)
      value)))


(define sf-defcomp
  (fn (args env)
    (let ((name-sym   (first args))
          (params-raw (nth args 1))
          (body       (nth args 2))
          (comp-name  (strip-prefix (symbol-name name-sym) "~"))
          (parsed     (parse-comp-params params-raw))
          (params     (first parsed))
          (has-children (nth parsed 1)))
      (let ((comp (make-component comp-name params has-children body env)))
        (env-set! env (symbol-name name-sym) comp)
        comp))))

(define parse-comp-params
  (fn (params-expr)
    ;; Parse (&key param1 param2 &children) → (params has-children)
    ;; Also accepts &rest as synonym for &children.
    (let ((params (list))
          (has-children false)
          (in-key false))
      (for-each
        (fn (p)
          (when (= (type-of p) "symbol")
            (let ((name (symbol-name p)))
              (cond
                (= name "&key")      (set! in-key true)
                (= name "&rest")     (set! has-children true)
                (= name "&children") (set! has-children true)
                has-children         nil  ;; skip params after &children/&rest
                in-key               (append! params name)
                :else                (append! params name)))))
        params-expr)
      (list params has-children))))


(define sf-defmacro
  (fn (args env)
    (let ((name-sym   (first args))
          (params-raw (nth args 1))
          (body       (nth args 2))
          (parsed     (parse-macro-params params-raw))
          (params     (first parsed))
          (rest-param (nth parsed 1)))
      (let ((mac (make-macro params rest-param body env (symbol-name name-sym))))
        (env-set! env (symbol-name name-sym) mac)
        mac))))

(define parse-macro-params
  (fn (params-expr)
    ;; Parse (a b &rest rest) → ((a b) rest)
    (let ((params (list))
          (rest-param nil))
      (reduce
        (fn (state p)
          (if (and (= (type-of p) "symbol") (= (symbol-name p) "&rest"))
            (assoc state "in-rest" true)
            (if (get state "in-rest")
              (do (set! rest-param (if (= (type-of p) "symbol")
                                     (symbol-name p) p))
                  state)
              (do (append! params (if (= (type-of p) "symbol")
                                    (symbol-name p) p))
                  state))))
        (dict "in-rest" false)
        params-expr)
      (list params rest-param))))


(define sf-defstyle
  (fn (args env)
    ;; (defstyle name expr) — bind name to evaluated expr (typically a StyleValue)
    (let ((name-sym (first args))
          (value    (trampoline (eval-expr (nth args 1) env))))
      (env-set! env (symbol-name name-sym) value)
      value)))


(define sf-defkeyframes
  (fn (args env)
    ;; (defkeyframes name (selector body) ...) — build @keyframes rule,
    ;; register in keyframes dict, return StyleValue.
    ;; Delegates to platform: build-keyframes returns a StyleValue.
    (let ((kf-name (symbol-name (first args)))
          (steps   (rest args)))
      (build-keyframes kf-name steps env))))


(define sf-begin
  (fn (args env)
    (if (empty? args)
      nil
      (do
        (for-each
          (fn (e) (trampoline (eval-expr e env)))
          (slice args 0 (dec (len args))))
        (make-thunk (last args) env)))))


(define sf-quote
  (fn (args env)
    (if (empty? args) nil (first args))))


(define sf-quasiquote
  (fn (args env)
    (qq-expand (first args) env)))

(define qq-expand
  (fn (template env)
    (if (not (= (type-of template) "list"))
      template
      (if (empty? template)
        (list)
        (let ((head (first template)))
          (if (and (= (type-of head) "symbol") (= (symbol-name head) "unquote"))
            (trampoline (eval-expr (nth template 1) env))
            ;; Walk children, handling splice-unquote
            (reduce
              (fn (result item)
                (if (and (= (type-of item) "list")
                         (= (len item) 2)
                         (= (type-of (first item)) "symbol")
                         (= (symbol-name (first item)) "splice-unquote"))
                  (let ((spliced (trampoline (eval-expr (nth item 1) env))))
                    (if (= (type-of spliced) "list")
                      (concat result spliced)
                      (if (nil? spliced) result (append result spliced))))
                  (append result (qq-expand item env))))
              (list)
              template)))))))


(define sf-thread-first
  (fn (args env)
    (let ((val (trampoline (eval-expr (first args) env))))
      (reduce
        (fn (result form)
          (if (= (type-of form) "list")
            (let ((f    (trampoline (eval-expr (first form) env)))
                  (rest-args (map (fn (a) (trampoline (eval-expr a env)))
                                 (rest form)))
                  (all-args  (cons result rest-args)))
              (cond
                (and (callable? f) (not (lambda? f)))
                  (apply f all-args)
                (lambda? f)
                  (trampoline (call-lambda f all-args env))
                :else (error (str "-> form not callable: " (inspect f)))))
            (let ((f (trampoline (eval-expr form env))))
              (cond
                (and (callable? f) (not (lambda? f)))
                  (f result)
                (lambda? f)
                  (trampoline (call-lambda f (list result) env))
                :else (error (str "-> form not callable: " (inspect f)))))))
        val
        (rest args)))))


(define sf-set!
  (fn (args env)
    (let ((name  (symbol-name (first args)))
          (value (trampoline (eval-expr (nth args 1) env))))
      (env-set! env name value)
      value)))


;; --------------------------------------------------------------------------
;; 6b. Macro expansion
;; --------------------------------------------------------------------------

(define expand-macro
  (fn (mac raw-args env)
    (let ((local (env-merge (macro-closure mac) env)))
      ;; Bind positional params (unevaluated)
      (for-each
        (fn (pair)
          (env-set! local (first pair)
            (if (< (nth pair 1) (len raw-args))
              (nth raw-args (nth pair 1))
              nil)))
        (map-indexed (fn (i p) (list p i)) (macro-params mac)))
      ;; Bind &rest param
      (when (macro-rest-param mac)
        (env-set! local (macro-rest-param mac)
          (slice raw-args (len (macro-params mac)))))
      ;; Evaluate body → new AST
      (trampoline (eval-expr (macro-body mac) local)))))


;; --------------------------------------------------------------------------
;; 7. Higher-order forms
;; --------------------------------------------------------------------------

(define ho-map
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (coll (trampoline (eval-expr (nth args 1) env))))
      (map (fn (item) (trampoline (call-lambda f (list item) env))) coll))))

(define ho-map-indexed
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (coll (trampoline (eval-expr (nth args 1) env))))
      (map-indexed
        (fn (i item) (trampoline (call-lambda f (list i item) env)))
        coll))))

(define ho-filter
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (coll (trampoline (eval-expr (nth args 1) env))))
      (filter
        (fn (item) (trampoline (call-lambda f (list item) env)))
        coll))))

(define ho-reduce
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (init (trampoline (eval-expr (nth args 1) env)))
          (coll (trampoline (eval-expr (nth args 2) env))))
      (reduce
        (fn (acc item) (trampoline (call-lambda f (list acc item) env)))
        init
        coll))))

(define ho-some
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (coll (trampoline (eval-expr (nth args 1) env))))
      (some
        (fn (item) (trampoline (call-lambda f (list item) env)))
        coll))))

(define ho-every
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (coll (trampoline (eval-expr (nth args 1) env))))
      (every?
        (fn (item) (trampoline (call-lambda f (list item) env)))
        coll))))


(define ho-for-each
  (fn (args env)
    (let ((f    (trampoline (eval-expr (first args) env)))
          (coll (trampoline (eval-expr (nth args 1) env))))
      (for-each
        (fn (item) (trampoline (call-lambda f (list item) env)))
        coll))))


;; --------------------------------------------------------------------------
;; 8. Primitives — pure functions available in all targets
;; --------------------------------------------------------------------------
;; These are the ~80 built-in functions. Each target implements them
;; natively but they MUST have identical semantics. This section serves
;; as the specification — bootstrap compilers use it for reference.
;;
;; Primitives are NOT defined here as SX lambdas (that would be circular).
;; Instead, this is a declarative registry that bootstrap compilers read.
;; --------------------------------------------------------------------------

;; See primitives.sx for the full specification.


;; --------------------------------------------------------------------------
;; 9. Platform interface — must be provided by each target
;; --------------------------------------------------------------------------
;;
;; Type inspection:
;;   (type-of x)              → "number" | "string" | "boolean" | "nil"
;;                               | "symbol" | "keyword" | "list" | "dict"
;;                               | "lambda" | "component" | "macro" | "thunk"
;;   (symbol-name sym)        → string
;;   (keyword-name kw)        → string
;;
;; Constructors:
;;   (make-lambda params body env)           → Lambda
;;   (make-component name params has-children body env) → Component
;;   (make-macro params rest-param body env name) → Macro
;;   (make-thunk expr env)                   → Thunk
;;
;; Accessors:
;;   (lambda-params f)        → list of strings
;;   (lambda-body f)          → expr
;;   (lambda-closure f)       → env
;;   (lambda-name f)          → string or nil
;;   (set-lambda-name! f n)   → void
;;   (component-params c)     → list of strings
;;   (component-body c)       → expr
;;   (component-closure c)    → env
;;   (component-has-children? c) → boolean
;;   (macro-params m)         → list of strings
;;   (macro-rest-param m)     → string or nil
;;   (macro-body m)           → expr
;;   (macro-closure m)        → env
;;   (thunk? x)               → boolean
;;   (thunk-expr t)           → expr
;;   (thunk-env t)            → env
;;
;; Predicates:
;;   (callable? x)            → boolean (native function or lambda)
;;   (lambda? x)              → boolean
;;   (component? x)           → boolean
;;   (macro? x)               → boolean
;;   (primitive? name)        → boolean (is name a registered primitive?)
;;   (get-primitive name)     → function
;;
;; Environment:
;;   (env-has? env name)      → boolean
;;   (env-get env name)       → value
;;   (env-set! env name val)  → void (mutating)
;;   (env-extend env)         → new env inheriting from env
;;   (env-merge base overlay) → new env with overlay on top
;;
;; Mutation helpers (for parse-keyword-args):
;;   (dict-set! d key val)    → void
;;   (dict-get d key)         → value or nil
;;   (append! lst val)        → void (mutating append)
;;
;; Error:
;;   (error msg)              → raise/throw with message
;;   (inspect x)              → string representation for debugging
;;
;; Utility:
;;   (strip-prefix s prefix)  → string with prefix removed (or s unchanged)
;;   (apply f args)           → call f with args list
;;   (zip lists...)           → list of tuples
;;
;; CSSX (style system):
;;   (build-keyframes name steps env) → StyleValue (platform builds @keyframes)
;; --------------------------------------------------------------------------
