contract MutantMfers is ERC721, IERC2981, ReentrancyGuard, Ownable {
  using Counters for Counters.Counter;
  Counters.Counter private supplyCounter;

  string private customBaseURI = "https://api.mutantmfers.art/mutant/";
  string private customURIExt = "";

  uint256 private price = 26900000000000000;
  uint256 private royalty = 500;
  uint256 private constant MAX_SUPPLY = 10027;
  uint256 private MAX_MULTIMINT = 5;

  address private immutable accessTokenAddress = 0x79FCDEF22feeD20eDDacbB2587640e45491b757f;

  constructor() ERC721("MutantMfers", "MMFERS") {}

  /** URI HANDLING **/

  mapping(uint256 => string) private tokenURIMap;

  function setTokenURIMint(uint256 tokenId, string memory tokenURI_) private
  {
    tokenURIMap[tokenId] = tokenURI_;
  }
  
  function setTokenURI(uint256 tokenId, string memory tokenURI_) external onlyOwner
  {
    require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
    tokenURIMap[tokenId] = tokenURI_;
  }

  function setBaseURI(string memory customBaseURI_) external onlyOwner {
    customBaseURI = customBaseURI_;
  }

  function setURIExt(string memory customURIExt_) external onlyOwner {
    customURIExt = customURIExt_;
  }

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

  function tokenURI(uint256 tokenId) public view override
    returns (string memory)
  {
    require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
    string memory tokenURI_ = tokenURIMap[tokenId];
    if (bytes(tokenURI_).length > 0) {
      return tokenURI_;
    }
    return string(abi.encodePacked(super.tokenURI(tokenId), customURIExt));
  }

  /** MINTING **/

  function setPrice(uint256 price_) external onlyOwner
  {
    price = price_;
  }

  bool public metaMigrated = false;
  function setMetaMigrated(bool metaMigrated_) external onlyOwner {
    metaMigrated = metaMigrated_;
  }

  modifier mintCompliance(uint256[] calldata ids) {
    uint256 count = ids.length;
    require(saleIsActive, "The lab is not open");
    require(totalSupply() + count - 1 < MAX_SUPPLY, "Exceeds max supply");
    require(count <= MAX_MULTIMINT, "Mutate at most 5 mfers at a time");
    _;
  }

  function mint(address _recipient, uint256[] calldata ids) external onlyOwner nonReentrant mintCompliance(ids)	{
		uint256 count = ids.length;
    require(!metaMigrated, "You can't mutate this way");  
    for (uint256 i = 0; i < count; i++) {
      uint256 id = ids[i];
      _mint(_recipient, id);
      supplyCounter.increment();
    }
  }

  function mutate(uint256[] calldata ids) public payable nonReentrant mintCompliance(ids) {
    uint256 count = ids.length;
    require(!metaMigrated, "You can't mutate this way");
    require(msg.value >= price * count, "Insufficient payment");
    ERC721 accessToken = ERC721(accessTokenAddress);
    for (uint256 i = 0; i < count; i++) {
      uint256 id = ids[i];
      if (accessTokenIsActive) {
        require(
          accessToken.ownerOf(id) == msg.sender,
          "Mfer not owned"
        );
      }
      _mint(msg.sender, id);
      supplyCounter.increment();
    }
  }

  function mutate2(uint256[] calldata ids, string[] memory syringeHash) public payable nonReentrant mintCompliance(ids) {
    uint256 count = ids.length;
    require(metaMigrated, "You can't mutate this way");
    require(msg.value >= price * count, "Insufficient payment");
    ERC721 accessToken = ERC721(accessTokenAddress);
    for (uint256 i = 0; i < count; i++) {
      uint256 id = ids[i];
      if (accessTokenIsActive) {
        require(
          accessToken.ownerOf(id) == msg.sender,
          "Mfer not owned"
        );
      }
      _mint(msg.sender, id);
      setTokenURIMint(id, syringeHash[i]);
      supplyCounter.increment();
    }
  }

  function totalSupply() public view returns (uint256) {
    return supplyCounter.current();
  }

  /** ACTIVATION **/

  bool public saleIsActive = false;
  function setSaleIsActive(bool saleIsActive_) external onlyOwner {
    saleIsActive = saleIsActive_;
  }

  bool public accessTokenIsActive = true;
  function setAccessTokenIsActive(bool accessTokenIsActive_) external onlyOwner
  {
    accessTokenIsActive = accessTokenIsActive_;
  }

  /** PAYOUT **/

  address private constant payoutAddress1 =
    0xffB99aaB5E7B7B9507Df1979300a52034C1c60Cc;

  function withdraw() public nonReentrant {
    uint256 balance = address(this).balance;
    Address.sendValue(payable(payoutAddress1), balance);
  }

  /** ROYALTIES **/

  function setRoyalty(uint256 royalty_) external onlyOwner
  {
    royalty = royalty_;
  }

  function royaltyInfo(uint256, uint256 salePrice) external view override
    returns (address receiver, uint256 royaltyAmount)
  {
    return (address(this), (salePrice * royalty) / 10000);
  }

  function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, IERC165)
    returns (bool)
  {
    return (
      interfaceId == type(IERC2981).interfaceId ||
      super.supportsInterface(interfaceId)
    );
  }
}
function setRoyalty(uint256 royalty_) external onlyOwner
{
  royalty = royalty_;
}

/** ROYALTIES **/