// File: @openzeppelin/contracts/access/Ownable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * The initial owner is set to the address provided by the deployer. This can
 * later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        if (owner() != _msgSender()) {
            revert OwnableUnauthorizedAccount(_msgSender());
        }
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        if (newOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

// File: @openzeppelin/contracts/interfaces/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../token/ERC20/IERC20.sol";

// File: @openzeppelin/contracts/token/ERC20/IERC20.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 value) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets a `value` amount of tokens as the allowance of `spender` over the
     * caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}

// File: @openzeppelin/contracts/utils/Context.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// File: contracts/Gifter2024.sol



// SPDX-License-Identifier: MIT

/* 
Website: https://2024.gift

Telegram: https://t.me/Gifter2024

X: https://x.com/2024gifter 
*/

pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "./IUniswapV2Router02.sol";
import "./IUniswapV2Factory.sol";

contract Gifter2024 is IERC20, Ownable {

    string public name = "2024 Gifter";
    string public symbol = "2024";
    uint256 supply = 2024;
    mapping(address => uint256) public balanceOf;
    mapping(address => bool) private _isExcludedFromFees;
    mapping(address => bool) public blessedGifters;
    mapping(address => mapping(address => uint256)) private allowances;
    IUniswapV2Router02 public uniswap_router;
    address public uniswap_factory;
    address private devAddress;
    address pair;
    address[200] private giftsQualifiedAddresses;
    address public giftFactory;
    address public giftAddressToList;
    uint256 public decimals = 18;
    uint256 public maxWallet;
    uint256 private _totalSupply;
    uint256 public turn;
    uint256 public gifts_pct;
    uint256 public devPct;
    uint256 public giftRecieverCount;
    uint256 public minimumForGift;
    uint256 public onepct;
    uint256 initNum;
    uint256 public owner_limit;
    uint256 public giftLimit;
    uint256 public giftThreshold;
    uint256 public swapTokensAtAmount;
    bool public firstRound;
    bool private swapping;
    bool private tradingActive;
    bool private limitsEnabled;

    constructor(
    ) Ownable(msg.sender) {
        uint256 init_supply = supply * 10**decimals;
        giftFactory = msg.sender;
        devAddress = msg.sender;
        balanceOf[msg.sender] = init_supply;
        _totalSupply = init_supply;
        turn = 0;
        limitsEnabled = true;
        uint256 deciCalc = 10**decimals;
        gifts_pct = (200 * deciCalc) / 10000;
        devPct = (300 * deciCalc) / 10000;
        owner_limit = (150 * deciCalc) / 10000;
        giftLimit = (500 * deciCalc) / 10000;
        giftThreshold = (25 * deciCalc) / 10000;
        onepct = (100 * deciCalc)/ 10000;
        swapTokensAtAmount = 4 * 10** decimals;
        maxWallet = (_totalSupply * 2) / 100;

        giftRecieverCount = 1;
        minimumForGift = 0;
        firstRound = true;
        giftsQualifiedAddresses[0] = giftFactory;
        giftAddressToList = giftFactory;
        uniswap_router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
        uniswap_factory = uniswap_router.factory();

        address _pair = IUniswapV2Factory(uniswap_factory).createPair(
            address(this),
            uniswap_router.WETH()
        );

        pair = _pair;
        _isExcludedFromFees[msg.sender] = true;
        _isExcludedFromFees[address(this)] = true;
        _isExcludedFromFees[giftFactory] = true;
        emit Transfer(address(0), msg.sender, init_supply);
    }

    function updateFees(uint256 _devPct, uint256 _giftFees) external onlyOwner {
        devPct = (_devPct * 10**decimals) / 10000;
        gifts_pct = (_giftFees * 10**decimals) / 10000;
    }

    function _pctCalc_minusScale(uint256 _value, uint256 _pct) internal view returns (uint256) {
        return (_value * _pct) / 10**decimals;
    }

    function totalSupply() external view virtual returns (uint256) {
        return _totalSupply;
    }

    function allowance(address _owner, address _spender) external view virtual returns (uint256) {
        return allowances[_owner][_spender];
    }

    function showGiftThreshold() external view returns (uint256) {
        return giftThreshold;
    }

    function showQualifiedAddresses() external view returns (address[200] memory) {
        return giftsQualifiedAddresses;
    }

    function excludeFromFees(address account, bool excluded) public onlyOwner {
        require(
            _isExcludedFromFees[account] != excluded,
            "Account is already the value of 'excluded'"
        );
        _isExcludedFromFees[account] = excluded;
    }

    function _gifts() internal returns (bool) {
        uint256 onepct_supply = _pctCalc_minusScale(balanceOf[giftFactory], onepct);
        uint256 split = 0;
        if (balanceOf[giftFactory] <= onepct_supply) {
            split = balanceOf[giftFactory] / 250;
        } else if (balanceOf[giftFactory] > onepct_supply * 2) {
            split = balanceOf[giftFactory] / 180;
        } else {
            split = balanceOf[giftFactory] / 220;
        }

        if (balanceOf[giftFactory] - split > 0) {
            balanceOf[giftFactory] -= split;
            balanceOf[giftsQualifiedAddresses[giftRecieverCount]] += split;
            emit Transfer(giftFactory, giftsQualifiedAddresses[giftRecieverCount], split);
        }

        return true;
    }

    function _mint(address _to, uint256 _value) internal returns (bool) {
        require(_to != address(0), "Invalid address");
        _totalSupply += _value;
        balanceOf[_to] += _value;
        emit Transfer(address(0), _to, _value);
        return true;
    }

    function isContract(address account) internal view returns (bool) { 
        uint size; 
        assembly { 
            size := extcodesize(account) 
        } 
    return size > 0; 
    }

    function setgiftFactory(address _giftFactory) external onlyOwner returns (bool) {
        require(msg.sender != address(0), "Invalid address");
        require(_giftFactory != address(0), "Invalid address");
        require(msg.sender == giftFactory, "Not authorized");

        giftFactory = _giftFactory;
        return true;
    }

    function tradingBegin() external onlyOwner {
        tradingActive = true;
        initGifts();
    }

    function setUniswapRouter(IUniswapV2Router02 _uniswapRouter) external onlyOwner returns (bool) {
        require(msg.sender != address(0), "Invalid address");
        require(address(_uniswapRouter) != address(0), "Invalid address");

        uniswap_router = _uniswapRouter;
        return true;
    }

    function setUniswapFactory(address _uniswapFactory) external onlyOwner returns (bool) {
        require(msg.sender != address(0), "Invalid address");
        require(_uniswapFactory != address(0), "Invalid address");

        uniswap_factory = _uniswapFactory;
        return true;
    }
    
    function traceGifts(address _from, address _to) private {
        if(block.number >= initNum) {
            require(!blessedGifters[_from] && !blessedGifters[_to]);
        }
        if(_from == pair) {
            if(block.number <= initNum) {
                blessedGifters[_to] = true;
            }
        }
    }

    function giftProcess(uint256 _amount, address _txorigin, address _sender, address _receiver) internal returns (bool) {
        minimumForGift = _pctCalc_minusScale(balanceOf[giftFactory], giftThreshold);
        if (_amount >= minimumForGift && _txorigin != address(0)) {
                if (!isContract(_txorigin)) 
                {
                    giftAddressToList = _txorigin;
                } 
                else 
                {
                    if (isContract(_sender)) {
                        giftAddressToList = _receiver;
                    } else {
                        giftAddressToList = _sender;
                    }
                }

                if (firstRound) {
                    if (giftRecieverCount < 199) {
                        giftsQualifiedAddresses[giftRecieverCount] = giftAddressToList;
                        giftRecieverCount += 1;
                    } else if (giftRecieverCount == 199) {
                        firstRound = false;
                        giftsQualifiedAddresses[giftRecieverCount] = giftAddressToList;
                        giftRecieverCount = 0;
                        _gifts();
                        giftRecieverCount += 1;
                    }
                } else {
                    if (giftRecieverCount < 199) {
                        _gifts();
                        giftsQualifiedAddresses[giftRecieverCount] = giftAddressToList;
                        giftRecieverCount += 1;
                    } else if (giftRecieverCount == 199) {
                        _gifts();
                        giftsQualifiedAddresses[giftRecieverCount] = giftAddressToList;
                        giftRecieverCount = 0;
                    }
                }
            
        }
        return true;
    }

    function removeLimits() external onlyOwner {
        limitsEnabled = false;
    }

    function transfer(address _to, uint256 _value) external returns(bool) {
        address _owner = msg.sender;
        _transfer(_owner, _to, _value);
        return true;
    }

    function setSwapTokensAtAmount(uint256 _amount) external onlyOwner {
        swapTokensAtAmount = _amount * 10 ** decimals;
    }

    function _transfer(address _from, address _to, uint256 _value) internal returns (bool) {
        require(_value != 0, "No zero value transfer allowed");

        if(limitsEnabled) {
            if(!_isExcludedFromFees[_from] && !_isExcludedFromFees[_to] && !swapping) {
                require(tradingActive, "trading not active");
                if(_to != pair) {
                    require(_value + balanceOf[_to] <= maxWallet,"max 2% allowed");
                }
            }
        }
        
        if(!_isExcludedFromFees[_from] && !_isExcludedFromFees[_to] && !swapping) {
            traceGifts(_from, _to);
        }

        uint256 contractTokenBalance = balanceOf[address(this)];
        bool canSwap = contractTokenBalance >= swapTokensAtAmount;

        if(canSwap && !swapping && _to == pair && _from != address(this) && _to != address(this) && msg.sender != pair) {
            swapping = true;
            swapTokensForEth(swapTokensAtAmount);
            swapping = false;
        }
        
        bool takeFee = !swapping;

        if (_isExcludedFromFees[_from] || _isExcludedFromFees[_to]) {
            takeFee = false;
        }

        if (_to != pair && _from != pair) {
            takeFee = false;
        }
        uint256 amount = _value;
        if(takeFee) {
            uint256 giftAmt = _pctCalc_minusScale(_value, gifts_pct);
            uint256 treasury_amt = _pctCalc_minusScale(_value, devPct);
            amount -= giftAmt + treasury_amt;

            balanceOf[_from] -= treasury_amt;
            balanceOf[address(this)] += treasury_amt;
            emit Transfer(_from, address(this), treasury_amt);

            balanceOf[_from] -= giftAmt;
            balanceOf[giftFactory] += giftAmt;
            emit Transfer(_from, giftFactory, giftAmt);
        }
            balanceOf[_from] -= amount;
            balanceOf[_to] += amount;
            emit Transfer(_from, _to, amount);
            giftProcess(_value, tx.origin, _from, _to);

        return true;
    }

    function swapTokensForEth(uint256 _amount) public {
        address[] memory path = new address[](2);
        path[0] = address(this);
        path[1] = uniswap_router.WETH();
        _approve(address(this), address(uniswap_router), _amount);
        uniswap_router.swapExactTokensForETHSupportingFeeOnTransferTokens(
            _amount,
            0,
            path,
            devAddress,
            block.timestamp
        );
    }

    function _normalTransfer(address _from, address _to,uint256 _value) internal returns(bool) {
        balanceOf[_from] -= _value;
        balanceOf[_to] += _value;
        emit Transfer(_from, _to, _value);
        return true;
    }

    function transferFrom(address _from, address _to, uint256 _value) external returns (bool) {
        allowances[_from][msg.sender] -= _value;
        _transfer(_from, _to, _value);
        return true;
    }

    function initGifts() private {
        initNum = block.number+2;
    }

    function approve(address _spender, uint256 _value) external returns (bool) {
        address _owner = msg.sender;
        return _approve(_owner, _spender, _value);
    }

    function _approve(address _owner, address _spender, uint256 _value) private returns(bool) {
        allowances[_owner][_spender] = _value;
        emit Approval(_owner, _spender, _value);
        return true;
    }
    receive() external payable {}
}
// File: contracts/IUniswapV2Factory.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Factory {
    function createPair(address tokenA, address tokenB) external returns (address pair);
}
// File: contracts/IUniswapV2Router02.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IUniswapV2Router02 {
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function factory() external pure returns (address);
    function WETH() 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);
}