contract PepeReborn is Ownable, Usernames {

  uint32[15] public cozyCoolDowns = [ // determined by generation / 2
      uint32(1 minutes),
      uint32(2 minutes),
      uint32(5 minutes),
      uint32(15 minutes),
      uint32(30 minutes),
      uint32(45 minutes),
      uint32(1 hours),
      uint32(2 hours),
      uint32(4 hours),
      uint32(8 hours),
      uint32(16 hours),
      uint32(1 days),
      uint32(2 days),
      uint32(4 days),
      uint32(7 days)
  ];

  struct Pepe {
      address master; // The master of the pepe
      uint256[2] genotype; // all genes stored here
      uint64 canCozyAgain; // time when pepe can have nice time again
      uint64 generation; // what generation?
      uint64 father; // father of this pepe
      uint64 mother; // mommy of this pepe
      uint8 coolDownIndex;
  }

  struct UndeadPepeMutable {
    address master; // The master of the pepe
    // uint256[2] genotype; // all genes stored here
    uint64 canCozyAgain; // time when pepe can have nice time again
    // uint64 generation; // what generation?
    // uint64 father; // father of this pepe
    // uint64 mother; // mommy of this pepe
    uint8 coolDownIndex;
    bool resurrected; // has the pepe been duplicated off the old contract
  }

  mapping(uint256 => bytes32) public pepeNames;

  // stores reborn pepes. index 0 holds pepe 5497
  Pepe[] private rebornPepes;

  // stores undead pepes. get the mutables from the old contract
  mapping(uint256 => UndeadPepeMutable) private undeadPepes;

  //address private constant PEPE_UNDEAD_ADDRRESS = 0x84aC94F17622241f313511B629e5E98f489AD6E4;
  //address private constant PEPE_AUCTION_SALE_UNDEAD_ADDRESS = 0x28ae3DF366726D248c57b19fa36F6D9c228248BE;
  //address private constant COZY_TIME_AUCTION_UNDEAD_ADDRESS = 0xE2C43d2C6D6875c8F24855054d77B5664c7e810f;
  address private PEPE_UNDEAD_ADDRRESS;
  address private PEPE_AUCTION_SALE_UNDEAD_ADDRESS;
  address private COZY_TIME_AUCTION_UNDEAD_ADDRESS;
  GenePoolInterface private genePool;

  uint256 private constant REBORN_PEPE_0 = 5497;

  bool public constant implementsERC721 = true; // signal erc721 support

  // solhint-disable-next-line const-name-snakecase
  string public constant name = "Crypto Pepe Reborn";
  // solhint-disable-next-line const-name-snakecase
  string public constant symbol = "CPRE";

  // Token Base URI
  string public baseTokenURI = "https://api.cryptopepes.lol/getPepe/";
  // Contract URI
  string private contractUri = "https://cryptopepes.lol/contract-metadata.json";

  // Mapping from owner to list of owned token IDs
  mapping(address => uint256[]) private wallets;
  // Mapping from token ID to index in owners wallet
  mapping(uint256 => uint256) private walletIndex;
  mapping(uint256 => address) public approved; // pepe index to address approved to transfer
  mapping(address => mapping(address => bool)) public approvedForAll;

  uint256 private preminedPepes = 0;
  uint256 private constant MAX_PREMINE = 1100;

  modifier onlyPepeMaster(uint256 pepeId) {
    require(_ownerOf(pepeId) == msg.sender);
    _;
  }

  modifier onlyAllowed(uint256 pepeId) {
    address master = _ownerOf(pepeId);
    // solhint-disable-next-line max-line-length
    require(msg.sender == master || msg.sender == approved[pepeId] || approvedForAll[master][msg.sender]); // check if msg.sender is allowed
    _;
  }

  event PepeBorn(uint256 indexed mother, uint256 indexed father, uint256 indexed pepeId);
  event PepeNamed(uint256 indexed pepeId);

  constructor(address baseAddress, address saleAddress, address cozyAddress, address genePoolAddress) public {
    PEPE_UNDEAD_ADDRRESS = baseAddress;
    PEPE_AUCTION_SALE_UNDEAD_ADDRESS = saleAddress;
    COZY_TIME_AUCTION_UNDEAD_ADDRESS = cozyAddress;
    setGenePool(genePoolAddress);
  }

  /**
   * @dev Internal function that creates a new pepe
   * @param  _genoType DNA of the new pepe
   * @param  _mother The ID of the mother
   * @param  _father The ID of the father
   * @param  _generation The generation of the new Pepe
   * @param  _master The owner of this new Pepe
   * @return The ID of the newly generated Pepe
   */
  // solhint-disable-next-line max-line-length
  function _newPepe(uint256[2] _genoType, uint64 _mother, uint64 _father, uint64 _generation, address _master) internal returns (uint256 pepeId) {
    uint8 tempCoolDownIndex;

    tempCoolDownIndex = uint8(_generation / 2);

    if (_generation > 28) {
      tempCoolDownIndex = 14;
    }

    Pepe memory _pepe = Pepe({
      master: _master, // The master of the pepe
      genotype: _genoType, // all genes stored here
      canCozyAgain: 0, // time when pepe can have nice time again
      father: _father, // father of this pepe
      mother: _mother, // mommy of this pepe
      generation: _generation, // what generation?
      coolDownIndex: tempCoolDownIndex
    });

    // push returns the new length, use it to get a new unique id
    pepeId = rebornPepes.push(_pepe) + REBORN_PEPE_0 - 1;

    // add it to the wallet of the master of the new pepe
    addToWallet(_master, pepeId);

    emit PepeBorn(_mother, _father, pepeId);
    emit Transfer(address(0), _master, pepeId);

    return pepeId;
  }

  /**
   * @dev Premine pepes. Can only be called by the owner and is limited to MAX_PREMINE
   * @param  _amount Amount of Pepes to premine
   */
  function pepePremine(uint256 _amount) public onlyOwner {
    for (uint i = 0; i < _amount; i++) {
      require(preminedPepes < MAX_PREMINE);//can only generate set amount during premine
      //create a new pepe
      // 1) who's genes are based on hash of the timestamp and the new pepe's id
      // 2) who has no mother or father
      // 3) who is generation zero
      // 4) who's master is the manager

      // solhint-disable-next-line
      _newPepe(genePool.randomDNA(uint256(keccak256(abi.encodePacked(block.timestamp, (REBORN_PEPE_0 + rebornPepes.length))))), 0, 0, 0, owner);
      ++preminedPepes;
    }
  }

  /*
   * @dev CozyTime two Pepes together
   * @param  _mother The mother of the new Pepe
   * @param  _father The father of the new Pepe
   * @param  _pepeReceiver Address receiving the new Pepe
   * @return If it was a success
   */
  function cozyTime(uint256 _mother, uint256 _father, address _pepeReceiver) external returns (bool) {
    // cannot cozyTime with itself
    require(_mother != _father);
    // ressurect parents if needed
    checkResurrected(_mother);
    checkResurrected(_father);
    // get parents
    Pepe memory mother = _getPepe(_mother);
    Pepe memory father = _getPepe(_father);
    // caller has to either be master or approved for mother
    // solhint-disable-next-line max-line-length
    require(mother.master == msg.sender || approved[_mother] == msg.sender || approvedForAll[mother.master][msg.sender]);
    // caller has to either be master or approved for father
    // solhint-disable-next-line max-line-length
    require(father.master == msg.sender || approved[_father] == msg.sender || approvedForAll[father.master][msg.sender]);
    // require both parents to be ready for cozytime
    // solhint-disable-next-line not-rely-on-time
    require(now > mother.canCozyAgain && now > father.canCozyAgain);
    // require both mother parents not to be father
    require(mother.father != _father && mother.mother != _father);
    require(father.mother != _mother && father.father != _mother);

    approved[_father] = address(0);
    approved[_mother] = address(0);

    uint256[2] memory newGenotype = genePool.breed(father.genotype, mother.genotype, REBORN_PEPE_0+rebornPepes.length);

    uint64 newGeneration;

    newGeneration = mother.generation + 1;
    if (newGeneration < father.generation + 1) { // if father generation is bigger
        newGeneration = father.generation + 1;
    }

    uint64 motherCanCozyAgain = _handleCoolDown(_mother);
    _handleCoolDown(_father);

    // birth new pepe
    // _pepeReceiver becomes the master of the pepe
    uint256 pepeId = _newPepe(newGenotype, uint64(_mother), uint64(_father), newGeneration, _pepeReceiver);
    // sets pepe birth when mother is done
    // solhint-disable-next-line max-line-length
    rebornPepes[rebornPepeIdToIndex(pepeId)].canCozyAgain = motherCanCozyAgain;

    return true;
  }

  /**
   * @dev Internal function to increase the coolDownIndex
   * @param pepeId The id of the Pepe to update the coolDown of
   * @return The time that pepe can cozy again
   */
  function _handleCoolDown(uint256 pepeId) internal returns (uint64){
    if(pepeId >= REBORN_PEPE_0){
      Pepe storage tempPep1 = rebornPepes[pepeId];
      // solhint-disable-next-line not-rely-on-time
      tempPep1.canCozyAgain = uint64(now + cozyCoolDowns[tempPep1.coolDownIndex]);
      if (tempPep1.coolDownIndex < 14) {// after every cozy time pepe gets slower
        tempPep1.coolDownIndex++;
      }
      return tempPep1.canCozyAgain;
    }else{
      // this function is only called in cozyTime(), pepe has already been resurrected
      UndeadPepeMutable storage tempPep2 = undeadPepes[pepeId];
      // solhint-disable-next-line not-rely-on-time
      tempPep2.canCozyAgain = uint64(now + cozyCoolDowns[tempPep2.coolDownIndex]);
      if (tempPep2.coolDownIndex < 14) {// after every cozy time pepe gets slower
        tempPep2.coolDownIndex++;
      }
      return tempPep2.canCozyAgain;
    }
  }

  /**
   * @dev Set the name of a Pepe. Can only be set once
   * @param pepeId ID of the pepe to name
   * @param _name The name to assign
   */
  function setPepeName(uint256 pepeId, bytes32 _name) public onlyPepeMaster(pepeId) returns(bool) {
      require(pepeNames[pepeId] == 0x0000000000000000000000000000000000000000000000000000000000000000);
      pepeNames[pepeId] = _name;
      emit PepeNamed(pepeId);
      return true;
  }

  /**
   * @dev Transfer a Pepe to the auction contract and auction it
   * @param  pepeId ID of the Pepe to auction
   * @param  _auction Auction contract address
   * @param  _beginPrice Price the auction starts at
   * @param  _endPrice Price the auction ends at
   * @param  _duration How long the auction should run
   */
  // solhint-disable-next-line max-line-length
  function transferAndAuction(uint256 pepeId, address _auction, uint256 _beginPrice, uint256 _endPrice, uint64 _duration) public onlyPepeMaster(pepeId) {
    //checkResurrected(pepeId);
    _transfer(msg.sender, _auction, pepeId);// transfer pepe to auction
    AuctionBase auction = AuctionBase(_auction);
    auction.startAuctionDirect(pepeId, _beginPrice, _endPrice, _duration, msg.sender);
  }

  /**
   * @dev Approve and buy. Used to buy cozyTime in one call
   * @param  pepeId Pepe to cozy with
   * @param  _auction Address of the auction contract
   * @param  _cozyCandidate Pepe to approve and cozy with
   * @param  _candidateAsFather Use the candidate as father or not
   */
  // solhint-disable-next-line max-line-length
  function approveAndBuy(uint256 pepeId, address _auction, uint256 _cozyCandidate, bool _candidateAsFather) public payable onlyPepeMaster(_cozyCandidate) {
    checkResurrected(pepeId);
    approved[_cozyCandidate] = _auction;
    // solhint-disable-next-line max-line-length
    RebornCozyTimeAuction(_auction).buyCozy.value(msg.value)(pepeId, _cozyCandidate, _candidateAsFather, msg.sender); // breeding resets approval
  }

  /**
   * @dev The same as above only pass an extra parameter
   * @param  pepeId Pepe to cozy with
   * @param  _auction Address of the auction contract
   * @param  _cozyCandidate Pepe to approve and cozy with
   * @param  _candidateAsFather Use the candidate as father or not
   * @param  _affiliate Address to set as affiliate
   */
  // solhint-disable-next-line max-line-length
  function approveAndBuyAffiliated(uint256 pepeId, address _auction, uint256 _cozyCandidate, bool _candidateAsFather, address _affiliate) public payable onlyPepeMaster(_cozyCandidate) {
    checkResurrected(pepeId);
    approved[_cozyCandidate] = _auction;
    // solhint-disable-next-line max-line-length
    RebornCozyTimeAuction(_auction).buyCozyAffiliated.value(msg.value)(pepeId, _cozyCandidate, _candidateAsFather, msg.sender, _affiliate); // breeding resets approval
  }

  /**
   * @dev Get the time when a pepe can cozy again
   * @param  pepeId ID of the pepe
   * @return Time when the pepe can cozy again
   */
  function getCozyAgain(uint256 pepeId) public view returns(uint64) {
    return _getPepe(pepeId).canCozyAgain;
  }

  /**
   *  ERC721 Compatibility
   *
   */
  event Approval(address indexed _owner, address indexed _approved, uint256 pepeId);
  event Transfer(address indexed _from, address indexed _to, uint256 indexed pepeId);
  event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

  /**
   * @dev Get the total number of Pepes
   * @return total Returns the total number of pepes
   */
  function totalSupply() public view returns(uint256) {
    return REBORN_PEPE_0 + rebornPepes.length - 1;
  }

  /**
   * @dev Get the number of pepes owned by an address
   * Note that this only includes reborn and resurrected pepes
   * Pepes that are still dead are not counted.
   * @param  _owner Address to get the balance from
   * @return balance The number of pepes
   */
  function balanceOf(address _owner) external view returns (uint256 balance) {
    return wallets[_owner].length;
  }

  /**
   * @dev Get the owner of a Pepe
   * Note that this returns pepes from old auctions
   * @param  pepeId the token to get the owner of
   * @return the owner of the pepe
   */
  function ownerOf(uint256 pepeId) external view returns (address) {
    return _getPepe(pepeId).master;
  }

  /**
   * @dev Get the owner of a Pepe
   * Note that this returns pepes from old auctions
   * @param  pepeId the token to get the owner of
   * @return the owner of the pepe
   */
  function _ownerOf(uint256 pepeId) internal view returns (address) {
    return _getPepe(pepeId).master;
  }

  /**
   * @dev Get the id of an token by its index
   * @param _owner The address to look up the tokens of
   * @param _index Index to look at
   * @return pepeId the ID of the token of the owner at the specified index
   */
  function tokenOfOwnerByIndex(address _owner, uint256 _index) public constant returns (uint256 pepeId) {
    // The index must be smaller than the balance,
    // to guarantee that there is no leftover token returned.
    require(_index < wallets[_owner].length);
    return wallets[_owner][_index];
  }

  /**
   * @dev Private method that ads a token to the wallet
   * @param _owner Address of the owner
   * @param pepeId Pepe ID to add
   */
  function addToWallet(address _owner, uint256 pepeId) private {
    /*
    uint256 length = wallets[_owner].length;
    wallets[_owner].push(pepeId);
    walletIndex[pepeId] = length;
    */
    walletIndex[pepeId] = wallets[_owner].length;
    wallets[_owner].push(pepeId);
  }

  /**
   * @dev Remove a token from a address's wallet
   * @param _owner Address of the owner
   * @param pepeId Token to remove from the wallet
   */
  function removeFromWallet(address _owner, uint256 pepeId) private {
    // walletIndex returns 0 if not initialized to a value
    // verify before removing
    if(walletIndex[pepeId] == 0 && (wallets[_owner].length == 0 || wallets[_owner][0] != pepeId)) return;

    // pop last element from wallet, move it to this index
    uint256 tokenIndex = walletIndex[pepeId];
    uint256 lastTokenIndex = wallets[_owner].length - 1;
    uint256 lastToken = wallets[_owner][lastTokenIndex];
    wallets[_owner][tokenIndex] = lastToken;
    wallets[_owner].length--;
    walletIndex[pepeId] = 0;
    walletIndex[lastToken] = tokenIndex;
  }

  /**
   * @dev Internal transfer function
   * @param _from Address sending the token
   * @param _to Address to token is send to
   * @param pepeId ID of the token to send
   */
  function _transfer(address _from, address _to, uint256 pepeId) internal {
    checkResurrected(pepeId);
    if(pepeId >= REBORN_PEPE_0) rebornPepes[rebornPepeIdToIndex(pepeId)].master = _to;
    else undeadPepes[pepeId].master = _to;
    approved[pepeId] = address(0);//reset approved of pepe on every transfer
    //remove the token from the _from wallet
    removeFromWallet(_from, pepeId);
    //add the token to the _to wallet
    addToWallet(_to, pepeId);
    emit Transfer(_from, _to, pepeId);
  }

  /**
   * @dev transfer a token. Can only be called by the owner of the token
   * @param  _to Addres to send the token to
   * @param  pepeId ID of the token to send
   */
  // solhint-disable-next-line no-simple-event-func-name
  function transfer(address _to, uint256 pepeId) public onlyPepeMaster(pepeId) returns(bool) {
    _transfer(msg.sender, _to, pepeId);//after master modifier invoke internal transfer
    return true;
  }

  /**
   * @dev Approve a address to send a token
   * @param _to Address to approve
   * @param pepeId Token to set approval for
   */
  function approve(address _to, uint256 pepeId) external onlyPepeMaster(pepeId) {
    approved[pepeId] = _to;
    emit Approval(msg.sender, _to, pepeId);
  }

  /**
   * @dev Approve or revoke approval an address for all tokens of a user
   * @param _operator Address to (un)approve
   * @param _approved Approving or revoking indicator
   */
  function setApprovalForAll(address _operator, bool _approved) external {
    approvedForAll[msg.sender][_operator] = _approved;
    emit ApprovalForAll(msg.sender, _operator, _approved);
  }

  /**
   * @dev Get approved address for a token
   * @param pepeId Token ID to get the approved address for
   * @return The address that is approved for this token
   */
  function getApproved(uint256 pepeId) external view returns (address) {
    return approved[pepeId];
  }

  /**
   * @dev Get if an operator is approved for all tokens of that owner
   * @param _owner Owner to check the approval for
   * @param _operator Operator to check approval for
   * @return Boolean indicating if the operator is approved for that owner
   */
  function isApprovedForAll(address _owner, address _operator) external view returns (bool) {
    return approvedForAll[_owner][_operator];
  }

  /**
   * @dev Function to signal support for an interface
   * @param interfaceID the ID of the interface to check for
   * @return Boolean indicating support
   */
  function supportsInterface(bytes4 interfaceID) external pure returns (bool) {
    if(
         interfaceID == 0x01ffc9a7 // ERC 165
      || interfaceID == 0x80ac58cd // ERC 721 base
      || interfaceID == 0x780e9d63 // ERC 721 enumerable
      || interfaceID == 0x4f558e79 // ERC 721 exists
      || interfaceID == 0x5b5e139f // ERC 721 metadata
      // TODO: add more interfaces such as
      // 0x150b7a02: ERC 721 receiver
    ) {
      return true;
    }
    return false;
  }

  /**
   * @dev Safe transferFrom function
   * @param _from Address currently owning the token
   * @param _to Address to send token to
   * @param pepeId ID of the token to send
   */
  function safeTransferFrom(address _from, address _to, uint256 pepeId) external {
    _safeTransferFromInternal(_from, _to, pepeId, "");
  }

  /**
   * @dev Safe transferFrom function with aditional data attribute
   * @param _from Address currently owning the token
   * @param _to Address to send token to
   * @param pepeId ID of the token to send
   * @param _data Data to pass along call
   */
  // solhint-disable-next-line max-line-length
  function safeTransferFrom(address _from, address _to, uint256 pepeId, bytes _data) external {
    _safeTransferFromInternal(_from, _to, pepeId, _data);
  }

  /**
   * @dev Internal Safe transferFrom function with aditional data attribute
   * @param _from Address currently owning the token
   * @param _to Address to send token to
   * @param pepeId ID of the token to send
   * @param _data Data to pass along call
   */
  // solhint-disable-next-line max-line-length
  function _safeTransferFromInternal(address _from, address _to, uint256 pepeId, bytes _data) internal onlyAllowed(pepeId) {
    require(_ownerOf(pepeId) == _from);//check if from is current owner
    require(_to != address(0));//throw on zero address

    _transfer(_from, _to, pepeId); //transfer token

    if (isContract(_to)) { //check if is contract
      // solhint-disable-next-line max-line-length
      require(ERC721TokenReceiver(_to).onERC721Received(_from, pepeId, _data) == bytes4(keccak256("onERC721Received(address,uint256,bytes)")));
    }
  }

  /**
   * @dev TransferFrom function
   * @param _from Address currently owning the token
   * @param _to Address to send token to
   * @param pepeId ID of the token to send
   * @return If it was successful
   */
  // solhint-disable-next-line max-line-length
  function transferFrom(address _from, address _to, uint256 pepeId) public onlyAllowed(pepeId) returns(bool) {
    require(_ownerOf(pepeId) == _from);//check if _from is really the master.
    require(_to != address(0));
    _transfer(_from, _to, pepeId);//handles event, balances and approval reset;
    return true;
  }

  /**
   * @dev Utility method to check if an address is a contract
   * @param _address Address to check
   * @return Boolean indicating if the address is a contract
   */
  function isContract(address _address) internal view returns (bool) {
    uint size;
    // solhint-disable-next-line no-inline-assembly
    assembly { size := extcodesize(_address) }
    return size > 0;
  }

  /**
   * @dev Returns whether the specified token exists
   * @param pepeId uint256 ID of the token to query the existence of
   * @return whether the token exists
   */
  function exists(uint256 pepeId) public view returns (bool) {
    return 0 < pepeId && pepeId <= (REBORN_PEPE_0 + rebornPepes.length - 1);//this.totalSupply();
  }

  /**
   * @dev Returns an URI for a given token ID
   * Throws if the token ID does not exist. May return an empty string.
   * @param pepeId uint256 ID of the token to query
   */
  function tokenURI(uint256 pepeId) public view returns (string) {
    require(exists(pepeId));
    return string(abi.encodePacked(baseTokenURI, toString(pepeId)));
  }

  /**
   * @dev Changes the base URI for metadata.
   * @param baseURI the new base URI
   */
   function setBaseTokenURI(string baseURI) public onlyOwner {
     baseTokenURI = baseURI;
   }

   /**
    * @dev Returns the URI for the contract
    * @return the uri
    */
   function contractURI() public view returns (string) {
     return contractUri;
   }

   /**
    * @dev Changes the URI for the contract
    * @param uri the new uri
    */
   function setContractURI(string uri) public onlyOwner {
     contractUri = uri;
   }

  /**
   * @dev Converts a `uint256` to its ASCII `string` representation.
   * @param value a number to convert to string
   * @return a string representation of the number
   */
  function toString(uint256 value) internal pure returns (string memory) {
    // Borrowed from Open Zeppelin, which was
    // Inspired by OraclizeAPI's implementation - MIT licence
    // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
    if (value == 0) {
      return "0";
    }
    uint256 temp = value;
    uint256 digits;
    while (temp != 0) {
      digits++;
      temp /= 10;
    }
    bytes memory buffer = new bytes(digits);
    uint256 index = digits - 1;
    temp = value;
    while (temp != 0) {
      buffer[index--] = byte(uint8(48 + temp % 10));
      temp /= 10;
    }
    return string(buffer);
  }

  /**
   * @dev get Pepe information
   * Returns information as separate variables
   * @param  pepeId ID of the Pepe to get information of
   * @return master
   * @return genotype
   * @return canCozyAgain
   * @return generation
   * @return father
   * @return mother
   * @return pepeName
   * @return coolDownIndex
   */
  // solhint-disable-next-line max-line-length
  function getPepe(uint256 pepeId) public view returns (address master, uint256[2] genotype, uint64 canCozyAgain, uint64 generation, uint256 father, uint256 mother, bytes32 pepeName, uint8 coolDownIndex) {
    Pepe memory pepe = _getPepe(pepeId);
    master = pepe.master;
    genotype = pepe.genotype;
    canCozyAgain = pepe.canCozyAgain;
    generation = pepe.generation;
    father = pepe.father;
    mother = pepe.mother;
    pepeName = pepeNames[pepeId];
    coolDownIndex = pepe.coolDownIndex;
  }

  /**
   * @dev get Pepe information
   * Returns information as a single Pepe struct
   * @param  pepeId ID of the Pepe to get information of
   * @return pepe info
   */
  function _getPepe(uint256 pepeId) internal view returns (Pepe memory) {
    if(pepeId >= REBORN_PEPE_0) {
      uint256 index = rebornPepeIdToIndex(pepeId);
      require(index < rebornPepes.length);
      return rebornPepes[index];
    }else{
      (address master, uint256[2] memory genotype, uint64 canCozyAgain, uint64 generation, uint256 father, uint256 mother, , uint8 coolDownIndex) = _getUndeadPepe(pepeId);
      return Pepe({
        master: master, // The master of the pepe
        genotype: genotype, // all genes stored here
        canCozyAgain: canCozyAgain, // time when pepe can have nice time again
        father: uint64(father), // father of this pepe
        mother: uint64(mother), // mommy of this pepe
        generation: generation, // what generation?
        coolDownIndex: coolDownIndex
      });
    }
  }

  /**
   * @dev get undead pepe information
   * @param  pepeId ID of the Pepe to get information of
   * @return master
   * @return genotype
   * @return canCozyAgain
   * @return generation
   * @return father
   * @return mother
   * @return pepeName
   * @return coolDownIndex
   */
  function _getUndeadPepe(uint256 pepeId) internal view returns (address master, uint256[2] genotype, uint64 canCozyAgain, uint64 generation, uint256 father, uint256 mother, bytes32 pepeName, uint8 coolDownIndex) {
    // if undead, pull from old contract
    (master, genotype, canCozyAgain, generation, father, mother, pepeName, coolDownIndex) = PepeBase(PEPE_UNDEAD_ADDRRESS).getPepe(pepeId);
    if(undeadPepes[pepeId].resurrected){
      // if resurrected, pull from undead map
      master = undeadPepes[pepeId].master;
      canCozyAgain = undeadPepes[pepeId].canCozyAgain;
      pepeName = pepeNames[pepeId];
      coolDownIndex = undeadPepes[pepeId].coolDownIndex;
    }else if(master == PEPE_AUCTION_SALE_UNDEAD_ADDRESS || master == COZY_TIME_AUCTION_UNDEAD_ADDRESS){
      // if on auction, return to seller
      (master, , , , , ) = AuctionBase(master).auctions(pepeId);
    }
  }

  // Useful for tracking resurrections
  event PepeResurrected(uint256 pepeId);

  /**
   * @dev Checks if the pepe needs to be resurrected from the old contract and if so does.
   * @param pepeId ID of the Pepe to check
   */
  // solhint-disable-next-line max-line-length
  function checkResurrected(uint256 pepeId) public {
    if(pepeId >= REBORN_PEPE_0) return;
    if(undeadPepes[pepeId].resurrected) return;
    (address _master, , uint64 _canCozyAgain, , , , bytes32 _pepeName, uint8 _coolDownIndex) = _getUndeadPepe(pepeId);
    undeadPepes[pepeId] = UndeadPepeMutable({
      master: _master, // The master of the pepe
      canCozyAgain: _canCozyAgain, // time when pepe can have nice time again
      coolDownIndex: _coolDownIndex,
      resurrected: true
    });
    if(_pepeName != 0x0000000000000000000000000000000000000000000000000000000000000000) pepeNames[pepeId] = _pepeName;
    addToWallet(_master, pepeId);
    emit PepeResurrected(pepeId);
  }

  /**
   * @dev Calculates reborn pepe array index
   * @param pepeId ID of the pepe to check
   * @return array index
   */
  function rebornPepeIdToIndex(uint256 pepeId) internal pure returns (uint256) {
    require(pepeId >= REBORN_PEPE_0);
    return pepeId - REBORN_PEPE_0;
  }

  /**
   * @dev Changes the address of the previous contracts
   * This is only a precaution in case I mess up deployment
   * @param baseaddr the correct PepeBase address
   * @param saleauctionaddr the correct PepeSaleAuction address
   * @param cozyauctionaddr the correct CozyTimeAuction address
   */
  function setPrevContracts(address baseaddr, address saleauctionaddr, address cozyauctionaddr) public onlyOwner {
    PEPE_UNDEAD_ADDRRESS = baseaddr;
    PEPE_AUCTION_SALE_UNDEAD_ADDRESS = saleauctionaddr;
    COZY_TIME_AUCTION_UNDEAD_ADDRESS = cozyauctionaddr;
  }

  /**
   * @dev Changes the gene pool contract address
   * @param genePoolAddress the new pool address
   */
  function setGenePool(address genePoolAddress) public onlyOwner {
    GenePoolInterface pool = GenePoolInterface(genePoolAddress);
    require(pool.isGenePool());
    genePool = pool;
  }
}
function contractURI() public view returns (string) {
  return contractUri;
}

/**
 * @dev Returns the URI for the contract
 * @return the uri
 */