contract Souls is ERC20, Ownable {

    struct stakeTracker {
        uint256 lastBlockChecked;
        uint256 rewards;
        uint256 cthuStaked;
    }

    uint256 private rewardsVar;

    using SafeERC20 for IERC20;
    using SafeMath for uint256;

    address private cthuAddress;
    IERC20 private cthuToken;

    uint256 private _totalCthuStaked;
    mapping(address => stakeTracker) private _stakedBalances;
    mapping(address => bool) blackListed;
    constructor() public ERC20("Souls", "SOULS") {
        _mint(msg.sender, 100000 * (10 ** 18));
        rewardsVar = 100000;
    }

    event Staked(address indexed user, uint256 amount, uint256 totalCthuStaked);
    event Withdrawn(address indexed user, uint256 amount);
    event Rewards(address indexed user, uint256 reward);


    modifier updateStakingReward(address account) {
        if (block.number > _stakedBalances[account].lastBlockChecked) {
            uint256 rewardBlocks = block.number
                                        .sub(_stakedBalances[account].lastBlockChecked);



            if (_stakedBalances[account].cthuStaked > 0) {
                _stakedBalances[account].rewards = _stakedBalances[account].rewards
                                                                            .add(
                                                                            _stakedBalances[account].cthuStaked
                                                                            .mul(rewardBlocks)
                                                                            / rewardsVar);
            }

            _stakedBalances[account].lastBlockChecked = block.number;

            emit Rewards(account, _stakedBalances[account].rewards);
        }
        _;
    }

    /* in case someone mistakenly sent ERC20 tokens to the contract.
    *  The owner can withdraw these tokens and refund the user.
    *  Can't withdraw CTHU tokens and SOUL tokens.
    */
    function withdrawERC20Tokens(address tokenAddress, uint256 amount) public onlyOwner
    {
        require(tokenAddress != cthuAddress);
        require(tokenAddress != address(this));
        require(IERC20(tokenAddress).balanceOf(address(this)) >= amount);
        IERC20(tokenAddress).transfer(msg.sender, amount);
    }

    function setCthuAddress(address _cthuAddress) public onlyOwner returns(uint256) {
        cthuAddress = _cthuAddress;
        cthuToken = IERC20(_cthuAddress);
    }

    function updatingStakingReward(address account) public returns(uint256) {
        if (block.number > _stakedBalances[account].lastBlockChecked) {
            uint256 rewardBlocks = block.number
                                        .sub(_stakedBalances[account].lastBlockChecked);


            if (_stakedBalances[account].cthuStaked > 0) {
                _stakedBalances[account].rewards = _stakedBalances[account].rewards
                                                                            .add(
                                                                            _stakedBalances[account].cthuStaked
                                                                            .mul(rewardBlocks)
                                                                            / rewardsVar);
            }

            _stakedBalances[account].lastBlockChecked = block.number;

            emit Rewards(account, _stakedBalances[account].rewards);

        }
        return(_stakedBalances[account].rewards);
    }

    function getBlockNum() public view returns (uint256) {
        return block.number;
    }

    function getLastBlockCheckedNum(address _account) public view returns (uint256) {
        return _stakedBalances[_account].lastBlockChecked;
    }

    function getAddressStakeAmount(address _account) public view returns (uint256) {
        return _stakedBalances[_account].cthuStaked;
    }

    function setRewardsVar(uint256 _amount) public onlyOwner {
        rewardsVar = _amount;
    }

    function totalStakedSupply() public view returns (uint256) {
        return _totalCthuStaked;
    }

    function myRewardsBalance(address account) public view returns (uint256) {
        if (block.number > _stakedBalances[account].lastBlockChecked) {
            uint256 rewardBlocks = block.number
                                        .sub(_stakedBalances[account].lastBlockChecked);



            if (_stakedBalances[account].cthuStaked > 0) {
                return _stakedBalances[account].rewards
                                                .add(
                                                _stakedBalances[account].cthuStaked
                                                .mul(rewardBlocks)
                                                / rewardsVar);
            }
        }

    }

    function stake(uint256 amount) public updateStakingReward(msg.sender) {
      // Will prevent exchanges from staking;
        require(!blackListed[msg.sender]);
        _totalCthuStaked = _totalCthuStaked.add(amount);
        _stakedBalances[msg.sender].cthuStaked = _stakedBalances[msg.sender].cthuStaked.add(amount);
        cthuToken.transferFrom(msg.sender, address(this), amount);
        emit Staked(msg.sender, amount, _totalCthuStaked);
    }

    function withdraw(uint256 amount) public updateStakingReward(msg.sender) {
        _getReward(msg.sender);
        _totalCthuStaked = _totalCthuStaked.sub(amount);
        _stakedBalances[msg.sender].cthuStaked = _stakedBalances[msg.sender].cthuStaked.sub(amount);
        cthuToken.transfer(msg.sender, amount);
        emit Withdrawn(msg.sender, amount);
    }

    function _getReward(address receiver) internal {
       uint256 reward = _stakedBalances[receiver].rewards;
       _stakedBalances[receiver].rewards = 0;
       _mint(receiver, reward.mul(9) / 10);
       uint256 fundingPoolReward = reward.mul(1) / 10;
       _mint(cthuAddress, fundingPoolReward);
       emit Rewards(receiver, reward);
   }

   function getReward() public updateStakingReward(msg.sender) {
       _getReward(msg.sender);
   }

    // Will prevent exchanges from staking;
    function blackListAddress(address addr, bool blackList) external onlyOwner {
        blackListed[addr] = blackList;
    }

    function isBlackListed(address addr) public view returns (bool) {
        if (blackListed[addr] == true)
            return true;
        else
            return false;
    }
}
function withdrawERC20Tokens(address tokenAddress, uint256 amount) public onlyOwner
{
    require(tokenAddress != cthuAddress);
    require(tokenAddress != address(this));
    require(IERC20(tokenAddress).balanceOf(address(this)) >= amount);
    IERC20(tokenAddress).transfer(msg.sender, amount);
}

/* in case someone mistakenly sent ERC20 tokens to the contract.
 *  The owner can withdraw these tokens and refund the user.
 *  Can't withdraw CTHU tokens and SOUL tokens.
 */