contract Funcles is ERC721I, Ownable, MerkleWhitelist, 
WhitelistMint, PublicMint, Security, PayableGovernance {

    // Constructor
    constructor() payable ERC721I("Funcles", "FUNCLES") {}

    // Project Constraints
    uint256 public mintPrice = 0.085 ether;
    uint256 public maxSupply = 3333;

    // Public Limits
    uint256 public maxMintsPerPublic = 2;
    mapping(address => uint256) public addressToPublicMints;

    // Whitelist Limits
    uint256 public maxMintsPerWl = 2;
    mapping(address => uint256) public addressToWlMints;

    // Administrative Functions
    function setMintPrice(uint256 mintPrice_) external onlyOwner {
        mintPrice = mintPrice_;
    }
    function setMaxSupply(uint256 maxSupply_) external onlyOwner {
        maxSupply = maxSupply_;
    }
    
    // Public and WL Mint Limits
    function setMaxMintsPerPublic(uint256 maxMintsPerPublic_) external onlyOwner {
        maxMintsPerPublic = maxMintsPerPublic_;
    }
    function setMaxMintsPerWl(uint256 maxMintsPerWl_) external onlyOwner {
        maxMintsPerWl = maxMintsPerWl_;
    }

    // Token URI
    function setBaseTokenURI(string calldata uri_) external onlyOwner {
        _setBaseTokenURI(uri_);
    }
    function setBaseTokenURI_EXT(string calldata ext_) external onlyOwner {
        _setBaseTokenURI_EXT(ext_);
    }

    // MerkleRoot
    function setMerkleRoot(bytes32 merkleRoot_) external onlyOwner {
        _setMerkleRoot(merkleRoot_);
    }
    
    // Public Mint
    function setPublicMint(bool bool_, uint256 time_) external onlyOwner {
        _setPublicMint(bool_, time_);
    }

    // Whitelist Mint
    function setWhitelistMint(bool bool_, uint256 time_) external onlyOwner {
        _setWhitelistMint(bool_, time_);
    }

    // Withdrawals handled by PayableGovernance //

    // Internal Functions
    function _mintMany(address to_, uint256 amount_) internal {
        require(maxSupply >= totalSupply + amount_,
            "Not enough Funcles remaining!");
        
        uint256 _startId = totalSupply + 1; // iterate from 1

        for (uint256 i = 0; i < amount_; i++) {
            _mint(to_, _startId + i);
        }

        totalSupply += amount_;
    }

    // Owner Mint
    function ownerMint(address[] calldata tos_, uint256[] calldata amounts_) 
    external onlyOwner {
        require(tos_.length == amounts_.length,
            "Array lengths mismatch!");
        
        for (uint256 i = 0; i < tos_.length; i++) {
            _mintMany(tos_[i], amounts_[i]);
        }
    }

    // Whitelist Mint
    function whitelistMint(bytes32[] calldata proof_, uint256 amount_) external
    payable onlySender whitelistMintEnabled {
        require(isWhitelisted(msg.sender, proof_),
            "You are not whitelisted!");
        require(maxMintsPerWl >= addressToWlMints[msg.sender] + amount_,
            "You don't have enough funlist mints!");
        require(msg.value == mintPrice * amount_,
            "Invalid value sent!");
        
        // Add address to WL minted
        addressToWlMints[msg.sender] += amount_;

        // Now, mint to msg.sender
        _mintMany(msg.sender, amount_);
    }

    // Public Mint
    function publicMint(uint256 amount_) external payable
    onlySender publicMintEnabled {
        require(maxMintsPerPublic >= addressToPublicMints[msg.sender] + amount_,
            "You don't have enough Public Mints!");
        require(msg.value == mintPrice * amount_,
            "Invalid value sent!");

        // Add address to Public Mints
        addressToPublicMints[msg.sender] += amount_;
        
        // Now, mint to msg.sender
        _mintMany(msg.sender, amount_);
    }
}
function setMaxMintsPerPublic(uint256 maxMintsPerPublic_) external onlyOwner {
    maxMintsPerPublic = maxMintsPerPublic_;
}

// Public and WL Mint Limits