contract GrumpyMutants is ERC721Enumerable, Ownable {
  using Strings for uint256;
  
  string public baseURI;
  string public baseExtension = ".json";
  string public notRevealedUri;
  
  uint256 public costPresale = 0.06 ether;
  uint256 public costMainsale = 0.08 ether;
  uint256 public maxSupply = 10000;
  uint256 public maxMintAmountMainSale = 20;
  uint256 public maxMintAmountPresalePerWallet = 5;
  uint256 public maxReserveNfts = 20;
  uint256 public _reserved = 0;

  bool public paused = true;
  bool public revealed = false;
  bool public onlyWhitelisted = true;
  
  mapping(address => bool) private _presaleList;
  mapping(address => uint256) public addressMintedBalanceMainSale;
  mapping(address => uint256) public addressMintedBalancePreSale;

  constructor(
    string memory _name,
    string memory _symbol,
    string memory _initBaseURI,
    string memory _initNotRevealedUri
  ) ERC721(_name, _symbol) {
    setBaseURI(_initBaseURI);
    setNotRevealedURI(_initNotRevealedUri);
  }
  

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

    // public
    function preSaleMint(uint256 _mintAmount) internal {
        if (msg.sender != owner()){
            require(!paused, "the contract is paused");
            require(
            _presaleList[msg.sender] == true,
            "user is not whitelisted"
            );
        }
        require(_mintAmount > 0, "need to mint at least 1 GrumpyMutants");
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply - ( maxReserveNfts - _reserved) , "max NFT supply limit exceeded");
        require(addressMintedBalancePreSale[msg.sender] + _mintAmount <= maxMintAmountPresalePerWallet, "You exceed the per wallet limit" );
        
        
        if (msg.sender != owner()) {
            require(msg.value >= costPresale * _mintAmount, "insufficient funds");
        }
    
        for (uint256 i = 1; i <= _mintAmount; i++) {
          
          _safeMint(msg.sender, supply + i);
          addressMintedBalancePreSale[msg.sender]++;
        }
  }


  
  function reservedMint(uint256 number) public onlyOwner {
        require(_reserved + number <= maxReserveNfts, "Not enough Reserved NFTs for Creator");
        uint256 supply = totalSupply();
        for (uint256 i = 1; i <= number; i++) {
            _safeMint(msg.sender, supply + i);
            _reserved++;
        }
    }
    function addToPresaleListing(address[] calldata _addresses)
        external
        onlyOwner
        {
            for (uint256 index = 0; index < _addresses.length; index++) {
                require(
                    _addresses[index] != address(0),
                    "Can't add a zero address"
                );
                if (_presaleList[_addresses[index]] == false) {
                    _presaleList[_addresses[index]] = true;
                }
            }
        }
    
    function addOneToPresale(address _address)
        external
        onlyOwner
        {
            require(
                    _address != address(0),
                    "Can't add a zero address"
                );
                if (_presaleList[_address] == false) {
                    _presaleList[_address] = true;
                }
        }
    
    function removeFromPresale(address[] calldata _addresses)
        external
        onlyOwner
        {
            for (uint256 ind = 0; ind < _addresses.length; ind++) {
                require(
                    _addresses[ind] != address(0),
                    "Can't remove a zero address"
                );
                if (_presaleList[_addresses[ind]] == true) {
                    _presaleList[_addresses[ind]] = false;
                }
            }
        }
    
    function checkUserOnPresaleList(address _address) external view returns (bool) {
        return _presaleList[_address];
    }
  
  function saleMint(uint256 _mintAmount) internal {
        if (msg.sender != owner()){
            require(!paused, "the contract is paused");
        }
        require(_mintAmount > 0, "need to mint at least 1 NFT");
        uint256 supply = totalSupply();
        require(supply + _mintAmount <= maxSupply - ( maxReserveNfts - _reserved), "max NFT limit exceeded");
        require(addressMintedBalanceMainSale[msg.sender] + _mintAmount <= maxMintAmountMainSale, "You exceed the per wallet limit" );
    
        if (msg.sender != owner()) {
            require(msg.value >= costMainsale * _mintAmount, "insufficient funds");
        }
        
        for (uint256 i = 1; i <= _mintAmount; i++) {
          _safeMint(msg.sender, supply + i);
          addressMintedBalanceMainSale[msg.sender]++;
        }

    }
    
    
    function mainMint(uint256 _mintAmount) external payable{
        
        if (onlyWhitelisted){
            preSaleMint(_mintAmount);
        }
        
        else{
            saleMint(_mintAmount);
        }
    }
  
  
  function walletOfOwner(address _owner)
    public
    view
    returns (uint256[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256[] memory tokenIds = new uint256[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(_owner, i);
    }
    return tokenIds;
  }

  function tokenURI(uint256 tokenId)
    public
    view
    virtual
    override
    returns (string memory)
  {
    require(
      _exists(tokenId),
      "ERC721Metadata: URI query for nonexistent token"
    );
    
    if(revealed == false) {
        return notRevealedUri;
    }

    string memory currentBaseURI = _baseURI();
    return bytes(currentBaseURI).length > 0
        ? string(abi.encodePacked(currentBaseURI, tokenId.toString(), baseExtension))
        : "";
  }
  
  
  function airDropNFTs(address[] calldata _addresses)
        external
        onlyOwner
    
    {
        if (!paused){
            uint256 supply = totalSupply();
            require(
                supply + _addresses.length <= totalSupply(),
                "max total supply exceeded"
                );
            for (uint256 i = 1; i <= _addresses.length; i++) {
              _safeMint(_addresses[i-1], supply + i);
            }
        }
    }

    
  
  function revertReveal() public onlyOwner() {
      revealed = !revealed;
  }
  
  function setMaxMintAmountPresalePerWallet(uint256 _limit) public onlyOwner() {
    maxMintAmountPresalePerWallet = _limit;
  }
  
  function setCostMainSaleCost(uint256 _newCost) public onlyOwner() {
    costMainsale = _newCost;
  }
  function setCostPreSaleCost(uint256 _newCost) public onlyOwner() {
    costPresale = _newCost;
  }
 

  function setMaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner() {
    maxMintAmountMainSale = _newmaxMintAmount;
  }

  function setBaseURI(string memory _newBaseURI) public onlyOwner {
    baseURI = _newBaseURI;
  }

  function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
    baseExtension = _newBaseExtension;
  }
  
  function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
    notRevealedUri = _notRevealedURI;
  }

  function revertPause() external onlyOwner {
      paused = !paused;
  }
  function revertWhitelisting() external onlyOwner{
      onlyWhitelisted = !onlyWhitelisted;
  }
 
  function withdraw() external payable onlyOwner {
    (bool success, ) = payable(msg.sender).call{value: address(this).balance}("");
    require(success);
  }
}
function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }
// internal