;; @contract Treasury
;; @version 1.1

(impl-trait .treasury-trait-v1-1.treasury-trait)
(use-trait ft-trait .sip-010-trait-ft-standard.sip-010-trait)
(use-trait value-calculator-trait .value-calculator-trait-v1-1.value-calculator-trait)

;; ------------------------------------------
;; Constants
;; ------------------------------------------

(define-constant ERR-NOT-AUTHORIZED u2003001)
(define-constant ERR-DEBTOR-NOT-AUTHORIZED u2003002)
(define-constant ERR-DEPOSITOR-NOT-AUTHORIZED u2003003)

(define-constant ERR-CONTRACT-DISABLED u2001001)

(define-constant ERR-WRONG-VALUE-CALCULATOR u2002001)

(define-constant ERR-RESERVE-TOKEN-NOT-ENABLED u2000001)
(define-constant ERR-INSUFFICIENT-RESERVES u2000002)
(define-constant ERR-EXCEED-DEBT-LIMIT u2000003)

;; ------------------------------------------
;; Variables
;; ------------------------------------------

(define-data-var contract-is-enabled bool true)

(define-data-var total-reserves uint u0)
(define-data-var total-debt uint u0)

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

(define-map minter-info
  { minter: principal }
  {
    enabled: bool
  }
)

(define-map debtor-info
  { 
    debtor: principal,
    token: principal
  }
  {
    enabled: bool
  }
)

(define-map depositor-info
  { 
    depositor: principal,
    token: principal
  }
  {
    enabled: bool
  }
)

(define-map reserve-tokens
  { token: principal }
  {
    enabled: bool,
    last-total-token-value: uint,
    liquidity-token: bool,
    value-calculator: principal
  }
)

;; ------------------------------------------
;; Var & Map Helpers
;; ------------------------------------------

(define-read-only (get-contract-is-enabled)
  (var-get contract-is-enabled)
)

(define-read-only (get-minter-info (minter principal))
  (default-to
    {
      enabled: false,
    }
    (map-get? minter-info { minter: minter })
  )
)

(define-read-only (get-debtor-info (debtor principal) (token principal))
  (default-to
    {
      enabled: false,
    }
    (map-get? debtor-info { debtor: debtor, token: token })
  )
)

(define-read-only (get-depositor-info (depositor principal) (token principal))
  (default-to
    {
      enabled: false,
    }
    (map-get? depositor-info { depositor: depositor, token: token })
  )
)

(define-read-only (get-total-reserves)
  (var-get total-reserves)
)

(define-read-only (get-total-debt)
  (var-get total-debt)
)

(define-read-only (get-reserve-token (token principal))
  (default-to
    {
      enabled: false,
      last-total-token-value: u0,
      liquidity-token: false,
      value-calculator: .treasury-v1-1
    }
    (map-get? reserve-tokens { token: token })
  )
)

;; ------------------------------------------
;; Mutative
;; ------------------------------------------

(define-public (mint (recipient principal) (amount uint))
  (let (
    (minter-enabled (get enabled (get-minter-info contract-caller)))

    (excess-reserves (unwrap-panic (get-excess-reserves)))
  )
    (asserts! (var-get contract-is-enabled) (err ERR-CONTRACT-DISABLED))
    (asserts! minter-enabled (err ERR-NOT-AUTHORIZED))
    (asserts! (<= amount excess-reserves) (err ERR-INSUFFICIENT-RESERVES))

    (contract-call? .lydian-token mint recipient amount)
  )
)

;; ------------------------------------------
;; Managerial
;; ------------------------------------------

(define-public (audit-reserve-token (token-trait <ft-trait>) (value-calculator <value-calculator-trait>))
  (let (
    (token-balance (unwrap-panic (contract-call? token-trait get-balance (as-contract tx-sender))))
    (token-value (unwrap-panic (get-token-value token-trait value-calculator token-balance)))

    (token (contract-of token-trait))
    (token-map (get-reserve-token (contract-of token-trait)))
    (token-last-total-value (get last-total-token-value token-map))

    (new-total-reserves (+ (- (var-get total-reserves) token-last-total-value) token-value))
  )
    (asserts! (get enabled token-map) (err ERR-RESERVE-TOKEN-NOT-ENABLED))
    (asserts! (is-eq (get value-calculator token-map) (contract-of value-calculator)) (err ERR-RESERVE-TOKEN-NOT-ENABLED))

    (map-set reserve-tokens { token: token } (merge token-map { last-total-token-value: token-value }))
    (var-set total-reserves new-total-reserves)

    (ok new-total-reserves)
  )
)

