contract Memestake is Ownable {
    using SafeMath for uint256;
    using SafeERC20 for IERC20; 
    
       
    struct UserInfo{
        uint256 amount; // How many tokens got staked by user.
        uint256 rewardDebt; // Reward debt. See Explanation below.

        // We do some fancy math here. Basically, any point in time, the amount of 
        // claimable MFUND by a user is:
        //
        //   pending reward = (user.amount * pool.accMFundPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accMfundPerShare` (and `lastRewardedBlock`) gets updated.
        //   2. User receives the pending reward sent to his/her address.
        //   3. User's `amount` gets updated.
        //   4. User's `rewardDebt` gets updated.
    }

    struct PoolInfo {
        IERC20 tokenContract; // Address of Token contract.
        uint256 allocPoint; // Allocation points from the pool
        uint256 lastRewardBlock; // Last block number where MFUND got distributed.
        uint256 accMfundPerShare; // Accumulated MFUND per share.
    }

    Memefund public mFundToken;
    uint256 public mFundPerBlock;

    PoolInfo[] public poolInfo;

    mapping (uint256 => mapping(address => UserInfo)) public userInfo;

    mapping (address => bool) isTokenContractAdded;

    uint256 public totalAllocPoint;

    uint256 public totalMfund;

    uint256 public startBlock;

    uint256 public endBlock;

    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event Claim(address indexed user, uint256 indexed pid, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);



    constructor(Memefund _mFundToken,
                uint256 _totalMfund,
                uint256 _startBlock,
                uint256 _endBlock) public{
                require(address(_mFundToken) != address(0), "constructor: _mFundToken must not be zero address!");
                require(_totalMfund > 0, "constructor: _totalMfund must be greater than 0");

                mFundToken = _mFundToken;
                totalMfund = _totalMfund;
                startBlock = _startBlock;
                endBlock = _endBlock;
                
                uint256 numberOfBlocksForFarming = endBlock.sub(startBlock);
                mFundPerBlock = totalMfund.div(numberOfBlocksForFarming);
    }
    
    /// @notice Returns the number of pools that have been added by the owner
    /// @return Number of pools
    function numberOfPools() external view returns(uint256){
        return poolInfo.length;
    }
    

    /// @notice Create a new LPT pool by whitelisting a new ERC20 token.
    /// @dev Can only be called by the contract owner
    /// @param _allocPoint Governs what percentage of the total LPT rewards this pool and other pools will get
    /// @param _tokenContract Address of the staking token being whitelisted
    /// @param _withUpdate Set to true for updating all pools before adding this one
    function add(uint256 _allocPoint, IERC20 _tokenContract, bool _withUpdate) public onlyOwner {
        require(block.number < endBlock, "add: must be before end");
        address tokenContractAddress = address(_tokenContract);
        require(tokenContractAddress != address(0), "add: _tokenConctract must not be zero address");
        require(isTokenContractAdded[tokenContractAddress] == false, "add: already whitelisted");

        if (_withUpdate) {
            massUpdatePools();
        }

        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolInfo.push(PoolInfo({
            tokenContract : _tokenContract,
            allocPoint : _allocPoint,
            lastRewardBlock : lastRewardBlock,
            accMfundPerShare : 0
        }));

        isTokenContractAdded[tokenContractAddress] = true;
    }

    /// @notice Update a pool's allocation point to increase or decrease its share of contract-level rewards
    /// @notice Can also update the max amount that can be staked per user
    /// @dev Can only be called by the owner
    /// @param _pid ID of the pool being updated
    /// @param _allocPoint New allocation point
    /// @param _withUpdate Set to true if you want to update all pools before making this change - it will checkpoint those rewards
    function set(uint256 _pid, uint256 _allocPoint, bool _withUpdate) public onlyOwner {
        require(block.number < endBlock, "set: must be before end");
        require(_pid < poolInfo.length, "set: invalid _pid");

        if (_withUpdate) {
            massUpdatePools();
        }

        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);

        poolInfo[_pid].allocPoint = _allocPoint;
    }

    /// @notice View function to see pending and unclaimed Mfunds for a given user
    /// @param _pid ID of the pool where a user has a stake
    /// @param _user Account being queried
    /// @return Amount of MFUND tokens due to a user
    function pendingRewards(uint256 _pid, address _user) external view returns (uint256) {
        require(_pid < poolInfo.length, "pendingMfund: invalid _pid");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];

        uint256 accMfundPerShare = pool.accMfundPerShare;
        uint256 lpSupply = pool.tokenContract.balanceOf(address(this));

        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 maxBlock = block.number <= endBlock ? block.number : endBlock;
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, maxBlock);
            uint256 mFundReward = multiplier.mul(mFundPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            accMfundPerShare = accMfundPerShare.add(mFundReward.mul(1e18).div(lpSupply));
        }

        return user.amount.mul(accMfundPerShare).div(1e18).sub(user.rewardDebt);
    }
    
    
    function claimRewards(uint256 _pid) public{
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accMfundPerShare).div(1e24).sub(user.rewardDebt);
        require(pending > 0, "harvest: no reward owed");
        user.rewardDebt = user.amount.mul(pool.accMfundPerShare).div(1e24);
        safeMfundTransfer(msg.sender, pending);
        emit Claim(msg.sender, _pid, pending);
    }
        /// @notice Cycles through the pools to update all of the rewards accrued
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }
    
    
    

    /// @notice Updates a specific pool to track all of the rewards accrued up to the TX block
    /// @param _pid ID of the pool
    function updatePool(uint256 _pid) public {
        require(_pid < poolInfo.length, "updatePool: invalid _pid");

        PoolInfo storage pool = poolInfo[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }

        uint256 tokenContractSupply = pool.tokenContract.balanceOf(address(this));
        if (tokenContractSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }

        uint256 maxEndBlock = block.number <= endBlock ? block.number : endBlock;
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, maxEndBlock);

        // No point in doing any more logic as the rewards have ended
        if (multiplier == 0) {
            return;
        }

        uint256 mFundReward = multiplier.mul(mFundPerBlock).mul(pool.allocPoint).div(totalAllocPoint);

        pool.accMfundPerShare = pool.accMfundPerShare.add(mFundReward.mul(1e18).div(tokenContractSupply));
        pool.lastRewardBlock = maxEndBlock;
    }

    /// @notice Where any user can stake their ERC20 tokens into a pool in order to farm $LPT
    /// @param _pid ID of the pool
    /// @param _amount Amount of ERC20 being staked
    function deposit(uint256 _pid, uint256 _amount) external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);

        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accMfundPerShare).div(1e18).sub(user.rewardDebt);
            if (pending > 0) {
                safeMfundTransfer(msg.sender, pending);
            }
        }

        if (_amount > 0) {
            pool.tokenContract.safeTransferFrom(address(msg.sender), address(this), _amount);
            user.amount = user.amount.add(_amount);
        }

        user.rewardDebt = user.amount.mul(pool.accMfundPerShare).div(1e18);
        emit Deposit(msg.sender, _pid, _amount);
    }

    /// @notice Allows a user to withdraw any ERC20 tokens staked in a pool
    /// @dev Partial withdrawals permitted
    /// @param _pid Pool ID
    /// @param _amount Being withdrawn
    function withdraw(uint256 _pid, uint256 _amount) external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        require(user.amount >= _amount, "withdraw: _amount not good");

        updatePool(_pid);

        uint256 pending = user.amount.mul(pool.accMfundPerShare).div(1e18).sub(user.rewardDebt);
        if (pending > 0) {
            safeMfundTransfer(msg.sender, pending);
        }

        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.tokenContract.safeTransfer(address(msg.sender), _amount);
        }

        user.rewardDebt = user.amount.mul(pool.accMfundPerShare).div(1e18);
        emit Withdraw(msg.sender, _pid, _amount);
    }

    /// @notice Emergency only. Should the rewards issuance mechanism fail, people can still withdraw their stake
    /// @param _pid Pool ID
    function emergencyWithdraw(uint256 _pid) external {
        require(_pid < poolInfo.length, "updatePool: invalid _pid");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        uint256 amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;

        pool.tokenContract.safeTransfer(address(msg.sender), amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }

    ////////////
    // Private /
    ////////////

    /// @dev Safe MFUND transfer function, just in case if rounding error causes pool to not have enough LPTs.
    /// @param _to Whom to send MFUND into
    /// @param _amount of MFUND to send
    function safeMfundTransfer(address _to, uint256 _amount) internal {
        uint256 mFundBal = mFundToken.balanceOf(address(this));
        if (_amount > mFundBal) {
            mFundToken.transfer(_to, mFundBal);
        } else {
            mFundToken.transfer(_to, _amount);
        }
    }

    /// @notice Return reward multiplier over the given _from to _to block.
    /// @param _from Block number
    /// @param _to Block number
    /// @return Number of blocks that have passed
    function getMultiplier(uint256 _from, uint256 _to) private view returns (uint256) {
        return _to.sub(_from);
    }
}
function emergencyWithdraw(uint256 _pid) external {
        require(_pid < poolInfo.length, "updatePool: invalid _pid");

        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];

        uint256 amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;

        pool.tokenContract.safeTransfer(address(msg.sender), amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }
/// @notice Emergency only. Should the rewards issuance mechanism fail, people can still withdraw their stake
/// @param _pid Pool ID