contract wLitiSale is Context, Ownable {

    using SafeMath for uint256;

    //+---------------------------------------------------------------------------------------+
    // Structures
    //+---------------------------------------------------------------------------------------+

    //Type for tracking referral memebers and their bonus percentages
    struct Referrer {

        bool isReferrer;  //If true, referer is allowed to receive referral bonuses
        uint256 bonusPercent; //Percentage bonus amount

    }

    //+---------------------------------------------------------------------------------------+
    // Contract Data Members
    //+---------------------------------------------------------------------------------------+

    //Referral info
    address private _masterReferrerWallet; //Wallet of the master referrer (this person ALWAYS recieves a bonus)
    uint256 private _maxBonusPercent; //Max bonus that can be given to referrers
    mapping(address => Referrer) _referrers; //Track referrer info

    //Sale info
    IERC20 private _token; //Token to be sold
    address private _ETHWallet; //Wallet ETH is sent to
    uint256 private _saleCount; //Counts the number of sales
    uint256 private _tokenPrice; //ETH price per token
    uint256 private _saleSupplyLeft; //Supply left in sale
    uint256 private _saleSupplyTotal; //Total supply of sale
    uint256 private _saleStartTime; //Sale start epoch timestamp
    uint256 private _saleEndTime; //Sale end epoch timestamp
    mapping(uint256 => uint256) _weiRaised; //Track wei raised from each sale

    //+---------------------------------------------------------------------------------------+
    // Constructors
    //+---------------------------------------------------------------------------------------+

    /** Constructor to build the contract
      *
      * @param token - the contract address of the token that is being sold
      * @param ETHWallet - the wallet that ETH will be sent to after every purchase
      * @param masterReferrerWallet - the wallet of the master referrer
      *
      **/
    constructor(address token, address ETHWallet, address masterReferrerWallet) {

        _token = IERC20(token);
        _ETHWallet = ETHWallet;
        _masterReferrerWallet = masterReferrerWallet;

    }

    //+---------------------------------------------------------------------------------------+
    // Getters
    //+---------------------------------------------------------------------------------------+

    function getMasterReferrerWallet() public view returns (address) { return _masterReferrerWallet; }

    function getReferrerBonusPercent(address referrer) public view returns (uint256) { return _referrers[referrer].bonusPercent; }

    function getMaxBonusPercent() public view returns (uint256) { return _maxBonusPercent; }

    function getTokenPrice() public view returns (uint256) { return _tokenPrice; }

    function getSaleSupplyLeft() public view returns (uint256) { return _saleSupplyLeft; }

    function getSaleSupplyTotal() public view returns (uint256) { return _saleSupplyTotal; }

    function getSaleStartTime() public view returns (uint256) { return _saleStartTime; }

    function getSaleEndTime() public view returns (uint256) { return _saleEndTime; }

    function getSaleCount() public view returns (uint256) { return _saleCount; }

    function getWeiRaised(uint256 sale) public view returns (uint256) { return _weiRaised[sale]; }

    function getETHWallet() public view returns (address) { return _ETHWallet; }

    function isSaleActive() public view returns (bool) {

        return (block.timestamp > _saleStartTime &&
                block.timestamp < _saleEndTime);

    }

    function isReferrer(address referrer) public view returns (bool) { return _referrers[referrer].isReferrer; }

    //+---------------------------------------------------------------------------------------+
    // Private Functions
    //+---------------------------------------------------------------------------------------+

    function transferReferralTokens(address referrer, uint256 bonusPercent, uint purchaseAmountBig) private {

        uint256 referralAmountBig = purchaseAmountBig.mul(bonusPercent).div(10**2);
        _token.transfer(referrer, (referralAmountBig));

    }

    //+---------------------------------------------------------------------------------------+
    // Public/User Functions
    //+---------------------------------------------------------------------------------------+

    /** Purchase tokens from contract and distribute token bonuses to referrers. Master referrer will ALWAYS recieve at least
      *     a 1% token bonus. A second referrer address is required to be provided when purchasing and they will recieve at least 1%.
      *     A third referrer is optional, but not required. If the optional referrer is an autherized Referrer by the contract owner, then
      *     the optional referrer will receive a minimum of a 1% token bonus.
      *
      *  @param purchaseAmount - the amount of tokens that the purchaser wants to buy
      *  @param referrer - second referrer that is required
      *  @param optionalReferrer - third referrer that is optional
      **/
    function purchaseTokens(uint256 purchaseAmount, address referrer, address optionalReferrer) public payable {

        require(_msgSender() != address(0), "AddressZero cannot purchase tokens");
        require(isSaleActive(), "Sale is not active");
        require(getTokenPrice() != 0, "Token price is not set");
        require(getMaxBonusPercent() != 0, "Referral bonus percent is not set");
        require(isReferrer(referrer), "Referrer is not authorized");

        //Calculate big number amounts
        uint256 purchaseAmountBig = purchaseAmount * 1 ether; //Amount of tokens user is purchasing
        uint256 totalRAmountBig = purchaseAmountBig.mul(_maxBonusPercent).div(10**2); //Amount of tokens referrers will earn
        uint256 totalAmountBig = purchaseAmountBig.add(totalRAmountBig); //Total amount of tokens being distributed
        uint256 masterBonusPercent = _maxBonusPercent; //Bonus percent for the master referrer

        require(totalAmountBig <= _saleSupplyLeft, "Purchase amount is bigger than the remaining sale supply");

        uint256 totalPrice = purchaseAmount * _tokenPrice; //Total ETH price for tokens
        require(msg.value >= totalPrice, "Payment amount too low");

        //If the optionalReferrer is an authorized referrer, then distribute referral bonus tokens
        if(isReferrer(optionalReferrer)) {

            require(_referrers[referrer].bonusPercent + _referrers[optionalReferrer].bonusPercent < _maxBonusPercent,
                "Referrers bonus percent must be less than max bonus");

            //Subtract the master's bonus by the referrers' bonus AND transfer tokens to the optional referrer
            masterBonusPercent = masterBonusPercent.sub(_referrers[referrer].bonusPercent).sub(_referrers[optionalReferrer].bonusPercent);
            transferReferralTokens(optionalReferrer, _referrers[optionalReferrer].bonusPercent, purchaseAmountBig);

        }
        //There is only one referrer, ignore the optional referrer
        else {

            require(_referrers[referrer].bonusPercent < _maxBonusPercent, "Referrer bonus percent must be less than max bonus");

            //Subtract the master's bonus by the referrer's bonus
            masterBonusPercent = masterBonusPercent.sub(_referrers[referrer].bonusPercent);

        }

        //Transfer tokens to referrer, master referrer, and purchaser
        transferReferralTokens(referrer, _referrers[referrer].bonusPercent, purchaseAmountBig);
        transferReferralTokens(_masterReferrerWallet, masterBonusPercent, purchaseAmountBig);
        _token.transfer(msg.sender, (purchaseAmountBig));

        //Modify sale information
        _weiRaised[_saleCount] = _weiRaised[_saleCount] + totalPrice;
        _saleSupplyLeft = _saleSupplyLeft - (totalAmountBig);

        //Transfer ETH back to presale wallet
        address payable walletPayable = payable(_ETHWallet);
        walletPayable.transfer(totalPrice);

        //Transfer extra ETH back to buyer
        address payable client = payable(msg.sender);
        client.transfer(msg.value - totalPrice);

    }

    //+---------------------------------------------------------------------------------------+
    // Setters (Owner Only)
    //+---------------------------------------------------------------------------------------+

    //Set the max bonue that referrers can earn
    function setMaxBonusPercent(uint256 percent) public onlyOwner { _maxBonusPercent = percent; }

    //Set the ETH price of the tokens
    function setTokenPrice(uint256 price) public onlyOwner { _tokenPrice = price; }

    //Set the wallet to receive ETH
    function setETHWallet(address ETHWallet) public onlyOwner { _ETHWallet = ETHWallet; }

    //Set the master referrer wallet
    function setMasterReferrerWallet(address masterReferrerWallet) public onlyOwner { _masterReferrerWallet = masterReferrerWallet; }

    //Set referrer bonus percent
    function setReferrerBonusPercent(address referrer, uint256 bonusPercent) public onlyOwner {

        _referrers[referrer].bonusPercent = bonusPercent;

    }

    //+---------------------------------------------------------------------------------------+
    // Controls (Owner Only)
    //+---------------------------------------------------------------------------------------+

    //Add a referrer
    function addReferrer(address referrer, uint256 bonusPercent) public onlyOwner {

        require(!isReferrer(referrer), "Address is already a referrer");
        require(bonusPercent < _maxBonusPercent, "Referrer bonus cannot be equal to or greater than max bonus");
        require(bonusPercent > 0, "Bonus percent must be greater than 0");

        _referrers[referrer].isReferrer = true;
        _referrers[referrer].bonusPercent = bonusPercent;

    }

    //Remove a referrer
    function removeReferrer(address referrer) public onlyOwner {

        require(isReferrer(referrer), "Address already is not a referrer");

        delete _referrers[referrer];

    }

    //Withdraw a number of tokens from contract to the contract owner
    function withdrawToken(uint256 amount) public onlyOwner {
        _token.transfer(owner(), amount);
    }

    //Withdraw ALL tokens from contract to the contract owner
    function withdrawAllTokens() public onlyOwner {
        _token.transfer(owner(), _token.balanceOf(address(this)));
    }

    //Create a sale
    function createSale(uint256 supply, uint256 timeStart, uint256 timeEnd) public onlyOwner {

        require(supply <= _token.balanceOf(address(this)), "Supply too high, not enough tokens in contract");
        require(timeStart >= block.timestamp, "Sale start time cannot be in the past");
        require(timeEnd > timeStart, "Sale start time cannot be before the end time");

        //Store sale info
        _saleSupplyTotal = supply;
        _saleSupplyLeft = supply;
        _saleStartTime = timeStart;
        _saleEndTime = timeEnd;
        _saleCount += 1;

    }

}
/** This contract is designed for coordinating the sale of wLiti tokens to purchasers
 *     and includes a referral system where referrers earn bonus tokens on each wLiti
 *     sale.
 **/
function setReferrerBonusPercent(address referrer, uint256 bonusPercent) public onlyOwner {

    _referrers[referrer].bonusPercent = bonusPercent;

}

//Set referrer bonus percent