contract DividendPayingToken is
    ERC20,
    DividendPayingTokenInterface,
    DividendPayingTokenOptionalInterface
{
    using SafeMath for uint256;
    using SafeMathUint for uint256;
    using SafeMathInt for int256;

    uint256 internal constant ONE = 10**18;
    uint256 internal constant MAGNITUDE = 2**128;
    uint256 internal magnifiedDividendPerShare;

    mapping(address => int256) internal magnifiedDividendCorrections;
    mapping(address => uint256) internal withdrawnDividends;

    uint256 public totalDividendsDistributed;

    constructor(string memory _name, string memory _symbol)
        ERC20(_name, _symbol)
    {}

    receive() external payable {
        distributeDividends();
    }

    /**
     * @dev function requires sending ethereum
     * @dev Magnified divident per share will be 0 on the time of deployement
     */

    function distributeDividends() public payable override {
        require(totalSupply() > 0);
        if (msg.value > 0) {
            magnifiedDividendPerShare = magnifiedDividendPerShare.add(
                (msg.value).mul(MAGNITUDE) / totalSupply()
            );
            emit DividendsDistributed(msg.sender, msg.value);
            totalDividendsDistributed = totalDividendsDistributed.add(
                msg.value
            );
        }
    }

    function withdrawDividend() external virtual override {
        _withdrawDividendOfUser(payable(msg.sender));
    }

    function _withdrawDividendOfUser(address payable user)
        internal
        returns (uint256)
    {
        uint256 _withdrawableDividend = withdrawableDividendOf(user);
        if (_withdrawableDividend > 0) {
            emit DividendWithdrawn(user, _withdrawableDividend);
            withdrawnDividends[user] = withdrawnDividends[user].add(
                _withdrawableDividend
            );
            (bool success, ) = user.call{
                value: _withdrawableDividend,
                gas: 3000
            }("");

            if (!success) {
                withdrawnDividends[user] = withdrawnDividends[user].sub(
                    _withdrawableDividend
                );
            } else {
                return 0;
            }
            return _withdrawableDividend;
        }
        return 0;
    }

    function dividendOf(address _owner)
        external
        view
        override
        returns (uint256)
    {
        return withdrawableDividendOf(_owner);
    }

    function withdrawableDividendOf(address _owner)
        public
        view
        override
        returns (uint256)
    {
        return accumulativeDividendOf(_owner).sub(withdrawnDividends[_owner]);
    }

    function withdrawnDividendOf(address _owner)
        external
        view
        override
        returns (uint256)
    {
        return withdrawnDividends[_owner];
    }

    function accumulativeDividendOf(address _owner)
        public
        view
        override
        returns (uint256)
    {
        return
            magnifiedDividendPerShare
                .mul(balanceOf(_owner))
                .toInt256Safe()
                .add(magnifiedDividendCorrections[_owner])
                .toUint256Safe() / MAGNITUDE;
    }

    function _mint(address account, uint256 value) internal override {
        super._mint(account, value);

        magnifiedDividendCorrections[account] = magnifiedDividendCorrections[
            account
        ].sub((magnifiedDividendPerShare.mul(value)).toInt256Safe());
    }

    function _burn(address account, uint256 value) internal override {
        super._burn(account, value);
        magnifiedDividendCorrections[account] = magnifiedDividendCorrections[
            account
        ].add((magnifiedDividendPerShare.mul(value)).toInt256Safe());
    }

    function _setBalance(address account, uint256 newBalance) internal {
        uint256 currentBalance = balanceOf(account);
        if (newBalance > currentBalance) {
            uint256 mintAmount = newBalance.sub(currentBalance);
            _mint(account, mintAmount);
        } else if (newBalance < currentBalance) {
            uint256 burnAmount = currentBalance.sub(newBalance);
            _burn(account, burnAmount);
        }
    }
}
/** DividendPayingToken simplified explanation:
 *
 * When the holder reaching minimumTokenBalanceForDividends, non-transferable Divident Paying tokens will be minted.
 * Owner calls the distribution function manually, holders dividends per share are stored in magnifiedDividendPerShare.
 *
 * Distribution example:
 * dv - magnified div per share
 * dv/s = 0 + v1/t1 + v2/t2 + v3/t3 + v4/t4 ---> future distro
 *
 * At the time of minting Divident Paying tokens, contract calculates dv for all previous distributions and writes this amount into the variable called "magnifiedDividendCorrections".
 * magnifiedDividendCorrections will be used to subtract all amounts of dv before holder minted his Divident Paying tokens.
 */
function distributeDividends() public payable override {
    require(totalSupply() > 0);
    if (msg.value > 0) {
        magnifiedDividendPerShare = magnifiedDividendPerShare.add(
            (msg.value).mul(MAGNITUDE) / totalSupply()
        );
        emit DividendsDistributed(msg.sender, msg.value);
        totalDividendsDistributed = totalDividendsDistributed.add(
            msg.value
        );
    }
}

/**
 * @dev function requires sending ethereum
 * @dev Magnified divident per share will be 0 on the time of deployement
 */