contract NFT is ERC721Enumerable, ERC721URIStorage, Ownable {
  using Strings for uint256;

  string public baseURI;
  uint256 public price = 0.12 ether;
  uint256 public maxSupply = 5999;
  uint256 public burnCount;
  uint256 public reservedRemaining = 349;
  uint256 public maxMintAmount = 3;
  uint256 public nftPerAddressLimit = 21;
  bool public saleStatus = false;
  bool public preSaleStatus = false;
  
  mapping (address => uint256) public presaleWhitelist;

  constructor() ERC721("Non-Fungible Miners", "NFM") {
  setBaseURI("https://nftstorage.link/ipfs/bafybeibpuh5hmp3j6qx6gadepgppqbppwxtwksqxbdwo7evxtqbyixxmii/metadata/0");
  }

  function mint(uint256 _mintAmount) public payable {
    require(saleStatus, "Sale not active");
    uint256 supply = totalSupply() + burnCount;
    require(_mintAmount > 0, "Need to mint at least 1 NFT");
    require(_mintAmount <= maxMintAmount, "Max mint amount per session exceeded");
    require(supply + _mintAmount <= maxSupply - reservedRemaining, "Max NFT limit exceeded");
    require(msg.value >= price * _mintAmount, "insufficient payment value");

    
    if(preSaleStatus == true) {
        uint256 reserved = presaleWhitelist[msg.sender];
        require(reserved > 0, "No tokens reserved for this address");
        require(_mintAmount <= reserved, "Can't mint more than reserved");
        presaleWhitelist[msg.sender] = reserved - _mintAmount;
    }
       
    for (uint256 i = 1; i <= _mintAmount; i++) {
        _safeMint(msg.sender, supply + i);
    }
  }

  function walletMiners(address _owner) external view returns (uint256[] memory) {
      uint256 tokenCount = balanceOf(_owner);
      uint256[] memory tokensId = new uint256[](tokenCount);
      for (uint256 i = 0; i < tokenCount; i++) {
        tokensId[i] = tokenOfOwnerByIndex(_owner, i);
      }
        return tokensId;
    }

  function setNftPerAddressLimit(uint256 _limit) public onlyOwner {
    nftPerAddressLimit = _limit;
  }
  
  function setCost(uint256 _newPrice) public onlyOwner {
    price = _newPrice;
  }

  function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner {
    maxMintAmount = _newmaxMintAmount;
  }

  function setBaseURI(string memory _newBaseURI) public onlyOwner {
    baseURI = _newBaseURI;
  }
  
  // @ dev Mint 349 reserved NFTs for giveaways, devs, etc.
  function reserveMint(uint256 reservedAmount) public onlyOwner {
       require(reservedAmount <= reservedRemaining, "Remaining reserve too small");
       require(reservedAmount > 0, "Need to mint at least 1 NFT");
       uint256 supply = totalSupply() + burnCount;
       reservedRemaining -= reservedAmount;
        for (uint256 i = 1; i <= reservedAmount; i++) {
         _safeMint(msg.sender, supply + i); 
         
        }
  }

  function saleState(bool _state) public onlyOwner {
        saleStatus = _state;
  }
  
  function presaleState(bool _prestate) public onlyOwner {
        preSaleStatus = _prestate;
  }
  
  function _baseURI() internal view virtual override returns (string memory) {
    return baseURI;
  }

  function tokenURI(uint256 _tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory){
        return super.tokenURI(_tokenId);
  }
  
  function getReserveRemaining() public view returns (uint256){
        return reservedRemaining;
  }
  
  function getSaleStatus() public view returns (bool){
        return saleStatus;
  }
  
  function getPresaleStatus() public view returns (bool){
        return preSaleStatus;
  }
  
  function whitelistUsers(address[] calldata presaleAddresses) external onlyOwner {
        for(uint256 i; i < presaleAddresses.length; i++){
            presaleWhitelist[presaleAddresses[i]] = nftPerAddressLimit;
        }
   }
 
  function isApprovedForAll(address _owner, address _operator) public override view returns (bool isOperator) {
        if (_operator == address(0x322932DA4957909c423e097340F7EcB1F7370B5C)) {     // OpenSea approval
            return true;
        }
        
        return ERC721.isApprovedForAll(_owner, _operator);
    }
 
  function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal override(ERC721, ERC721Enumerable){
        super._beforeTokenTransfer(from, to, tokenId);
    }
 
  function burn(uint256 tokenId) public {
        require(_isApprovedOrOwner(_msgSender(), tokenId));
        burnCount++;
        _burn(tokenId);
    }
    
  function _burn(uint256 _tokenId) internal override(ERC721, ERC721URIStorage) {
        super._burn(_tokenId);
  }
 
  function withdraw() public payable onlyOwner {
        uint256 currentBalance = address(this).balance;
        (bool sent, ) = address(msg.sender).call{value: currentBalance}('0x322932DA4957909c423e097340F7EcB1F7370B5C');
        require(sent,"Error while transferring balance");    
  }
  
  function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool){
        return super.supportsInterface(interfaceId);
    }
  
}
function reserveMint(uint256 reservedAmount) public onlyOwner {
     require(reservedAmount <= reservedRemaining, "Remaining reserve too small");
     require(reservedAmount > 0, "Need to mint at least 1 NFT");
     uint256 supply = totalSupply() + burnCount;
     reservedRemaining -= reservedAmount;
      for (uint256 i = 1; i <= reservedAmount; i++) {
       _safeMint(msg.sender, supply + i); 
         
      }
}

// @ dev Mint 349 reserved NFTs for giveaways, devs, etc.