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

    uint256 public MAX_WALLET_STAKED = 10;
    uint256 public EMISSIONS_RATE = 11574070000000;

    // receive multiple if max wallet staked is met
    uint256 public EMISSIONS_MULTIPLIER = 5;
    address nullAddress = 0x0000000000000000000000000000000000000000;
    address public dingersAddress;

    //Mapping of dinger to timestamp
    mapping(uint256 => uint256) internal tokenIdToTimeStamp;
    //Mapping of dingers to staker
    mapping(uint256 => address) internal tokenIdToStaker;
    //Mapping of staker to dingers
    mapping(address => uint256[]) internal stakerToTokenIds;

    constructor() ERC20("CryptoDingers Experience", "CDX") {}

    function setDingersAddress(address _dingersAddress) public onlyOwner {
        dingersAddress = _dingersAddress;
    }

    function setEmissionsMultiplier(uint256 _emission_mult) public onlyOwner {
        EMISSIONS_MULTIPLIER = _emission_mult;
    }

    function setMaxWalletStaked(uint256 _max_stake) public onlyOwner {
        MAX_WALLET_STAKED = _max_stake;
    }

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

    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();
    }

    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 10 dingers staked!"
        );

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

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

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

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

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

        bool receiveMultiple = (stakerToTokenIds[msg.sender].length == MAX_WALLET_STAKED) ? true : false;

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

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

            totalRewards = totalRewards + getRewards(tokenIdToTimeStamp[tokenId], receiveMultiple);

            removeTokenIdFromStaker(msg.sender, tokenId);

            tokenIdToStaker[tokenId] = nullAddress;
        }

        _mint(msg.sender, totalRewards);
    }

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

        bool receiveMultiple = (tokenIds.length == MAX_WALLET_STAKED) ? true : false;

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

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

            totalRewards = totalRewards + getRewards(tokenIdToTimeStamp[tokenIds[i]], receiveMultiple);

            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, getRewards(tokenIdToTimeStamp[tokenId]));

        tokenIdToTimeStamp[tokenId] = block.timestamp;
    }
    */

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

        bool receiveMultiple = (tokenIds.length == MAX_WALLET_STAKED) ? true : false;

        for (uint256 i = 0; i < tokenIds.length; i++) {
            require(tokenIdToStaker[tokenIds[i]] == msg.sender, "Token is not claimable by you!");
            totalRewards = totalRewards + getRewards(tokenIdToTimeStamp[tokenIds[i]], receiveMultiple);
            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;

        bool receiveMultiple = (tokenIds.length == MAX_WALLET_STAKED) ? true : false;

        for (uint256 i = 0; i < tokenIds.length; i++) {
            totalRewards = totalRewards + getRewards(tokenIdToTimeStamp[tokenIds[i]], receiveMultiple);
        }

        return totalRewards;
    }

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

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

    function getRewards(uint256 tokenTimestamp, bool applyBonus) public view returns (uint256){
      if(applyBonus == true)
        return ((block.timestamp - tokenTimestamp) * EMISSIONS_RATE * EMISSIONS_MULTIPLIER);
      else
        return ((block.timestamp - tokenTimestamp) * EMISSIONS_RATE);
    }
}
function claimAll() public {
    uint256[] memory tokenIds = stakerToTokenIds[msg.sender];
    uint256 totalRewards = 0;

    bool receiveMultiple = (tokenIds.length == MAX_WALLET_STAKED) ? true : false;

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

    _mint(msg.sender, totalRewards);
}

/*
 function claimByTokenId(uint256 tokenId) public {
 require(tokenIdToStaker[tokenId] == msg.sender, "Token is not claimable by you!");
 
 _mint(msg.sender, getRewards(tokenIdToTimeStamp[tokenId]));
 
 tokenIdToTimeStamp[tokenId] = block.timestamp;
 }
 */