contract PledgeContract is PartnerAuthority {

  using SafeMath for uint256;
  using SafeCompare for string;

  /**
   * @dev Type of execution state of the pledge contract（irreversible）
   */
  enum StatusChoices {
    NO_PLEDGE_INFO,
    PLEDGE_CREATE_MATCHING,
    PLEDGE_REFUND
  }

  string public pledgeTokenName;
  uint256 public pledgeId;
  address internal maker;
  address internal token20;
  address internal factory;
  address internal escrowContract;
  uint256 internal pledgeAccountSum;
  // order contract address
  address internal orderContract;
  string internal loanTokenName;
  StatusChoices internal status;
  address internal tokenPoolAddress;
  string internal constant TOKEN_ETH = "ETH";
  string internal constant TOKEN_USDT = "USDT";
  // ETH pledge account
  mapping(address => uint256) internal verifyEthAccount;


  /**
   * Event for createOrderContract logging.
   * @param newOrderContract management contract address.
   */
  event CreateOrderContract(address newOrderContract);


  /**
   * Event for WithdrawToken logging.
   * @param maker address of investor.
   * @param pledgeTokenName token name.
   * @param refundSum number of tokens withdrawn.
   */
  event WithdrawToken(address indexed maker, string pledgeTokenName, uint256 refundSum);


  /**
   * Event for appendEscrow logging.
   * @param maker address of borrower.
   * @param appendSum append amount.
   */
  event AppendEscrow(address indexed maker, uint256 appendSum);


  /**
   * @dev Constructor initial contract configuration parameters
   */
  constructor(uint256 _pledgeId, address _factory , address _escrowContract) public {
    require(_pledgeId > 0 && _factory != address(0) && _escrowContract != address(0));
    pledgeId = _pledgeId;
    factory = _factory;
    status = StatusChoices.NO_PLEDGE_INFO;
    escrowContract = _escrowContract;
  }



  // -----------------------------------------
  // external interface
  // -----------------------------------------



  function() external payable {
    require(status != StatusChoices.PLEDGE_REFUND);
    // Identify the borrower.
    if (maker != address(0)) {
      require(address(msg.sender) == maker);
    }
    // Record basic information about the borrower's pledge ETH
    verifyEthAccount[msg.sender] = verifyEthAccount[msg.sender].add(msg.value);
  }


  /**
   * @dev Add the pledge information and transfer the pledged token into the corresponding currency pool.
   * @param _pledgeTokenName maker pledge token name.
   * @param _maker borrower address.
   * @param _pledgeSum pledge amount.
   * @param _loanTokenName pledge token type.
   */
  function addRecord(string _pledgeTokenName, address _maker, uint256 _pledgeSum, string _loanTokenName) public onlyOwner {
    require(_maker != address(0) && _pledgeSum > 0 && status != StatusChoices.PLEDGE_REFUND);
    // Add the pledge information for the first time.
    if (status == StatusChoices.NO_PLEDGE_INFO) {
      // public data init.
      maker = _maker;
      pledgeTokenName = _pledgeTokenName;
      tokenPoolAddress = checkedTokenPool(pledgeTokenName);
      PledgeFactory(factory).updatePledgeType(pledgeId, pledgeTokenName);
      // Assign rights to the operation of the contract pool
      PledgeFactory(factory).tokenPoolOperater(tokenPoolAddress, address(this));
      // Create order management contracts.
      createOrderContract(_loanTokenName);
    }
    // Record information of each pledge.
    pledgeAccountSum = pledgeAccountSum.add(_pledgeSum);
    PledgePoolBase(tokenPoolAddress).addRecord(maker, pledgeAccountSum, pledgeId, pledgeTokenName);
    // Transfer the pledge token to the appropriate token pool.
    if (pledgeTokenName.stringCompare(TOKEN_ETH)) {
      require(verifyEthAccount[maker] >= _pledgeSum);
      tokenPoolAddress.transfer(_pledgeSum);
    } else {
      token20 = checkedToken(pledgeTokenName);
      require(ERC20(token20).balanceOf(address(this)) >= _pledgeSum);
      require(safeErc20Transfer(token20,tokenPoolAddress, _pledgeSum));
    }
  }

  /**
   * @dev Increase the number of pledged tokens.
   * @param _appendSum append amount.
   */
  function appendEscrow(uint256 _appendSum) public onlyOwner {
    require(status == StatusChoices.PLEDGE_CREATE_MATCHING);
    addRecord(pledgeTokenName, maker, _appendSum, loanTokenName);
    emit AppendEscrow(maker, _appendSum);
  }


  /**
   * @dev Withdraw pledge behavior.
   * @param _maker borrower address.
   */
  function withdrawToken(address _maker) public onlyOwner {
    require(status != StatusChoices.PLEDGE_REFUND);
    uint256 pledgeSum = 0;
    // there are two types of retractions.
    if (status == StatusChoices.NO_PLEDGE_INFO) {
      pledgeSum = classifySquareUp(_maker);
    } else {
      status = StatusChoices.PLEDGE_REFUND;
      require(PledgePoolBase(tokenPoolAddress).withdrawToken(pledgeId, maker, pledgeAccountSum));
      pledgeSum = pledgeAccountSum;
    }
    emit WithdrawToken(_maker, pledgeTokenName, pledgeSum);
  }


  /**
   * @dev Executed in some extreme unforsee cases, to avoid eth locked.
   * @param _tokenName recycle token type.
   * @param _amount Number of eth to recycle.
   */
  function recycle(string _tokenName, uint256 _amount) public onlyOwner {
    require(status != StatusChoices.NO_PLEDGE_INFO && _amount>0);
    if (_tokenName.stringCompare(TOKEN_ETH)) {
      require(address(this).balance >= _amount);
      owner.transfer(_amount);
    } else {
      address token = checkedToken(_tokenName);
      require(ERC20(token).balanceOf(address(this)) >= _amount);
      require(safeErc20Transfer(token,owner, _amount));
    }
  }



  /**
   * @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 _token20,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;
  }



  // -----------------------------------------
  // internal interface
  // -----------------------------------------



  /**
   * @dev Create an order process management contract for the match and repayment business.
   * @param _loanTokenName expect loan token type.
   */
  function createOrderContract(string _loanTokenName) internal {
    require(bytes(_loanTokenName).length > 0);
    status = StatusChoices.PLEDGE_CREATE_MATCHING;
    address loanToken20 = checkedToken(_loanTokenName);
    OrderManageContract newOrder = new OrderManageContract(_loanTokenName, loanToken20, maker);
    setPartner(address(newOrder));
    newOrder.setPartner(owner);
    // update contract public data.
    orderContract = newOrder;
    loanTokenName = _loanTokenName;
    emit CreateOrderContract(address(newOrder));
  }

  /**
   * @dev classification withdraw.
   * @dev Execute without changing the current contract data state.
   * @param _maker borrower address.
   */
  function classifySquareUp(address _maker) internal returns(uint256 sum) {
    if (pledgeTokenName.stringCompare(TOKEN_ETH)) {
      uint256 pledgeSum = verifyEthAccount[_maker];
      require(pledgeSum > 0 && address(this).balance >= pledgeSum);
      _maker.transfer(pledgeSum);
      verifyEthAccount[_maker] = 0;
      sum = pledgeSum;
    } else {
      uint256 balance = ERC20(token20).balanceOf(address(this));
      require(balance > 0);
      require(safeErc20Transfer(token20,_maker, balance));
      sum = balance;
    }
  }

  /**
   * @dev Check wether the token is included for a token name.
   * @param _tokenName token name.
   */
  function checkedToken(string _tokenName) internal view returns(address) {
    address tokenAddress = EscrowMaintainContract(escrowContract).includeToken(_tokenName);
    require(tokenAddress != address(0));
    return tokenAddress;
  }

  /**
   * @dev Check wether the token pool is included for a token name.
   * @param _tokenName pledge token name.
   */
  function checkedTokenPool(string _tokenName) internal view returns(address) {
    address tokenPool = EscrowMaintainContract(escrowContract).includeTokenPool(_tokenName);
    require(tokenPool != address(0));
    return tokenPool;
  }



  // -----------------------------------------
  // business relationship interface
  // (Only the order contract has authority to operate)
  // -----------------------------------------



  /**
   * @dev Refund of the borrower’s pledge.
   */
  function _conclude() public onlyPartner {
    require(status == StatusChoices.PLEDGE_CREATE_MATCHING);
    status = StatusChoices.PLEDGE_REFUND;
    require(PledgePoolBase(tokenPoolAddress).refundTokens(pledgeId, pledgeAccountSum, maker));
  }

  /**
   * @dev Expired for repayment or close position.
   * @param _taker address of investor.
   */
  function _forceConclude(address _taker) public onlyPartner {
    require(_taker != address(0) && status == StatusChoices.PLEDGE_CREATE_MATCHING);
    status = StatusChoices.PLEDGE_REFUND;
    require(PledgePoolBase(tokenPoolAddress).refundTokens(pledgeId, pledgeAccountSum, _taker));
  }



  // -----------------------------------------
  // query interface (use no gas)
  // -----------------------------------------



  /**
   * @dev Get current contract order status.
   * @return pledgeStatus state indicate.
   */
  function getPledgeStatus() public view returns(string pledgeStatus) {
    if (status == StatusChoices.NO_PLEDGE_INFO) {
      pledgeStatus = "NO_PLEDGE_INFO";
    } else if (status == StatusChoices.PLEDGE_CREATE_MATCHING) {
      pledgeStatus = "PLEDGE_CREATE_MATCHING";
    } else {
      pledgeStatus = "PLEDGE_REFUND";
    }
  }

  /**
   * @dev get order contract address. use no gas.
   */
  function getOrderContract() public view returns(address) {
    return orderContract;
  }

  /**
   * @dev Gets the total number of tokens pledged under the current contract.
   */
  function getPledgeAccountSum() public view returns(uint256) {
    return pledgeAccountSum;
  }

  /**
   * @dev get current contract borrower address.
   */
  function getMakerAddress() public view returns(address) {
    return maker;
  }

  /**
   * @dev get current contract pledge Id.
   */
  function getPledgeId() external view returns(uint256) {
    return pledgeId;
  }

}
/**
 * @title PledgeContract
 * @dev Pledge process management contract
 */
function getOrderContract() public view returns(address) {
    return orderContract;
  }
/**
 * @dev get order contract address. use no gas.
 */