
;; wnbt-v1
;; Wannabe (semi-fungible) Token
(impl-trait 'SPDBEG5X8XD50SPM1JJH0E5CTXGDV5NJTKAKKR5V.sip013-semi-fungible-token-trait.sip013-semi-fungible-token-trait)

;;
;; =========
;; CONSTANTS
;; =========
;;
(define-fungible-token wnbt-sft)
(define-non-fungible-token wnbt-sft-id 
    { token-id: uint, 
         owner: principal,
        expiry: uint,
    })

(define-constant contract-owner tx-sender)


;; Error definitions
(define-constant ERR-CONTRACT-OWNER-ONLY (err u1000))
(define-constant ERR-OPERATOR-ONLY (err u1001))
(define-constant ERR-TOKEN-EXPIRED (err u1002))
(define-constant ERR-TOKEN-EXISTS (err u1003))
(define-constant ERR-INVALID-TOKEN-ID (err u1004))
(define-constant ERR-BALANCE-EXCEEDED (err u1005))
(define-constant ERR-TOKEN-NOT-EXPIRED (err u1006))

;; error codes required by sip-013
(define-constant ERR-INSUFFICIENT-BALANCE (err u1))
(define-constant ERR-RECIPIENT-IS-SENDER (err u2))
(define-constant ERR-AMOUNT-IS-ZERO (err u3))
(define-constant ERR-INVALID-SENDER (err u4))

;;
;; ==================
;; DATA MAPS AND VARS
;; ==================
;;
(define-data-var operator principal tx-sender)


(define-map balances { token-id: uint, owner: principal, expiry: uint } uint)
(define-map token-supplies uint uint)
(define-map uris uint (string-ascii 256))
(define-map token-expiry uint uint)

;;
;; =================
;; PRIVATE FUNCTIONS
;; =================
;;

(define-private (assert-owner)
    (ok (asserts! (is-eq tx-sender contract-owner)
                  ERR-CONTRACT-OWNER-ONLY)))

(define-private (assert-operator)
    (ok (asserts! (is-eq tx-sender (var-get operator))
                  ERR-OPERATOR-ONLY)))

(define-private (tag-nft-token-id (nft-token-id {token-id: uint,
                                                 owner: principal,
                                                 expiry: uint}))
    (let (
          (owner (get owner nft-token-id))
          )
      (and
       (is-some (nft-get-owner? wnbt-sft-id nft-token-id))
       (try! (nft-burn? wnbt-sft-id nft-token-id owner))
      )
     (nft-mint? wnbt-sft-id  nft-token-id owner)
     )
  )

(define-private (get-balance-uint (token-id uint) (user principal)
                                  (expiry uint))
    (default-to u0 (map-get? balances
                             { token-id: token-id, owner: user,
                               expiry: expiry })))

(define-private (set-balance (token-id uint)
                             (owner principal)
                             (expiry uint)
                             (balance uint)
                             )
    (map-set balances { token-id: token-id, owner: owner, expiry: expiry }
             balance)
  )

(define-private (get-time) 
    (default-to block-height 
        (get-block-info? time block-height)))


;;
;; ================
;; PUBLIC FUNCTIONS
;; ================
;;
(define-public (set-operator (new-op principal))
    (if (is-eq tx-sender contract-owner)
        (ok (var-set operator new-op))
        ERR-CONTRACT-OWNER-ONLY))

(define-read-only (get-operator) 
    (var-get operator))

(define-read-only (get-token-expiry (token-id uint))
    (map-get? token-expiry token-id))

;; SIP-013 required functions
(define-read-only (get-balance (token-id uint) 
                               (user principal))
    (match (map-get? token-expiry token-id)
           exp (ok (get-balance-uint token-id user exp))
           (ok u0)))

(define-read-only (get-overall-balance (user principal))
    (ok (ft-get-balance wnbt-sft user)))

(define-read-only (get-total-supply (token-id uint))
    (ok (default-to u0 (map-get? token-supplies token-id))))

(define-read-only (get-overall-supply)
    (ok (ft-get-supply wnbt-sft))
  )

(define-read-only (get-decimals (token-id uint))
    (ok u0))

(define-read-only (get-token-uri (token-id uint))
    (ok (map-get? uris token-id)))

(define-public (transfer (token-id uint)
                         (amount uint)
                         (sender principal)
                         (recipient principal))
    (let
        (
         (expiry (unwrap! (map-get? token-expiry token-id)
                          ERR-INVALID-TOKEN-ID))
         (balance (get-balance-uint token-id sender expiry))
         (rbalance (get-balance-uint token-id recipient expiry))
         )
      (asserts! (or (is-eq sender tx-sender)
                    (is-eq sender contract-caller))
                ERR-INVALID-SENDER)
      (asserts! (<= (get-time) expiry) ERR-TOKEN-EXPIRED)
      (asserts! (<= amount balance) ERR-INSUFFICIENT-BALANCE)
      (try! (ft-transfer? wnbt-sft amount sender recipient))
      (try! (tag-nft-token-id {token-id: token-id, owner: sender,
                              expiry: expiry }))
      (try! (tag-nft-token-id {token-id: token-id, owner: recipient,
                              expiry: expiry}))
      (set-balance token-id sender expiry (- balance amount))
      (set-balance token-id recipient expiry (+ rbalance amount))
      (print { type: "sft_transfer", token-id: token-id,
             amount: amount, sender: sender, recipient: recipient })
      (ok true)
      )
  )

(define-public (transfer-memo (token-id uint)
                              (amount uint)
                              (sender principal)
                              (recipient principal)
                              (memo (buff 34)))
    (begin
     (try! (transfer token-id amount sender recipient))
     (print memo)
     (ok true)
     )
  )

;; Burning
;;

(define-public (destroy-expired-token (token-id uint)
                                      (user principal))
    (let
        (
         (expiry (unwrap! (map-get? token-expiry token-id)
                          ERR-INVALID-TOKEN-ID))
         (balance (get-balance-uint token-id user expiry))
         )

      (try! (assert-operator))
      (asserts! (< expiry (get-time)) ERR-TOKEN-NOT-EXPIRED)
      (and (> balance u0)
           (try! (ft-burn? wnbt-sft balance user)))
      (map-delete balances { token-id: token-id, owner: user, expiry: expiry })
      (map-set token-supplies token-id 
               (- (default-to u0 
                      (map-get? token-supplies token-id))
                  balance))
      (try! (nft-burn? wnbt-sft-id {token-id: token-id,
                                    owner: user,
                                    expiry: expiry} user))
      (print {type: "sft_burn", token-id: token-id, amount: balance,
             sender: user})
      (ok true)
      )
  )


(define-public (burn (token-id uint)
                     (amount uint)
                     (user principal))
    (let
        (
         (expiry (unwrap! (map-get? token-expiry token-id)
                          ERR-INVALID-TOKEN-ID))
         (balance (get-balance-uint token-id user expiry))
         )
      (asserts! (> amount u0) ERR-AMOUNT-IS-ZERO)
      (asserts! (>= expiry (get-time)) ERR-TOKEN-EXPIRED)
      (asserts! (>= balance amount) ERR-BALANCE-EXCEEDED)
      (try! (assert-operator))
      (set-balance token-id user expiry (- balance amount))
      (map-set token-supplies token-id 
               (- (default-to u0 
                      (map-get? token-supplies token-id))
                  amount))
      (try! (ft-burn? wnbt-sft amount user))
      (print {type: "sft_burn", token-id: token-id, amount: amount,
             sender: user})
      (ok true)
      )
  )

;; Minting
(define-public (mint (token-id uint)
                     (amount uint)
                     (expiry uint)
                     (recipient principal))
    (let (
          (time (get-time))
          )
     (try! (assert-operator))
     (asserts! (is-none (map-get? token-expiry token-id))
               ERR-TOKEN-EXISTS)
     (asserts! (> amount u0) ERR-AMOUNT-IS-ZERO)
     (asserts! (> expiry time) ERR-TOKEN-EXPIRED)
     (try! (ft-mint? wnbt-sft amount recipient))
     (try! (tag-nft-token-id { token-id: token-id, 
                             owner: recipient,
                             expiry: expiry }))
     (set-balance token-id recipient expiry
                  (+ (get-balance-uint token-id recipient expiry)
                     amount))
     (map-set token-expiry token-id expiry)
     (map-set token-supplies token-id (+ (unwrap-panic
                                          (get-total-supply token-id))
                                         amount))
     (print {type: "sft_mint", token-id: token-id, amount: amount,
            recipient: recipient})
     (ok true)
     )
  )

(define-public (set-token-uri (token-id uint)
                              (uri-maybe (optional (string-ascii 256))))
    (begin
     (try! (assert-operator))
     (ok
      (match uri-maybe
             uri (map-set uris token-id uri)
             (map-delete uris token-id)))
     )
  )
