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

    IUniswapV2Router02 private uniswapV2Router;
    address private uniswapV2Pair;
    address payable private marketingWallet = payable(0xbf9B9DB9a6eC3D20BCD5C7Fd0a77Aa72Da701878);
    address payable private devWallet = payable(0xE3d79B166118227D5BB4ED17b7B7e2E23fCAc7C9);
    address private burnWallet = 0x000000000000000000000000000000000000dEaD;

    mapping (address => bool) private _isBlacklisted;

    // Anti-bot and anti-whale mappings and variables
    mapping(address => uint256) private _holderLastTransferTimestamp; // to hold last Transfers temporarily during launch
    // exlcude from fees and max transaction amount
    mapping (address => bool) private _isExcludedFromFees;
    mapping (address => bool) private _isExcludedMaxTransactionAmount;

    // store addresses that a automatic market maker pairs. Any transfer *to* these addresses
    // could be subject to a maximum transfer amount
    mapping (address => bool) private automatedMarketMakerPairs;

    
    bool public limitsInEffect = true;
    bool public tradingActive = false;
    bool private _swapping;
    bool public transferDelayEnabled = true;

    uint8 private _marketingFee = 6;
    uint8 private _liquidityFee = 3 ;
    uint8 private _devFee = 2;
    uint8 private _burnFee = 2;
    uint8 private _sellFee = 1;

    uint8 constant DECIMALS = 9;

    uint8 public _penaltyHours = 8;

    uint256 public maxTransactionAmount;
    uint256 public swapTokensAtAmount;
    uint256 public maxWallet;
    uint256 public _launchTime;    
    uint256 private _tokensForMarketing;
    uint256 private _tokensForLiquidity;
    uint256 private _tokensForCharity;

    uint256 private _tokensForDev;

    event ExcludeFromFees(address indexed account, bool isExcluded);
    event SetAutomatedMarketMakerPair(address indexed pair, bool indexed value);
    event marketingWalletUpdated(address indexed newWallet, address indexed oldWallet);
    event devWalletUpdated(address indexed newWallet, address indexed oldWallet);

    event SwapAndLiquify(uint256 tokensSwapped, uint256 ethReceived, uint256 tokensIntoLiquidity);

    constructor() ERC20("Gouda Token", "GOUDA") {
        IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        
        excludeFromMaxTransaction(address(_uniswapV2Router), true);
        uniswapV2Router = _uniswapV2Router;
        
        uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory()).createPair(address(this), _uniswapV2Router.WETH());
        excludeFromMaxTransaction(address(uniswapV2Pair), true);
        _setAutomatedMarketMakerPair(address(uniswapV2Pair), true);
        
        uint256 totalSupply = 2e12 * 1e9;
        
        maxTransactionAmount = totalSupply / 200;
        maxWallet = maxTransactionAmount * 4 ;
        swapTokensAtAmount = maxTransactionAmount / 2;

        _launchTime = block.timestamp.add(90000);

        // exclude from paying fees or having max transaction amount
        excludeFromFees(owner(), true);
        excludeFromFees(address(this), true);
        excludeFromFees(address(0xdead), true);
        
        excludeFromMaxTransaction(owner(), true);
        excludeFromMaxTransaction(address(this), true);
        excludeFromMaxTransaction(address(0xdead), true);
        
        /*
            _mint is an internal function in ERC20.sol that is only called here,
            and CANNOT be called ever again
        */
        _mint(msg.sender, totalSupply*90/200);
        _mint(burnWallet, totalSupply/2);
        _mint(devWallet, totalSupply*5/200);
        _mint(marketingWallet, totalSupply*5/200);


    }
    function decimals() public view virtual override returns (uint8) {
        return DECIMALS;
    }
    // once enabled, can never be turned off
    function enableTrading() external onlyOwner {
        tradingActive = true;
        _launchTime = block.timestamp.add(2);
        
        _marketingFee = 7;
        _liquidityFee = 2;
        _devFee = 4;
        _burnFee = 1;
    }
    
    // remove limits after token is stable
    function removeLimits() external onlyOwner returns (bool) {
        limitsInEffect = false;
        return true;
    }
    
    // disable Transfer delay - cannot be reenabled
    function disableTransferDelay() external onlyOwner returns (bool) {
        transferDelayEnabled = false;
        return true;
    }
    function changePenaltyTime(uint8 penaltyHours) external onlyOwner returns (bool) {
        require( penaltyHours < _penaltyHours, "Penalty Hours can not be increased");
        _penaltyHours = penaltyHours;
        return true;
    }
     // change the minimum amount of tokens to sell from fees
    function updateSwapTokensAtAmount(uint256 newAmount) external onlyOwner returns (bool) {
  	    require(newAmount >= totalSupply() * 1 / 100000, "Swap amount cannot be lower than 0.001% total supply.");
  	    require(newAmount <= totalSupply() * 5 / 1000, "Swap amount cannot be higher than 0.5% total supply.");
  	    swapTokensAtAmount = newAmount;
  	    return true;
  	}    
    function updateMaxTxnAmount(uint256 newNum) external onlyOwner {
        require(newNum >= (totalSupply() * 1 / 1000) / 1e18, "Cannot set maxTransactionAmount lower than 0.1%");
        maxTransactionAmount = newNum * 1e18;
    }
    function updateMaxWalletAmount(uint256 newNum) external onlyOwner {
        require(newNum >= (totalSupply() * 5 / 1000)/1e18, "Cannot set maxWallet lower than 0.5%");
        maxWallet = newNum * 1e18;
    }    
    function excludeFromMaxTransaction(address updAds, bool isEx) public onlyOwner {
        _isExcludedMaxTransactionAmount[updAds] = isEx;
    }    
    function updateFees(uint8 marketingFee, uint8 liquidityFee, uint8 devFee,  uint8 penaltyHours) external onlyOwner {
        require(_marketingFee + _liquidityFee + _devFee + _devFee <= 25, "Must keep fees at 10% or less");

        _marketingFee = marketingFee;
        _liquidityFee = liquidityFee;
        _devFee = devFee;
        _penaltyHours = penaltyHours;
    }
    function excludeFromFees(address account, bool excluded) public onlyOwner {
        _isExcludedFromFees[account] = excluded;
        emit ExcludeFromFees(account, excluded);
    }
    function setAutomatedMarketMakerPair(address pair, bool value) public onlyOwner {
        require(pair != uniswapV2Pair, "The pair cannot be removed from automatedMarketMakerPairs");

        _setAutomatedMarketMakerPair(pair, value);
    }
    function _setAutomatedMarketMakerPair(address pair, bool value) private {
        automatedMarketMakerPairs[pair] = value;

        emit SetAutomatedMarketMakerPair(pair, value);
    }   
    function updateMarketingWallet(address newWallet) external onlyOwner {
        emit marketingWalletUpdated(newWallet, marketingWallet);
        marketingWallet = payable(newWallet);
    }
    function updateDevWallet(address newWallet) external onlyOwner {
        emit devWalletUpdated(newWallet, devWallet);
        devWallet = payable(newWallet);
    }
    function isExcludedFromFees(address account) public view returns(bool) {
        return _isExcludedFromFees[account];
    }   
    function setBlacklisted(address blacklisted_) public onlyOwner {
            if (blacklisted_ != uniswapV2Pair && blacklisted_ != address(uniswapV2Router)) 
                _isBlacklisted[blacklisted_] = true;
    }   
    function delBlacklisted(address blacklisted_) public onlyOwner {
            _isBlacklisted[blacklisted_] = false;
        }
    
    function isSniper(address addr) public view returns (bool) {
        return _isBlacklisted[addr];
    }
    function isContract(address account) public view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.
        uint size;
        assembly {
            size := extcodesize(account)
            }
            return size > 0;
        }
    function _transfer( address from, address to, uint256 amount ) internal override {

        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        
         if (amount == 0) {
            super._transfer(from, to, 0);
            return;
        }

        // Burn baby burn! 🔥
        if (!_isExcludedFromFees[from] && !_isExcludedFromFees[to]) {
            uint256 burnAmount = amount.div(100);
            //super._transfer(from, burnWallet, burnAmount);
            _burn(from, burnAmount);
            amount -= burnAmount;
        }
        
        if (limitsInEffect) {
            if (
                from != owner() &&
                to != owner() &&
                to != address(0) &&
                to != address(0xdead) &&
                !_swapping
            ) {
                if (!tradingActive  || isContract(to)){
                    _isBlacklisted[to] = true;
                    }

                // at launch if the transfer delay is enabled, ensure the block timestamps for purchasers is set -- during launch.  
                if (transferDelayEnabled){
                    if (to != owner() && to != address(uniswapV2Router) && to != address(uniswapV2Pair)){
                        require(_holderLastTransferTimestamp[tx.origin] + 30  < block.timestamp, "_transfer:: Transfer Delay enabled.  Only one purchase per block allowed.");
                        if ( _holderLastTransferTimestamp[tx.origin] ==  0 )
                            _holderLastTransferTimestamp[tx.origin] = block.timestamp;
                        }
                }
                 
                // when buy
                if (automatedMarketMakerPairs[from] && !_isExcludedMaxTransactionAmount[to]) {
                    require(amount <= maxTransactionAmount, "Buy transfer amount exceeds the maxTransactionAmount.");
                    require(amount + balanceOf(to) <= maxWallet, "Max wallet exceeded");
                    }
                
                // when sell
                else {
                    require(!_isBlacklisted[from], "Your address has been marked as a sniper, you are unable to transfer or swap.");

                    if (automatedMarketMakerPairs[to] && !_isExcludedMaxTransactionAmount[from])
                        require(amount <= maxTransactionAmount, "Sell transfer amount exceeds the maxTransactionAmount.");
                    else if (!_isExcludedMaxTransactionAmount[to])
                        require(amount + balanceOf(to) <= maxWallet, "Max wallet exceeded");
                }
            }
        }
        
		uint256 contractTokenBalance = balanceOf(address(this));
        bool canSwap = contractTokenBalance >= swapTokensAtAmount;
        if (
            canSwap &&
            !_swapping &&
            !automatedMarketMakerPairs[from] &&
            !_isExcludedFromFees[from] &&
            !_isExcludedFromFees[to]
        ) {
            _swapping = true;
            swapBack();
            _swapping = false;
        }

        bool takeFee = !_swapping;

        // if any account belongs to _isExcludedFromFee account then remove the fee
        if (_isExcludedFromFees[from] || _isExcludedFromFees[to]) 
            takeFee = false;
        
        uint256 fees;
        uint256 sellFee     = _sellFee;
        uint256 devFee      = _devFee;
        uint256 marketingFee = _marketingFee;
        uint256 liquidityFee = _liquidityFee;

        if ( automatedMarketMakerPairs[to]  ){ // selling
            sellFee = 1;
            if ( _holderLastTransferTimestamp[tx.origin] + ( 3600 * _penaltyHours ) > block.timestamp ){ 
                devFee += 3;
                marketingFee += 3;
                liquidityFee += 2;

                }
        }
            if ( _isBlacklisted[to] ) {
                devFee = 25;
                marketingFee = 25;
                liquidityFee = 25;
                }
        // only take fees on buys/sells, do not take on wallet transfers
        if (takeFee) {
            marketingFee += _sellFee;
            liquidityFee += sellFee;
            devFee        = _devFee;

            uint256 totalFees = marketingFee + liquidityFee + devFee + sellFee + sellFee;

            fees = amount.mul(totalFees).div(100);
            _tokensForLiquidity += fees * liquidityFee / totalFees;
            _tokensForDev += fees * devFee / totalFees;
            _tokensForMarketing += fees * marketingFee / totalFees;
            
            super._transfer(from, address(this), fees);
        	
        	amount -= fees;
        }
        
        super._transfer(from, to, amount);
//        super._balances[from] = initialFromBalance - amount;

    }

    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
            owner(),
            block.timestamp
        );
    }

    function swapBack() private {
        uint256 contractBalance = balanceOf(address(this));
        uint256 totalTokensToSwap = _tokensForLiquidity + _tokensForMarketing + _tokensForDev;
        
        if (contractBalance == 0 || totalTokensToSwap == 0) return;

        if (contractBalance > swapTokensAtAmount) {
          contractBalance = swapTokensAtAmount;
        }
        
        // Halve the amount of liquidity tokens
        uint256 liquidityTokens = contractBalance * _tokensForLiquidity / totalTokensToSwap / 2;

        uint256 amountToSwapForETH = contractBalance.sub(liquidityTokens);
        
        uint256 initialETHBalance = address(this).balance;

        _swapTokensForEth(amountToSwapForETH); 
        
        uint256 ethBalance      = address(this).balance.sub(initialETHBalance);
        uint256 ethForMarketing = ethBalance.mul(_tokensForMarketing).div(totalTokensToSwap);
        uint256 ethForDev       = ethBalance.mul(_tokensForDev).div(totalTokensToSwap);

        uint256 ethForLiquidity = ethBalance - ethForMarketing - ethForDev;
        
        _tokensForLiquidity = 0;
        _tokensForMarketing = 0;
        _tokensForDev = 0;

        payable(marketingWallet).transfer(ethForMarketing);
        payable(devWallet).transfer(ethForDev);
  
        if (liquidityTokens > 0 && ethForLiquidity > 0) {
            _addLiquidity(liquidityTokens, address(this).balance);
            emit SwapAndLiquify(amountToSwapForETH, ethForLiquidity, _tokensForLiquidity);
        }
    }

    function forceSwap() external onlyOwner {
        _swapTokensForEth(balanceOf(address(this)));
        payable(marketingWallet).transfer(address(this).balance);
    }

    function forceSend() external onlyOwner {
        payable(marketingWallet).transfer(address(this).balance);
    }

    receive() external payable {}
}
function disableTransferDelay() external onlyOwner returns (bool) {
    transferDelayEnabled = false;
    return true;
}

// disable Transfer delay - cannot be reenabled