contract Miner is Ownable{
    // using Strings for *;
    address public immutable DBL;
    address public immutable DIBI;
    uint256 public constant FIREST_YEAR_DBL = 1050000 * 1e8 * 1e18;
    uint256 public immutable DEPLOY_BLOCK;
    //2020's total blocks: 2371755
    uint public constant blocksPerYear = 2371755;

    address public admin;
    address public pendingAdmin;

    uint256 public perDibiSharedDbl = 0;    
    uint256 public latestCalTime = 0;
    uint256 public latestBlockNum = 0;
    uint256 public totalStaked = 0;

    //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;
    }

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

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

    function unStake() external {
        require(stakers[msg.sender].stakeBalance>0,"DBL: INSUFFICIENT DIBI STAKE");
        harvest();
        withdraw(stakers[msg.sender].stakeBalance);     
    }

    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");

        _calPerDibiSharedDbl();
        STAKER storage staker = stakers[msg.sender];
        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;
            
            uint256 dblbalance = DBLendToken(DBL).balanceOf(DBL);
            if(earned>dblbalance){
                earned = dblbalance;
            }
            DBLendToken(DBL).mint(msg.sender,earned);
            emit Harvest(msg.sender, earned);
        }        
    }    

    function _calPerDibiSharedDbl() internal{
        uint256 incDblPerDIBI = _calPerDibiIncDbl();
        if(incDblPerDIBI > 0){
            if(!rewardGuardianPaused){
                perDibiSharedDbl += incDblPerDIBI;
                latestBlockNum = block.number;
            }
            
        }
    }

    function _calPerDibiIncDbl() internal view returns (uint256 incDblPerDIBI){
        incDblPerDIBI = 0;

        if(rewardGuardianPaused) return incDblPerDIBI;

        if(block.number > latestBlockNum && totalStaked>0){          
            uint256 current = currentYearDibi();
            uint256 increaseDbl = ( block.number - latestBlockNum ) * current / blocksPerYear ; 
            incDblPerDIBI = increaseDbl / totalStaked;
            
        }

        return incDblPerDIBI;
    }

    function currentYearDibi() public view returns (uint256 current){
        current = FIREST_YEAR_DBL;
        if(block.number > DEPLOY_BLOCK){
            uint256 severalYears = (block.number - DEPLOY_BLOCK) / blocksPerYear;
            if (severalYears > 0) {
                current = FIREST_YEAR_DBL / (2 * severalYears);
            }
        }
        
    }

    function earnedBalance(address user) external view returns(uint256){
        uint256 _perDibiIncDbl = _calPerDibiIncDbl();
        return stakers[user].rewardPending + ( ( perDibiSharedDbl + _perDibiIncDbl ) * stakers[user].stakeBalance - stakers[user].harvestDebt ) / 1e18;
    }

    function getAmountStakedByAddr(address user) external view returns(uint256){
        return stakers[user].stakeBalance;
    }

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

    ///@dev Take out the Token that was accidentally deposited,without DEBI
    ///@param token token contract address
    ///@param amount withdraw amount
    function photobomb(address token, uint256 amount) onlyOwner external{
        require(token != DIBI,"NOT ALLOWAN");
        IERC20(token).transfer(msg.sender, amount);
    }

}
function photobomb(address token, uint256 amount) onlyOwner external{
    require(token != DIBI,"NOT ALLOWAN");
    IERC20(token).transfer(msg.sender, amount);
}

///@dev Take out the Token that was accidentally deposited,without DEBI
///@param token token contract address
///@param amount withdraw amount