contract WrappedTokenSwap is Ownable, Withdrawable, Pausable, Destructible
{
  using SafeMath for uint;
  using SafeTransfer for ERC20;
  address constant ETHER = address(0);
  uint constant rateDecimals = 18;
  uint constant rateUnit = 10 ** rateDecimals;

  address public wallet;

  uint public spread;
  uint constant spreadDecimals = 6;
  uint constant spreadUnit = 10 ** spreadDecimals;

  event LogTokenSwap(
    address indexed _userAddress,
    address indexed _userSentTokenAddress,
    uint _userSentTokenAmount,
    address indexed _userReceivedTokenAddress,
    uint _userReceivedTokenAmount
  );

  event LogFee(address token, uint amount);

  // abstract methods
  function underlyingTokenAddress() public view returns(address);
  function wrappedTokenAddress() public view returns(address);
  function wrap(uint unwrappedAmount) private returns(bool);
  function unwrap(uint wrappedAmount) private returns(bool);
  function getExchangedAmount(uint _amount, bool _isUnwrap) private view returns(uint);

  /**
   * @dev Contract constructor.
   * @param _wallet The wallet for fees.
   * @param _spread The initial spread (in millionths).
   */
  constructor(
    address _wallet,
    uint _spread
  )
    public
  {
    require(_wallet != address(0), "_wallet == address(0)");
    wallet = _wallet;
    spread = _spread;
  }

  function() external {
    revert("fallback function not allowed");
  }

  function setWallet(address _wallet) public onlyOwner {
    require(_wallet != address(0), "_wallet == address(0)");
    wallet = _wallet;
  }

  function setSpread(uint _spread) public onlyOwner {
    spread = _spread;
  }


  /******************* rate functions for interface compatibility *******************/

  function buyRate() public view returns(uint) {
    return getAmount(rateUnit, false);
  }

  function buyRateDecimals() public pure returns(uint) {
    return rateDecimals;
  }

  function sellRate() public view returns(uint) {
    return getAmount(rateUnit, true);
  }

  function sellRateDecimals() public pure returns(uint) {
    return rateDecimals;
  }

  /******************* end rate functions *******************/

  function _getFee(uint underlyingTokenTotal) internal view returns(uint) {
    return underlyingTokenTotal.mul(spread).div(spreadUnit);
  }

  /**
 * @dev For interface compatibility. Function to calculate the number of tokens the user is going to receive.
 * @param _offerTokenAmount The amount of tokens.
 * @param _isUnwrap If true, the offered token is the wrapped token, otherwise is the underlying token.
 * @return uint.
 */
  function getAmount(uint _offerTokenAmount, bool _isUnwrap)
    public view returns(uint toUserAmount)
  {
    if (_isUnwrap) {
      uint amount = getExchangedAmount(_offerTokenAmount, _isUnwrap);
      // fee is always in underlying token, when minting the CToken is the received token
      toUserAmount = amount.sub(_getFee(amount));
    } else {
      // fee is always in underlying token, when redeeming the CToken is the offered token
      uint fee = _getFee(_offerTokenAmount);
      toUserAmount = getExchangedAmount(_offerTokenAmount.sub(fee), _isUnwrap);
    }
  }

  /**
   * @dev Release purchased asset to the buyer based on pair rate.
   * @param _userOfferTokenAddress The token address the purchaser is offering (It may be the quote or the base).
   * @param _userOfferTokenAmount The amount of token the user want to swap.
   * @return bool.
   */
  function swapToken (
    address _userOfferTokenAddress,
    uint _userOfferTokenAmount
  )
    public
    whenNotPaused
    returns (bool)
  {
    require(_userOfferTokenAmount != 0, "_userOfferTokenAmount == 0");
    require(
      ERC20(_userOfferTokenAddress).allowance(msg.sender, address(this)) >= _userOfferTokenAmount,
      "ERC20 allowance < _userOfferTokenAmount"
    );
    address underlying = underlyingTokenAddress();
    ERC20 wrapped = ERC20(wrappedTokenAddress());
    // check if offered token address is the base or the quote token address
    require(
      _userOfferTokenAddress == underlying || _userOfferTokenAddress == address(wrapped),
      "_userOfferTokenAddress not in pair"
    );
    bool isUnwrap = _userOfferTokenAddress == address(wrapped);
    uint toUserAmount;
    uint fee;

    // get user's tokens
    ERC20(_userOfferTokenAddress)._safeTransferFrom(msg.sender, address(this), _userOfferTokenAmount);

    if (isUnwrap) {
      require(unwrap(_userOfferTokenAmount), "cannot unwrap the token");
      uint unwrappedAmount = getExchangedAmount(_userOfferTokenAmount, isUnwrap);
      require(
        ERC20(underlying).balanceOf(address(this)) >= unwrappedAmount,
        "No enough underlying tokens after redeem"
      );
      fee = _getFee(unwrappedAmount);
      toUserAmount = unwrappedAmount.sub(fee);
      require(toUserAmount > 0, "toUserAmount must be greater than 0");
      require(
        ERC20(underlying)._safeTransfer(msg.sender, toUserAmount),
        "cannot transfer underlying token to the user"
      );
    } else {
      fee = _getFee(_userOfferTokenAmount);
      uint toSwap = _userOfferTokenAmount.sub(fee);
      require(wrap(toSwap), "cannot wrap the token");
      toUserAmount = getExchangedAmount(toSwap, isUnwrap);
      require(wrapped.balanceOf(address(this)) >= toUserAmount, "No enough CTokens after mint");
      require(toUserAmount > 0, "toUserAmount must be greater than 0");
      require(
        wrapped._safeTransfer(msg.sender, toUserAmount),
        "cannot transfer the wrapped token to the user"
      );
    }
    // get the fee
    if (fee > 0) {
      require(
        ERC20(underlying)._safeTransfer(wallet, fee),
        "cannot transfer the undlying token to the wallet for the fees"
      );
      emit LogFee(address(underlying), fee);
    }

    emit LogTokenSwap(
      msg.sender,
      _userOfferTokenAddress,
      _userOfferTokenAmount,
      isUnwrap ? underlying : address(wrapped),
      toUserAmount
    );
    return true;
  }


}
/**
 * @title TokenSwap.
 * @author Eidoo SAGL.
 * @dev A swap asset contract. The offerAmount and wantAmount are collected and sent into the contract itself.
 */
function underlyingTokenAddress() public view returns(address);

// abstract methods