(impl-trait 'SP2A665S3H6FVMZSY4VJ17ESXX21CGS0A32984B1H.sip-010-trait.sip-010-trait)
(impl-trait 'SP2A665S3H6FVMZSY4VJ17ESXX21CGS0A32984B1H.token-trait.token-trait)

(define-fungible-token ROMA u21000000000000)
(define-constant max-supply u21000000000000)
(define-constant cntrct-owner tx-sender)

(define-data-var current-divisor uint u1)
(define-data-var next-target-divisor uint u10000000000000)

;; error handling
(define-constant permission-denied-err (err u403))
(define-constant not-enough-funds-err (err u404)) ;; not found
(define-constant sender-equals-recipient-err (err u405)) ;; method not allowed
(define-constant invalid-amount-err (err u409)) ;; conflict
(define-constant err-invalid-value (err u422)) ;; conflict
(define-constant contract-err (err u500)) ;; conflict

;; admin contracts
(define-data-var administrative-contracts (list 100 principal) (list) )
(define-data-var current-removing-administrative (optional principal) none )
;; is address an administrative address
(define-private (is-administrative (address principal))
  (or
    (is-eq cntrct-owner address )
    (not (is-none (index-of (var-get administrative-contracts) address)) )
  )
)
(define-read-only (is-admin (address principal))
  (begin
    (asserts! (is-administrative address) permission-denied-err)
    (ok u1)
  )
)
(define-public (add-address-to-administrative
    (address principal)
  )
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (var-set administrative-contracts (unwrap-panic (as-max-len? (append (var-get administrative-contracts) address) u100) )) contract-err )
    (ok true)
  )
)
(define-private (filter-remove-from-administrative 
    (address principal )
  )
  (
    not (is-eq (some address) (var-get current-removing-administrative))
  )
)
(define-public (remove-address-from-adminstrative
    (address principal)
  )
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (var-set current-removing-administrative (some address) ) contract-err )
    (asserts! (var-set administrative-contracts (filter filter-remove-from-administrative (var-get administrative-contracts) ) ) contract-err )
    (ok true)
  )
)


;; minters contracts
(define-data-var minter-contracts (list 100 principal) (list) )
(define-data-var current-removing-minter (optional principal) none )
;; is address an minter address
(define-private (is-minter (address principal))
    (or
      (is-eq cntrct-owner address )
      (not (is-none (index-of (var-get minter-contracts) address)) )
    )
  )
(define-read-only (is-minter-address (address principal))
  (begin
    (asserts! (is-minter address) permission-denied-err)
    (ok u1)
  )
)
(define-public (add-address-to-minter
    (address principal)
  )
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (var-set minter-contracts (unwrap-panic (as-max-len? (append (var-get minter-contracts) address) u100) )) (err u2 ) )
    (ok true)
  )
)
(define-private (filter-remove-from-minter 
    (address principal )
  )
  (
    not (is-eq (some address) (var-get current-removing-minter))
  )
)
(define-public (remove-address-from-minter
    (address principal)
  )
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (var-set current-removing-minter (some address) ) (err u2) )
    (asserts! (var-set minter-contracts (filter filter-remove-from-minter (var-get minter-contracts) ) ) (err u3) )
    (ok true)
  )
)

;; return if is in minter or administrative addresses
(define-private (is-minter-or-administrative (address principal))
    (or 
      (is-administrative address)
      (is-minter address)
    )
  )




;; bonus percentage for minting
(define-data-var bonus-percentage uint u0)
;; percentage is x100
(define-public (set-bonus-percentage (percentage uint))
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (>= percentage u0) err-invalid-value)
    (asserts! (<= percentage u10000) err-invalid-value)
    (var-set bonus-percentage percentage)
    (ok u1)
  )
)

;; bonus contracts
(define-data-var bonus-contracts (list 100 principal) (list) )
(define-data-var current-removing-bonus (optional principal) none )
;; is address a bonus address
(define-private (is-bonus (address principal))
    (or
      (is-eq cntrct-owner address )
      (not (is-none (index-of (var-get bonus-contracts) address)) )
    )
  )
