contract ChosenOnes is ERC721, Ownable, ReentrancyGuard {
    using Strings for uint256;
    using Counters for Counters.Counter;

    Counters.Counter private _supply;

    bytes32 private _merkleRoot;

    uint256 public constant PRICE = 0.1 ether;
    uint256 public constant MAX_SUPPLY = 7777;
    uint256 public constant MAX_MINTS_PER_TRANSACTION = 5;

    string public baseURI;

    bool public preSaleActive;
    bool public publicSaleActive;
    bool public revealed;

    address public proxyRegistryAddress;

    mapping(address => uint) private addressToNumMinted;

    constructor(
        address _proxyRegistryAddress,
        string memory _initialBaseURI
    )
        ERC721("Chosen Ones by Seek One", "CHOSENONES")
    {
        proxyRegistryAddress = _proxyRegistryAddress;
        baseURI = _initialBaseURI;
    }

    // Modifiers
    modifier _checkMint(uint256 _amount) {
        require(_amount > 0, "Must mint more than 0 tokens.");
        // We add + 1 to these constants because GTE and LTE checks add more gas
        // Source: https://medium.com/coinmonks/gas-optimization-in-solidity-part-i-variables-9d5775e43dde
        require(_amount < MAX_MINTS_PER_TRANSACTION + 1, "Mints per tx exceeded.");
        require(_supply.current() + _amount < MAX_SUPPLY + 1, "Not enough tokens.");

        require(msg.value == PRICE * _amount, "Insufficient funds supplied.");
        _;
    }

    // Internal/private functions
    function _baseURI() internal view override returns (string memory) {
        return baseURI;
    }

    function _internalMint(address _recipient, uint256 _amount) internal {
        for (uint256 i = 0; i < _amount; i++) {
            _supply.increment();
            _safeMint(_recipient, _supply.current());
        }
    }

    function _leaf(string memory allowance, string memory payload) internal pure returns (bytes32) {
        return keccak256(abi.encodePacked(payload, allowance));
    }

    function _verify(bytes32 leaf, bytes32[] memory proof) internal view returns (bool) {
        return MerkleProof.verify(proof, _merkleRoot, leaf);
    }

    // See the comment at the bottom of this file for more info on why this is needed.
    function isApprovedForAll(address owner, address operator)
        override
        public
        view
        returns (bool)
    {
        OpenSeaProxyRegistry proxyRegistry = OpenSeaProxyRegistry(proxyRegistryAddress);
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }

        return super.isApprovedForAll(owner, operator);
    }

    function mint(uint256 _amount) public payable _checkMint(_amount) nonReentrant {
        require(publicSaleActive, "Minting is disabled.");

        _internalMint(_msgSender(), _amount);
    }

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

    function whitelistMint(uint256 _amount, uint256 _allowance, bytes32[] calldata _proof) public payable _checkMint(_amount) nonReentrant {
        require(preSaleActive, "Pre-sale minting is disabled.");
        string memory payload = string(abi.encodePacked(_msgSender()));
        require(_verify(_leaf(Strings.toString(_allowance), payload), _proof), "Invalid Merkle Tree proof supplied.");
        require(addressToNumMinted[_msgSender()] + _amount <= _allowance, "Exceeds token allowance.");

        addressToNumMinted[_msgSender()] += _amount;
        _internalMint(_msgSender(), _amount);
    }

    // Owner Getters
    function getAddressToNumMinted(address _address) public view onlyOwner returns (uint) {
        return addressToNumMinted[_address];
    }

    function getContractBalance() public view onlyOwner returns (uint256) {
        return address(this).balance;
    }

    function getCurrentTokenId() public view onlyOwner returns (uint256) {
        return _supply.current();
    }

    function getMerkleRoot() public view onlyOwner returns (bytes32) {
        return _merkleRoot;
    }

    // Owner Setters
    function setBaseURI(string memory _newBaseURI) external onlyOwner {
        baseURI = _newBaseURI;
    }

    function setMerkleRoot(bytes32 _newMerkleRoot) external onlyOwner {
        _merkleRoot = _newMerkleRoot;
    }

    function setPreSaleActive(bool _state) external onlyOwner {
        preSaleActive = _state;
    }

    function setPublicSaleActive(bool _state) external onlyOwner {
        publicSaleActive = _state;
    }

    function setProxyRegistryAddress(address _proxyRegistryAddress) external onlyOwner {
        proxyRegistryAddress = _proxyRegistryAddress;
    }

    // Other owner functions
    function reserveTokens(uint256 _amount, address _address) public onlyOwner {
        _internalMint(_address, _amount);
    }

    function withdraw() public onlyOwner {
        (bool success, ) = payable(owner()).call{value: address(this).balance}("");
        require(success);
    }
}
function getAddressToNumMinted(address _address) public view onlyOwner returns (uint) {
    return addressToNumMinted[_address];
}

// Owner Getters