// Copyright (C) 2010-2013  MarketFactory, Inc. All Rights Reserved.
#ifndef __MFAPI_MFCLIENT__
#define __MFAPI_MFCLIENT__
#include <API/Clients/CPP/Proto/APITypes.h>
#include <API/Clients/CPP/Proto/LogLevel.h>
#include <API/Clients/CPP/Proto/Side.h>
#include <API/Clients/CPP/Proto/OrderType.h>
#include <API/Clients/CPP/Proto/TimeInForce.h>
#include <API/Clients/CPP/Proto/APITest.h>
#include <API/Clients/CPP/Proto/APIResult.h>

#include <stdint.h>
#include <string>
#include <vector>

namespace MFAPI {
class MFHandler;
class MFFloat;
class APIFeed;
class APIMarket;
class ClientImp;

// An object for storing KeyValue pairs that are for order parameter lists
class KeyValue {
 public:
  KeyValue(const std::string& k, const std::string& v) : key(k), value(v) {}
  std::string key;
  std::string value;
};

typedef std::vector<KeyValue> Parameters;

/**
 * An API client, which processes a single network connection.
 *
 * There may be more than one client instance, one for each network connection.
 * A client dispatches callbacks on a handler interface which the client
 * implements. @see MFHandler.
 */
class MFClient {
 public:
  /**
   * Internal methods used for testing. Do not call.
   */
  void setUseHeartbeat(bool use);

  /**
   * Internal methods used for testing. Do not call.
   */
  void setDebug(bool debug);

  /**
   * Enable/Disable sticky subscriprions.
   * Set clearSubscriptionsOnStop=false if subscriptions should persist on
   * stop/start.
   */
  void setStickySubscriptions(bool sticky);
  void setStickySubscriptions(bool sticky, bool clearSubscriptionsOnStop);

  /**
   * Used for implementing smart pointers for Python wrappers. You could use
   * this if you want to manage this object with boost::intrusive_ptr() (but
   * that's your choice). See intrusive_ptr_add_ref/intrusive_ptr_release
   * functions below.
   */
  void addref();

  /**
   * Used for implementing smart pointers for Python wrappers. You could use
   * this if you want to manage this object with boost::intrusive_ptr() (but
   * that's your choice). See intrusive_ptr_add_ref/intrusive_ptr_release
   * functions below.
   */
  void release();

  /**
   * Internal method that turns on API-side logging to the given directory.
   * Do not use this in production; Call it with a null pointer to disable.
   * (This may start a thread dedicated to logging to a binary file.)
   *
   * Return OK on success, or error if the directory is not accessible and/or
   *logging
   * could not be enabled.
   */
  static APIResult::Type setlog(const char* directory, bool aenable = false);

 private:
  MFClient(const MFClient&);
  MFClient& operator=(const MFClient&);

 public:
  /**
   * Create a new client object. A new client needs to be connected explicitly
   * (see connect()).
   */
  MFClient(const char* username, const char* password, const char* hostname1,
           int port1);

  /**
   * Create a new client object. A new client needs to be connected explicitly
   * (see connect()).
   */
  MFClient(const char* username, const char* password, const char* hostname1,
           int port1, const char* hostname2, int port2);

  virtual ~MFClient();

  /**
   * Connect to the first available server with user credentials. Returns
   * APIResult.OK if the connection attempt succeeded.
   *
   * Note: this should not be called by _handler.
   */
  APIResult::Type connect();

  //--------------------------------------------------------------------------
  // Factory methods.

  /**
   * A convenience method which creates a new client object, attempts to
   * connect to Whisperer, and cleans up and throws an exception if we could
   * not connect. Otherwise the new client object/ownership is returned;
   */
  static MFClient* open(const char* username, const char* password,
                        const char* hostname, int port);

  /**
   * A convenience method which creates a new client object, attempts to
   * connect to Whisperer, and cleans up and throws an exception if we could
   * not connect. Otherwise the new client object/ownership is returned;
   */
  static MFClient* open(const char* username, const char* password,
                        const char* hostname1, int port1, const char* hostname2,
                        int port2);

  /**
   * Return true if the channel is connected.
   */
  bool isConnected();

  /**
   * Return true if the channel is currently dispatching.
   */
  bool isRunning();

