package com.marketfactory.sample;

import com.marketfactory.api.*;

import java.util.logging.Logger;

import static com.marketfactory.api.APIResult.*;

/**
 * Sample for using the non-blocking event polling mechanism {@link MFClient#processEvents(MFHandler)}.
 * This will allow you to keep ownership of the processing thread so it can do other work, process other MFClients, etc.
 * <br>
 * The key differences are:
 * <ul>
 *  <li> no onStart; onStop; onConnect; onDisconnect events
 *  <li> use the {@link MFClient#connectNonBlocking()} method. This will connect to the server and send
 *  the logon messages. It will however not wait for the response. You need to poll for events with processEvents
 *  to update the client state.
 * </ul>
 */
public class BasicPollingSample {

    private static final String DEFAULT_HOSTNAME = "ny9.marketfactory.com";
    private static final int DEFAULT_PORT = 10033;

    private static final Logger LOG = Logger.getLogger("example");

    private static final String USAGE_PARAMETERS = "username password [hostname] [port]";
    private MFClient client;
    private MFHandler handler;
    private APIResult lastApiResult;
    private State currentState;

    private static String username;
    private static String password;
    private static String hostname;
    private static int port;

    public static void main(String args[]) {
        if (args.length < 2) {
            throw new RuntimeException("Not enough parameters provided. Parameters: " + USAGE_PARAMETERS);
        }

        username = args[0];
        password = args[1];
        hostname = args.length >= 3 ? args[2] : DEFAULT_HOSTNAME;
        port = args.length >= 4 ? Integer.valueOf(args[3]) : DEFAULT_PORT;

        new BasicPollingSample().start();
    }

    /**
     * Example of a processing loop
     */
    private void start() {

        initializeClient();

        while (currentState.shouldContinue()) {
            processEvents();
            currentState = currentState.transition();
            otherClientActivities();
        }

        shutdownClient();
        LOG.info("Exit. ApiResult: " + lastApiResult);
    }

    private void initializeClient() {
        this.client = new MFClient(username, password, hostname, port);
        this.handler = new APIHandler();  // Create a handler to receive all the callbacks.
        this.currentState = new DisconnectedState();
        this.lastApiResult = OK;
    }

    private void shutdownClient() {
        this.client.close();
    }

    private void processEvents() {
        this.lastApiResult = client.processEvents(handler);
    }

    private void otherClientActivities() {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            // noop
        }
    }

    /**
     * State machine for the polling sample states
     */
    private interface State {
        boolean shouldContinue();

        State transition();
    }

    /**
     * Connect to he MF server. This sends the logon message but does not wait for the response, need to
     * make processEvent calls to poll and handle the server response messages and update the MFClient state.
     */
    private class DisconnectedState extends RunningState {

        public State transition() {

            if (lastApiResult != OK && lastApiResult != NOT_IMPLEMENTED) {
                return new ExitState();
            }

            lastApiResult = client.connectNonBlocking();
            LOG.info("Sending non blocking connection request");
            if (lastApiResult == OK) {
                return new WaitForConnectionResponseState();
            } else {
                LOG.severe("Could not connect. [result: " + lastApiResult + "]");
                return new ExitState();
            }
        }
    }

    /**
     * After sending the connection messages we need to wait for the server to respond.
     * If the response was successful the client will report 'isConnected' as true and we can subscribe.
     */
    private class WaitForConnectionResponseState extends RunningState implements State {

        @Override
        public State transition() {
            if (lastApiResult != OK) {
                return new ExitState();
            }

            if (client.isConnected()) {  // do the subscriptions
                final APIFeed feed = client.getFeedByName("HOTSPOT");
                for (APIMarket market : feed.marketList) {
                    client.subscribeMD(market.feedID, market.marketID);
                }

                client.subscribeOF(feed.feedID);

                return new RunningState();
            } else {
                return this;
            }
        }
    }


    /**
     * Here we are running and polling. Callbacks are being serviced. Any ApiResult that is not OK
     * will terminate execution.
     */
    private class RunningState implements State {
        @Override
        public boolean shouldContinue() {
            return true;
        }

        @Override
        public State transition() {
            if (lastApiResult != OK) {
                return new ExitState();
            } else {
                return this;
            }
        }
    }

    private class ExitState implements State {

        @Override
        public boolean shouldContinue() {
            return false;
        }

        @Override
        public State transition() {
            return this;
        }
    }

    // ==============================================
    // Handler which logs the received events
    // ==============================================
    private static class APIHandler extends AbstractMFHandler {
        private int numHeartbeats = 0;

        public void onHeartbeat(Heartbeat heartbeat, MFClient client) {
            if (numHeartbeats++ % 1000 == 0) {
                LOG.info("===== NumHeartbeats: " + numHeartbeats);
            }
        }

        public void onSubscriptionEvent(SubscriptionEvent event, MFClient client) {
            LOG.info("===== onSubscriptionEvent: " + event);
        }

        public void onMarketData(MarketView mvd, MFClient client) {
            LOG.info("===== onMarketData: " + mvd);
        }

        public void onBatchComplete(int batchDefID, MFClient client) {
            LOG.info("===== onBatchComplete: ");
        }

        public void onEventReport(EventReport eventReport, MFClient client) {
            LOG.info("===== EventReport: ");
        }

        public void onOrderReceived(OrderReceived orderReceived, MFClient client) {
            LOG.info("===== onOrderReceived: " + orderReceived);
        }

        public void onOrderRejected(OrderRejected orderRejected, MFClient client) {
            LOG.info("===== onOrderRejected: " + orderRejected);
        }

        public void onOrderSubmitted(OrderSubmitted orderSubmitted, MFClient client) {
            LOG.info("===== onOrderSubmitted: " + orderSubmitted);
        }

        public void onOrderCanceled(OrderCanceled orderCanceled, MFClient client) {
            LOG.info("===== onOrderCanceled: " + orderCanceled);
        }

        public void onOrderCancelRejected(OrderCancelRejected orderCancelRejected, MFClient client) {
            LOG.info("===== onOrderCancelRejected: " + orderCancelRejected);
        }

        public void onDropCopy(DropCopy dropCopy, MFClient client) {
            LOG.info("===== onDropCopy:" + dropCopy);
        }

        public void onTradeCapture(TradeCapture tradeCapture, MFClient client) {
            LOG.info("===== onTradeCapture: " + tradeCapture);
        }

        public void onOrderDone(OrderDone orderDone, MFClient client) {
            LOG.info("===== onOrderDone: " + orderDone);
        }
    }
}