contract CreamUSD is ERC20, ERC20Detailed {
    using SafeMath for uint;
    using SignedSafeMath for int256;
    using SafeERC20 for IERC20;

    mapping(address => bool) public coins;
    mapping(address => Normalizer) public normalizers;

    address public governance;

    constructor() public ERC20Detailed("Cream USD", "crUSD", 18) {
        governance = msg.sender;
    }

    function setGovernance(address _governance) external {
        require(msg.sender == governance, "!governance");
        governance = _governance;
    }

    function approveCoins(address _coin, Normalizer _normalizer) external {
        require(msg.sender == governance, "!governance");
        coins[_coin] = true;
        if (address(_normalizer) != address(0)) {
            normalizers[_coin] = _normalizer;
        }
    }

    uint public fee = 99985;
    uint public constant BASE = 100000;

    uint public constant A = 0.75e18;
    uint public count = 0;
    mapping(address => bool) tokens;

    function f(int256 _x, int256 x, int256 y) internal pure returns (int256 _y) {
        int256 k;
        int256 c;
        {
            int256 u = x.add(y.mul(int256(A)).div(1e18));
            int256 v = y.add(x.mul(int256(A)).div(1e18));
            k = u.mul(v);
            c = _x.mul(_x).sub(k.mul(1e18).div(int256(A)));
        }

        int256 cst = int256(A).add(int256(1e36).div(int256(A)));
        int256 _b = _x.mul(cst).div(1e18);

        int256 D = _b.mul(_b).sub(c.mul(4));

        require(D >= 0, "!root");

        _y = (-_b).add(D.sqrt()).div(2);
    }

    // Calculate output given exact input
    function getOutExactIn(address token, int256 input, int256 x, int256 y) public view returns (uint output) {
        int256 _x = x.add(input);
        int256 _y = f(_x, x, y);
        output = uint(y.sub(_y));
        if (address(normalizers[token]) != address(0)) {
            output = output.mul(1e18).div(normalizers[token].getPrice(token));
        }
    }

    // Calculate input given exact output
    function getInExactOut(address token, int256 output, int256 x, int256 y) public view returns (uint input) {
        int256 _y = y.sub(output);
        int256 _x = f(_y, y, x);
        input = uint(_x.sub(x));
        if (address(normalizers[token]) != address(0)) {
            input = input.mul(1e18).div(normalizers[token].getPrice(token));
        }
    }

    // Normalize coin to 1e18
    function normalize1e18(IERC20 token, uint _amount) public view returns (uint) {
        uint _decimals = ERC20Detailed(address(token)).decimals();
        if (_decimals == uint(18)) {
            return _amount;
        } else {
            return _amount.mul(1e18).div(uint(10)**_decimals);
        }
    }

    // Normalize coin to original decimals
    function normalize(IERC20 token, uint _amount) public view returns (uint) {
        uint _decimals = ERC20Detailed(address(token)).decimals();
        if (_decimals == uint(18)) {
            return _amount;
        } else {
            return _amount.mul(uint(10)**_decimals).div(1e18);
        }
    }

    // Contract balance of coin normalized to 1e18
    function balance(IERC20 token) public view returns (uint) {
        address _token = address(token);
        uint _balance = IERC20(_token).balanceOf(address(this));
        if (address(normalizers[_token]) != address(0)) {
            _balance = _balance.mul(normalizers[_token].getPrice(_token)).div(1e18);
        }
        return normalize1e18(token, _balance);
    }

    // Converter helper to int256
    function i(uint x) public pure returns (int256) {
        return int256(x);
    }

    function swapExactAmountIn(IERC20 from, IERC20 to, uint input, uint minOutput, uint deadline) external returns (uint output) {
        require(coins[address(from)] == true, "!coin");
        require(coins[address(to)] == true, "!coin");

        require(block.timestamp <= deadline, "expired");

        output = normalize(to, getOutExactIn(address(to), i(normalize1e18(from, input.mul(fee).div(BASE))), i(balance(from)), i(balance(to))));

        require(output >= minOutput, "slippage");

        from.safeTransferFrom(msg.sender, address(this), input);
        to.safeTransfer(msg.sender, output);
    }

    function swapExactAmountOut(IERC20 from, IERC20 to, uint maxInput, uint output, uint deadline) external returns (uint input) {
        require(coins[address(from)] == true, "!coin");
        require(coins[address(to)] == true, "!coin");

        require(block.timestamp <= deadline, "expired");

        input = normalize(from, getInExactOut(address(from), i(normalize1e18(to, output)), i(balance(from)), i(balance(to))));
        input = input.mul(BASE).divCeil(fee);

        require(input <= maxInput, "slippage");

        from.safeTransferFrom(msg.sender, address(this), input);
        to.safeTransfer(msg.sender, output);
    }

    function addLiquidityExactIn(IERC20 from, uint input, uint minOutput, uint deadline) external returns (uint output) {
        require(coins[address(from)] == true, "!coin");
        require(block.timestamp <= deadline, "expired");

        if (totalSupply() == 0) {
            output = normalize1e18(from, input);
        } else {
            output = getOutExactIn(address(from), i(normalize1e18(from, input.mul(fee).div(BASE))), i(balance(from)), i(totalSupply().div(count)));
        }

        require(output >= minOutput, "slippage");

        from.safeTransferFrom(msg.sender, address(this), input);
        _mint(msg.sender, output);

        if (!tokens[address(from)] && balance(from) > 0 ) {
            tokens[address(from)] = true;
            count = count.add(1);
        }
    }

    function addLiquidityExactOut(IERC20 from, uint maxInput, uint output, uint deadline) external returns (uint input) {
        require(coins[address(from)] == true, "!coin");
        require(block.timestamp <= deadline, "expired");

        if (totalSupply() == 0) {
            input = normalize(from, output);
        } else {
            input = normalize(from, getInExactOut(address(from), i(output), i(balance(from)), i(totalSupply().div(count))));
            input = input.mul(BASE).divCeil(fee);
        }

        require(input <= maxInput, "slippage");

        from.safeTransferFrom(msg.sender, address(this), input);
        _mint(msg.sender, output);

        if (!tokens[address(from)] && balance(from) > 0 ) {
            tokens[address(from)] = true;
            count = count.add(1);
        }
    }

    function removeLiquidityExactIn(IERC20 to, uint input, uint minOutput, uint deadline) external returns (uint output) {
        require(block.timestamp <= deadline, "expired");

        output = normalize(to, getOutExactIn(address(this), i(input.mul(fee).div(BASE)), i(totalSupply().div(count)), i(balance(to))));

        require(output >= minOutput, "slippage");

        _burn(msg.sender, input);
        to.safeTransfer(msg.sender, output);

        if (balance(to) == 0) {
            tokens[address(to)] = false;
            count = count.sub(1);
        }
    }

    function removeLiquidityExactOut(IERC20 to, uint maxInput, uint output, uint deadline) external returns (uint input) {
        require(block.timestamp <= deadline, "expired");

        input = getInExactOut(address(to), i(normalize1e18(to, output)), i(totalSupply().div(count)), i(balance(to)));
        input = input.mul(BASE).divCeil(fee);

        require(input <= maxInput, "slippage");

        _burn(msg.sender, input);
        to.safeTransfer(msg.sender, output);

        if (balance(to) == 0) {
            tokens[address(to)] = false;
            count = count.sub(1);
        }
    }
}
function balance(IERC20 token) public view returns (uint) {
    address _token = address(token);
    uint _balance = IERC20(_token).balanceOf(address(this));
    if (address(normalizers[_token]) != address(0)) {
        _balance = _balance.mul(normalizers[_token].getPrice(_token)).div(1e18);
    }
    return normalize1e18(token, _balance);
}

// Contract balance of coin normalized to 1e18