// Contract: DigitalArtists
// Target function: function safeTransferFrom(
_].state;
    }

    /**
     * @notice Returns how many NFTs give address minted on premint and public mint for specific collection
     * @param collectionId_ collection ID
     * @param addr_ checked address
     */
    function collectionAddressMintedStats(uint256 collectionId_, address addr_)
        public
        view
        returns (uint32, uint32)
    {
        return (
            _collections[collectionId_].minted[addr_],
            _collections[collectionId_].preminted[addr_]
        );
    }

    /**
     * @notice Returns generated balance from mint campaign of collection
     * @param collectionId_ collection ID
     */
    function collectionBalance(uint256 collectionId_)
        public
        view
        returns (uint256)
    {
        return _collections[collectionId_].balance;
    }

    /**
     * @notice Returns boolean flag indicating if address is collection authorized
     * @param collectionId_ collection ID
     * @param addr_ checked address
     */
    function isCollectionAuthorized(uint256 collectionId_, address addr_)
        public
        view
        returns (bool)
    {
        return _collections[collectionId_].authorized[addr_];
    }

    /**
     * @notice Returns collection bounds - starting token id, last token id, current token id
     * @param collectionId_ collection ID
     */
    function collectionBounds(uint256 collectionId_)
        public
        view
        returns (
            uint256,
            uint256,
            uint256
        )
    {
        unchecked {
            return (
                _collections[collectionId_].tokenIdFrom + 1,
                _collections[collectionId_].tokenIdFrom +
                    _collections[collectionId_].maxSupply,
                _collections[collectionId_].tokenId
            );
        }
    }

    /**
     * @notice Returns info about collection - collection name, artist name, base URI, artist address, max supply
     * @param collectionId_ collection ID
     */
    function collectionInfo(uint256 collectionId_)
        public
        view
        returns (
            string memory,
            string memory,
            string memory,
            address,
            uint32
        )
    {
        return (
            string(abi.encodePacked(_collections[collectionId_].name)),
            string(
                abi.encodePacked(
                    _artists[_collections[collectionId_].artistAddr].name
                )
            ),
            string(abi.encodePacked(_collections[collectionId_].baseUri)),
            _collections[collectionId_].artistAddr,
            _collections[collectionId_].maxSupply
        );
    }

    /**
     * @notice Allows collection authorized or contract owner to retrive split payments information
     * @param collectionId_ collection ID
     */
    function collectionPaymentsInfo(uint256 collectionId_)
        public
        view
        onlyAuthorized(collectionId_)
        returns (
            uint256,
            uint256,
            uint16,
            uint16
        )
    {
        return (
            _collections[collectionId_].withdrawn[0],
            _collections[collectionId_].withdrawn[1],
            _collections[collectionId_].percentage[0],
            _collections[collectionId_].percentage[1]
        );
    }

    /**
     * @notice Allows contract owner or known artists to create new collectins on this contract
     * @param artistAddr_ collection artists address (required)
     * @param maxSupply_ collection max supply (required)
     * @param premintMaxSupply_ collection premint max supply (optional)
     */
    function createCollection(
        address artistAddr_,
        uint32 maxSupply_,
        uint32 premintMaxSupply_
    ) public {
        _nonZeroAddress(artistAddr_);
        require(maxSupply_ > 0 && maxSupply_ <= 5000000, "Invalid supply");

        if (_isCreateCollectionPublic == 0) {
            // Leave an option to publicly allow this operation
            require(
                _msgSender() == owner() || _knownArtists[_msgSender()],
                "Unauthorized"
            );
        }

        Collection storage c = _collections[++_lastCollectionId];
        c.artistAddr = artistAddr_;
        c.maxSupply = maxSupply_;

        if (premintMaxSupply_ > 0) {
            _collections[_lastCollectionId]
                .premintMaxSupply = premintMaxSupply_;
        }

        uint256 prevIdx;
        unchecked {
            prevIdx = _lastCollectionId - 1;
            _artists[artistAddr_].collectionsCount++;
        }

        // Every new collection's tokenId will start from the previous end + 10000(gap)
        c.tokenId =
            _collections[prevIdx].tokenIdFrom +
            _collections[prevIdx].maxSupply +
            10000;
        c.tokenIdFrom = c.tokenId;
    }

    /**
     * @notice Returns boolean flag indicating whether collection exists
     * @param collectionId_ collection ID
     */
    function collectionExists(uint256 collectionId_)
        public
        view
        returns (bool)
    {
        return _collections[collectionId_].artistAddr != address(0);
    }

    /**
     * @notice Allows collection authorized or contract owner to toggle address collection authorization state
     * @param collectionId_ collection ID
     * @param addr_ address to toggle authorization state for
     * @param state_ authorization state - true or false
     */
    function toggleCollectionAuthorization(
        uint256 collectionId_,
        address addr_,
        bool state_
    ) public onlyAuthorized(collectionId_) {
        _nonZeroAddress(addr_);
        _collections[collectionId_].authorized[addr_] = state_;
        emit CollectionAuthorizationUpdated(collectionId_, addr_, state_);
    }

    /**
     * @notice Allows contract owner to set royalty for specific collection
     * @param collectionId_ collection ID
     * @param royaltyAddr_ new royalty address
     * @param royaltyBasis_ new royalty basis
     */
    function updateCollectionRoyalty(
        uint256 collectionId_,
        address royaltyAddr_,
        uint16 royaltyBasis_
    ) public onlyOwner {
        require(royaltyBasis_ <= 5000, "Maximum 50% royalty");
        _collections[collectionId_].royaltyAddr = royaltyAddr_;
        _collections[collectionId_].royaltyBasis = royaltyBasis_;
    }

    /**
     * @notice Returns royalty information about collection
     * @param collectionId_ collection ID
     */
    function collectionRoyaltyInfo(uint256 collectionId_)
        public
        view
        returns (address, uint16)
    {
        return (
            _collections[collectionId_].royaltyAddr == address(0)
                ? _collections[collectionId_].artistAddr
                : _collections[collectionId_].royaltyAddr,
            _collections[collectionId_].royaltyBasis
        );
    }

    /**
     * @notice Allows artist or contract owner to update on-chain info about artist by address
     * @param addr_ artist address
     * @param name_ new artist name
     * @param info_ new artist info - free text
     */
    function updateArtist(
        address addr_,
        string memory name_,
        string memory info_
    ) public {
        require(
            _msgSender() == owner() || _msgSender() == addr_,
            "Unauthorized"
        );

        if (bytes(name_).length > 0) {
            _artists[addr_].name = name_;
        }

        if (bytes(info_).length > 0) {
            _artists[addr_].info = info_;
        }
    }

    /**
     * @notice Allows contract owner to update on-chain collection name
     * @param collectionId_ collection ID
     * @param collectionName_ collection name
     */
    function updateCollectionName(
        uint256 collectionId_,
        string memory collectionName_
    ) public onlyOwner {
        _collections[collectionId_].name = bytes32(bytes(collectionName_));
    }

    /**
     * @notice Allows contract owner to update collection's artist address
     * @param collectionId_ collection ID
     * @param artistAddr_ new artist address
     */
    function updateCollectionArtistAddr(
        uint256 collectionId_,
        address artistAddr_
    ) public onlyOwner {
        _nonZeroAddress(artistAddr_);
        address oldAddress = _collections[collectionId_].artistAddr;
        _collections[collectionId_].artistAddr = artistAddr_;
        emit CollectionArtistAddressUpdated(
            collectionId_,
            artistAddr_,
            oldAddress
        );
    }

    /**
     * @notice Returns artist address for collection
     * @param collectionId_ collection ID
     */
    function collectionArtistAddr(uint256 collectionId_)
        public
        view
        returns (address)
    {
        return _collections[collectionId_].artistAddr;
    }

    /**
     * @notice Allows collection authorized or contract owner to add addresses to premint list
     * @param collectionId_ collection ID
     * @param addresses_ array of addresses
     */
    function addToCollectionPremintList(
        uint256 collectionId_,
        address[] memory addresses_
    ) public onlyAuthorized(collectionId_) {
        Collection storage c = _collections[collectionId_];

        require(
            c.premintListCount + addresses_.length <= c.premintListCap,
            "Presale list overflow"
        );

        for (uint256 i = 0; i < addresses_.length; i++) {
            if (!c.premintList[addresses_[i]]) {
                c.premintList[addresses_[i]] = true;
                unchecked {
                    c.premintListCount++;
                }
            }
        }
    }

    /**
     * @notice Allows collection authorized or contract owner to remove addresses from premint list
     * @param collectionId_ collection ID
     * @param addresses_ array of addresses
     */
    function removeFromCollectionPremintList(
        uint256 collectionId_,
        address[] memory addresses_
    ) public onlyAuthorized(collectionId_) {
        Collection storage c = _collections[collectionId_];

        for (uint256 i = 0; i < addresses_.length; i++) {
            if (c.premintList[addresses_[i]]) {
                c.premintList[addresses_[i]] = false;
                unchecked {
                    c.premintListCount--;
                }
            }
        }
    }

    /**
     * @notice Returns boolean flag indicating if address is on the collection's premint list
     * @param collectionId_ collection ID
     * @param addr_ checked address
     */
    function isOnCollectionPremintList(uint256 collectionId_, address addr_)
        public
        view
        returns (bool)
    {
        return _collections[collectionId_].premintList[addr_];
    }

    /**
     * @notice Returns collection's premint list capacity and current count
     * @param collectionId_ collection ID
     */
    function collectionPremintListDetails(uint256 collectionId_)
        public
        view
        returns (uint16, uint16)
    {
        return (
            _collections[collectionId_].premintListCap,
            _collections[collectionId_].premintListCount
        );
    }

    /**
     * @notice Allows collection authorized or contract owner to update premint and mint prices for collection
     * @param collectionId_ collection ID
     * @param shouldUpdateMintPrice_ flag indicating whether mint price should be updated
     * @param mintPrice_ mint price to be set if shouldUpdateMintPrice_ > 0
     * @param shouldUpdatepremintPrice_ flag indicating whether premint price should be updated
     * @param premintPrice_ premint price to be set if shouldUpdatepremintPrice_ > 0
     */
    function updateCollectionMintPrices(
        uint256 collectionId_,
        uint8 shouldUpdateMintPrice_,
        uint256 mintPrice_,
        uint8 shouldUpdatepremintPrice_,
        uint256 premintPrice_
    ) public onlyAuthorized(collectionId_) {
        // using parameter which determines if value should be updated
        // proceed with update if shouldUpdate..._ paramters != 0

        if (shouldUpdateMintPrice_ != 0) {
            _collections[collectionId_].mintPrice = mintPrice_;
        }

        if (shouldUpdatepremintPrice_ != 0) {
            _collections[collectionId_].premintPrice = premintPrice_;
        }
    }

    /**
     * @notice Returns collection's mint and premint prices
     * @param collectionId_ collection ID
     */
    function collectionMintPrices(uint256 collectionId_)
        public
        view
        returns (uint256, uint256)
    {
        return (
            _collections[collectionId_].mintPrice,
            _collections[collectionId_].premintPrice
        );
    }

    /**
     * @notice Allows collection authorized or contract owner to update collection capacities - mint, premint, premint list
     * @param collectionId_ collection ID
     * @param shouldUpdateMintCap_ flag if mint capacity is updated
     * @param mintCap_ mint capacity to set
     * @param shouldUpdatePremintCap_ flag if premint capacity is updated
     * @param premintCap_ premint capacity to set
     * @param shouldUpdatePremintListCap_ flag if premint list capacity is updated
     * @param premintListCap_ premint list capacity to set
     */
    function updateCollectionCaps(
        uint256 collectionId_,
        uint8 shouldUpdateMintCap_,
        uint16 mintCap_,
        uint8 shouldUpdatePremintCap_,
        uint16 premintCap_,
        uint8 shouldUpdatePremintListCap_,
        uint16 premintListCap_
    ) public onlyAuthorized(collectionId_) {
        // using parameter which determines if value should be updated
        // proceed with update if shouldUpdate..._ paramters != 0

        if (shouldUpdateMintCap_ != 0) {
            _collections[collectionId_].mintCap = mintCap_;
        }

        if (shouldUpdatePremintCap_ != 0) {
            _collections[collectionId_].premintCap = premintCap_;
        }

        if (shouldUpdatePremintListCap_ != 0) {
            _collections[collectionId_].premintListCap = premintListCap_;
        }
    }

    /**
     * @notice Returns collection's mint and premint capacities
     * @param collectionId_ collection ID
     */
    function collectionMintCaps(uint256 collectionId_)
        public
        view
        returns (uint16, uint16)
    {
        return (
            _collections[collectionId_].mintCap,
            _collections[collectionId_].premintCap
        );
    }

    /**
     * @notice Mint function - will mint to message sender
     * @param collectionId_ collection ID
     * @param amount_ minted NFTs count
     */
    function mint(uint256 collectionId_, uint16 amount_) public payable {
        mintTo(collectionId_, amount_, _msgSender());
    }

    /**
     * @notice Mint function - will mint to receiver_
     * @param collectionId_ collection ID
     * @param amount_ minted NFTs count
     * @param receiver_ receiver of the minted NFTs
     */
    function mintTo(
        uint256 collectionId_,
        uint16 amount_,
        address receiver_
    ) public payable nonReentrant {
        _nonZeroAddress(receiver_);
        require(collectionExists(collectionId_), "Nonexistent collection");

        Collection storage c = _collections[collectionId_];

        require(
            totalSupplyByCollectionId(collectionId_) < c.maxSupply,
            "Mint completed"
        );
        require(amount_ > 0, "Invalid mint amount");

        if (c.state == 1) {
            require(c.premintList[receiver_], "Not on whitelist");

            uint32 minted = c.preminted[receiver_];
            unchecked {
                c.preminted[receiver_] += amount_;
            }

            _mint(
                collectionId_,
                c.premintPrice,
                receiver_,
                amount_,
                c.premintCap,
                c.premintMaxSupply,
                minted
            );
        } else if (c.state == 2) {
            uint32 minted = c.minted[receiver_];
            unchecked {
                c.minted[receiver_] += amount_;
            }
            _mint(
                collectionId_,
                c.mintPrice,
                receiver_,
                amount_,
                c.mintCap,
                c.maxSupply,
                minted
            );
        } else {
            revert("Mint not active");
        }
    }

    /**
     * @notice Mint function - allows collection authorized or contract owner to mint
     * @param collectionId_ collection ID
     * @param amount_ minted NFTs count
     * @param receiver_ receiver of the minted NFTs
     */
    function mintInternal(
        uint256 collectionId_,
        uint16 amount_,
        address receiver_
    ) public onlyAuthorized(collectionId_) nonReentrant {
        _nonZeroAddress(receiver_);

        Collection storage c = _collections[collectionId_];

        require(
            amount_ + totalSupplyByCollectionId(collectionId_) <= c.maxSupply,
            "Supply overflow"
        );

        for (uint16 i = 0; i < amount_; i++) {
            unchecked {
                c.tokenId += 1;
            }

            require(!_exists(c.tokenId), "Token already minted");

            _beforeTokenTransfer(address(0), receiver_, c.tokenId);

            _tokenIdToCollectionId[c.tokenId] = collectionId_;
            unchecked {
                _balances[receiver_] += 1;
            }
            _owners[c.tokenId] = receiver_;

            emit Transfer(
                address(0),
                receiver_,
                _collections[collectionId_].tokenId
            );
        }
    }

    /**
     * @notice Transfer token from address to another
     * @param from_ current owner
     * @param to_ new owner address
     * @param tokenId_ desired token ID
     */
    function transferFrom(
        address from_,
        address to_,
        uint256 tokenId_
    ) public {
        require(
            _isApprovedOrOwner(_msgSender(), tokenId_),
            "Not owner nor approved"
        );

        _transfer(from_, to_, tokenId_);
    }