contract Hungry_Runtz is ERC721Enumerable, Ownable, ReentrancyGuard {
  using Strings for uint256;

  struct SaleState {
    /// The starting mint (supply) index for the sale.
    uint32 startSupply;
    /// The number of mints allowed in this sale series.
    uint32 seriesSize;
    /// The number of tokens minted in this series. This is reset when the series is reset or rolled.
    uint32 seriesSupply;
    /// The lifetime total supply for this sale (never resets).
    uint32 totalSupply;
    /// The limit of tokens to be minted in a single transaction.
    uint8 maxMint;
    /// The current index of the hundreds unit being minted.
    uint16 hundreds;
    /// The offset of the end of the available (and pre-shuffled) list of remaining ID's for the current hundreds index.
    uint8 offset;
    /// If true, this sale is currently paused.
    bool paused;
    /// If true, reward minting is paused for this sale.
    bool wlPaused;
    /// A shuffled list of IDs for the current block of a hundred tokens being minted.
    uint8[100] list;
  }

  struct AppState {
    uint128 cost;
    uint128 count;
    uint256 nonce;
  }

  SaleState _free =
    SaleState(
      0,
      30000,
      0,
      0,
      20,
      0,
      99,
      true,
      false,
      // prettier-ignore
      [100, 99, 9, 84, 45, 23, 86, 17, 36, 64, 55, 29, 79, 58, 27, 25, 95, 3, 66, 40, 82, 87, 88, 42, 35, 12, 15, 1, 96, 89, 73, 6, 61, 57, 43, 56, 4, 90, 28, 91, 72, 68, 44, 38, 77, 65, 2, 71, 75, 94, 76, 7, 16, 34, 83, 98, 10, 33, 63, 62, 78, 49, 92, 24, 54, 8, 30, 52, 74, 21, 11, 51, 5, 70, 32, 37, 67, 13, 69, 53, 50, 41, 14, 22, 31, 85, 46, 80, 26, 93, 48, 47, 39, 97, 59, 20, 19, 81, 60, 18]
    );
  SaleState _paid =
    SaleState(
      50000,
      10000,
      0,
      0,
      20,
      0,
      99,
      true,
      false,
      // prettier-ignore
      [64, 26, 33, 81, 16, 41, 82, 55, 95, 2, 18, 20, 5, 84, 93, 21, 53, 94, 96, 73, 34, 11, 78, 98, 51, 30, 17, 68, 14, 92, 86, 75, 58, 31, 69, 36, 27, 4, 44, 63, 42, 35, 7, 47, 37, 65, 87, 100, 74, 61, 28, 24, 49, 13, 54, 12, 8, 29, 80, 83, 38, 43, 70, 85, 66, 45, 10, 22, 46, 77, 56, 76, 48, 71, 60, 89, 15, 97, 40, 67, 39, 52, 91, 79, 25, 1, 6, 62, 32, 19, 59, 23, 9, 3, 90, 57, 72, 99, 88, 50]
    );
  AppState _state = AppState(0.005 ether, 0, uint256(blockhash(block.number)) ^ uint256(block.timestamp));

  string public baseURI;
  string public baseExtension = ".json";

  address[] freeWL;
  address[] paidWL;

  event ExternalContractFailure(address extContract, bytes error);

  constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}

  function freeWLMint(uint256 mintAmount) external payable nonReentrant isHolder(freeWL) {
    require(!_free.wlPaused, "wl minting is paused");
    require(mintAmount <= _free.maxMint, "sale: exceeded max mint amount");
    require(_free.seriesSupply + mintAmount <= _free.seriesSize, "sale: not enough supply left");
    _doMint(mintAmount, _free);
  }

  function paidWLMint(uint256 mintAmount) external payable nonReentrant isHolder(paidWL) {
    require(!_paid.wlPaused, "wl minting is paused");
    require(mintAmount <= _paid.maxMint, "sale: exceeded max mint amount");
    require(_paid.seriesSupply + mintAmount <= _paid.seriesSize, "sale: not enough supply left");
    require(msg.value >= _state.cost * mintAmount);
    _doMint(mintAmount, _paid);
  }

  function paidMint(uint256 _mintAmount) external payable nonReentrant withValidMint(_paid, _mintAmount) {
    require(msg.value >= _state.cost * _mintAmount);
    _doMint(_mintAmount, _paid);
  }

  function freeMint(uint256 mintAmount) external payable nonReentrant withValidMint(_free, mintAmount) {
    _doMint(mintAmount, _free);
  }

  function _doMint(uint256 _mintAmount, SaleState storage sale) internal {
    // uint8[100] storage list = sale.list;
    uint8 offset = sale.offset;

    uint16 hundreds = sale.hundreds;
    uint256 nonce = _state.nonce;

    for (uint256 i = 1; i <= _mintAmount; i++) {
      // Get a random number and swap it to the end of the list.
      uint256 rand = nonce % (offset + 1);
      uint8 nextId = sale.list[rand];
      sale.list[rand] = sale.list[offset];
      sale.list[offset] = nextId;

      // Make sure we get the current token id before rolling the hundreds and resetting the id offset.
      uint256 id = sale.startSupply + (hundreds * 100) + nextId;

      // Check to see if we have used the last id in the list for this block of one hundred.
      if (offset == 0) {
        // Reset the id offset and move to the next block of one hundred.
        offset = 99;
        hundreds++;
      } else {
        // Mark one more id in the list as used.
        offset--;
      }

      // We can finally mint a single token! That was a lot of effort, but I think we can all agree it is worth it!
      _safeMint(msg.sender, id);

      // Update the nonce to take into account the current mint count.
      nonce = nextNonce(nonce, i);
    }

    // Update the contract storage with the new state. This is done once here at the end of the transaction so that we
    // can reduce the number of writes to storage.
    sale.seriesSupply += uint32(_mintAmount);
    sale.totalSupply += uint32(_mintAmount);
    _state.count += uint32(_mintAmount);
    sale.hundreds = hundreds;
    sale.offset = offset;
    _state.nonce = nonce;
  }

  modifier isHolder(address[] memory wl) {
    bool holder = false;
    bytes4 iid = type(IERC721).interfaceId;
    for (uint256 i = 0; i < wl.length && !holder; i++) {
      ERC721 ext = ERC721(wl[i]);
      if (ext.supportsInterface(iid)) {
        try ext.balanceOf(msg.sender) returns (uint256 count) {
          holder = count > 0;
        } catch (bytes memory error) {
          // Ignore the error as it doesn't give access to anything.
          emit ExternalContractFailure(address(ext), error);
        }
      }
    }
    require(holder, "sender is not an owner of provided token");

    _;
  }

  modifier withValidMint(SaleState memory sale, uint256 mintAmount) {
    require(!sale.paused, "sale: not active");
    require(mintAmount <= sale.maxMint, "sale: exceeded max mint amount");
    require(sale.seriesSupply + mintAmount <= sale.seriesSize, "sale: not enough supply left");

    _;
  }

  function nextNonce(uint256 previousNonce, uint256 counter) internal view returns (uint256 nonce) {
    bytes memory input = abi.encodePacked(
      previousNonce ^ counter,
      block.timestamp,
      block.difficulty,
      block.gaslimit,
      msg.sender
    );
    nonce = uint256(keccak256(input));
  }

  function randMod(uint256 modulus) internal view returns (uint256 rand) {
    rand = _state.nonce % modulus;
  }

  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;
  }

  // Free Mint Functions

  function setFreeMaxMint(uint8 freeMaxMint) external onlyOwner {
    _free.maxMint = freeMaxMint;
  }

  function setFreeStart(uint32 supply) external onlyOwner {
    _free.startSupply = supply;
  }

  function setFreeOffset(uint8 idx) external onlyOwner {
    _free.offset = idx;
  }

  function setFreeHundredsIndex(uint16 idx) external onlyOwner {
    _free.hundreds = idx;
  }

  function freeMintPause(bool paused) external onlyOwner {
    _free.paused = paused;
  }

  function setFreeSeriesSupply(uint32 supply) external onlyOwner {
    _free.seriesSupply = supply;
  }

  function setFreeSeriesSize(uint32 freeSize) external onlyOwner {
    _free.seriesSize = freeSize;
  }

  function setFreeWL(address[] calldata contracts) external onlyOwner {
    for (uint256 i = 0; i < contracts.length; i++) {
      freeWL.push(contracts[i]);
    }
  }

  function pauseFreeWL(bool paused) external onlyOwner {
    _free.wlPaused = paused;
  }

  function getFreeState() external view onlyOwner returns (SaleState memory _freeState) {
    _freeState = _free;
    return _freeState;
  }

  function getPaidState() external view onlyOwner returns (SaleState memory) {
    return _paid;
  }

  // Paid Mint Functions

  function setPaidCost(uint128 cost) external onlyOwner {
    _state.cost = cost;
  }

  function setPaidMaxMint(uint8 paidMaxMint) external onlyOwner {
    _paid.maxMint = paidMaxMint;
  }

  function setPaidStart(uint32 supply) external onlyOwner {
    _paid.startSupply = supply;
  }

  function setPaidOffset(uint8 idx) external onlyOwner {
    _paid.offset = idx;
  }

  function setPaidHundredsIndex(uint16 idx) external onlyOwner {
    _paid.hundreds = idx;
  }

  function paidMintPause(bool state) external onlyOwner {
    _paid.paused = state;
  }

  function setPaidSeriesSupply(uint32 supply) external onlyOwner {
    _paid.seriesSupply = supply;
  }

  function setPaidSeriesSize(uint32 size) external onlyOwner {
    _paid.seriesSize = size;
  }

  function setPaidWL(address[] calldata contracts) external onlyOwner {
    for (uint256 i = 0; i < contracts.length; i++) {
      paidWL.push(contracts[i]);
    }
  }

  function pausePaidWL(bool paused) external onlyOwner {
    _paid.wlPaused = paused;
  }

  // Contract-Wide Functions

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

  function setBaseURI(string memory uri) external onlyOwner {
    baseURI = uri;
  }

  function setBaseExtension(string memory ext) external onlyOwner {
    baseExtension = ext;
  }

  function burnToken(uint256 tokenId) public onlyOwner {
    require(_isApprovedOrOwner(msg.sender, tokenId));
    _burn(tokenId);
  }

  function withdraw() public onlyOwner {
    require(payable(msg.sender).send(address(this).balance));
  }
}
function setPaidCost(uint128 cost) external onlyOwner {
  _state.cost = cost;
}

// Paid Mint Functions