contract EHashToken is EHashBaseToken, ReentrancyGuard{
    using SafeMath for uint256;
    using Address for address payable;

    // @dev a revenue share multiplier to avert divison downflow.
    uint256 internal constant REVENUE_SHARE_MULTIPLIER = 1e18;
    
    // @dev update period in secs for revenue distribution.
    uint256 public updatePeriod = 30;

    // @dev for tracking of holders' claimable revenue.
    mapping (address => uint256) internal _revenueBalance;
    
    // @dev for tracking of holders' claimed revenue.
    mapping (address => uint256) internal _revenueClaimed;
    
    /// @dev RoundData always kept for each round.
    struct RoundData {
        uint256 accTokenShare;     // accumulated unit ehash share for each settlement.
        uint256 roundEthers;    // total ethers received in this round. 
    }
    
    /// @dev round index mapping to RoundData.
    mapping (uint => RoundData) private _rounds;
    
    /// @dev mark token holders' highest settled revenue round.
    mapping (address => uint) private _settledRevenueRounds;
    
    /// @dev a monotonic increasing round index, STARTS FROM 1
    uint private _currentRound = 1;
    
    /// @dev expected next update time
    uint private _nextUpdate = block.timestamp + updatePeriod;

    /// @dev manager's address
    address payable managerAddress;

    /// @dev Revenue Claiming log
    event Claim(address indexed account, uint256 amount);
    
    /// @dev Update log
    event Update(uint256 AccTokenShare, uint256 RoundEthers);

    /// @dev Received log
    event Received(address indexed account, uint256 amount);
    
    /// @dev Settle Log
    event Settle(address indexed account, uint LastSettledRound, uint256 Revenue);
    
    constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) 
        EHashBaseToken(_name, _symbol, _decimals, _initialSupply)
        public {
    }
    
    // @dev tryUpdate function
    modifier tryUpdate() {
        if (block.timestamp > _nextUpdate) {
            update();
        }
        _;
    }
    
    /**
     * @notice set manager's address
     */
    function setManagerAddress(address payable account) external onlyOwner {
        require (account != address(0), "0 address");
        managerAddress = account;
    }
    
    /** 
     * @notice set update period
     */
    function setUpdatePeriod(uint nsecs) external onlyOwner {
        require (nsecs > 0," period should be positive");
        updatePeriod = nsecs;
    }
    
    /**
     * @notice get manager's address
     */
     function getManagerAddress() external view returns(address) {
         return managerAddress;
     }
    
    /**
     * @notice default ether receiving function 
     */
    receive() external payable tryUpdate {
        _rounds[_currentRound].roundEthers += msg.value;
        emit Received(msg.sender, msg.value);
    }

    /**
     * @notice check unclaimed revenue 
     */
    function checkUnclaimedRevenue(address account) public view returns(uint256 revenue) {
        uint256 accountTokens = balanceOf(account);
        uint lastSettledRound = _settledRevenueRounds[account];

        uint256 roundRevenue = _rounds[_currentRound-1].accTokenShare.sub(_rounds[lastSettledRound].accTokenShare)
                                    .mul(accountTokens)
                                    .div(REVENUE_SHARE_MULTIPLIER);  // NOTE: div by REVENUE_SHARE_MULTIPLIER

        return _revenueBalance[account].add(roundRevenue);
    }
    
    /**
     * @notice get user's life-time gain
     */
    function checkTotalRevenue(address account) external view returns(uint256 revenue) {
        return checkUnclaimedRevenue(account) + _revenueClaimed[account];
    }
    
    /**
     * @notice get round N information
     */
    function getRoundData(uint round) external view returns(uint256 accTokenShare, uint256 roundEthers) {
        return (_rounds[round].accTokenShare, _rounds[round].roundEthers);
    }
    
    /**
     * @notice get current round
     */
    function getCurrentRound() external view returns(uint round) {
        return _currentRound;
    }

    /**
     * @notice token holders claim revenue
     */
    function claim() external whenNotPaused {
        // settle un-distributed revenue in rounds to _revenueBalance;
        _settleRevenue(msg.sender);

        // revenue balance change
        uint256 revenue = _revenueBalance[msg.sender];
        _revenueBalance[msg.sender] = 0; // zero sender's balance
        
        // transfer ETH to msg.sender
        msg.sender.sendValue(revenue);
        
        // record claimed revenue
        _revenueClaimed[msg.sender] += revenue;
        
        // log
        emit Claim(msg.sender, revenue);
    }
    
     /**
     * @notice settle revenue in rounds to _revenueBalance, 
     * settle revenue happens before any token exchange such as ERC20-transfer,mint,burn,
     * and active claim();
     */
    function _settleRevenue(address account) internal tryUpdate {
        uint256 accountTokens = balanceOf(account);
        uint lastSettledRound = _settledRevenueRounds[account];
        
        uint256 roundRevenue = _rounds[_currentRound-1].accTokenShare.sub(_rounds[lastSettledRound].accTokenShare)
                                    .mul(accountTokens)
                                    .div(REVENUE_SHARE_MULTIPLIER);  // NOTE: div by REVENUE_SHARE_MULTIPLIER

        // mark highest settled round revenue claimed.
        _settledRevenueRounds[account] = _currentRound - 1;
        // set back balance to storage
        _revenueBalance[account] += roundRevenue;
        // log
        emit Settle(account, _settledRevenueRounds[account], _revenueBalance[account]);
    }
    
    /**
     * @dev See {ERC20-_beforeTokenTransfer}.
     *
     * Requirements:
     *
     * - the contract must not be paused.
     * - accounts must not trigger the locked `amount` during the locked period.
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
        require(!paused(), "EHash: token transfer while paused");
        
        // handle revenue settlement before token number changes.
        if (from != address(0)) {
            _settleRevenue(from);
        }
        
        if (to != address(0)) {
            _settleRevenue(to);
        }
        
        super._beforeTokenTransfer(from, to, amount);
    }
    
    
    /**
     * @dev update function to settle rounds shifting and rounds share.
     */
    function update() internal nonReentrant {
        require (block.timestamp > _nextUpdate, "period not expired");
        require (managerAddress != address(0), "manager address has not set");
        
        // rules:
        // 80% of ethers in this round belongs to all token holders
        //  roundEthers * 80% / totalSupply()
        // and, 20% of ethers belongs to manager
        uint256 roundEthers = _rounds[_currentRound].roundEthers;
        uint256 managerRevenue = roundEthers.mul(20).div(100);
        uint256 holdersEthers = roundEthers.sub(managerRevenue);
        
        // send to manager
        managerAddress.sendValue(managerRevenue);
        
        // substract manager's revenue
        
        // set accmulated holder's share
        _rounds[_currentRound].accTokenShare = _rounds[_currentRound-1].accTokenShare
                                                .add(
                                                    holdersEthers
                                                    .mul(REVENUE_SHARE_MULTIPLIER) // NOTE: multiplied by REVENUE_SHARE_MULTIPLIER here
                                                    .div(totalSupply())
                                                );

        // log                                            
        emit Update(_rounds[_currentRound].accTokenShare, _rounds[_currentRound].roundEthers);
        
        // next round setting                                 
        _currentRound++;
        _nextUpdate = block.timestamp + updatePeriod;
    }
}
function setUpdatePeriod(uint nsecs) external onlyOwner {
    require (nsecs > 0," period should be positive");
    updatePeriod = nsecs;
}

/** 
 * @notice set update period
 */