contract DynamicSwap is ERC20, ERC20Detailed {
    using SafeMath for uint;
    using SignedSafeMath for int256;
    using SafeERC20 for IERC20;
    
    mapping(address => bool) coins;
    address public governance;
    
    constructor() public ERC20Detailed("DynamicSwap", "dUSD", 18) {
        governance = msg.sender;
    }
    
    function setGovernance(address _governance) external {
        require(msg.sender == governance, "!governance");
        governance = _governance;
    }
    
    function approveCoins(address _coin) external {
        require(msg.sender == governance, "!governance");
        coins[_coin] = true;
    }
    
    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(int256 input, int256 x, int256 y) public pure returns (uint) {
        int256 _x = x.add(input);
        int256 _y = f(_x, x, y);
        return uint(y.sub(_y));
    }

    // Calculate input given exact output
    function getInExactOut(int256 output, int256 x, int256 y) public pure returns (uint) {
        int256 _y = y.sub(output);
        int256 _x = f(_y, y, x);
        return uint(_x.sub(x));
    }
    
    // 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) {
        return normalize1e18(token, token.balanceOf(address(this)));
    }
    
    // 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(block.timestamp <= deadline, "expired");
        
        output = normalize(to, getOutExactIn(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(block.timestamp <= deadline, "expired");
        
        input = normalize(from, getInExactOut(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 = input;
        } else {
            output = getOutExactIn(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 = output;
        } else {
            input = normalize(from, getInExactOut(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(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;
        }
    }
    
    function removeLiquidityExactOut(IERC20 to, uint maxInput, uint output, uint deadline) external returns (uint input) {
        require(block.timestamp <= deadline, "expired");
        
        input = getInExactOut(i(normalize1e18(to, input)), 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;
        }
    }
}
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 1e18