contract Marketable {
  using SafeMath for uint256;
  using ArraySet for ArraySet.Uint256Set;

  event SaleOfferAskChanged(
    uint256 indexed saleOrderId,
    address indexed buyer,
    uint256 newAskPrice
  );

  event SaleOfferBidChanged(
    uint256 indexed saleOrderId,
    address indexed buyer,
    uint256 newBidPrice
  );

  event SaleOrderStatusChanged(uint256 indexed orderId, SaleOrderStatus indexed status);
  event SaleOfferStatusChanged(uint256 indexed orderId, address indexed buyer, SaleOfferStatus indexed status);

  enum SaleOrderStatus {
    INACTIVE,
    ACTIVE
  }

  enum SaleOfferStatus {
    INACTIVE,
    ACTIVE
  }

  enum EscrowCurrency {
    ETH,
    ERC20
  }

  struct SaleOrder {
    SaleOrderStatus status;

    uint256 id;
    address seller;
    address operator;
    uint256 createdAt;
    uint256 ask;
    address lastBuyer;

    // Order currency
    IERC20 tokenContract;

    // Escrow order currency
    EscrowCurrency escrowCurrency;
  }

  struct SaleOffer {
    SaleOfferStatus status;

    address buyer;
    uint256 ask;
    uint256 bid;
    uint256 lastAskAt;
    uint256 lastBidAt;
    uint256 createdAt;
  }

  uint256 internal idCounter;

  mapping(uint256 => SaleOrder) public saleOrders;
  mapping(uint256 => mapping(address => SaleOffer)) public saleOffers;

  function createSaleOffer(
    uint256 _orderId,
    uint256 _bid
  )
    external
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];

    require(saleOrder.seller != msg.sender, "Can't apply as seller");
    require(saleOrder.status == SaleOrderStatus.ACTIVE, "SaleOrderStatus should be ACTIVE");
    require(_bid > 0, "Negative ask price");

    SaleOffer storage saleOffer = saleOffers[_orderId][msg.sender];
    require(saleOffer.status == SaleOfferStatus.INACTIVE, "Offer already exists");

    saleOffer.buyer = msg.sender;
    saleOffer.bid = _bid;
    saleOffer.ask = saleOrder.ask;
    saleOffer.lastBidAt = block.timestamp;
    saleOffer.createdAt = block.timestamp;

    emit SaleOfferBidChanged(_orderId, msg.sender, _bid);
    emit SaleOfferAskChanged(_orderId, msg.sender, saleOrder.ask);

    _changeSaleOfferStatus(saleOrder, msg.sender, SaleOfferStatus.ACTIVE);
  }

  function changeSaleOfferAsk(
    uint256 _orderId,
    address _buyer,
    uint256 _ask
  )
    external
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];
    SaleOffer storage saleOffer = saleOffers[_orderId][_buyer];

    require(saleOrder.status == SaleOrderStatus.ACTIVE, "ACTIVE sale order required");
    require(saleOffer.status == SaleOfferStatus.ACTIVE, "ACTIVE sale offer status required");
    require(saleOrder.operator == msg.sender || saleOrder.seller == msg.sender, "Market. Only operator or seller allowed");

    saleOffer.ask = _ask;

    emit SaleOfferAskChanged(_orderId, msg.sender, saleOrder.ask);
  }

  function changeSaleOfferBid(
    uint256 _orderId,
    uint256 _bid
  )
    external
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];
    SaleOffer storage saleOffer = saleOffers[_orderId][msg.sender];

    require(saleOrder.status == SaleOrderStatus.ACTIVE, "ACTIVE sale order required");
    require(saleOffer.status == SaleOfferStatus.ACTIVE, "ACTIVE sale offer status required");
    require(saleOffer.buyer == msg.sender, "Market. Only buyer allowed");

    saleOffer.bid = _bid;

    emit SaleOfferBidChanged(_orderId, msg.sender, _bid);
  }

  function closeSaleOffer(
    uint256 _orderId,
    address _buyer
  )
    external
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];
    SaleOffer storage saleOffer = saleOffers[_orderId][_buyer];

    require(
      msg.sender == saleOrder.seller || msg.sender == saleOffer.buyer,
      "Only seller/buyer are allowed");

    _changeSaleOfferStatus(saleOrder, _buyer, SaleOfferStatus.INACTIVE);
  }

  // INTERNAL

  function _nextId() internal returns (uint256) {
    idCounter += 1;
    return idCounter;
  }

  function _createSaleOrder(
    address _operator,
    uint256 _ask,
    EscrowCurrency _currency,
    IERC20 _erc20address
  )
    internal
    returns (uint256 id)
  {
    id = _nextId();

    require(_ask > 0, "Negative ask price");

    SaleOrder storage saleOrder = saleOrders[id];

    saleOrder.id = id;
    saleOrder.seller = msg.sender;
    saleOrder.operator = _operator;
    saleOrder.escrowCurrency = _currency;
    saleOrder.tokenContract = _erc20address;
    saleOrder.ask = _ask;
    saleOrder.createdAt = block.timestamp;

    _changeSaleOrderStatus(saleOrders[id], SaleOrderStatus.ACTIVE);

    return id;
  }

  function _closeSaleOrder(
    uint256 _orderId
  )
    internal
  {
    SaleOrder storage saleOrder = saleOrders[_orderId];

    _changeSaleOrderStatus(saleOrder, SaleOrderStatus.INACTIVE);
  }

  function _changeSaleOrderStatus(
    SaleOrder storage _order,
    SaleOrderStatus _status
  )
    internal
  {
    emit SaleOrderStatusChanged(_order.id, _status);

    _order.status = _status;
  }

  function _changeSaleOfferStatus(
    SaleOrder storage _saleOrder,
    address _buyer,
    SaleOfferStatus _status
  )
    internal
  {
    emit SaleOfferStatusChanged(_saleOrder.id, _buyer, _status);

    saleOffers[_saleOrder.id][_buyer].status = _status;
  }

  // GETTERS

  function saleOfferExists(
    uint256 _rId,
    address _buyer
  )
    external
    view
    returns (bool)
  {
    return saleOffers[_rId][_buyer].status == SaleOfferStatus.ACTIVE;
  }

  function saleOrderExists(uint256 _rId) external view returns (bool) {
    return saleOrders[_rId].status == SaleOrderStatus.ACTIVE;
  }
}
function _nextId() internal returns (uint256) {
  idCounter += 1;
  return idCounter;
}

// INTERNAL