contract Juice is ERC20Burnable, Ownable {
    using SafeMath for uint256;

    uint256 public MAX_WALLET_STAKED = 100;
    uint256 public EMISSIONS_RATE = 11574070000000;

    address nullAddress = 0x0000000000000000000000000000000000000000;

    address public nfTokenAddress;

    //Mapping of mouse to timestamp
    mapping(uint256 => uint256) internal tokenIdToTimeStamp;

    //Mapping of mouse to staker
    mapping(uint256 => address) internal tokenIdToStaker;

    //Mapping of staker to NFT id
    mapping(address => uint256[]) internal stakerToTokenIds;

    uint256 internal stakedCount = 0;

    constructor() ERC20("Juice", "JUICE") {}

    function setNfTokenAddress(address _nfTokenAddress) public onlyOwner {
        nfTokenAddress = _nfTokenAddress;
        return;
    }

    function getTokensStaked(address staker)
        public
        view
        returns (uint256[] memory)
    {
        return stakerToTokenIds[staker];
    }

    /**
    * @dev Get Staked Count
    */
    function getStakedCount()
        public
        view
        returns (uint256)
    {
        return stakedCount;
    }

    function remove(address staker, uint256 index) internal {
        if (index >= stakerToTokenIds[staker].length) return;

        for (uint256 i = index; i < stakerToTokenIds[staker].length - 1; i++) {
            stakerToTokenIds[staker][i] = stakerToTokenIds[staker][i + 1];
        }
        stakerToTokenIds[staker].pop();
        stakedCount--;
    }

    function removeTokenIdFromStaker(address staker, uint256 tokenId) internal {
        for (uint256 i = 0; i < stakerToTokenIds[staker].length; i++) {
            if (stakerToTokenIds[staker][i] == tokenId) {
                //This is the tokenId to remove;
                remove(staker, i);
            }
        }
    }

    function stakeByIds(uint256[] memory tokenIds) public {
        require(
            stakerToTokenIds[msg.sender].length + tokenIds.length <=
                MAX_WALLET_STAKED,
            "Must have less than 50 tokens staked!"
        );

        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                IERC721(nfTokenAddress).ownerOf(tokenIds[i]) == msg.sender &&
                    tokenIdToStaker[tokenIds[i]] == nullAddress,
                "Token must be stakable by you!"
            );

            IERC721(nfTokenAddress).transferFrom(
                msg.sender,
                address(this),
                tokenIds[i]
            );

            stakerToTokenIds[msg.sender].push(tokenIds[i]);

            tokenIdToTimeStamp[tokenIds[i]] = block.timestamp;
            tokenIdToStaker[tokenIds[i]] = msg.sender;
            stakedCount++;
        }
    }

    function unstakeAll() public {
        require(
            stakerToTokenIds[msg.sender].length > 0,
            "Must have at least one token staked!"
        );
        uint256 totalRewards = 0;

        for (uint256 i = stakerToTokenIds[msg.sender].length; i > 0; i--) {
            uint256 tokenId = stakerToTokenIds[msg.sender][i - 1];

            IERC721(nfTokenAddress).transferFrom(
                address(this),
                msg.sender,
                tokenId
            );

            totalRewards =
                totalRewards +
                ((block.timestamp - tokenIdToTimeStamp[tokenId]) * emissionRateByTokenId(tokenId));

            removeTokenIdFromStaker(msg.sender, tokenId);

            tokenIdToStaker[tokenId] = nullAddress;
        }

        _mint(msg.sender, totalRewards);
    }

    function unstakeByIds(uint256[] memory tokenIds) public {
        uint256 totalRewards = 0;

        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                tokenIdToStaker[tokenIds[i]] == msg.sender,
                "Message Sender was not original staker!"
            );

            IERC721(nfTokenAddress).transferFrom(
                address(this),
                msg.sender,
                tokenIds[i]
            );

            totalRewards =
                totalRewards +
                ((block.timestamp - tokenIdToTimeStamp[tokenIds[i]]) * emissionRateByTokenId(tokenIds[i]));

            removeTokenIdFromStaker(msg.sender, tokenIds[i]);

            tokenIdToStaker[tokenIds[i]] = nullAddress;
        }

        _mint(msg.sender, totalRewards);
    }

    function claimByTokenId(uint256 tokenId) public {
        require(
            tokenIdToStaker[tokenId] == msg.sender,
            "Token is not claimable by you!"
        );

        _mint(
            msg.sender,
            ((block.timestamp - tokenIdToTimeStamp[tokenId]) * emissionRateByTokenId(tokenId))
        );

        tokenIdToTimeStamp[tokenId] = block.timestamp;
    }

    function claimAll() public {
        uint256[] memory tokenIds = stakerToTokenIds[msg.sender];
        uint256 totalRewards = 0;

        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(
                tokenIdToStaker[tokenIds[i]] == msg.sender,
                "Token is not claimable by you!"
            );

            totalRewards =
                totalRewards +
                ((block.timestamp - tokenIdToTimeStamp[tokenIds[i]]) * emissionRateByTokenId(tokenIds[i]));

            tokenIdToTimeStamp[tokenIds[i]] = block.timestamp;
        }

        _mint(msg.sender, totalRewards);
    }

    function getAllRewards(address staker) public view returns (uint256) {
        uint256[] memory tokenIds = stakerToTokenIds[staker];
        uint256 totalRewards = 0;

        for (uint256 i = 0; i < tokenIds.length; i++) {
            totalRewards =
                totalRewards +
                ((block.timestamp - tokenIdToTimeStamp[tokenIds[i]]) * emissionRateByTokenId(tokenIds[i]));
        }

        return totalRewards;
    }

    function getRewardsByTokenId(uint256 tokenId)
        public
        view
        returns (uint256)
    {
        require(
            tokenIdToStaker[tokenId] != nullAddress,
            "Token is not staked!"
        );

        uint256 secondsStaked = block.timestamp - tokenIdToTimeStamp[tokenId];

        return secondsStaked * emissionRateByTokenId(tokenId);
    }

    function getStaker(uint256 tokenId) public view returns (address) {
        return tokenIdToStaker[tokenId];
    }

        /**
     * @dev Returns the current emission rate by token id
     */
    function emissionRateByTokenId(uint256 tokenId) public view returns (uint256) {

        if(tokenId <= 4096)
            return 1 * EMISSIONS_RATE;
        if(tokenId > 4096 && tokenId <= 6144)
            return 2 * EMISSIONS_RATE;
        if(tokenId > 6144 && tokenId <= 7168)
            return 3 * EMISSIONS_RATE;
        if(tokenId > 7168 && tokenId <= 7680)
            return 4 * EMISSIONS_RATE;
        if(tokenId > 7680 && tokenId <= 7936)
            return 5 * EMISSIONS_RATE;
        if(tokenId > 7936 && tokenId <= 8064)
            return 6 * EMISSIONS_RATE;
        if(tokenId > 8064 && tokenId <= 8128)
            return 7 * EMISSIONS_RATE;
        if(tokenId > 8128 && tokenId <= 8160)
            return 8 * EMISSIONS_RATE;
        if(tokenId > 8160 && tokenId <= 8176)
            return 16 * EMISSIONS_RATE;
        if(tokenId > 8176 && tokenId <= 8184)
            return 32 * EMISSIONS_RATE;
        if(tokenId > 8184 && tokenId <= 8188)
            return 64 * EMISSIONS_RATE;
        if(tokenId > 8188 && tokenId <= 8190)
            return 128 * EMISSIONS_RATE;
        if(tokenId > 8190 && tokenId == 8191)
            return 256 * EMISSIONS_RATE;

        revert();
    }

    function mintForBurnedNft(address account, uint256 amount) public {
        require(nfTokenAddress == msg.sender, "You can't call this method");
        _mint(account, amount);
    }
}
// contracts/Token.sol
function getStakedCount()
    public
    view
    returns (uint256)
{
    return stakedCount;
}

/**
 * @dev Get Staked Count
 */