  /**
   *  Run a loop that will continuously dispatch incoming messages to a
   *  _handler implementation. This method returns some time after a request
   *  to stop is made using stop(), or if an error occurs (e.g. the connection
   *  is broken). A zero return value indicates a normal exit via a stop
   *  request.
   *
   * If the connection is broken and the client succesfully fails over to a
   * secondary server, this dispatcher does not return; onDisconnect() and
   * onConnect() are invoked to let the handler know that this occurred. If
   * the reconnection attempt is not successful, the dispatcher will return an
   * error code.
   *
   * Every call to run() must be preceded by a successful connect() --
   * this call is made for you, but only once, when a client is obtained via
   *open().
   *
   * Note: this should not be invoked by _handler.
   */
  APIResult::Type run(MFHandler* handler);

  /**
   *  For internal use only-- do not use this.
   */
  void get(int& fd1, int& fd2) const;
  APIResult::Type processEvents(MFHandler* handler);

  /**
   * Request to stop the event dispatch loop, does not disconnect from the
   * server. It is legal to dispatch events on this instance again later. This
   * function does not block.
   */
  APIResult::Type stop();

  /**
   * Block until the client has exited the dispatch loop.
   * If the client isn't running, this returns immediately.
   *
   * Note: this should not be called by _handler.
   */
  APIResult::Type join();
  /**
   * (Alias for join()).
   */
  APIResult::Type wait();

  /**
   * Close the connection to the server. The client must not be dispatching
   * for this to legal; if we're dispatching, this returns an error code
   * and does nothing.
   *
   * Note: this should not be called by _handler.
   */
  APIResult::Type close();

  //--------------------------------------------------------------------------
  // Configuration

  /**
   * Get protocol api signature.
   */
  const char* getSignature();

  /**
   * Get the last received sequenceID after successful subscription.
   * Returns -2 if not subscribed.
   * Returns -1 if there are no OF messages for the client on the feed.
   */
  SequenceID_t getSequenceID(FeedID_t feedID);

  /**
   * After a successful connection is established, you may access the server's
   * list of feeds, markets. If you have not yet connected to the server this
   * will return empty.
   */
  APIFeed const* getFeeds(int& n);

  /**
   * After a successful connection is established, you may access the server's
   * list of feeds, markets. This method will return a pointer to the feed of
   * a given name if it exists and NULL if it does not. (this class maintains
   * ownership to the object so do not delete it).
   *
   * @return a pointer to the Feed of the given name or NULL if it does not
   *exist.
   */
  const APIFeed* getFeedByName(const char* name);

  /**
   * After a successful connection is established, you may access the server's
   * list of feeds, markets.  This method will return a pointer to the feed of
   * a given id if it exists and NULL if it does not. (this class maintains
   * ownership to the object so do not delete it).
   *
   *  @return a pointer to the Feed of the given id or NULL if it does not
   *exist.
   */
  const APIFeed* getFeedByID(FeedID_t id);

  /**
   * After a successful connection is established, you may access the server's
   * list of feeds, markets.  This method will return a pointer to the market
   *with
   * a given name within the specified feed if such market exists or NULL if
   *there is
   * no market with the same name.
   *
   *  @return a pointer to the Market with the given name within the given feed
   *or
   * NULL if it does not exist.
   */
  const APIMarket* getMarketByName(const APIFeed& feed, const char* name);

  /**
   * After a successful connection is established, you may access the server's
   * list of feeds, markets.  This method will return a pointer to the market
   *with
   * a given id within the specified feed if such market exists or NULL if there
   *is
   * no market with the same id.
   *
   *  @return a pointer to the Market with the given market id within the given
   *feed or
   * NULL if it does not exist.
   */
  const APIMarket* getMarketByID(const APIFeed& feed, MarketID_t marketID);

  //--------------------------------------------------------------------------
  // Replay

