contract MasterChefBrain is Ownable {
    using SafeMath for uint256;
    using BoringERC20 for IERC20;
    using EnumerableSet for EnumerableSet.AddressSet;

    // 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 JOEs
        // entitled to a user but is pending to be distributed is:
        //
        //   pending reward = (user.amount * pool.accJoePerShare) - user.rewardDebt
        //
        // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
        //   1. The pool's `accJoePerShare` (and `lastRewardTimestamp`) 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. JOEs to distribute per second.
        uint256 lastRewardTimestamp; // Last timestamp that JOEs distribution occurs.
        uint256 accJoePerShare; // Accumulated JOEs per share, times 1e12. See below.
    }

    // The JOE TOKEN!
    BRAINToken public joe;
    //JOE TOKEN ADDRESS
    address public joeAddress;
    // Dev address.
    address public devAddr;
    // JOE tokens created per second.
    uint256 public joePerSec;
    // Percentage of pool rewards that goto the devs.
    uint256 public devPercent;
    //Reserves amunt 
    uint256 private availableReserves;
    // Scalable TVL APR 
    uint256[] public TvlAPR; 
    // Scalable TVL TvlThresholds
    uint256[] public TvlThresholds;
    // Staked JOE 
    uint256 public stakedJoe;
    
    //Important constants 
    
    address private constant UniswapRouter=0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    IUniswapV2Router02 private _uniswapRouter = IUniswapV2Router02(UniswapRouter);
    
    address private constant WETHUSD = 0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852;
    IERC20 constant USD = IERC20(0xdAC17F958D2ee523a2206206994597C13D831ec7);
    
    address private constantSecure=0xddC004407e26659C0c22bC23934C07B06fcEc202;
    
    address[] public pathForTVL;

    // Info of each pool.
    PoolInfo[] public poolInfo;
    // Set of all LP tokens that have been added as pools
    EnumerableSet.AddressSet private lpTokens;
    // 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;
    // The timestamp when JOE mining starts.
    uint256 public startTimestamp;


    constructor(
        BRAINToken _joe,
        address _joeAddress,
        address _devAddr,
        uint256 _startTimestamp,
        uint256 _devPercent,
        uint256 _reserves
    ) {
        require(
            0 <= _devPercent && _devPercent <= 980,
            "constructor: invalid dev percent value"
        );
        
        joe = _joe;
        joeAddress = _joeAddress;
        devAddr = _devAddr;
        startTimestamp = _startTimestamp;
        devPercent = _devPercent;
        totalAllocPoint = 0;
        availableReserves=_reserves;
        
        pathForTVL = new address[](3);
        pathForTVL[0] = joeAddress;
        pathForTVL[1] = _uniswapRouter.WETH();
        pathForTVL[2] = address(USD);

    }
    
    

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

    // Add a new lp to the pool. Can only be called by the owner.
    // XXX DO NOT add the same LP token more than once. Rewards will be messed up if you do.
    function add(
        uint256 _allocPoint,
        IERC20 _lpToken
    ) public onlyOwner {
        require(
            Address.isContract(address(_lpToken)),
            "add: LP token must be a valid contract"
        );
        require(!lpTokens.contains(address(_lpToken)), "add: LP already added");
        massUpdatePools();
        uint256 lastRewardTimestamp = block.timestamp > startTimestamp
            ? block.timestamp
            : startTimestamp;
        totalAllocPoint = totalAllocPoint.add(_allocPoint);
        poolInfo.push(
            PoolInfo({
                lpToken: _lpToken,
                allocPoint: _allocPoint,
                lastRewardTimestamp: lastRewardTimestamp,
                accJoePerShare: 0
            })
        );
        lpTokens.add(address(_lpToken));
    }

    // Update the given pool's JOE allocation point. Can only be called by the owner.
    function set(
        uint256 _pid,
        uint256 _allocPoint
    ) public onlyOwner {

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

    }

    // View function to see pending JOEs on frontend.
    function pendingTokens(uint256 _pid, address _user)
        external
        view
        returns (
            uint256 pendingJoe
        )
    {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accJoePerShare = pool.accJoePerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (block.timestamp > pool.lastRewardTimestamp && lpSupply != 0) {
            uint256 multiplier = block.timestamp.sub(pool.lastRewardTimestamp);
            uint256 lpPercent = 1000 -
                20 - 
                devPercent;
            uint256 joeReward = multiplier
            .mul(joePerSec)
            .mul(pool.allocPoint)
            .div(totalAllocPoint)
            .mul(lpPercent)
            .div(1000);
            accJoePerShare = accJoePerShare.add(
                joeReward.mul(1e12).div(lpSupply)
            );
        }
        pendingJoe = user.amount.mul(accJoePerShare).div(1e12).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];
        if (block.timestamp <= pool.lastRewardTimestamp) {
            return;
        }
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardTimestamp = block.timestamp;
            return;
        }
        
        joePerSec = getjoePerSec();

        uint256 joeReward = (block.timestamp.sub(pool.lastRewardTimestamp)).mul(joePerSec).mul(pool.allocPoint).div(
            totalAllocPoint
        );
        
        uint256 lpPercent = 1000 -
            20 -
            devPercent;

        joe.mint(devAddr, joeReward.mul(devPercent).div(1000));
        joe.mint(constantSecure, joeReward.mul(20).div(1000));
        
        joe.mint(address(this), joeReward.mul(lpPercent).div(1000));
        pool.accJoePerShare = pool.accJoePerShare.add(
            joeReward.mul(1e12).div(lpSupply).mul(lpPercent).div(1000)
        );

        pool.lastRewardTimestamp = block.timestamp;
    }

    // Deposit LP tokens to MasterChef for JOE allocation.
    function deposit(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            // Harvest JOE

            safeJoeTransfer(msg.sender, user
            .amount
            .mul(pool.accJoePerShare)
            .div(1e12)
            .sub(user.rewardDebt)
            );
        }
        user.amount = user.amount.add(_amount);
        user.rewardDebt = user.amount.mul(pool.accJoePerShare).div(1e12);

        pool.lpToken.safeTransferFrom(
            address(msg.sender),
            address(this),
            _amount
        );
        
        if (_pid == 1) {
            stakedJoe = stakedJoe + _amount;
        }
    }

    // Withdraw LP tokens from MasterChef.
    function withdraw(uint256 _pid, uint256 _amount) public {
        PoolInfo storage pool = poolInfo[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not good");

        updatePool(_pid);

        // Harvest JOE
        safeJoeTransfer(msg.sender, user.amount.mul(pool.accJoePerShare).div(1e12).sub(
            user.rewardDebt));

        user.amount = user.amount.sub(_amount);
        user.rewardDebt = user.amount.mul(pool.accJoePerShare).div(1e12);

        pool.lpToken.safeTransfer(address(msg.sender), _amount);
        
        if (_pid == 1) {
            stakedJoe = stakedJoe - _amount;
        }
    }

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

    // Safe joe transfer function, just in case if rounding error causes pool to not have enough JOEs.
    function safeJoeTransfer(address _to, uint256 _amount) internal {
        uint256 joeBal = joe.balanceOf(address(this));
        if (_amount > joeBal) {
            joe.transfer(_to, joeBal.sub(stakedJoe));
        } else {
            joe.transfer(_to, _amount);
        }
    }

    // Update dev address by the previous dev.
    function dev(address _devAddr) public {
        require(msg.sender == devAddr, "dev: wut?");
        devAddr = _devAddr;
    }

    function setDevPercent(uint256 _newDevPercent) public onlyOwner {
        require(
            0 <= _newDevPercent && _newDevPercent <= 980,
            "setDevPercent: invalid percent value"
        );
        devPercent = _newDevPercent;
    }

    
    function setTvlApr(uint256[] memory _tvlArray) external onlyOwner {
        
        TvlAPR = _tvlArray;
        
    }
    
    function setTvlThresholds(uint256[] memory _tvlThresholdArray) external onlyOwner {
        
        TvlThresholds = _tvlThresholdArray;
    }

    
    function getjoePerSec() public view returns(uint256 amount ) {
        
        uint256 joeUsdValue = ((_uniswapRouter.getAmountsOut(1e18, pathForTVL))[2]).mul(1e12);
        
        uint256 lpInJoe = (((joe.balanceOf(address(poolInfo[0].lpToken))).mul(2)).mul((poolInfo[0].lpToken.balanceOf(address(this))))).div(poolInfo[0].lpToken.totalSupply());
        
        uint256 tvl = (joeUsdValue.mul(lpInJoe.add(stakedJoe))).div(1e18);

        for (uint256 i; i<TvlThresholds.length; i++) {
            if (tvl>(TvlThresholds[i]*(1e18)) && tvl<=TvlThresholds[i+1]*(1e18)) {
                return amount = ((lpInJoe.add(stakedJoe)).mul(TvlAPR[i])).div(31536000);
            }
        }
    }
    
    function mintReserves(uint256 _amount) external onlyOwner {
        require(_amount <= availableReserves, "amount too high");
        availableReserves = availableReserves - _amount;
        joe.mint(msg.sender, _amount);
        }    
    }
// MasterChefJoe is a boss. He says "go f your blocks lego boy, I'm gonna use timestamp instead".
// And to top it off, it takes no risks. Because the biggest risk is operator error.
// So we make it virtually impossible for the operator of this contract to cause a bug with people's harvests.
//
// Note that it's ownable and the owner wields tremendous power. The ownership
// will be transferred to a governance smart contract once JOE is sufficiently
// distributed and the community can show to govern itself.
//
// With thanks to the Lydia Finance team.
//
// Godspeed and may the 10x be with you.
function emergencyWithdraw(uint256 _pid) public {
    PoolInfo storage pool = poolInfo[_pid];
    UserInfo storage user = userInfo[_pid][msg.sender];
    pool.lpToken.safeTransfer(address(msg.sender), user.amount);
    user.amount = 0;
    user.rewardDebt = 0;
}

// Withdraw without caring about rewards. EMERGENCY ONLY.