/*
 * Copyright 2014 Parity authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.paritytrading.parity.fix;

import static com.paritytrading.philadelphia.fix44.FIX44Enumerations.*;
import static com.paritytrading.philadelphia.fix44.FIX44MsgTypes.*;
import static com.paritytrading.philadelphia.fix44.FIX44Tags.*;

import com.paritytrading.foundation.ASCII;
import com.paritytrading.nassau.soupbintcp.SoupBinTCP;
import com.paritytrading.nassau.soupbintcp.SoupBinTCPClient;
import com.paritytrading.nassau.soupbintcp.SoupBinTCPClientStatusListener;
import com.paritytrading.parity.net.poe.POE;
import com.paritytrading.parity.net.poe.POEClientListener;
import com.paritytrading.parity.util.Instrument;
import com.paritytrading.parity.util.Instruments;
import com.paritytrading.philadelphia.FIXConfig;
import com.paritytrading.philadelphia.FIXConnection;
import com.paritytrading.philadelphia.FIXConnectionStatusListener;
import com.paritytrading.philadelphia.FIXMessage;
import com.paritytrading.philadelphia.FIXMessageListener;
import com.paritytrading.philadelphia.FIXValue;
import com.paritytrading.philadelphia.FIXValueFormatException;
import java.io.Closeable;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

class Session implements Closeable {

    private static final String UNKNOWN_ORDER_ID = "NONE";

    private static final SoupBinTCP.LoginRequest loginRequest = new SoupBinTCP.LoginRequest();

    private static final POE.EnterOrder enterOrder = new POE.EnterOrder();

    private static final POE.CancelOrder cancelOrder = new POE.CancelOrder();

    private static final FIXMessage txMessage = new FIXMessage(64, 64);

    private static final ByteBuffer txBuffer = ByteBuffer.allocateDirect(POE.MAX_INBOUND_MESSAGE_LENGTH);

    private long nextOrderEntryId;

    private final Orders orders;

    private final FIXConnection fix;

    private final SoupBinTCPClient orderEntry;

    private final Instruments instruments;

    Session(OrderEntryFactory orderEntry, SocketChannel fix,
            FIXConfig config, Instruments instruments) throws IOException {
        this.nextOrderEntryId = 1;

        this.orders = new Orders();

        OrderEntryListener orderEntryListener = new OrderEntryListener();

        this.orderEntry = orderEntry.create(orderEntryListener, orderEntryListener);

        FIXListener fixListener = new FIXListener();

        this.fix = new FIXConnection(fix, config, fixListener, fixListener);

        this.instruments = instruments;
    }

    @Override
    public void close() throws IOException {
        fix.close();
        orderEntry.close();
    }

    FIXConnection getFIX() {
        return fix;
    }

    SoupBinTCPClient getOrderEntry() {
        return orderEntry;
    }

    private void send(POE.InboundMessage message) throws IOException {
        txBuffer.clear();
        message.put(txBuffer);
        txBuffer.flip();

        orderEntry.send(txBuffer);
    }

    private class FIXListener implements FIXMessageListener, FIXConnectionStatusListener {

        @Override
        public void message(FIXMessage message) throws IOException {
            FIXValue msgType = message.getMsgType();

            if (msgType.length() != 1) {
                invalidMsgType(message);
                return;
            }

            switch (msgType.asChar()) {
            case NewOrderSingle:
                newOrderSingle(message);
                break;
            case OrderCancelReplaceRequest:
                orderCancel(message, OrderCancelReplaceRequest);
                break;
            case OrderCancelRequest:
                orderCancel(message, OrderCancelRequest);
                break;
            default:
                invalidMsgType(message);
                break;
            }
        }

        private void newOrderSingle(FIXMessage message) throws IOException {
            FIXValue clOrdIdValue  = null;
            FIXValue accountValue  = null;
            FIXValue sideValue     = null;
            FIXValue symbolValue   = null;
            FIXValue orderQtyValue = null;
            FIXValue priceValue    = null;

            for (int i = 0; i < message.getFieldCount(); i++) {
                switch (message.tagAt(i)) {
                case ClOrdID:
                    clOrdIdValue = message.valueAt(i);
                    break;
                case Account:
                    accountValue = message.valueAt(i);
                    break;
                case Side:
                    sideValue = message.valueAt(i);
                    break;
                case Symbol:
                    symbolValue = message.valueAt(i);
                    break;
                case OrderQty:
                    orderQtyValue = message.valueAt(i);
                    break;
                case Price:
                    priceValue = message.valueAt(i);
                    break;
                }
            }

            if (requiredTagMissing(message, clOrdIdValue, "ClOrdID(11)"))
                return;

            if (requiredTagMissing(message, sideValue, "Side(54)"))
                return;

            if (requiredTagMissing(message, symbolValue, "Symbol(55)"))
                return;

            if (requiredTagMissing(message, orderQtyValue, "OrderQty(38)"))
                return;

            if (requiredTagMissing(message, priceValue, "Price(44)"))
                return;

            long orderEntryId = nextOrderEntryId++;

            ASCII.putLongLeft(enterOrder.orderId, orderEntryId);

            String clOrdId = clOrdIdValue.asString();

            String account = null;

            if (accountValue != null)
                account = accountValue.asString();

            char side = sideValue.asChar();

            switch (side) {
            case SideValues.Buy:
                enterOrder.side = POE.BUY;
                break;
            case SideValues.Sell:
                enterOrder.side = POE.SELL;
                break;
            default:
                valueIsIncorrect(message, "Unknown value in Side(54)");
                return;
            }

            String symbol = symbolValue.asString();

            try {
                enterOrder.instrument = ASCII.packLong(symbol);
            } catch (IllegalArgumentException e) {
                incorrectDataFormatForValue(message, "Expected 'String' in Symbol(55)");
                return;
            }

            double orderQty = 0.0;

            try {
                orderQty = orderQtyValue.asFloat();
            } catch (FIXValueFormatException e) {
                incorrectDataFormatForValue(message, "Expected 'float' in OrderQty(38)");
                return;
            }

            if (orderQty < 0.0) {
                sendOrderRejected(clOrdId, OrdRejReasonValues.IncorrectQuantity, account,
                        symbol, side, orderQty);
                return;
            }

            Instrument config = instruments.get(enterOrder.instrument);
            if (config == null) {
                sendOrderRejected(clOrdId, OrdRejReasonValues.UnknownSymbol, account,
                        symbol, side, orderQty);
                return;
            }

            enterOrder.quantity = (long)(orderQty * config.getSizeFactor());

            double price = 0.0;

            try {
                price = priceValue.asFloat();
            } catch (FIXValueFormatException e) {
                incorrectDataFormatForValue(message, "Expected 'float' in Price(44)");
                return;
            }

            if (price < 0.0) {
                sendOrderRejected(clOrdId, OrdRejReasonValues.BrokerCredit, account,
                        symbol, side, orderQty);
                return;
            }

            Order order = orders.findByClOrdID(clOrdId);
            if (order != null) {
                sendOrderRejected(order, OrdRejReasonValues.DuplicateOrder);
                return;
            }

            enterOrder.price = (long)(price * config.getPriceFactor());

            orders.add(new Order(orderEntryId, clOrdId, account, side, symbol, orderQty));

            send(enterOrder);
        }

        private void orderCancel(FIXMessage message, char msgType) throws IOException {
            FIXValue clOrdIdValue     = null;
            FIXValue origClOrdIdValue = null;
            FIXValue orderQtyValue    = null;

            for (int i = 0; i < message.getFieldCount(); i++) {
                switch (message.tagAt(i)) {
                case ClOrdID:
                    clOrdIdValue = message.valueAt(i);
                    break;
                case OrigClOrdID:
                    origClOrdIdValue = message.valueAt(i);
                    break;
                case OrderQty:
                    orderQtyValue = message.valueAt(i);
                    break;
                }
            }

            if (requiredTagMissing(message, origClOrdIdValue, "OrigClOrdID(41)"))
                return;

            if (requiredTagMissing(message, clOrdIdValue, "ClOrdID(11)"))
                return;

            if (msgType == OrderCancelReplaceRequest) {
                if (requiredTagMissing(message, orderQtyValue, "OrderQty(38)"))
                    return;
            }

            char cxlRejResponseTo = CxlRejResponseToValues.OrderCancelRequest;

            if (msgType == OrderCancelReplaceRequest)
                cxlRejResponseTo = CxlRejResponseToValues.OrderCancel;

            String origClOrdId = origClOrdIdValue.asString();
            String clOrdId     = clOrdIdValue.asString();

            Order order = orders.findByClOrdID(origClOrdId);
            if (order == null) {
                sendOrderCancelReject(clOrdId, origClOrdId, cxlRejResponseTo);

                return;
            } else if (order.isInPendingStatus()) {
                sendOrderCancelReject(order, clOrdId, cxlRejResponseTo,
                        CxlRejReasonValues.OrderAlreadyInPendingStatus);

                return;
            }

            if (orders.findByClOrdID(clOrdId) != null) {
                sendOrderCancelReject(order, clOrdId, cxlRejResponseTo,
                        CxlRejReasonValues.DuplicateClOrdID);
                return;
            }

            double orderQty = 0.0;

            if (msgType == OrderCancelReplaceRequest) {
                try {
                    orderQty = orderQtyValue.asFloat();
                } catch (FIXValueFormatException e) {
                    incorrectDataFormatForValue(message, "Expected 'float' in OrderQty(38)");
                    return;
                }
            }

            double qty = Math.max(orderQty - order.getCumQty(), 0);

            Instrument config = instruments.get(order.getSymbol());

            order.setNextClOrdID(clOrdId);
            order.setCxlRejResponseTo(cxlRejResponseTo);

            ASCII.putLongLeft(cancelOrder.orderId, order.getOrderEntryID());
            cancelOrder.quantity = (long)(qty * config.getSizeFactor());

            send(cancelOrder);

            char execType  = ExecTypeValues.PendingCancel;
            char ordStatus = OrdStatusValues.PendingCancel;

            if (msgType == OrderCancelReplaceRequest) {
                execType  = ExecTypeValues.PendingReplace;
                ordStatus = OrdStatusValues.PendingReplace;
            }

            sendOrderCancelAcknowledgement(order, execType, ordStatus);
        }

        @Override
        public void close(FIXConnection connection, String message) throws IOException {
            orderEntry.close();
        }

        @Override
        public void heartbeatTimeout(FIXConnection connection) throws IOException {
        }

        @Override
        public void logon(FIXConnection connection, FIXMessage message) throws IOException {
            FIXValue username = message.valueOf(Username);
            FIXValue password = message.valueOf(Password);

            if (requiredTagMissing(message, username, "Username(553)"))
                return;

            if (requiredTagMissing(message, password, "Password(554)"))
                return;

            fix.updateCompID(message);

            ASCII.putLeft(loginRequest.username, username.asString());
            ASCII.putLeft(loginRequest.password, password.asString());
            ASCII.putRight(loginRequest.requestedSession, "");
            ASCII.putLongRight(loginRequest.requestedSequenceNumber, 0);

            orderEntry.login(loginRequest);
        }

        @Override
        public void logout(FIXConnection connection, FIXMessage message) throws IOException {
            fix.sendLogout();

            orderEntry.logout();
        }

        @Override
        public void reject(FIXConnection connection, FIXMessage message) {
        }

        @Override
        public void sequenceReset(FIXConnection connection) {
        }

        @Override
        public void tooLowMsgSeqNum(FIXConnection connection, long receivedMsgSeqNum, long expectedMsgSeqNum) {
        }

    }

    private void invalidMsgType(FIXMessage message) throws IOException {
        fix.sendReject(message.getMsgSeqNum(), SessionRejectReasonValues.InvalidMsgType,
                "Invalid MsgType(35)");
    }

    private boolean requiredTagMissing(FIXMessage message, FIXValue value, String tag) throws IOException {
        if (value != null)
            return false;

        fix.sendReject(message.getMsgSeqNum(), SessionRejectReasonValues.RequiredTagMissing, tag + " missing");

        return true;
    }

    private void valueIsIncorrect(FIXMessage message, String text) throws IOException {
        fix.sendReject(message.getMsgSeqNum(), SessionRejectReasonValues.ValueIsIncorrect, text);
    }

    private void incorrectDataFormatForValue(FIXMessage message, String text) throws IOException {
        fix.sendReject(message.getMsgSeqNum(), SessionRejectReasonValues.IncorrectDataFormatForValue, text);
    }

    private void sendOrderCancelReject(String clOrdId, String origClOrdId, char cxlRejResponseTo) throws IOException {
        fix.prepare(txMessage, OrderCancelReject);

        txMessage.addField(OrderID).setString(UNKNOWN_ORDER_ID);
        txMessage.addField(ClOrdID).setString(clOrdId);
        txMessage.addField(OrigClOrdID).setString(origClOrdId);
        txMessage.addField(OrdStatus).setChar(OrdStatusValues.Rejected);
        txMessage.addField(CxlRejResponseTo).setChar(cxlRejResponseTo);
        txMessage.addField(CxlRejReason).setInt(CxlRejReasonValues.UnknownOrder);

        fix.send(txMessage);
    }

    private void sendOrderCancelReject(Order order, String clOrdId, char cxlRejResponseTo,
            int cxlRejReason) throws IOException {
        fix.prepare(txMessage, OrderCancelReject);

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(clOrdId);
        txMessage.addField(OrigClOrdID).setString(order.getClOrdID());
        txMessage.addField(OrdStatus).setChar(OrdStatusValues.Rejected);
        txMessage.addField(CxlRejResponseTo).setChar(cxlRejResponseTo);
        txMessage.addField(CxlRejReason).setInt(cxlRejReason);

        fix.send(txMessage);
    }

    private void sendOrderCancelReject(Order order) throws IOException {
        fix.prepare(txMessage, OrderCancelReject);

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(order.getNextClOrdID());
        txMessage.addField(OrigClOrdID).setString(order.getClOrdID());
        txMessage.addField(OrdStatus).setChar(OrdStatusValues.Filled);
        txMessage.addField(CxlRejResponseTo).setChar(order.getCxlRejResponseTo());
        txMessage.addField(CxlRejReason).setInt(CxlRejReasonValues.TooLateToCancel);

        fix.send(txMessage);
    }

    private void sendOrderAccepted(Order order) throws IOException {
        fix.prepare(txMessage, ExecutionReport);

        String symbol = order.getSymbol();

        Instrument config = instruments.get(symbol);

        int priceFractionDigits = config.getPriceFractionDigits();
        int sizeFractionDigits  = config.getSizeFractionDigits();

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(order.getClOrdID());
        txMessage.addField(ExecID).setString(fix.getCurrentTimestamp());
        txMessage.addField(ExecType).setChar(ExecTypeValues.New);
        txMessage.addField(OrdStatus).setChar(order.getOrdStatus());

        if (order.getAccount() != null)
            txMessage.addField(Account).setString(order.getAccount());

        txMessage.addField(Symbol).setString(symbol);
        txMessage.addField(Side).setChar(order.getSide());
        txMessage.addField(OrderQty).setFloat(order.getOrderQty(), sizeFractionDigits);
        txMessage.addField(LeavesQty).setFloat(order.getLeavesQty(), sizeFractionDigits);
        txMessage.addField(CumQty).setFloat(order.getCumQty(), sizeFractionDigits);
        txMessage.addField(AvgPx).setFloat(order.getAvgPx(), priceFractionDigits);

        fix.send(txMessage);
    }

    private void sendOrderRejected(Order order, int ordRejReason) throws IOException {
        fix.prepare(txMessage, ExecutionReport);

        String symbol = order.getSymbol();

        Instrument config = instruments.get(symbol);

        int priceFractionDigits = config.getPriceFractionDigits();
        int sizeFractionDigits  = config.getSizeFractionDigits();

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(order.getClOrdID());
        txMessage.addField(ExecID).setString(fix.getCurrentTimestamp());
        txMessage.addField(ExecType).setChar(ExecTypeValues.Rejected);
        txMessage.addField(OrdStatus).setChar(OrdStatusValues.Rejected);
        txMessage.addField(OrdRejReason).setInt(ordRejReason);

        if (order.getAccount() != null)
            txMessage.addField(Account).setString(order.getAccount());

        txMessage.addField(Symbol).setString(symbol);
        txMessage.addField(Side).setChar(order.getSide());
        txMessage.addField(OrderQty).setFloat(order.getOrderQty(), sizeFractionDigits);
        txMessage.addField(LeavesQty).setFloat(0.0, sizeFractionDigits);
        txMessage.addField(CumQty).setFloat(order.getCumQty(), sizeFractionDigits);
        txMessage.addField(AvgPx).setFloat(order.getAvgPx(), priceFractionDigits);

        fix.send(txMessage);
    }

    private void sendOrderRejected(String clOrdId, int ordRejReason,
            String account, String symbol, char side, double orderQty) throws IOException {
        fix.prepare(txMessage, ExecutionReport);

        Instrument config = instruments.get(symbol);

        int priceFractionDigits = 0;
        int sizeFractionDigits  = 0;

        if (config != null) {
            priceFractionDigits = config.getPriceFractionDigits();
            sizeFractionDigits  = config.getSizeFractionDigits();
        }

        txMessage.addField(OrderID).setString(UNKNOWN_ORDER_ID);
        txMessage.addField(ClOrdID).setString(clOrdId);
        txMessage.addField(ExecID).setString(fix.getCurrentTimestamp());
        txMessage.addField(ExecType).setChar(ExecTypeValues.Rejected);
        txMessage.addField(OrdStatus).setChar(OrdStatusValues.Rejected);
        txMessage.addField(OrdRejReason).setInt(ordRejReason);

        if (account != null)
            txMessage.addField(Account).setString(account);

        txMessage.addField(Symbol).setString(symbol);
        txMessage.addField(Side).setChar(side);
        txMessage.addField(OrderQty).setFloat(orderQty, sizeFractionDigits);
        txMessage.addField(LeavesQty).setFloat(0.0, sizeFractionDigits);
        txMessage.addField(CumQty).setFloat(0.0, sizeFractionDigits);
        txMessage.addField(AvgPx).setFloat(0.0, priceFractionDigits);

        fix.send(txMessage);
    }

    private void sendOrderExecuted(Order order, double lastQty, double lastPx, Instrument config) throws IOException {
        fix.prepare(txMessage, ExecutionReport);

        int priceFractionDigits = config.getPriceFractionDigits();
        int sizeFractionDigits  = config.getSizeFractionDigits();

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(order.getClOrdID());
        txMessage.addField(ExecID).setString(fix.getCurrentTimestamp());
        txMessage.addField(ExecType).setChar(ExecTypeValues.Trade);
        txMessage.addField(OrdStatus).setChar(order.getOrdStatus());

        if (order.getAccount() != null)
            txMessage.addField(Account).setString(order.getAccount());

        txMessage.addField(Symbol).setString(order.getSymbol());
        txMessage.addField(Side).setChar(order.getSide());
        txMessage.addField(OrderQty).setFloat(order.getOrderQty(), sizeFractionDigits);
        txMessage.addField(LastQty).setFloat(lastQty, sizeFractionDigits);
        txMessage.addField(LastPx).setFloat(lastPx, priceFractionDigits);
        txMessage.addField(LeavesQty).setFloat(order.getLeavesQty(), sizeFractionDigits);
        txMessage.addField(CumQty).setFloat(order.getCumQty(), sizeFractionDigits);
        txMessage.addField(AvgPx).setFloat(order.getAvgPx(), priceFractionDigits);

        fix.send(txMessage);
    }

    private void sendOrderCancelAcknowledgement(Order order, char execType, char ordStatus) throws IOException {
        fix.prepare(txMessage, ExecutionReport);

        String symbol = order.getSymbol();

        Instrument config = instruments.get(symbol);

        int priceFractionDigits = config.getPriceFractionDigits();
        int sizeFractionDigits  = config.getSizeFractionDigits();

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(order.getNextClOrdID());
        txMessage.addField(OrigClOrdID).setString(order.getClOrdID());
        txMessage.addField(ExecID).setString(fix.getCurrentTimestamp());
        txMessage.addField(ExecType).setChar(execType);
        txMessage.addField(OrdStatus).setChar(ordStatus);

        if (order.getAccount() != null)
            txMessage.addField(Account).setString(order.getAccount());

        txMessage.addField(Symbol).setString(symbol);
        txMessage.addField(Side).setChar(order.getSide());
        txMessage.addField(OrderQty).setFloat(order.getOrderQty(), sizeFractionDigits);
        txMessage.addField(LeavesQty).setFloat(order.getLeavesQty(), sizeFractionDigits);
        txMessage.addField(CumQty).setFloat(order.getCumQty(), sizeFractionDigits);
        txMessage.addField(AvgPx).setFloat(order.getAvgPx(), priceFractionDigits);

        fix.send(txMessage);
    }

    private void sendOrderCanceled(Order order, Instrument config) throws IOException {
        fix.prepare(txMessage, ExecutionReport);

        int priceFractionDigits = config.getPriceFractionDigits();
        int sizeFractionDigits  = config.getSizeFractionDigits();

        char execType  = ExecTypeValues.Canceled;
        char ordStatus = OrdStatusValues.Canceled;

        if (order.getLeavesQty() > 0) {
            execType  = ExecTypeValues.Replaced;
            ordStatus = order.getOrdStatus();
        }

        txMessage.addField(OrderID).setInt(order.getOrderID());
        txMessage.addField(ClOrdID).setString(order.getClOrdID());
        txMessage.addField(OrigClOrdID).setString(order.getOrigClOrdID());
        txMessage.addField(ExecID).setString(fix.getCurrentTimestamp());
        txMessage.addField(ExecType).setChar(execType);
        txMessage.addField(OrdStatus).setChar(ordStatus);

        if (order.getAccount() != null)
            txMessage.addField(Account).setString(order.getAccount());

        txMessage.addField(Symbol).setString(order.getSymbol());
        txMessage.addField(Side).setChar(order.getSide());
        txMessage.addField(OrderQty).setFloat(order.getOrderQty(), sizeFractionDigits);
        txMessage.addField(LeavesQty).setFloat(order.getLeavesQty(), sizeFractionDigits);
        txMessage.addField(CumQty).setFloat(order.getCumQty(), sizeFractionDigits);
        txMessage.addField(AvgPx).setFloat(order.getAvgPx(), priceFractionDigits);

        fix.send(txMessage);
    }

    private class OrderEntryListener implements POEClientListener, SoupBinTCPClientStatusListener {

        @Override
        public void orderAccepted(POE.OrderAccepted message) throws IOException {
            long orderEntryId = ASCII.getLong(message.orderId);

            Order order = orders.findByOrderEntryID(orderEntryId);
            if (order == null)
                return;

            order.orderAccepted(message.orderNumber);

            sendOrderAccepted(order);
        }

        @Override
        public void orderRejected(POE.OrderRejected message) throws IOException {
            long orderEntryId = ASCII.getLong(message.orderId);

            Order order = orders.findByOrderEntryID(orderEntryId);
            if (order == null)
                return;

            switch (message.reason) {
            case POE.ORDER_REJECT_REASON_UNKNOWN_INSTRUMENT:
                sendOrderRejected(order, OrdRejReasonValues.UnknownSymbol);
                break;
            case POE.ORDER_REJECT_REASON_INVALID_PRICE:
                sendOrderRejected(order, OrdRejReasonValues.Other);
                break;
            case POE.ORDER_REJECT_REASON_INVALID_QUANTITY:
                sendOrderRejected(order, OrdRejReasonValues.IncorrectQuantity);
                break;
            default:
                sendOrderRejected(order, OrdRejReasonValues.Other);
                break;
            }

            orders.removeByOrderEntryID(orderEntryId);
        }

        @Override
        public void orderExecuted(POE.OrderExecuted message) throws IOException {
            long orderEntryId = ASCII.getLong(message.orderId);

            Order order = orders.findByOrderEntryID(orderEntryId);
            if (order == null)
                return;

            Instrument config = instruments.get(order.getSymbol());

            double lastQty = message.quantity / config.getSizeFactor();
            double lastPx  = message.price    / config.getPriceFactor();

            order.orderExecuted(lastQty, lastPx);

            sendOrderExecuted(order, lastQty, lastPx, config);

            if (order.getLeavesQty() == 0) {
                orders.removeByOrderEntryID(orderEntryId);

                if (order.isInPendingStatus())
                    sendOrderCancelReject(order);
            }
        }

        @Override
        public void orderCanceled(POE.OrderCanceled message) throws IOException {
            long orderEntryId = ASCII.getLong(message.orderId);

            Order order = orders.findByOrderEntryID(orderEntryId);
            if (order == null)
                return;

            Instrument config = instruments.get(order.getSymbol());

            order.orderCanceled(message.canceledQuantity / config.getSizeFactor());

            sendOrderCanceled(order, config);

            if (order.getLeavesQty() == 0)
                orders.removeByOrderEntryID(orderEntryId);
        }

        @Override
        public void heartbeatTimeout(SoupBinTCPClient session) throws IOException {
            fix.sendLogout("Trading system not available");
        }

        @Override
        public void loginAccepted(SoupBinTCPClient session, SoupBinTCP.LoginAccepted payload) throws IOException {
            fix.sendLogon(false);
        }

        @Override
        public void loginRejected(SoupBinTCPClient session, SoupBinTCP.LoginRejected payload) throws IOException {
            switch (payload.rejectReasonCode) {
            case SoupBinTCP.LOGIN_REJECT_CODE_NOT_AUTHORIZED:
                fix.sendLogout("Not authorized");
                break;
            case SoupBinTCP.LOGIN_REJECT_CODE_SESSION_NOT_AVAILABLE:
                fix.sendLogout("Session not available");
                break;
            }
        }

        @Override
        public void endOfSession(SoupBinTCPClient session) throws IOException {
            fix.sendLogout();
        }

    }

}