contract CryptoCraftNFT is ERC721, Ownable {
    // Private 
    string private baseURI;
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIdCounter;
    
    // Sale status
    bool public whitelistMintIsActive = false;
    bool public publicMintIsActive = false;
    
    // Constants 
    // To maximize gas savings, max_supply is offset by 1. 
    uint256 public constant MAX_SUPPLY = 4001;
    uint256 public constant MAX_PER_TXN = 5;
    uint256 public constant TOKEN_PRICE = 0.05 ether;
    
    // Maps address to number of remaining whitelist mints
    mapping(address => uint8) private whitelist;
    
    // Reserved for team, giveaways, and promotions
    uint256 public reserveSupply = 25; 

    constructor() ERC721("CryptoCraft", "CRAFT") {
        _tokenIdCounter.increment();
    }
    
    function publicMint(uint256 _amount) external payable {        
        require(publicMintIsActive, "CC: Public Mint is not active");
        require(_tokenIdCounter.current() + _amount + reserveSupply <= MAX_SUPPLY, "CC: Max token supply exceeded");
        require(TOKEN_PRICE * _amount <= msg.value, "CC: Ether value sent is not correct");
        require(_amount <= MAX_PER_TXN, "CC: Exceeded max mint per txn");
        
        for (uint256 i = 0; i < _amount; i++) {
            _tokenIdCounter.increment();
            _safeMint(msg.sender, _tokenIdCounter.current());
        }
    }
    
    function whitelistMint(uint8 _amount) external payable {
        require(whitelistMintIsActive,  "CC: Whitelist Mint is not active");
        require(_amount <= whitelist[msg.sender], "CC: You don't have enough whitelist mints remaining");
        require(_tokenIdCounter.current() + _amount + reserveSupply <= MAX_SUPPLY, "CC: Max token supply exceeded");
        require(TOKEN_PRICE * _amount <= msg.value, "CC: Ether value sent is not correct");
        
        whitelist[msg.sender] -= _amount; 
        for (uint256 i = 0; i < _amount; i++) {
            _tokenIdCounter.increment();
            _safeMint(msg.sender, _tokenIdCounter.current());
        }
    }
    
    function numAvailableToMint(address addr) external view returns (uint8) {
        return whitelist[addr];
    }
    
    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }
    
    
    // onlyOwner 
    function setWhitelist(address[] calldata addresses, uint8 numAllowedToMint) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            whitelist[addresses[i]] = numAllowedToMint;
        }
    }
    
    function reserve(uint256 n, address mintReceiver) public onlyOwner {
      require(_tokenIdCounter.current() + n + reserveSupply <= MAX_SUPPLY, "CC: Max token supply exceeded");
      require(n <= reserveSupply, "CC: Reserve supply exceeded");
      
      reserveSupply -= n; 
      for (uint256 i = 0; i < n; i++) {
            uint256 tokenId = _tokenIdCounter.current();
            _tokenIdCounter.increment();
            _safeMint(mintReceiver, tokenId);
      }
    }
    
    function setBaseURI(string calldata _tokenBaseURI) external onlyOwner {
        baseURI = _tokenBaseURI;
    }
    
    function setWhitelistMintIsActive(bool _whitelistMintIsActive) external onlyOwner {
        whitelistMintIsActive = _whitelistMintIsActive;
    }
    
    function setPublicMintIsActive(bool _publicMintIsActive) external onlyOwner {
        publicMintIsActive = _publicMintIsActive;
    }
    
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
}
function setWhitelist(address[] calldata addresses, uint8 numAllowedToMint) external onlyOwner {
    for (uint256 i = 0; i < addresses.length; i++) {
        whitelist[addresses[i]] = numAllowedToMint;
    }
}

// onlyOwner 