contract BadboyBillionaires is ERC721A
{
    using Strings for uint256;

    string public baseURI;
    uint256 public cost;
    uint256 public maxSupply;
    uint256 public maxMintAmount = 50;
    uint256 public maxMintPerTransaction = 50;
    address public owner;
    bool public preSaleLive;
    bool public mintLive;

    mapping(address => bool) private whiteList;
    mapping(address => uint256) private mintCount;

    modifier onlyOwner() {
        require(owner == msg.sender, "not owner");
        _;
    }

    modifier preSaleIsLive() {
        require(preSaleLive, "preSale not live");
        _;
    }

    modifier mintIsLive() {
        require(mintLive, "mint not live");
        _;
    }

    constructor(string memory defaultBaseURI) ERC721A("Bad Boy Billionaires", "BBB", maxMintAmount) {
        owner = msg.sender;
        baseURI = defaultBaseURI;
        maxSupply = 6666;
        maxMintPerTransaction;
        cost = 50000000000000000;
    }

    function isWhiteListed(address _address) public view returns (bool){
        return whiteList[_address];
    }

    function mintedByAddressCount(address _address) public view returns (uint256){
        return mintCount[_address];
    }

    function setCost(uint256 _newCost) public onlyOwner {
        cost = _newCost;
    }

    function setMaxSupply(uint256 _newSupply) public onlyOwner {
        maxSupply = _newSupply;
    }

    function setmaxMintAmount(uint256 _newmaxMintAmount) public onlyOwner {
        maxMintAmount = _newmaxMintAmount;
    }

    function setmaxMintPerTransaction(uint256 _newmaxMintPerTransaction) public onlyOwner {
        maxMintPerTransaction = _newmaxMintPerTransaction;
    }

    // Minting functions
    function mint(uint256 _mintAmount) external payable mintIsLive {
        address _to = msg.sender;
        uint256 minted = mintCount[_to];
        require(minted + _mintAmount <= maxMintAmount, "mint over max");
        require(_mintAmount <= maxMintPerTransaction, "amount must < max");
        require(totalSupply() + _mintAmount <= maxSupply, "mint over supply");
        require(msg.value >= cost * _mintAmount, "insufficient funds");

        mintCount[_to] = minted + _mintAmount;
        _safeMint(msg.sender, _mintAmount);
    }

    function preSaleMint(uint256 _mintAmount) external payable preSaleIsLive {
        address _to = msg.sender;
        uint256 minted = mintCount[_to];
        require(whiteList[_to], "not whitelisted");
        require(minted + _mintAmount <= maxMintAmount, "mint over max");
        require(totalSupply() + _mintAmount <= maxSupply, "mint over supply");
        require(msg.value >= cost * _mintAmount, "insufficient funds");

        mintCount[_to] = minted + _mintAmount;
        _safeMint(msg.sender, _mintAmount);
    }

    // Only Owner executable functions
    function mintByOwner(address _to, uint256 _mintAmount) external onlyOwner {
        require(totalSupply() + _mintAmount <= maxSupply, "mint over supply");
        if (_mintAmount <= maxBatchSize) {
            _safeMint(_to, _mintAmount);
            return;
        }
        
        uint256 leftToMint = _mintAmount;
        while (leftToMint > 0) {
            if (leftToMint <= maxBatchSize) {
                _safeMint(_to, leftToMint);
                return;
            }
            _safeMint(_to, maxBatchSize);
            leftToMint = leftToMint - maxBatchSize;
        }
    }

    function addToWhiteList(address[] calldata _addresses) external onlyOwner {
        for (uint256 i; i < _addresses.length; i++) {
            whiteList[_addresses[i]] = true;
        }
    }

    function togglePreSaleLive() external onlyOwner {
        if (preSaleLive) {
            preSaleLive = false;
            return;
        }
        preSaleLive = true;
    }

    function toggleMintLive() external onlyOwner {
        if (mintLive) {
            mintLive = false;
            return;
        }
        preSaleLive = false;
        mintLive = true;
    }

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

    function withdraw() external payable onlyOwner {

        (bool hs, ) = payable(0x2D6D4CBbE680250C1fE92ECBfFdbEF3F891ce553).call{value: address(this).balance * 15 / 100}("");
        require(hs);

        (bool success, ) = msg.sender.call{value: address(this).balance}("");
        require(success, "Transfer failed");
    }

    function setOwnersExplicit(uint256 quantity) external onlyOwner {
        _setOwnersExplicit(quantity);
    }

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

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

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

    fallback() external payable { }

    receive() external payable { }
}
function mint(uint256 _mintAmount) external payable mintIsLive {
    address _to = msg.sender;
    uint256 minted = mintCount[_to];
    require(minted + _mintAmount <= maxMintAmount, "mint over max");
    require(_mintAmount <= maxMintPerTransaction, "amount must < max");
    require(totalSupply() + _mintAmount <= maxSupply, "mint over supply");
    require(msg.value >= cost * _mintAmount, "insufficient funds");

    mintCount[_to] = minted + _mintAmount;
    _safeMint(msg.sender, _mintAmount);
}

// Minting functions