;; Board
;; Control contract for all badger board canvases
;; Written by StrataLabs

;; Board
;; The Board represents the 5000-tile canvas that users can draw on (place single tiles)
;; For each tile, the Board stores the color, collection, & owner (principal that placed the tile).

;; Round
;; Every month (4320 blocks), the Board is reset & a new Round begins.

;; Existing Badger collections
;; Badger -> SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.btc-badgers-v2
;; Baby -> SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.baby-badgers


;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Cons, Vars & Maps ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;
;;;;;;;;;;;;
;;; Cons ;;;
;;;;;;;;;;;;
;;;;;;;;;;;;

(use-trait nft-trait 'SP2PABAF9FTAJYNFZH93XENAJ8FVY99RRM50D2JG9.nft-trait.nft-trait)

;; The max amount of possible canvas tiles
(define-constant tiles-in-canvas u5000)

;; Whitelisted badger collections
;; (define-data-var name type value)

;; Replacement fee in stx (25 stx)
(define-constant replacement-fee u25000000)

;; Deployer address for fees
(define-constant deployer tx-sender)

;; Canvas duration in blocks (4320 blocks = 1 month)
(define-constant canvas-duration u4320)


;;;;;;;;;;;;
;;;;;;;;;;;;
;;; Errs ;;;
;;;;;;;;;;;;
;;;;;;;;;;;;
(define-constant ERR-END-HEIGHT-NOT-REACHED (err u0))
(define-constant ERR-UNWRAP-META (err u1))
(define-constant ERR-UNWRAP-TILE (err u2))
(define-constant ERR-NOT-ENOUGH-TILES (err u3))
(define-constant ERR-INVALID-POSITION (err u4))
(define-constant ERR-POSITION-NOT-EMPTY (err u5))
(define-constant ERR-NOT-OWNER (err u6))
(define-constant ERR-WRONG-COLLECTION-COLOR (err u7))
(define-constant ERR-TILE-LIST-OVERFLOW (err u8))
(define-constant ERR-SPEND-TILE (err u9))
(define-constant ERR-NOT-AUTH (err u10))
(define-constant ERR-NOT-ENOUGH-COLORS (err u11))
(define-constant ERR-INDEX-ISSUE (err u12))
(define-constant ERR-CANVAS-ALREADY-MINTED (err u12))
(define-constant ERR-CANVAS-NOT-MINTED (err u13))
(define-constant ERR-ADMIN-ALREADY-ADDED (err u14))
(define-constant ERR-ADMIN-LIST-OVERFLOW (err u15))



;;;;;;;;;;;;
;;;;;;;;;;;;
;;; Vars ;;;
;;;;;;;;;;;;
;;;;;;;;;;;;

;; @desc - Var that keeps track of the active canvas index
(define-data-var canvas-index uint u1)

;; @desc - Var that keeps track of current Badger colors
(define-data-var badger-colors (list 8 (string-ascii 6)) (list "ffffff" "000000" "ff0000" "00ff00" "0000ff" "ffff00" "00ffff" "ff00ff"))

;; @desc - Var list of principals that are considered admins
(define-data-var admins (list 10 principal) (list tx-sender))

;; @desc - Var list of Badger collections
(define-data-var badger-collections (list 25 principal) (list 'SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.btc-badgers-v2 'SP27F9EJH20K3GT6GHZG0RD08REZKY2TDMD6D9M2Z.baby-badgers))


;;;;;;;;;;;;
;;;;;;;;;;;;
;;; Maps ;;;
;;;;;;;;;;;;
;;;;;;;;;;;;

;; @desc - Map that keeps track of a canvas (key: canvas-id/uint) & it's meta properties 
(define-map canvas-meta uint
  {
    charity-name:         (string-ascii 256),
    charity-description:  (string-ascii 256),
    charity-url:          (string-ascii 256),
    placed-tiles:         (list 5000 uint),
    end-height:           uint,
    minted:               bool
  }
)

;; @desc - Map that keeps track of a tile (key: tile-id/uint) & it's competitive properties
(define-map canvas-competition uint {
  badger-placed: uint,
  competitor-placed: uint,
  competitor-collection: principal,
  competitor-colors:    (list 8 (string-ascii 6)),
})

;; @desc - Map that keeps track of a tile in a canvas (key: canvas-id/uint, tile-id/uint) & it's properties (value: color/uint, collection/uint, owner principal)
(define-map tile { canvas: uint, tile: uint } 
  (list 100 
    {
      color: (string-ascii 6),
      collection: (optional principal),
      owner: principal
    }
  )
)



;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Read-Only Funcs ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;



;; Get Canvas Meta Info
;; @desc - Get the current canvas meta
(define-read-only (get-canvas-meta (canvas-id uint))
  (map-get? canvas-meta canvas-id)
)

;; Get Canvas Competition Info
;; @desc - Get the current canvas competition
(define-read-only (get-canvas-competition (canvas-id uint))
  (map-get? canvas-competition canvas-id)
)

;; Get Tile Info
;; @desc - Get the current tile info
(define-read-only (get-tile (canvas-id uint) (tile-id uint))
  (map-get? tile { canvas: canvas-id, tile: tile-id })
)



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Place/Replace Funcs ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;
;; Place Tile ;;
;;;;;;;;;;;;;;;;
;; @desc - Main function for placing a tile on the active canvas
;; @param - Position (uint), Color (string-ascii 6), Collection (optional principal)
;; Collection-id should be optional since user can have purchases ; collection can't be optional because traits can't be optional?, pass Badgers by default
(define-public (place-tile (position uint) (color (string-ascii 6)) (collection <nft-trait>) (collection-id (optional uint))) 
  (let
    (
      (current-canvas-index (var-get canvas-index))
      (current-user-tile-balance (unwrap! (contract-call? .tiles get-total-balance) (err u0)))
      (current-canvas-meta (unwrap! (map-get? canvas-meta current-canvas-index) ERR-UNWRAP-META))
      (current-canvas-end-height (get end-height current-canvas-meta))
      (current-placed-tiles (get placed-tiles current-canvas-meta))
      (current-canvas-competition (unwrap! (map-get? canvas-competition current-canvas-index) ERR-END-HEIGHT-NOT-REACHED))
      (current-competitor (get competitor-collection current-canvas-competition))
      (current-competitor-colors (get competitor-colors current-canvas-competition))
      (current-badger-placement (get badger-placed current-canvas-competition))
      (current-competitor-placement (get competitor-placed current-canvas-competition))
      (current-tile (map-get? tile { canvas: current-canvas-index, tile: position}))
      (current-collection-id (default-to u0 collection-id))
    )

    ;; Assert that the canvas is active / block-height is less than the end-height
    (asserts! (< block-height current-canvas-end-height) ERR-END-HEIGHT-NOT-REACHED)

    ;; Assert that user has a tile to place
    (asserts! (> current-user-tile-balance u0) ERR-NOT-ENOUGH-TILES)

    ;; Assert that the tile position is valid
    (asserts! (< position tiles-in-canvas) ERR-INVALID-POSITION)

    ;; Assert that tile position is empty
    (asserts! (is-none current-tile) ERR-POSITION-NOT-EMPTY)

    ;; Check if collection-id is-some collection parameter is-equal to either current-competitor or badger_contract
    (if (is-some collection-id)

      ;; Provided collection-id / aka might have generated balance
      (begin 

        ;; Assert that tx-sender is-equal to get-owner of collection
        (asserts! (is-eq (some tx-sender) (unwrap! (contract-call? collection get-owner current-collection-id) ERR-END-HEIGHT-NOT-REACHED)) ERR-NOT-OWNER)

        ;; Check if collection is-equal to current-competitor or badger_contract
        (if (is-some (index-of (var-get badger-collections) (contract-of collection)))

          ;; Badger
          (begin 

            ;; Assert that color is in badger-colors
            (asserts! (is-some (index-of (var-get badger-colors) color)) ERR-WRONG-COLLECTION-COLOR)

            ;; Update canvas-competition (Badger)
            (map-set canvas-competition current-canvas-index 
              (merge 
                current-canvas-competition 
                { badger-placed: (+ u1 current-badger-placement) }
              )
            )

            ;; Update tile with first list item
            (map-set tile {canvas: current-canvas-index, tile: position} 
              (list {
                color: color,
                collection: (some (contract-of collection)),
                owner: tx-sender
              })
            )

          )

          ;; Competitor
          (begin 

            ;; Assert that color is in current-competitor-colors
            (asserts! (is-some (index-of (get competitor-colors current-canvas-competition) color)) ERR-WRONG-COLLECTION-COLOR)

            ;; Update canvas-competition (Competitor)
            (map-set canvas-competition current-canvas-index 
              (merge 
                current-canvas-competition 
                { competitor-placed: (+ u1 current-competitor-placement) }
              )
            )

            ;; Update tile with first list item
            (map-set tile {canvas: current-canvas-index, tile: position} 
              (list {
                color: color,
                collection: (some current-competitor),
                owner: tx-sender
              })
            )

          )

        )

      )

      ;; Did not have either collection, set tile
      (map-set tile {canvas: current-canvas-index, tile: position} 
        (list {
          color: color,
          collection: none,
          owner: tx-sender
        })
      )

    )

    ;; Update canvas-meta
    (map-set canvas-meta current-canvas-index 
      (merge 
        current-canvas-meta 
        { placed-tiles: (unwrap! (as-max-len? (append current-placed-tiles position) u5000) ERR-TILE-LIST-OVERFLOW) }
      )
    )

    ;; Subtract tile from user
    (ok (unwrap! (contract-call? .tiles spend-tile) ERR-SPEND-TILE))
  )
)

;;;;;;;;;;;;;;;;;;
;; Replace Tile ;;
;;;;;;;;;;;;;;;;;;
;; @desc - Main function for replacing a tile on the active canvas
;; @param - Position (uint), Color (string-ascii 6), Collection (optional principal)
(define-public (replace-tile (position uint) (color (string-ascii 6)) (collection <nft-trait>) (collection-id (optional uint))) 
  (let 
    (
      (current-canvas-index (var-get canvas-index))
      (current-user-tile-balance (unwrap! (contract-call? .tiles get-total-balance) (err u0)))
      (current-canvas-meta (unwrap! (map-get? canvas-meta current-canvas-index) ERR-UNWRAP-META))
      (current-canvas-end-height (get end-height current-canvas-meta))
      (current-placed-tiles (get placed-tiles current-canvas-meta))
      (current-canvas-competition (unwrap! (map-get? canvas-competition current-canvas-index) ERR-END-HEIGHT-NOT-REACHED))
      (current-competitor (get competitor-collection current-canvas-competition))
      (current-competitor-colors (get competitor-colors current-canvas-competition))
      (current-badger-placement (get badger-placed current-canvas-competition))
      (current-competitor-placement (get competitor-placed current-canvas-competition))
      (current-tile (unwrap! (map-get? tile { canvas: current-canvas-index, tile: position}) ERR-UNWRAP-TILE))
      (current-collection-id (default-to u0 collection-id))
    )

    ;; Assert that the canvas is active / block-height is less than the end-height
    (asserts! (< block-height current-canvas-end-height) ERR-END-HEIGHT-NOT-REACHED)

    ;; Pay replace fee
    (try! (stx-transfer? replacement-fee tx-sender deployer))

    ;; Check if collection-id is-some collection parameter is-equal to either current-competitor or badger_contract
    (ok (if (or (is-eq (contract-of collection) current-competitor) (is-some (index-of (var-get badger-collections) (contract-of collection))))
      
      ;; Provided active collection
      (begin 

        ;; Assert that tx-sender is-equal to get-owner of collection
        (asserts! (is-eq (some tx-sender) (unwrap! (contract-call? collection get-owner current-collection-id) ERR-END-HEIGHT-NOT-REACHED)) ERR-NOT-OWNER)

        ;; Check if collection is-equal to current-competitor or badger_contract
        (if (is-some (index-of (var-get badger-collections) (contract-of collection)))

          ;; Badger
          (begin 

            ;; Assert that color is in badger-colors
            (asserts! (is-some (index-of (var-get badger-colors) color)) ERR-WRONG-COLLECTION-COLOR)

            ;; Update canvas-competition (Badger)
            (map-set canvas-competition current-canvas-index 
              (merge 
                current-canvas-competition 
                { badger-placed: (+ u1 current-badger-placement), competitor-placed: (- current-competitor-placement u1) }
              )
            )

            ;; Update tile by adding new item to list
            (map-set tile {canvas: current-canvas-index, tile: position} 
              (unwrap! (as-max-len? 
                (append current-tile 
                  {
                    color: color,
                    collection: (some (contract-of collection)),
                    owner: tx-sender
                  }
                ) 
              u100) ERR-TILE-LIST-OVERFLOW)
            )

          )

          ;; Competitor
          (begin 

            ;; Assert that color is in current-competitor-colors
            (asserts! (is-some (index-of (get competitor-colors current-canvas-competition) color)) ERR-WRONG-COLLECTION-COLOR)

            ;; Update canvas-competition (Competitor)
            (map-set canvas-competition current-canvas-index 
              (merge 
                current-canvas-competition 
                { badger-placed: (- current-badger-placement u1), competitor-placed: (+ u1 current-competitor-placement) }
              )
            )

            ;; Update tile by adding new item to list
            (map-set tile {canvas: current-canvas-index, tile: position} 
              (unwrap! (as-max-len? 
                (append current-tile 
                  {
                    color: color,
                    collection: (some current-competitor),
                    owner: tx-sender
                  }
                ) 
              u100) ERR-TILE-LIST-OVERFLOW)
            )

          )

        )
      )
      
      ;; No collection
      (map-set tile {canvas: current-canvas-index, tile: position} 
        (unwrap! (as-max-len? 
          (append current-tile 
            {
              color: color,
              collection: none,
              owner: tx-sender
            }
          ) 
        u100) ERR-TILE-LIST-OVERFLOW)
      )
    
    ))
  )
)



;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; Admin Control Funcs ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;;;;;;;;;;;;;;;;;;;;;;
;; Mint Ended Canvas ;;
;;;;;;;;;;;;;;;;;;;;;;;
;; @desc - Mint ended canvas by calling giving-nft contract
(define-public (mint-ended-canvas) 
  (let 
    (
      (current-canvas-index (var-get canvas-index))
      (current-charity-badger-index (unwrap! (contract-call? .giving-nft get-last-token-id) ERR-INDEX-ISSUE))
      (current-canvas-meta (unwrap! (map-get? canvas-meta current-canvas-index) ERR-UNWRAP-META))
      (current-canvas-end-height (get end-height current-canvas-meta))
      (current-canvas-competition (unwrap! (map-get? canvas-competition current-canvas-index) ERR-END-HEIGHT-NOT-REACHED))
      (current-competitor (get competitor-collection current-canvas-competition))
      (current-badger-placement (get badger-placed current-canvas-competition))
      (current-competitor-placement (get competitor-placed current-canvas-competition))
      ;;(canvas-winner (if (> current-badger-placement current-competitor-placement) badger-contract current-competitor))
    )

    ;; Assert that tx-sender is in the admin list
    (asserts! (is-some (index-of (var-get admins) tx-sender)) ERR-NOT-AUTH)

    ;; Assert that the canvas is over / block-height is greater than the end-height
    (asserts! (> block-height current-canvas-end-height) ERR-END-HEIGHT-NOT-REACHED)

    ;; Assert that the canvas is not already minted
    (asserts! (not (get minted current-canvas-meta)) ERR-CANVAS-ALREADY-MINTED)

    ;; Assert that current-canvas-index is-equal to current-charity-badger-index
    (asserts! (is-eq current-canvas-index current-charity-badger-index) ERR-INDEX-ISSUE)

    ;; Mint Charity Badger
    ;;(try! (contract-call? .giving-nft mint-charity-badger canvas-winner (get charity-name current-canvas-meta) (get charity-description current-canvas-meta) (get charity-url current-canvas-meta)))

    ;; Update canvas-meta
    (ok (map-set canvas-meta current-canvas-index 
      (merge 
        current-canvas-meta 
        { minted: true }
      )
    ))

  )
)

;;;;;;;;;;;;;;;;
;; New Canvas ;;
;;;;;;;;;;;;;;;;
;; @desc - Admin function for creating a new canvas
;; @param - Badger Colors (list 6 (string-ascii 6)), Competitor Collection (principal), Competitor Colors (list 6 (string-ascii 6)), charity-name (string-ascii 256), charity-description (string-ascii 256), charity-url (string-ascii 256)
(define-public (new-canvas (new-badger-colors (list 8 (string-ascii 6))) (new-competitor-collection principal) (new-competitor-colors (list 8 (string-ascii 6))) (charity-name (string-ascii 256)) (charity-description (string-ascii 256)) (charity-url (string-ascii 256)))
  (let 
    (
      (current-canvas-index (var-get canvas-index))
      (current-canvas-meta (map-get? canvas-meta current-canvas-index))
      (current-canvas-end-height (get end-height current-canvas-meta))
      (next-canvas-index (+ u1 current-canvas-index))
      (next-end-height (+ block-height canvas-duration))
    )

    ;; Assert that tx-sender is in the admin list
    (asserts! (is-some (index-of (var-get admins) tx-sender)) ERR-NOT-AUTH)

    ;; Assert that badger-colors is-equal to 6
    (asserts! (is-eq (len new-badger-colors) u6) ERR-NOT-ENOUGH-COLORS)

    ;; Assert that competitor-colors is-equal to 6
    (asserts! (is-eq (len new-competitor-colors) u6) ERR-NOT-ENOUGH-COLORS)


    ;; Check if first canvas
    (ok (if (is-eq current-canvas-index u1) 
      ;; First Canvas
      (begin 

          ;; Start canvas-meta
          (map-set canvas-meta current-canvas-index 
            {
              end-height: next-end-height,
              placed-tiles: (list),
              charity-name: charity-name,
              charity-description: charity-description,
              charity-url: charity-url,
              minted: false
            }
          )

        ;; Update canvas-competition
        (map-set canvas-competition current-canvas-index 
          {
            competitor-collection: new-competitor-collection,
            competitor-colors: new-competitor-colors,
            badger-placed: u0,
            competitor-placed: u0
          }
        )
      )

      ;; N Canvas
      (begin 

          ;; Assert that current-canvas is already minted
          (asserts! (get minted (unwrap! current-canvas-meta ERR-UNWRAP-META)) ERR-CANVAS-NOT-MINTED)

          ;; Assert that block-height is higher than current-canvas-end-height (aka last canvas is over)
          (asserts! (> block-height (unwrap! current-canvas-end-height ERR-UNWRAP-META)) ERR-END-HEIGHT-NOT-REACHED)

          ;; Update canvas-meta
          (map-set canvas-meta next-canvas-index 
            {
              end-height: next-end-height,
              placed-tiles: (list),
              charity-name: charity-name,
              charity-description: charity-description,
              charity-url: charity-url,
              minted: false
            }
          )

        ;; Update canvas-competition
        (map-set canvas-competition next-canvas-index 
          {
            competitor-collection: new-competitor-collection,
            competitor-colors: new-competitor-colors,
            badger-placed: u0,
            competitor-placed: u0
          }
        )

      )
    ))

  )
)


;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;
;;;; Admin Funcs ;;;;
;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;

;; Add Admin
;; @desc - function for adding a new admin to admin-list
;; @param - new-admin (principal)
(define-public (add-admin (new-admin principal))
  (let 
    (
      (current-admins (var-get admins))
    )

    ;; Assert that tx-sender is in the admin list
    (asserts! (is-some (index-of current-admins tx-sender)) ERR-NOT-AUTH)

    ;; Assert that new-admin is not already in the admin list
    (asserts! (not (is-some (index-of current-admins new-admin))) ERR-ADMIN-ALREADY-ADDED)

    ;; Update admins
    (ok (var-set admins (unwrap! (as-max-len? (append current-admins new-admin) u10) ERR-ADMIN-LIST-OVERFLOW)))

  )
)

;; Add Badger Contracts
;; @desc - function for adding a new admin to admin-list
;; @param - new-admin (principal)
(define-public (add-badger-collection (new-badger-collection principal))
  (let 
    (
      (current-admins (var-get admins))
      (current-badger-collections (var-get badger-collections))
    )

    ;; Assert that tx-sender is in the admin list
    (asserts! (is-some (index-of current-admins tx-sender)) ERR-NOT-AUTH)

    ;; Assert that new-badger-collection is not already in the admin list
    (asserts! (not (is-some (index-of current-badger-collections new-badger-collection))) ERR-ADMIN-ALREADY-ADDED)

    ;; Update admins
    (ok (var-set badger-collections (unwrap! (as-max-len? (append current-badger-collections new-badger-collection) u25) ERR-ADMIN-LIST-OVERFLOW)))

  )
)

;; Admin Place Tile
;; @desc - Admin function for placing a tile on the canvas
(define-public (admin-place-tile (position uint) (color (string-ascii 6))) 
  (let 
    (
      (current-canvas-index (var-get canvas-index))
      (current-canvas-meta (unwrap! (map-get? canvas-meta current-canvas-index) ERR-UNWRAP-META))
      (current-placed-tiles (get placed-tiles current-canvas-meta))
      (current-end-height (get end-height current-canvas-meta))
      (current-minted-status (get minted current-canvas-meta))
      (current-tile (map-get? tile { canvas: current-canvas-index, tile: position}))
    )

    ;; Assert that the canvas is active / block-height is less than the end-height
    (asserts! (< block-height current-end-height) ERR-END-HEIGHT-NOT-REACHED)

    ;; Assert that the tile position is valid
    (asserts! (< position tiles-in-canvas) ERR-INVALID-POSITION)

    ;; Assert that tile position is empty
    (asserts! (is-none current-tile) ERR-POSITION-NOT-EMPTY)

    ;; Assert that the color is valid
    (asserts! (is-eq (len color) u6) ERR-NOT-ENOUGH-COLORS)


    ;; Update tile by adding new item to list
    (map-set tile {canvas: current-canvas-index, tile: position} 
      (list {
        color: color,
        collection: none,
        owner: tx-sender
      })
    )

    ;; Update canvas-meta by merging current-canvas-meta with appended placed-tiles
    (ok (map-set canvas-meta current-canvas-index 
      (merge 
        current-canvas-meta 
        { placed-tiles: (unwrap! (as-max-len? (append current-placed-tiles position) u5000) ERR-TILE-LIST-OVERFLOW) }
      )
    ))

  )
)