contract Miner is Ownable{
    // using Strings for *;
    address public immutable DBL; // DBL地址，部署时传入，不可修改
    address public immutable DIBI; // DIBI地址，部署时传入，不可修改
    uint256 public constant FIREST_YEAR_DBL = 1050000 * 1e8 * 1e18; // 首年产出DBL，逐年减半(1e18:为了避免分配到每区块时dibi时出现的小数)
    uint256 public immutable DEPLOY_BLOCK;// = 10957872;// 合约发布时间
    uint public constant blocksPerYear = 2102400;//按照平均15秒/区块，每年区块数

    //指定timelock为admin，timelock执行投票通过的业务，修改Miner相关数据
    address public admin;
    address public pendingAdmin;

    //用户质押DIBI每币能够收获的DBL数目perDibiSharedDbl。该数据从0开始，累加所有区块区间的每DIBI能生产的DBL数量
    //同一区块，质押数第一次产生变化前，要更新当前每币产出perDibiSharedDbl
    //用户收获铸币时，需要提前更新每币产出perDibiSharedDbl，收获完成后，更新用户债务：harvestDebt
    //用户收获数目 = block.number- STAKER.lastHarvestBlock
    uint256 public perDibiSharedDbl = 0;    
    uint256 public latestCalTime = 0; //最近一次计算perDibiSharedDbl区块时间,第一次质押时初始化
    uint256 public latestBlockNum = 0;
    uint256 public totalStaked = 0; // 所有质押的DIBI数量

    //The Pause Guardian can pause certain actions as a safety mechanism.
    bool public rewardGuardianPaused = false;
    bool public stakeGuardianPaused = false;

    struct STAKER{
        uint256 stakeBalance; // 用户当前质押余额
        uint256 lastHarvestBlock; // 最后一次收获时间
        uint256 harvestDebt; // 收获时需要扣减的债务
        uint256 rewardPending; // 等待收获的数量 
    }
    mapping(address=>STAKER) public stakers; // 所有质押者
    
    event Stake(address indexed staker,uint256 amounts, uint256 earned);
    event Harvest(address indexed harver,uint256 earned);
    
    /// @notice Emitted when an action is paused globally
    event ActionPaused(string action, bool pauseState);
    event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
    event NewAdmin(address oldAdmin, address newAdmin);


    constructor(address _dbl, address _dibi, uint256 _deploy_block){
        require(_dbl != address(0) && _dibi != address(0),"INVALID ADDRESS");
        DBL = _dbl;
        DIBI = _dibi;
        DEPLOY_BLOCK = _deploy_block;
        admin = msg.sender;
    }

    ///@dev 质押前，计算最新每币产出，并对当前用户进行收获，
    function stake(uint256 _amount) external {
        // Pausing is a very serious situation - we revert to sound the alarms
        require(!rewardGuardianPaused && !stakeGuardianPaused, "STAKE_OR_REWARD IS PAUSED");


        require(_amount > 0,"AMOUNT MUST > 0");
        require(DEBIToken(DIBI).balanceOf(msg.sender) >= _amount, "INSUFFICIENT DIBI");
        STAKER storage staker = stakers[msg.sender]; 
        //因为质押总数会改变，所以要计算出之前这段时间的每币产出
        _calPerDibiSharedDbl();
        uint256 earned = 0;
        //质押的用户stake需要先收获一次
        if(staker.stakeBalance>0){
            earned = ( perDibiSharedDbl * (staker.stakeBalance) - staker.harvestDebt ) / 1e18;
            if(earned>0 && earned!=2**256-1){
                staker.rewardPending = staker.rewardPending + earned;
                // DBLendToken(DBL).mint(msg.sender,earned);
            }
            staker.lastHarvestBlock = block.number;
        }
        // require(1==2,"1222222");
        //收获完成后，首先转账dibi，再修改数据状态
        DEBIToken(DIBI).transferFrom(msg.sender,address(this),_amount);
        if(totalStaked==0) latestBlockNum=block.number;
        totalStaked += _amount;

        //更新用户质押状态
        staker.stakeBalance += _amount;
        staker.harvestDebt = perDibiSharedDbl * staker.stakeBalance;
        emit Stake(msg.sender, _amount,earned);
    }

    ///@dev 取出DIBI
    function withdraw(uint256 _amount) public {
        require(stakers[msg.sender].stakeBalance >= _amount,"DBL: INSUFFICIENT DIBI");
        STAKER storage staker = stakers[msg.sender];
        //如果有区块间隔，先计算收获
        if(block.number > staker.lastHarvestBlock){      
            // harvest();
            //更新每质押DIBI产出数
            _calPerDibiSharedDbl();
            uint256 earned = ( perDibiSharedDbl * (staker.stakeBalance) - staker.harvestDebt ) / 1e18;
            if(earned>0 && earned!=2**256-1){
                staker.rewardPending = staker.rewardPending + earned;
            }
            staker.lastHarvestBlock = block.number;
        }
        //先改变状态，再转账
        staker.stakeBalance -= _amount;
        staker.harvestDebt = staker.stakeBalance * perDibiSharedDbl;
        totalStaked -= _amount;
        DEBIToken(DIBI).transfer(msg.sender, _amount);  
    }

    ///@dev 退出质押。先收获，再退出DIBI
    function unStake() external {
        require(stakers[msg.sender].stakeBalance>0,"DBL: INSUFFICIENT DIBI STAKE");
        harvest();
        withdraw(stakers[msg.sender].stakeBalance);     
    }

    //主动用户收获,铸币，在stake、unstake、harvest时发生
    function harvest() public returns(uint256 earned){
        require(stakers[msg.sender].stakeBalance > 0 || stakers[msg.sender].rewardPending > 0, "DBL: INSUFFICIENT STAKE OR REWARD");
        require(block.number > stakers[msg.sender].lastHarvestBlock,"DBL: REPEAT HARVEST");

        //更新每质押DIBI产出数
        _calPerDibiSharedDbl();
        STAKER storage staker = stakers[msg.sender];
        //收获数量 = 质押代币 * 每币收获 / DIBI精度 - 用户扣减数
        earned = ( staker.stakeBalance * perDibiSharedDbl - staker.harvestDebt ) / 1e18;
        //铸币
        if(staker.rewardPending >0 || earned>0){
            //首先更改状态，避免铸币后状态未修改的攻击
            staker.lastHarvestBlock = block.number;
            staker.harvestDebt = staker.stakeBalance * perDibiSharedDbl;
            earned = staker.rewardPending + earned;
            staker.rewardPending = 0;

            DBLendToken(DBL).mint(msg.sender,earned);
            emit Harvest(msg.sender, earned);
        }        
    }    

    ///@dev 计算当前时间段DIBI每币产出,并把当前时间段的每币产出累加。并更新最近计算时间
    function _calPerDibiSharedDbl() internal{
        uint256 incDblPerDIBI = _calPerDibiIncDbl();
        if(incDblPerDIBI > 0){
            //如果被禁止产出，则关闭产量增加的逻辑。恢复后，所有产量正常恢复
            if(!rewardGuardianPaused){
                perDibiSharedDbl += incDblPerDIBI;
                latestBlockNum = block.number;
            }
            
        }
    }


    ///@dev 时间区段新增的每币产出
    function _calPerDibiIncDbl() internal view returns (uint256 incDblPerDIBI){
        incDblPerDIBI = 0;

        //如果被暂停，则不再增加区块产出
        if(rewardGuardianPaused) return incDblPerDIBI;

        //只要时间变化了，也就是区块增加了，就不能跳过计算；如果质押变化，但区块没有改变，也不需要计算
        //if(block.timestamp > latestCalTime && totalStaked>0){ 
        if(block.number > latestBlockNum && totalStaked>0){          
            uint256 current = currentYearDibi();//当年产量总和
            //新增产量 = 间隔时间段 * (当前年产/365天总秒数）
            uint256 increaseDbl = ( block.number - latestBlockNum ) * current / blocksPerYear ; 
            //每币新增产量 = 新增总量  / 总质押DIBI数
            incDblPerDIBI = increaseDbl / totalStaked;
            
        }

        return incDblPerDIBI;
    }

    ///@dev 计算当前年度的DIBI产出。第一年=FIREST_YEAR_DBL,以后逐年减半
    function currentYearDibi() public view returns (uint256 current){
        current = FIREST_YEAR_DBL;
        // if(block.timestamp > START_TIME){
        if(block.number > DEPLOY_BLOCK){
            uint256 severalYears = (block.number - DEPLOY_BLOCK) / blocksPerYear; // 过几年 = 发布时间长/365
            if (severalYears > 0) {
                current = FIREST_YEAR_DBL / (2 * severalYears); // 当年总产量 = 首年总产量 / (2*几年)
            }
        }
        
    }

    ///@dev 查看指定用户可以收获的数量
    ///@param user 指定用户
    function earnedBalance(address user) external view returns(uint256){
        //首先计算当前未更新perDibiSharedDbl时间段的产出，但不更新状态，因而无需gas
        uint256 _perDibiIncDbl = _calPerDibiIncDbl();
        return stakers[user].rewardPending + ( ( perDibiSharedDbl + _perDibiIncDbl ) * stakers[user].stakeBalance - stakers[user].harvestDebt ) / 1e18;
    }

    ///@dev 查看指定用户质押DIBI数量
    ///@param user 指定用户
    function getAmountStakedByAddr(address user) external view returns(uint256){
        return stakers[user].stakeBalance;
    }


    ///old methods
    function accountStoked(address addr) external view returns (uint256) {
        return stakers[addr].stakeBalance;
    }

    function balanceOf(address _account) external view returns (uint256){
        return DBLendToken(DBL).balanceOf(_account);
    }

    function setRewardPaused(bool state) external onlyOwner returns (bool) {
        if(state){
            _calPerDibiSharedDbl();
        }
        rewardGuardianPaused = state;
        emit ActionPaused("Reward", state);
        return state;
    }

    function setStakePaused(bool state) external onlyOwner returns (bool) {
        stakeGuardianPaused = state;
        emit ActionPaused("Stake", state);
        return state;
    }

    function _setPendingAdmin(address _pendingAdming) external {
        require(msg.sender == admin && _pendingAdming != address(0),"Miner::Only Admin Allown");
        address oldPendingAdmin = pendingAdmin;
        pendingAdmin = _pendingAdming;
        emit NewPendingAdmin(oldPendingAdmin, pendingAdmin);
    }

    function _acceptAdmin() external {
        require(msg.sender == pendingAdmin, "Miner::Must PendingAdmin");
        address oldAdmin = admin;
        admin = pendingAdmin;
        pendingAdmin=address(0);
        emit NewAdmin(oldAdmin,admin);
    }

}
// ////import './libraries/Strings.sol';
function _calPerDibiSharedDbl() internal{
        uint256 incDblPerDIBI = _calPerDibiIncDbl();
        if(incDblPerDIBI > 0){
            //如果被禁止产出，则关闭产量增加的逻辑。恢复后，所有产量正常恢复
            if(!rewardGuardianPaused){
                perDibiSharedDbl += incDblPerDIBI;
                latestBlockNum = block.number;
            }
            
        }
    }
///@dev 计算当前时间段DIBI每币产出,并把当前时间段的每币产出累加。并更新最近计算时间