contract PPMarket is Marketable, Ownable, ChargesFee {
  using SafeERC20 for IERC20;
  using SafeMath for uint256;

  uint256 public constant VERSION = 2;

  bytes32 public constant GALT_FEE_KEY = bytes32("MARKET_GALT");
  bytes32 public constant ETH_FEE_KEY = bytes32("MARKET_ETH");

  struct SaleOrderDetails {
    address propertyToken;
    uint256[] propertyTokenIds;
    string dataLink;
  }

  IPPGlobalRegistry internal globalRegistry;

  // (propertyTokenAddress (ERC721) => (tokenId => mutex))
  mapping(uint256 => SaleOrderDetails) public saleOrderDetails;

  constructor(
    IPPGlobalRegistry _globalRegistry,
    uint256 _ethFee,
    uint256 _galtFee
  )
    public
    ChargesFee(_ethFee, _galtFee)
  {
    globalRegistry = _globalRegistry;
  }

  function createSaleOrder(
    address _propertyToken,
    uint256[] calldata _propertyTokenIds,
    address _operator,
    uint256 _ask,
    string calldata _dataLink,
    EscrowCurrency _currency,
    IERC20 _erc20address
  )
    external
    payable
    returns (uint256)
  {
    _performCreateSaleOrderChecks(_propertyToken, _propertyTokenIds);
    _acceptPayment(_propertyToken);

    uint256 id = _createSaleOrder(
      _operator,
      _ask,
      _currency,
      _erc20address
    );

    SaleOrderDetails storage details = saleOrderDetails[id];

    details.propertyToken = _propertyToken;
    details.propertyTokenIds = _propertyTokenIds;
    details.dataLink = _dataLink;

    return id;
  }

  function closeSaleOrder(
    uint256 _orderId
  )
    external
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];
    require(saleOrder.seller == msg.sender, "Market. Only seller allowed");
    _closeSaleOrder(_orderId);
  }

  function closeNotActualSaleOrder(
    uint256 _orderId
  )
    external
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];
    SaleOrderDetails storage details = saleOrderDetails[_orderId];

    IPPToken propertyToken = IPPToken(details.propertyToken);

    uint256 len = details.propertyTokenIds.length;

    bool someTokenNotOwnedBySeller = false;
    for (uint256 i = 0; i < len; i++) {
      if (propertyToken.ownerOf(details.propertyTokenIds[i]) != saleOrder.seller) {
        someTokenNotOwnedBySeller = true;
        break;
      }
    }

    require(someTokenNotOwnedBySeller, "Market. All tokens owned by seller");
    _closeSaleOrder(_orderId);
  }

  // INTERNAL

  function _performCreateSaleOrderChecks(
    address _propertyToken,
    uint256[] memory _propertyTokenIds
  )
    internal
    view
  {
    IPPTokenRegistry(globalRegistry.getPPTokenRegistryAddress()).requireValidToken(_propertyToken);

    uint256 len = _propertyTokenIds.length;
    uint256 tokenId;

    require(len > 0, "There should be at least one token ID specified");

    for (uint256 i = 0; i < len; i++) {
      tokenId = _propertyTokenIds[i];
      require(IPPToken(_propertyToken).exists(tokenId), "Property token with the given ID doesn't exist");
      require(IERC721(_propertyToken).ownerOf(tokenId) == msg.sender, "Sender should own the token");
    }
  }

  function _galtToken() internal view returns (IERC20) {
    return IERC20(globalRegistry.getGaltTokenAddress());
  }

  // Overrides ChargesFee._acceptPayment() and accepts an additional property owner fee along with the protocol fee
  function _acceptPayment(address _propertyToken) internal {
    address payable controller = IPPToken(_propertyToken).controller();

    if (msg.value == 0) {
      _galtToken().transferFrom(msg.sender, address(this), galtFee);

      uint256 propertyOwnerFee = IPPTokenController(controller).fees(GALT_FEE_KEY);
      _galtToken().transferFrom(msg.sender, controller, propertyOwnerFee);
    } else {
      uint256 propertyOwnerFee = IPPTokenController(controller).fees(ETH_FEE_KEY);
      uint256 totalFee = ethFee.add(propertyOwnerFee);

      require(msg.value == totalFee, "Invalid fee");

      controller.transfer(propertyOwnerFee);
    }
  }

  // GETTERS

  function getSaleOrderDetails(uint256 _rId)
    external
    view
    returns (
      uint256[] memory propertyTokenIds,
      address propertyToken,
      string memory dataLink
    )
  {
    return (
      saleOrderDetails[_rId].propertyTokenIds,
      saleOrderDetails[_rId].propertyToken,
      saleOrderDetails[_rId].dataLink
    );
  }

  function getSaleOrderAsk(uint256 _rId) external view returns (uint256) {
    return saleOrders[_rId].ask;
  }

  function requireValidEthOrder(uint256 _rId) external view {
    SaleOrder storage order = saleOrders[_rId];

    require(order.status == SaleOrderStatus.ACTIVE, "Order isn't active");
    require(order.escrowCurrency == Marketable.EscrowCurrency.ETH, "Invalid currency");
  }
}
function _acceptPayment(address _propertyToken) internal {
  address payable controller = IPPToken(_propertyToken).controller();

  if (msg.value == 0) {
    _galtToken().transferFrom(msg.sender, address(this), galtFee);

    uint256 propertyOwnerFee = IPPTokenController(controller).fees(GALT_FEE_KEY);
    _galtToken().transferFrom(msg.sender, controller, propertyOwnerFee);
  } else {
    uint256 propertyOwnerFee = IPPTokenController(controller).fees(ETH_FEE_KEY);
    uint256 totalFee = ethFee.add(propertyOwnerFee);

    require(msg.value == totalFee, "Invalid fee");

    controller.transfer(propertyOwnerFee);
  }
}

// Overrides ChargesFee._acceptPayment() and accepts an additional property owner fee along with the protocol fee