contract NewElite is ERC721, ERC721Enumerable, Ownable {
    // Private 
    string private baseURI;
    
    // Sale status
    bool public vipMintIsActive = false;
    bool public publicMintIsActive = false;
    
    // Constants 
    uint256 public constant MAX_SUPPLY = 10000;
    uint256 public constant MAX_PER_TXN = 10;
    uint256 public constant TOKEN_PRICE = 0.1 ether;
    
    // Maps address to number of remaining vip mints
    mapping(address => uint8) private vipList;
    
    // Reserved for team, giveaways, and promotions
    uint256 public reserveSupply = 100; 

    constructor() ERC721("NewElite", "ELITE") {}
    
    function publicMint(uint256 _amount) external payable {
        uint256 supply = totalSupply();
        
        require(publicMintIsActive, "Elite: Public Mint is not active");
        require(supply + _amount + reserveSupply <= MAX_SUPPLY, "Elite: Max token supply exceeded");
        require(TOKEN_PRICE * _amount <= msg.value, "Elite: Ether value sent is not correct");
        require(_amount <= MAX_PER_TXN, "Elite: Exceeded max mint per txn");
        
        for (uint256 i = 0; i < _amount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }
    
    function vipPresaleMint(uint8 _amount) external payable {
        uint256 supply = totalSupply();
        require(vipMintIsActive,  "Elite: Vip Mint is not active");
        require(_amount <= vipList[msg.sender], "Elite: Exceeded your max vip mint");
        require(supply + _amount + reserveSupply <= MAX_SUPPLY, "Elite: Max token supply exceeded");
        require(TOKEN_PRICE * _amount <= msg.value, "Elite: Ether value sent is not correct");
        
        vipList[msg.sender] -= _amount; 
        for (uint256 i = 0; i < _amount; i++) {
            _safeMint(msg.sender, supply + i);
        }
    }
    
    function numAvailableToMint(address addr) external view returns (uint8) {
        return vipList[addr];
    }
    
    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }
    
    
    // onlyOwner 
    function setVipList(address[] calldata addresses, uint8 numAllowedToMint) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            vipList[addresses[i]] = numAllowedToMint;
        }
    }
    
    function reserve(uint256 n) public onlyOwner {
      uint supply = totalSupply();
      require(supply + n + reserveSupply <= MAX_SUPPLY, "Elite: Max token supply exceeded");
      require(n <= reserveSupply, "Elite: Reserve supply exceeded");
      
      reserveSupply -= n; 
      for (uint256 i = 0; i < n; i++) {
          _safeMint(msg.sender, supply + i);
      }
    }
    
    function setBaseURI(string calldata _tokenBaseURI) external onlyOwner {
        baseURI = _tokenBaseURI;
    }
    
    function setVipMintIsActive(bool _vipMintIsActive) external onlyOwner {
        vipMintIsActive = _vipMintIsActive;
    }
    
    function setPublicMintIsActive(bool _publicMintIsActive) external onlyOwner {
        publicMintIsActive = _publicMintIsActive;
    }
    
    function withdraw() public onlyOwner {
        uint balance = address(this).balance;
        payable(msg.sender).transfer(balance);
    }
    
    
    // Override
    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
        super._beforeTokenTransfer(from, to, tokenId);
    }

    function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) {
        return super.supportsInterface(interfaceId);
    }
}
function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable) {
    super._beforeTokenTransfer(from, to, tokenId);
}

// Override