contract GoodMarketMaker is BancorFormula, DSMath, SchemeGuard {
    using SafeMath for uint256;

    // For calculate the return value on buy and sell
    BancorFormula bancor;

    // Entity that holds a reserve token
    struct ReserveToken {
        // Determines the reserve token balance
        // that the reserve contract holds
        uint256 reserveSupply;
        // Determines the current ratio between
        // the reserve token and the GD token
        uint32 reserveRatio;
        // How many GD tokens have been minted
        // against that reserve token
        uint256 gdSupply;
    }

    // The map which holds the reserve token entities
    mapping(address => ReserveToken) public reserveTokens;

    // Emits when a change has occurred in a
    // reserve balance, i.e. buy / sell will
    // change the balance
    event BalancesUpdated(
        // The account who initiated the action
        address indexed caller,
        // The address of the reserve token
        address indexed reserveToken,
        // The incoming amount
        uint256 amount,
        // The return value
        uint256 returnAmount,
        // The updated total supply
        uint256 totalSupply,
        // The updated reserve balance
        uint256 reserveBalance
    );

    // Emits when the ratio changed. The caller should be the Avatar by definition
    event ReserveRatioUpdated(address indexed caller, uint256 nom, uint256 denom);

    // Emits when new tokens should be minted
    // as a result of incoming interest.
    // That event will be emitted after the
    // reserve entity has been updated
    event InterestMinted(
        // The account who initiated the action
        address indexed caller,
        // The address of the reserve token
        address indexed reserveToken,
        // How much new reserve tokens been
        // added to the reserve balance
        uint256 addInterest,
        // The GD supply in the reserve entity
        // before the new minted GD tokens were
        // added to the supply
        uint256 oldSupply,
        // The number of the new minted GD tokens
        uint256 mint
    );

    // Emits when new tokens should be minted
    // as a result of a reserve ratio expansion
    // change. This change should have occurred
    // on a regular basis. That event will be
    // emitted after the reserve entity has been
    // updated
    event UBIExpansionMinted(
        // The account who initiated the action
        address indexed caller,
        // The address of the reserve token
        address indexed reserveToken,
        // The reserve ratio before the expansion
        uint256 oldReserveRatio,
        // The GD supply in the reserve entity
        // before the new minted GD tokens were
        // added to the supply
        uint256 oldSupply,
        // The number of the new minted GD tokens
        uint256 mint
    );

    // Defines the daily change in the reserve ratio in RAY precision.
    // In the current release, only global ratio expansion is supported.
    // That will be a part of each reserve token entity in the future.
    uint256 public reserveRatioDailyExpansion;

    /**
     * @dev Constructor
     * @param _avatar The avatar of the DAO
     * @param _nom The numerator to calculate the global `reserveRatioDailyExpansion` from
     * @param _denom The denominator to calculate the global `reserveRatioDailyExpansion` from
     */
    constructor(
        Avatar _avatar,
        uint256 _nom,
        uint256 _denom
    ) public SchemeGuard(_avatar) {
        reserveRatioDailyExpansion = rdiv(_nom, _denom);
    }

    modifier onlyActiveToken(ERC20 _token) {
        ReserveToken storage rtoken = reserveTokens[address(_token)];
        require(rtoken.gdSupply > 0, "Reserve token not initialized");
        _;
    }

    /**
     * @dev Allows the DAO to change the daily expansion rate
     * it is calculated by _nom/_denom with e27 precision. Emits
     * `ReserveRatioUpdated` event after the ratio has changed.
     * Only Avatar can call this method.
     * @param _nom The numerator to calculate the global `reserveRatioDailyExpansion` from
     * @param _denom The denominator to calculate the global `reserveRatioDailyExpansion` from
     */
    function setReserveRatioDailyExpansion(uint256 _nom, uint256 _denom)
        public
        onlyAvatar
    {
        require(_denom > 0, "denominator must be above 0");
        reserveRatioDailyExpansion = rdiv(_nom, _denom);
        emit ReserveRatioUpdated(msg.sender, _nom, _denom);
    }

    // NOTICE: In the current release, if there is a wish to add another reserve token,
    //  `end` method in the reserve contract should be called first. Then, the DAO have
    //  to deploy a new reserve contract that will own the market maker. A scheme for
    // updating the new reserve must be deployed too.

    /**
     * @dev Initialize a reserve token entity with the given parameters
     * @param _token The reserve token
     * @param _gdSupply Initial supply of GD to set the price
     * @param _tokenSupply Initial supply of reserve token to set the price
     * @param _reserveRatio The starting reserve ratio
     */
    function initializeToken(
        ERC20 _token,
        uint256 _gdSupply,
        uint256 _tokenSupply,
        uint32 _reserveRatio
    ) public onlyOwner {
        reserveTokens[address(_token)] = ReserveToken({
            gdSupply: _gdSupply,
            reserveSupply: _tokenSupply,
            reserveRatio: _reserveRatio
        });
    }

    /**
     * @dev Calculates how much to decrease the reserve ratio for _token by
     * the `reserveRatioDailyExpansion`
     * @param _token The reserve token to calculate the reserve ratio for
     * @return The new reserve ratio
     */
    function calculateNewReserveRatio(ERC20 _token)
        public
        view
        onlyActiveToken(_token)
        returns (uint32)
    {
        ReserveToken memory reserveToken = reserveTokens[address(_token)];
        uint32 ratio = reserveToken.reserveRatio;
        if (ratio == 0) {
            ratio = 1e6;
        }
        return
            uint32(
                rmul(
                    uint256(ratio).mul(1e21), // expand to e27 precision
                    reserveRatioDailyExpansion
                )
                    .div(1e21) // return to e6 precision
            );
    }

    /**
     * @dev Decreases the reserve ratio for _token by the `reserveRatioDailyExpansion`
     * @param _token The token to change the reserve ratio for
     * @return The new reserve ratio
     */
    function expandReserveRatio(ERC20 _token)
        public
        onlyOwner
        onlyActiveToken(_token)
        returns (uint32)
    {
        ReserveToken storage reserveToken = reserveTokens[address(_token)];
        uint32 ratio = reserveToken.reserveRatio;
        if (ratio == 0) {
            ratio = 1e6;
        }
        reserveToken.reserveRatio = calculateNewReserveRatio(_token);
        return reserveToken.reserveRatio;
    }

    /**
     * @dev Calculates the buy return in GD according to the given _tokenAmount
     * @param _token The reserve token buying with
     * @param _tokenAmount The amount of reserve token buying with
     * @return Number of GD that should be given in exchange as calculated by the bonding curve
     */
    function buyReturn(ERC20 _token, uint256 _tokenAmount)
        public
        view
        onlyActiveToken(_token)
        returns (uint256)
    {
        ReserveToken memory rtoken = reserveTokens[address(_token)];
        return
            calculatePurchaseReturn(
                rtoken.gdSupply,
                rtoken.reserveSupply,
                rtoken.reserveRatio,
                _tokenAmount
            );
    }

    /**
     * @dev Calculates the sell return in _token according to the given _gdAmount
     * @param _token The desired reserve token to have
     * @param _gdAmount The amount of GD that are sold
     * @return Number of tokens that should be given in exchange as calculated by the bonding curve
     */
    function sellReturn(ERC20 _token, uint256 _gdAmount)
        public
        view
        onlyActiveToken(_token)
        returns (uint256)
    {
        ReserveToken memory rtoken = reserveTokens[address(_token)];
        return
            calculateSaleReturn(
                rtoken.gdSupply,
                rtoken.reserveSupply,
                rtoken.reserveRatio,
                _gdAmount
            );
    }

    /**
     * @dev Updates the _token bonding curve params. Emits `BalancesUpdated` with the
     * new reserve token information.
     * @param _token The reserve token buying with
     * @param _tokenAmount The amount of reserve token buying with
     * @return (gdReturn) Number of GD that will be given in exchange as calculated by the bonding curve
     */
    function buy(ERC20 _token, uint256 _tokenAmount)
        public
        onlyOwner
        onlyActiveToken(_token)
        returns (uint256)
    {
        uint256 gdReturn = buyReturn(_token, _tokenAmount);
        ReserveToken storage rtoken = reserveTokens[address(_token)];
        rtoken.gdSupply = rtoken.gdSupply.add(gdReturn);
        rtoken.reserveSupply = rtoken.reserveSupply.add(_tokenAmount);
        emit BalancesUpdated(
            msg.sender,
            address(_token),
            _tokenAmount,
            gdReturn,
            rtoken.gdSupply,
            rtoken.reserveSupply
        );
        return gdReturn;
    }

    /**
     * @dev Updates the _token bonding curve params. Emits `BalancesUpdated` with the
     * new reserve token information.
     * @param _token The desired reserve token to have
     * @param _gdAmount The amount of GD that are sold
     * @return Number of tokens that will be given in exchange as calculated by the bonding curve
     */
    function sell(ERC20 _token, uint256 _gdAmount)
        public
        onlyOwner
        onlyActiveToken(_token)
        returns (uint256)
    {
        ReserveToken storage rtoken = reserveTokens[address(_token)];
        require(rtoken.gdSupply > _gdAmount, "GD amount is higher than the total supply");
        uint256 tokenReturn = sellReturn(_token, _gdAmount);
        rtoken.gdSupply = rtoken.gdSupply.sub(_gdAmount);
        rtoken.reserveSupply = rtoken.reserveSupply.sub(tokenReturn);
        emit BalancesUpdated(
            msg.sender,
            address(_token),
            _gdAmount,
            tokenReturn,
            rtoken.gdSupply,
            rtoken.reserveSupply
        );
        return tokenReturn;
    }

    /**
     * @dev Calculates the sell return with contribution in _token and update the bonding curve params.
     * Emits `BalancesUpdated` with the new reserve token information.
     * @param _token The desired reserve token to have
     * @param _gdAmount The amount of GD that are sold
     * @param _contributionGdAmount The number of GD tokens that will not be traded for the reserve token
     * @return Number of tokens that will be given in exchange as calculated by the bonding curve
     */
    function sellWithContribution(
        ERC20 _token,
        uint256 _gdAmount,
        uint256 _contributionGdAmount
    ) public onlyOwner onlyActiveToken(_token) returns (uint256) {
        require(
            _gdAmount >= _contributionGdAmount,
            "GD amount is lower than the contribution amount"
        );
        ReserveToken storage rtoken = reserveTokens[address(_token)];
        require(rtoken.gdSupply > _gdAmount, "GD amount is higher than the total supply");

        // Deduces the convertible amount of GD tokens by the given contribution amount
        uint256 amountAfterContribution = _gdAmount.sub(_contributionGdAmount);

        // The return value after the deduction
        uint256 tokenReturn = sellReturn(_token, amountAfterContribution);
        rtoken.gdSupply = rtoken.gdSupply.sub(_gdAmount);
        rtoken.reserveSupply = rtoken.reserveSupply.sub(tokenReturn);
        emit BalancesUpdated(
            msg.sender,
            address(_token),
            _contributionGdAmount,
            tokenReturn,
            rtoken.gdSupply,
            rtoken.reserveSupply
        );
        return tokenReturn;
    }

    /**
     * @dev Current price of GD in `token`. currently only cDAI is supported.
     * @param _token The desired reserve token to have
     * @return price of GD
     */
    function currentPrice(ERC20 _token)
        public
        view
        onlyActiveToken(_token)
        returns (uint256)
    {
        ReserveToken memory rtoken = reserveTokens[address(_token)];
        GoodDollar gooddollar = GoodDollar(address(avatar.nativeToken()));
        return
            calculateSaleReturn(
                rtoken.gdSupply,
                rtoken.reserveSupply,
                rtoken.reserveRatio,
                (10**uint256(gooddollar.decimals()))
            );
    }

    //TODO: need real calculation and tests
    /**
     * @dev Calculates how much G$ to mint based on added token supply (from interest)
     * and on current reserve ratio, in order to keep G$ price the same at the bonding curve
     * formula to calculate the gd to mint: gd to mint =
     * addreservebalance * (gdsupply / (reservebalance * reserveratio))
     * @param _token the reserve token
     * @param _addTokenSupply amount of token added to supply
     * @return how much to mint in order to keep price in bonding curve the same
     */
    function calculateMintInterest(ERC20 _token, uint256 _addTokenSupply)
        public
        view
        onlyActiveToken(_token)
        returns (uint256)
    {
        GoodDollar gooddollar = GoodDollar(address(avatar.nativeToken()));
        uint256 decimalsDiff = uint256(27).sub(uint256(gooddollar.decimals()));
        //resulting amount is in RAY precision
        //we divide by decimalsdiff to get precision in GD (2 decimals)
        return rdiv(_addTokenSupply, currentPrice(_token)).div(10**decimalsDiff);
    }

    /**
     * @dev Updates bonding curve based on _addTokenSupply and new minted amount
     * @param _token The reserve token
     * @param _addTokenSupply Amount of token added to supply
     * @return How much to mint in order to keep price in bonding curve the same
     */
    function mintInterest(ERC20 _token, uint256 _addTokenSupply)
        public
        onlyOwner
        returns (uint256)
    {
        if (_addTokenSupply == 0) {
            return 0;
        }
        uint256 toMint = calculateMintInterest(_token, _addTokenSupply);
        ReserveToken storage reserveToken = reserveTokens[address(_token)];
        uint256 gdSupply = reserveToken.gdSupply;
        uint256 reserveBalance = reserveToken.reserveSupply;
        reserveToken.gdSupply = gdSupply.add(toMint);
        reserveToken.reserveSupply = reserveBalance.add(_addTokenSupply);
        emit InterestMinted(
            msg.sender,
            address(_token),
            _addTokenSupply,
            gdSupply,
            toMint
        );
        return toMint;
    }

    /**
     * @dev Calculate how much G$ to mint based on expansion change (new reserve
     * ratio), in order to keep G$ price the same at the bonding curve. the
     * formula to calculate the gd to mint: gd to mint =
     * (reservebalance / (newreserveratio * currentprice)) - gdsupply
     * @param _token The reserve token
     * @return How much to mint in order to keep price in bonding curve the same
     */
    function calculateMintExpansion(ERC20 _token)
        public
        view
        onlyActiveToken(_token)
        returns (uint256)
    {
        ReserveToken memory reserveToken = reserveTokens[address(_token)];
        uint32 newReserveRatio = calculateNewReserveRatio(_token); // new reserve ratio
        uint256 reserveDecimalsDiff = uint256(
            uint256(27).sub(ERC20Detailed(address(_token)).decimals())
        ); // //result is in RAY precision
        uint256 denom = rmul(
            uint256(newReserveRatio).mul(1e21),
            currentPrice(_token).mul(10**reserveDecimalsDiff)
        ); // (newreserveratio * currentprice) in RAY precision
        GoodDollar gooddollar = GoodDollar(address(avatar.nativeToken()));
        uint256 gdDecimalsDiff = uint256(27).sub(uint256(gooddollar.decimals()));
        uint256 toMint = rdiv(
            reserveToken.reserveSupply.mul(10**reserveDecimalsDiff), // reservebalance in RAY precision
            denom
        )
            .div(10**gdDecimalsDiff); // return to gd precision
        return toMint.sub(reserveToken.gdSupply);
    }

    /**
     * @dev Updates bonding curve based on expansion change and new minted amount
     * @param _token The reserve token
     * @return How much to mint in order to keep price in bonding curve the same
     */
    function mintExpansion(ERC20 _token) public onlyOwner returns (uint256) {
        uint256 toMint = calculateMintExpansion(_token);
        ReserveToken storage reserveToken = reserveTokens[address(_token)];
        uint256 gdSupply = reserveToken.gdSupply;
        uint256 ratio = reserveToken.reserveRatio;
        reserveToken.gdSupply = gdSupply.add(toMint);
        expandReserveRatio(_token);
        emit UBIExpansionMinted(msg.sender, address(_token), ratio, gdSupply, toMint);
        return toMint;
    }
}
/**
 @title Dynamic reserve ratio market maker
 */
function calculateMintInterest(ERC20 _token, uint256 _addTokenSupply)
    public
    view
    onlyActiveToken(_token)
    returns (uint256)
{
    GoodDollar gooddollar = GoodDollar(address(avatar.nativeToken()));
    uint256 decimalsDiff = uint256(27).sub(uint256(gooddollar.decimals()));
    //resulting amount is in RAY precision
    //we divide by decimalsdiff to get precision in GD (2 decimals)
    return rdiv(_addTokenSupply, currentPrice(_token)).div(10**decimalsDiff);
}

/**
 * @dev Calculates how much G$ to mint based on added token supply (from interest)
 * and on current reserve ratio, in order to keep G$ price the same at the bonding curve
 * formula to calculate the gd to mint: gd to mint =
 * addreservebalance * (gdsupply / (reservebalance * reserveratio))
 * @param _token the reserve token
 * @param _addTokenSupply amount of token added to supply
 * @return how much to mint in order to keep price in bonding curve the same
 */