contract LevelUp is Context, IERC20, Ownable, ReentrancyGuard {
    using SafeMath for uint256;
    using Address for address;

    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    mapping(address => bool) private _isExcludedFromFee;
    mapping(address => bool) private _isExcludedFromMaxTx;
    mapping(address => uint256) public nextAvailableClaimDate;

    string private _name = "Level Up";
    string private _symbol = "LVLUP";
    uint8 private _decimals = 9;
    uint256 private _totalSupply = 100 * 1e12 * 1e9;

    IUniSwapV2Router02 public uniSwapV2Router;
    address public uniSwapV2Pair;
    address payable public marketWallet;

    uint256 public _maxTxAmount = _totalSupply.mul(5).div(1000); // must be 0.5% percent per transaction
    uint256 public minTokenNumberToSell = _totalSupply.div(100000); // 0.001% max tx amount will trigger swap and add liquidity
    uint256 public rewardCycleBlock = 24 hours;
    uint256 public sellCoolDownDuration = 60 seconds;
    uint256 public sellCoolDownTriggeredAt;
    
    
    bool public swapAndLiquifyEnabled; // must be true to auto liquidate the pool
    bool public reflectionFeesdiabled; // fees are enabled by default
    bool inSwapAndLiquify = false;
    bool public _tradingOpen;   // must be true to enable trading

    uint256 public _poolFee = 80;   // 8% will be added to pool for ETH reward
    uint256 public _marketFee = 40; // 4% will go to the market address
    uint256 public _totalFeePerTx = 120;    // 12% by default
    uint256 public _totalSellFeePerTx = 250;    // 25% by defult

    event SwapAndLiquifyEnabledUpdated(bool enabled);
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived
    );

    event ClaimEthSuccessfully(
        address recipient,
        uint256 ethReceived,
        uint256 nextAvailableClaimDate
    );

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

    constructor(address payable _marketWallet) {
        marketWallet = _marketWallet;
        _balances[owner()] = _totalSupply;

        IUniSwapV2Router02 _uniSwapV2Router = IUniSwapV2Router02(
            0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
        );
        // 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;

        //exclude owner and this contract from fee
        _isExcludedFromFee[owner()] = true;
        _isExcludedFromFee[address(this)] = true;

        // exclude from max tx
        _isExcludedFromMaxTx[owner()] = true;
        _isExcludedFromMaxTx[address(this)] = true;

        emit Transfer(address(0), owner(), _totalSupply);
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function totalSupply() public view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) public view override returns (uint256) {
        return _balances[account];
    }

    function transfer(address recipient, uint256 amount)
        public
        override
        returns (bool)
    {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender)
        public
        view
        override
        returns (uint256)
    {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount)
        public
        override
        returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) public override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(
            sender,
            _msgSender(),
            _allowances[sender][_msgSender()].sub(
                amount,
                "LVLUP: transfer amount exceeds allowance"
            )
        );
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].add(addedValue)
        );
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue)
        public
        virtual
        returns (bool)
    {
        _approve(
            _msgSender(),
            spender,
            _allowances[_msgSender()][spender].sub(
                subtractedValue,
                "LVLUP: decreased allowance below zero"
            )
        );
        return true;
    }

    function excludeFromFee(address account) public onlyOwner {
        _isExcludedFromFee[account] = true;
    }

    function includeInFee(address account) public onlyOwner {
        _isExcludedFromFee[account] = false;
    }

    // for 1% input 100
    function setMaxTxPercent(uint256 maxTxAmount) public onlyOwner {
        _maxTxAmount = _totalSupply.mul(maxTxAmount).div(10000);
    }

    function setMinTokenNumberToSell(uint256 _amount) public onlyOwner {
        minTokenNumberToSell = _amount;
    }

    function setExcludeFromMaxTx(address _address, bool value)
        public
        onlyOwner
    {
        _isExcludedFromMaxTx[_address] = value;
    }

    function setRewardCycleBlock(uint256 _newCycle) public onlyOwner {
        rewardCycleBlock = _newCycle;
    }

    function setSellCoolDownDuration(uint256 _newDuration) public onlyOwner {
        sellCoolDownDuration = _newDuration;
    }

    function setFeePercent(uint256 poolFee, uint256 marketFee, uint256 sellFee) external onlyOwner {
        _poolFee = poolFee;
        _marketFee = marketFee;
        _totalFeePerTx = poolFee.add(marketFee);
        _totalSellFeePerTx = sellFee;
    }

    function enableTrading() external onlyOwner {
        require(!_tradingOpen,"LVLUP: Already enabled");
        _tradingOpen = true;
        swapAndLiquifyEnabled = true;

        emit SwapAndLiquifyEnabledUpdated(true);
    }

    function setSwapAndLiquifyEnabled(bool _state) public onlyOwner {
        swapAndLiquifyEnabled = _state;
        emit SwapAndLiquifyEnabledUpdated(_state);
    }

    function setReflectionFees(bool _state) external onlyOwner {
        reflectionFeesdiabled = _state;
    }

    function setMarketAddress(address payable _marketAddress)
        external
        onlyOwner
    {
        marketWallet = _marketAddress;
    }

    function changeRouter(IUniSwapV2Router02 _router, address _pair)
        external
        onlyOwner
    {
        uniSwapV2Router = _router;
        uniSwapV2Pair = _pair;
    }

    //to receive ETH from uniSwapV2Router when swapping
    receive() external payable {}

    function totalFeePerTx(uint256 tAmount) internal view returns (uint256) {
        uint256 percentage = tAmount.mul(_poolFee.add(_marketFee)).div(1e3);
        return percentage;
    }

    function isExcludedFromFee(address account) public view returns (bool) {
        return _isExcludedFromFee[account];
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) private {
        require(owner != address(0), "LVLUP: approve from the zero address");
        require(spender != address(0), "LVLUP: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function _transfer(
        address from,
        address to,
        uint256 amount
    ) private {
        require(from != address(0), "LVLUP: transfer from the zero address");
        require(to != address(0), "LVLUP: transfer to the zero address");
        require(amount > 0, "LVLUP: transfer amount must be greater than zero");
        require(balanceOf(from) >= amount,"LVLUP: not enough balance");

        if (
            !_isExcludedFromMaxTx[from] &&
            !_isExcludedFromMaxTx[to] // by default false
        ) {
            require(
                amount <= _maxTxAmount,
                "LVLUP: Transfer amount exceeds the maxTxAmount."
            );

            if (!_tradingOpen) {
                require(
                    from != uniSwapV2Pair && to != uniSwapV2Pair,
                    "LVLUP: Trading is not enabled"
                );
            }

            if(to == uniSwapV2Pair){
                require(sellCoolDownTriggeredAt.add(sellCoolDownDuration) < block.timestamp,
                "LVLUP: wait for selling cool down"
                );
                sellCoolDownTriggeredAt = block.timestamp;
            }
        }

        // swap and liquify
        swapAndLiquify(from, to);

        //indicates if fee should be deducted from transfer
        bool takeFee = true;

        //if any account belongs to _isExcludedFromFee account then remove the fee
        if (
            _isExcludedFromFee[from] ||
            _isExcludedFromFee[to] ||
            reflectionFeesdiabled
        ) {
            takeFee = false;
        }

        //transfer amount, it will take tax, burn, liquidity fee
        _tokenTransfer(from, to, amount, takeFee);
    }

    //this method is responsible for taking all fee, if takeFee is true
    function _tokenTransfer(
        address sender,
        address recipient,
        uint256 amount,
        bool takeFee
    ) private {
        if (!takeFee) {
            _balances[sender] = _balances[sender].sub(amount);
            _balances[recipient] = _balances[recipient].add(amount);

            emit Transfer(sender, recipient, amount);
        }else if(recipient == uniSwapV2Pair){
            uint256 _totalFee = amount.mul(_totalSellFeePerTx).div(1e3);
            uint256 transferAmount = amount.sub(_totalFee);
            _balances[sender] = _balances[sender].sub(amount);
            _balances[recipient] = _balances[recipient].add(transferAmount);
            _takeBothFee(amount);

            emit Transfer(sender, recipient, transferAmount);
        } else {
            uint256 _totalFee = amount.mul(_totalFeePerTx).div(1e3);
            uint256 transferAmount = amount.sub(_totalFee);
            _balances[sender] = _balances[sender].sub(amount);
            _balances[recipient] = _balances[recipient].add(transferAmount);
            _takeBothFee(amount);

            emit Transfer(sender, recipient, transferAmount);
        }
    }

    function _takeBothFee(uint256 tAmount) internal {
        uint256 totalFee = tAmount.mul(_marketFee.add(_poolFee)).div(1e3);
        _balances[address(this)] = _balances[address(this)].add(totalFee);

        emit Transfer(_msgSender(), address(this), totalFee);
    }

    function calculateEthReward(address ofAddress)
        public
        view
        returns (uint256)
    {
        uint256 remainingSupply = uint256(_totalSupply)
            .sub(balanceOf(address(0)))
            .sub(balanceOf(0x000000000000000000000000000000000000dEaD))
            .sub(balanceOf(address(uniSwapV2Pair))); // exclude burned wallet
        // exclude liquidity wallet

        uint256 reward = address(this)
            .balance
            .mul(balanceOf(address(ofAddress)))
            .div(100)
            .div(remainingSupply);
        return reward;
    }

    function claimEthReward() public isHuman nonReentrant {
        require(
            !address(msg.sender).isContract(),
            "LVLUP: Contract can not call this function"
        );
        require(
            nextAvailableClaimDate[msg.sender] <= block.timestamp,
            "LVLUP: next claim not reached"
        );
        require(
            balanceOf(msg.sender) >= 0,
            "LVLUP: must own LVLUP to claim reward"
        );

        uint256 reward = calculateEthReward(msg.sender);
        payable(msg.sender).transfer(reward);

        // update rewardCycleBlock
        nextAvailableClaimDate[msg.sender] =
            block.timestamp +
            getRewardCycleBlock();

        emit ClaimEthSuccessfully(
            msg.sender,
            reward,
            nextAvailableClaimDate[msg.sender]
        );
    }

    function getRewardCycleBlock() public view returns (uint256) {
        return rewardCycleBlock;
    }

    function getContractBalance() public view returns (uint256) {
        return address(this).balance;
    }

    function swapAndLiquify(address from, address to) private {
        // 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.
        uint256 contractTokenBalance = balanceOf(address(this));

        if (contractTokenBalance >= _maxTxAmount) {
            contractTokenBalance = _maxTxAmount;
        }

        bool shouldSell = contractTokenBalance >= minTokenNumberToSell;

        if (
            !inSwapAndLiquify &&
            shouldSell &&
            from != uniSwapV2Pair &&
            swapAndLiquifyEnabled &&
            !(from == address(this) && to == address(uniSwapV2Pair)) // swap 1 time
        ) {
            // only sell for minTokenNumberToSell, decouple from _maxTxAmount

            contractTokenBalance = minTokenNumberToSell;
            // approve contract
            _approve(
                address(this),
                address(uniSwapV2Router),
                contractTokenBalance
            );

            // split the contract balance into 2 pieces

            uint256 initialBalance = address(this).balance;

            // now is to lock into pool
            Utils.swapTokensForEth(
                address(uniSwapV2Router),
                contractTokenBalance
            );

            // how much ETH did we just swap into?

            // capture the contract's current ETH balance.
            // this is so that we can capture exactly the amount of ETH that the swap creates

            uint256 deltaBalance = address(this).balance.sub(initialBalance);
            uint256 totalPercent = _marketFee.add(_poolFee);
            uint256 _marketPercent = _marketFee.mul(1e4).div(totalPercent);

            marketWallet.transfer(deltaBalance.mul(_marketPercent).div(1e4));

            emit SwapAndLiquify(
                contractTokenBalance,
                deltaBalance
            );
        }
    }
}
// Protocol by team BloctechSolutions.com
//to receive ETH from uniSwapV2Router when swapping