contract Bunaverse is ERC721 {
    using SafeMath for uint256;

    address public owner;
    ILuckyEggs luckyEggs;
    ERC721 partnerToken;
    
    // Bunnies own;
    uint256 public Max_Supply = 9999;
    uint public Reserved = 20;
    uint public SalePrice = 80000000000000000;  // 0.08 ETH
    uint public TokensMinted = 0;
    uint256[] private _allTokens;
    bool public SaleActive  =  true; 
    
    // Change Identity of Bunnies and Eggs
    mapping(uint => bool) public exist;
    bool public ChangeIdentityActive = false;

    // LovePill own;
    uint private LovePill_newTokenID = 88888;
    uint private LovePill_StartIndex = LovePill_newTokenID;
    uint public LovePillQuantity = 1112;
    uint public LovePillMinted = 0;

    // Breed section;
    bool public BreedActive = false;
    uint public BreedCount = 0;
    uint public Breed_StartIndex = 30001;
    uint public Breed_NextIndex  = Breed_StartIndex;
    uint public BreedPrice = 50000000000000000;  // 0.05 ETH
    
    // CrossBreed section;
    bool public CrossBreedActive = false;
    uint public CrossBreedCount = 0;
    uint public CrossBreed_StartIndex = 40001;
    uint public CrossBreed_NextIndex  = CrossBreed_StartIndex;
    uint public CrossBreedPrice = 80000000000000000;  // 0.08 ETH
    
    // Recycle section;
    uint public RecycleCount = 0;
    uint public Recycle_StartIndex = 10001;
    uint public Recycle_NextIndex  = Recycle_StartIndex;
    bool public RecycleActive = false;
    
    // List of addresses that have a number of reserved tokens for presale
    mapping (address => uint256) public presaleReserved;
    
    event Minted(address to, uint startTokenId, uint noOfTokens);
    event PriceUpdated(uint newPrice);
    event OwnerUpdated(address newOwner);
    event IdentityChange(address _by, uint _tokenId, string _name, string _desc);
    
    // Owner addresses
    address payable private fundWallet1;
    address payable private fundWallet2;
    

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }
    
    constructor(address _fundWallet1, address _fundWallet2, address _LuckyEggsContract)
    // name of token
    ERC721("Bunaverse", "Bunaverse") {
        owner = msg.sender;
        fundWallet1 = payable(_fundWallet1);
        fundWallet2 = payable(_fundWallet2);
        luckyEggs = ILuckyEggs(_LuckyEggsContract);
    }

    function fundWalletView() public view returns(address){
        require(msg.sender == owner || msg.sender == fundWallet1, "Only owner");
        return fundWallet1;
    }  

    function fundWalletUpdate(address _fundWallet1, address _fundWallet2) public returns(address){
        require(msg.sender == owner || msg.sender == fundWallet1, "Only owner");
        fundWallet1 = payable(_fundWallet1);
        fundWallet2 = payable(_fundWallet2);
    }  

    // Admin minting function to reserve tokens for the team, collabs, customs and giveaways
    function mintReserved(uint256 _amount) public onlyOwner {
        // Limited to a publicly set amount
        require( _amount <= Reserved, "Can't reserve more than set amount" );
        Reserved -= _amount;
        for(uint256 i; i < _amount; i++){
            _safeMint( msg.sender, TokensMinted + i );
        }
        luckyEggs.mintLuckyEggs(_amount, msg.sender);
        emit Minted(msg.sender, TokensMinted + 1, _amount);
        TokensMinted = TokensMinted + _amount;
    }


    function updatePrice(uint newprice) public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        SalePrice = newprice;
        emit PriceUpdated(newprice);
    }

    function activateSale() public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        SaleActive = !SaleActive;
    }

    // Standard mint function
    function mintSales(uint256 _amount) public payable {
       // uint256 supply = totalSupply();
        require( SaleActive,                     "Sale isn't active" );
        require( _amount > 0 && _amount < 11,    "Can only mint between 1 and 10 tokens at once" );
        require( TokensMinted + _amount <= Max_Supply, "Can't mint more than max supply" );
        require( msg.value == SalePrice * _amount,   "Wrong amount of ETH sent" );
        for(uint256 i; i < _amount; i++){
            _safeMint( msg.sender, TokensMinted + i );
        }
        luckyEggs.mintLuckyEggs(_amount, msg.sender);
        emit Minted(msg.sender, TokensMinted + 1, _amount);
        TokensMinted = TokensMinted + _amount;
    }

    function activateRecycling() public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        RecycleActive = !RecycleActive;
    }

    /* Recycle Bunnies */
    function recycle(uint token1_ID, uint token2_ID) public {
        require(RecycleActive == true, "Recycling is not active yet");
        require(token1_ID != token2_ID, "Same tokens cannot recycle :P ");
        require(token1_ID < LovePill_StartIndex, "Please check token 1");
        require(token2_ID < LovePill_StartIndex, "Please check token 2");
        require(ownerOf(token1_ID) == msg.sender, "Don't recycle what you don't own! Check token 1");
        require(ownerOf(token2_ID) == msg.sender, "Don't recycle what you don't own! Check token 2");
        _mint(msg.sender, Recycle_NextIndex);
        Recycle_NextIndex += 1;
        RecycleCount +=  1;
        sacrifice(token1_ID);
        sacrifice(token2_ID);
    }
    
    function sacrifice(uint256 _tokenId) public {
        require(_exists(_tokenId), "Token does not exist.");
        require(ownerOf(_tokenId) == _msgSender(), "Don't sacrifice what you don't own!");
        _burn(_tokenId);
    }

   
    function changeLovePillLimit(uint  add_newTokens) public{
        require(msg.sender == owner || msg.sender == fundWallet1, "Only owner");
        LovePillQuantity += add_newTokens;
    }

    /* Love Pill */
    function mintLovePill(address[] memory _players) public {
        require(msg.sender == owner || msg.sender == fundWallet1, "Only owner can mint");
        uint256 numberOfPlayers = _players.length;
        for (uint256 i = 0; i < numberOfPlayers; i++) {
            require(LovePillQuantity != LovePillMinted, "No Love Pill Available.");
            _mint(_players[i], LovePill_newTokenID);
            LovePillMinted++;
            LovePill_newTokenID++;
        }
    }

    function updateBreedPrice(uint newprice) public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        BreedPrice = newprice;
        emit PriceUpdated(newprice);
    }

    function activateBreeding() public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        BreedActive = !BreedActive;
    }

    /* Breed Bunnies */
    function breed(uint token1_ID, uint token2_ID, uint LovePill_tokenId) public payable {
        require(token1_ID != token2_ID, "You don't need to come here for self breeding");
        require(token1_ID < LovePill_StartIndex, "You can only Breed Bunnies... Please check token 1!");
        require(token2_ID < LovePill_StartIndex, "You can only Breed Bunnies... Please check token 2!");
        require(LovePill_tokenId >= LovePill_StartIndex, "No Love without Love Pill...");
        require(ownerOf(LovePill_tokenId) == msg.sender, "Own Love Pill for Breeding!!!");
        require(ownerOf(token1_ID) == msg.sender, "Trying to breed someone else's Bunny? Own token 1");
        require(ownerOf(token2_ID) == msg.sender, "Trying to breed someone else's Bunny? Own token 2");
        require(msg.value >= BreedPrice, "Breeding Price is not correct.");  //User must pay set price.`
        _mint(msg.sender, Breed_NextIndex);
        Breed_NextIndex += 1;
        BreedCount +=  1;
        sacrifice(LovePill_tokenId);
    }
    
    function updateCrossBreedPrice(uint newprice) public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        CrossBreedPrice = newprice;
        emit PriceUpdated(newprice);
    }

    function activateCrossBreeding() public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        CrossBreedActive = !CrossBreedActive;
    }
    
    function setCrossBreedingAddress(address _address) public {
        partnerToken = ERC721(_address);
    }
    
    /* CrossBreed Bunnies */
    function crossBreed(uint token1_ID, uint token2_ID, uint LovePill_tokenId) public payable {
        require(partnerToken.ownerOf(token1_ID) == msg.sender || partnerToken.ownerOf(token2_ID) == msg.sender, "Atleast one token should come from partner");
        if (partnerToken.ownerOf(token1_ID) == msg.sender) {
            require(token2_ID < LovePill_StartIndex, "Please check token 2...");
            require(ownerOf(token2_ID) == msg.sender, "User should own token 2");
        }
        else if (partnerToken.ownerOf(token2_ID) == msg.sender) {
            require(token1_ID < LovePill_StartIndex, "Please check token 1...");
            require(ownerOf(token1_ID) == msg.sender, "User should own token 1");
        }
        require(LovePill_tokenId >= LovePill_StartIndex, "No Love without Love Pill...");
        require(ownerOf(LovePill_tokenId) == msg.sender, "Own Love Pill for Breeding!!!");
        
        require(msg.value >= CrossBreedPrice, "Cross Breeding Price is not correct.");  //User must pay set price.`
        _mint(msg.sender, CrossBreed_NextIndex);
        CrossBreed_NextIndex += 1;
        CrossBreedCount +=  1;
        sacrifice(LovePill_tokenId);
    }

    function activateChangeIdentity() public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        ChangeIdentityActive = !ChangeIdentityActive;
    }
   
    /* Change Name and Description */   
   function changeIdentity(uint _tokenId, string memory _name, string memory _desc) public {
        require(ChangeIdentityActive == true, "New identity creation is not active yet");
        require(ownerOf(_tokenId) == msg.sender, "Hey, don't update someone else's tokens!");
        emit IdentityChange(msg.sender, _tokenId, _name, _desc);
    }
   
    function balancer() public view returns (uint256){
        return address(this).balance;
    }
   
    function updateOwner(address newOwner) public{
        require(msg.sender == owner || msg.sender == fundWallet1);
        owner = newOwner;
        emit OwnerUpdated(newOwner);
    }

    function withdrawTeam(uint256 amount) public payable onlyOwner {
        uint256 percent = amount / 100;
        require(payable(fundWallet1).send(percent * 50)); 
        require(payable(fundWallet2).send(percent * 50)); 
    }
    
    function updateURI(string memory new_URI) public{
        require(msg.sender == owner || msg.sender == fundWallet1, "Only owner");
       _setBaseURI(new_URI);
    }
}
function recycle(uint token1_ID, uint token2_ID) public {
    require(RecycleActive == true, "Recycling is not active yet");
    require(token1_ID != token2_ID, "Same tokens cannot recycle :P ");
    require(token1_ID < LovePill_StartIndex, "Please check token 1");
    require(token2_ID < LovePill_StartIndex, "Please check token 2");
    require(ownerOf(token1_ID) == msg.sender, "Don't recycle what you don't own! Check token 1");
    require(ownerOf(token2_ID) == msg.sender, "Don't recycle what you don't own! Check token 2");
    _mint(msg.sender, Recycle_NextIndex);
    Recycle_NextIndex += 1;
    RecycleCount +=  1;
    sacrifice(token1_ID);
    sacrifice(token2_ID);
}

/* Recycle Bunnies */