;; Sip009 Compliant NFT
;; (impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)
(impl-trait .sip009-nft-trait.sip009-nft-trait)
(define-non-fungible-token nft uint)
;; CONSTANT VARIABLES
(define-constant contract-owner tx-sender)
(define-constant err-invalid-user (err u101))
(define-constant err-invalid-token (err u102))
(define-constant err-sold-out (err u103))
;; DATA VARIABLES
(define-data-var last-token-id uint u0)
(define-data-var mint-limit uint u10)
(define-data-var ipfs-root (string-ascii 80) "ipfs://ipfs/QmPyxdEhSzjSjTTi7sNiErdKHj5qHWA1NoU6MxrAfjigHL/")
(define-data-var royalty-rate uint u0.05)
(define-data-var set-reward uint u0.20)
(define-data-var collection-reward uint u0.25)
(define-data-var set-size uint u10)
(define-data-var collection-id uint u0)
(define-data-var set-id uint u0)
(define-data-var set-reward-address (address 20) "0x0000000000000000000000000000000000000000")
(define-data-var collection-reward-address (address 20) "0x0000000000000000000000000000000000000000")
(define-data-var set-winner-address (address 20) "0x0000000000000000000000000000000000000000")
(define-data-var collection-winner-address (address 20) "0x0000000000000000000000000000000000000000")
;; READ-ONLY FUNCTIONS
(define-read-only (get-mint-limit) (ok (var-get mint-limit)))
(define-read-only (get-ipfs-root) (ok (var-get ipfs-root)))
(define-read-only (get-last-token-id) (ok (var-get last-token-id)))
(define-read-only (get-owner (token-id uint)) (ok (nft-get-owner? nft token-id)))
(define-read-only (get-token-uri (token-id uint)) (ok (some (concat (concat (var-get ipfs-root) "$TOKEN_ID") ".json"))))
(define-read-only (stx-balance) (stx-get-balance (as-contract tx-sender)))
(define-read-only (stx-balance-of (address principal)) (stx-get-balance address))
;; PUBLIC FUNCTIONS
(define-public (transfer (token-id uint) (sender principal) (recipient principal))
    (begin
        (asserts! (is-eq tx-sender sender) err-invalid-user)
        (nft-transfer? nft token-id sender recipient)))
(define-public (mint (recipient principal))
    (let
        ((token-id (+ (var-get last-token-id) u1))
            (count (var-get last-token-id)))
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (asserts! (< count (var-get mint-limit)) err-sold-out)
        (try! (nft-mint? nft token-id recipient royalty-rate))
        (var-set last-token-id token-id)
        (ok token-id)))
(define-public (set-ipfs-root (new-ipfs-root (string-ascii 80)))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (var-set ipfs-root new-ipfs-root)))
(define-public (set-mint-limit (new-mint-limit uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (var-set mint-limit new-mint-limit)))
(define-public (transfer-stx (address principal) (amount uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (as-contract (stx-transfer? amount (as-contract tx-sender) address))))
(define-public (pick-set-winner (set-id uint))
    (let
        ((set-winner-address (var-get set-winner-address))
            (set-reward (var-get set-reward)))
        (try! (nft-pick-set-winner? nft set-id))
        (var-set set-winner-address set-winner-address)
        (ok set-winner-address)))
(define-public (pay-set-reward (set-winner-address (address 20)) (set-reward uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (as-contract (stx-transfer? set-reward (as-contract tx-sender) set-winner-address))))
(define-public (pick-collection-winner (collection-id uint))
    (let
        ((collection-winner-address (var-get collection-winner-address))
            (collection-reward (var-get collection-reward)))
        (try! (nft-pick-collection-winner? nft collection-id))
        (var-set collection-winner-address collection-winner-address)
        (ok collection-winner-address)))
(define-public (pay-collection-reward (collection-winner-address (address 20)) (collection-reward uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (as-contract (stx-transfer? collection-reward (as-contract tx-sender) collection-winner-address))))
(define-public (generate-random-number-for-lottery-ticket (token-id uint))
    (let
        ((random-number (random-uint 256)))
        (try! (nft-set-random-number? nft token-id random-number))
        (ok random-number)))
(define-public (check-lottery-won (token-id uint))
    (let
        ((random-number (nft-get-random-number? nft token-id)))
        (if (is-some random-number)
            (ok true)
            (ok false))))
(define-public (check-lottery-winner-paid (token-id uint))
    (let
        ((set-winner-address (var-get set-winner-address))
            (collection-winner-address (var-get collection-winner-address)))
        (if (or (is-eq (nft-get-owner? nft token-id) set-winner-address)
            (is-eq (nft-get-owner? nft token-id) collection-winner-address))
            (ok true)
            (ok false))))
(define-public (get-tickets-sold) (ok (var-get last-token-id)))
(define-public (get-tickets-remaining)
    (ok (- (var-get mint-limit) (var-get last-token-id))))
(define-public (get-odds-of-winning (set-id uint))
    (let
        ((set-size (var-get set-size)))
        (ok (/ u1 set-size))))
(define-public (get-ticket-owner-wallet (token-id uint))
    (ok (nft-get-owner? nft token-id)))
;; PRIVATE FUNCTIONS
(define-private (mint-for-set (set-id uint))
    (let
        ((token-id (+ (var-get last-token-id) u1))
            (count (var-get last-token-id)))
        (asserts! (< count (var-get mint-limit)) err-sold-out)
        (try! (nft-mint-for-set? nft token-id set-id royalty-rate))
        (var-set last-token-id token-id)
        (ok token-id)))
(define-private (mint-for-collection (collection-id uint))
    (let
        ((token-id (+ (var-get last-token-id) u1))
            (count (var-get last-token-id)))
        (asserts! (< count (var-get mint-limit)) err-sold-out)
        (try! (nft-mint-for-collection? nft token-id collection-id royalty-rate))
        (var-set last-token-id token-id)
        (ok token-id)))
(define-private (add-token-to-set (token-id uint) (set-id uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (try! (nft-add-token-to-set? nft token-id set-id))))
(define-private (add-token-to-collection (token-id uint) (collection-id uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (try! (nft-add-token-to-collection? nft token-id collection-id))))
(define-private (create-set (set-id uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (let
            ((set-size (var-get set-size))
                (set-reward-address (var-get set-reward-address)))
            (try! (nft-create-set? nft set-id set-size set-reward-address)))))
(define-private (create-collection (collection-id uint))
    (begin
        (asserts! (is-eq tx-sender contract-owner) err-invalid-user)
        (let
            ((set-size (var-get set-size))
                (collection-reward-address (var-get collection-reward-address)))
            (try! (nft-create-collection? nft collection-id set-size collection-reward-address)))))
