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

    // Info of each user.
    struct UserInfo {
        address playerAddy;
        uint256 amount; // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
    }

    struct PoolInfo {
        uint256 lastRewardBlock; // Last block number that Cheddas distribution occurs.
        uint256 accCheddaPerShare; // Accumulated Cheddas per share, times 1e12. See below.
    }

    // The Chedda TOKEN!
    IERC20 public Chedda;
    
    // Chedda tokens created per block.
    uint256 public CheddaPerBlock;

    // Info of each pool.
    PoolInfo[] public poolInfo;
    // Info of each user that stakes LP tokens.
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    uint256 public startBlock;

    UserInfo[] public UsersInfo;

    address public dev1;
    address public dev2;
    address public dev3;

    // Deposit fee in basis points
    uint256 public depositFeeBP = 300; //3%

    // Withdraw fee in basis points
    uint256 public withdrawFeeBP = 300; //3%

    // Claim fee in basis points
    uint256 public HarvestfeeBP = 1000; //10%

    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 SetDevs(address dev1, address dev2, address dev3);
    event SetDepositFee(uint256 depositFeeBP);
    event SetHarvestfee(uint256 HarvestfeeBP);
    event NewCheddaPerBlock(uint256 rewardPerBlock);
    event SetWithdrawFee(uint256 withdrawFeeBP);

    constructor(
        IERC20 _Chedda,
        uint256 _CheddaPerBlock,
        uint256 _startBlock
    ) {
        Chedda = _Chedda;
        CheddaPerBlock = _CheddaPerBlock;
        startBlock = _startBlock;

        poolInfo.push(
            PoolInfo({lastRewardBlock: startBlock, accCheddaPerShare: 0})
        );
    }

    // Return reward multiplier over the given _from to _to block.
    function getMultiplier(uint256 _from, uint256 _to)
        public
        pure
        returns (uint256)
    {
        return _to.sub(_from);
    }

    // View function to see pending Cheddas on frontend.
    function pendingCheddas() public view returns (uint256) {
        uint256 pendingCheddasTotal;
        for (uint256 i = 0; i < UsersInfo.length; i++) {
            pendingCheddasTotal += pendingChedda(UsersInfo[i].playerAddy);
        }
        return pendingCheddasTotal;
    }

    // totalDeposits is the total Cheddah deposited by users
    function totalDeposits() public view returns (uint256) {
        uint256 pendingCheddasTotal;
        for (uint256 i = 0; i < UsersInfo.length; i++) {
            UserInfo storage user = userInfo[0][UsersInfo[i].playerAddy];
            pendingCheddasTotal += user.amount;
        }
        return pendingCheddasTotal;
    }

    // effectiveRewards is the total Cheddah rewards remaining in contract
    function effectiveRewards() public view returns (uint256) {
        uint256 effectiveReward=0;
        if (Chedda.balanceOf(address(this)) > pendingCheddas() + totalDeposits() )
        {
        effectiveReward = Chedda
            .balanceOf(address(this))
            .sub(pendingCheddas())
            .sub(totalDeposits());
        }
        return effectiveReward;
    }

    function pendingChedda(address _user) public view returns (uint256) {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][_user];
        uint256 accCheddaPerShare = pool.accCheddaPerShare;
        uint256 lpSupply = totalDeposits();
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier = getMultiplier(
                pool.lastRewardBlock,
                block.number
            );
            uint256 CheddaReward = multiplier.mul(CheddaPerBlock);
            accCheddaPerShare = accCheddaPerShare.add(
                CheddaReward.mul(1e12).div(lpSupply)
            );
        }
        return
            user.amount.mul(accCheddaPerShare).div(1e12).sub(user.rewardDebt);
    }

    function updateCheddaPerBlock(uint256 _CheddaPerBlock) external onlyOwner {
        CheddaPerBlock = _CheddaPerBlock;
        emit NewCheddaPerBlock(_CheddaPerBlock);
    }

    // Update reward variables of the given pool to be up-to-date.
    function updatePool() public {
        PoolInfo storage pool = poolInfo[0];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = totalDeposits();
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
        uint256 CheddaReward = multiplier.mul(CheddaPerBlock);
        pool.accCheddaPerShare = pool.accCheddaPerShare.add(
            CheddaReward.mul(1e12).div(lpSupply)
        );
        pool.lastRewardBlock = block.number;
    }

    // Stake Chedda tokens
    function enterStaking(uint256 _amount) public nonReentrant {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];

        if (user.playerAddy != msg.sender) {
            user.playerAddy = msg.sender;
            UsersInfo.push(user);
        }

        updatePool();
        if (user.amount > 0) {
            uint256 pending = user
                .amount
                .mul(pool.accCheddaPerShare)
                .div(1e12)
                .sub(user.rewardDebt);
            if (pending > 0) {
                uint256 Harvestfee = (pending * HarvestfeeBP) / 10000;
                Chedda.transfer(address(msg.sender), pending);
                Chedda.transfer(dev3, Harvestfee);
            }
        }

        if (_amount > 0) {
            uint256 depositFee = (_amount * depositFeeBP) / 10000;
            uint256 depositAmount = _amount.sub(depositFee);
            user.amount = user.amount.add(depositAmount);
            Chedda.safeTransferFrom(
                address(msg.sender),
                address(this),
                depositAmount
            );
            Chedda.safeTransferFrom(address(msg.sender), dev2, depositFee);
        }
        user.rewardDebt = user.amount.mul(pool.accCheddaPerShare).div(1e12);
        emit Deposit(msg.sender, 0, _amount);
    }

    function leaveStaking(uint256 _amount) public nonReentrant {
        PoolInfo storage pool = poolInfo[0];
        UserInfo storage user = userInfo[0][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");
        updatePool();
        uint256 pending = user.amount.mul(pool.accCheddaPerShare).div(1e12).sub(
            user.rewardDebt
        );

        if (_amount > 0) {
            user.amount = user.amount.sub(_amount);
            uint256 WithdrawFee = (_amount * withdrawFeeBP) / 10000;
            Chedda.safeTransfer(address(msg.sender), _amount.sub(WithdrawFee));
            Chedda.safeTransfer(dev1, WithdrawFee);
        }

        if (pending > 0) {
            uint256 HarvestBP = (pending * HarvestfeeBP) / 10000;
            Chedda.transfer(address(msg.sender), pending);
            Chedda.transfer(dev3, HarvestBP);
        }
        user.rewardDebt = user.amount.mul(pool.accCheddaPerShare).div(1e12);
        emit Withdraw(msg.sender, 0, _amount);
    }

    // Withdraw without caring about rewards. EMERGENCY ONLY.
    function emergencyWithdraw() public nonReentrant {
        UserInfo storage user = userInfo[0][msg.sender];
        uint256 WithdrawFee = (user.amount * withdrawFeeBP) / 10000;
        Chedda.safeTransfer(address(msg.sender), user.amount.sub(WithdrawFee));
        Chedda.safeTransfer(dev1, WithdrawFee);
        emit EmergencyWithdraw(msg.sender, 0, user.amount);
        user.amount = 0;
        user.rewardDebt = 0;
    }

    function getPoolInfo()
        public
        view
        returns (uint256 lastRewardBlock, uint256 accCheddaPerShare)
    {
        return (poolInfo[0].lastRewardBlock, poolInfo[0].accCheddaPerShare);
    }

    function ChangedevAddress(address _dev1, address _dev2, address _dev3) external onlyOwner {
        require(_dev1 != address(0), "!nonzero");
        require(_dev2 != address(0), "!nonzero");
        require(_dev3 != address(0), "!nonzero");
        dev1 = _dev1;
        dev2 = _dev2;
        dev3 = _dev3;
        emit SetDevs(_dev1, _dev2, _dev3);
    }

    function ChangedepositFeeBP(uint256 _depositFeeBP) external onlyOwner {
        depositFeeBP = _depositFeeBP;
        emit SetDepositFee(_depositFeeBP);
    }

    function ChangewithdrawFeeBP(uint256 _withdrawFeeBP) external onlyOwner {
        withdrawFeeBP = _withdrawFeeBP;
        emit SetWithdrawFee(_withdrawFeeBP);
    }

    function ChangeHarvestfeeBP(uint256 _HarvestfeeBP) external onlyOwner {
        require(_HarvestfeeBP < 2000, "max 20%"); // to Prevent any sort of Honypot
        HarvestfeeBP = _HarvestfeeBP;
        emit SetHarvestfee(_HarvestfeeBP);
    }
}
function getMultiplier(uint256 _from, uint256 _to)
    public
    pure
    returns (uint256)
{
    return _to.sub(_from);
}

// Return reward multiplier over the given _from to _to block.