contract AvadarkNextGeneration is Ownable, ERC721Enumerable, ReentrancyGuard {
    using SafeMath for uint256;

    string public AvadarkNextGeneration_PROVENANCE = "";
    string public BASE_URI = "";

    uint256 public constant PRICE = 0.03 ether;
    uint256 public constant MAX_AvadarkNextGeneration = 6666;
    uint256 public constant PURCHASE_LIMIT = 20;
    uint256 public constant PRESALE_MAX_PURCHASE_LIMIT = 1;
    uint256 public AvadarkNextGenerationReserve = 35;
    uint256 public totalPublicSupply;

    bool public saleIsActive = false;
    bool public preSaleIsActive = false;

    address public devTeamAddress = 0x000431DdC083C803f025C13184cdCe11fF959e64;

    mapping(address => bool) private _allowList;
    mapping(address => uint256) private _claimedList;

    constructor() ERC721("AvadarkNextGeneration", "ANG") {}

    function withdraw() public onlyOwner {
        uint256 value = address(this).balance;
        sendValueTo(devTeamAddress, (value * 15) / 100);
        sendValueTo(msg.sender, (value * 85) / 100);
    }

    /**
    * @dev Send value to address
    */
    function sendValueTo(address to_, uint256 value) internal {
        address payable to = payable(to_);
        (bool success, ) = to.call{value: value}("");
        require(success, "Transfer failed.");
    }

    /**
    * @dev Flip the pre / main sale flags
    */
    function flipSaleState() public onlyOwner {
        saleIsActive = !saleIsActive;
    }

    function flipPreSaleState() public onlyOwner {
        preSaleIsActive = !preSaleIsActive;
    }

    /**
    * @dev Return base URI
    */
    function _baseURI() internal view override returns (string memory) {
        return BASE_URI;
    }

    /**
    * @dev Set data
    */
    function setBaseURI(string memory baseURI_) public onlyOwner {
        BASE_URI = baseURI_;
    }

    function setDevAddress(address newAddress) public onlyOwner {
        devTeamAddress = newAddress;
    }

    function setProvenanceHash(string memory provenanceHash) public onlyOwner {
        AvadarkNextGeneration_PROVENANCE = provenanceHash;
    }

    /**
    * @dev Add array of address's to the whitelist
    */
    function addToAllowList(address[] calldata addresses) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "Can't add the null address");
            _allowList[addresses[i]] = true;
        }
    }

    function onAllowList(address addr) external view returns (bool) {
        return _allowList[addr];
    }

    function removeFromAllowList(address[] calldata addresses) external onlyOwner
    {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "Can't add the null address");
            _allowList[addresses[i]] = false;
        }
    }

    /**
    * @dev Minting functions
    */
    function mint(uint256 numberOfTokens) public payable nonReentrant {
        require(saleIsActive, "Sale is not active");
        require(!preSaleIsActive, "Pre sale is active");
        require(totalSupply().add(numberOfTokens) <= MAX_AvadarkNextGeneration,"All tokens have been minted");
        require(numberOfTokens > 0 && numberOfTokens <= PURCHASE_LIMIT,"Would exceed PURCHASE_LIMIT");
        require(msg.value / numberOfTokens == PRICE, "Eth value is not good");

        for (uint256 i = 0; i < numberOfTokens; i++) {
            if (totalPublicSupply < MAX_AvadarkNextGeneration) {
                uint256 tokenId = totalPublicSupply;
                totalPublicSupply += 1;
                _safeMint(msg.sender, tokenId);
            }
        }
    }

    function claim() public
    {
        require(!saleIsActive, "Sale is active");
        require(preSaleIsActive, "Pre Sale is not active");
        require(totalSupply().add(1) <= MAX_AvadarkNextGeneration,"All tokens have been minted");
        require(_allowList[msg.sender], "Not on whitelist");
        require(_claimedList[msg.sender] + 1 <= PRESALE_MAX_PURCHASE_LIMIT, "Purchase exceeds max allowed");

        uint256 tokenId = totalPublicSupply;
        totalPublicSupply += 1;
        _claimedList[msg.sender] += 1;
        _safeMint(msg.sender, tokenId);
    }

    function reserve(address _to, uint256 _reserveAmount) public onlyOwner {
        require(_reserveAmount > 0 && _reserveAmount <= AvadarkNextGenerationReserve,"Not enough reserve left for team");
        require(totalSupply().add(_reserveAmount) <= MAX_AvadarkNextGeneration,"All tokens have been minted");

        for (uint256 i = 0; i < _reserveAmount; i++) {
            uint256 tokenId = totalPublicSupply;
            totalPublicSupply += 1;
            _safeMint(_to, tokenId);
        }

        AvadarkNextGenerationReserve = AvadarkNextGenerationReserve.sub(_reserveAmount);
    }

    /**
    * @dev Burn Function
    */
    function burnSingleAvadark(uint256 firstTokenId) public {
        require(_isApprovedOrOwner(_msgSender(), firstTokenId), "Caller is not owner nor approved");
        _burn(firstTokenId);
    }

    function burnTwoAvadark(uint256 firstTokenId, uint256 secondTokenId) public {
        require(_isApprovedOrOwner(_msgSender(), firstTokenId) && _isApprovedOrOwner(_msgSender(), secondTokenId), "Caller is not owner nor approved");
        _burn(firstTokenId);
        _burn(secondTokenId);
    }

    function burnThreeAvadark(uint256 firstTokenId, uint256 secondTokenId, uint256 thirdTokenId) public {
        require(_isApprovedOrOwner(_msgSender(), firstTokenId) && _isApprovedOrOwner(_msgSender(), secondTokenId) && _isApprovedOrOwner(_msgSender(), thirdTokenId), "Caller is not owner nor approved");
        _burn(firstTokenId);
        _burn(secondTokenId);
        _burn(thirdTokenId);
    }

    /**
    * @dev Returns a list of tokens for an address
    */
    function tokensOfOwner(address _owner) external view returns (uint256[] memory)
    {
        uint256 tokenCount = balanceOf(_owner);
        if (tokenCount == 0) {
            return new uint256[](0);
        } else {
            uint256[] memory result = new uint256[](tokenCount);
            uint256 index;
            for (index = 0; index < tokenCount; index++) {
                result[index] = tokenOfOwnerByIndex(_owner, index);
            }
            return result;
        }
    }
}
function mint(uint256 numberOfTokens) public payable nonReentrant {
    require(saleIsActive, "Sale is not active");
    require(!preSaleIsActive, "Pre sale is active");
    require(totalSupply().add(numberOfTokens) <= MAX_AvadarkNextGeneration,"All tokens have been minted");
    require(numberOfTokens > 0 && numberOfTokens <= PURCHASE_LIMIT,"Would exceed PURCHASE_LIMIT");
    require(msg.value / numberOfTokens == PRICE, "Eth value is not good");

    for (uint256 i = 0; i < numberOfTokens; i++) {
        if (totalPublicSupply < MAX_AvadarkNextGeneration) {
            uint256 tokenId = totalPublicSupply;
            totalPublicSupply += 1;
            _safeMint(msg.sender, tokenId);
        }
    }
}

/**
 * @dev Minting functions
 */