contract DAOBnBNFT is Ownable, ERC721Opt, ERC721OptOwnersExplicit, ERC721OptBatchBurnable, ERC721OptBatchTransferable {
     using Strings for uint16;

    /* Base URI for token URIs */
    string public baseURI;

    /* OpenSea user account proxy */
    address public openSeaProxyRegistryAddress;

    /* Token contract */
    IToken public token;

    uint16 silverCardsMax = 6400;
    uint16 blackCardsMax = 2700;

    uint16 blackCardsMinted;

    /* mapping of each wallets black cards */
    mapping(address => uint16) public walletBlackCards;
    
    /* Minter addressess */
    mapping(address => bool) public minters;

    /* mapping of each token id to card type 0 = silver, 1 = black */
    mapping(uint16 => bool) _blackCardTokenIds;
    
    constructor(string memory name_, string memory symbol_, string memory _initialBaseURI, address _openSeaProxyRegistryAddress, address[] memory _minters) ERC721Opt(name_, symbol_) {
        baseURI = _initialBaseURI;
        openSeaProxyRegistryAddress = _openSeaProxyRegistryAddress;
        
        for (uint256 i = 0; i < _minters.length; i++) {
            minters[_minters[i]] = true;
        }
    }

    /**
     * @dev Get silver cards left for sale
     */
    function silverCardsLeft() public view returns (uint256) {
        return silverCardsMax - (_nextTokenId - 1 - blackCardsMinted);
    }

    /**
     * @dev Get black cards left for sale
     */
    function blackCardsLeft() public view returns (uint256) {
        return blackCardsMax - blackCardsMinted;
    }

    /**
     * @dev Get the card type for a specific tokenId
     */
    function tokenCardType(uint16 tokenId) public view returns (string memory) {
        if (!_exists(tokenId)) revert CardTypeQueryForNonexistentToken();

        if (_blackCardTokenIds[tokenId]) {
            return "black";
        }

        return "silver";
    }

    /**
     * @dev Override to if default approved for OS proxy accounts or normal approval
     */
    function isApprovedForAll(address owner, address operator)
        public
        view
        override
        returns (bool)
    {
        // Whitelist OpenSea proxy contract for easy trading.
        OpenSeaProxyRegistry openSeaProxyRegistry = OpenSeaProxyRegistry(
            openSeaProxyRegistryAddress
        );
        if (address(openSeaProxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return ERC721Opt.isApprovedForAll(owner, operator);
    }

    /**
     * @dev Override to change the baseURI used in tokenURI
     */
    function _baseURI() internal view virtual override returns (string memory) {
        return baseURI;
    }

    /**
     * @dev Override to change tokenURI format
     */
    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        if (!_exists(tokenId)) revert URIQueryForNonexistentToken();

        return
            bytes(baseURI).length > 0
                ? string(abi.encodePacked(_baseURI(), tokenCardType(uint16(tokenId)), '.json'))
                : '';
    }

    /**
     * @dev Mint of specific card type to address. 
     */
    function mint(uint16 silverCardsAmount, uint16 blackCardsAmount, address to) public {
        if(!minters[msg.sender]) revert OnlyMintersCanMint();
        if(silverCardsAmount + blackCardsAmount == 0) revert NoMintAmountProvided();
        if(_nextTokenId - 1 - blackCardsMinted + silverCardsAmount > silverCardsMax) revert AllSilverCardsMinted();
        if(blackCardsMinted + blackCardsAmount > blackCardsMax) revert AllBlackCardsMinted();

        if (blackCardsAmount > 0) {
            blackCardsMinted += blackCardsAmount;

            uint16 tokenId = uint16(_nextTokenId) + silverCardsAmount;

            for (uint16 i; i < blackCardsAmount; i++) {
                _blackCardTokenIds[tokenId++] = true;
            }
        }
        
        _safeMint(to, silverCardsAmount + blackCardsAmount, '');
    }

    /**
     * @dev Override so we can update token rewards before transfer happens
     */
    function _beforeTokenTransfers(
        address from,
        address to,
        uint256 startTokenId,
        uint256 quantity
    ) internal virtual override {
        if (address(token) != address(0)) {
            token.updateRewards(from);
            token.updateRewards(to);
        }

        uint16 blackCards;

        for(uint16 i = uint16(startTokenId); i < startTokenId + quantity; i++) {
            if (_blackCardTokenIds[i]) {
                blackCards += 1;
            }
        }

        if (from != address(0)) {
            walletBlackCards[from] -= blackCards;
        }
        if (to != address(0)) {
            walletBlackCards[to] += blackCards;
        }

        super._beforeTokenTransfers(from, to, startTokenId, quantity);
    }

    /**
     * @dev Set the base uri for token metadata
     */
    function setBaseURI(string memory _newBaseURI) external onlyOwner {
        baseURI = _newBaseURI;
    }

    /**
     * @dev Set minter status for addresses
     */
    function setMinters(address[] calldata addresses, bool allowed) external onlyOwner {
        for(uint256 i = 0; i < addresses.length; i++) {
            minters[addresses[i]] = allowed;
        }
    }

    /**
     * @dev Update available cards
     */
    function updateAvailableCards(uint16 silverCards, uint16 blackCards) external onlyOwner {
        silverCardsMax = silverCards;
        blackCardsMax = blackCards;
    }

    /**
     * @dev Set the token contract
     */
    function setToken(IToken _token) external onlyOwner {
        token = _token;
    }

    /**
     * @dev Force update all owners for better transfers
     */
    function updateOwners(uint256 quantity) external onlyOwner {
        _setOwnersExplicit(quantity);
    }
}
function setBaseURI(string memory _newBaseURI) external onlyOwner {
    baseURI = _newBaseURI;
}

/**
 * @dev Set the base uri for token metadata
 */