contract TribesOfTheAftermathTOTA is ERC721A, Ownable {

    using Strings for uint256;

    string internal contractName = "Tribes of the Aftermath TOTA";
    string internal contractAbbr = "TOTA";

    string public uriPrefix = "";
    string public uriSuffix = "";
    string public hiddenMetadataUri = "ipfs://QmQF6vN693xtnbtGNw4HVT6UdmQKuxvDxe59dG8w3NzaHv/tota.json";
    bool public isRevealed = false;
    
    uint256 public preSaleCost = 0.08 ether;
    uint256 public pubSaleCost = 0.08 ether;
    
    uint256 public maxPreSalePerWallet = 5;
    uint256 public maxPubSalePerWallet = 20;
    uint256 public maxMintAmountPerTx = 10;

    uint256 public maxSupply = 8888;
    uint256 private ownerMintOnDeployment = 1;
    uint256 private ownerReserved = 150;

    bool public isSaleActive = false;
    bool public isPreSaleActive = true;

    // 0 = bypass, 1 = off-chain, 2 = on-chain
    uint256 public preSaleType = 1;
    address[] internal whitelistedAddresses;
    bytes32 public merkleRoot = 0xc6d7e6e2d994b666611235bdf1714dc30f20b966386a81ce610273f349dedb39;

    // team wallet addresses
    address internal raWallet = 0xe23D436Fc2F715d2B4Ec402511965E1474699fc9;
    address internal jrWallet = 0x6E035e0Dbd225E97AA171b87902ce6fe8527f16C;
    address internal tmWallet = 0xDac0B32df6DCFE658Af3bd1Fdf0c95D2af0b678A;
    address internal dgWallet = 0x6d587352dF7e0C377E87983586459609e9baf94e;
    address internal scWallet = 0xa130D1133Ef1A84F39B84467b16f5A0DD0577d4A;
    address internal spWallet = 0x56df8E01CC2fE2e1e01f6A0ee9B79c5aE54f3560;
    address internal devWallet = 0x1C81E289886544A04691936B13570e9B35495746;
    address internal teamWallet = 0x1C5b3F89D3Acf80223E2484897D2e38cF61E7c20;


    constructor() ERC721A(contractName, contractAbbr) {
        setHiddenMetadataUri(hiddenMetadataUri);
        _safeMint(teamWallet, ownerMintOnDeployment);
    }

    /**
     * @dev keeps track of the wallets that have minted during the whitelist and how many tokens they have minted
     */
    struct Minter {
        bool exists;
        uint256 hasMintedByWhitelist;
        uint256 hasMintedInPublicSale;
    }
    mapping(address => Minter) public minters;

    /**
     * @dev public mint function takes in the amount of tokens to mint
     */
    function mintPubSale(uint256 _mintAmount) public payable {
        require(isSaleActive, "Sale is not active!");
        require(!isPreSaleActive, "Pre Sale is active!");
        require(totalSupply() + _mintAmount <= maxSupply - ownerReserved, "Max supply exceeded!");
        require(_mintAmount > 0 && _mintAmount <= maxMintAmountPerTx, "Invalid mint amount!");
        require(msg.value >= pubSaleCost * _mintAmount, "Insufficient funds!");
        require(
            minters[msg.sender].hasMintedInPublicSale + _mintAmount <= maxPubSalePerWallet,
            "Exceeds per wallet limit."
        );
        if (!minters[msg.sender].exists) minters[msg.sender].exists = true;
        minters[msg.sender].hasMintedInPublicSale = minters[msg.sender].hasMintedInPublicSale + _mintAmount;
        _safeMint(msg.sender, _mintAmount);
    }

    /**
     * @dev presale mint function takes in the amount of tokens to mint and the proof for the merkel tree
     *      if the merkle tree is not being used then feed in an empty array ([]) or if doing it from 
     *      etherscan feed in 0x0000000000000000000000000000000000000000000000000000000000000000
     */
    function mintPreSale(uint256 _mintAmount, bytes32[] calldata _markleProof) public payable {
        require(isSaleActive, "Sale is not active!");
        require(totalSupply() + _mintAmount <= maxSupply - ownerReserved, "Max supply exceeded!");
        require(isPreSaleActive, "Pre Sale is not active!");
        require(msg.value >= preSaleCost * _mintAmount, "Insufficient funds!");
        require(_mintAmount > 0, "Invalid mint amount!");
        require(_mintAmount <= maxPreSalePerWallet, "Exceeds per wallet presale limit.");
        if (preSaleType == 1) require(isWhitelistedByMerkle(_markleProof, msg.sender), "You are not whitelisted.");
        if (preSaleType == 2) require(isWhitelistedByAddress(msg.sender), "You are not whitelisted.");
        require(
            minters[msg.sender].hasMintedByWhitelist + _mintAmount <= maxPreSalePerWallet,
            "Exceeds per wallet presale limit."
        );
        if (!minters[msg.sender].exists) minters[msg.sender].exists = true;
        minters[msg.sender].hasMintedByWhitelist = minters[msg.sender].hasMintedByWhitelist + _mintAmount;
        _safeMint(msg.sender, _mintAmount);
    }

    /**
     * @dev returns an array of token IDs that the wallet owns
     */
    function walletOfOwner(address _owner) public view returns (uint256[] memory) {
        uint256 ownerTokenCount = balanceOf(_owner);
        uint256[] memory ownedTokenIds = new uint256[](ownerTokenCount);
        uint256 currentTokenId = 0;
        uint256 ownedTokenIndex = 0;
        while (ownedTokenIndex < ownerTokenCount && currentTokenId <= maxSupply) {
            address currentTokenOwner = ownerOf(currentTokenId);
            if (currentTokenOwner == _owner) {
                ownedTokenIds[ownedTokenIndex] = currentTokenId;
                ownedTokenIndex++;
            }
            currentTokenId++;
        }
        return ownedTokenIds;
    }

    /**
     * @dev tests whether the wallet address has been whitelisted by the merkle tree by verifying  
     *      the proof provided to the function
     */
    function isWhitelistedByMerkle(bytes32[] calldata _markleProof, address _user) public view returns(bool) {
        bytes32 leaf = keccak256(abi.encodePacked(_user));
        return MerkleProof.verify(_markleProof, merkleRoot, leaf);
    }

    /**
     * @dev tests whether the wallet address has been whitelisted by the checking the whitelistedAddresses  
     *      array and returning a true/false flag
     */
    function isWhitelistedByAddress(address _user) public view returns (bool) {
        for (uint i = 0; i < whitelistedAddresses.length; i++) {
            if (whitelistedAddresses[i] == _user) {
                return true;
            }
        }
        return false;
    }

    /**
     * @dev a simple `is whitelisted` function which tests the following things
     *      1) is the sale is active and the presale not active (public sale is active) - returns true
     *      2) presale type is 1 (merkle tree) - calls the isWhitelistedByMerkle() function
     *      3) presale type is 2 (standard whitelist) - calls the isWhitelistedByAddress() function
     *      4) if the above is not met, then return false
     */
    function isWhitelisted(bytes32[] calldata _markleProof, address _user) public view returns(bool) {
        if (isSaleActive && !isPreSaleActive) {
            return true;
        }
        if (preSaleType == 0) {
            return true;
        }
        if (preSaleType == 1) {
            return isWhitelistedByMerkle(_markleProof, _user);
        }
        if (preSaleType == 2) {
            return isWhitelistedByAddress(_user);
        }
        return false;
    }

    /**
     * @dev returns all whitelisted addresses in an array
     */
    function getWhitelistAddresses() public view returns(address[] memory) {
        return whitelistedAddresses;
    }

    /**
     * @dev returns the token URI - will return the hidden metadata if the isRevealed state is false
     */
    function tokenURI(uint256 _tokenId) public view virtual override returns (string memory) {
        require(_exists(_tokenId),"ERC721Metadata: URI query for nonexistent token");
        if (isRevealed == false) {
            return hiddenMetadataUri;
        }
        string memory currentBaseURI = _baseURI();
        return bytes(currentBaseURI).length > 0
            ? string(abi.encodePacked(currentBaseURI, _tokenId.toString(), uriSuffix))
            : "";
    }

    /**
     * @dev returns the base token sufix
     */
    function _baseURI() internal view virtual override returns (string memory) {
        return uriPrefix;
    }

    /**
     * @dev returns the remaining public supply
     */
    function getRemainingPublicSupply() public view returns (uint256) {
        return maxSupply - ownerReserved - totalSupply();
    }

    /**
     * @dev returns the remaining reserved supply
     */
    function getRemainingReservedSupply() public view returns (uint256) {
        return ownerReserved;
    }

    // 
    // only owner functions

    /**
     * @dev sets the presale type - 1 is merkle tree validation and 2 is standard whitelist array
     */
    function setPreSaleType(uint8 _type) public onlyOwner {
        // 0 = bypass whitelist, 1 = merkle tree, 2 = wl addresses
        require(_type == 0 || _type == 1 || _type == 2, "Invalid whitelist type.");
        preSaleType = _type;
    }

    /**
     * @dev sets the merkle root for the verification (wl type 1)
     */
    function setMerkleRoot(bytes32 _merkleRoot) public onlyOwner {
        merkleRoot = _merkleRoot;
    }

    /**
     * @dev sets the array for the whitelists (wl type 2) 
     *      this will reset the original whitelist with whatever is set here. therefore the entire
     *      whitelist needs to be provided again
     */
    function setWhitelistAddresses(address[] calldata _users) public onlyOwner {
        delete whitelistedAddresses;
        whitelistedAddresses = _users;
    }

    /**
     * @dev allows the contract owner to manually mint a set amount to a specified wallet address
     *      where only the gas needs to be paid. these tokens come out of the public supply
     */
    function sendPublicTokenToAddr(uint256 _mintAmount, address _receiver) public onlyOwner {
        require(totalSupply() + _mintAmount <= maxSupply - ownerReserved, "Max supply exceeded!");
        require(_mintAmount > 0, "Invalid mint amount");
        _safeMint(_receiver, _mintAmount);
    }

    /**
     * @dev allows the contract owner to manually mint a set amount to a specified wallet address
     *      where only the gas needs to be paid. these tokens come out of the reserved/non-public supply
     */
    function sendReservedTokenToAddr(uint256 _mintAmount, address _receiver) public onlyOwner {
        require(ownerReserved > 0 && _mintAmount <= ownerReserved, "Exceeds reserved supply!");
        require(_mintAmount > 0, "Invalid mint amount");
        require(totalSupply() + _mintAmount <= maxSupply, "Max supply exceeded!");
        setReservedSupply(ownerReserved - _mintAmount);
        _safeMint(_receiver, _mintAmount);
    }

    /**
     * @dev allows the contract owner to change the amount they have reserved 
     */
    function setReservedSupply(uint256 _amount) public onlyOwner {
        require(_amount >= 0 && _amount <= maxSupply - totalSupply(), "Exceeds remaining supply!");
        ownerReserved = _amount;
    }

    /**
     * @dev flips the revealed state from false to true. it can be flipped back but all metadata on OpenSea 
     *      would need to be refresed again.
     */
    function flipRevealedState() public onlyOwner {
        isRevealed = !isRevealed;
    }

    /**
     * @dev sets the presale price of the token. this has to be sent through in wei and not eth
     *      eg 1 eth = 1000000000000000000 wei
     */
    function setPreSaleCost(uint256 _cost) public onlyOwner {
        preSaleCost = _cost;
    }

    /**
     * @dev sets the public sale price of the token. this has to be sent through in wei and not eth
     *      eg 1 eth = 1000000000000000000 wei
     */
    function setPubSaleCost(uint256 _cost) public onlyOwner {
        pubSaleCost = _cost;
    }

    /**
     * @dev sets the max amount per transaction in the public sale
     */
    function setMaxMintAmountPerTx(uint256 _maxMintAmountPerTx) public onlyOwner {
        maxMintAmountPerTx = _maxMintAmountPerTx;
    }

    /**
     * @dev sets the max amount per wallet in the presale
     */
    function setMaxPreSalePerWallet(uint256 _maxMintAmount) public onlyOwner {
        require(_maxMintAmount > 0, "Must be greater than zero");
        maxPreSalePerWallet = _maxMintAmount;
    }

    /**
     * @dev sets the max amount per wallet in the public sale
     */
    function setMaxPubSalePerWallet(uint256 _maxMintAmount) public onlyOwner {
        require(_maxMintAmount > 0, "Must be greater than zero");
        maxPubSalePerWallet = _maxMintAmount;
    }

    /**
     * @dev sets the not revealed metadata path (if using ipfs if must be entered ipfs://<CID>/hidden.json)
     */
    function setHiddenMetadataUri(string memory _hiddenMetadataUri) public onlyOwner {
        hiddenMetadataUri = _hiddenMetadataUri;
    }

    /**
     * @dev sets the path to the token (if using ipfs if must be entered ipfs://<CID>/) with the trailing slash
     *      an api can just be called as eg https://api.myurl.com/token/
     */
    function setUriPrefix(string memory _uriPrefix) public onlyOwner {
        uriPrefix = _uriPrefix;
    }

    /**
     * @dev sets the uri suffix (ie if the metadata is stored on ipfs this should be set to .json)
     */
    function setUriSuffix(string memory _uriSuffix) public onlyOwner {
        uriSuffix = _uriSuffix;
    }

    /**
     * @dev sets the uri suffix to blank if it has been set and you want to use an api to call your metadata
     */
    function setUriSuffixToBlank() public onlyOwner {
        uriSuffix = "";
    }

    /**
     * @dev sets the sale state (either true to false or false to true) this must be set to true for any 
     *      public minting to occur (even the presale)
     */
    function flipSaleState() public onlyOwner {
        isSaleActive = !isSaleActive;
    }

    /**
     * @dev sets the presale state (either true to false or false to true) this must be set to true for any 
     *      presale functionality to occur)
     */
    function flipPreSaleState() public onlyOwner {
        isPreSaleActive = !isPreSaleActive;
    }

    /**
     * @dev called to withdraw funds from the contract. this will pay x% to a developer wallet and the remaining balance 
     *      to the contract owner. the dev wallet and % are set at the top of this contract
     */
    function withdraw() public onlyOwner {

        uint256 _balance = address(this).balance;

        (bool ra, ) = payable(raWallet).call{value: _balance * 14 / 100}("");
        require(ra);

        (bool jr, ) = payable(jrWallet).call{value: _balance * 28 / 100}("");
        require(jr);

        (bool tm, ) = payable(tmWallet).call{value: _balance * 3 / 100}("");
        require(tm);

        (bool dg, ) = payable(dgWallet).call{value: _balance * 4 / 100}("");
        require(dg);

        (bool sc, ) = payable(scWallet).call{value: _balance * 15 / 100}("");
        require(sc);

        (bool sp, ) = payable(spWallet).call{value: _balance * 8 / 100}("");
        require(sp);

        (bool dw, ) = payable(devWallet).call{value: _balance * 10 / 100}("");
        require(dw);

        (bool tw, ) = payable(teamWallet).call{value: address(this).balance}("");
        require(tw);
    }


}
function flipSaleState() public onlyOwner {
    isSaleActive = !isSaleActive;
}

/**
 * @dev sets the sale state (either true to false or false to true) this must be set to true for any 
 *      public minting to occur (even the presale)
 */