contract Citizens is ERC721, Ownable {
    using Strings for uint256;

    // Counter
    using Counters for Counters.Counter;
    Counters.Counter private _tokenSupply;

    // Constant variables
    // ------------------------------------------------------------------------

    uint256 public constant TOTAL_SUPPLY = 3000; // Total amount of Citizens
    uint256 public constant RESERVED_SUPPLY = 100; // Amount of Citizens reserved for the contract
    uint256 public constant MAX_SUPPLY = TOTAL_SUPPLY - RESERVED_SUPPLY; // Maximum amount of Citizens
    uint256 public constant PRESALE_SUPPLY = 1500; // Presale supply
    uint256 public constant MAX_PER_TX = 10; // Max amount of Citizens per tx (public sale)
    uint256 public constant PRICE = 0.04 ether;
    uint256 public constant PRESALE_MAX_MINT = 3;
    uint256 public constant MAX_MINT_PUBLIC = 20;

    // Team addresses
    // ------------------------------------------------------------------------
    address private constant _a1 = 0x926b8edBef960305cBcAA839b1019c0a54358f2C; // fredo
    address private constant _a2 = 0x6f0ce6920568e2D55eB1074314df3ea61584980b; // sammi
    address private constant _a3 = 0xA582ad581f44Bf431f5f020242ab91a25170E200; // shellz

    // State variables
    // ------------------------------------------------------------------------
    bool public isPresaleActive = false;
    bool public isPublicSaleActive = false;
    bool public revealed = false;

    // Presale arrays
    // ------------------------------------------------------------------------
    mapping(address => bool) private _presaleEligible;
    mapping(address => uint256) private _presaleClaimed;

    // URI variables
    // ------------------------------------------------------------------------
    string public notRevealedURI;
    string baseURI;

    // Events
    // ------------------------------------------------------------------------
    event BaseTokenURIChanged(string baseTokenURI);

    // Constructor
    // ------------------------------------------------------------------------
    constructor(string memory _initNotRevealedUri)
        ERC721("ONE WRLD", "CITIZEN ID")
    {
        setNotRevealedURI(_initNotRevealedUri);
    }

    // Modifiers
    // ------------------------------------------------------------------------
    modifier onlyPresale() {
        require(isPresaleActive, "PRESALE_NOT_ACTIVE");
        _;
    }

    modifier onlyPublicSale() {
        require(isPublicSaleActive, "PUBLIC_SALE_NOT_ACTIVE");
        _;
    }

    // Anti-bot functions
    // ------------------------------------------------------------------------

    function isContractCall(address addr) internal view returns (bool) {
        uint256 size;
        assembly {
            size := extcodesize(addr)
        }
        return size > 0;
    }

    // Presale functions
    // ------------------------------------------------------------------------
    function addToPresaleList(address[] calldata addresses) external onlyOwner {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "NULL_ADDRESS");
            require(!_presaleEligible[addresses[i]], "DUPLICATE_ENTRY");

            _presaleEligible[addresses[i]] = true;
            _presaleClaimed[addresses[i]] = 0;
        }
    }

    function removeFromPresaleList(address[] calldata addresses)
        external
        onlyOwner
    {
        for (uint256 i = 0; i < addresses.length; i++) {
            require(addresses[i] != address(0), "NULL_ADDRESS");
            require(_presaleEligible[addresses[i]], "NOT_IN_PRESALE");

            _presaleEligible[addresses[i]] = false;
        }
    }

    function isEligibleForPresale(address addr) external view returns (bool) {
        require(addr != address(0), "NULL_ADDRESS");

        return _presaleEligible[addr];
    }

    function hasClaimedPresale(address addr) external view returns (bool) {
        require(addr != address(0), "NULL_ADDRESS");

        return _presaleClaimed[addr] >= 1;
    }

    function togglePresaleStatus() external onlyOwner {
        isPresaleActive = !isPresaleActive;
    }

    function togglePublicSaleStatus() external onlyOwner {
        isPublicSaleActive = !isPublicSaleActive;
    }

    // Mint functions
    // ------------------------------------------------------------------------
    function claimReservedCitizen(uint256 quantity, address addr)
        external
        onlyOwner
    {
        require(_tokenSupply.current() < TOTAL_SUPPLY, "SOLD_OUT");
        require(
            _tokenSupply.current() + quantity <= TOTAL_SUPPLY,
            "EXCEEDS_TOTAL_SUPPLY"
        );

        for (uint256 i = 0; i < quantity; i++) {
            _tokenSupply.increment();
            _safeMint(addr, _tokenSupply.current());
        }
    }

    function claimPresaleCitizen(uint256 quantity) external payable onlyPresale {
        require(_presaleEligible[msg.sender], "NOT_ELIGIBLE_FOR_PRESALE");
        require(_presaleClaimed[msg.sender] + quantity <= PRESALE_MAX_MINT, "PRESALE_MAX_CLAIMED");
        require(_tokenSupply.current() < PRESALE_SUPPLY, "PRESALE_SOLD_OUT");
        require(
            _tokenSupply.current() + quantity <= PRESALE_SUPPLY,
            "EXCEEDS_PRESALE_SUPPLY"
        );

        if (msg.sender != owner()) {
            require(PRICE * quantity == msg.value, "INVALID_ETH_AMOUNT");
        }

        for (uint256 i = 0; i < quantity; i++) {
            _presaleClaimed[msg.sender] += 1;
            _tokenSupply.increment();
            _safeMint(msg.sender, _tokenSupply.current());
        }
    }

    function getBalanceOfAddress(address addr) public view returns (uint256) {
      return balanceOf(addr);
    }

    function mint(uint256 quantity) external payable onlyPublicSale {
        require(tx.origin == msg.sender, "GO_AWAY_BOT_ORIGIN");
        require(!isContractCall(msg.sender), "GO_AWAY_BOT_CONTRACT");
        require(quantity <= MAX_MINT_PUBLIC, "PUBLIC_SALE_MAX_CLAIMED");

        require(_tokenSupply.current() < MAX_SUPPLY, "SOLD_OUT");
        require(quantity > 0, "QUANTITY_CANNOT_BE_ZERO");
        require(quantity <= MAX_PER_TX, "EXCEEDS_MAX_MINT");
        require(
            _tokenSupply.current() + quantity <= MAX_SUPPLY,
            "EXCEEDS_MAX_SUPPLY"
        );

        if (msg.sender != owner()) {
            require(PRICE * quantity == msg.value, "INVALID_ETH_AMOUNT");
        }

        for (uint256 i = 0; i < quantity; i++) {
            _tokenSupply.increment();
            _safeMint(msg.sender, _tokenSupply.current());
        }
    }
    
    function tokensMinted() public view returns (uint256) {
      return _tokenSupply.current();
    }

    // Base URI Functions
    // ------------------------------------------------------------------------

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

    function tokenURI(uint256 tokenId)
        public
        view
        virtual
        override
        returns (string memory)
    {
        require(
            _exists(tokenId),
            "ERC721Metadata: URI query for nonexistent token"
        );

        if (revealed == false) {
            return notRevealedURI;
        }

        string memory currentBaseURI = _baseURI();
        return
            bytes(currentBaseURI).length > 0
                ? string(
                    abi.encodePacked(
                        currentBaseURI,
                        tokenId.toString(),
                        ".json"
                    )
                )
                : "";
    }

    //only owner
    function reveal(string memory revealedURI) public onlyOwner {
        baseURI = revealedURI;
        revealed = true;
    }

    function setNotRevealedURI(string memory _notRevealedURI) public onlyOwner {
        notRevealedURI = _notRevealedURI;
    }

    function setBaseURI(string memory _newBaseURI) public onlyOwner {
        baseURI = _newBaseURI;
    }

    // Withdrawal functions
    // ------------------------------------------------------------------------
    function withdrawAll() external onlyOwner {
        uint256 _a1amount = (address(this).balance * 20) / 100;
        uint256 _a2amount = (address(this).balance * 40) / 100;
        uint256 _a3amount = (address(this).balance * 40) / 100;

        require(payable(_a1).send(_a1amount), "FAILED_TO_SEND_TO_A1");
        require(payable(_a2).send(_a2amount), "FAILED_TO_SEND_TO_A2");
        require(payable(_a3).send(_a3amount), "FAILED_TO_SEND_TO_A3");
    }
}
function addToPresaleList(address[] calldata addresses) external onlyOwner {
    for (uint256 i = 0; i < addresses.length; i++) {
        require(addresses[i] != address(0), "NULL_ADDRESS");
        require(!_presaleEligible[addresses[i]], "DUPLICATE_ENTRY");

        _presaleEligible[addresses[i]] = true;
        _presaleClaimed[addresses[i]] = 0;
    }
}

// Presale functions
// ------------------------------------------------------------------------