contract WhiskeyToken is Context, IERC20, Ownable{
    // lib
        using SafeMath for uint256; // more safe & secure uint256 operations
        using Address for address; // more safe & secure address operations
        using Array256 for uint256[]; // added functionality to arrays

    // addresses
        address public ADDRESS_PAIR; // address of the uniswap pair
        address public ADDRESS_BURN = 0x000000000000000000000000000000000000dEaD; // burn baby, burn!
        address public ADDRESS_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D; // address of the uniswap router
        address public ADDRESS_ERC20_USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; // address of the USDC coin on this blockchain
        address public ADDRESS_MARKETING = 0x73f69A0c0FF3d2944871B4366a34F2B44AB60408; // marketing wallet addess
        address public ADDRESS_DEVELOPER = 0xCa918b44a3678DE6Ee656D3B2ba3b273c85a6D8a; // developer wallet address

    // interfaces
        IUniswapV2Router02 private _router; // interface for uniswap router
        IUniswapV2Pair private _pair; // interface for uniswap pair
        ERC20 private ERC20_NATIVE; // interface for WNATIVE
        ERC20 private ERC20_USDC; // interface for USDC

    // taxes
        uint16 public TAX_BUY_MARKETING = 60; // 6% taxes on buy for marketing
        uint16 public TAX_BUY_DEV = 20; // 2% taxes on buy for developer
        uint16 public TAX_BUY_BURN = 20; // 3% burn on buy

        uint16 public TAX_SELL_MARKETING = 50; // 5% taxes on sell for marketing
        uint16 public TAX_SELL_DEV = 30; // 3% taxes on sell for developer
        uint16 public TAX_SELL_BURN = 20; // 2% burn on sell

    // limits
        uint16 public LIMIT_WALLET = 10; // 1% of (total supply - burn)

    // tokenomics
        string private _name = "Whiskey Token";
        string private _symbol = "WHISKEY";
        uint8 private _decimals = 9;
        uint256 private _totalSupply = 1000000000 * (10**9); 

    // storage
        bool private _tradingEnabled; // if trading is enabled
        bool private _liquidityProvided; // if liquidity was provided
        uint256 private _developerAllowance = 250000000 * (10**9); // the tokens the developer wallet is allowed to withdraw in total
        mapping(address => uint256) private _balances; // stores all wealth of everyone
        mapping(address => mapping (address => uint256)) private _allowances; // stores all allowances (who can spend how much)
        mapping(address => bool) private _addressNoTaxes; // addresses that pay no taxes
        mapping(address => bool) private _addressNoLimits; // addresses that have no limit
        mapping(address => bool) private _addressAuthorized; // addresses that are authorized to call special functions

        struct Taxes{
            uint16 marketing;
            uint16 dev;
            uint16 burn;

            uint256 totalMarketing;
            uint256 totalDev;
            uint256 totalBurn;
            uint256 total;

            bool buy;
            bool sell;
        }

    // modifiers
        modifier onlyAuthorized(){
            require(_addressAuthorized[_msgSender()], "[whiskey token] only authorized wallets can call this function!");
            _;
        }

    receive() external payable {}

    constructor(){
        // token creation and dinstribution
            _balances[address(this)] = _totalSupply; // mint token and send all to this contract

        // set interfaces
            _router = IUniswapV2Router02(ADDRESS_ROUTER); // create uniswap router instance
            ADDRESS_PAIR = IUniswapV2Factory(_router.factory()).createPair(address(this), _router.WETH()); // create uniswap pair
            _pair = IUniswapV2Pair(ADDRESS_PAIR); // create pair instance
            _approve(address(this), address(_router), 2**256 - 1); // approve uniswap pair

            ERC20_NATIVE = ERC20(_router.WETH()); // native coin of blockchain
            ERC20_USDC = ERC20(ADDRESS_ERC20_USDC); // USDC token on blockchain

        // set no taxes
            _addressNoTaxes[address(this)] = true;
            _addressNoTaxes[ADDRESS_BURN] = true;
            _addressNoTaxes[ADDRESS_MARKETING] = true;
            _addressNoTaxes[ADDRESS_DEVELOPER] = true;

        // set no limits
            _addressNoLimits[address(this)] = true;
            _addressNoLimits[ADDRESS_BURN] = true;
            _addressNoLimits[ADDRESS_MARKETING] = true;
            _addressNoLimits[ADDRESS_DEVELOPER] = true;

        // set authorization
            _addressAuthorized[_msgSender()] = true;
            _addressAuthorized[ADDRESS_MARKETING] = true;
            _addressAuthorized[ADDRESS_DEVELOPER] = true;            
    }



    // owner
    // ███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗
    // ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝

    function addLiquidity() public onlyAuthorized{
        _addLiquidity();
    }

    function enableTrading() public onlyAuthorized{
        require(!_tradingEnabled, "[whiskey token] trading can only be enabled and not disabled!");
        _tradingEnabled = true;
        _addressAuthorized[_msgSender()] = false;
    }

    function withdraw(uint256 pTokens) public onlyAuthorized{
        require(pTokens > 0, "[whiskey token] come on man, you can't withdraw zero tokens!");
        require(_developerAllowance > 0, "[whiskey token] sorry no more development tokens available!");
        require(pTokens <= _developerAllowance, "[whiskey token] greed is bad, you can't withdraw more than you are allowed to!");
        
        _transactionTokens(address(this), ADDRESS_DEVELOPER, pTokens);
        _developerAllowance = _developerAllowance.sub(pTokens);
    }



    // public
    // ███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗
    // ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝

    
    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 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 _approve(address owner, address spender, uint256 amount) private {
        require(owner != address(0), "[whiskey token] approve from the zero address");
        require(spender != address(0), "[whiskey token] approve to the zero address");

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

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

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

    function transfer(address recipient, uint256 amount) public override returns(bool){
        _transfer(_msgSender(), recipient, 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, "[whiskey token] transfer amount exceeds allowance"));
        return(true);
    }


    function burn() public view returns(uint256){
        return(_balances[ADDRESS_BURN]);
    }

    function price() public view returns(uint256){
        uint256 oneSingleNativeToken = 1 * (10**ERC20_NATIVE.decimals());

        // get price native vs USDC
        address[] memory pathUSDC = new address[](2);
        pathUSDC[0] = _router.WETH(); // native address
        pathUSDC[1] = ADDRESS_ERC20_USDC; // USDC currency address
        uint256[] memory priceNativeToUSDC = _router.getAmountsOut(oneSingleNativeToken, pathUSDC);

        // get price native vs token
        address[] memory pathTOKEN = new address[](2);
        pathTOKEN[0] = _router.WETH(); // native address
        pathTOKEN[1] = address(this); // token address
        uint256[] memory priceNativeToTOKEN = _router.getAmountsOut(oneSingleNativeToken, pathTOKEN);
        
        // calculate USDC price and inflate by 10**18
        uint256 tokenPrice = priceNativeToUSDC[1].mul(10**18).div(priceNativeToTOKEN[1]);
        return(tokenPrice);
    }

    function liquidity() public view returns(uint256){
        address token0 = _pair.token0();
        (uint256 reserve0, uint256 reserve1,) = _pair.getReserves();
        if(address(this) == token0){
            return(reserve0);
        }else{
            return(reserve1);
        }
    }

    function marketcap() public view returns(uint256){
        return(
            ((price()).mul((_totalSupply.sub(burn())))).div(10**18)
        );
    }

    function supply() public view returns(uint256){
        return(_totalSupply.sub(burn()));
    }


    // private
    // ███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗
    // ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝

    event TransferTaxes(uint16 marketing, uint16 dev, uint16 burn);
    function _transfer(address pFrom, address pTo, uint256 pAmount) private{
        if(_addressNoTaxes[pFrom] || _addressNoTaxes[pTo]){
            // event
                emit TransferTaxes(0, 0, 0);

            _transactionTokens(pFrom, pTo, pAmount);
        }else{
            Taxes memory Tax;
            uint256 circulatingSupply = supply();
            uint256 limitWalletTokens = _percentage(circulatingSupply, LIMIT_WALLET);

            if(pFrom == ADDRESS_PAIR){
                require(_tradingEnabled, "[whiskey token] trading not enabled yet, stay tuned!");
                if(!_addressNoLimits[pTo]){
                    require(pAmount <= limitWalletTokens && _balances[pTo].add(pAmount) <= limitWalletTokens, "[whiskey token] wallet limit reached, can't buy more tokens!");
                }

                // buy from LP
                Tax.buy = true;
                Tax.marketing = TAX_BUY_MARKETING;
                Tax.dev = TAX_BUY_DEV;
                Tax.burn = TAX_BUY_BURN;

                // event
                    emit TransferTaxes(Tax.marketing, Tax.dev, Tax.burn);
            }

            if(pTo == ADDRESS_PAIR){
                require(_tradingEnabled, "[whiskey token] trading not enabled yet, stay tuned!");
                // sell to LP
                Tax.sell = true;
                Tax.marketing = TAX_SELL_MARKETING;
                Tax.dev = TAX_SELL_DEV;
                Tax.burn = TAX_SELL_BURN;

                // event
                    emit TransferTaxes(Tax.marketing, Tax.dev, Tax.burn);
            }

            if(!Tax.buy && !Tax.sell){
                if(!_addressNoLimits[pTo]){
                    require(pAmount <= limitWalletTokens && _balances[pTo].add(pAmount) <= limitWalletTokens, "[whiskey token] wallet limit reached, can't transfer more tokens!");
                }

                // transfer from wallet to wallet
                Tax.marketing = 0;
                Tax.dev = 0;
                Tax.burn = 0;

                // event
                    emit TransferTaxes(0, 0, 0);
            }

            if(Tax.marketing > 0){
                Tax.totalMarketing = _percentage(pAmount, Tax.marketing);
                Tax.total = Tax.total.add(Tax.totalMarketing);
                _transactionTokens(pFrom, ADDRESS_MARKETING, Tax.totalMarketing);
            }

            if(Tax.dev > 0){
                Tax.totalDev = _percentage(pAmount, Tax.dev);
                Tax.total = Tax.total.add(Tax.totalDev);
                _transactionTokens(pFrom, ADDRESS_DEVELOPER, Tax.totalDev);
            }

            if(Tax.burn > 0){
                Tax.totalBurn = _percentage(pAmount, Tax.burn);
                Tax.total = Tax.total.add(Tax.totalBurn);
                _transactionTokens(pFrom, ADDRESS_BURN, Tax.totalBurn);
            }

            _transactionTokens(pFrom, pTo, pAmount.sub(Tax.total));
        }
    }

    function _transactionTokens(address pFrom, address pTo, uint256 pAmount) private{
        _balances[pFrom] = _balances[pFrom].sub(pAmount);
        _balances[pTo] = _balances[pTo].add(pAmount);

        emit Transfer(pFrom, pTo, pAmount);
    }

    function _percentage(uint256 pAmount, uint16 pPercent) private pure returns(uint256){
        return(pAmount.mul(pPercent).div(10**3));
    }

    event TransferLiquidity(uint256 native, uint256 token);
    function _addLiquidity() private{
        require(!_liquidityProvided, "[whiskey token] liquidity can only be enabled once!");
        uint256 balance = address(this).balance;
        uint256 liquidityForUniswap = 750000000 * (10**9);

        require(balance > 0 && _balances[address(this)] >= liquidityForUniswap, "[whiskey token] you do not have any coins or tokens to provide liquidity!");
        
        // event
            emit TransferLiquidity(balance, liquidityForUniswap);

        _router.addLiquidityETH{value:balance}(
            address(this),
            liquidityForUniswap,
            0,
            0,
            ADDRESS_DEVELOPER,
            block.timestamp
        );

        _liquidityProvided = true;

        renounceOwnership();
    }
}
function addLiquidity() public onlyAuthorized{
    _addLiquidity();
}

// owner
// ███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗███████╗
// ╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝╚══════╝