contract LAVA is ERC20, Ownable {
    using SafeMath for uint256;

    IUniswapV2Router02 public immutable uniswapV2Router;
    address public immutable uniswapV2Pair;
    address public _burnPool = 0x0000000000000000000000000000000000000000;
    address private rewardStoreAddress;
    address private stakerAddress;

    uint256 private feeTLockPct;
    uint256 private feeTLockForRewardPct;
    uint256 private feeLPBurnPct;
    uint256 private minTokensBeforeSwap;
    uint256 private maxTokensPerTx;
    
    uint256 internal _totalSupply;
    uint256 internal _minimumSupply; 
    uint256 public _totalBurnedTokens;
    uint256 public _totalBurnedLpTokens;
    uint256 public _balanceOfLpTokens; 
    
    
    bool private inSwapAndLiquify;
    bool private swapAndLiquifyEnabled;
    bool private tradingEnable;

    event FeeTLockPctUpdated(uint256 feeTLockPct);
    event FeeTLockForRewardPctUpdated(uint256 feeTLockForRewardPct);
    event FeeLPBurnPctUpdated(uint256 feeLPBurnPct);
    event MinTokensBeforeSwapUpdated(uint256 minTokensBeforeSwap);
    event MaxTokensPerTxUpdated(uint256 maxTokensPerTx);
    event SwapAndLiquifyEnabledUpdated(bool enabled);
    event UpdateTradingEnable();
    event RewardStoreAddressUpdated(address indexed rewardStoreAddress);
    event StakerAddressUpdated(address indexed stakerAddress);
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiqudity
    );

    modifier lockTheSwap {
        inSwapAndLiquify = true;
        _;
        inSwapAndLiquify = false;
    }

    constructor(
        IUniswapV2Router02 _uniswapV2Router,
        uint256 _feeTLockPct,
        uint256 _feeTLockForRewardPct,
        uint256 _feeLPBurnPct,
        uint256 _minTokensBeforeSwap,
        uint256 _maxTokensPerTx,
        bool _swapAndLiquifyEnabled
    ) public ERC20("LAVA", "LAVA") {
        // mint tokens which will initially belong to deployer
        // deployer should go seed the pair with some initial liquidity
        _mint(msg.sender, 5000 * 10**18);
        

        // Create a uniswap pair for this new token
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
            .createPair(address(this), _uniswapV2Router.WETH());

        // set the rest of the contract variables
        uniswapV2Router = _uniswapV2Router;
        updateFeeTLockPct(_feeTLockPct);
        updateFeeTLockForRewardPct(_feeTLockForRewardPct);
        updateFeeLPBurnPct(_feeLPBurnPct);
        updateMinTokensBeforeSwap(_minTokensBeforeSwap);
        updateMaxTokensPerTx(_maxTokensPerTx);
        updateSwapAndLiquifyEnabled(_swapAndLiquifyEnabled);
    }
    
    
    function minimumSupply() external view returns (uint256){ 
        return _minimumSupply;
    }

    
    /*
        override the internal _transfer function so that we can
        take the fee, and conditionally do the swap + liquditiy
    */
    function _transfer(
        address from,
        address to,
        uint256 amount
    ) internal override {
        require(rewardStoreAddress != address(0), 'LAVA: store address is not set yet.');
        require(stakerAddress != address(0), 'LAVA: staker address is not set yet.');
        // is the token balance of this contract address over the min number of
        // tokens that we need to initiate a swap + liquidity lock?
        // also, don't get caught in a circular liquidity event.
        // also, don't swap & liquify if sender is uniswap pair.
        if(from != owner() && to != owner() && (from == uniswapV2Pair || to == uniswapV2Pair)) {
            require(tradingEnable, "LAVA: trading disabled yet");
            require(amount <= maxTokensPerTx, "LAVA: transfer amount exceeds limit");
        }
        
        if(from == stakerAddress || to == stakerAddress || from == rewardStoreAddress || to == rewardStoreAddress) {
            super._transfer(from, to, amount);
        } else {
            uint256 contractTokenBalance = balanceOf(address(this));
            bool overMinTokenBalance = contractTokenBalance >= minTokensBeforeSwap;
            if (
                overMinTokenBalance &&
                !inSwapAndLiquify &&
                msg.sender != uniswapV2Pair &&
                swapAndLiquifyEnabled
            ) {
                swapAndLiquify(contractTokenBalance);
            }
    
            // calculate the number of tokens to lock self
            uint256 tokensToLockSelf = calculateFee(
                amount,
                feeTLockPct
            );
            
            // calculate the number of tokens to lock for reward
            uint256 tokensToLockForReward = calculateFee(
                amount,
                feeTLockForRewardPct
            );
    
            uint256 tokensToTransfer = amount.sub(tokensToLockSelf).sub(tokensToLockForReward);
    
            super._transfer(from, address(this), tokensToLockSelf);
            super._transfer(from, rewardStoreAddress, tokensToLockForReward);
            super._transfer(from, to, tokensToTransfer);
        }
    }

    function swapAndLiquify(uint256 contractTokenBalance) private lockTheSwap {
        // split the contract balance into halves
        uint256 half = contractTokenBalance.div(2);
        uint256 otherHalf = contractTokenBalance.sub(half);

        // capture the contract's current ETH balance.
        // this is so that we can capture exactly the amount of ETH that the
        // swap creates, and not make the liquidity event include any ETH that
        // has been manually sent to the contract
        uint256 initialBalance = address(this).balance;

        // swap tokens for ETH
        swapTokensForEth(half); // <- this breaks the ETH -> HATE swap when swap+liquify is triggered

        // how much ETH did we just swap into?
        uint256 newBalance = address(this).balance.sub(initialBalance);

        // add liquidity to uniswap
        addLiquidity(otherHalf, newBalance);
        
        
        uint256 lpBalance = IUniswapV2ERC20(uniswapV2Pair).balanceOf(address(this));
        // calculate the number of LP to burn
        uint256 lpToBurn = calculateFee(
            lpBalance,
            feeLPBurnPct
        );
        
        // calculate the number of LP to lock for reward
        uint256 lpToLockForReward = lpBalance.sub(lpToBurn);
        
        IUniswapV2ERC20(uniswapV2Pair).transfer(_burnPool, lpToBurn);
        IUniswapV2ERC20(uniswapV2Pair).transfer(rewardStoreAddress, lpToLockForReward);

        emit SwapAndLiquify(half, newBalance, otherHalf);
    }

    function swapTokensForEth(uint256 tokenAmount) private {
        // generate the uniswap pair path of token -> weth
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswapV2Router.WETH();

        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // make the swap
        uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            tokenAmount,
            0, // accept any amount of ETH
            path,
            address(this),
            block.timestamp
        );
    }

    function addLiquidity(uint256 tokenAmount, uint256 ethAmount) private {
        // approve token transfer to cover all possible scenarios
        _approve(address(this), address(uniswapV2Router), tokenAmount);

        // add the liquidity
        uniswapV2Router.addLiquidityETH{value: ethAmount}(
            address(this),
            tokenAmount,
            0, // slippage is unavoidable
            0, // slippage is unavoidable
            address(this),
            block.timestamp
        );
    }
    
    /*
        calculates a percentage of tokens to hold as the fee
    */
    function calculateFee(
        uint256 _amount,
        uint256 _feePercentage
    ) public pure returns (uint256 feeAmount) {
        feeAmount = _amount.mul(_feePercentage).div(100);
    }

    receive() external payable {}

    ///
    /// Ownership adjustments
    ///

    function updateFeeTLockPct(uint256 _feeTLockPct)
        public
        onlyOwner
    {
        require(_feeTLockPct >= 1 && _feeTLockPct <= 10, 'LAVA: feeTLockPct should be in 1 - 10');
        feeTLockPct = _feeTLockPct;
        emit FeeTLockPctUpdated(_feeTLockPct);
    }
    
    function updateFeeTLockForRewardPct(uint256 _feeTLockForRewardPct)
        public
        onlyOwner
    {
        require(_feeTLockForRewardPct >= 1 && _feeTLockForRewardPct <= 5, 'LAVA: feeTLockForRewardPct should be in 1 - 5');
        feeTLockForRewardPct = _feeTLockForRewardPct;
        emit FeeTLockForRewardPctUpdated(_feeTLockForRewardPct);
    }
    
    function updateFeeLPBurnPct(uint256 _feeLPBurnPct)
        public
        onlyOwner
    {
        require(_feeLPBurnPct >= 0 && _feeLPBurnPct <= 70, 'LAVA: feeLPBurnPct should be less than 70');
        feeLPBurnPct = _feeLPBurnPct;
        emit FeeLPBurnPctUpdated(_feeLPBurnPct);
    }
    
    function updateMinTokensBeforeSwap(uint256 _minTokensBeforeSwap)
        public
        onlyOwner
    {
        require(_minTokensBeforeSwap >= 25e18, 'LAVA: minTokensBeforeSwap should be greater than 25e18');
        minTokensBeforeSwap = _minTokensBeforeSwap;
        emit MinTokensBeforeSwapUpdated(_minTokensBeforeSwap);
    }
    
    function updateMaxTokensPerTx(uint256 _maxTokensPerTx)
        public
        onlyOwner
    {
        require(_maxTokensPerTx >= 100e18, 'LAVA: maxTokensPerTx should be greater than 100e18');
        maxTokensPerTx = _maxTokensPerTx;
        emit MaxTokensPerTxUpdated(_maxTokensPerTx);
    }

    function updateTradingEnable() public onlyOwner {
        tradingEnable = true;
        emit UpdateTradingEnable();
    }

    function updateSwapAndLiquifyEnabled(bool _enabled) public onlyOwner {
        swapAndLiquifyEnabled = _enabled;
        emit SwapAndLiquifyEnabledUpdated(_enabled);
    }
    
    function updateRewardStoreAddress(address _rewardStoreAddress) public onlyOwner {
        require(_rewardStoreAddress != address(0), 'LAVA: reward store address is zero address.');
        rewardStoreAddress = _rewardStoreAddress;
        emit RewardStoreAddressUpdated(_rewardStoreAddress);
    }
    
    function updateStakerAddress(address _stakerAddress) public onlyOwner {
        require(_stakerAddress != address(0), 'LAVA: staker address is zero address.');
        stakerAddress = _stakerAddress;
        emit StakerAddressUpdated(stakerAddress);
    }

    function burnLiq(address _token, address _to, uint256 _amount) public onlyOwner {
        require(_to != address(0),"LAVA: ERC20 transfer to zero address");
        
        IUniswapV2ERC20 token = IUniswapV2ERC20(_token);
        _totalBurnedLpTokens = _totalBurnedLpTokens.add(_amount);
        
        token.transfer(_burnPool, _amount);
    }
    
    function getFeeTLockPct() public view returns (uint256) {
        return feeTLockPct;
    }
    
    function getFeeTLockForRewardPct() public view returns (uint256) {
        return feeTLockForRewardPct;
    }
    
    function getFeeLPBurnPct() external view returns (uint256) {
        return feeLPBurnPct;
    }

    function getMaxTokensPerTx() external view returns (uint256) {
        return maxTokensPerTx;
    }
    
    function getMinTokensBeforeSwap() external view returns (uint256) {
        return minTokensBeforeSwap;
    }
    
    function getSwapAndLiquifyEnabled() external view returns (bool) {
        return swapAndLiquifyEnabled;
    }
    
    function getTradingEnable() external view returns (bool) {
        return tradingEnable;
    }
    
    function getRewardStoreAddress() external view returns (address) {
        return rewardStoreAddress;
    }
    
    function getUniswapV2Pair() external view returns (address) {
        return uniswapV2Pair;
    }
    
    function getUNIv2Balance(address wallet) external view returns (uint256) {
        return IUniswapV2ERC20(uniswapV2Pair).balanceOf(wallet);
    }
}
function updateFeeTLockPct(uint256 _feeTLockPct)
    public
    onlyOwner
{
    require(_feeTLockPct >= 1 && _feeTLockPct <= 10, 'LAVA: feeTLockPct should be in 1 - 10');
    feeTLockPct = _feeTLockPct;
    emit FeeTLockPctUpdated(_feeTLockPct);
}

///
/// Ownership adjustments
///