  /**
   * Request replay of order flow events in the range [fromSequenceID,
   *toSequenceID].
   *
   * @return APIResult.OK if the replay request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type replay(FeedID_t feedID, int fromSequenceID, int toSequenceID);

  //--------------------------------------------------------------------------
  // Subscription

  /**
   * Subscribe to market data updates for a specific feed.
   * onSubscriptionEvent() will be called to notify that the flow of updates
   * is ready. If the subscription is valid, onMarketData() will be called
   * later on. The client subscription ID allows one-to-one message mapping.
   *
   * @return APIResult.OK if the subscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type subscribeMD(FeedID_t feedID, MarketID_t marketID,
                              const std::string& clSubID);

  /**
   * Subscribe to market data updates for a specific feed.
   * onSubscriptionEvent() will be called to notify that the flow of updates
   * is ready. If the subscription is valid, onMarketData() will be called
   * later on.
   *
   * @return APIResult.OK if the subscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type subscribeMD(FeedID_t feedID, MarketID_t marketID);

  /**
   * Subscribe to trading for a specific feed. onSubscriptionEvent() will be
   * called to notify that the order gateway is ready. If the subscription is
   * valid, it will be possible to submit and cancel orders and receive
   * trading events.
   *
   * @return APIResult.OK if the subscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type subscribeOF(FeedID_t feedID, const std::string& clSubID);

  /**
   * Subscribe to trading for a specific feed. onSubscriptionEvent() will be
   * called to notify that the order gateway is ready. If the subscription is
   * valid, it will be possible to submit and cancel orders and receive
   * trading events.
   *
   * @return APIResult.OK if the subscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type subscribeOF(FeedID_t feedID);

  /**
   * Subscribe to all the market data feeds. This is intended as a test
   * utility; ideally you control which feeds and markets you connect to.
   *
   * @return APIResult.OK if the subscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type subscribeAllMD();

  /**
   * Unsubscribe to market data updates for a specific feed.
   *
   * @return APIResult.OK if the unsubscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type unsubscribeMD(FeedID_t feedID, MarketID_t marketID,
                                const std::string& clSubID);

  /**
   * Unsubscribe to market data updates for a specific feed.
   *
   * @return APIResult.OK if the unsubscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type unsubscribeMD(FeedID_t feedID, MarketID_t marketID);

  /**
   * Unsubscribe to trading for a specific feed.
   *
   * @return APIResult.OK if the unsubscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type unsubscribeOF(FeedID_t feedID, const std::string& clSubID);

  /**
   * Unsubscribe to trading for a specific feed.
   *
   * @return APIResult.OK if the unsubscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type unsubscribeOF(FeedID_t feedID);

  /**
   * Unsubscribe all market data and trading subscriptions. Call this if you
   * want to stop receiving callbacks but don't want to disconnect.
   *
   * @return APIResult.OK if the unsubscription request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type unsubscribeAll();

  /**
   * Register for a given list of markets on a given feed.
   *
   * @return Pair
   * The first member is APIResult.OK if the comb has been registered.
   * The second member is a unique id to identify the market combination.
   */
  std::pair<APIResult::Type, BatchDefID_t> registerMarketComb(FeedID_t feedID,
                                                              int* marketIds,
                                                              int nmarkets);

  /**
   * Unregister market combination based on the id provided in onBatchEvent
   * callback.
   * Returns APIResult.OK if the unregistration request has been sent.
   */
  APIResult::Type unregisterMarketComb(int batchDefID);

  //--------------------------------------------------------------------------
  // Market Data

  /**
   * Request a market data asnapshot for the feed and market ID.
   * (Note that after connecting you should get a snapshot automatically, so
   * this should be used rarely.)
   *
   * @return APIResult.OK if the snapshot request has been sent,
   * APIResult.SOCKERR_WRITING on error.
   */
  APIResult::Type requestSnapshot(FeedID_t feedID, MarketID_t marketID);

  //--------------------------------------------------------------------------
  // Trading

  /**
   *  Submit an order.
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   */
  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      OrderType::Type ordType, TimeInForce::Type tif);

  /**
   *  Submit an order without minAmount (kept for backward compatibility).
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   */
  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      const MFFloat& stopPrice, const MFFloat& maxShowAmt,
      OrderType::Type ordType, TimeInForce::Type tif);

  /**
   *  Submit an order.
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   */
#if MF_VERSION_AS_NUMBER == 3130
  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      const MFFloat& stopPrice, const MFFloat& maxShowAmt,
      OrderType::Type ordType, TimeInForce::Type tif,
      const Parameters* const parameters);

  /**
   *  Submit an order.
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   *
   *  This method allows the parameter to be input as a JSON string rather than
   *a vector or parameters.
   *  Simpler for the python interface.
   */
  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      const MFFloat& stopPrice, const MFFloat& maxShowAmt,
      OrderType::Type ordType, TimeInForce::Type tif,
      const char* const quoteID);

   /**
   *  Submit modification to an existing order.
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   */
   std::pair<APIResult::Type, OrderID_t> modifyOrder(const char* clOrdID,
                                                    const char* clNewID,
                                                    const MFFloat& amount,
                                                    const MFFloat& price);

	  
