(impl-trait 'SPX9XMC02T56N9PRXV4AM9TS88MMQ6A1Z3375MHD.nft-trait.nft-trait)

;; non fungible token, using sip-009
(define-non-fungible-token CityCoins-Bulls uint)

;; errors
(define-constant err-no-more-nfts u300)
(define-constant err-sale-is-not-active u301)
(define-constant err-not-authorized u401)
(define-constant err-invalid-user u500)
(define-constant err-failed-to-transfer-ft u601)

;; constants
(define-constant contract-owner tx-sender)
(define-constant total-supply u100)
(define-constant artist-address 'SP34ZEET21QZMHC7HEKSCEP3B0S53S1GDGZT12M3A)
(define-constant stackerdao-treasury 'SP2BSV94A650WGZ2YZ5Y8HM93W01NGT4GY0W2BN3P.stackerdao-treasury)
(define-constant treasury .citybulls-treasury-v1)

;; variables
(define-data-var price uint u3000)
(define-data-var last-id uint u0)
(define-data-var sale-active bool false)
(define-data-var base-token-uri (string-ascii 210) "ipfs://")

;; utils
(define-constant FOLDS_TWO (list true true))
(define-constant NUM_TO_CHAR (list "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"))
(define-constant ids (list u1 u59 u2 u58 u69 u46 u5 u91 u98 u16 u38 u41 u65 u3 u80 u78 u97 u30 u95 u81 u20 u36 u18 u9 u75 u43 u66 u96 u53 u54 u42 u32 u82 u94 u74 u100 u77 u56 u7 u47 u88 u34 u31 u67 u87 u24 u99 u40 u27 u12 u15 u79 u89 u10 u61 u6 u64 u55 u71 u22 u29 u25 u45 u85 u26 u60 u50 u93 u48 u51 u49 u37 u14 u44 u86 u19 u39 u8 u72 u84 u4 u17 u76 u62 u23 u21 u33 u70 u73 u68 u83 u57 u63 u90 u13 u11 u52 u92 u28 u35 u1 u82 u69 u66 u4 u23 u20 u98 u2 u56 u55 u29 u42 u16 u25 u84 u99 u78 u41 u75 u13 u43 u64 u86 u30 u73 u14 u59 u48 u52 u34 u61 u85 u21 u57 u35 u72 u62 u68 u28 u22 u74 u51 u76 u39 u94 u12 u71 u47 u53 u50 u96 u54 u46 u5 u63 u37 u70 u7 u60 u19 u27 u8 u58 u49 u15 u33 u9 u81 u93 u11 u91 u83 u89 u24 u31 u77 u18 u17 u65 u44 u40 u67 u95 u92 u6 u79 u26 u38 u80 u32 u45 u3 u36 u87 u97 u10 u88 u100 u90))

;; PUBLIC FUNCTIONS

(define-public (claim)
  (let
    (
      (next-id (+ u1 (var-get last-id)))
    )

    (asserts! (var-get sale-active) (err err-sale-is-not-active))
    (asserts! (< (var-get last-id) total-supply) (err err-no-more-nfts))
    
    (begin
      (send-funds)
      (try! (nft-mint? CityCoins-Bulls next-id tx-sender))
      (var-set last-id next-id)
      (ok next-id)
    )
  )
)

(define-public (set-price (new-price uint))
  (if (is-eq tx-sender contract-owner)
    (begin 
      (var-set price new-price)
      (ok true)
    )
    (err err-not-authorized)
  )
)

(define-public (set-base-token-uri (new-base-token-uri (string-ascii 210)))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-authorized))
    (ok (var-set base-token-uri new-base-token-uri))
  )
)

(define-public (transfer (token-id uint) (sender principal) (recipient principal))
  (begin
    (if (and (is-eq tx-sender sender))
      (match (nft-transfer? CityCoins-Bulls token-id sender recipient)
        success (ok success)
        error (err error)
      )
      (err err-invalid-user)
    )
  )
)

;; set public sale flag
(define-public (flip-sale)
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-authorized))
    (var-set sale-active (not (var-get sale-active)))
    (ok (var-get sale-active))
  )
)

;; PRIVATE FUNCTIONS

(define-private (send-funds)
  (begin
    ;; 2% StackerDAOs fee
    (unwrap! (contract-call? 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27.miamicoin-token transfer (/ (* (var-get price) u2) u100) tx-sender stackerdao-treasury (some 0x11)) (err err-failed-to-transfer-ft))
    ;; send 30% to CityCoins-Bulls DAO Treasury
    (unwrap! (contract-call? 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27.miamicoin-token transfer (/ (* (var-get price) u30) u100) tx-sender treasury (some 0x11)) (err err-failed-to-transfer-ft))
    (if (not (is-eq tx-sender artist-address))
      ;; send 68% to CityCoins-Bulls artist
      (unwrap! (contract-call? 'SP466FNC0P7JWTNM2R9T199QRZN1MYEDTAR0KP27.miamicoin-token transfer (/ (* (var-get price) u68) u100) tx-sender artist-address (some 0x11)) (err err-failed-to-transfer-ft))
      false
    )
  )
)

;; start utils to convert uint to string
(define-private (concat-uint (ignore bool) (input { dec: uint, data: (string-ascii 3) }))
  (let 
    (
      (last-val (get dec input))
    )
    (if (is-eq last-val u0)
      {
          dec: last-val,
          data: (get data input)
      }
      (if (< last-val u10)
        {
            dec: u0,
            data: (concat-num-to-string last-val (get data input))
        }
        {
            dec: (/ last-val u10),
            data: (concat-num-to-string (mod last-val u10) (get data input))
        }
      )
    )
  )
)

(define-private (concat-num-to-string (num uint) (right (string-ascii 3)))
    (unwrap-panic (as-max-len? (concat (unwrap-panic (element-at NUM_TO_CHAR num)) right) u3))
)

(define-private (uint-to-string (num uint))
  (if (is-eq num u0)
    (unwrap-panic (as-max-len? "0" u3))
    (get data (fold concat-uint FOLDS_TWO { dec: num, data: ""}))
  )
)
;; end utils to convert uint to string

;; READ-ONLY FUNCTIONS

(define-read-only (get-owner (token-id uint))
  (ok (nft-get-owner? CityCoins-Bulls token-id))
)

(define-read-only (get-last-token-id)
  (ok (var-get last-id))
)

(define-read-only (get-token-uri (token-id uint))
  (ok (as-max-len? (concat (concat (var-get base-token-uri) (uint-to-string token-id)) ".json") u256))
)