contract OrderManageContract is PartnerAuthority {
  using SafeMath for uint256;
  using SafeCompare for string;

  /**
   * @dev Status of current business execution contract.
   */
  enum StatusChoices {
    NO_LOAN,
    REPAYMENT_WAITING,
    REPAYMENT_ALL,
    CLOSE_POSITION,
    OVERDUE_STOP
  }

  string internal constant TOKEN_ETH = "ETH";
  string internal constant TOKEN_USDT = "USDT";
  address public maker;
  address public taker;
  address internal token20;

  uint256 public toTime;
  // the amount of the borrower’s final loan.
  uint256 public outLoanSum;
  uint256 public repaymentSum;
  uint256 public lastRepaymentSum;
  string public loanTokenName;
  // Borrower's record of the pledge.
  StatusChoices internal status;

  // Record the amount of the borrower's offline transfer.
  mapping(address => uint256) public ethAmount;

  /**
   * Event for takerOrder logging.
   * @param taker address of investor.
   * @param outLoanSum the amount of the borrower’s final loan.
   */
  event TakerOrder(address indexed taker, uint256 outLoanSum);


  /**
   * Event for executeOrder logging.
   * @param maker address of borrower.
   * @param lastRepaymentSum current order repayment amount.
   */
  event ExecuteOrder(address indexed maker, uint256 lastRepaymentSum);

  /**
   * Event for forceCloseOrder logging.
   * @param toTime order repayment due date.
   * @param transferSum balance under current contract.
   */
  event ForceCloseOrder(uint256 indexed toTime, uint256 transferSum);

  /**
   * Event for WithdrawToken logging.
   * @param taker address of investor.
   * @param refundSum number of tokens withdrawn.
   */
  event WithdrawToken(address indexed taker, uint256 refundSum);



  function() external payable {
    // Record basic information about the borrower's REPAYMENT ETH
    ethAmount[msg.sender] = ethAmount[msg.sender].add(msg.value);
  }


  /**
   * @dev Constructor initial contract configuration parameters
   * @param _loanTokenAddress order type supported by the token.
   */
  constructor(string _loanTokenName, address _loanTokenAddress, address _maker) public {
    require(bytes(_loanTokenName).length > 0 && _maker != address(0));
    if (!_loanTokenName.stringCompare(TOKEN_ETH)) {
      require(_loanTokenAddress != address(0));
      token20 = _loanTokenAddress;
    }
    toTime = now;
    maker = _maker;
    loanTokenName = _loanTokenName;
    status = StatusChoices.NO_LOAN;
  }

  /**
   * @dev Complete an order combination and issue the loan to the borrower.
   * @param _taker address of investor.
   * @param _toTime order repayment due date.
   * @param _repaymentSum total amount of money that the borrower ultimately needs to return.
   */
  function takerOrder(address _taker, uint32 _toTime, uint256 _repaymentSum) public onlyOwnerOrPartner {
    require(_taker != address(0) && _toTime > 0 && now <= _toTime && _repaymentSum > 0 && status == StatusChoices.NO_LOAN);
    taker = _taker;
    toTime = _toTime;
    repaymentSum = _repaymentSum;

    // Transfer the token provided by the investor to the borrower's address
    if (loanTokenName.stringCompare(TOKEN_ETH)) {
      require(ethAmount[_taker] > 0 && address(this).balance > 0);
      outLoanSum = address(this).balance;
      maker.transfer(outLoanSum);
    } else {
      require(token20 != address(0) && ERC20(token20).balanceOf(address(this)) > 0);
      outLoanSum = ERC20(token20).balanceOf(address(this));
      require(safeErc20Transfer(maker, outLoanSum));
    }

    // Update contract business execution status.
    status = StatusChoices.REPAYMENT_WAITING;

    emit TakerOrder(taker, outLoanSum);
  }






  /**
   * @dev Only the full repayment will execute the contract agreement.
   */
  function executeOrder() public onlyOwnerOrPartner {
    require(now <= toTime && status == StatusChoices.REPAYMENT_WAITING);
    // The borrower pays off the loan and performs two-way operation.
    if (loanTokenName.stringCompare(TOKEN_ETH)) {
      require(ethAmount[maker] >= repaymentSum && address(this).balance >= repaymentSum);
      lastRepaymentSum = address(this).balance;
      taker.transfer(repaymentSum);
    } else {
      require(ERC20(token20).balanceOf(address(this)) >= repaymentSum);
      lastRepaymentSum = ERC20(token20).balanceOf(address(this));
      require(safeErc20Transfer(taker, repaymentSum));
    }

    PledgeContract(owner)._conclude();
    status = StatusChoices.REPAYMENT_ALL;
    emit ExecuteOrder(maker, lastRepaymentSum);
  }



  /**
   * @dev Close position or due repayment operation.
   */
  function forceCloseOrder() public onlyOwnerOrPartner {
    require(status == StatusChoices.REPAYMENT_WAITING);
    uint256 transferSum = 0;

    if (now <= toTime) {
      status = StatusChoices.CLOSE_POSITION;
    } else {
      status = StatusChoices.OVERDUE_STOP;
    }

    if(loanTokenName.stringCompare(TOKEN_ETH)){
        if(ethAmount[maker] > 0 && address(this).balance > 0){
            transferSum = address(this).balance;
            maker.transfer(transferSum);
        }
    }else{
        if(ERC20(token20).balanceOf(address(this)) > 0){
            transferSum = ERC20(token20).balanceOf(address(this));
            require(safeErc20Transfer(maker, transferSum));
        }
    }

    // Return pledge token.
    PledgeContract(owner)._forceConclude(taker);
    emit ForceCloseOrder(toTime, transferSum);
  }



  /**
   * @dev Withdrawal of the token invested by the taker.
   * @param _taker address of investor.
   * @param _refundSum refundSum number of tokens withdrawn.
   */
  function withdrawToken(address _taker, uint256 _refundSum) public onlyOwnerOrPartner {
    require(status == StatusChoices.NO_LOAN);
    require(_taker != address(0) && _refundSum > 0);
    if (loanTokenName.stringCompare(TOKEN_ETH)) {
      require(address(this).balance >= _refundSum && ethAmount[_taker] >= _refundSum);
      _taker.transfer(_refundSum);
      ethAmount[_taker] = ethAmount[_taker].sub(_refundSum);
    } else {
      require(ERC20(token20).balanceOf(address(this)) >= _refundSum);
      require(safeErc20Transfer(_taker, _refundSum));
    }
    emit WithdrawToken(_taker, _refundSum);
  }


  /**
   * @dev Since the implementation of usdt ERC20.sol transfer code does not design the return value,
   * @dev which is different from most ERC20 token interfaces,most erc20 transfer token agreements return bool.
   * @dev it is necessary to independently adapt the interface for usdt token in order to transfer successfully
   * @dev if not, the transfer may fail.
   */
  function safeErc20Transfer(address _toAddress,uint256 _transferSum) internal returns (bool) {
    if(loanTokenName.stringCompare(TOKEN_USDT)){
      UsdtERC20(token20).transfer(_toAddress, _transferSum);
    }else{
      require(ERC20(token20).transfer(_toAddress, _transferSum));
    }
    return true;
  }



  /**
   * @dev Get current contract order status.
   */
  function getPledgeStatus() public view returns(string pledgeStatus) {
    if (status == StatusChoices.NO_LOAN) {
      pledgeStatus = "NO_LOAN";
    } else if (status == StatusChoices.REPAYMENT_WAITING) {
      pledgeStatus = "REPAYMENT_WAITING";
    } else if (status == StatusChoices.REPAYMENT_ALL) {
      pledgeStatus = "REPAYMENT_ALL";
    } else if (status == StatusChoices.CLOSE_POSITION) {
      pledgeStatus = "CLOSE_POSITION";
    } else {
      pledgeStatus = "OVERDUE_STOP";
    }
  }

}
/**
 * @title OrderManageContract
 * @dev Order process management contract.
 */
function safeErc20Transfer(address _toAddress,uint256 _transferSum) internal returns (bool) {
  if(loanTokenName.stringCompare(TOKEN_USDT)){
    UsdtERC20(token20).transfer(_toAddress, _transferSum);
  }else{
    require(ERC20(token20).transfer(_toAddress, _transferSum));
  }
  return true;
}

/**
 * @dev Since the implementation of usdt ERC20.sol transfer code does not design the return value,
 * @dev which is different from most ERC20 token interfaces,most erc20 transfer token agreements return bool.
 * @dev it is necessary to independently adapt the interface for usdt token in order to transfer successfully
 * @dev if not, the transfer may fail.
 */