;; ------------------------------------------
;; Value
;; ------------------------------------------

(define-public (get-excess-reserves)
  (let (
    (total-supply (unwrap-panic (contract-call? .lydian-token get-total-supply)))
    (supply-min-debt (- total-supply (var-get total-debt)))
  )
    (if (>= (var-get total-reserves) supply-min-debt)
      (ok (- (var-get total-reserves) supply-min-debt))
      (ok u0)
    )
  )
)

(define-public (get-token-value (token-trait <ft-trait>) (value-calculator <value-calculator-trait>) (amount uint))
  (let (
    (token-map (get-reserve-token (contract-of token-trait)))
  )
    (if (and 
      (is-eq (contract-of value-calculator) (get value-calculator token-map))
      (get enabled token-map)
    )
      (contract-call? value-calculator get-valuation token-trait amount)
      (ok u0)
    )
  )
)

;; ------------------------------------------
;; Deposits
;; ------------------------------------------

(define-public (deposit (token-trait <ft-trait>) (value-calculator <value-calculator-trait>) (amount uint) (profit uint))
  (let (
    (depositor tx-sender)
    (token-map (get-reserve-token (contract-of token-trait)))
    (value (unwrap-panic (get-token-value token-trait value-calculator amount)))
    (value-min-profit (- value profit))

    (depositor-enabled (get enabled (get-depositor-info depositor (contract-of token-trait))))
  )
    (asserts! (var-get contract-is-enabled) (err ERR-CONTRACT-DISABLED))
    (asserts! depositor-enabled (err ERR-DEPOSITOR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of value-calculator) (get value-calculator token-map)) (err ERR-WRONG-VALUE-CALCULATOR))

    (try! (contract-call? token-trait transfer amount depositor (as-contract tx-sender) none))

    (try! (as-contract (contract-call? .lydian-token mint depositor value-min-profit)))

    (try! (audit-reserve-token token-trait value-calculator))

    (ok amount)
  )
)

(define-public (withdraw (token-trait <ft-trait>) (value-calculator <value-calculator-trait>) (amount uint))
  (let (
    (depositor tx-sender)
    (token-map (get-reserve-token (contract-of token-trait)))
    (value (unwrap-panic (get-token-value token-trait value-calculator amount)))

    (depositor-enabled (get enabled (get-depositor-info depositor (contract-of token-trait))))
  )
    (asserts! (var-get contract-is-enabled) (err ERR-CONTRACT-DISABLED))
    (asserts! depositor-enabled (err ERR-DEPOSITOR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of value-calculator) (get value-calculator token-map)) (err ERR-WRONG-VALUE-CALCULATOR))

    (try! (as-contract (contract-call? token-trait transfer amount (as-contract tx-sender) depositor none)))

    (try! (as-contract (contract-call? .lydian-token burn depositor value)))

    (try! (audit-reserve-token token-trait value-calculator))

    (ok amount)
  )
)

;; ------------------------------------------
;; Debt
;; ------------------------------------------