(define-read-only (is-bonus-address (address principal))
  (begin
    (asserts! (is-bonus address) permission-denied-err)
    (ok u1)
  )
)
(define-public (add-address-to-bonus
    (address principal)
  )
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (var-set bonus-contracts (unwrap-panic (as-max-len? (append (var-get bonus-contracts) address) u100) )) (err u2 ) )
    (ok true)
  )
)
(define-private (filter-remove-from-bonus 
    (address principal )
  )
  (
    not (is-eq (some address) (var-get current-removing-bonus))
  )
)
(define-public (remove-address-from-bonus
    (address principal)
  )
  (begin
    (asserts! (is-administrative tx-sender) permission-denied-err)
    (asserts! (var-set current-removing-bonus (some address) ) (err u2) )
    (asserts! (var-set bonus-contracts (filter filter-remove-from-bonus (var-get bonus-contracts) ) ) (err u3) )
    (ok true)
  )
)
(define-read-only (list-bonus-contracts)
  (ok (var-get bonus-contracts))
  )
(define-read-only (get-bonus-percentage)
  (ok (var-get bonus-percentage))
  )


;; mint token preventing runtime error
(define-private (mint-amount (amount uint) (address principal))
  (if 
    (<= (+ (ft-get-supply ROMA) amount) max-supply)
    (ft-mint? ROMA amount address)
    (if 
      ( is-eq u0 (- max-supply (ft-get-supply ROMA)) )
      (ok true)
      (ft-mint? ROMA (- max-supply (ft-get-supply ROMA)) address)
    )
  )
)


;; mint bonuses to bonus contracts
(define-data-var current-mint-amount uint u0 )
(define-private (give-address-bonus (address principal))
    (unwrap-panic (mint-amount (/ (* ( / (var-get current-mint-amount) u100 ) (var-get bonus-percentage) ) u100) address))
  )
(define-private (mint-bonuses (amount uint))
  (if
    (> (var-get bonus-percentage) u0)
    (begin
      (var-set current-mint-amount amount)
      (map give-address-bonus (var-get bonus-contracts) )
      true
    )
    true
  )
)



;; double divisor for targeting the step
(define-private (double-divisor)
  (if (> (ft-get-supply ROMA) (var-get next-target-divisor))
    (begin
      (var-set current-divisor (* (var-get current-divisor) u2))
      (var-set next-target-divisor 
        (+ 
          (/ 
            (- 
              max-supply
              (var-get next-target-divisor)
            ) 
            u2 
          ) 
          (var-get next-target-divisor) 
        ) )
      true
    )
    true
  )
)



;; Public functions
(define-read-only (get-name)
  (ok "ROMATOKEN"))

(define-read-only (get-decimals)
  (ok u6))

(define-read-only (get-symbol)
  (ok "ROMA"))

(define-read-only (get-token-uri)
  (ok (some "ipfs://QmaKHAtFbHghuRY1qQmtSVbnbY8RfrHu7j5syqqPdUjENn") ))

(define-read-only (get-total-supply)
  (ok (ft-get-supply ROMA)))

;; Transfers tokens to a specified principal.
(define-public (transfer (amount uint) (sender principal) (recipient principal) (bf (optional (buff 34))) )
  (if (is-eq sender tx-sender)
    (match (ft-transfer? ROMA amount tx-sender recipient)
      result (ok true)
      error (ft-transfer-err error))
  permission-denied-err))

;; get address token balance
(define-read-only (get-balance (owner principal))
   (ok (ft-get-balance ROMA owner)))

(define-read-only (get-balance-of (owner principal))
   (ok (ft-get-balance ROMA owner)))

(define-read-only (get-current-supply)
   (ok (ft-get-supply ROMA)))

(define-read-only (get-current-divisor)
   (ok (var-get current-divisor)))

(define-read-only (get-next-target-divisor)
   (ok (var-get next-target-divisor)))

;; divide amount with current divisor
(define-private (get-amount-divided (amount uint))
    (/ amount (var-get current-divisor))
  )

;; Mint new tokens.
(define-public (mint (account principal) (amount uint))
    (let ((divided-amount (get-amount-divided amount)))
      (asserts! (is-minter-or-administrative tx-sender) permission-denied-err)
      (unwrap-panic (mint-amount divided-amount account))
      (mint-bonuses divided-amount )
      (asserts! (double-divisor) contract-err )
      (ok divided-amount)))

(define-public (burn (account principal) (amount uint))
    (begin
      (asserts! (is-eq account tx-sender) permission-denied-err)
      (ft-burn? ROMA amount account)
    )
)

(define-private (ft-transfer-err (code uint))
  (if (is-eq u1 code)
    not-enough-funds-err
    (if (is-eq u2 code)
      sender-equals-recipient-err
      (if (is-eq u3 code)
        invalid-amount-err
        (err code)))))


;; Initialize the contract giving 1M to contract owner
(begin
  (mint cntrct-owner u1000000000000))