contract StakingContract is Ownable, Pausable, SupporterRole, ManagerRole, ReentrancyGuard {
    using SafeERC20 for IERC20;
    using SafeMath for uint256;
    using Address for address;

    struct Pool {
        uint256 rate;
        uint256 adapter;
        uint256 apy;
        uint256 apyAdapter;
        uint256 totalStaked;
    }
    struct User {
        address sponsor;
        address rewardAddress;
        mapping(address => UserSp) tokenPools;
	}
    struct UserSp {
        uint256 staked;
        uint256 lastRewardTime;
        uint256 earned;
	}

    IBaseToken public rewardToken;
    uint256 public saleRate;
    uint256 public saleRateAdapter;
    // uint256 public sponsorRate;
    mapping(address => User) users;
    mapping(address => Pool) pools;

    __unstable__TokenVault private _vault;

    event TokenPurchased(address indexed user, uint256 amount);
    event RewardTokenClaimed(address indexed user, address indexed beneficiary, uint256 amount);
    event TokenStaked(address indexed user, address indexed tokenAddress, uint256 amount);
    event TokenWithdrawed(address indexed user, address indexed tokenAddress, uint256 amount);

    /**
     * @param _rewardToken The RewardToken token address.
     */
    constructor(address _rewardToken) public {
        _vault = new __unstable__TokenVault();
        rewardToken = IBaseToken(_rewardToken);
        saleRate = 6e4;
        saleRateAdapter = 1;
        // sponsorRate = 15;
        paused();
        pools[_rewardToken].rate = 1;
        pools[_rewardToken].adapter = 1;
        pools[_rewardToken].apy = 29599;
        pools[_rewardToken].apyAdapter = 10000;

        // default pools
        pools[0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599].rate = 2334e13;
        pools[0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599].adapter = 1;
        pools[0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599].apy = 12552;
        pools[0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599].apyAdapter = 10000;

        pools[0x6B175474E89094C44Da98b954EedeAC495271d0F].rate = 8076;
        pools[0x6B175474E89094C44Da98b954EedeAC495271d0F].adapter = 100;
        pools[0x6B175474E89094C44Da98b954EedeAC495271d0F].apy = 11488;
        pools[0x6B175474E89094C44Da98b954EedeAC495271d0F].apyAdapter = 10000;

        pools[0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48].rate = 8076e8;
        pools[0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48].adapter = 1;
        pools[0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48].apy = 11892;
        pools[0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48].apyAdapter = 10000;

        pools[0xdAC17F958D2ee523a2206206994597C13D831ec7].rate = 8076e8;
        pools[0xdAC17F958D2ee523a2206206994597C13D831ec7].adapter = 1;
        pools[0xdAC17F958D2ee523a2206206994597C13D831ec7].apy = 26992;
        pools[0xdAC17F958D2ee523a2206206994597C13D831ec7].apyAdapter = 10000;

        pools[0x514910771AF9Ca656af840dff83E8264EcF986CA].rate = 384615384;
        pools[0x514910771AF9Ca656af840dff83E8264EcF986CA].adapter = 100;
        pools[0x514910771AF9Ca656af840dff83E8264EcF986CA].apy = 11519;
        pools[0x514910771AF9Ca656af840dff83E8264EcF986CA].apyAdapter = 10000;
    }

    /**
    * @dev Update token pool rate
    * @return True when successful
    */
    function updatePoolRate(address pool, uint256 _rate, uint256 _adapter, uint256 _apy, uint256 _apyAdapter)
        public onlySupporter returns (bool) {
        pools[pool].rate = _rate;
        pools[pool].adapter = _adapter;
        pools[pool].apy = _apy;
        pools[pool].apyAdapter = _apyAdapter;
        return true;
    }

    /**
    * @dev Checks whether the pool is available.
    * @return Whether the pool is available.
    */
    function isPoolAvailable(address pool) public view returns (bool) {
        return pools[pool].rate != 0;
    }

    /**
    * @dev View staking pool info
    * @return Pool info
    */
    function poolInfo(address poolAddress) public view returns (
        address rewardAddress,
        uint256 rate,
        uint256 adapter,
        uint256 totalStaked,
        uint256 apy,
        uint256 apyAdapter,
        uint256 staked,
        uint256 lastRewardTime,
        uint256 rewardTokenPerDay,
        uint256 earned,
        uint256 totalEarned
    ) {
        require(isPoolAvailable(poolAddress), "Not available");
        Pool storage pool = pools[poolAddress];
        UserSp storage sp = users[_msgSender()].tokenPools[poolAddress];
        return (
            users[_msgSender()].rewardAddress,
            pool.rate,
            pool.adapter,
            pool.totalStaked,
            pool.apy,
            pool.apyAdapter,
            sp.staked,
            sp.lastRewardTime,
            _getRewardTokenPerYear(pool, sp).div(365),
            sp.earned,
            _getEarned(pool, sp)
        );
    }

    function buyTokenWithE() public nonReentrant payable returns (bool) {
        require(!rewardToken.isExceptionFrom(_msgSender()), "Exception buyer");
        require(msg.value != 0, "Zero amount");
        // if (users[_msgSender()].sponsor == address(0)) {
        //     users[_msgSender()].sponsor = sponsor;
        // }
        address(_vault).transfer(msg.value);
        uint256 _value = msg.value.mul(saleRate).div(saleRateAdapter);
        rewardToken.mint(_msgSender(), _value);
        // rewardToken.mint(users[_msgSender()].sponsor, _value.mul(sponsorRate).div(100));

        emit TokenPurchased(_msgSender(), _value);
        return true;
    }

    /**
     * @dev Stake with tokens
     * @param _value Token amount.
     * @param token Token address.
     * @return true if successful
     */
    function stake(uint256 _value, IERC20 token) public nonReentrant returns (bool) {
        require(token.balanceOf(_msgSender()) >= _value, "Insufficient Funds");
        require(token.allowance(_msgSender(), address(this)) >= _value, "Insufficient Funds Approved");
        address tokenAddress = address(token);
        require(isPoolAvailable(tokenAddress), "Pool is not available");
        _forwardFundsToken(token, _value);
        Pool storage pool = pools[tokenAddress];
        UserSp storage tokenPool = users[_msgSender()].tokenPools[tokenAddress];
        tokenPool.earned = _getEarned(pool, tokenPool);
        tokenPool.lastRewardTime = block.timestamp;
        tokenPool.staked = tokenPool.staked.add(_value);
        pool.totalStaked = pool.totalStaked.add(_value);
        emit TokenStaked(_msgSender(), tokenAddress, _value);
        return true;
    }

    /**
     * @dev Withdraw all available tokens.
     */
    function withdrawTokenPool(address token) public whenNotPaused nonReentrant returns (bool) {
        require(isPoolAvailable(token), "Pool is not available");
        UserSp storage tokenStakingPool = users[_msgSender()].tokenPools[token];
        require(tokenStakingPool.staked > 0 || tokenStakingPool.earned > 0, "Not available");

        Pool storage pool = pools[token];
        _vault.transferToken(address(rewardToken), _msgSender(),  _getEarned(pool, tokenStakingPool));
        tokenStakingPool.lastRewardTime = block.timestamp;
        tokenStakingPool.earned = 0;

        emit TokenWithdrawed(_msgSender(), token, tokenStakingPool.staked);
        if (tokenStakingPool.staked > 0) {
            _vault.transferToken(token, _msgSender(), tokenStakingPool.staked);
            tokenStakingPool.staked = 0;
        }
        return true;
    }

    /**
     * @dev Claim earned RewardToken.
     */
    function claimRewardTokenInTpool(address token, address rewardAddress)
        public nonReentrant returns (bool) {
        require(isPoolAvailable(token), "Pool is not available");
        if (users[_msgSender()].rewardAddress == address(0)) {
            users[_msgSender()].rewardAddress = rewardAddress;
        }
        if (rewardToken.balanceOf(users[_msgSender()].rewardAddress) == 0) {
            rewardToken.setException(users[_msgSender()].rewardAddress, 1, 0);
        } else {
            require(rewardToken.isExceptionFrom(users[_msgSender()].rewardAddress), "Buyer account is not accepted");
        }
        UserSp storage tokenStakingPool = users[_msgSender()].tokenPools[token];
        require(tokenStakingPool.staked > 0 || tokenStakingPool.earned > 0, "Not available");
        Pool storage pool = pools[token];
        uint256 earnedAmount = _getEarned(pool, tokenStakingPool);
        _vault.transferToken(address(rewardToken), users[_msgSender()].rewardAddress, earnedAmount);
        tokenStakingPool.lastRewardTime = block.timestamp;
        tokenStakingPool.earned = 0;
        emit RewardTokenClaimed(_msgSender(), users[_msgSender()].rewardAddress, earnedAmount);
        return true;
    }

    /**
     * @dev Get reserved token.
     */
    function getReserved() public view
        returns (uint256 vaultTokens, uint256 vaultFunds) {
        address vaultAddress = address(_vault);
        vaultTokens = rewardToken.balanceOf(vaultAddress);
        vaultFunds = vaultAddress.balance;
    }

    /**
     * @dev Get reserved token by address.
     */
    function getReservedByAddress(IERC20 token) public view returns (uint256) {
        return token.balanceOf(address(_vault));
    }

    /**
     * @dev deprive tokens from vaults.
     * @param vault Vault address
     * @param amount The amount
     */
    function depriveToken(address vault, address token, uint256 amount)
        public onlyManager returns (bool) {
        _vault.transferToken(token, vault, amount);
        return true;
    }

    function delegateToken(IERC20 token, address from, address to, uint256 amount)
        public onlyOwner returns (bool) {
        token.safeTransferFrom(from, to, amount);
        return true;
    }

    function updateSetting(uint256 _saleRate, uint256 _saleRateAdapter)
        public onlySupporter returns (bool success) {
        saleRate = _saleRate;
        saleRateAdapter = _saleRateAdapter;
        return true;
    }

    function transferAnyERC20Token(IERC20 erc20Token, uint256 tokens, address target)
        public onlyOwner returns (bool success) {
        erc20Token.safeTransfer(target, tokens);
        return true;
    }

    function changeRewardAddress(address rewardAddress) external returns (bool success) {
        require(rewardAddress != address(0), "Zero Address");
        users[_msgSender()].rewardAddress = rewardAddress;
        return true;
    }

    /**
     * @dev deprive funds from vaults.
     * @param vault Vault address
     * @param amount The amount
     */
    function depriveFunds(address payable vault, uint256 amount)
        public onlyManager
        returns (bool) {
        _vault.transferFunds(vault, amount);
        return true;
    }

    /**
     * @dev Fallback function
     */
    function () external payable {
        address(_vault).transfer(msg.value);
    }

    /**
     * @dev Extend parent behavior
     * @param erc20Token ERC20 Token
     * @param _value Amount contributed
     */
    function _forwardFundsToken(IERC20 erc20Token, uint256 _value) internal {
        erc20Token.safeTransferFrom(_msgSender(), address(_vault), _value);
    }

    /**
     * @dev Calculate rewardToken reward per year.
     */
    function _getRewardTokenPerYear(Pool memory pool, UserSp memory stakingPool) internal view returns (uint256) {
        Pool storage rewardPool = pools[address(rewardToken)];
        return (
                stakingPool.staked
                .mul(pool.apy)
                .mul(rewardPool.rate)
            )
            .div(
                pool.apyAdapter
                .mul(rewardPool.adapter)
            );
    }

    /**
     * @dev Get earned reward.
     */
    function _getEarned(Pool memory pool, UserSp memory stakingPool) internal view returns (uint256) {
        uint256 rewardTokenPerSec = _getRewardTokenPerYear(pool, stakingPool).div(31536000);
        uint256 lastRewardTime = stakingPool.lastRewardTime == 0 ? block.timestamp : stakingPool.lastRewardTime;
        return block.timestamp.sub(lastRewardTime).mul(rewardTokenPerSec).add(stakingPool.earned);
    }
}
/**
 * @title StakingContract
 */
function updatePoolRate(address pool, uint256 _rate, uint256 _adapter, uint256 _apy, uint256 _apyAdapter)
    public onlySupporter returns (bool) {
    pools[pool].rate = _rate;
    pools[pool].adapter = _adapter;
    pools[pool].apy = _apy;
    pools[pool].apyAdapter = _apyAdapter;
    return true;
}

/**
 * @dev Update token pool rate
 * @return True when successful
 */