contract AquaToken is
    MinterRole,
    WhitelistAdminRole
{
    using SafeMath for uint256;

    mapping (address => uint256) private _wOwned;
    mapping (address => mapping (address => uint256)) private _allowances;

    uint256 private constant MAX = ~uint256(0);
    // denotes display total (aqua)
    uint256 private _aTotal = 0;
    // denotes actual total (waves)
    uint256 private _wTotal = 0;
    // display total fees
    uint256 private _aFeeTotal;

    address public fountainAddress;
    address public uniswapPairAddress;
    // tax divisor - 25 => 4% (100/25)
    uint256 public taxDivisor = 25;

    string private _name;
    string private _symbol;
    uint8 private _decimals;

    bool public tokenPaused;
    mapping (address => bool) public pauseWhitelist;
    //set claimable amounts for the token swap
    mapping (address => uint256) public claimingAmounts;

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event RewardLiquidityProviders(uint256 value);

    constructor (string memory name, string memory symbol, uint8 decimals) public {
        _name = name;
        _symbol = symbol;
        _decimals = decimals;
        pauseWhitelist[_msgSender()] = true;

        //mint max supply ~17418, rest will be burned after claim is over
        _mint(address(this), 17418000000000);
        // enable token pause to avoid frontrunning lp listing, once LP is listed, we destroy the usage of tokenPaused
        tokenPaused = true;
    }

    function name() public view returns (string memory) {
        return _name;
    }

    function symbol() public view returns (string memory) {
        return _symbol;
    }

    function decimals() public view returns (uint8) {
        return _decimals;
    }

    function setTokenPaused(bool paused) external onlyWhitelistAdmin {
        require(paused == false, "AquaToken::setTokenPaused: you can only unpause the token");
        tokenPaused = paused;
        // burn the rest of the tokens this contract has
        _transferStandard(address(this), address(0), balanceOf(address(this)));
    }

    function setTaxDivisor(uint256 _taxDivisor) public onlyWhitelistAdmin {
        require(_taxDivisor == 0 || _taxDivisor >= 10, "AquaToken::setTaxDivisor: too small");
        taxDivisor = _taxDivisor;
    }

    function setUniswapPair(address _uniswapPairAddress) public onlyWhitelistAdmin {
        uniswapPairAddress = _uniswapPairAddress;
    }

    function setFountainAddress(address _fountainAddress) public onlyWhitelistAdmin {
        fountainAddress = _fountainAddress;
    }

    function rewardLiquidityProviders() external {
        require(balanceOf(address(this)) > 0, "Transfer amount must be greater than zero");
        require(balanceOf(address(_msgSender())) > 0, "You must be an account holder to call this function");

        uint256 originalBalance = balanceOf(address(this));

        uint256 uniswapPairAmount = originalBalance.mul(475).div(575); // ~83%
        uint256 fountainPairAmount = originalBalance.mul(72).div(575); // ~12%
        uint256 userRewardAmount = originalBalance.mul(28).div(575); // ~5%

        _transferStandard(address(this), uniswapPairAddress, uniswapPairAmount);
        IUniswapV2Pair(uniswapPairAddress).sync();

        _transferStandard(address(this), fountainAddress, fountainPairAmount);
        IUniswapV2Pair(fountainAddress).sync();

        _transferStandard(address(this), _msgSender(), userRewardAmount);

        emit RewardLiquidityProviders(originalBalance);
    }

    function totalSupply() public view returns (uint256) {
        // since we burn tokens, return supply - current burn balance
        return _aTotal.sub(balanceOf(address(0)));
    }

    // display only
    function totalFees() public view returns (uint256) {
        return _aFeeTotal;
    }

    function balanceOf(address account) public view returns (uint256) {
        require(_wOwned[account] <= _wTotal, "Amount must be less than total waves");
        uint256 currentRate =  _getRate();
        return _wOwned[account].div(currentRate);
    }

    function transfer(address recipient, uint256 amount) public returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    function allowance(address owner, address spender) public view returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) public returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        return true;
    }

    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
        return true;
    }

    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        return true;
    }

    function _transfer(address from, address to, uint256 amount) internal {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");
        require(amount > 0, "ERC20: Transfer amount must be greater than zero");
        require(balanceOf(from) >= amount, "ERC20: transfer amount exceeds balance");
        require(pauseWhitelist[from] == true || tokenPaused == false, "ERC20: Token is currently paused");

        // disable tax for whitelisters (crowdsale and treasury)
        if (taxDivisor != 0 && pauseWhitelist[from] == false) {
            uint256 taxAmount = amount.div(taxDivisor);

            uint256 uniswapPairAmount = taxAmount.mul(500).div(1000); // 50%
            uint256 fountainAmount = taxAmount.mul(75).div(1000); // 7.5%
            uint256 burnedAmount = taxAmount.mul(250).div(1000);  // 25%
            uint256 holdersAmount = taxAmount.mul(175).div(1000); // 17.5%

            require(fountainAmount.add(uniswapPairAmount).add(burnedAmount).add(holdersAmount) <= taxAmount, "ERC20Transfer::taxTransfer: Math is broken");
            _transferStandard(from, address(this), uniswapPairAmount.add(fountainAmount));
            _transferStandard(from, address(0), burnedAmount);
            _transferStandard(from, to, amount.sub(taxAmount));

            _distributeFee(from, holdersAmount);
        }
        else {
            _transferStandard(from, to, amount);
        }
    }

    function _transferStandard(address sender, address recipient, uint256 amount) private {
        uint256 currentRate =  _getRate();

        uint256 rAmount = amount.mul(currentRate);
        _wOwned[sender] = _wOwned[sender].sub(rAmount);
        _wOwned[recipient] = _wOwned[recipient].add(rAmount);

        emit Transfer(sender, recipient, amount);
    }

    function _distributeFee(address sender, uint256 aFee) private {
        uint256 currentRate =  _getRate();

        uint256 wFee = aFee.mul(currentRate);
        _wOwned[sender] = _wOwned[sender].sub(wFee);

        _wTotal = _wTotal.sub(wFee);
        _aFeeTotal = _aFeeTotal.add(aFee);

        emit Transfer(sender, address(0), aFee);
    }

    function _getRate() private view returns(uint256) {
        return _wTotal.div(_aTotal);
    }

    function _mint(address account, uint256 amount) internal {
        require(account != address(0), "ERC20: mint to the zero address");
        require(amount > 0, "ERC20: mint amount is zero");
        _aTotal = _aTotal.add(amount);
        _wTotal = (MAX - (MAX % _aTotal));

        // only have 1 minter, they will have the entire supply
        _wOwned[account] = _wTotal;

        pauseWhitelist[account] = true;

        emit Transfer(address(0), account, amount);
    }

    function _approve(address owner, address spender, uint256 amount) private {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    function setClaims(address[] calldata _recipients, uint256[] calldata _values) external onlyWhitelistAdmin {
        for (uint256 index = 0; index < _recipients.length; index++) {
            claimingAmounts[_recipients[index]] = _values[index];
        }
    }

    function claimTokens() external {
        uint256 amount = claimingAmounts[_msgSender()];
        require(amount > 0, "ERC20: beneficiary is not due any tokens");

        claimingAmounts[_msgSender()] = 0;
        _transferStandard(address(this), _msgSender(), amount);
    }
}
function totalFees() public view returns (uint256) {
    return _aFeeTotal;
}

// display only