contract FacelessTotz is ERC721Enumerable, Ownable, ReentrancyGuard {
    using Strings for uint256;

    uint256 public maxSupply = 888;
    uint256 public nextID = 14; // 13 already on opensea TODO double check
    uint256 public _member_price = 20000000000000000; // 0.02 eth
    uint256 public _holder_price = 15000000000000000; // 0.015 eth
    uint256 public _price = 25000000000000000; // 0.025 eth
    uint256 public maxBuy = 10;

    mapping(address => uint256) public member_quota;
    mapping(address => uint256) public holder_quota;
    mapping(address => uint256) public whitelist_quota;

    bool public whitelistSell = false;
    bool public memberSell = true;
    bool public holderSell = true;
    bool public sell = false;

    string public baseURI = "";
    string private _contractURI =
        "https://storageapi.fleek.co/yoyoismee-team-bucket/faceless-totz/totz";

    constructor() ERC721("FacelessTotz", "FTZ") {
        // HOLDER
        holder_quota[0x53eA5F4A77F5A063612D2e34B247AD14e1858DE6] = 3;
        holder_quota[0x5047a016Ca9A346a8a6A86F9ea9c231C5094F084] = 2;
        holder_quota[0x9C50b5eb0CBC4EfEFab20554200Da775720173f9] = 10;
        holder_quota[0xB910ccE85837a5f2CA90dB8fCeFA051E1727C752] = 3;
        holder_quota[0xdda24F18Eb2E35123200b9eaA668Cdf87DE58388] = 3;
        holder_quota[0x7FCd58B46f76a7B9aADc27CD0A607D5702bc182A] = 6;
        holder_quota[0xf5994ADAB26b15418e5b14A09C0A230E0F68F467] = 10;
        holder_quota[0x05b98944A834dC2Ec348a5dA101556cA2ab01A69] = 2;
        holder_quota[0xA905ae0D0Db1dA939A541862386400de63c71926] = 10;
        holder_quota[0x380CFcD0b98Cc56aA57A7eB9D90996A98360A494] = 3;
        holder_quota[0xd8e36Cd26A35B8Fe0443560Ad957BcC729Ade10c] = 6;
        holder_quota[0x9578A7Ae6952B0cdCd0E8186ca3139B77C5aA5b1] = 3;
        holder_quota[0x468D073a86466A254EcA4D408BbA15d9cd42Ad91] = 2;
        holder_quota[0x870bb39d8040a86B1FB55cfA9f640909DaC441Fe] = 3;
        holder_quota[0xaE244b7bAEF1fbd69a9303F2095aF13A930304F9] = 3;
        holder_quota[0x60Ee29796DbeaC110a92D2C4c4ca2A2CDaC35B3a] = 10;
        holder_quota[0x34b37e529e5A6be09703D061231EfF90D09D5679] = 2;
        holder_quota[0x71E0Da67B053d536DC247C89C8206a33b3BACcd6] = 10;
        holder_quota[0x5ba58Ac2b59Cc04b6960D1090665BE32B9906b63] = 3;
        holder_quota[0x7f7a100DC0Fb1A59378b3B8B34Ac44Fab511Dd5A] = 3;
        holder_quota[0xE628E7EB5807f73a555A41867aF235351d603e2f] = 1;
        holder_quota[0x1a12e4A2AfD46569d3166A6600fC57a8Bf9eD423] = 10;
        holder_quota[0x945FC81C9edc66609F54cD19Cdb4500713F8a825] = 10;
        holder_quota[0x276ae69309b0CFde2CBdB9151b1ED8ABEF1501BB] = 10;
        holder_quota[0xee10426CbF833Ce01D0bB7c0b05C5E9E4bE01bB1] = 2;
        holder_quota[0xFdc3E8eDd74A90fe971EF7d56a0C66c870b10F5D] = 6;
        holder_quota[0x2a8f1940ff9EdB8a6be937F171Cc12e1E2eAb264] = 1;
        holder_quota[0x2a753194B84B2Fb7E01ee8b3Cd9b0fd4e7c49B5c] = 2;
        holder_quota[0x431f89c9890920661B2658cc4d9d9724A947A0a8] = 3;
        holder_quota[0x468D073a86466A254EcA4D408BbA15d9cd42Ad91] = 2;
        holder_quota[0x2Fb14f818A03566b41EF9C670488d5c708547d2e] = 10;
        holder_quota[0x59C5EF981ddF319e7Dae5328B4f54E8D1EA1d430] = 10;
        holder_quota[0x30790ccD8d366552484a27BD0f42E9c980D1C3E9] = 10;
        holder_quota[0x3d272E560ADD9e0dF198daE4069510C660D35434] = 10;
        holder_quota[0x86057854D8cF621a0384606FCf454e579e48E808] = 10;

        // Member
        member_quota[0xBe8017fF522bdF8AC822789D6DAA4f517791F85E] = 3;
        member_quota[0x00f903a27295c1BB22Cf685b9452e3183d07e5FC] = 6;
        member_quota[0x222550475087294a7f5ba4F87A06e7cd1103363d] = 3;
        member_quota[0xb11F427AB9B65b475EE786cE36Ed3b36634b8670] = 3;
        member_quota[0xD80775766186eF44c73422fDF97D92701C27f70E] = 6;
        member_quota[0x7A70536c4D695B1Ec9DF972e91461E834bfB00e8] = 6;
        member_quota[0xc3A6E2b72EE8478A85593Bfe86fCd612855BeAe2] = 2;
        member_quota[0xe001891949372e1AA33C50c7EA19568bE32ecDE7] = 6;
        member_quota[0x92a923B1407701117A9A94e3e137f1Bd4cB3c663] = 3;
        member_quota[0xB6fe1d4bE26159715803Ec9929244D8B2c1cFE9C] = 6;
    }

    function setWhitelist(address[] memory wallets, uint256 quota)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < wallets.length; i++) {
            whitelist_quota[wallets[i]] = quota;
        }
    }

    function setMember(address[] memory wallets, uint256 quota)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < wallets.length; i++) {
            member_quota[wallets[i]] = quota;
        }
    }

    function setHolder(address[] memory wallets, uint256 quota)
        public
        onlyOwner
    {
        for (uint256 i = 0; i < wallets.length; i++) {
            holder_quota[wallets[i]] = quota;
        }
    }

    function setPrice(uint256 newPrice) public onlyOwner {
        _price = newPrice;
    }

    function setSell(bool _sell) public onlyOwner {
        sell = _sell;
    }

    function buy(uint256 amount) public payable nonReentrant {
        require(sell, "not selling");
        require(amount <= maxBuy, "at most 10 totz at a time");
        require(nextID + amount - 1 <= maxSupply, "out of totz");
        require(msg.value >= _price * amount, "invalid eth");

        for (uint256 i = 1; i <= amount; i++) {
            _safeMint(msg.sender, nextID);
            nextID += 1;
        }
    }

    function setHolderSell(bool _holderSell) public onlyOwner {
        holderSell = _holderSell;
    }

    function holderBuy(uint256 amount) public payable nonReentrant {
        require(holderSell, "not selling");
        require(amount <= maxBuy, "at most 10 totz at a time");
        require(nextID + amount - 1 <= maxSupply, "out of totz");
        require(msg.value >= _holder_price * amount, "invalid eth");
        require(holder_quota[msg.sender] >= amount, "no quota");
        holder_quota[msg.sender] -= amount;

        for (uint256 i = 1; i <= amount; i++) {
            _safeMint(msg.sender, nextID);
            nextID += 1;
        }
    }

    function setMemberSell(bool _memberSell) public onlyOwner {
        memberSell = _memberSell;
    }

    function memberBuy(uint256 amount) public payable nonReentrant {
        require(memberSell, "not selling");
        require(amount <= maxBuy, "at most 10 totz at a time");
        require(nextID + amount - 1 <= maxSupply, "out of totz");
        require(msg.value >= _member_price * amount, "invalid eth");
        require(member_quota[msg.sender] >= amount, "no quota");
        member_quota[msg.sender] -= amount;

        for (uint256 i = 1; i <= amount; i++) {
            _safeMint(msg.sender, nextID);
            nextID += 1;
        }
    }

    function setWhitelistSell(bool _whitelistSell) public onlyOwner {
        whitelistSell = _whitelistSell;
    }

    function whitelistBuy(uint256 amount) public payable nonReentrant {
        require(whitelistSell, "not selling");
        require(amount <= maxBuy, "at most 10 totz at a time");
        require(nextID + amount - 1 <= maxSupply, "out of totz");
        require(msg.value >= _price * amount, "invalid eth");
        require(whitelist_quota[msg.sender] >= amount, "no quota");
        whitelist_quota[msg.sender] -= amount;
        for (uint256 i = 1; i <= amount; i++) {
            _safeMint(msg.sender, nextID);
            nextID += 1;
        }
    }

    function withdraw() public nonReentrant {
        // 10% to promoter
        payable(0x1f562c3259b5266a1a934a4d20d22b7135D3600d).transfer(
            address(this).balance / 10
        );
        // the rest to owner
        payable(0x68FF5cB5F3D1bB0E162978449Cd410E04BF752d7).transfer(
            address(this).balance
        );
    }

    /**
     * Override isApprovedForAll to whitelist user's OpenSea proxy accounts to enable gas-less listings.
     */
    function isApprovedForAll(address owner, address operator)
        public
        view
        override
        returns (bool)
    {
        // opensea mainnet
        ProxyRegistry proxyRegistry = ProxyRegistry(
            0xa5409ec958C83C3f309868babACA7c86DCB077c1
        );

        // Whitelist OpenSea proxy contract for easy trading.
        if (address(proxyRegistry.proxies(owner)) == operator) {
            return true;
        }
        return super.isApprovedForAll(owner, operator);
    }

    function setContractURI(string memory newContractURI) external onlyOwner {
        _contractURI = newContractURI;
    }

    // for OPENSEA
    function contractURI() external view returns (string memory) {
        return _contractURI;
    }

    function setBaseURI(string memory newBaseURI) external onlyOwner {
        baseURI = newBaseURI;
    }

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

    function tokenURI(uint256 tokenId)
        public
        view
        override
        returns (string memory)
    {
        require(_exists(tokenId), "nonexistent token");
        return string(abi.encodePacked(baseURI, tokenId.toString()));
    }
}
function contractURI() external view returns (string memory) {
    return _contractURI;
}

// for OPENSEA