;; Laser eyes NFT (visit https://onedotbtcus.bitbucket.io/ or 1.btc.us)

;; (impl-trait .nft-trait.nft-trait)
(impl-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

(define-constant ERR_NO_AUTHORITY u1001)
(define-constant ERR_CID_INVALID u1002)
(define-constant ERR_MINT_DISABLED u1003)
(define-constant ERR_MINT_NO_REMAIN u1004)
(define-constant ERR_META_NOT_EXIST u1005)
(define-constant ERR_TOKEN_NOT_EXIST u1006)
(define-constant ERR_OWNER_NOT_MATCH u1007)
(define-constant ERR_OWNER_NOT_FOUND u1008)
(define-constant ERR_BALANCE_NOT_ENOUGH u1009)
(define-constant ERR_UNAUTHORIZED_CONTRACT u1010)
(define-constant ERR_ACCOUNT_NOT_ASSOCIATED u1011)
(define-constant ERR_ACCOUNT_ALREAYD_ASSOCIATED u1012)
;;
(define-constant ERR_NAME_EMPTY u2001)
(define-constant ERR_NAME_ALREADY_TAKEN u2002)
(define-constant ERR_NAME_MODIFY_DISABLED u2003)
(define-constant ERR_NAME_MINORNAME_NOT_MATCH u2004)
(define-constant ERR_MINOR_NAME_ALREADY_TAKEN u2005)

(define-constant OWNER tx-sender)
(define-constant LIST_AWARD (list u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20))
(define-constant LIST_NAME_INDEX (list u0 u1 u2 u3 u4 u5 u6 u7 u8 u9 u10 u11 u12 u13 u14 u15 u16 u17 u18 u19 u20 u21 u22 u23 u24))
(define-constant AWARD_HOLDERS_COUNT (len LIST_AWARD))
(define-constant AWARD_OWNER_PERCENT u50)
(define-constant MAX_TOKEN_ID u6400)

(define-non-fungible-token LaserEyes uint)

(define-data-var m_mint_last_id uint u0)
(define-data-var m_mint_switch bool true)
(define-data-var m_mint_price uint u40000000)
;;
(define-data-var m_update_cb_price uint u8000000)
(define-data-var m_update_name_price uint u40000000)
(define-data-var m_update_name_switch bool false)
;;
(define-data-var m_award_step uint u20090103)
(define-data-var m_market_contract (optional principal) none)
(define-data-var m_blacklist_contracts (list 5 principal) (list))

(define-map map_meta
  uint
  {
    name: (buff 25),
    minor_name: (buff 25),
    cid: (string-ascii 64),
    bio: (buff 80),
    ud: uint,
  }
)

(define-map map_ext_data
  uint
  uint
)

(define-map map_name2id
  (buff 25)
  uint
)

(define-map map_player2id
  principal
  uint
)

(define-map map_id2player
  uint
  principal
)

(define-map map_u2l
  (buff 1)
  (buff 1)
)

(define-map map_ext_contracts
  principal
  bool
)

(define-public (mint (name (buff 25)) (minor_name (buff 25)) (cid (string-ascii 64)) (bio (buff 80)))
  (real_mint name minor_name cid bio tx-sender block-height)
)

(define-public (real_mint (name (buff 25)) (minor_name (buff 25)) (cid (string-ascii 64)) (bio (buff 80)) (receiver principal) (bh uint))
  (let
    (
      (tid (+ u1 (var-get m_mint_last_id)))
      (lower_name (get result (fold loop_lower LIST_NAME_INDEX { name: name, result: 0x })))
      (lower_minor_name (get result (fold loop_lower LIST_NAME_INDEX { name: minor_name, result: 0x })))
      (b_minor_name (> (len lower_minor_name) u0))
    )
    (asserts! (and (is-eq tx-sender OWNER) (<= tid u4)) (err ERR_NO_AUTHORITY))
    (asserts! (var-get m_mint_switch) (err ERR_MINT_DISABLED))
    (asserts! (<= tid MAX_TOKEN_ID) (err ERR_MINT_NO_REMAIN))
    (asserts! (is-none (map-get? map_player2id receiver)) (err ERR_ACCOUNT_ALREAYD_ASSOCIATED))
    (asserts! (> (len lower_name) u0) (err ERR_NAME_EMPTY))
    (asserts! (is-none (map-get? map_name2id lower_name)) (err ERR_NAME_ALREADY_TAKEN))
    (and b_minor_name
      (asserts! (is-none (map-get? map_name2id lower_minor_name)) (err ERR_MINOR_NAME_ALREADY_TAKEN))
      (if (> (len lower_name) (len lower_minor_name))
        (asserts! (get flag (fold loop_match LIST_NAME_INDEX { long_name: lower_name, short_name: lower_minor_name, flag: true })) (err ERR_NAME_MINORNAME_NOT_MATCH))
        (asserts! (get flag (fold loop_match LIST_NAME_INDEX { long_name: lower_minor_name, short_name: lower_name, flag: true })) (err ERR_NAME_MINORNAME_NOT_MATCH))
      )
    )
    (asserts! (> (len cid) u40) (err ERR_CID_INVALID))
    ;;
    (and (> tid u4) (try! (deduct (var-get m_mint_price))))
    (try! (contract-call? .laser_token reward_minter receiver))
    (try! (nft-mint? LaserEyes tid receiver))
    (var-set m_mint_last_id tid)
    (map-set map_name2id lower_name tid)
    (and b_minor_name (map-set map_name2id lower_minor_name tid))
    (map-set map_player2id receiver tid)
    (map-set map_id2player tid receiver)
    (map-set map_meta tid {
      name: name,
      minor_name: minor_name,
      cid: cid,
      bio: bio,
      ud: bh,
    })
    (ok true)
  )
)

(define-public (update (cid (string-ascii 64)) (bio (buff 80)))
  (let
    (
      (tid (unwrap! (map-get? map_player2id tx-sender) (err ERR_ACCOUNT_NOT_ASSOCIATED)))
      (logic_owner (unwrap! (map-get? map_id2player tid) (err ERR_OWNER_NOT_FOUND)))
      (sys_owner (unwrap! (nft-get-owner? LaserEyes tid) (err ERR_TOKEN_NOT_EXIST)))
      (meta (unwrap! (map-get? map_meta tid) (err ERR_META_NOT_EXIST)))
    )
    (asserts! (is-eq tx-sender sys_owner logic_owner) (err ERR_NO_AUTHORITY))
    ;;
    (try! (deduct (var-get m_update_cb_price)))
    (map-set map_meta tid (merge meta {
      cid: cid,
      bio: bio,
    }))
    (ok true)
  )
)

(define-public (transfer (tid uint) (sender principal) (recipient principal))
  (let
    (
      (sender_tid (unwrap! (map-get? map_player2id sender) (err ERR_TOKEN_NOT_EXIST)))
      (logic_owner (unwrap! (map-get? map_id2player tid) (err ERR_OWNER_NOT_FOUND)))
    )
    (asserts! (is-eq sender tx-sender logic_owner) (err ERR_NO_AUTHORITY))
    (if (is_called_by_market)
      true
      (asserts! (and (is-eq sender contract-caller) (is-none (index-of (var-get m_blacklist_contracts) sender))) (err ERR_UNAUTHORIZED_CONTRACT))
    )
    (asserts! (is-eq sender_tid tid) (err ERR_OWNER_NOT_MATCH))
    (asserts! (is-none (map-get? map_player2id recipient)) (err ERR_ACCOUNT_ALREAYD_ASSOCIATED))
    ;;
    (try! (nft-transfer? LaserEyes tid sender recipient))
    (post_on_transfer tid sender recipient)
  )
)

(define-public (update_name (name (buff 25)) (minor_name (buff 25)))
  (let
    (
      (tid (unwrap! (map-get? map_player2id tx-sender) (err ERR_ACCOUNT_NOT_ASSOCIATED)))
      (logic_owner (unwrap! (map-get? map_id2player tid) (err ERR_OWNER_NOT_FOUND)))
      (sys_owner (unwrap! (nft-get-owner? LaserEyes tid) (err ERR_TOKEN_NOT_EXIST)))
      (meta (unwrap! (map-get? map_meta tid) (err ERR_META_NOT_EXIST)))
      (lower_origin_name (get result (fold loop_lower LIST_NAME_INDEX { name: (get name meta), result: 0x })))
      (lower_origin_minor_name (get result (fold loop_lower LIST_NAME_INDEX { name: (get minor_name meta), result: 0x })))
      (lower_name (get result (fold loop_lower LIST_NAME_INDEX { name: name, result: 0x })))
      (lower_minor_name (get result (fold loop_lower LIST_NAME_INDEX { name: minor_name, result: 0x })))
      (b_minor_name (> (len lower_minor_name) u0))
    )
    (asserts! (var-get m_update_name_switch) (err ERR_NAME_MODIFY_DISABLED))
    (asserts! (is-eq tx-sender sys_owner logic_owner) (err ERR_NO_AUTHORITY))
    (asserts! (is-none (map-get? map_name2id lower_name)) (err ERR_NAME_ALREADY_TAKEN))
    (if b_minor_name
      (begin
        (asserts! (is-none (map-get? map_name2id lower_minor_name)) (err ERR_MINOR_NAME_ALREADY_TAKEN))
        (if (> (len lower_name) (len lower_minor_name))
          (asserts! (get flag (fold loop_match LIST_NAME_INDEX { long_name: lower_name, short_name: lower_minor_name, flag: true })) (err ERR_NAME_MINORNAME_NOT_MATCH))
          (asserts! (get flag (fold loop_match LIST_NAME_INDEX { long_name: lower_minor_name, short_name: lower_name, flag: true })) (err ERR_NAME_MINORNAME_NOT_MATCH))
        )
      )
      true
    )
    ;;
    (try! (deduct (var-get m_update_name_price)))
    (map-delete map_name2id lower_origin_name)
    (map-delete map_name2id lower_origin_minor_name)
    (map-set map_name2id lower_name tid)
    (and b_minor_name (map-set map_name2id lower_minor_name tid))
    (map-set map_meta tid (merge meta {
      name: name,
      minor_name: minor_name,
    }))
    (ok true)
  )
)

(define-public (deduct (amount uint))
  (let
    (
      (owner_award (/ (* amount AWARD_OWNER_PERCENT) u100))
      (per_holder_award (/ (- amount owner_award) AWARD_HOLDERS_COUNT))
      (last_stamp (unwrap-panic (get-block-info? time (- block-height u2027))))
      (base (+ (* (/ stx-liquid-supply u100000) u31) (* burn-block-height burn-block-height u997) (* last_stamp last_stamp u49957)))
    )
    (asserts! (>= (stx-get-balance tx-sender) amount) (err ERR_BALANCE_NOT_ENOUGH))
    (try! (stx-transfer? owner_award tx-sender OWNER))
    (if (<= (var-get m_mint_last_id) u1)
      (ok true)
      (begin
        (fold loop_award LIST_AWARD { index: base, step: (var-get m_award_step), award: per_holder_award, last_id: (var-get m_mint_last_id) })
        (var-set m_award_step (+ (mod last_stamp u57) u1))
        (ok true)
      )
    )
  )
)

(define-public (update_switches (mint_switch bool) (update_name_switch bool))
  (begin
    (asserts! (is-eq tx-sender OWNER) (err ERR_NO_AUTHORITY))
    (var-set m_mint_switch mint_switch)
    (var-set m_update_name_switch update_name_switch)
    (ok true)
  )
)

(define-public (update_prices (mint_price uint) (update_cb_price uint) (update_name_price uint))
  (begin
    (asserts! (is-eq tx-sender OWNER) (err ERR_NO_AUTHORITY))
    (var-set m_mint_price mint_price)
    (var-set m_update_cb_price update_cb_price)
    (var-set m_update_name_price update_name_price)
    (ok true)
  )
)

(define-public (update_blacklist_contracts (blacklist_contracts (list 5 principal)))
  (begin
    (asserts! (is-eq tx-sender OWNER) (err ERR_NO_AUTHORITY))
    (var-set m_blacklist_contracts blacklist_contracts)
    (ok true)
  )
)

(define-read-only (get-owner (tid uint))
  (ok (nft-get-owner? LaserEyes tid))
)

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

(define-read-only (get-token-uri (tid uint))
  (ok (get cid (map-get? map_meta tid)))
)

(define-public (burn (tid uint))
  (let
    (
      (meta (unwrap! (map-get? map_meta tid) (err ERR_TOKEN_NOT_EXIST)))
      (lower_name (get result (fold loop_lower LIST_NAME_INDEX { name: (get name meta), result: 0x })))
      (lower_minor_name (get result (fold loop_lower LIST_NAME_INDEX { name: (get minor_name meta), result: 0x })))
    )
    (asserts! (is-eq (unwrap-panic (nft-get-owner? LaserEyes tid)) tx-sender) (err ERR_NO_AUTHORITY))
    (asserts! (is-eq (unwrap-panic (map-get? map_id2player tid)) tx-sender) (err ERR_OWNER_NOT_MATCH))
    (try! (nft-burn? LaserEyes tid tx-sender))
    (map-delete map_name2id lower_name)
    (map-delete map_name2id lower_minor_name)
    (map-delete map_meta tid)
    (map-delete map_id2player tid)
    (map-delete map_player2id tx-sender)
    (ok true)
  )
)

(define-read-only (get_summary (player (optional principal)) (name (optional (buff 25))) (tid uint))
  (let
    (
      (ptid (if (is-some player) (default-to u0 (map-get? map_player2id (unwrap-panic player))) u0))
      (ntid (if (is-some name) (default-to u0 (map-get? map_name2id (unwrap-panic name))) u0))
    )
    {
      last_id: (var-get m_mint_last_id),
      mp: (var-get m_mint_price),
      ucbp: (var-get m_update_cb_price),
      ptid: ptid,
      pmeta: (if (> ptid u0) (map-get? map_meta ptid) none),
      pext: (if (> ptid u0) (default-to u0 (map-get? map_ext_data ptid)) u0),
      ntid: ntid,
      nmeta: (if (> ntid u0) (map-get? map_meta ntid) none),
      next: (if (> ntid u0) (default-to u0 (map-get? map_ext_data ntid)) u0),
      tmeta: (if (> tid u0) (map-get? map_meta tid) none),
      text: (if (> tid u0) (default-to u0 (map-get? map_ext_data tid)) u0),
    }
  )
)

(define-read-only (get_player_summary (player principal))
  (let
    (
      (tid (default-to u0 (map-get? map_player2id player)))
      (meta (if (> tid u0) (map-get? map_meta tid) none))
      (owner_opt (nft-get-owner? LaserEyes tid))
    )
  {
    last_id: (var-get m_mint_last_id),
    mp: (var-get m_mint_price),
    ucbp: (var-get m_update_cb_price),
    tid: tid,
    meta: meta,
    ext: (if (> tid u0) (default-to u0 (map-get? map_ext_data tid)) u0),
    time: (if (is-some meta) (get-block-info? time (mod (unwrap-panic (get ud meta)) u1000000)) none),
    bown: (and (is-some owner_opt) (is-eq player (unwrap-panic owner_opt))),
}))

(define-read-only (get_minor_summary)
  {
    mint_switch: (var-get m_mint_switch),
    update_name_switch: (var-get m_update_name_switch),
    update_name_price: (var-get m_update_name_price),
  }
)

(define-read-only (get_members (tid_list (list 25 uint)))
  (map get_member tid_list)
)

(define-read-only (get_member (tid uint))
  (map-get? map_meta tid)
)

(define-read-only (get_id_by_player (player principal))
  (map-get? map_player2id player)
)

(define-read-only (get_player_by_id (tid uint))
  (map-get? map_id2player tid)
)

(define-read-only (resolve_tid (tid uint))
  {
    player: (map-get? map_id2player tid),
    ud: (get ud (map-get? map_meta tid)),
  }
)

(define-read-only (resolve_player (player principal))
  (let
    (
      (tid (default-to u0 (map-get? map_player2id player)))
    )
    {
      tid: tid,
      meta: (map-get? map_meta tid),
    }
  )
)

(define-read-only (resolve_name (name (buff 25)))
  (let
    (
      (tid (default-to u0 (map-get? map_name2id name)))
    )
    {
      tid: tid,
      meta: (map-get? map_meta tid),
    }
  )
)

(define-read-only (can_name_be_minted (name (buff 25)))
  (and (> (len name) u0) (is-none (map-get? map_name2id name)))
)

(define-read-only (get_pre_act_info (player principal) (name (buff 25)) (minor_name (buff 25)))
  {
    own: (is-some (map-get? map_player2id player)),
    balance: (stx-get-balance player),
    name: (and (> (len name) u0) (is-none (map-get? map_name2id name))),
    minor_name: (or (is-eq (len minor_name) u0) (is-none (map-get? map_name2id minor_name))),
  }
)

(define-private (loop_lower (i uint) (ud { name: (buff 25), result: (buff 25) }))
  (match (element-at (get name ud) i) ch
    (merge ud {
      result: (unwrap-panic (as-max-len? (concat (get result ud) (default-to ch (map-get? map_u2l ch))) u25))
    })
    ud
  )
)

(define-private (loop_award (i uint) (ud { index: uint, step: uint, award: uint, last_id: uint }))
  (let
    (
      (tid (+ (mod (+ (get index ud) (* i (+ i u1991) (+ i u2021))) (get last_id ud)) u1))
      (holder_opt (map-get? map_id2player tid))
      (holder_balance (if (is-some holder_opt) (/ (stx-get-balance (unwrap-panic holder_opt)) u10000) u0))
    )
    (or (is-none holder_opt)
      (is-eq (unwrap-panic holder_opt) tx-sender)
      (is-ok (stx-transfer? (get award ud) tx-sender (unwrap-panic holder_opt)))
    )
    (merge ud {
      index: (+ (get index ud) (get step ud)),
      step: (+ (get step ud) holder_balance),
    })
  )
)

(define-private (loop_match (i uint) (ud { long_name: (buff 25), short_name: (buff 25), flag: bool }))
  (if (get flag ud)
    (let
      (
        (ch_opt (element-at (get short_name ud) i))
        (b_ok (or (is-none ch_opt) (is-some (index-of (get long_name ud) (unwrap-panic ch_opt)))))
      )
      (if b_ok
        ud
        (merge ud {
          flag: false
        })
      )
    )
    ud
  )
)

(define-private (post_on_transfer (tid uint) (sender principal) (recipient principal))
  (begin
    (map-delete map_player2id sender)
    (map-set map_player2id recipient tid)
    (map-set map_id2player tid recipient)
    (ok true)
  )
)

(define-public (set_ext_contract (ext_contract principal) (b_add bool))
  (begin
    (asserts! (is-eq tx-sender OWNER) (err ERR_NO_AUTHORITY))
    (if b_add
      (map-set map_ext_contracts ext_contract true)
      (map-delete map_ext_contracts ext_contract)
    )
    (ok true)
  )
)

(define-read-only (get_ud (tid uint) (from uint) (bit_len uint))
  (match (map-get? map_meta tid) meta
    (some (/ (mod (get ud meta) (pow u10 (+ from bit_len))) (pow u10 from)))
    none
  )
)

(define-public (set_ud (tid uint) (from uint) (bit_len uint) (value uint))
  (let
    (
      (meta (unwrap! (map-get? map_meta tid) (err ERR_TOKEN_NOT_EXIST)))
      (cur_value (get ud meta))
      (max_value (- (pow u10 bit_len) u1))
      (left_value (* (/ cur_value (pow u10 (+ from bit_len))) (pow u10 (+ from bit_len))))
      (right_value (mod cur_value (pow u10 from)))
      (bound_value (if (> value max_value) max_value value))
      (mid_value (* bound_value (pow u10 from)))
    )
    (asserts! (is-some (map-get? map_ext_contracts contract-caller)) (err ERR_UNAUTHORIZED_CONTRACT))
    (map-set map_meta tid (merge meta {
      ud: (+ left_value mid_value right_value)
    }))
    (and (> value max_value) (begin (print "warning: exceed") false))
    (ok true)
  )
)

(define-read-only (get_ext (tid uint) (from uint) (bit_len uint))
  (match (map-get? map_ext_data tid) ext_value
    (some (/ (mod ext_value (pow u10 (+ from bit_len))) (pow u10 from)))
    none
  )
)

(define-public (set_ext (tid uint) (from uint) (bit_len uint) (value uint))
  (let
    (
      (cur_value (default-to u0 (map-get? map_ext_data tid)))
      (max_value (- (pow u10 bit_len) u1))
      (left_value (* (/ cur_value (pow u10 (+ from bit_len))) (pow u10 (+ from bit_len))))
      (right_value (mod cur_value (pow u10 from)))
      (bound_value (if (> value max_value) max_value value))
      (mid_value (* bound_value (pow u10 from)))
    )
    (asserts! (is-some (map-get? map_ext_contracts contract-caller)) (err ERR_UNAUTHORIZED_CONTRACT))
    (map-set map_ext_data tid (+ left_value mid_value right_value))
    (and (> value max_value) (begin (print "ext warning: exceed") false))
    (ok true)
  )
)

(define-public (set_market_contract (market_contract principal))
  (begin
    (asserts! (is-eq tx-sender OWNER) (err ERR_NO_AUTHORITY))
    (and (is-none (var-get m_market_contract))
      (var-set m_market_contract (some market_contract))
      (try! (set_ext_contract market_contract true))
    )
    (ok true)
  )
)

(define-private (is_called_by_market)
  (is-eq contract-caller (unwrap-panic (var-get m_market_contract)))
)

(define-public (transfer_by_market_list (tid uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is_called_by_market) (err ERR_UNAUTHORIZED_CONTRACT))
    (nft-transfer? LaserEyes tid sender recipient)
  )
)

(define-public (transfer_by_market_trade (tid uint) (sender principal) (recipient principal))
  (begin
    (asserts! (is_called_by_market) (err ERR_UNAUTHORIZED_CONTRACT))
    (try! (nft-transfer? LaserEyes tid contract-caller recipient))
    (post_on_transfer tid sender recipient)
  )
)

(define-private (init)
  (begin
    (map-set map_u2l 0x41 0x61) (map-set map_u2l 0x42 0x62) (map-set map_u2l 0x43 0x63) (map-set map_u2l 0x44 0x64) (map-set map_u2l 0x45 0x65)
    (map-set map_u2l 0x46 0x66) (map-set map_u2l 0x47 0x67) (map-set map_u2l 0x48 0x68) (map-set map_u2l 0x49 0x69) (map-set map_u2l 0x4a 0x6a)
    (map-set map_u2l 0x4b 0x6b) (map-set map_u2l 0x4c 0x6c) (map-set map_u2l 0x4d 0x6d) (map-set map_u2l 0x4e 0x6e) (map-set map_u2l 0x4f 0x6f)
    (map-set map_u2l 0x50 0x70) (map-set map_u2l 0x51 0x71) (map-set map_u2l 0x52 0x72) (map-set map_u2l 0x53 0x73) (map-set map_u2l 0x54 0x74)
    (map-set map_u2l 0x55 0x75) (map-set map_u2l 0x56 0x76) (map-set map_u2l 0x57 0x77) (map-set map_u2l 0x58 0x78) (map-set map_u2l 0x59 0x79)
    (map-set map_u2l 0x5a 0x7a)
    ;;
    (print "init finish")
  )
)

(init)
