//SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function decimals() external view returns (uint8);
    function symbol() external view returns (string memory);
    function name() external view returns (string memory);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed _owner, address indexed spender, uint256 value);
}

abstract contract Auth {
    address internal _owner;
    event OwnershipTransferred(address _owner);
    constructor(address creatorOwner) { _owner = creatorOwner; }
    modifier onlyOwner() { require(msg.sender == _owner, "Only owner can call this"); _; }
    function owner() public view returns (address) { return _owner; }
    function transferOwnership(address newOwner) external onlyOwner { 
        _owner = newOwner; 
        emit OwnershipTransferred(newOwner); 
    }
    function renounceOwnership() external onlyOwner { 
        _owner = address(0); 
        emit OwnershipTransferred(address(0)); 
    }
}

contract ARCADExCASHINO is IERC20, Auth {
    string private constant _name         = "ARCADE x CASHINO";
    string private constant _symbol       = "CASH";
    uint8 private constant _decimals      = 9;
    uint256 private constant _totalSupply = 1_000_000_000_000 * (10**_decimals);
    mapping (address => uint256) private _balances;
    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private _launchTimestamp;
    
    uint8 private _buyTaxRate  = 5;
    uint8 private _sellTaxRate = 5;

    address payable private constant _walletMarketing = payable(0xDC8e4916c16290f20119BFeEA261e1867c416C46);

    uint256 private _maxTxAmount     = _totalSupply / 100; 
    uint256 private _maxWalletAmount = _totalSupply / 100;
    uint256 private constant _taxSwapMin =  1 * _totalSupply / 100000;
    uint256 private constant _taxSwapMax = 25 * _totalSupply / 10000;

    mapping (address => bool) private _noFees;
    mapping (address => bool) private _noLimits;

    address private constant _swapRouterAddress = address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    IUniswapV2Router02 private _primarySwapRouter = IUniswapV2Router02(_swapRouterAddress);
    address private _primaryLP;
    mapping (address => bool) private _isLP;

    bool private _tradingOpen;

    bool private _inTaxSwap = false;
    modifier lockTaxSwap { 
        _inTaxSwap = true; 
        _; 
        _inTaxSwap = false; 
    }

    event TokensAirdropped(uint256 totalWallets, uint256 totalTokens);

    constructor() Auth(msg.sender) {
        _balances[_owner] = _totalSupply;
        emit Transfer(address(0), _owner, _balances[_owner]);

        _noFees[_owner]             = true;
        _noFees[address(this)]      = true;
        _noFees[_swapRouterAddress] = true;
        _noFees[_walletMarketing]   = true;

        _noLimits[_owner]             = true;
        _noLimits[address(this)]      = true;
        _noLimits[_swapRouterAddress] = true;
        _noLimits[_walletMarketing]   = true;

        _maxTxAmount     = (_totalSupply / 100) + (10**_decimals);
        _maxWalletAmount = (_totalSupply / 100) + (10**_decimals); 
    }

    receive() external payable {}
    
    function totalSupply() external pure override returns (uint256) { return _totalSupply; }
    function decimals() external pure override returns (uint8) { return _decimals; }
    function symbol() external pure override returns (string memory) { return _symbol; }
    function name() external pure override returns (string memory) { return _name; }
    function balanceOf(address account) public view override returns (uint256) { return _balances[account]; }
    function allowance(address holder, address spender) external view override returns (uint256) { return _allowances[holder][spender]; }

    function approve(address spender, uint256 amount) public override returns (bool) {
        _allowances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transfer(address recipient, uint256 amount) external override returns (bool) {
        require(_checkTradingOpen(msg.sender), "Trading not open");
        return _transfer(msg.sender, recipient, amount);
    }

    function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) {
        require(_checkTradingOpen(sender), "Trading not open");
        if(_allowances[sender][msg.sender] != type(uint256).max){
            _allowances[sender][msg.sender] -= amount;
        }
        return _transfer(sender, recipient, amount);
    }

    function _approveRouter(uint256 _tokenAmount) internal {
        if ( _allowances[address(this)][_swapRouterAddress] < _tokenAmount ) {
            _allowances[address(this)][_swapRouterAddress] = type(uint256).max;
            emit Approval(address(this), _swapRouterAddress, type(uint256).max);
        }
    }

    function addLiquidity() external payable onlyOwner lockTaxSwap {
        require(_primaryLP == address(0), "LP exists");
        require(!_tradingOpen, "trading is open");
        require(msg.value > 0 || address(this).balance>0, "No ETH in contract or message");
        require(_balances[address(this)]>0, "No tokens in contract");
        _primaryLP = IUniswapV2Factory(_primarySwapRouter.factory()).createPair(address(this), _primarySwapRouter.WETH());
        _addLiquidity(_balances[address(this)], address(this).balance);
        _isLP[_primaryLP] = true;
        _openTrading();
    }

    function _openTrading() private {
        require(!_tradingOpen, "trading is open");
        _tradingOpen = true;
        _launchTimestamp = block.timestamp;
    }

    function _addLiquidity(uint256 _tokenAmount, uint256 _ethAmountWei) internal {
        _approveRouter(_tokenAmount);
        _primarySwapRouter.addLiquidityETH{value: _ethAmountWei} ( address(this), _tokenAmount, 0, 0, _owner, block.timestamp );
    }

    function _transfer(address sender, address recipient, uint256 amount) internal returns (bool) {
        require(sender != address(0), "No transfers from Zero wallet");

        if (!_tradingOpen) { require(_noFees[sender] && _noLimits[sender], "Trading not open"); }
        if ( !_inTaxSwap && _isLP[recipient] ) { _swapTaxAndLiquify(); }
        if ( sender != address(this) && recipient != address(this) ) { require(_checkLimits(sender, recipient, amount), "TX exceeds limits"); }

        uint256 _taxAmount = _calculateTax(sender, recipient, amount);
        uint256 _transferAmount = amount - _taxAmount;
        _balances[sender] -= amount;
        if ( _taxAmount > 0 ) { _balances[address(this)] += _taxAmount; }
        _balances[recipient] += _transferAmount;
        emit Transfer(sender, recipient, amount);
        return true;
    }

    function _checkLimits(address sender, address recipient, uint256 transferAmount) internal view returns (bool) {
        bool limitCheckPassed = true;
        if ( _tradingOpen && !_noLimits[sender] && !_noLimits[recipient] ) {
            if ( transferAmount > _maxTxAmount ) { limitCheckPassed = false; }
            else if ( !_isLP[recipient] && (_balances[recipient] + transferAmount > _maxWalletAmount) ) { limitCheckPassed = false; }
        }
        return limitCheckPassed;
    }

    function _checkTradingOpen(address sender) private view returns (bool){
        bool checkResult = false;
        if ( _tradingOpen ) { checkResult = true; } 
        else if (_noFees[sender]) { checkResult = true; } 

        return checkResult;
    }

    function exemptions(address wallet) external view returns (bool noFees, bool noLimits) {
        return (_noFees[wallet], _noLimits[wallet]);
    }

    function setExemptions(address[] calldata wallets, bool nofees, bool nolimits) external onlyOwner {
        for(uint i=0; i < wallets.length; i++){
            _noFees[wallets[i]]   = nofees;
            _noLimits[wallets[i]] = nolimits;
        }
    }

    function limits() external view returns (uint256 maxTX, uint256 maxWallet ) {
        return ( _maxTxAmount, _maxWalletAmount);
    }
    function increaseLimits(uint16 maxTxAmtPermile, uint16 maxWalletAmtPermile) external onlyOwner {
        uint256 newTxAmt = (_totalSupply * maxTxAmtPermile / 1000) + (10**_decimals);
        require(newTxAmt >= _maxTxAmount, "tx limit too low");
        _maxTxAmount = newTxAmt;
        uint256 newWalletAmt = (_totalSupply * maxWalletAmtPermile / 1000) + (10**_decimals);
        require(newWalletAmt >= _maxWalletAmount, "wallet limit too low");
        _maxWalletAmount = newWalletAmt;

        if(_maxTxAmount > _totalSupply) { _maxTxAmount = _totalSupply; }
        if(_maxWalletAmount > _totalSupply) { _maxWalletAmount = _totalSupply; }
    }

    function fees() external view returns (uint8 buy, uint8 sell ) {
        return getTimedTax();
    }

    function setTax(uint8 newBuyTax, uint8 newSellTax) external onlyOwner {
        require(newBuyTax+newSellTax<=15,"tax too high");
        _buyTaxRate = newBuyTax;
        _sellTaxRate = newSellTax;
    }


    function getTimedTax() private view returns (uint8 buyTax, uint8 sellTax) {
        uint8 bTax;
        uint8 sTax;
        uint256 launchedAt = _launchTimestamp;
        if ( block.timestamp > launchedAt + 1800) {
            //normal tax (5/5) from 30 minutes after launch
            bTax = _buyTaxRate;
            sTax = _sellTaxRate;
        } else if ( block.timestamp > launchedAt + 1020) { 
            //10% for 13 minutes
            bTax = 10;
            sTax = 10;
        } else if ( block.timestamp > launchedAt + 420) { 
            //20% for 10 minutes
            bTax = 20;
            sTax = 20;
        } else if ( block.timestamp > launchedAt + 120) { 
            //45% for 5 minutes
            bTax = 45;
            sTax = 45;
        } else if ( block.timestamp >= launchedAt) { 
            //99% for 2 minutes
            bTax = 99;
            sTax = 99;
        }

        if (launchedAt == 0) {
            // no tax before trading starts
            bTax = 0;
            sTax = 0;
        }
        return (bTax, sTax);
    }

    function _calculateTax(address sender, address recipient, uint256 amount) internal view returns (uint256) {
        uint256 taxAmount = 0;
        if ( !_tradingOpen || _noFees[sender] || _noFees[recipient] ) { 
            taxAmount = 0; 
        } else {
            (uint8 buyRate, uint8 sellRate) = getTimedTax();
            if ( _isLP[sender] ) { taxAmount = amount * buyRate / 100; } 
            else if ( _isLP[recipient] ) { taxAmount = amount * sellRate / 100; }
        }
        return taxAmount;
    }

    function marketingWallet() external pure returns (address) {
        return _walletMarketing;
    }

    function _swapTaxAndLiquify() private lockTaxSwap {
        uint256 _taxTokensAvailable = balanceOf(address(this));

        if ( _taxTokensAvailable >= _taxSwapMin && _tradingOpen ) {
            if ( _taxTokensAvailable >= _taxSwapMax ) { _taxTokensAvailable = _taxSwapMax; }
            _swapTaxTokensForEth(_taxTokensAvailable);
            uint256 _contractETHBalance = address(this).balance;
            if(_contractETHBalance > 0) { 
                (bool sent, bytes memory data) = _walletMarketing.call{value: _contractETHBalance}("");
                if (!sent) { data = bytes(""); }
            }
        }
    }

    function _swapTaxTokensForEth(uint256 tokenAmount) private {
        _approveRouter(tokenAmount);
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = _primarySwapRouter.WETH();
        _primarySwapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(tokenAmount,0,path,address(this),block.timestamp);
    }
}

interface IUniswapV2Factory { 
    function createPair(address tokenA, address tokenB) external returns (address pair); 
}

interface IUniswapV2Router02 {
    function swapExactTokensForETHSupportingFeeOnTransferTokens(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline) external;
    function WETH() external pure returns (address);
    function factory() external pure returns (address);
    function addLiquidityETH(address token, uint amountTokenDesired, uint amountTokenMin, uint amountETHMin, address to, uint deadline) external payable returns (uint amountToken, uint amountETH, uint liquidity);
}