contract CHIZchef is Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    using SafeERC20 for IERC20;

    // Info of each user.
    struct UserInfo {
        uint256 amount;         // How many LP tokens the user has provided.
        uint256 rewardDebt;     // Reward debt. See explanation below.
        //
        // We do some fancy math here. Basically, any point in time, the amount of CHIZ
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accChizPerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accChizPerShare` (and `lastRewardBlock`) 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.
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken;           // Address of LP token contract.
        uint256 allocPoint;       // How many allocation points assigned to this pool. CHIZes to distribute per block.
        uint256 lastRewardBlock;  // Last block number that CHIZes distribution occurs.
        uint256 accChizPerShare;   // Accumulated CHIZes per share, times 1e36. See below.
        uint16 depositFeeBP;      // Deposit fee in basis points
	    uint256 lpSupply;
        bool isPoolPrivileged; 
    }

    // The CHIZ TOKEN!
    IERC20 public chizToken;
    IERC721 public nftPrivilegeContractOne;
    IERC721 public nftPrivilegeContractTwo;
    address public devAddress;
    address public feeAddress;
    uint256 constant max_chiz_reward_amount = 8000000 ether;

    // CHIZ tokens created per block.
    uint256 public chizPerBlock = 7.71 ether;
    // CHIZ tokens distributed so far
    uint256 public chizDistributedAmount = 0;

    // Info of each pool.
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens.
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    // Total allocation points. Must be the sum of all allocation points in all pools.
    uint256 public totalAllocPoint = 0;
    // The block number when CHIZ mining starts.
    uint256 public startBlock;
    // The block number when farming ends.
    uint256 public endBlock;

    uint256 public constant MAXIMUM_EMISSION_RATE = 10 ether;

    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 SetFeeAddress(address indexed user, address indexed newAddress);
    event SetDevAddress(address indexed user, address indexed newAddress);
    event UpdateEmissionRate(address indexed user, uint256 chizPerBlock);
    event PoolAdd(address indexed user, IERC20 lpToken, uint256 allocPoint, uint256 lastRewardBlock, uint16 depositFeeBP);
    event PoolSet(address indexed user, IERC20 lpToken, uint256 allocPoint, uint256 lastRewardBlock, uint16 depositFeeBP);
    event UpdateStartBlock(address indexed user, uint256 startBlock);
    constructor(
        IERC721 _nftPrivilegeContractOne,
        IERC721 _nftPrivilegeContractTwo,
        IERC20 _chizToken,
        uint256 _startBlock,
        address _devAddress,
        address _feeAddress
        
    ) public {
        nftPrivilegeContractOne = _nftPrivilegeContractOne;
        nftPrivilegeContractTwo = _nftPrivilegeContractTwo;
        chizToken = _chizToken;
        startBlock = _startBlock;
        devAddress = _devAddress;
        feeAddress = _feeAddress;
        endBlock = _startBlock;
        
    }

    function poolLength() external view returns (uint256) {
        return poolInfo.length;
    }

    mapping(IERC20 => bool) public poolExistence;
    modifier nonDuplicated(IERC20 _lpToken) {
        require(poolExistence[_lpToken] == false, "nonDuplicated: duplicated");
        _;
    }

    // Add a new lp to the pool. Can only be called by the owner.
    function add(uint256 _allocPoint, IERC20 _lpToken, uint16 _depositFeeBP, bool _isPoolPrivileged) external onlyOwner {
	    _lpToken.balanceOf(address(this));
        require(_depositFeeBP <= 500, "add: invalid deposit fee basis points");

        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolExistence[_lpToken] = true;
        poolInfo.push(PoolInfo({
            lpToken: _lpToken,
            allocPoint: _allocPoint,
            lastRewardBlock: lastRewardBlock,
            accChizPerShare: 0,
            depositFeeBP: _depositFeeBP,
	        lpSupply: 0,
            isPoolPrivileged: _isPoolPrivileged
        }));
	    emit PoolAdd(msg.sender, _lpToken, _allocPoint,lastRewardBlock,_depositFeeBP);
    }

    // Update the given pool's CHIZ allocation point and deposit fee. Can only be called by the owner.
    function set(uint256 _pid, uint256 _allocPoint, uint16 _depositFeeBP) external onlyOwner {
        require(_depositFeeBP <= 500, "set: invalid deposit fee basis points");
        totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(_allocPoint);
        poolInfo[_pid].allocPoint = _allocPoint;
        poolInfo[_pid].depositFeeBP = _depositFeeBP;
	    emit PoolSet(msg.sender, poolInfo[_pid].lpToken, _allocPoint,poolInfo[_pid].lastRewardBlock,_depositFeeBP);
    }

    // Return reward multiplier over the given _from to _to block.
    function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
        if (chizDistributedAmount >= max_chiz_reward_amount) return 0;
 
        return _to.sub(_from);
    }

    // View function to see pending CHIZes on frontend.
    function pendingChiz(uint256 _pid, address _user) external view returns (uint256) {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accChizPerShare = pool.accChizPerShare;

        if (block.number > pool.lastRewardBlock && pool.lpSupply != 0 && totalAllocPoint > 0) {
            uint256 lastBlock = block.number < endBlock ? block.number : endBlock;
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, lastBlock);
            uint256 chizReward = multiplier.mul(chizPerBlock).mul(pool.allocPoint).div(totalAllocPoint);
            accChizPerShare = accChizPerShare.add(chizReward.mul(1e36).div(pool.lpSupply));
        }
        return user.amount.mul(accChizPerShare).div(1e36).sub(user.rewardDebt);
    }

    // Update reward variables for all pools. Be careful of gas spending!
    function massUpdatePools() public {
        uint256 length = poolInfo.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Update reward variables of the given pool to be up-to-date.
    function updatePool(uint256 _pid) public {
        PoolInfo storage pool = poolInfo[_pid];
        uint256 lastBlock = block.number < endBlock ? block.number : endBlock;
        if (lastBlock <= pool.lastRewardBlock) {
            return;
        }
        if (pool.lpSupply == 0 || pool.allocPoint == 0) {
            pool.lastRewardBlock = lastBlock;
            return;
        }
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, lastBlock);
        uint256 chizReward = multiplier.mul(chizPerBlock).mul(pool.allocPoint).div(totalAllocPoint);

        if(chizDistributedAmount.add(chizReward.mul(102).div(100)) <= max_chiz_reward_amount){
            chizToken.safeTransfer(devAddress, chizReward.div(50));
        }

        pool.accChizPerShare = pool.accChizPerShare.add(chizReward.mul(1e36).div(pool.lpSupply));
        pool.lastRewardBlock = block.number;
    }

    // Deposit LP tokens to MasterChef for CHIZ allocation.
    function deposit(uint256 _pid, uint256 _amount) nonReentrant external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        if(pool.isPoolPrivileged){
            // Privileged Pool NFT Check
            require(nftPrivilegeContractOne.balanceOf(msg.sender) > 0 && nftPrivilegeContractTwo.balanceOf(msg.sender) > 0, "NFTs are required for deposit");

        }
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount.mul(pool.accChizPerShare).div(1e36).sub(user.rewardDebt);
            if (pending > 0) {
                safeChizTransfer(msg.sender, pending);
            }
        }
        if (_amount > 0) {
	        uint256 balanceBefore = pool.lpToken.balanceOf(address(this));
            pool.lpToken.safeTransferFrom(address(msg.sender), address(this), _amount);
            _amount = pool.lpToken.balanceOf(address(this)).sub(balanceBefore);
            require(_amount > 0, "we dont accept deposits of 0");
            if (pool.depositFeeBP > 0) {
                uint256 depositFee = _amount.mul(pool.depositFeeBP).div(10000);
                pool.lpToken.safeTransfer(feeAddress, depositFee);
                user.amount = user.amount.add(_amount).sub(depositFee);
		        pool.lpSupply = pool.lpSupply.add(_amount).sub(depositFee);
            } else {
                user.amount = user.amount.add(_amount);
		        pool.lpSupply = pool.lpSupply.add(_amount);
            }
        }
        user.rewardDebt = user.amount.mul(pool.accChizPerShare).div(1e36);
        emit Deposit(msg.sender, _pid, _amount);
    }

    // Withdraw LP tokens from MasterChef.
    function withdraw(uint256 _pid, uint256 _amount) nonReentrant external {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool(_pid);
        uint256 pending = user.amount.mul(pool.accChizPerShare).div(1e36).sub(user.rewardDebt);
        if (pending > 0) {
            safeChizTransfer(msg.sender, pending);
        }
        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            pool.lpToken.safeTransfer(address(msg.sender), _amount);
	        pool.lpSupply = pool.lpSupply.sub(_amount);
        }
        user.rewardDebt = user.amount.mul(pool.accChizPerShare).div(1e36);
        emit Withdraw(msg.sender, _pid, _amount);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw(uint256 _pid) nonReentrant external{
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 amount = user.amount;
	    pool.lpSupply = pool.lpSupply.sub(user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
        pool.lpToken.safeTransfer(address(msg.sender), amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }

    // Safe chiz transfer function, just in case if rounding error causes pool to not have enough CHIZ.
    function safeChizTransfer(address _to, uint256 _amount) internal {
        uint256 chizBal = chizToken.balanceOf(address(this));
        bool transferSuccess = false;
        if (_amount > chizBal) {
            transferSuccess = chizToken.transfer(_to, chizBal);
            chizDistributedAmount = chizDistributedAmount.add(chizBal);
        } else {
            transferSuccess = chizToken.transfer(_to, _amount);
            chizDistributedAmount = chizDistributedAmount.add(_amount);
        }
        require(transferSuccess, "safeChizTransfer: Transfer failed");
    }

    // Update dev address by the previous dev.
    function setDevAddress(address _devAddress) external onlyOwner {
	require(_devAddress != address(0), "!nonzero");
        devAddress = _devAddress;
        emit SetDevAddress(msg.sender, _devAddress);
    }

    function setFeeAddress(address _feeAddress) external onlyOwner {
	require(_feeAddress != address(0), "!nonzero");
        feeAddress = _feeAddress;
        emit SetFeeAddress(msg.sender, _feeAddress);
    }

    function updateEmissionRate(uint256 _chizPerBlock) external onlyOwner {
        require(_chizPerBlock <= MAXIMUM_EMISSION_RATE, "Too High");
        massUpdatePools();
        chizPerBlock = _chizPerBlock;
        emit UpdateEmissionRate(msg.sender, _chizPerBlock);
    }

    // Only update before start of farm
    function updateStartBlock(uint256 _startBlock) onlyOwner external{
        require(startBlock > block.number, "Farm already started");
        uint256 length = poolInfo.length;
        for(uint256 pid = 0; pid < length; ++pid){
            PoolInfo storage pool = poolInfo[pid];
            pool.lastRewardBlock = _startBlock;
        }
            startBlock = _startBlock;
        emit UpdateStartBlock(msg.sender, _startBlock);
    }
     // Fund the farm, increase the end block
    function fund(uint256 _amount) public onlyOwner {
        require(block.number < endBlock, "fund: too late, the farm is closed");

        chizToken.transferFrom(address(msg.sender), address(this), _amount);
        endBlock += _amount.div(chizPerBlock);
    }
}
// MasterChef is the master of Chiz. He can make Chiz and he is a fair guy.
// Note that it's ownable and the owner wields tremendous power. The ownership
// will be transferred to a governance smart contract once Chiz is sufficiently
// distributed and the community can show to govern itself.
//
// Have fun reading it. Hopefully it's bug-free. God bless.
// European boys play fair, don't worry.
function updateStartBlock(uint256 _startBlock) onlyOwner external{
    require(startBlock > block.number, "Farm already started");
    uint256 length = poolInfo.length;
    for(uint256 pid = 0; pid < length; ++pid){
        PoolInfo storage pool = poolInfo[pid];
        pool.lastRewardBlock = _startBlock;
    }
        startBlock = _startBlock;
    emit UpdateStartBlock(msg.sender, _startBlock);
}

// Only update before start of farm