contract SuperlativeApesStaking is ERC20("Superlative Staking", "SLAPE"), IERC721Receiver, Ownable, ReentrancyGuard {

    using EnumerableSet for EnumerableSet.UintSet;

    mapping (address => uint256) public totalStakedTokens;
    mapping(uint256 => uint256) public stakedDays;
    mapping(address => EnumerableSet.UintSet) private _deposits;

    mapping(address => EnumerableSet.UintSet) private _depositsMutant;
    mapping (address => uint256) public totalStakedMutantTokens;
    mapping(uint256 => uint256) public stakedMutantDays;

    uint256 public ReservedForLiquidity = 888888 ether;

    uint256 constant public APE_RATE = 10 ether; 
    uint256 constant public GOLD_RATE = 20 ether;


    uint256 constant public MUTANT_RATE = 5 ether;
    uint256 constant public MUTANT_M3_RATE = 20 ether;

    
    uint256[] public GOLD_APES = [1207, 415 , 286 , 2591, 2457, 2131, 2128, 1704, 1605, 1529, 1437, 1378, 3794, 3776, 3730, 3207, 3028, 2663, 4441, 4354, 4325, 3910];
    uint256 [] public M3 = [3499,1046,4407,83,2736];
    

    address[] public stakedWalletSlape;
    address[] public stakedWalletMutant;
    address[] private tempArray;

    IERC721 public superlativeApesContract = IERC721(0x1e87eE9249Cc647Af9EDEecB73D6b76AF14d8C27);
    IERC721 public superlativeMutantContract = IERC721(0x9FB2EEb75754815c5Cc9092Cd53549cEa5dc404f);

    constructor() 
    {
        _mint(msg.sender,ReservedForLiquidity);
    }

    function stakedWalletsLength() public view returns(uint256)
    {
        return stakedWalletSlape.length;
    }

    function teamMint(uint256 totalAmount) public onlyOwner
    {
        _mint(msg.sender, (totalAmount * 10 ** 18));
    }

    function StakeYourSlape(uint256[] calldata tokenID) public 
    {
        require(superlativeApesContract.isApprovedForAll(msg.sender, address(this)), "You are not Approved to stake your NFT!");
        
        for(uint256 i; i<tokenID.length; i++)
        {
            superlativeApesContract.safeTransferFrom(msg.sender, address(this), tokenID[i], "");

            if(!contains(msg.sender))
            {
                stakedWalletSlape.push(msg.sender);
            }
            
            _deposits[msg.sender].add(tokenID[i]);
            totalStakedTokens[msg.sender] = totalStakedTokens[msg.sender] + 1;
            stakedDays[tokenID[i]] = block.timestamp;
        }
    }

    function unStakeYourSlape(uint256[] calldata tokenID) public 
    {
        claimTokens();
        for(uint256 i; i<tokenID.length; i++)
        {
            superlativeApesContract.safeTransferFrom(address(this), msg.sender, tokenID[i], "");
            _deposits[msg.sender].remove(tokenID[i]);
            totalStakedTokens[msg.sender] = totalStakedTokens[msg.sender] + 1;

            if(_deposits[msg.sender].values().length < 1)
            {
                remove(msg.sender);
            }
        }
    }

    function contains(address _incoming) internal view returns(bool)
    {
        bool doesListContainElement = false;
    
        for (uint i=0; i < stakedWalletSlape.length; i++) {
            if (_incoming == stakedWalletSlape[i]) {
                doesListContainElement = true;
            }
        }

        return doesListContainElement;
    }
    
    function remove(address _incoming) internal {

        delete tempArray;
        
        for (uint256 i = 0; i<stakedWalletSlape.length; i++){
            if(stakedWalletSlape[i] != _incoming)
            {
                tempArray.push(stakedWalletSlape[i]);
            }
        }

        delete stakedWalletSlape;

        stakedWalletSlape = tempArray;
        
    }


    function claimTokens() public 
    {
        uint256 payout = 0;
        for(uint256 i = 0; i < _deposits[msg.sender].length(); i++)
        {
            payout += totalRewardsToPay(_deposits[msg.sender].at(i));
            stakedDays[_deposits[msg.sender].at(i)] = block.timestamp;
        }
        _mint(msg.sender, payout);
    }

    function viewRewardTotal() external view returns (uint256)
    {
        uint256 payout = 0;
        
        for(uint256 i = 0; i < _deposits[msg.sender].length(); i++)
        {
            payout = payout + totalRewardsToPay(_deposits[msg.sender].at(i));
        }
        return payout;
    }

    function totalRewardsToPay(uint256 tokenId) internal view returns(uint256)
    {
        uint256 payout = 0;
        
        payout = howManyDaysStaked(tokenId) * APE_RATE;

        for(uint256 i=0; i<GOLD_APES.length; i++)
        {
            if(tokenId == GOLD_APES[i])
            {
                payout = howManyDaysStaked(tokenId) * GOLD_RATE;
            }   
        }
        return payout;
    }

    function howManyDaysStaked(uint256 tokenId) public view returns(uint256)
    {
        
        require(
            _deposits[msg.sender].contains(tokenId),
            'Token not deposited'
        );
        
        uint256 timeCalc = block.timestamp - stakedDays[tokenId];
        uint256 returndays = timeCalc / 86400;
       
        return returndays;
    }

    function returnStakedTokens() public view returns (uint256[] memory)
    {
        return _deposits[msg.sender].values();
    }
    


    //Mutant Functions

    function containsMutate(address _incoming) internal view returns(bool)
    {
        bool doesListContainElement = false;
    
        for (uint i=0; i < stakedWalletMutant.length; i++) {
            if (_incoming == stakedWalletMutant[i]) {
                doesListContainElement = true;
            }
        }

        return doesListContainElement;
    }

    function unStakeYourMutant(uint256[] calldata tokenID) public 
    {
        claimTokensMutants();
        for(uint256 i; i<tokenID.length; i++)
        {
            superlativeMutantContract.safeTransferFrom(address(this), msg.sender, tokenID[i], "");
            _depositsMutant[msg.sender].remove(tokenID[i]);
            totalStakedMutantTokens[msg.sender] = totalStakedMutantTokens[msg.sender] + 1;

            if(_depositsMutant[msg.sender].values().length < 1)
            {
                remove(msg.sender);
            }
        }
    }

    function StakeYourMutant(uint256[] calldata tokenID) public 
    {
        require(superlativeMutantContract.isApprovedForAll(msg.sender, address(this)), "You are not Approved to stake your NFT!");
        
        for(uint256 i; i<tokenID.length; i++)
        {
            superlativeMutantContract.safeTransferFrom(msg.sender, address(this), tokenID[i], "");

            if(!containsMutate(msg.sender))
            {
                stakedWalletMutant.push(msg.sender);
            }
            
            _depositsMutant[msg.sender].add(tokenID[i]);
            totalStakedMutantTokens[msg.sender] = totalStakedMutantTokens[msg.sender] + 1;
            stakedMutantDays[tokenID[i]] = block.timestamp;
        }
    }

    function claimTokensMutants() public 
    {
        uint256 payout = 0;
        for(uint256 i = 0; i < _depositsMutant[msg.sender].length(); i++)
        {
            payout += totalRewardsToPayMutant(_depositsMutant[msg.sender].at(i));   
            stakedMutantDays[_depositsMutant[msg.sender].at(i)] = block.timestamp;
        }

        _mint(msg.sender, payout);
    }

    function viewRewardTotalMutant() external view returns (uint256)
    {
        uint256 payout = 0;
        
        for(uint256 i = 0; i < _depositsMutant[msg.sender].length(); i++)
        {
            payout = payout + totalRewardsToPayMutant(_depositsMutant[msg.sender].at(i));
        }
        return payout;
    }

    function howManyDaysStakedMutant(uint256 tokenId) public view returns(uint256)
    {
        
        require(
            _depositsMutant[msg.sender].contains(tokenId),
            'Token not deposited'
        );
        
        uint256 timeCalc = block.timestamp - stakedMutantDays[tokenId];
        uint256 returndays = timeCalc / 86400;
       
        return returndays;
    }

    function returnStakedTokensMutant() public view returns (uint256[] memory)
    {
        return _depositsMutant[msg.sender].values();
    }

    function totalRewardsToPayMutant(uint256 tokenId) internal view returns(uint256)
    {
        uint256 payout = howManyDaysStakedMutant(tokenId) * MUTANT_RATE;
        
        for(uint256 i=0;i<M3.length;i++)
        {
            if(M3[i] == tokenId)
            {
                payout = howManyDaysStakedMutant(tokenId) * MUTANT_M3_RATE;
            }
        }

        if(tokenId >= 13333)
        {
            payout = howManyDaysStakedMutant(tokenId) * MUTANT_M3_RATE;
        }
        
        
        return payout;
    }


    function onERC721Received(
        address,
        address,
        uint256,
        bytes calldata
    ) external pure override returns (bytes4) {
        return IERC721Receiver.onERC721Received.selector;
    }
}
/// SPDX-License-Identifier: UNLICENSED
function containsMutate(address _incoming) internal view returns(bool)
{
    bool doesListContainElement = false;
    
    for (uint i=0; i < stakedWalletMutant.length; i++) {
        if (_incoming == stakedWalletMutant[i]) {
            doesListContainElement = true;
        }
    }

    return doesListContainElement;
}

//Mutant Functions