(impl-trait .googlier-liquidation-pool-trait-v1.liquidation-pool-trait)

;; Errors
(define-constant ERR-NOT-AUTHORIZED u32401)
(define-constant ERR-UNSTAKE-AMOUNT-EXCEEDED u32002)
(define-constant ERR-WITHDRAWAL-AMOUNT-EXCEEDED u32003)
(define-constant ERR-EMERGENCY-SHUTDOWN-ACTIVATED u32004)
(define-constant ERR-STAKE-NOT-UNLOCKED u32005)

;; Variables
(define-data-var fragments-per-token uint u1000000000000)
(define-data-var total-fragments uint u0)
(define-data-var shutdown-activated bool false)
(define-data-var lockup-blocks uint u4320)

;; ---------------------------------------------------------
;; Maps
;; ---------------------------------------------------------

(define-map staker-fragments 
  { 
    staker: principal,
  } 
  {
    fragments: uint
  }
)

(define-read-only (get-staker-fragments (staker principal))
  (default-to
    { fragments: u0 }
    (map-get? staker-fragments { staker: staker })
  )
)

(define-map staker-lockup 
  { 
    staker: principal,
  } 
  {
    start-block: uint
  }
)

(define-read-only (get-staker-lockup (staker principal))
  (default-to
    { start-block: u0 }
    (map-get? staker-lockup { staker: staker })
  )
)

(define-read-only (get-shutdown-activated)
  (or
    (unwrap-panic (contract-call? .googlier-dao get-emergency-shutdown-activated))
    (var-get shutdown-activated)
  )
)

;; @desc toggles the killswitch
(define-public (toggle-shutdown)
  (begin
    (asserts! (is-eq tx-sender (contract-call? .googlier-dao get-guardian-address)) (err ERR-NOT-AUTHORIZED))

    (ok (var-set shutdown-activated (not (var-get shutdown-activated))))
  )
)


;; ---------------------------------------------------------
;; Getters
;; ---------------------------------------------------------

;; @desc get current lockup time
(define-read-only (get-lockup-blocks)
  (ok (var-get lockup-blocks))
)

;; @desc get current total fragments
(define-read-only (get-total-fragments)
  (ok (var-get total-fragments))
)

;; @desc get current fragments per token
(define-read-only (get-fragments-per-token)
  (ok (var-get fragments-per-token))
)

;; @desc get tokens of given staker
;; @param staker; the staker to get tokens of
(define-read-only (get-tokens-of (staker principal))
  (let (
    (per-token (var-get fragments-per-token))
  )
    (if (is-eq per-token u0)
      (ok u0)
      (let (
        (user-fragments (get fragments (get-staker-fragments staker)))
      )
        (ok (/ user-fragments per-token))
      )
    )
  )
)

;; @desc get share % of user at given block
;; @param user; the user to get share of
;; @param block; block on which shares are calculated
(define-read-only (get-shares-at (user principal) (block uint))
  (let (
    (block-hash (unwrap-panic (get-block-info? id-header-hash block)))

    (user-fragments (get fragments (at-block block-hash (get-staker-fragments user))))
    (fragments-total (unwrap-panic (at-block block-hash (get-total-fragments))))
  )
    (if (is-eq fragments-total u0)
      (ok u0)
      (ok (/ (* user-fragments u10000000) fragments-total))
    )
  )
)

;; ---------------------------------------------------------
;; Stake / unstake
;; ---------------------------------------------------------

;; @desc stake USD in pool
;; @param amount; amount to stake
(define-public (stake (amount uint))
  (let (
    (staker tx-sender)

    (user-fragments (get fragments (get-staker-fragments staker)))
    (add-user-fragments (* amount (var-get fragments-per-token)))
    (new-user-fragments (+ user-fragments add-user-fragments))
    (new-total-fragments (+ (var-get total-fragments) add-user-fragments))
  )
    (asserts! (not (get-shutdown-activated)) (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED))

    ;; Transfer USD
    (try! (contract-call? .USD-token transfer amount staker (as-contract tx-sender) none))

    ;; Update user tokens
    (map-set staker-fragments  { staker: staker } { fragments: new-user-fragments })

    ;; Update lockup
    (map-set staker-lockup  { staker: staker } { start-block: block-height })

    ;; Update total fragments
    (var-set total-fragments new-total-fragments)

    (ok amount)
  )
)

;; @desc unstake USD from pool
;; @param amount; amount to unstake
(define-public (unstake (amount uint))
  (let (
    (staker tx-sender)

    (stake-start-block (get start-block (get-staker-lockup staker)))
    (user-fragments (get fragments (get-staker-fragments staker)))
    (remove-user-fragments (* amount (var-get fragments-per-token)))
  )
    (asserts! (not (get-shutdown-activated)) (err ERR-EMERGENCY-SHUTDOWN-ACTIVATED))
    (asserts! (>= user-fragments remove-user-fragments) (err ERR-UNSTAKE-AMOUNT-EXCEEDED))
    (asserts! (>= block-height (+ stake-start-block (var-get lockup-blocks))) (err ERR-STAKE-NOT-UNLOCKED))

    (let (
      (new-user-fragments (- user-fragments remove-user-fragments))
      (new-total-fragments (- (var-get total-fragments) remove-user-fragments))
    )
      ;; Transfer USD
      (try! (as-contract (contract-call? .USD-token transfer amount (as-contract tx-sender) staker none)))

      ;; Update user tokens
      (map-set staker-fragments  { staker: staker } { fragments: new-user-fragments })

      ;; Update total fragments
      (var-set total-fragments new-total-fragments)

      (ok amount)
    )
  )
)


;; ---------------------------------------------------------
;; Withdraw
;; ---------------------------------------------------------

;; @desc max amount of USD that can be withdrawn for liquidations
(define-read-only (max-withdrawable-USD)
  (let (
    (USD-balance (unwrap-panic (contract-call? .USD-token get-balance (as-contract tx-sender))))
    (USD-to-keep u1000000000) ;; always keep 1k USD
  )
    (if (<= USD-balance USD-to-keep)
      (ok u0)
      (ok (- USD-balance USD-to-keep))
    )
  )
)

;; @desc withdraw USD for liquidations (only for auction-engine)
;; @param amount; amount to withdraw
(define-public (withdraw (amount uint))
  (let (
    (sender tx-sender)

    (USD-balance (unwrap-panic (contract-call? .USD-token get-balance (as-contract tx-sender))))
    (new-USD-balance (- USD-balance amount))
    (new-fragments-per-token (/ (var-get total-fragments) new-USD-balance))
  )
    (asserts! (is-eq tx-sender (unwrap-panic (contract-call? .googlier-dao get-qualified-name-by-name "auction-engine"))) (err ERR-NOT-AUTHORIZED))
    (asserts! (<= amount (unwrap-panic (max-withdrawable-USD))) (err ERR-WITHDRAWAL-AMOUNT-EXCEEDED))

    ;; Transfer token
    (try! (as-contract (contract-call? .USD-token transfer amount (as-contract tx-sender) sender none)))

    ;; Update fragments-per-token
    (var-set fragments-per-token new-fragments-per-token)

    (ok amount)
  )
)

;; ---------------------------------------------------------
;; Update
;; ---------------------------------------------------------

(define-public (update-lockup (blocks uint))
  (begin
    (asserts! (is-eq tx-sender (contract-call? .googlier-dao get-dao-owner)) (err ERR-NOT-AUTHORIZED))

    (var-set lockup-blocks blocks)
    
    (ok true)
  )
)
