// Contract: ApeGateFinance
// Target function: function airdropToWallets(
256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountToken, uint256 amountETH);

    function swapExactTokensForTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapTokensForExactTokens(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactETHForTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function swapTokensForExactETH(
        uint256 amountOut,
        uint256 amountInMax,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapExactTokensForETH(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external returns (uint256[] memory amounts);

    function swapETHForExactTokens(
        uint256 amountOut,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable returns (uint256[] memory amounts);

    function quote(
        uint256 amountA,
        uint256 reserveA,
        uint256 reserveB
    ) external pure returns (uint256 amountB);

    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountOut);

    function getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) external pure returns (uint256 amountIn);

    function getAmountsOut(uint256 amountIn, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);

    function getAmountsIn(uint256 amountOut, address[] calldata path)
        external
        view
        returns (uint256[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline
    ) external returns (uint256 amountETH);

    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint256 liquidity,
        uint256 amountTokenMin,
        uint256 amountETHMin,
        address to,
        uint256 deadline,
        bool approveMax,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (uint256 amountETH);

    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external payable;

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;

    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint256 amountIn,
        uint256 amountOutMin,
        address[] calldata path,
        address to,
        uint256 deadline
    ) external;
}

// Core Logic
// --------------------------------------------------------------
contract ApeGateFinance is Context, IERC20, Ownable {
    // Imports
    using SafeMath for uint256;
    using Address for address;

    // Context
    string private _name = "Ape Gate Finance";
    string private _symbol = "APEGATE";
    uint8 private _decimals = 18;

    // Supply
    uint256 private _totalSupply = 1 * 1e9 * 1e18;
    uint256 private minimumTokensBeforeSwap = (_totalSupply * 25) / 100000;

    // Restrictions
    uint256 public _maxTxAmount = (_totalSupply * 1) / 100;
    uint256 public _walletMax = (_totalSupply * 2) / 100;
    bool public checkWalletLimit = true;

    // wallets
    address payable public liquidityWallet =
        payable(0x60707C54B324173C744dA86e096f1b9816D7aFd4);
    address payable public buybackWallet =
        payable(0x60707C54B324173C744dA86e096f1b9816D7aFd4);
    address public immutable deadAddress =
        0x000000000000000000000000000000000000dEaD;

    // fees
    uint256 public liquidityFeeBuy = 0;
    uint256 public buybackFeeBuy = 1;
    uint256 public totalFeesBuy;
    uint256 public maxTotalFeeBuy = 2;

    // fees
    uint256 public liquidityFeeSell = 0;
    uint256 public buybackFeeSell = 3;
    uint256 public totalFeesSell;
    uint256 public maxTotalFeeSell = 4;

    // distribution ratio
    uint256 public _liquiditySharePercentage = 0;
    uint256 public _operationsSharePercentage = 100;
    uint256 public _totalDistributionShares;

    // max amounts
    mapping(address => uint256) _balances;
    mapping(address => mapping(address => uint256)) private _allowances;
    mapping(address => bool) public isExcludedFromFee;
    mapping(address => bool) public isWalletLimitExempt;
    mapping(address => bool) isTxLimitExempt;

    // Router Information
    mapping(address => bool) public isMarketPair;
    IUniswapV2Router02 public uniswapV2Router;
    address public uniswapPair;

    // toggle swap back (fees)
    bool inSwapAndLiquify;
    uint256 public tokensForLiquidity;
    uint256 public tokensForOperations;

    // Launch Settings
    bool public tradingOpen = false;

    // events
    event LiquidityWalletUpdated(
        address indexed newLiquidityWallet,
        address indexed oldLiquidityWallet
    );
    event BuybackWalletUpdated(
        address indexed newOperationsWallet,
        address indexed oldOperationsWallet
    );
    event SwapAndLiquify(
        uint256 tokensSwapped,
        uint256 ethReceived,
        uint256 tokensIntoLiqudity
    );
    event SwapTokensForETH(uint256 amountIn, address[] path);

    // toogle to stop swap if already underway
    modifier lockTheSwap() {
        inSwapAndLiquify = true;
        _;
        inSwapAndLiquify = false;
    }

    constructor() {
        // load total fees
        totalFeesBuy = buybackFeeBuy + liquidityFeeBuy;
        totalFeesSell = buybackFeeSell + liquidityFeeSell;

        // load total distribution
        _totalDistributionShares =
            _liquiditySharePercentage +
            _operationsSharePercentage;

        // create router ------------------------------
        IUniswapV2Router02 _uniswapV2Router;
        if (block.chainid == 1) {
            _uniswapV2Router = IUniswapV2Router02(
                0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
            );
        } else if (block.chainid == 5) {
            _uniswapV2Router = IUniswapV2Router02(
                0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D
            );
        } else revert();

        // Create a uniswap pair for this new token
        uniswapPair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(
            address(this),
            _uniswapV2Router.WETH()
        );
        uniswapV2Router = _uniswapV2Router;
        isMarketPair[address(uniswapPair)] = true;

        // set allowances
        _allowances[address(this)][address(uniswapV2Router)] = _totalSupply;

        // exclude from paying fees or having max transaction amount
        isExcludedFromFee[owner()] = true;
        isExcludedFromFee[address(this)] = true;
        isExcludedFromFee[address(0xdead)] = true;
        isExcludedFromFee[liquidityWallet] = true;
        isExcludedFromFee[buybackWallet] = true;

        // exclude contracts from max wallet size
        isWalletLimitExempt[owner()] = true;
        isWalletLimitExempt[address(uniswapPair)] = true;
        isWalletLimitExempt[address(this)] = true;
        isWalletLimitExempt[liquidityWallet] = true;
        isWalletLimitExempt[buybackWallet] = true;

        // exclude contracts from max wallet size
        isTxLimitExempt[owner()] = true;
        isTxLimitExempt[address(uniswapPair)] = true;
        isTxLimitExempt[address(this)] = true;
        isTxLimitExempt[liquidityWallet] = true;
        isTxLimitExempt[buybackWallet] = true;

        _balances[_msgSender()] = _totalSupply;
        emit Transfer(address(0), _msgSender(), _totalSupply);
    }

    receive() external payable {}

    // @dev Public read functions start -------------------------------------
    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];
    }

    // custom allowance methods
    function allowance(address owner, address spender)
        public
        view
        override
        returns (uint256)
    {
        return _allowances[owner][spender];
    }

    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,
                "ERC20: decreased allowance below zero"
            )
        );
        return true;
    }

    bool isFeeCheck = false;

    // get minimum tokens before swap
    function minimumTokensBeforeSwapAmount() public view returns (uint256) {
        return minimumTokensBeforeSwap;
    }

    // approve spending methods
    function approve(address spender, uint256 amount)
        public
        override
        returns (bool)
    {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function _approve(
        address owner,
        address spender,
        uint256 amount
    ) private {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");
        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function getCirculatingSupply() public view returns (uint256) {
        return _totalSupply.sub(balanceOf(deadAddress));
    }

    function getBlock() public view returns (uint256) {
        return block.number;
    }

    // @dev Owner functions start -------------------------------------

    // toogle market pair status
    function setMarketPairStatus(address account, bool newValue)
        public
        onlyOwner
    {
        isMarketPair[account] = newValue;
    }

    // set excluded xx limit
    function setIsTxLimitExempt(address holder, bool exempt)
        external
        onlyOwner
    {
        isTxLimitExempt[holder] = exempt;
    }

    // update max tx amount
    function setMaxTxAmount(uint256 maxTxRate) external onlyOwner {
        require(maxTxRate >= 1);
        _maxTxAmount = (_totalSupply * maxTxRate) / 100;
    }

    // set excluded tax
    function setIsExcludedFromFee(address account, bool newValue)
        public
        onlyOwner
    {
        isExcludedFromFee[account] = newValue;
    }

    // update fees
    function updateFees(uint256 _operationsFee, uint256 _liquidityFee)
        external
        onlyOwner
    {
        _updateFeesBuy(_operationsFee, _liquidityFee);
        require(
            totalFeesBuy <= maxTotalFeeBuy,
            "Must keep fees at maxTotalFeeBuy or less"
        );
    }

    function updateFeesSell(uint256 _operationsFee, uint256 _liquidityFee)
        external
        onlyOwner
    {
        _updateFeesSell(_operationsFee, _liquidityFee);
        require(
            totalFeesSell <= maxTotalFeeSell,
            "Must keep fees at maxTotalFeeSell or less"
        );
    }

    function _updateFeesBuy(uint256 _operationsFee, uint256 _liquidityFee)
        private
    {
        buybackFeeBuy = _operationsFee;
        liquidityFeeBuy = _liquidityFee;
        totalFeesBuy = buybackFeeBuy + liquidityFeeBuy;
    }

    function _updateFeesSell(uint256 _operationsFee, uint256 _liquidityFee)
        private
    {
        buybackFeeSell = _operationsFee;
        liquidityFeeSell = _liquidityFee;
        totalFeesSell = buybackFeeSell + liquidityFeeSell;
    }

    // set distribution settings
    function setDistributionSettings(
        uint256 newLiquidityShare,
        uint256 newOperationsShare
    ) external onlyOwner {
        _liquiditySharePercentage = newLiquidityShare;
        _operationsSharePercentage = newOperationsShare;
        _totalDistributionShares =
            _liquiditySharePercentage +
            _operationsSharePercentage;
        require(
            _totalDistributionShares == 100,
            "Distribution needs to total to 100"
        );
    }

    // remove wallet limit
    function enableDisableWalletLimit(bool newValue) external onlyOwner {
        checkWalletLimit = newValue;
    }

    // set excluded wallet limit
    function setIsWalletLimitExempt(address holder, bool exempt)
        external
        onlyOwner
    {
        isWalletLimitExempt[holder] = exempt;
    }

    // update wallet limit
    function setWalletLimit(uint256 newLimit) external onlyOwner {
        _walletMax = newLimit;
    }

    // change the minimum amount of tokens to sell from fees
    function setNumTokensBeforeSwap(uint256 newLimit) external onlyOwner {
        minimumTokensBeforeSwap = newLimit;
    }

    function updateLiquidityWallet(address newAddress) external onlyOwner {
        require(newAddress != address(0), "Cannot set to address 0");
        isExcludedFromFee[newAddress] = true;
        isExcludedFromFee[liquidityWallet] = false;
        emit LiquidityWalletUpdated(newAddress, liquidityWallet);
        liquidityWallet = payable(newAddress);
    }

    function updateBuybackWallet(address newAddress) external onlyOwner {
        require(newAddress != address(0), "Cannot set to address 0");
        isExcludedFromFee[newAddress] = true;
        isExcludedFromFee[buybackWallet] = false;
        emit BuybackWalletUpdated(newAddress, buybackWallet);
        buybackWallet = payable(newAddress);
    }

    // transfer amount to address
    function transferToAddressETH(address payable recipient, uint256 amount)
        private
    {
        recipient.transfer(amount);
    }

    // change router address
    function changeRouterAddress(address newRouterAddress)
        public
        onlyOwner
        returns (address newPairAddress)
    {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(
            newRouterAddress
        );
        newPairAddress = IUniswapV2Factory(_uniswapV2Router.factory()).getPair(
            address(this),
            _uniswapV2Router.WETH()
        );

        // check if new pair deployed
        if (newPairAddress == address(0)) {
            newPairAddress = IUniswapV2Factory(_uniswapV2Router.factory())
                .createPair(address(this), _uniswapV2Router.WETH());
        }

        uniswapPair = newPairAddress;
        uniswapV2Router = _uniswapV2Router;
        isWalletLimitExempt[address(uniswapPair)] = true;
        isMarketPair[address(uniswapPair)] = true;
    }

    // once enabled, can never be turned off
    function setTrading() public onlyOwner {
        tradingOpen = true;
    }