(use-trait ft-trait .trait-sip-010.sip-010-trait)

(define-constant ERR-NOT-AUTHORIZED (err u1000))
(define-constant ERR-SCHEDULE-NOT-FOUND (err u1001))
(define-constant ERR-PAUSED (err u1002))
(define-constant ERR-TOKEN-MISMATCH (err u1004))
(define-constant ERR-VESTING-NOT-STARTED (err u1005))
(define-constant ERR-INVALID-VESTING-DETAILS (err u1006))

(define-constant ONE_8 u100000000)

(define-data-var contract-owner principal tx-sender)

(define-map vestings uint { multiplier: uint, start: uint, end: uint })
(define-map checkpoints { launch-id: uint, user: principal } uint)

(define-data-var paused bool true)

;; read-only calls

(define-read-only (get-contract-owner)
  (var-get contract-owner))

(define-read-only (get-vesting-or-fail (launch-id uint))
    (ok (unwrap! (map-get? vestings launch-id) ERR-SCHEDULE-NOT-FOUND)))

(define-read-only (get-checkpoint-or-fail (launch-id uint) (user principal))
    (let (
            (vesting-details (try! (get-vesting-or-fail launch-id))))
        (match (map-get? checkpoints { launch-id: launch-id, user: user })
            some-value (ok some-value)
            (ok (get start vesting-details)))))

(define-read-only (is-paused)
  (var-get paused))

(define-read-only (get-user-stats (launch-id uint) (user principal))
    (let (
            (vesting-details (try! (get-vesting-or-fail launch-id)))
            (launch-details (try! (contract-call? .alex-launchpad-v1-7 get-launch-or-fail launch-id)))
            (tickets-won (contract-call? .alex-launchpad-v1-7 get-tickets-won launch-id user))
            (total-amount (* tickets-won (get launch-tokens-per-ticket launch-details) (get multiplier vesting-details)))
            (vesting-amount-per-block (div-down total-amount (* (- (get end vesting-details) (get start vesting-details)) ONE_8)))
            (checkpoint (try! (get-checkpoint-or-fail launch-id user)))
            (new-checkpoint (max (get start vesting-details) (min (get end vesting-details) block-height)))
            (vested-amount (* (- new-checkpoint checkpoint) vesting-amount-per-block))
            (claimed-amount (* (- checkpoint (get start vesting-details)) vesting-amount-per-block)))
        (ok {
          available-amount: vested-amount, total-amount: total-amount, claimed-amount: claimed-amount, 
          checkpoint: checkpoint, new-checkpoint: new-checkpoint, 
          start-block: (get start vesting-details), end-block: (get end vesting-details),
          launch-token: (get launch-token launch-details) })))

;; governance calls

(define-public (set-contract-owner (owner principal))
  (begin
    (try! (check-is-owner))
    (ok (var-set contract-owner owner))))

(define-public (transfer (token-trait <ft-trait>) (amount uint) (recipient principal))
    (begin
        (try! (check-is-owner))
        (as-contract (contract-call? token-trait transfer-fixed amount tx-sender recipient none))))

(define-public (set-vesting (launch-id uint) (details { multiplier: uint, start: uint, end: uint }) (token-trait <ft-trait>))
    (let (
            (launch-details (try! (contract-call? .alex-launchpad-v1-7 get-launch-or-fail launch-id)))
            (total-tickets-won (contract-call? .alex-launchpad-v1-7 get-total-tickets-won launch-id))
            (total-amount (* total-tickets-won (get launch-tokens-per-ticket launch-details) (get multiplier details))))
        (try! (check-is-owner))
        (asserts! (is-eq (contract-of token-trait) (get launch-token launch-details)) ERR-TOKEN-MISMATCH)
        (asserts! (> (get end details) (get start details)) ERR-INVALID-VESTING-DETAILS)
        (map-set vestings launch-id details)
        (print { notification: "set-vesting", payload: { launch-id: launch-id, details: details } })
        (contract-call? token-trait transfer-fixed total-amount tx-sender (as-contract tx-sender) none)))

(define-public (pause (new-paused bool))
  (begin
    (try! (check-is-owner))
    (ok (var-set paused new-paused))))

;; public calls

(define-public (claim (launch-id uint) (user principal) (token-trait <ft-trait>))
    (let (
            (user-stats (try! (get-user-stats launch-id user))))
        (asserts! (not (is-paused)) ERR-PAUSED)
        (asserts! (> block-height (get start-block user-stats)) ERR-VESTING-NOT-STARTED)
        (asserts! (is-eq (contract-of token-trait) (get launch-token user-stats)) ERR-TOKEN-MISMATCH)

        (map-set checkpoints { launch-id: launch-id, user: user } (get new-checkpoint user-stats))
        (and (> (get available-amount user-stats) u0) (try! (as-contract (contract-call? token-trait transfer-fixed (get available-amount user-stats) tx-sender user none))))

        (print { notification: "claim", payload: { launch-id: launch-id, user: user, amount: (get available-amount user-stats) } })
        (ok true)))

(define-public (claim-many (launch-ids (list 200 uint)) (user (list 200 principal)) (token-trait (list 200 <ft-trait>)))
  (fold check-err (map claim launch-ids user token-trait) (ok true)))

;; private calls

(define-private (check-is-owner)
  (ok (asserts! (is-eq tx-sender (var-get contract-owner)) ERR-NOT-AUTHORIZED)))

(define-private (mul-down (a uint) (b uint))
    (/ (* a b) ONE_8))

(define-private (div-down (a uint) (b uint))
  (if (is-eq a u0) u0 (/ (* a ONE_8) b)))

(define-private (min (a uint) (b uint))
    (if (<= a b) a b))

(define-private (max (a uint) (b uint))
    (if (>= a b) a b))

(define-private (check-err (result (response bool uint)) (prior (response bool uint)))
    (match prior
        ok-value result
        err-value (err err-value)))