(define-public (incur-debt (token-trait <ft-trait>) (value-calculator <value-calculator-trait>) (amount uint))
  (let (
    (debtor tx-sender)
    (token-map (get-reserve-token (contract-of token-trait)))
    (value (unwrap-panic (get-token-value token-trait value-calculator amount)))
    (debtor-enabled (get enabled (get-debtor-info debtor (contract-of token-trait))))

    (available-debt (unwrap-panic (contract-call? .staked-lydian-token get-available-debt debtor)))
  )
    (asserts! (var-get contract-is-enabled) (err ERR-CONTRACT-DISABLED))
    (asserts! debtor-enabled (err ERR-DEBTOR-NOT-AUTHORIZED))
    (asserts! (>= available-debt value) (err ERR-EXCEED-DEBT-LIMIT))
    (asserts! (is-eq (contract-of value-calculator) (get value-calculator token-map)) (err ERR-WRONG-VALUE-CALCULATOR))

    (try! (as-contract (contract-call? .staked-lydian-token change-debt value debtor true)))

    (var-set total-debt (+ (var-get total-debt) value))

    (try! (as-contract (contract-call? token-trait transfer amount (as-contract tx-sender) debtor none)))

    (try! (audit-reserve-token token-trait value-calculator))

    (ok amount)
  )
)

(define-public (repay-debt (token-trait <ft-trait>) (value-calculator <value-calculator-trait>) (amount uint))
  (let (
    (debtor tx-sender)
    (token-map (get-reserve-token (contract-of token-trait)))
    (value (unwrap-panic (get-token-value token-trait value-calculator amount)))
    (debtor-enabled (get enabled (get-debtor-info debtor (contract-of token-trait))))
  )
    (asserts! (var-get contract-is-enabled) (err ERR-CONTRACT-DISABLED))
    (asserts! debtor-enabled (err ERR-DEBTOR-NOT-AUTHORIZED))
    (asserts! (is-eq (contract-of value-calculator) (get value-calculator token-map)) (err ERR-WRONG-VALUE-CALCULATOR))

    (try! (as-contract (contract-call? .staked-lydian-token change-debt value debtor false)))

    (var-set total-debt (- (var-get total-debt) value))

    (try! (contract-call? token-trait transfer amount debtor (as-contract tx-sender) none))

    (try! (audit-reserve-token token-trait value-calculator))

    (ok amount)
  )
)

;; ---------------------------------------------------------
;; Admin
;; ---------------------------------------------------------

(define-public (transfer-tokens (token <ft-trait>) (amount uint) (recipient principal) (value-calculator <value-calculator-trait>))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))
    (try! (as-contract (contract-call? token transfer amount (as-contract tx-sender) recipient none)))
    (ok true)
  )
)

(define-public (enable-reserve-token (token-trait <ft-trait>) (liquidity-token bool) (value-calculator principal))
  (let (
    (token (contract-of token-trait))
    (token-map (get-reserve-token token))
  )
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))

    (map-set reserve-tokens { token: token } (merge token-map { 
      enabled: true, 
      liquidity-token: liquidity-token, 
      value-calculator: value-calculator 
    }))
    (ok true)
  )
)

(define-public (disable-reserve-token (token-trait <ft-trait>))
  (let (
    (token (contract-of token-trait))
    (token-map (get-reserve-token token))
  )
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))

    (map-set reserve-tokens { token: token } (merge token-map { enabled: false }))
    (ok true)
  )
)

(define-public (set-active-minter-info (minter principal) (enabled bool))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))

    (map-set minter-info { minter: minter } { enabled: enabled })
    (ok true)
  )
)

(define-public (set-debtor-info (debtor principal) (token principal) (enabled bool))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))

    (map-set debtor-info { debtor: debtor, token: token } { enabled: enabled })
    (ok true)
  )
)

(define-public (set-depositor-info (depositor principal) (token principal) (enabled bool))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))

    (map-set depositor-info { depositor: depositor, token: token } { enabled: enabled })
    (ok true)
  )
)

(define-public (set-contract-is-enabled (enabled bool))
  (begin
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))
    (var-set contract-is-enabled enabled)
    (ok true)
  )
)

(define-public (migrate-funds (token-trait <ft-trait>) (recipient principal))
  (let (
    (token-balance (unwrap-panic (contract-call? token-trait get-balance (as-contract tx-sender))))
  )
    (asserts! (is-eq tx-sender .lydian-dao) (err ERR-NOT-AUTHORIZED))
    
    ;; Transfer token
    (if (> token-balance u0)
      (try! (as-contract (contract-call? token-trait transfer token-balance (as-contract tx-sender) recipient none)))
      true
    )

    (ok true)
  )
)