#else
  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      const MFFloat& stopPrice, const MFFloat& maxShowAmt,
      const MFFloat& minFillAmt, OrderType::Type ordType,
      TimeInForce::Type tif);

  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      const MFFloat& stopPrice, const MFFloat& maxShowAmt,
      const MFFloat& minFillAmt, OrderType::Type ordType, TimeInForce::Type tif,
      const char* quoteID);

  /**
   *  Submit an order.
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   */
  std::pair<APIResult::Type, OrderID_t> submitOrder(
      const char* clOrdID, FeedID_t feedID, MarketID_t marketID,
      Side::Type side, const MFFloat& amount, const MFFloat& price,
      const MFFloat& stopPrice, const MFFloat& maxShowAmt,
      OrderType::Type ordType, TimeInForce::Type tif, const char* quoteID);
  /**
   *  Submit modification without minAmount (kept for backward compatibility).
   *
   *  @return Pair
   *  The first member is APIResult.OK if the order has been sent.
   *  The second member is the orderID.
   */
   
   std::pair<MFAPI::APIResult::Type, long int> modifyOrder(const char* clOrdID,
														   const char* clNewID,
                                                           const MFFloat& amount,
                                                           const MFFloat& price);
   
  std::pair<APIResult::Type, OrderID_t> modifyOrder(const char* clOrdID,
                                                    const char* clNewID,
                                                    const MFFloat& amount,
                                                    const MFFloat& minFillAmt,
                                                    const MFFloat& price);
#endif /* MF_VERSION_AS_NUMBER */


  /**
   * Cancel a previously placed order by client order id ('clOrdID'). Return
   * APIResult.INVALID_ORDERID if the order cannot be found locally, i.e.,
   * if the 'clOrdID' does not correspond to any previously placed order on
   * this client interface.
   */
  APIResult::Type cancelOrder(const char* cxlID, const char* clOrdID);

  /**
   * Cancel a previously placed order by internal 'orderID'. Return
   * APIResult.INVALID_ORDERID if the internal 'orderID' could not be provided.
   */
  APIResult::Type cancelOrder(const char* cxlID, OrderID_t orderID);

  /**
   * Cancel all the orders on a feed.
   */
  APIResult::Type cancelAll(FeedID_t feedID, const char* cxlID);

  /**
   * Cancel all the orders, on all feeds.
   */
  APIResult::Type cancelAll(const char* cxlID);

  //--------------------------------------------------------------------------
  // Other

  /**
   * Logs a debug message into the Whisperer server log file. (We use this for
   * inserting markers for automated testing and debugging.)
   */
  void log(const char* message);
  void log(const char* message, LogLevel::Type level);

  /**
   * Internal testing function. This can be used to perform client-side
   * testing. See the documentation for the corresponding enum to find out
   * what it does.
   */
  void test(APITest::Type test);

  /**
   * Get the average ping time in nanoseconds.
   */
  Timestamp_t getAveragePingTime();

  /**
   * Get the origFeedSequenceID for the last
   * DropCopyMessage.
   */
  SequenceID_t getOrigFeedSequenceID();

 protected:
  /**
   * Admin interface: Call this method to allow non-admin API clients to
   * connect.
   */
  APIResult::Type allowClientsToConnect();

  /**
   * Admin interface: Lock / unlock user accounts.
   */
  APIResult::Type lockUser(const char* clRequestID, const char* username,
                           bool locked);

 private:
  ClientImp* imp;
  unsigned int _rc;
};

// In case you would like to use boost::intrusive_ptr to manage the lifetime of
// this object, these are defined for you (but you don't have to.)
inline void intrusive_ptr_add_ref(MFClient* p) { p->addref(); }
inline void intrusive_ptr_release(MFClient* p) { p->release(); }
}
#endif
// InclKwds: MFClient
