contract TheLonelyGnomes is Ownable, ERC721A, ReentrancyGuard {

  uint256 public constant freeSaleLimit = 1000;
  uint256 public constant maxPerAddressDuringMint = 30;

  struct SaleConfig {
    uint64 publicPrice;
    bool paused;
  }

  SaleConfig public saleConfig;

  constructor() ERC721A("The Lonely Gnomes", "GNOMES", 10, 6000) {
    saleConfig.publicPrice = 0.01 ether;
    saleConfig.paused = true;
  }

  modifier callerIsUser() {
    require(tx.origin == msg.sender, "The caller is another contract");
    _;
  }  

  function publicSaleMint(uint256 quantity)
    external
    payable
    callerIsUser
  {
    uint256 mintableQuantity = quantity;
    SaleConfig memory config = saleConfig;
    bool state = config.paused;
    uint256 publicPrice = uint256(config.publicPrice);

    require(
      isPublicSaleOn(publicPrice, state),
      "public sale has not begun yet"
    );
    require(totalSupply() + quantity <= collectionSize, "reached max supply");
    require(
      numberMinted(msg.sender) + quantity <= maxPerAddressDuringMint,
      "can not mint this many"
    );
    if( (totalSupply() < freeSaleLimit) && (totalSupply() + quantity > freeSaleLimit)){
      mintableQuantity = freeSaleLimit - totalSupply();
    }
    _safeMint(msg.sender, mintableQuantity);
    if ( totalSupply() > freeSaleLimit ){
      refundIfOver(publicPrice * mintableQuantity);
    }
  }

  function refundIfOver(uint256 price) private {
    require(msg.value >= price, "Need to send more ETH.");
    if (msg.value > price) {
      payable(msg.sender).transfer(msg.value - price);
    }
  }

  function isPublicSaleOn(
    uint256 publicPriceWei,
    bool state
  ) internal view returns (bool) {
    return
      publicPriceWei != 0 &&
      !state;
  }

  // For marketing etc.
  function devMint(uint256 quantity) external onlyOwner {
    require(totalSupply() + quantity <= collectionSize, "reached max supply");
    require(
      quantity % maxBatchSize == 0,
      "can only mint a multiple of the maxBatchSize"
    );
    uint256 numChunks = quantity / maxBatchSize;
    for (uint256 i = 0; i < numChunks; i++) {
      _safeMint(msg.sender, maxBatchSize);
    }
  }

  function SetupSaleInfo(
    uint64 publicPriceWei,
    bool state
  ) external onlyOwner {
    saleConfig = SaleConfig(
      publicPriceWei,
      state
    );
  }

  // // metadata URI
  string private _baseTokenURI;

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

  function setBaseURI(string calldata baseURI) external onlyOwner {
    _baseTokenURI = baseURI;
  }

  function withdrawMoney() external onlyOwner nonReentrant {
    (bool success, ) = msg.sender.call{value: address(this).balance}("");
    require(success, "Transfer failed.");
  }

  function setOwnersExplicit(uint256 quantity) external onlyOwner nonReentrant {
    _setOwnersExplicit(quantity);
  }

  function numberMinted(address owner) public view returns (uint256) {
    return _numberMinted(owner);
  }

  function getOwnershipData(uint256 tokenId)
    external
    view
    returns (TokenOwnership memory)
  {
    return ownershipOf(tokenId);
  }
}
function devMint(uint256 quantity) external onlyOwner {
  require(totalSupply() + quantity <= collectionSize, "reached max supply");
  require(
    quantity % maxBatchSize == 0,
    "can only mint a multiple of the maxBatchSize"
  );
  uint256 numChunks = quantity / maxBatchSize;
  for (uint256 i = 0; i < numChunks; i++) {
    _safeMint(msg.sender, maxBatchSize);
  }
}

// For marketing etc.