(use-trait tradables-trait .tradables-trait.tradables-trait)

(define-data-var minimum-listing-stx-price uint u1000000) ;; minimum listing price 1 STX
(define-data-var minimum-listing-sto-price uint u1000000) ;; minimum listing price 1 STX
(define-data-var primary-commission uint u250)
(define-data-var listings-frozen bool false) ;; turn off the ability to list additional NFTs
(define-data-var purchases-frozen bool false) ;; turn off the ability to purchase NFTs
(define-data-var unlistings-frozen bool false) ;; turn off the ability to unlist NFTs
 
(define-map on-sale
  {tradables: principal, tradable-id: uint}
  {price: uint, currency: uint, commission: uint, owner: principal, royalty-address: principal, royalty-percent: uint}
)

(define-constant contract-owner tx-sender)
(define-constant err-payment-failed u100)
(define-constant err-transfer-failed u200)
(define-constant err-not-allowed u300)
(define-constant err-duplicate-entry u400)
(define-constant err-tradable-not-found u500)
(define-constant err-commission-or-price-too-low u600)
(define-constant err-listings-frozen u700)
(define-constant err-commission-payment-failed u800)
(define-constant err-royalty-payment-failed u900)
(define-constant err-contract-not-authorized u1000)
(define-constant err-wrong-currency u1100)

