contract MetaSneaker is ERC721Enumerable, Ownable {
  uint256 public maxSupply = 4000;
  bool public whiteListSaleStatus = false;
  bool public publicSaleStatus = false;
  bool public revealStatus = false;
  uint256 public whiteListPrice = 0.22 ether;
  uint256 public publicPrice = 0.25 ether;
  uint256 public mintTxLimit = 5;
  string public baseUri = "";
  string public contractURI = "https://gateway.pinata.cloud/ipfs/QmcCuXWqPyFtsPUy5zP5cxHLzJHZd9HPXQtQvXVWkc27oc";
  string public beforeRevealURI;
  mapping (address => bool) private _whiteList;
  uint256 private _airdropCount; // default 0;
  mapping (uint256 => address) private _airdropList; // airdrop_id => address, airdrop_id's min value is 1
  mapping (address => uint256) private _airdropAmount;
  event TokensMinted(address receiver, uint256 numOfToken);
  modifier onlyWhiteListed {
    require(_whiteList[msg.sender], "not_whitelisted");
    _;
  }
  modifier onlyAirdropped {
    require(getAirdroppedTokens(msg.sender) > 0, "not_airdropped");
    _;
  }
  constructor() ERC721("Meta Sneakers", "SNEAKERS") {}
  function _baseURI() internal view override returns (string memory) {
    return baseUri;
  }
  function _mintToken(address to, uint256 numOfToken) internal {
    require(numOfToken > 0, "zero_amount");
    require(numOfToken <= mintTxLimit, "exceed_tx_limit");
    require((totalSupply() + numOfToken) <= maxSupply, "exceed_max_supply");
    require(to != address(0), "invalid_to_address");
    for (uint256 i=0; i<numOfToken; i++) {
      _safeMint(to, totalSupply() + 1);
    }
    emit TokensMinted(to, numOfToken);
  }
  function airdrop() external onlyAirdropped {
    _mintToken(msg.sender, getAirdroppedTokens(msg.sender));
  }
  function whiteListSale(uint256 numOfToken) external payable onlyWhiteListed {
    require(whiteListPrice * numOfToken <= msg.value, "insuf_price");
    require(whiteListSaleStatus, "not_sale");
    _mintToken(msg.sender, numOfToken);
  }
  function publicSale(uint256 numOfToken) external payable {
    require(publicPrice * numOfToken <= msg.value, "insuf_price");
    require(publicSaleStatus, "not_sale");
    _mintToken(msg.sender, numOfToken);
  }
  function tokenURI(uint256 tokenId) public  view override returns (string memory) {
    require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
    if (revealStatus){
      return super.tokenURI(tokenId);
    } else {
      return beforeRevealURI;
    }
  }
  function walletOfOwner(address walletAddress) public view returns (uint256[] memory) {
    uint256 tokenCount = balanceOf(walletAddress);
    uint256[] memory tokenIds = new uint256[](tokenCount);
    for (uint256 i=0; i < tokenCount; i++) {
      tokenIds[i] = tokenOfOwnerByIndex(walletAddress, i);
    }
    return tokenIds;
  }
  function isWhitelisted(address addr) public view returns(bool){
    return _whiteList[addr];
  }
  function totalAirdrops() public view returns(uint256) {
    return _airdropCount;
  }
  function getAirdroppedTokens(address addr) public view returns(uint256) {
    uint256 airdrop_id;
    uint256 airdrop_amount = 0;
    for (uint256 i=0; i < _airdropCount; i++) {
      airdrop_id = i + 1;
      if (_airdropList[airdrop_id] == addr) {
        airdrop_amount = _airdropAmount[addr];
        break;
      }
    }
    return airdrop_amount;
  }
  /**
   * Owner methods
  **/
  function adminSetMaxSupply(uint256 newSupply) external onlyOwner {
    maxSupply = newSupply;
  }
  function adminSetBaseURI(string memory newUri) external onlyOwner {
    baseUri = newUri;
  }
  function adminSetMintTxLimit(uint256 newLimit) external onlyOwner {
    mintTxLimit = newLimit;
  }
  function adminSetWhitelistSaleState(bool newStatus) external onlyOwner {
    whiteListSaleStatus = newStatus;
  }
  function adminSetPublicSaleState(bool newStatus) external onlyOwner {
    publicSaleStatus = newStatus;
  }
  function adminSetWhitelistPrice(uint256 newPrice) external onlyOwner {
    whiteListPrice = newPrice;
  }
  function adminSetPublicPrice(uint256 newPrice) external onlyOwner {
    publicPrice = newPrice;
  }
  function adminSetRevealStatus(bool newStatus) external onlyOwner {
    revealStatus = newStatus;
  }
  function adminSetBeforeRevealURI(string memory newUri) external onlyOwner {
    beforeRevealURI = newUri;
  }
  function adminAddToWhitelistBulk(address[] calldata addrs) external onlyOwner {
    for (uint256 i=0; i < addrs.length; i++) {
      _whiteList[addrs[i]] = true;
    }
  }
  function adminRemoveFromWhitelist(address addr) external onlyOwner {
    _whiteList[addr] = false;
  }
  function adminAirdrop(uint256 startRange, uint256 endRange) external onlyOwner {
    require(startRange >= 0 && endRange <= _airdropCount,"out_of_airdrop_count");
    require(startRange <= endRange);
    for (uint256 i=startRange; i<endRange; i++) {
      uint256 airdrop_id = i + 1;
      address holder_addr = _airdropList[airdrop_id];
      uint256 num_of_token = _airdropAmount[holder_addr];
      if (holder_addr != address(0) && num_of_token > 0) {
        for (uint256 j=0; j<num_of_token; j++) {
          _safeMint(holder_addr, totalSupply() + 1);
        }
        _airdropAmount[holder_addr] = 0;
      }
    }
  }
  function adminAddAirdropBulk(address[] calldata addrs, uint256[] calldata numOfTokens) external onlyOwner {
    require (addrs.length == numOfTokens.length, 'invalid_params');
    uint256 airdrop_id;
    for (uint256 i=0; i < addrs.length; i++) {
      airdrop_id = totalAirdrops() + 1;
      _airdropList[airdrop_id] = addrs[i];
      _airdropAmount[addrs[i]] = numOfTokens[i];
      _airdropCount = airdrop_id;
    }
  }
  function adminRemoveAirdrop(address addr) external onlyOwner {
    uint256 airdrop_id;
    for (uint256 i=0; i < _airdropCount; i++) {
      airdrop_id = i + 1;
      if (_airdropList[airdrop_id] == addr) {
        _airdropAmount[addr] = 0;
        break;
      }
    }
  }
  function withdraw() external onlyOwner {
    require(address(this).balance > 0, "no_balance");
    bool success = false;
    (success, ) = (payable(msg.sender)).call{value: address(this).balance}("");
    require(success, "withdraw_failed");
  }
}
// MetaSneaker.sol
function adminSetMaxSupply(uint256 newSupply) external onlyOwner {
  maxSupply = newSupply;
}

/**
 * Owner methods
 **/