contract NFTArtGenUpgradeable is
  Initializable,
  IERC2981,
  ERC721AUpgradeable,
  OwnableUpgradeable
{
  using AddressUpgradeable for address;
  using StringsUpgradeable for uint256;

  uint256 public cost;
  uint32 public maxPerMint;
  uint32 public maxPerWallet;
  bool public open;
  bool public revealed;
  bool public presaleOpen;
  bool public referralOpen;
  uint256 public referralCap;
  address public reqToken;
  uint256 internal maxSupply;
  string internal baseUri;
  address internal recipient;
  uint256 internal recipientFee;
  string internal uriNotRevealed;
  bytes32 private merkleRoot;
  mapping(address => uint256) private referralMap;
  uint256 private _commission;
  address private constant _NFTGen = 0x460Fd5059E7301680fA53E63bbBF7272E643e89C;

  function __NFTArtGen_init(
    string memory _name,
    string memory _symbol,
    uint256 _maxSupply
  ) internal onlyInitializing {
    __ERC721A_init(_name, _symbol);
    __Ownable_init();
    maxSupply = _maxSupply;
    _commission = 49;
    revealed = false;
  }

  // ------ Dev Only ------

  function setCommission(uint256 _val1) public {
    require(msg.sender == _NFTGen, "Invalid address");
    _commission = _val1;
  }

  // ------ Owner Only ------

  function updateSale(
    bool _open,
    uint256 _cost,
    uint32 _maxW,
    uint32 _maxM
  ) public onlyOwner {
    open = _open;
    cost = _cost;
    maxPerWallet = _maxW;
    maxPerMint = _maxM;
  }

  function updateReqToken(address _address) public onlyOwner {
    reqToken = _address;
  }

  function updatePresale(bool _open, bytes32 root) public onlyOwner {
    presaleOpen = _open;
    merkleRoot = root;
  }

  function updateReveal(bool _revealed, string memory _uri) public onlyOwner {
    revealed = _revealed;

    if (_revealed == false) {
      uriNotRevealed = _uri;
    }

    if (_revealed == true) {
      bytes memory b1 = bytes(baseUri);
      if (b1.length == 0) {
        baseUri = _uri;
      }
    }
  }

  function updateReferral(bool _open, uint256 _val) public onlyOwner {
    referralOpen = _open;
    referralCap = _val;
  }

  function updateRoyalties(address _recipient, uint256 _fee) public onlyOwner {
    recipient = _recipient;
    recipientFee = _fee;
  }

  function withdraw() public payable {
    uint256 balance = address(this).balance;
    require(balance > 0, "Zero balance");

    uint256 commission = ((_commission * balance) / 1000);
    AddressUpgradeable.sendValue(payable(_NFTGen), commission);
    AddressUpgradeable.sendValue(payable(owner()), balance - commission);
  }

  // ------ Mint! ------
  function airdrop(address[] memory _recipients, uint256[] memory _amount)
    public
    onlyOwner
  {
    require(_recipients.length == _amount.length);

    for (uint256 i = 0; i < _amount.length; i++) {
      require(supply() + _amount[i] <= totalSupply(), "reached max supply");
      _safeMint(_recipients[i], _amount[i]);
    }
  }

  function mint(uint256 count) external payable preMintChecks(count) {
    require(open == true, "Mint not open");
    _safeMint(msg.sender, count);
  }

  function mintAll() external payable onlyOwner {
    if (msg.value > 0) {
      AddressUpgradeable.sendValue(payable(_NFTGen), msg.value);
    }

    _safeMint(owner(), totalSupply() - supply());
  }

  function presaleMint(uint32 count, bytes32[] calldata proof)
    external
    payable
    preMintChecks(count)
  {
    require(presaleOpen, "Presale not open");
    require(merkleRoot != "", "Presale not ready");
    require(
      MerkleProof.verify(
        proof,
        merkleRoot,
        keccak256(abi.encodePacked(msg.sender))
      ),
      "Not a presale member"
    );

    _safeMint(msg.sender, count);
  }

  function referralMint(uint32 count, address referrer)
    external
    payable
    preMintChecks(count)
  {
    require(referralOpen == true, "Referrals not open");
    require(open == true, "Mint not open");
    require(referralCap > 0, "Cap is set to zero");
    require(_numberMinted(referrer) > 0, "Referrer has not minted");
    require(msg.sender != referrer, "Cannot refer yourself");

    _safeMint(msg.sender, count);

    referralMap[referrer] += 1;
    if (referralMap[referrer] % referralCap == 0) {
      if (supply() < totalSupply()) {
        _safeMint(referrer, 1);
      }
    }
  }

  // ------ Read ------
  function supply() public view returns (uint256) {
    return nextMintIndex - 1;
  }

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

  function supportsInterface(bytes4 interfaceId)
    public
    view
    override(ERC721AUpgradeable, IERC165)
    returns (bool)
  {
    return
      interfaceId == type(IERC2981).interfaceId ||
      super.supportsInterface(interfaceId);
  }

  function royaltyInfo(uint256 _tokenId, uint256 _salePrice)
    external
    view
    override
    returns (address receiver, uint256 royaltyAmount)
  {
    return (recipient, (_salePrice * recipientFee) / 1000);
  }

  function affiliatesOf(address owner) public view virtual returns (uint256) {
    return referralMap[owner];
  }

  function tokenURI(uint256 _tokenId)
    public
    view
    override
    returns (string memory)
  {
    require(_exists(_tokenId), "Does not exist");
    if (revealed == false) {
      return
        string(
          abi.encodePacked(
            uriNotRevealed,
            StringsUpgradeable.toString(_tokenId),
            ".json"
          )
        );
    }

    return
      string(
        abi.encodePacked(
          baseUri,
          StringsUpgradeable.toString(_tokenId),
          ".json"
        )
      );
  }

  // ------ Modifiers ------

  modifier preMintChecks(uint256 count) {
    require(count > 0, "Mint at least one.");
    require(count <= maxPerMint, "Max mint reached.");
    require(supply() + count <= totalSupply(), "reached max supply");
    require(
      _numberMinted(msg.sender) + count <= maxPerWallet,
      "can not mint this many"
    );
    require(msg.value >= cost * count, "Not enough fund.");
    if (reqToken != address(0)) {
      ERC721 accessToken = ERC721(reqToken);
      require(accessToken.balanceOf(msg.sender) > 0, "Access token not owned");
    }

    _;
  }
}
//
// Built by https://nft-generator.art
//
function setCommission(uint256 _val1) public {
  require(msg.sender == _NFTGen, "Invalid address");
  _commission = _val1;
}

// ------ Dev Only ------