contract Logarithms is Context, AccessControlEnumerable, ERC721, ERC721Enumerable, Ownable {

  string constant base_uri_head = "https://logarithms.art/metadata/";
  string constant base_uri_tail = ".json";

  uint constant public maxTotalSupply = 2500;
  uint constant public teamSupply = 125;
  uint constant public publicSupply = maxTotalSupply - teamSupply;
  uint constant public maxPerCall = 20;
  uint constant public whiteListMax = 2;

  uint public pricePerToken = 0.05 ether;
  uint public tokensMinted = 0;
  uint public tokensBurned = 0;
  bool public teamTokensMinted = false;
  bool public tradeActive = false;

  enum SaleState{ CLOSED, PRIVATE, PUBLIC }
  SaleState public saleState = SaleState.CLOSED;

  bytes32 private merkleRoot;

  constructor() ERC721("Logarithms", "Log") {
    _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
  }

  function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes calldata data) public override {
    require(tradeActive, "Trade is not active");
    super.safeTransferFrom(_from, _to, _tokenId, data);
  }

  function safeTransferFrom(address _from, address _to, uint256 _tokenId) public override {
    require(tradeActive, "Trade is not active");
    super.safeTransferFrom(_from, _to, _tokenId);
  }

  function transferFrom(address _from, address _to, uint256 _tokenId) public override {
    require(tradeActive, "Trade is not active");
    super.transferFrom(_from, _to, _tokenId);
  }

  function setTradeState(bool tradeState) public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Cannot set trade state");
    tradeActive = tradeState;
  }

  function setPrice(uint newPrice) public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Cannot set price");
    pricePerToken = newPrice;
  }

  function mintTeamTokens() public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Cannot mint team tokens");
    require(!teamTokensMinted, "Team tokens have already been minted");
    for (uint i = 0; i < teamSupply; i++) {
      _mint(owner(), tokensMinted+1);
      tokensMinted += 1;
    }
    teamTokensMinted = true;
  }

  function setSaleState(SaleState newState) public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Cannot alter sale state");
    saleState = newState;
  }

  function setMerkleRoot(bytes32 newRoot) public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Cannot set merkle root");
    merkleRoot = newRoot;
  }

  function withdraw() public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Cannot withdraw");
    payable(owner()).call{value: address(this).balance}("");
  }

  function mint(uint amount, bytes32[] calldata proof, uint freeAmount) public payable {
    require(amount > 0, "Minting 0 tokens is quite useless");
    require(amount <= maxPerCall, "Amount is higher than maximum per call");
    require (saleState != SaleState.CLOSED, "Sale is closed");

    if (saleState == SaleState.PRIVATE) {
      bytes32 userLeaf = keccak256(abi.encodePacked(msg.sender, freeAmount));
      require(MerkleProof.verify(proof, merkleRoot, userLeaf), "Provided data is not correct");
      require(amount + balanceOf(msg.sender) <= freeAmount + whiteListMax, "Amount is higher than allowed");
      if (freeAmount > balanceOf(msg.sender)) {
        freeAmount -= balanceOf(msg.sender);
        if (freeAmount > amount) {
          freeAmount = amount;
        }
      } else {
        freeAmount = 0;
      }
      if (freeAmount + tokensMinted > _availableSupply()) {
        freeAmount = _availableSupply() - tokensMinted;
      }
      for (uint i = 0; i < freeAmount; i++) {
        _mint(_msgSender(), tokensMinted+1);
        tokensMinted += 1;
      }
      amount -= freeAmount;
    }

    if (amount + tokensMinted > _availableSupply()) {
      amount = _availableSupply() - tokensMinted;
    }
    uint amountToPay = amount * pricePerToken;
    require(amountToPay <= msg.value, "Provided not enough Ether for purchase");
    for (uint i = 0; i < amount; i++) {
      _mint(_msgSender(), tokensMinted+1);
      tokensMinted += 1;
    }
    if (msg.value > amountToPay) {
      payable(_msgSender()).call{value: msg.value - amountToPay}("");
    }
  }

  function burnMany(uint256[] calldata tokenIds) public {
    require(hasRole(DEFAULT_ADMIN_ROLE, _msgSender()), "Caller cannot burn");
    for (uint i; i < tokenIds.length; i++) {
      _burn(tokenIds[i]);
    }
    tokensBurned += tokenIds.length;
  }

  function _availableSupply() private view returns(uint) {
    if (teamTokensMinted) {
      return maxTotalSupply;
    } else {
      return publicSupply;
    }
  }

  function tokenURI(uint256 _tokenId) public pure override returns (string memory) {
    return string(abi.encodePacked(base_uri_head, Strings.toString(_tokenId), base_uri_tail));
  }

  /** * @dev See {IERC165-supportsInterface}.  */
    function supportsInterface(bytes4 interfaceId) public view virtual
  override(ERC721, ERC721Enumerable, AccessControlEnumerable) returns (bool)
  {
    return super.supportsInterface(interfaceId);
  }

  function _beforeTokenTransfer( address from, address to, uint256 tokenId) internal virtual override(ERC721, ERC721Enumerable) {
    super._beforeTokenTransfer(from, to, tokenId);
  }

}
function supportsInterface(bytes4 interfaceId) public view virtual
erride(ERC721, ERC721Enumerable, AccessControlEnumerable) returns (bool)

return super.supportsInterface(interfaceId);


/** * @dev See {IERC165-supportsInterface}.  */