(define-read-only (get-listing (tradables <tradables-trait>) (tradable-id uint))
  (match (map-get? on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
    nft-data 
    (ok nft-data)
    (err err-tradable-not-found)
  )
)

(define-private (get-royalty (contract principal) (tradable-id uint))
  (match (map-get? on-sale {tradables: contract, tradable-id: tradable-id})
    nft-data 
    (ok nft-data)
    (err err-tradable-not-found)
  )
)
 
(define-private (get-owner (tradables <tradables-trait>) (tradable-id uint))
  (contract-call? tradables get-owner tradable-id)
)

(define-private (transfer-tradable-to-escrow (tradables <tradables-trait>) (tradable-id uint))
  (begin
    (contract-call? tradables transfer tradable-id tx-sender (as-contract tx-sender))
  )
)

(define-private (transfer-tradable-from-escrow (tradables <tradables-trait>) (tradable-id uint))
  (let ((owner tx-sender))
    (begin
      (as-contract (contract-call? tradables transfer tradable-id (as-contract tx-sender) owner))
    )
  )
)

(define-private (return-tradable-from-escrow (tradables <tradables-trait>) (tradable-id uint))
  (match (map-get? on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
    nft-data
    (let ((owner tx-sender))
      (begin
        (as-contract (contract-call? tradables transfer tradable-id (as-contract tx-sender) (get owner nft-data)))
      )
    )
    (err err-tradable-not-found)
  )
)

(define-public (list-asset (tradables <tradables-trait>) (tradable-id uint) (price uint) (currency uint) )
  (begin
    (asserts! (is-eq false (var-get listings-frozen)) (err err-listings-frozen))
      (let ((tradable-owner (unwrap! (unwrap-panic (get-owner tradables tradable-id)) (err err-tradable-not-found)))
           (royalty (get-royalty (contract-of tradables) tradable-id))
           (commission (var-get primary-commission))
          )
        (if (is-eq currency u1)
            (if (>= price (var-get minimum-listing-stx-price))
                (if (is-eq tradable-owner tx-sender)
                    (if (map-insert on-sale {tradables: (contract-of tradables), tradable-id: tradable-id}
                        {price: price, currency: currency, commission: commission, owner: tradable-owner, royalty-address: contract-owner, royalty-percent: u0})
                    (begin
                    (match (transfer-tradable-to-escrow tradables tradable-id)
                        success (begin
                            (ok true))
                        error (begin (print error) (err err-transfer-failed))))
                    (err err-duplicate-entry)
                    )
                (err err-not-allowed)
                )
                (err err-commission-or-price-too-low)
              )
              (if (is-eq currency u2)
                (if (>= price (var-get minimum-listing-sto-price))
                    (if (is-eq tradable-owner tx-sender)
                        (if (map-insert on-sale {tradables: (contract-of tradables), tradable-id: tradable-id}
                            {price: price, currency: currency, commission: commission, owner: tradable-owner, royalty-address: contract-owner, royalty-percent: u0})
                        (begin
                        (match (transfer-tradable-to-escrow tradables tradable-id)
                            success (begin
                                (ok true))
                            error (begin (print error) (err err-transfer-failed))))
                        (err err-duplicate-entry)
                        )
                    (err err-not-allowed)
                    )
                    (err err-commission-or-price-too-low)
                )
                (err err-wrong-currency)
              )
        )
    )
  )
)

(define-public (unlist-asset (tradables <tradables-trait>) (tradable-id uint))
  (begin
    (asserts! (is-eq false (var-get unlistings-frozen)) (err err-listings-frozen))
    (match (map-get? on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
      nft-data 
      (if (is-eq (get owner nft-data) tx-sender)
          (match (transfer-tradable-from-escrow tradables tradable-id)
             success (begin
                       (map-delete on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
                       (ok true))
             error (begin (print error) (err err-transfer-failed)))
          (err err-not-allowed)
      )
      (err err-tradable-not-found)
    )
  )
)

;; tx sender has to send the required amount
;; tx sender receives NFT
;; owner gets paid out the amount minus commission
;; stacksocean address gets paid out commission
(define-public (purchase-asset (tradables <tradables-trait>) (tradable-id uint))
  (begin
    (asserts! (is-eq false (var-get purchases-frozen)) (err err-listings-frozen))
    (match (map-get? on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
      nft-data 
        (let ((price (get price nft-data)) 
            (commission-amount (/ (* price (get commission nft-data)) u10000)) 
            (royalty-amount (/ (* price (get royalty-percent nft-data)) u10000)) 
            (to-owner-amount (- (- price commission-amount) royalty-amount))
             (currency (get currency nft-data))
            ) 
            ;; first send the amount to the owner
            (if (is-eq currency u1)
                (match (stx-transfer? to-owner-amount tx-sender (get owner nft-data))
                    owner-success ;; sending money to owner succeeded
                    (match (stx-transfer? commission-amount tx-sender contract-owner)
                        commission-success ;; sending commission to contract owner succeeded
                        (if (> royalty-amount u0)
                            (match (stx-transfer? royalty-amount tx-sender (get royalty-address nft-data))
                            royalty-success ;; sending royalty to artist succeeded
                            (match (transfer-tradable-from-escrow tradables tradable-id)
                                transfer-success (begin 
                                (map-delete on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
                                (ok true) ;; sending NFT to buyer succeeded
                                )
                                error (err err-transfer-failed)
                            )
                            error (err err-royalty-payment-failed)
                            )
                            (match (transfer-tradable-from-escrow tradables tradable-id)
                            transfer-success (begin 
                                (map-delete on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
                                (ok true) ;; sending NFT to buyer succeeded
                            )
                            error (err err-transfer-failed)
                            )
                        )
                        error (err err-commission-payment-failed)
                    )
                    error (err err-payment-failed)
                )
              (if (is-eq currency u2) ;;Send the second currency
                (match (contract-call? 'SP2V1F1QA2X5HEMADTY9YGBG6ES3Q1Y95435A329C.sto-token transfer to-owner-amount tx-sender (get owner nft-data) none)
                    owner-success ;; sending money to owner succeeded
                    (match (contract-call? 'SP2V1F1QA2X5HEMADTY9YGBG6ES3Q1Y95435A329C.sto-token transfer commission-amount tx-sender contract-owner none)
                        commission-success ;; sending commission to contract owner succeeded
                        (if (> royalty-amount u0)
                          (match (contract-call? 'SP2V1F1QA2X5HEMADTY9YGBG6ES3Q1Y95435A329C.sto-token transfer royalty-amount tx-sender (get royalty-address nft-data) none)
                            royalty-success ;; sending royalty to artist succeeded
                            (match (transfer-tradable-from-escrow tradables tradable-id)
                                transfer-success (begin 
                                (map-delete on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
                                (ok true) ;; sending NFT to buyer succeeded
                                )
                                error (err err-transfer-failed)
                            )
                            error (err err-royalty-payment-failed)
                          )
                          (match (transfer-tradable-from-escrow tradables tradable-id)
                            transfer-success (begin 
                                (map-delete on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
                                (ok true) ;; sending NFT to buyer succeeded
                            )
                            error (err err-transfer-failed)
                          )
                        )
                        error (err err-commission-payment-failed)
                    )
                    error (err err-payment-failed)
                )
                (err err-payment-failed)
              )
            )
        )
        (err err-tradable-not-found))
    )
)


(define-public (admin-unlist-asset (tradables <tradables-trait>) (tradable-id uint))
  (match (map-get? on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
    nft-data 
    (if (is-eq contract-owner tx-sender)
        (match (return-tradable-from-escrow tradables tradable-id)
           success (begin
                     (map-delete on-sale {tradables: (contract-of tradables), tradable-id: tradable-id})
                     (ok true))
           error (begin (print error) (err err-transfer-failed)))
        (err err-not-allowed)
    )
    (err err-tradable-not-found)
  )
)

(define-public (set-primary-commission (commission uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-allowed))
    (ok (var-set primary-commission commission))
  )
)


(define-public (set-minimum-listing-stx-price (price uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-allowed))
    (ok (var-set minimum-listing-stx-price price))
  )
)
(define-public (set-minimum-listing-sto-price (price uint))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-allowed))
    (ok (var-set minimum-listing-sto-price price))
  )
)

(define-public (set-listings-frozen (frozen bool))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-allowed))
    (ok (var-set listings-frozen frozen))
  )
)

(define-public (set-unlistings-frozen (frozen bool))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-allowed))
    (ok (var-set unlistings-frozen frozen))
  )
)

(define-public (set-purchases-frozen (frozen bool))
  (begin
    (asserts! (is-eq tx-sender contract-owner) (err err-not-allowed))
    (ok (var-set purchases-frozen frozen))
  )
)