contract PreEtherlite is ERC20, Ownable, AccessControl, Pausable {

    address payable receipientAddress;
    IERC20 tokenAddresses;
    uint public minInvest = 500000000000000000000000;
    address[] public tokenAddress;
    mapping(address => bool) public allowTokenAddress;
    bytes32 private DOMAIN_SEPERATOR;
    uint256 public referralFee = 10;
    mapping(address => address) public ownerToReferral;
    mapping(address => address[]) public referralToreferee;

    bytes32 private constant INVITE_ONLY = keccak256("INVITE_ONLY"); 

    event TokenBoughtWithETH(address indexed buyerAddress, address admin, address indexed referralAddress, uint256 value, uint256 amount);
    event TokenBoughtWithToken(address indexed buyerAddress, address admin, address indexed referralAddress, uint256 value, uint256 amount, address tokenAddress);
    
    constructor(uint256 initialSupply, bytes32 key, address payable _receipientAddress) public ERC20("Pre-EtherLite", "xETL") {
        _mint(address(this), initialSupply);
        receipientAddress = _receipientAddress;
        _setupRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _setupRole(INVITE_ONLY, _msgSender());
        _setupRole(INVITE_ONLY, _receipientAddress);
        DOMAIN_SEPERATOR = key;
    }
    
    modifier authorisedSignatory(bytes32 signature){
        bytes32 authKey = keccak256(
            abi.encodePacked(
                INVITE_ONLY,
                keccak256(abi.encodePacked(DOMAIN_SEPERATOR, _msgSender()))
            )
        );
        require(signature == authKey, "Invalid Transaction");
        _;
    }
    
    function changeMin(uint _minInvest) public onlyOwner returns(bool){
        minInvest = _minInvest;
    }
    
    function changeReferralFee(uint _referralFee) public onlyOwner returns(bool){
        referralFee = _referralFee;
    }

    function mint(address to, uint256 amount) public onlyOwner{
        require(hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Caller is not a minter");
        _mint(to, amount);
    }

    function whiteListAddress(address _to, uint _isWhitelist) public onlyOwner{
        if(_isWhitelist == 1) _setupRole(INVITE_ONLY, _to);
        if(_isWhitelist == 0) revokeRole(INVITE_ONLY, _to);
    }

    function whiteListTokenAddress(address[] memory _tokenAddress) public onlyOwner returns(bool){
        tokenAddress = _tokenAddress;
        for(uint256 i=0; i < tokenAddress.length; i++){
            allowTokenAddress[tokenAddress[i]] = true;
        }
        return true;
    }
    
    function isReferralvalid(address _referralAddress) public view returns (bool){
       return hasRole(INVITE_ONLY, _referralAddress);
    }
    
    function buyToken(address _buyerAddress, address _referralAddress, uint256 _amount, bytes32 _signature) public payable authorisedSignatory(_signature) whenNotPaused returns(uint256) {
        require(hasRole(INVITE_ONLY, _referralAddress), "Caller is not allow to buy token");
        require(_buyerAddress != address(0));
        require(_amount >= minInvest, "Require Minimum investment");
        require(msg.value > 0, "ETH value to small");
        tokenAddresses = IERC20(address(this));
        uint256 referralAmount = _amount.mul(referralFee).div(100);
        require(tokenAddresses.transfer(_buyerAddress, _amount));
        require(tokenAddresses.transfer(_referralAddress, referralAmount));
        receipientAddress.transfer(msg.value);
        _setupRole(INVITE_ONLY, _buyerAddress);
        ownerToReferral[_buyerAddress] = _referralAddress;
        referralToreferee[_referralAddress].push(_buyerAddress);
        TokenBoughtWithETH(_buyerAddress, receipientAddress, _referralAddress, msg.value, _amount);
        return _amount;
    }

    function buyTokenWithToken(address _buyerAddress, address _referralAddress, address _tokenAddress, uint _value, uint _amount, bytes32 _signature) public authorisedSignatory(_signature) whenNotPaused returns(uint256) {
        require(hasRole(INVITE_ONLY, _referralAddress), "Caller is not allow to buy token");
        require(allowTokenAddress[_tokenAddress], "Token Address is invalid");
        require(_buyerAddress != address(0));
        require(_amount >= minInvest, "Sent Token Amount is too small");
        require(_value > 0, "Receive value to small");
        tokenAddresses = IERC20(address(this));
        uint256 referralAmount = _amount.mul(referralFee).div(100);
        require(tokenAddresses.transfer(_buyerAddress, _amount));
        require(tokenAddresses.transfer(_referralAddress, referralAmount));
        tokenAddresses = IERC20(_tokenAddress);
        require(tokenAddresses.transferFrom(_buyerAddress, address(this), _value));
        require(tokenAddresses.transfer(receipientAddress, _value));
        _setupRole(INVITE_ONLY, _buyerAddress);
        ownerToReferral[_buyerAddress] = _referralAddress;
        referralToreferee[_referralAddress].push(_buyerAddress);
        TokenBoughtWithToken(_buyerAddress, receipientAddress, _referralAddress, _value, _amount, _tokenAddress);
        return _amount;
    }
    
    function getReferral(address _referralAddress) public view returns(address[] memory) {
        return referralToreferee[_referralAddress];
    }

    // Allow transfer of accidentally sent ERC20 tokens
    function withdrawTokens(address _recipient, address _token) public onlyOwner {
        IERC20 token = IERC20(_token);
        uint256 balance = token.balanceOf(address(this));
        require(token.transfer(_recipient, balance));
    }

    function withdrawETH() public onlyOwner {
         receipientAddress.transfer(address(this).balance);
    }

}
function withdrawTokens(address _recipient, address _token) public onlyOwner {
    IERC20 token = IERC20(_token);
    uint256 balance = token.balanceOf(address(this));
    require(token.transfer(_recipient, balance));
}

// Allow transfer of accidentally sent ERC20 tokens