contract ISaiIDaiSwap is Pausable, Destructible, IErc20Swap
{
  using SafeMath for uint;
  using SafeTransfer for ERC20;
  address constant ETHER = address(0);
  uint constant expScale = 1e18;
  uint constant rateDecimals = 18;
  uint constant rateUnit = 10 ** rateDecimals;

  BZxLoanTokenSwap public isaiSwap;
  BZxLoanTokenSwap public idaiSwap;
  SaiDaiTokenSwap public saidaiSwap;

  event LogWithdrawToken(
    address indexed _from,
    address indexed _token,
    uint amount
  );

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

  event UnexpectedBalance(address token, uint balance);

  constructor(
    address _csaiSwap,
    address _cdaiSwap,
    address _saidaiSwap
  )
    public
  {
    isaiSwap = BZxLoanTokenSwap(_csaiSwap);
    idaiSwap = BZxLoanTokenSwap(_cdaiSwap);
    saidaiSwap = SaiDaiTokenSwap(_saidaiSwap);

//    ICErc20 csai = csaiSwap.cToken();
//    require(csaiSwap.cToken().underlying() == address(saidaiSwap.sai()), "SAI tokens mismatch");
//    require(cdaiSwap.cToken().underlying() == address(saidaiSwap.dai()), "DAI tokens mismatch");
  }

  function() external {
    revert("fallback function not allowed");
  }
  /**
   * @dev Withdraw asset.
   * @param _tokenAddress Asset to be withdrawed.
   * @return bool.
   */
  function withdrawToken(address _tokenAddress)
    public
    onlyOwner
  {
    uint tokenBalance;
    if (_tokenAddress == ETHER) {
      tokenBalance = address(this).balance;
      msg.sender.transfer(tokenBalance);
    } else {
      tokenBalance = ERC20(_tokenAddress).balanceOf(address(this));
      ERC20(_tokenAddress)._safeTransfer(msg.sender, tokenBalance);
    }
    emit LogWithdrawToken(msg.sender, _tokenAddress, tokenBalance);
  }

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

  function buyRate() public view returns(uint) {
    uint csairedeem = isaiSwap.sellRate().mul(10 ** (rateDecimals.sub(isaiSwap.sellRateDecimals())));
    uint saidai = saidaiSwap.buyRate().mul(10 ** (rateDecimals.sub(saidaiSwap.buyRateDecimals())));
    uint cdaimint = idaiSwap.buyRate().mul(10 ** (rateDecimals.sub(idaiSwap.buyRateDecimals())));
    uint rate = csairedeem.mul(saidai).div(10 ** rateDecimals).mul(cdaimint).div(10 ** rateDecimals);
    return rate;
  }

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

  function sellRate() public pure returns(uint) {
    return 0;
  }

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

  function getRate(address src, address dst, uint256 /*srcAmount*/) external view
  returns(uint expectedRate, uint slippageRate)  // real rate = returned value / 1e18
  {
    if (src == address(isaiSwap.loanToken()) && dst == address(idaiSwap.loanToken())) {
      expectedRate = buyRate();
    } else {
      expectedRate = 0;
    }
    slippageRate = expectedRate;
  }

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

  /**
 * @dev For interface compatibility. Function to calculate the number of tokens the user is going to receive.
 * @param _offerTokenAmount The amount of tokens.
 * @param _isRedeem If true, the offered token is the CToken, otherwise is the underlying token.
 * @return uint.
 */
  function getAmount(uint _offerTokenAmount, bool _isRedeem)
    public view returns(uint toUserAmount)
  {
    // Swap cdai to csai forbidden
    return _isRedeem? 0 : _offerTokenAmount.mul(buyRate()).div(rateUnit);
  }

  /**
   * @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(
      _userOfferTokenAddress == address(isaiSwap.loanToken()),
      "_userOfferTokenAddress must be CSAI address"
    );
    swap(
      _userOfferTokenAddress,
      _userOfferTokenAmount,
      address(idaiSwap.loanToken()),
      0,
      0
    );
    return true;
  }

  function swap(address src, uint srcAmount, address dest, uint /*maxDestAmount*/, uint /*minConversionRate*/)
    public payable
    whenNotPaused
  {
    require(msg.value == 0, "ethers not supported");
    require(srcAmount != 0, "srcAmount == 0");
    require(
      ERC20(src).allowance(msg.sender, address(this)) >= srcAmount,
      "ERC20 allowance < srcAmount"
    );

    IBZxLoanToken isai = isaiSwap.loanToken();
    ERC20 sai = ERC20(isai.loanTokenAddress());
    IBZxLoanToken idai = idaiSwap.loanToken();
    ERC20 dai = ERC20(idai.loanTokenAddress());

    require(
      src == address(isai),
      "src must be iSAI address"
    );
    require(
      dest == address(idai),
      "dest must be iDAI address"
    );

    emptyBalance(address(isai));
    emptyBalance(address(sai));
    emptyBalance(address(dai));
    emptyBalance(address(idai));

    // get user's tokens
    isai.transferFrom(msg.sender, address(this), srcAmount);

    isai.approve(address(isaiSwap), srcAmount);
    isaiSwap.swapToken(address(isai), srcAmount);
    uint saiBalance = sai.balanceOf(address(this));
    require(saiBalance > 0, "SAI balance is 0");

    sai.approve(address(saidaiSwap), saiBalance);
    saidaiSwap.swapToken(address(sai), saiBalance);
    uint daiBalance = dai.balanceOf(address(this));
    require(daiBalance > 0, "DAI balance is 0");

    dai.approve(address(idaiSwap), daiBalance);
    idaiSwap.swapToken(address(dai), daiBalance);
    uint cdaiBalance = idai.balanceOf(address(this));
    require(cdaiBalance > 0, "iDAI balance is 0");

    require(idai.transfer(msg.sender, cdaiBalance), "Cannot tranfer iDAI to the msg.sender address");


    emit LogTokenSwap(
      msg.sender,
      src,
      srcAmount,
      address(idai),
      cdaiBalance
    );
  }

  function emptyBalance(address token) private {
    uint balance = ERC20(token).balanceOf(address(this));
    if (balance > 0) {
      require(ERC20(token)._safeTransfer(owner, balance), "cannot tranfer tokens in order to empty balance");
      emit UnexpectedBalance(token, balance);
    }
  }

}
/**
 * @title TokenSwap.
 * @author Eidoo SAGL.
 * @dev A swap asset contract. The offerAmount and wantAmount are collected and sent into the contract itself.
 */
function getAmount(uint _offerTokenAmount, bool _isRedeem)
  public view returns(uint toUserAmount)
{
  // Swap cdai to csai forbidden
  return _isRedeem? 0 : _offerTokenAmount.mul(buyRate()).div(rateUnit);
}

/**
 * @dev For interface compatibility. Function to calculate the number of tokens the user is going to receive.
 * @param _offerTokenAmount The amount of tokens.
 * @param _isRedeem If true, the offered token is the CToken, otherwise is the underlying token.
 * @return uint.
 */