contract VNDLZD is ERC721Enumerable, Ownable, ReentrancyGuard {
    uint public constant MAX = 3333;
	string public baseTokenURI;
	uint _saleTime = 1645822800;
	uint _presaleTime = 1645822800;
	uint _presalePrice = 0.06 ether;
    uint _price = 0.06 ether;
    uint8 _maxMintPerPresale = 6;
	mapping (uint256 => string) public _tokenURI;
	mapping (address => uint8) public _presaleAddresses;
	mapping (address => bool) public _presaleAddressesMinted;
    mapping (address => uint8) public _freeMintAddresses;
    mapping (address => bool) public _freeMintAddressesMinted;

	
    constructor(string memory newBaseURI) ERC721("Vandalized", "VNDLZD")  {
        setBaseURI(newBaseURI);
    }

    // Views
    function currentStage() public view returns (string memory) {
        if (block.timestamp >= _saleTime) return "Public sale";
        return "Presale";
    }

    function price(uint _count) public view returns (uint256) {
        return _price * _count;
    }

    function presalePrice(uint _count) public view returns (uint256) {
        return _presalePrice * _count;
    }

    function blockTimestamp() public view returns (uint256) {
        return block.timestamp;
    }

    function isInPresale(address _address) public view returns (uint8) {
        return _presaleAddresses[_address];
    }

    function isInMintedPresale(address _address) public view returns (bool) {
        return _presaleAddressesMinted[_address];
    }

    function isInFreeMint(address _address) public view returns (uint8) {
        return _freeMintAddresses[_address];
    }

    function isInMintedFreeMint(address _address) public view returns (bool) {
        return _freeMintAddressesMinted[_address];
    }

    function _baseURI() internal view virtual override returns (string memory) {
        return baseTokenURI;
    }

    // Owner only methods
    function setNewPrice(uint _newPrice) public onlyOwner {
        _price = _newPrice;
    }

    function setNewPresalePrice(uint _newPrice) public onlyOwner {
        _presalePrice = _newPrice;
    }

    function setBaseURI(string memory newBaseURI) public onlyOwner {
        baseTokenURI = newBaseURI;
    }
    
    function changeSaleTime(uint _newDate) public onlyOwner {
        _saleTime = _newDate;
    }
    
    function changePreSaleTime(uint _newDate) public onlyOwner {
        _presaleTime = _newDate;
    }

    function withdrawAll() public onlyOwner {
        require(payable(_msgSender()).send(address(this).balance));
    }

    function withdrawAllERC20(IERC20 token) public onlyOwner {
        require(
        token.transfer(msg.sender, token.balanceOf(address(this))),
        "Transfer failed"
        );
    }

    function addPresalers(address[] calldata _addresses) public onlyOwner {
        for (uint x = 0; x < _addresses.length; x++) {
            _presaleAddresses[_addresses[x]] = _maxMintPerPresale;
        }
    }
    
    function removePresalers(address[] calldata _addresses) public onlyOwner {
        for (uint x = 0; x < _addresses.length; x++) {
            _presaleAddresses[_addresses[x]] = 0;
        }
    }

    function addFreeMinters(address[] calldata _addresses, uint8[] calldata _amounts) public onlyOwner {
        for (uint x = 0; x < _addresses.length; x++) {
            _freeMintAddresses[_addresses[x]] = _amounts[x];
        }
    }

    function removeFreeMinters(address[] calldata _addresses) public onlyOwner {
        for (uint x = 0; x < _addresses.length; x++) {
            _freeMintAddresses[_addresses[x]] = 0;
        }
    }

    // Private methods
    function addToPresaleMinted(address _address) private {
        _presaleAddressesMinted[_address] = true;
    }

    function addToFreeMintMinted(address _address) private {
        _freeMintAddressesMinted[_address] = true;
    }

    // Public methods
    function mint(uint _count) public payable nonReentrant {
        require(totalSupply() + _count <= MAX, "Vandalized: Not enough left to mint");
        require(totalSupply() < MAX, "Vandalized: Not enough left to mint");
        require(_count <= 6, "Vandalized: Exceeds the max you can mint");
        
        if (block.timestamp >= _saleTime) {
            require(msg.value >= price(_count), "Vandalized: Value below price");
            for (uint x = 0; x < _count; x++) {
                _safeMint(msg.sender, totalSupply());
            }
        } else if (block.timestamp < _saleTime && block.timestamp >= _presaleTime) {
            require(msg.value >= presalePrice(_count), "Vandalized: Value below price");
            require(_count <= isInPresale(msg.sender), "Vandalized: Exceeds the max you can mint in the presale");
            require(isInPresale(msg.sender) > 0, "Vandalized: You are not in the presale");
            require(isInMintedPresale(msg.sender) == false, "Vandalized: You already minted from the presale");
            for (uint x = 0; x < _count; x++) {
                _safeMint(msg.sender, totalSupply());
            }
            addToPresaleMinted(msg.sender);
        }
    }

    function freeMint(uint _count) public nonReentrant {
        require(totalSupply() + _count <= MAX, "Vandalized: Not enough left to mint");
        require(totalSupply() < MAX, "Vandalized: Not enough left to mint");
        require(_count <= isInFreeMint(msg.sender), "Vandalized: Exceeds the max you can mint for free");
        require(isInFreeMint(msg.sender) > 0, "Vandalized: You are not in the free mint");
        require(isInMintedFreeMint(msg.sender) == false, "Vandalized: You already minted for free");

        for (uint x = 0; x < _count; x++) {
            _safeMint(msg.sender, totalSupply());
        }
        addToFreeMintMinted(msg.sender);
    }
}
function currentStage() public view returns (string memory) {
    if (block.timestamp >= _saleTime) return "Public sale";
    return "Presale";
}

// Views