contract CRBSale is Ownable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    /* ---------- VARIABLES ---------- */

    // The token being sold
    IERC20 public token;

    // USDT instance
    IERC20 public usdt;

    // Addresses where funds are collected
    address public firstWallet;
    address public secondWallet;

    // How many token units a buyer gets per wei
    uint256 public ethRate;
    uint256 public usdtRate;

    // Duration of Sale
    uint256 public duration;

    // Starting date of Sale
    uint256 public startDate;

    // Ending date of Sale
    uint256 public endDate;

    // Minimum amount of investment a user can make
    uint256 public minEthInvestment;
    uint256 public minUsdtInvestment;

    // Total amount raised
    uint256 public weiRaised;

    // Buy type
    enum BuyType {
        ETH,
        USDT
    }

    /* ---------- EVENTS ---------- */

    event TokensPurchased(
        address indexed beneficiary,
        uint256 value,
        uint256 amount
    );

    event TokensWithdrawn(address indexed beneficiary, uint256 amount);

    event FundsForward(address indexed walletAddress, uint256 amount);

    event ETHRateChanged(uint256 prevRate, uint256 newRate);

    event USDTRateChanged(uint256 prevRate, uint256 newRate);

    event MinETHInvestmentChanged(
        uint256 prevETHInvestment,
        uint256 newETHInvestment
    );

    event MinUSDTInvestmentChanged(
        uint256 prevUSDTInvestment,
        uint256 newUSDTInvestment
    );

    /* ---------- CONSTRUCTOR ---------- */

    constructor(
        address _firstWallet,
        address _secondWallet,
        IERC20 _usdt,
        IERC20 _token,
        uint256 _ethRate,
        uint256 _usdtRate,
        uint256 _duration,
        uint256 _startDate,
        uint256 _minEthInvestment,
        uint256 _minUsdtInvestment
    ) {
        firstWallet = _firstWallet;
        secondWallet = _secondWallet;
        usdt = _usdt;
        token = _token;

        ethRate = _ethRate;
        usdtRate = _usdtRate;
        duration = _duration;
        startDate = _startDate;
        endDate = _startDate + _duration;
        minEthInvestment = _minEthInvestment;
        minUsdtInvestment = _minUsdtInvestment;
    }

    /* ---------- PUBLIC FUNCTIONS ---------- */

    receive() external payable {
        buyWithETH();
    }

    function buyWithETH() public payable {
        require(msg.value >= minEthInvestment, "CRBSale: weiAmount not valid");
        _buyTokens(msg.value, BuyType.ETH);
    }

    function buyWithUSDT(uint256 _weiAmount) public {
        require(
            _weiAmount >= minUsdtInvestment,
            "CRBSale: weiAmount not valid"
        );
        _buyTokens(_weiAmount, BuyType.USDT);
    }

    /* ---------- INTERNAL FUNCTIONS ---------- */

    function _buyTokens(uint256 _weiAmount, BuyType _buyType)
        internal
        nonReentrant
    {
        _preValidatePurchase(_msgSender(), _weiAmount);
        _recieveUsdt(_msgSender(), _weiAmount, _buyType);

        // calculate token amount to be created
        uint256 tokens = _getTokenAmount(_weiAmount, _buyType);

        // update state
        weiRaised = weiRaised + _weiAmount;

        _processPurchase(_msgSender(), tokens);
        emit TokensPurchased(_msgSender(), _weiAmount, tokens);

        _forwardFunds(_weiAmount, _buyType);
    }

    function _preValidatePurchase(address _beneficiary, uint256 _weiAmount)
        internal
        view
    {
        require(
            _beneficiary != address(0),
            "CRBSale: beneficiary is the zero address"
        );
        require(_weiAmount != 0, "CRBSale: weiAmount is 0");
        require(
            block.timestamp >= startDate && block.timestamp <= endDate,
            "CRBSale: Sale period is ended"
        );
    }

    function _recieveUsdt(
        address _beneficiary,
        uint256 _weiAmount,
        BuyType _buyType
    ) internal {
        if (_buyType == BuyType.USDT) {
            usdt.safeTransferFrom(_beneficiary, address(this), _weiAmount);
        }
    }

    function _processPurchase(address _beneficiary, uint256 _tokenAmount)
        internal
    {
        // deliverTokens
        token.safeTransfer(_beneficiary, _tokenAmount);
    }

    function _getTokenAmount(uint256 _weiAmount, BuyType _buyType)
        internal
        view
        returns (uint256)
    {
        if (_buyType == BuyType.ETH) {
            return (_weiAmount / ethRate) * 10**18;
        } else {
            return (_weiAmount / usdtRate) * 10**18;
        }
    }

    function _forwardFunds(uint256 _weiAmount, BuyType _buyType) internal {
        uint256 _amount = _weiAmount / 2;

        if (_buyType == BuyType.ETH) {
            payable(firstWallet).transfer(_amount);
            payable(secondWallet).transfer(_amount);
        } else {
            usdt.safeTransfer(firstWallet, _amount);
            usdt.safeTransfer(secondWallet, _amount);
        }

        emit FundsForward(firstWallet, _amount);
        emit FundsForward(secondWallet, _amount);
    }

    /* ---------- ONLY-OWNER FUNCTIONS ---------- */

    function withdrawCRBTokens() public onlyOwner {
        uint256 _amount = token.balanceOf(address(this));
        token.safeTransfer(msg.sender, _amount);
        emit TokensWithdrawn(msg.sender, _amount);
    }

    function setEthRate(uint256 _ethRate) public onlyOwner {
        uint256 _preEthRate = ethRate;
        ethRate = _ethRate;
        emit ETHRateChanged(_preEthRate, _ethRate);
    }

    function setUsdtRate(uint256 _usdtRate) public onlyOwner {
        uint256 _preUsdtRate = usdtRate;
        usdtRate = _usdtRate;
        emit USDTRateChanged(_preUsdtRate, _usdtRate);
    }

    function setMinEthInvestment(uint256 _new) public onlyOwner {
        uint256 _prev = minEthInvestment;
        minEthInvestment = _new;
        emit MinETHInvestmentChanged(_prev, _new);
    }

    function setMinUsdtInvestment(uint256 _new) public onlyOwner {
        uint256 _prev = minUsdtInvestment;
        minUsdtInvestment = _new;
        emit USDTRateChanged(_prev, _new);
    }

    function setNewStartDate(uint256 _startDate, uint256 _duration)
        public
        onlyOwner
    {
        startDate = _startDate;
        endDate = _startDate + _duration;
    }
}
function _buyTokens(uint256 _weiAmount, BuyType _buyType)
    internal
    nonReentrant
{
    _preValidatePurchase(_msgSender(), _weiAmount);
    _recieveUsdt(_msgSender(), _weiAmount, _buyType);

    // calculate token amount to be created
    uint256 tokens = _getTokenAmount(_weiAmount, _buyType);

    // update state
    weiRaised = weiRaised + _weiAmount;

    _processPurchase(_msgSender(), tokens);
    emit TokensPurchased(_msgSender(), _weiAmount, tokens);

    _forwardFunds(_weiAmount, _buyType);
}

/* ---------- INTERNAL FUNCTIONS ---------- */