#lang racket/base

; Model and generation of abilities
(provide
  ; Ability database operations
  jsexpr->ability-database
  ability-database-select
  ; ability structure
  ability-name
  ability-description
  ability-author
  )

; Ability structure
(struct ability
  (name
   description
   author
   conditions ; list of conditions to get the ability
   ))

; Species condition
(struct species-condition
  (species-name
   gene-threshold
   automatic?))

; Non-automatic random limit
; Number of faces of the dice used to get an ability
; Should be higher than the max number of genes, as the number of genes is used as a threshold to get the ability
(define non-automatic-dice 125)

; Check a species condition
(define (species-condition-check spcond species-genes)
  (define gene (findf
                 (lambda (x)
                    (equal? (species-condition-species-name spcond) (car x)))
                 species-genes))
  (and
    gene
    (<= (species-condition-gene-threshold spcond) (cdr gene))
    (or (species-condition-automatic? spcond)
        (< (random non-automatic-dice) (cdr gene))) ; If not automatic, the more gene, the more chance to get it
    ))

; Affinity condition
(struct affinity-condition
  (element-name
   automatic?))

; Check an affinity condition
(define (affinity-condition-check afcond affinity-genes)
  (define gene (findf
                 (lambda (x)
                    (equal? (affinity-condition-element-name afcond) (car x)))
                 affinity-genes))
  (and
    gene
    (or (affinity-condition-automatic? afcond)
        (< (random non-automatic-dice) (cdr gene))) ; If not automatic, the more gene, the more chance to get it
    ))

; Convert a jsexpr to an ability condition
(define (jsexpr->ability-condition js)
  (define type (hash-ref js 'type))
  (cond
    ((equal? type "species")
     (species-condition
       (hash-ref js 'species)
       (hash-ref js 'gene-threshold)
       (hash-ref js 'automatic?)))
    ((equal? type "affinity")
     (affinity-condition
       (hash-ref js 'affinity)
       (hash-ref js 'automatic?)))
    (#t
      (error "Invalid ability condition type: " type))
    ))

; Check an ability condition
(define (ability-condition-check abcond species-genes affinity-composition)
  (cond
    ((species-condition? abcond)
     (species-condition-check abcond species-genes))
    ((affinity-condition? abcond)
     (affinity-condition-check abcond affinity-composition))
    (#t
      (error "Not an ability condition: " abcond))))

; Convert a jsexpr to an ability
(define (jsexpr->ability js)
  (ability
    (hash-ref js 'name)
    (hash-ref js 'description)
    (hash-ref js 'author)
    (map
      jsexpr->ability-condition
      (hash-ref js 'conditions))))

; Ability database
(struct ability-database
  (abilities ; a list of abilities
    ))

; Convert a jsexpr to an ability database
(define (jsexpr->ability-database js)
  (ability-database
    (map
      jsexpr->ability
      js)))

; Select the abilities from the database that match the criterias
; Note: species-genes and affinity-composition are in a form of '((name . count) ...), sorted by count
(define (ability-database-select db species-genes affinity-composition)
  (filter
    (lambda (ab)
      (findf
        (lambda (x)
          (ability-condition-check x species-genes affinity-composition))
        (ability-conditions ab)))
    (ability-database-abilities db)))
