contract MuleSquadNFT is ERC721Enumerable, Ownable { 
    
    uint public maxTokens = 690;
    uint public mintPrice = 0.069 ether;

    string internal baseTokenURI;
    string internal baseTokenURI_EXT;

    address internal shareAddress;

    bool public isPublicMinting = false;

    mapping(address => uint) public isWhitelisted;
    mapping(address => uint) public addressHasMinted;

    mapping(address => uint) public shareSign;

    event Mint(address indexed to, uint indexed tokenId);
    event AddToWhitelist(address whitelistedAddress);

    constructor() ERC721("MuleSquad", "MSQUAD") {}

    // modifiers
    modifier onlySender() {
        require(msg.sender == tx.origin, "No Smart Contracts Allowed!");
        _;
    }
    modifier onePerAddress() {
        require(addressHasMinted[msg.sender] == 0, "Only 1 mint per address!");
        _;
    }
    modifier isInWhitelist() {
        require(isWhitelisted[msg.sender] == 1, "You are not whitelisted!");
        _;
    }
    modifier publicMinting() {
        require(isPublicMinting, "Public claiming is not yet available!");
        _;
    }

    // funds withdrawal for owner
    function withdrawEther() external onlyOwner {
        require(shareAddress != address(0x0), "Share is not set");
        uint _balance = address(this).balance;
        uint _shares = _balance / 2;

        payable(shareAddress).transfer(_shares);
        payable(msg.sender).transfer(_shares);
    }

    function emergencyWithdrawEther() external onlyOwner {
        require(shareSign[shareAddress] == 1, "Share has not signed to accept this transaction.");
        payable(msg.sender).transfer(address(this).balance);
    }

    // contract administration
    function setPublicMinting(bool bool_) external onlyOwner {
        isPublicMinting = bool_;
    }
    function setBaseTokenURI(string memory uri_) external onlyOwner {
        baseTokenURI = uri_;
    }
    function setBaseTokenURIEXT(string memory ext_) external onlyOwner {
        baseTokenURI_EXT = ext_;
    }
    function setShareAddress(address address_) external onlyOwner {
        require(shareAddress == address(0x0), "Address already set!");
        shareAddress = address_;
    }
    function signAsShare() external {
        require(msg.sender == shareAddress, "You are not Share");
        shareSign[msg.sender] = 1;
    }

    // whitelisting function
    function addToWhitelist(address[] memory addresses_) external onlyOwner {
        for (uint i = 0; i < addresses_.length; i++) {
            isWhitelisted[addresses_[i]] = 1;
            emit AddToWhitelist(addresses_[i]);
        }
    }

    // owner mint
    function ownerMint(address to_, uint amount_) external onlyOwner {
        require((totalSupply() + amount_) <= maxTokens, "Not enough tokens!");
        for (uint i = 0; i < amount_; i++) {
            uint _mintId = totalSupply() + 1; // iterate from 1
            _mint(to_, _mintId);
            emit Mint(to_, _mintId);
        }
    }

    function whitelistClaim() external onlySender onePerAddress isInWhitelist {
        require((totalSupply() + 1) <= maxTokens, "Not enough tokens!");

        addressHasMinted[msg.sender] = 1;

        uint _mintId = totalSupply() + 1; // iterate from 1
        _mint(msg.sender, _mintId);
        emit Mint(msg.sender, _mintId);
    }

    function publicMint() payable external onlySender publicMinting {
        require(msg.value == mintPrice, "Invalid Value Sent!");
        require((totalSupply() + 1) <= maxTokens, "Not enough tokens!");

        uint _mintId = totalSupply() + 1; // iterate from 1
        _mint(msg.sender, _mintId);
        emit Mint(msg.sender, _mintId);
    }

    // public view functions
    function tokenURI(uint tokenId_) public view override returns (string memory) {
        return string(abi.encodePacked(baseTokenURI, Strings.toString(tokenId_), baseTokenURI_EXT));
    }
    function walletOfOwner(address address_) public view returns (uint256[] memory) {
        uint256 _balance = balanceOf(address_);
        uint256[] memory _tokenIds = new uint256[](_balance);
        for (uint256 i; i < _balance; i++) {
            _tokenIds[i] = tokenOfOwnerByIndex(address_, i);
        }
        return _tokenIds;
    }
}
function setPublicMinting(bool bool_) external onlyOwner {
    isPublicMinting = bool_;
}

// contract administration