contract LUCOVault is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    struct User {
        uint256 _LPDeposited;
        uint256 _rewardDebt;
    }

    event StakeCompleted(address _staker, uint256 _amount, uint256 _totalStaked, uint256 _time);
    event PoolUpdated(uint256 _blocksRewarded, uint256 _amountRewarded, uint256 _time);
    event RewardsClaimed(address _staker, uint256 _rewardsClaimed, uint256 _time);
    event EmergencyWithdrawOn(address _caller, bool _emergencyWithdraw, uint256 _time);
    event WithdrawCompleted(address _staker, uint256 _amount, uint256 _time);

    IERC20 public LPToken;
    IERC20 public LucoToken;
    address public rewardPool;
    uint256 public dailyReward;
    uint256 public accLucoPerShare;
    uint256 public lastRewardBlock;
    uint256 public totalStaked;

    bool public emergencyWithdraw = false;

    mapping(address => User) public userDetails;

    // Constructor will set the address of LUCO/ETH LP token
    constructor(address _LPToken, address _LucoToken, address _rewardPool, uint256 _dailyReward) Ownable() public {
        LPToken = IERC20(_LPToken);
        LucoToken = IERC20(_LucoToken);
        rewardPool = _rewardPool;
        lastRewardBlock = block.number;
        setDailyReward(_dailyReward);
        accLucoPerShare = 0;
    }

    // 6500 blocks in average day --- decimals * Luco balance of rewardPool / blocks / 10000 * dailyReward (in hundredths of %) = rewardPerBlock
    function getRewardPerBlock() public view returns(uint256) {
        return LucoToken.balanceOf(rewardPool).div(6500).div(10000).mul(dailyReward);
    }

    // % of reward pool to be distributed each day --- in hundredths of % 150 == 1.50%
    function setDailyReward(uint256 _dailyReward) public onlyOwner {
        dailyReward = _dailyReward;
    }

    // Function that will get balance of a certain stake
    function getUserBalance(address _staker) public view returns(uint256 _amountStaked) {
        return userDetails[_staker]._LPDeposited;
    }

    // Function that returns User's pending rewards
    function pendingRewards(address _staker) public view returns(uint256) {
        User storage user = userDetails[_staker];

        uint256 _accLucoPerShare = accLucoPerShare;

        if (block.number > lastRewardBlock && totalStaked != 0) {
            uint256 blocksToReward = block.number.sub(lastRewardBlock);
            uint256 lucoReward = blocksToReward.mul(getRewardPerBlock());
            _accLucoPerShare = _accLucoPerShare.add(lucoReward.mul(1e18).div(totalStaked));
        }

        return user._LPDeposited.mul(_accLucoPerShare).div(1e18).sub(user._rewardDebt);
    }

    // Function that updates LUCO/ETH LP pool
    function updatePool() public {
        if (block.number <= lastRewardBlock) {
            return;
        }

        if (totalStaked == 0) {
            lastRewardBlock = block.number;
            return;
        }

        uint256 blocksToReward = block.number.sub(lastRewardBlock);

        uint256 lucoReward = blocksToReward.mul(getRewardPerBlock());

        LucoToken.transferFrom(rewardPool, address(this), lucoReward);

        accLucoPerShare = accLucoPerShare.add(lucoReward.mul(1e18).div(totalStaked));
        lastRewardBlock = block.number;

        emit PoolUpdated(blocksToReward, lucoReward, now);
    }

    // Function that lets user stake LUCO/ETH LP
    function stakeLP(uint256 _amount) public {
        require(emergencyWithdraw == false, "emergency withdraw is on, cannot stake");
        require(_amount > 0, "Can not stake 0 LP tokens");
        require(LPToken.balanceOf(_msgSender()) >= _amount, "Do not have enough LP tokens to stake");

        updatePool();

        User storage user = userDetails[_msgSender()];

        if(user._LPDeposited > 0) {
            uint256 _pendingRewards = user._LPDeposited.mul(accLucoPerShare).div(1e18).sub(user._rewardDebt);

            if(_pendingRewards > 0) {
                LucoToken.transfer(_msgSender(), _pendingRewards);
                emit RewardsClaimed(_msgSender(), _pendingRewards, now);
            }
        }

        LPToken.transferFrom(_msgSender(), address(this), _amount);
        user._LPDeposited = user._LPDeposited.add(_amount);
        totalStaked = totalStaked.add(_amount);

        user._rewardDebt = user._LPDeposited.mul(accLucoPerShare).div(1e18);

        emit StakeCompleted(_msgSender(), _amount, user._LPDeposited, now);
    }

    // Function that will allow user to claim rewards
    function claimRewards() public {
        updatePool();

        User storage user = userDetails[_msgSender()];

        uint256 _pendingRewards = user._LPDeposited.mul(accLucoPerShare).div(1e18).sub(user._rewardDebt);
        require(_pendingRewards > 0, "No rewards to claim!");

        LucoToken.transfer(_msgSender(), _pendingRewards);

        user._rewardDebt = user._LPDeposited.mul(accLucoPerShare).div(1e18);

        emit RewardsClaimed(_msgSender(), _pendingRewards, now);
    }

    // Function that lets user unstake Luco/ETH LP in system
    function unstakeLP() public {
        require(emergencyWithdraw == true, "Can not withdraw");

        updatePool();

        User storage user = userDetails[_msgSender()];
        require(user._LPDeposited > 0, "User has no stake");

        uint256 _pendingRewards = user._LPDeposited.mul(accLucoPerShare).div(1e18).sub(user._rewardDebt);

        uint256 beingWithdrawn = user._LPDeposited;

        user._LPDeposited = 0;
        user._rewardDebt = 0;

        totalStaked = totalStaked.sub(beingWithdrawn);

        LPToken.transfer(_msgSender(), beingWithdrawn);
        LucoToken.transfer(_msgSender(), _pendingRewards);

        emit WithdrawCompleted(_msgSender(), beingWithdrawn, now);
        emit RewardsClaimed(_msgSender(), _pendingRewards, now);
    }

    // Function that will turn on emergency withdraws
    function turnEmergencyWithdrawOn() public onlyOwner() {
        require(emergencyWithdraw == false, "emergency withdrawing already allowed");
        emergencyWithdraw = true;
        emit EmergencyWithdrawOn(_msgSender(), emergencyWithdraw, now);
    }

}
function updatePool() public {
    if (block.number <= lastRewardBlock) {
        return;
    }

    if (totalStaked == 0) {
        lastRewardBlock = block.number;
        return;
    }

    uint256 blocksToReward = block.number.sub(lastRewardBlock);

    uint256 lucoReward = blocksToReward.mul(getRewardPerBlock());

    LucoToken.transferFrom(rewardPool, address(this), lucoReward);

    accLucoPerShare = accLucoPerShare.add(lucoReward.mul(1e18).div(totalStaked));
    lastRewardBlock = block.number;

    emit PoolUpdated(blocksToReward, lucoReward, now);
}

// Function that updates LUCO/ETH LP pool