contract Bog is Owned, ERC20Permit {
    using Strings for uint256;

    address public WETH;
    mapping(address => bool) public isExcludedFromFee;
    bool public feesEnabled;
    bool public swapEnabled;

    address public uniswapPair;

    // combine recipient and fee into a single storage slot
    uint256 public beneficiary;
    uint256 public staking;

    event BeneficiaryRewardUpdated(address oldBeneficiary, address newBeneficiary, uint256 oldFee, uint256 newFee);
    event StakingRewardUpdated(address oldBeneficiary, address newBeneficiary, uint256 oldFee, uint256 newFee);
    event FeesEnabledUpdated(bool enabled);
    event SwapEnabledUpdated(bool enabled);
    event ExcludedFromFeeUpdated(address account, bool excluded);

    modifier ensureAddressSet(address _beneficiary, uint256 _fee) {
        if (_fee > 0) {
            require(_beneficiary != address(0), "address not set");
        } else {
            require(_beneficiary == address(0), "set address to zero");
        }
        _;
    }

    constructor(
        uint256 _totalSupply,
        address _beneficiary,
        string memory _name,
        string memory _symbol,
        uint256 _initial_fee
    ) ERC20(_name, _symbol) ERC20Permit(_name) {
        _mint(_msgSender(), _totalSupply);

        // calculate future Uniswap V2 pair address
        address uniswapFactory = router().factory();
        address _WETH = router().WETH();
        WETH = _WETH;
        // calculate future uniswap pair address
        (address token0, address token1) = (_WETH < address(this) ? (_WETH, address(this)) : (address(this), _WETH));
        address pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            hex"ff",
                            uniswapFactory,
                            keccak256(abi.encodePacked(token0, token1)),
                            hex"96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f"
                        )
                    )
                )
            )
        );
        uniswapPair = pair;
        beneficiary = packBeneficiary(_beneficiary, _initial_fee);
        isExcludedFromFee[address(this)] = true;
        isExcludedFromFee[_beneficiary] = true;
        isExcludedFromFee[_msgSender()] = true;
    }

    receive() external payable {
        // Do nothing
    }

    /**
     * @notice adds or removes an account that is exempt from fee collection
     * @dev only callable by owner
     * @param _account account to modify
     * @param _excluded new value
     */
    function setExcludeFromFee(address _account, bool _excluded) public virtual onlyOwner {
        isExcludedFromFee[_account] = _excluded;
        emit ExcludedFromFeeUpdated(_account, _excluded);
    }

    /**
     * @dev helper function to pack address and fee into one storage slot
     */
    function packBeneficiary(address _beneficiary, uint256 _fee) public pure virtual returns (uint256) {
        require(_fee <= 10000, "INVALID_FEE");
        uint256 storedBeneficiary = uint256(uint160(_beneficiary));
        storedBeneficiary |= _fee << 160;
        return storedBeneficiary;
    }

    /**
     * @dev helper function to unpack address and fee from single storage slot
     */
    function unpackBeneficiary(uint256 _beneficiary) public pure virtual returns (address, uint256) {
        return (address(uint160(_beneficiary)), uint256(uint96(_beneficiary >> 160)));
    }

    /**
     * @notice sets recipient and fee amount of transfer fee
     * @dev excludes new beneficiary from fee
     * @dev only callable by owner
     * @param _newBeneficiary address of new beneficiary
     * @param _fee            fee sent to new beneficiary in permyriad
     */
    function setBeneficiary(address _newBeneficiary, uint256 _fee)
        external
        virtual
        ensureAddressSet(_newBeneficiary, _fee)
        onlyOwner
    {
        setExcludeFromFee(_newBeneficiary, true);
        (address currentBeneficiary, uint256 currentFee) = unpackBeneficiary(beneficiary);
        setExcludeFromFee(currentBeneficiary, false);
        uint256 newBeneficiary = packBeneficiary(_newBeneficiary, _fee);
        emit BeneficiaryRewardUpdated(currentBeneficiary, _newBeneficiary, currentFee, _fee);
        beneficiary = newBeneficiary;
    }

    /**
     * @notice sets recipient and fee amount of staking rewards
     * @dev excludes staking pool from fee
     * @dev only callable by owner
     * @param _contractAddress address of staking contract
     * @param _fee             fee sent to staking contract in permyriad
     */
    function setStaking(address _contractAddress, uint256 _fee)
        external
        virtual
        ensureAddressSet(_contractAddress, _fee)
        onlyOwner
    {
        setExcludeFromFee(_contractAddress, true);
        (address currentAddress, uint256 currentFee) = unpackBeneficiary(staking);
        setExcludeFromFee(currentAddress, false);
        uint256 newStaking = packBeneficiary(_contractAddress, _fee);
        emit StakingRewardUpdated(currentAddress, _contractAddress, currentFee, _fee);
        staking = newStaking;
    }

    /**
     * @notice sets whether account collects fees on token transfer
     * @dev only callable by owner
     * @param _enabled bool whether fees are enabled
     */
    function setFeesEnabled(bool _enabled) external virtual onlyOwner {
        emit FeesEnabledUpdated(_enabled);
        feesEnabled = _enabled;
    }

    /**
     * @notice sets whether collected fees are autoswapped
     * @dev only callable by owner
     * @param _enabled bool whether swap is enabled
     */
    function setSwapEnabled(bool _enabled) external virtual onlyOwner {
        emit SwapEnabledUpdated(_enabled);
        swapEnabled = _enabled;
    }

    function router() public pure virtual returns (IUniswapV2Router) {
        return IUniswapV2Router(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    }

    function _transfer(
        address sender,
        address recipient,
        uint256 amount
    ) internal override {
        // add liquidity
        // user => pair, msg.sender = router

        // remove liquidity
        // pair => router, msg.sender = pair
        // router => user, msg.sender = router

        // buy tokens for eth
        // pair => user, msg.sender = pair

        // sell tokens for eth
        // user => pair, msg.sender = router
        address pair = uniswapPair;
        // don't take a fee when
        // 1. fees are disabled
        // 2. the uniswap pair is neither sender nor recipient (non uniswap buy or sell)
        // 3. sender or recipient is excluded from fees
        // 4. sender is pair and recipient is router (2 transfers take place when liquidity is removed)
        if (
            !feesEnabled ||
            (sender != pair && recipient != pair) ||
            isExcludedFromFee[sender] ||
            isExcludedFromFee[recipient] ||
            (sender == pair && recipient == address(router()))
        ) {
            ERC20._transfer(sender, recipient, amount);
            return;
        }

        // get fees and recipients from storage
        (address beneficiaryAddress, uint256 transferFee) = unpackBeneficiary(beneficiary);
        if (transferFee > 0) {
            transferFee = handleFeeTransfer(sender, amount, beneficiaryAddress, transferFee);

            // don't autoswap when uniswap pair or router are sending tokens
            if (swapEnabled && sender != pair && sender != address(router())) {
                _swapTokensForEth(address(this));
                // if there are any ETH in the contract distribute rewards
                uint256 ethBalance = address(this).balance;
                (address stakingContract, uint256 stakingFee) = unpackBeneficiary(staking);
                uint256 stakingRewards = _calculateFee(ethBalance, stakingFee);
                if (stakingRewards > 0) {
                    _safeTransfer(stakingContract, stakingRewards);
                }
                _safeTransfer(beneficiaryAddress, ethBalance - stakingRewards);
            }
        }

        ERC20._transfer(sender, recipient, amount - transferFee);
    }

    function _safeTransfer(address _to, uint256 _value) internal {
        (bool success, ) = _to.call{value: _value}("");
        require(success, string(abi.encodePacked("ETH_TRANSFER_FAILED: ", uint256(uint160(_to)).toHexString(20))));
    }

    function handleFeeTransfer(
        address _sender,
        uint256 _amount,
        address _beneficiaryAddress,
        uint256 transferFee
    ) internal virtual returns (uint256) {
        transferFee = _calculateFee(_amount, transferFee);
        address feeRecipient = swapEnabled ? address(this) : _beneficiaryAddress;
        ERC20._transfer(_sender, feeRecipient, transferFee);
        return transferFee;
    }

    function _swapTokensForEth(address _to) internal virtual {
        uint256 tokenAmount = balanceOf(address(this));
        // only swap if more than 1e-5 tokens are in contract to avoid "UniswapV2: K" error
        if (tokenAmount > 10**13) {
            address[] memory path = new address[](2);
            path[0] = address(this);
            path[1] = WETH;

            _approve(address(this), address(router()), tokenAmount);
            router().swapExactTokensForETHSupportingFeeOnTransferTokens(tokenAmount, 0, path, _to, block.timestamp);
        }
    }

    function _calculateFee(uint256 _amount, uint256 _fee) internal pure virtual returns (uint256) {
        return (_amount * _fee) / 10000;
    }
}
function unpackBeneficiary(uint256 _beneficiary) public pure virtual returns (address, uint256) {
    return (address(uint160(_beneficiary)), uint256(uint96(_beneficiary >> 160)));
}

/**
 * @dev helper function to unpack address and fee from single storage slot
 */