contract BoredApeKennelClub is ERC721Enumerable, Ownable {  
  BAYC private bayc;

  uint constant public MAX_DOG_ADOPTION = 50;
  string public BakcProvenance;
  bool public saleIsActive = false;
  uint256 public collectionStartingIndex;
  uint256 public collectionStartingIndexBlock;
  uint256 public maxDogs;
  uint256 public setBlockTimestamp;
  uint256 public revealTimestamp;
  string private baseURI;

  constructor(
    string memory name,
    string memory symbol,
    uint256 maxNftSupply,
    uint256 saleStart,
    address dependentContractAddress
  ) ERC721(name, symbol) {
    maxDogs = maxNftSupply;
    revealTimestamp = saleStart + (86400 * 7);
    setBlockTimestamp = saleStart + (86400 * 6);
    bayc = BAYC(dependentContractAddress);
  }

  function setRevealTimestamp(uint256 revealTimeStamp) public onlyOwner {
    revealTimestamp = revealTimeStamp;
  }

  function setStartingBlockTimestamp(uint256 startingBlockTimestamp) public onlyOwner {
    setBlockTimestamp = startingBlockTimestamp;
  }

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

  function isMinted(uint256 tokenId) external view returns (bool) {
    require(tokenId < maxDogs, "tokenId outside collection bounds");

    return _exists(tokenId);
  }

  function _baseURI() internal view override returns (string memory) {
    return baseURI;
  }
  
  function setBaseURI(string memory uri) public onlyOwner {
    baseURI = uri;
  }

  function flipSaleState() public onlyOwner {
    saleIsActive = !saleIsActive;
  }

  function trySetStartingIndexBlock() private {
    if (
      collectionStartingIndexBlock == 0 &&
      (totalSupply() == maxDogs || block.timestamp >= setBlockTimestamp)
    ) {
      collectionStartingIndexBlock = block.number;
    }
  }

  /**
    * DM Gordy in Discord that you've got a cure for his bAby MoUtH.
    */
  function adoptDog(uint256 baycTokenId) public {
    require(saleIsActive, "Sale must be active to mint a Dog");
    require(totalSupply() < maxDogs, "Purchase would exceed max supply of Dogs");
    require(baycTokenId < maxDogs, "Requested tokenId exceeds upper bound");
    require(bayc.ownerOf(baycTokenId) == msg.sender, "Must own the Bored Ape for requested tokenId to mint a Dog");
    
    _safeMint(msg.sender, baycTokenId);
    trySetStartingIndexBlock();
  }

  /**
    * DM Garga in Discord, ask him if he's done his pushups
    */
  function adoptNDogs(uint256 startingIndex, uint256 numDogs) public {
    require(saleIsActive, "Sale must be active to mint a Dog");
    require(numDogs > 0, "Must adopt at least one dog");
    require(numDogs <= MAX_DOG_ADOPTION, "Cannot adopt more than fifty dogs at once");
    uint balance = bayc.balanceOf(msg.sender);
    require(balance > 0, "Must hold at least one Ape to mint a Dog");
    require(balance >= startingIndex + numDogs, "Must hold at least as many Apes as the number of Dogs you intend to mint");

    for(uint i = 0; i < balance && i < numDogs && i < MAX_DOG_ADOPTION; i++) {
      require(totalSupply() < maxDogs, "Cannot exceed max supply of Dogs.");
      uint tokenId = bayc.tokenOfOwnerByIndex(msg.sender, i + startingIndex);
      if (!_exists(tokenId)) {
        _safeMint(msg.sender, tokenId);
      }
    }

    trySetStartingIndexBlock();
  }

  /**
    * Set the starting index for the collection
    */
  function setStartingIndex() public {
    require(collectionStartingIndex == 0, "Starting index is already set");
    require(collectionStartingIndexBlock != 0, "Starting index block must be set");
    require(
      block.timestamp >= revealTimestamp || totalSupply() == maxDogs, 
      "Must be on or after the reveal time to set starting index"
    );

    collectionStartingIndex = uint256(blockhash(collectionStartingIndexBlock)) % maxDogs;

    // Just a sanity case in the worst case if this function is called late (EVM only stores last 256 block hashes)
    if ((block.number - collectionStartingIndexBlock) > 255) {
      collectionStartingIndex = uint256(blockhash(block.number - 1)) % maxDogs;
    }

    // Prevent default sequence
    if (collectionStartingIndex == 0) {
      collectionStartingIndex = collectionStartingIndex + 1;
    }
  }

  /**
    * Set the starting index block for the collection, essentially unblocking
    * setting starting index
    */
  function emergencySetStartingIndexBlock() public onlyOwner {
    require(collectionStartingIndexBlock == 0, "Starting index block is already set");
    require(collectionStartingIndex == 0, "Starting index is already set");

    collectionStartingIndexBlock = block.number;
  }
}
function adoptNDogs(uint256 startingIndex, uint256 numDogs) public {
  require(saleIsActive, "Sale must be active to mint a Dog");
  require(numDogs > 0, "Must adopt at least one dog");
  require(numDogs <= MAX_DOG_ADOPTION, "Cannot adopt more than fifty dogs at once");
  uint balance = bayc.balanceOf(msg.sender);
  require(balance > 0, "Must hold at least one Ape to mint a Dog");
  require(balance >= startingIndex + numDogs, "Must hold at least as many Apes as the number of Dogs you intend to mint");

  for(uint i = 0; i < balance && i < numDogs && i < MAX_DOG_ADOPTION; i++) {
    require(totalSupply() < maxDogs, "Cannot exceed max supply of Dogs.");
    uint tokenId = bayc.tokenOfOwnerByIndex(msg.sender, i + startingIndex);
    if (!_exists(tokenId)) {
      _safeMint(msg.sender, tokenId);
    }
  }

  trySetStartingIndexBlock();
}

/**
 * DM Garga in Discord, ask him if he's done his pushups
 */