contract SmartContract is ERC721Enumerable, Ownable {
  using Counters for Counters.Counter;
  using Strings for uint256;

  Counters.Counter _tokenIds;
  mapping(uint256 => string) _tokenURIs;

  string private _baseURIextended;

  struct RenderToken {
    uint256 id;
    string uri;
  }

  struct RenderTokenByOwner {
    uint256 id;
    string uri;
  }

  uint256 public mintPrice = 0.03 ether;
  uint256 public maxSupply = 7777;
  uint256 public maxTokensPerPurchase = 20;
  bool public isSalesActive = true;

  constructor(
    string memory _name,
    string memory _symbol,
    string memory _initBaseURI
  ) ERC721(_name, _symbol) {
    setBaseURI(_initBaseURI);
  }

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

  function tokenURI(uint256 tokenId)
  public
  view
  virtual
  override
  returns (string memory)
  {
    require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");

    string memory _tokenURI = _tokenURIs[tokenId];
    string memory base = _baseURI();

    if (bytes(base).length == 0) {
      return _tokenURI;
    }
    if (bytes(_tokenURI).length > 0) {
      return string(abi.encodePacked(base, _tokenURI));
    }
    return string(abi.encodePacked(base, tokenId.toString()));
  }

  function getAllTokens()
  public
  view
  returns (RenderToken[] memory)
  {
    uint256 lastestId = _tokenIds.current();
    uint256 counter = 0;
    RenderToken[] memory res = new RenderToken[](lastestId);
    for (uint256 i = 0; i < lastestId; i++) {
      if (_exists(counter)) {
        string memory uri = tokenURI(counter);
        res[counter] = RenderToken(counter, uri);
      }
      counter++;
    }
    return res;
  }

  function walletOfOwner(address _owner)
  public
  view
  returns (RenderTokenByOwner[] memory)
  {
    uint256 ownerTokenCount = balanceOf(_owner);
    RenderTokenByOwner[] memory res = new RenderTokenByOwner[](ownerTokenCount);
    for (uint256 i; i < ownerTokenCount; i++) {
      uint256 tokenId = tokenOfOwnerByIndex(_owner, i);
      string memory uri = tokenURI(tokenId);
      res[i] = RenderTokenByOwner(tokenId, uri);
    }
    return res;
  }

  function mint(address recipient, uint256 _amountRequestedTokens)
  public
  payable
  {
    uint256 supply = totalSupply();

    require(isSalesActive, "Sale is not active");
    require(supply + _amountRequestedTokens <= maxSupply, "Exceeds maximum tokens available for purchase");

    if (msg.sender != owner()) {
      require(_amountRequestedTokens > 0 && _amountRequestedTokens <= maxTokensPerPurchase, "Exceeds maximum tokens you can purchase in a single transaction");
      require(msg.value >= mintPrice * _amountRequestedTokens, string(abi.encodePacked("Ether value sent is not correct: ", msg.value)));
    }

    for (uint256 i = 1; i <= _amountRequestedTokens; i++) {
      _safeMint(recipient, supply + i);
    }
  }

  function getMaxTokenPerPurchase()
  public
  view
  returns (uint256)
  {
    return maxTokensPerPurchase;
  }

  function getMaxTokenSupply()
  public
  view
  returns (uint256)
  {
    return maxSupply;
  }

  function getTokenMinted()
  public
  view
  returns (uint256)
  {
    return totalSupply();
  }

  //only owner
  function setCost(uint256 _newCost)
  public
  onlyOwner
  {
    mintPrice = _newCost;
  }

  function setMaxMintAmount(uint256 _newmaxMintAmount)
  public
  onlyOwner
  {
    maxTokensPerPurchase = _newmaxMintAmount;
  }

  function setBaseURI(string memory _newBaseURI)
  public
  onlyOwner
  {
    _baseURIextended = _newBaseURI;
  }

  function saleActive(bool _state)
  public
  onlyOwner
  {
    isSalesActive = _state;
  }

  function withdraw()
  public
  payable
  onlyOwner
  {
    require(payable(msg.sender).send(address(this).balance));
  }
}
function setCost(uint256 _newCost)
public
onlyOwner
{
  mintPrice = _newCost;
}

//only owner