contract CryptoBeauties is Ownable, ERC721A, ReentrancyGuard {
    uint256 public immutable maxPerAddressDuringMint;
    uint256 public immutable amountForDevs;
    uint256 public amountForFreeList;
    uint256 private FreeWLpreSaleKey;
    uint256 private WLpreSaleKey;
    bool public PreSale=true;
    bool public Paused = false;
    

    address public Promoter = 0x414508c939B0B78347a22d1F2dE5A7747C1426A5;
    address public Developer = 0x64d556cAae3eD6Df460F9CA7A93470722D04bE05;
    address public DAO = 0x402881BAd73d3932C2472Ab0453E0b03B32C52F0;
    address public Project = 0x421b0B0603fD45719B480CF71670EC00aE64b309;


    struct SaleConfig {

        uint64 mintlistPrice;
        uint64 publicPrice;
    }

    SaleConfig public saleConfig;

    constructor(
        uint256 maxBatchSize_,
        uint256 collectionSize_,
        uint256 amountForFreeList_,
        uint256 amountForDevs_
    ) ERC721A("CryptoBeauties", "CRYPTOBEAUTIES", maxBatchSize_, collectionSize_) {
        maxPerAddressDuringMint = maxBatchSize_;
        amountForFreeList = amountForFreeList_;
        amountForDevs = amountForDevs_;
        require(
            amountForFreeList_ <= collectionSize_,
            "larger collection size needed"
        );
    }

    modifier callerIsUser() {
        require(tx.origin == msg.sender, "The caller is another contract");
        _;
    }

   // Free WhiteList Mint 

    function FreeWhiteListMint(uint256 quantity, uint256 _FWLKey) external payable callerIsUser {
        //price set based on quantity being minted
        uint256 price = uint256(saleConfig.mintlistPrice)*quantity;
        //to continue the quantity and the pric must both be greater than zero
        require(price > 0,"a balance is required");
        //This function requires PRESALE to be true
        require(PreSale, "There are no Free Whitelist sales at this moment ");
        require(!Paused,"This contract is temporarily paused");
        
        //Verify the minter is whitelisted
    
        require(FreeWLpreSaleKey == _FWLKey, "not eligible for freelist mint");
        
        //Make sure the minting does not allow more than collection size
        //Keep 150 available for the DAO Community and the owners
        require(totalSupply() + quantity + amountForDevs  <= collectionSize, "reached max supply");
        //this may be redundant
        uint256 ownerTokens = balanceOf(msg.sender);

        //Make sure the minter is Free Whitelisted for first token to be free
        if (ownerTokens==0) {
            require(amountForFreeList>0,"there are no more free mint tokens available");
            price = saleConfig.mintlistPrice*(quantity-1); 
            amountForFreeList--;
        } 

        //check quantity remains leass than 6 and MINT
        require(ownerTokens + quantity < 6,"this amount exceeds your allocation");
          _safeMint(msg.sender, quantity);
        refundIfOver(price);
    }

    //Whitelist Mint

    function WhiteListMint(uint256 quantity, uint256 _WLKey) external payable callerIsUser {
        //price set based on quantity being minted
        uint256 price = uint256(saleConfig.mintlistPrice)*quantity;
        //to continue the quantity and the pric must both be greater than zero
        require(price > 0,"a balance is required");
        //This function requires PRESALE to be true
        require(PreSale, "There are no Free Whitelist sales at this moment ");
        require(!Paused,"This contract is temporarily paused");
        //Verify the minter is whitelisted
        require(WLpreSaleKey == _WLKey, "not eligible for freelist mint");
        
        //Make sure the minting does not allow more than collection size
        //Keep 150 available for the DAO Community and the owners
        require(totalSupply() + quantity + amountForDevs  <= collectionSize, "reached max supply");
        //this may be redundant
        uint256 ownerTokens = balanceOf(msg.sender);

        //check quantity remains leass than 6 and MINT
        require(ownerTokens + quantity < 6,"this amount exceeds your allocation");
        _safeMint(msg.sender, quantity);
        refundIfOver(price);
    }

    //Owner Mint

    function AllowOwnerMint(uint256 quantity) external payable onlyOwner {
        require(totalSupply() + quantity <= collectionSize, "reached max supply");
         _safeMint(msg.sender, quantity);
    }

    //Public Sale Mint

    function publicSaleMint(uint256 quantity)
        external
        payable
        callerIsUser
    {
        require(!PreSale,"Only Whitelisted accounts can mint at the moment");
        require(!Paused,"This contract is temporarily paused");
        SaleConfig memory config = saleConfig;

        uint256 publicPrice = uint256(config.publicPrice);



        require(
            totalSupply() + quantity + amountForDevs <= collectionSize,
            "reached max supply"
        );
        require(
            balanceOf(msg.sender) + quantity <= maxPerAddressDuringMint,
            "can not mint this many"
        );
        _safeMint(msg.sender, quantity);
        refundIfOver(publicPrice * quantity);
    }

    function CollectionSize ()external view returns(uint256){
        return collectionSize;
    }

    function freeTokensRemaining ()external view returns(uint256){
        return amountForFreeList;
    }

    function refundIfOver(uint256 price) private {
        require(msg.value >= price, "Need to send more ETH.");
        if (msg.value > price) {
            payable(msg.sender).transfer(msg.value - price);
        }
    }

    function SetUpPublicSale(
        uint64 mintlistPriceWei,
        uint64 publicPriceWei
    ) external onlyOwner {
        saleConfig = SaleConfig(         
            mintlistPriceWei,
            publicPriceWei

        );
    }

  function setPaused(bool _state) external onlyOwner {
      Paused = _state;
  }

  function setPreSale(bool _state) external onlyOwner {
      PreSale = _state;
  }
  
  function setFreeWLpreSaleKey(uint256 _FreeKey) external onlyOwner {
      FreeWLpreSaleKey = _FreeKey;
  }

  function setWLpreSaleKey(uint256 _WhiteKey) external onlyOwner {
      WLpreSaleKey = _WhiteKey;
  }
 
    // // metadata URI
    string private _baseTokenURI = "https://ipfs.cryptobeauties.io/";

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

    function setupBaseURI(string calldata _baseURIz) external onlyOwner {
        _baseTokenURI = _baseURIz;
    }
    function setWithdrawAddress(address _DAO, address _Promoter, address _Developer, address _Project ) external onlyOwner {
        DAO = _DAO;
        Promoter = _Promoter;
        Developer = _Developer;
        Project = _Project;
    }
   

    function withdrawMoney() external onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        uint256 toPartner = 405*balance/1000;
        uint256 toDAO = 14*balance/100;
        uint256 toProject = 4*balance/100;
        require(payable(Promoter).send(toPartner));
        require(payable(Developer).send(toPartner));
        require(payable(DAO).send(toDAO));
        require(payable(Project).send(toProject));
    }

    function withdrawFinal() external onlyOwner nonReentrant {
        uint256 balance = address(this).balance;
        uint256 toPartner = 405*balance/1000;
        uint256 toDAO = 14*balance/100;
        require(payable(Promoter).send(toPartner));
        require(payable(Developer).send(toPartner));
        require(payable(DAO).send(toDAO));
        uint256 toOwner = balance - 2*toPartner - toDAO;
        require(payable(msg.sender).send(toOwner));
    }


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




    function getOwnershipData(uint256 tokenId)
        external
        view
        returns (TokenOwnership memory)
    {
        return ownershipOf(tokenId);
    }
}
function publicSaleMint(uint256 quantity)
    external
    payable
    callerIsUser
{
    require(!PreSale,"Only Whitelisted accounts can mint at the moment");
    require(!Paused,"This contract is temporarily paused");
    SaleConfig memory config = saleConfig;

    uint256 publicPrice = uint256(config.publicPrice);



    require(
        totalSupply() + quantity + amountForDevs <= collectionSize,
        "reached max supply"
    );
    require(
        balanceOf(msg.sender) + quantity <= maxPerAddressDuringMint,
        "can not mint this many"
    );
    _safeMint(msg.sender, quantity);
    refundIfOver(publicPrice * quantity);
}

//Public Sale Mint