contract BlfiNftTokenStaking is Ownable {

    /**
    * Libs for safeErc-20 and SafeMath
    */
    using SafeMath for uint256;
    using SafeERC20 for IERC20;


    /**
    * Public structs
    */

    /** Struct User */
    struct User {
        uint256 totalDeposited;
        uint256 startDate;
        uint256 lastTime;
    }

    /** Struct for Pool */
    struct Pool {
        uint256 totalSupplyLp;
        IERC20 lpToken;
    }

    IERC20 public coin;
    /** List of supported lp pools for staking */
    Pool[] public pools;
    /** HashMap of users that take part in staking */
    mapping(uint256 => mapping(address => User)) public userInfo;

    uint256 public timeLock = 15 days;


    /**
    * All events that uses in public transactions
    */
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event Claim(address indexed user, uint256 indexed pid, uint256 amount);

    //    /** Creates first epoch and init contract */
    constructor(IERC20 _coin) public {
        coin = _coin;
    }

    function changeCoin(IERC20 _coin) public onlyOwner {
        coin = _coin;
    }

    /** Method that used by user to deposit tokens.
    * Check for reward if user deposited before than calculate and send them reward
    * than transfer lp-tokens to contract and start staking
    */
    function deposit(uint pid, uint256 amount) public {
        User storage user = userInfo[pid][msg.sender];
        require(amount.div(10 ** 18) >= 10, "Didn't reach minimal");

        if (user.startDate == 0) {
            user.startDate = block.timestamp;
            user.lastTime = block.timestamp;
        }

        if (block.timestamp > (user.startDate + timeLock)) {
            claim(pid);
            // checking for rewards
        }
        user.totalDeposited = user.totalDeposited.add(amount);

        pools[pid].totalSupplyLp = pools[pid].totalSupplyLp.add(amount);
        pools[pid].lpToken.safeTransferFrom(address(msg.sender), address(this), amount);
        emit Deposit(msg.sender, pid, amount);
    }

    /** Method that used by user to withdraw tokens.
    * Check for reward if user deposited before than calculate and send them reward
    * than transfer lp-tokens to user
    */
    function withdraw(uint pid, uint256 amount) public {
        User storage user = userInfo[pid][msg.sender];

        require(user.totalDeposited >= amount, "BlfiNftTokenStaking: trying to withdraw more then are");
        require(block.timestamp > (user.startDate + timeLock), "Cooldown for 15 days");

        claim(pid);
        // checking for rewards

        user.totalDeposited = user.totalDeposited.sub(amount);
        pools[pid].totalSupplyLp = pools[pid].totalSupplyLp.sub(amount);
        pools[pid].lpToken.safeTransfer(address(msg.sender), amount);
        emit Withdraw(msg.sender, pid, amount);
    }

    /** Emergency withdraw use only in case of emergency.
    *   Nullify all rewards and withdraw lp-tokens
    */
    function emergencyWithdraw(uint pid) public {
        User storage user = userInfo[pid][msg.sender];
        require(block.timestamp > (user.startDate + timeLock), "Cooldown for 15 days");

        uint amount = user.totalDeposited;
        pools[pid].totalSupplyLp = pools[pid].totalSupplyLp.sub(amount);
        pools[pid].lpToken.safeTransfer(address(msg.sender), amount);

        user.totalDeposited = 0;
        user.lastTime = 0;
        emit EmergencyWithdraw(msg.sender, pid, amount);
    }

    /** Method that calculate user reward and withdraw it
    *  Calculate reward by missed epochs and withdraw eth
    */
    function claim(uint pid) public {
        User storage user = userInfo[pid][msg.sender];
        require(block.timestamp > (user.startDate + timeLock), "Cooldown for 15 days");
        uint amount = calcRewardWithoutPrecision(pid, msg.sender);
        user.lastTime = block.timestamp;
        safeCoinTransfer(msg.sender, amount.mul(10**18));
        emit Claim(msg.sender, pid, amount.mul(10**18));
    }


    /**
    * All admin functions
    */

    /** add new lp-pool to staking (can be called from deployer) */
    function add(IERC20 _lpToken) public onlyOwner {
        pools.push(Pool({
        lpToken : _lpToken,
        totalSupplyLp : 0
        }));
    }

    /** Emergency only. Withdraw eth from contract when something went wrong or for migration to new contract*/
    function withdrawTokens(uint256 amount) public onlyOwner {
        coin.transfer(msg.sender, amount);
    }

    /**
    *  View methods
    */

    /** Return length of pools list */
    function poolLength() external view returns (uint256) {
        return pools.length;
    }

    function calcReward(uint pid, address user) public view returns (uint256){
        User storage currentUser = userInfo[pid][user];

        uint256 shares = currentUser.totalDeposited.div(10).div(10 ** 18);

        uint price = getPricePerShare();
        uint256 timeDifference = block.timestamp.sub(currentUser.lastTime);
        uint256 reward = timeDifference.mul(10**18).div(price).mul(shares);
        return reward;
    }

    function calcRewardWithoutPrecision(uint pid, address user) public view returns (uint256){
        uint256 reward = calcReward(pid, user);
        return reward.div(10 ** 18);
    }

    function getPricePerShare() public view returns (uint256){
        return timeLock;
    }

    function safeCoinTransfer(address _to, uint256 _amount) internal {
        uint256 bal = coin.balanceOf(address(this));
        if (_amount > bal) {
            coin.transfer(_to, bal);
        } else {
            coin.transfer(_to, _amount);
        }
    }
}
function withdraw(uint pid, uint256 amount) public {
    User storage user = userInfo[pid][msg.sender];

    require(user.totalDeposited >= amount, "BlfiNftTokenStaking: trying to withdraw more then are");
    require(block.timestamp > (user.startDate + timeLock), "Cooldown for 15 days");

    claim(pid);
    // checking for rewards

    user.totalDeposited = user.totalDeposited.sub(amount);
    pools[pid].totalSupplyLp = pools[pid].totalSupplyLp.sub(amount);
    pools[pid].lpToken.safeTransfer(address(msg.sender), amount);
    emit Withdraw(msg.sender, pid, amount);
}

/** Method that used by user to withdraw tokens.
 * Check for reward if user deposited before than calculate and send them reward
 * than transfer lp-tokens to user
 */