/****************************************************************************
** Copyright (c) 2001-2014
**
** This file is part of the QuickFIX FIX Engine
**
** This file may be distributed under the terms of the quickfixengine.org
** license as defined by quickfixengine.org and appearing in the file
** LICENSE included in the packaging of this file.
**
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
**
** See http://www.quickfixengine.org/LICENSE for licensing information.
**
** Contact ask@quickfixengine.org if any conditions of this licensing are
** not clear to you.
**
****************************************************************************/

package quickfix.banzai;

import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.HashSet;
import java.util.Map;
import java.util.HashMap;
import quickfix.*;
import quickfix.banzai.Order;
import quickfix.field.*;
import javax.swing.SwingUtilities;

public class BanzaiApplication implements Application {
    private OrderTableModel orderTableModel = null;
    private ExecutionTableModel executionTableModel = null;
    private ObservableOrder observableOrder = new ObservableOrder();
    private ObservableLogon observableLogon = new ObservableLogon();
    static private TwoWayMap sideMap = new TwoWayMap();
    static private TwoWayMap typeMap = new TwoWayMap();
    static private TwoWayMap tifMap = new TwoWayMap();
    static private HashMap execIDs = new HashMap();

    public BanzaiApplication(OrderTableModel orderTableModel,
                             ExecutionTableModel executionTableModel) {
        this.orderTableModel = orderTableModel;
        this.executionTableModel = executionTableModel;
    }

    public void onCreate(SessionID sessionID) {}
    public void onLogon(SessionID sessionID) {
        observableLogon.logon(sessionID);
    }
    public void onLogout(SessionID sessionID) {
        observableLogon.logout(sessionID);
    }

    public void toAdmin(quickfix.Message message,
                        SessionID sessionID) {}

    public void toApp(quickfix.Message message,
                      SessionID sessionID) throws DoNotSend {}

    public void fromAdmin(quickfix.Message message,SessionID sessionID)
    throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, RejectLogon {}

    public void fromApp(quickfix.Message message, SessionID sessionID)
    throws FieldNotFound, IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
        try {
            SwingUtilities.invokeLater(new doFromApp(message, sessionID));
        } catch(Exception e) {}
    }

    public class doFromApp implements Runnable {
        private quickfix.Message message;
        private SessionID sessionID;

        public doFromApp(quickfix.Message message, SessionID sessionID) {
            this.message = message;
            this.sessionID = sessionID;
        }

        public void run() {
            try {
                MsgType msgType = new MsgType();
                if(message.getHeader().getField(msgType).valueEquals("8"))
                    executionReport(message, sessionID);
                if(message.getHeader().getField(msgType).valueEquals("9"))
                    cancelReject(message, sessionID);
            } catch(FieldNotFound e) {
                System.out.println(e);
            }
        }
    }

    private void executionReport(Message message, SessionID sessionID)
    throws FieldNotFound {

        ExecID execID = (ExecID)message.getField(new ExecID());
        if(alreadyProcessed(execID, sessionID))
            return;

        String id = message.getField(new ClOrdID()).getValue();
        Order order = orderTableModel.getOrder(id);
        if(order == null)
            return;

        LastShares lastShares = new LastShares(0);

        try {
            OrderQty orderQty = (OrderQty)message.getField(new OrderQty());
            int diff = order.getQuantity() - (int)orderQty.getValue();
            order.setQuantity((int)orderQty.getValue());
            order.setOpen(order.getOpen() - diff);
        } catch(FieldNotFound e) {}
        try {
            Price price = (Price)message.getField(new Price());
            order.setLimit(price.getValue());
        } catch(FieldNotFound e) {}
        try {
            message.getField(lastShares);
        } catch(FieldNotFound e) {}

        if(lastShares.getValue() > 0) {
            order.setExecuted((int)message.getField(new CumQty()).getValue());
            order.setAvgPx(message.getField(new AvgPx()).getValue());
        }
        OrdStatus ordStatus = (OrdStatus)message.getField(new OrdStatus());

        try {
            order.setOpen((int)message.getField(new LeavesQty()).getValue());
        } catch(FieldNotFound e) {
            // FIX40 doesn't have LeavesQty
            if(ordStatus.valueEquals(OrdStatus.REJECTED) ||
                    ordStatus.valueEquals(OrdStatus.CANCELED))
                order.setOpen(0);
            else if(ordStatus.valueEquals(OrdStatus.NEW)) {
                if(order.isNew()) {
                    order.setOpen(order.getQuantity());
                    order.setNew(false);
                }
            }
            order.setOpen(order.getOpen() - (int)lastShares.getValue());
        }

        if(ordStatus.valueEquals(OrdStatus.REJECTED))
            order.setRejected(true);
        else if(ordStatus.valueEquals(OrdStatus.CANCELED) ||
                ordStatus.valueEquals(OrdStatus.DONE_FOR_DAY))
            order.setCanceled(true);

        try {
            order.setMessage(message.getField(new Text()).getValue());
        } catch(FieldNotFound e) {
            order.setMessage(null);
        }

        orderTableModel.updateOrder(order, id);
        observableOrder.update(order);

        if(lastShares.getValue() > 0) {
            Execution execution = new Execution();
            execution.setExchangeID(sessionID + message.getField(new ExecID()).getValue());
            execution.setSymbol(message.getField(new Symbol()).getValue());
            execution.setQuantity((int)lastShares.getValue());
            execution.setPrice(message.getField(new LastPx()).getValue());
            Side side = (Side)message.getField(new Side());
            execution.setSide(FIXSideToSide(side));
            executionTableModel.addExecution(execution);
        }
    }

    private void cancelReject(Message message, SessionID sessionID)
    throws FieldNotFound {

        String id = message.getField(new ClOrdID()).getValue();
        Order order = orderTableModel.getOrder(id);
        if(order == null)
            return;
        if(order.getOriginalID() != null)
            order = orderTableModel.getOrder(order.getOriginalID());

        try {
            order.setMessage(message.getField(new Text()).getValue());
        } catch(FieldNotFound e) {}
        orderTableModel.updateOrder(order, order.getID());
    }

    private boolean alreadyProcessed(ExecID execID, SessionID sessionID) {
        HashSet set = (HashSet)execIDs.get(sessionID);
        if(set == null) {
            set = new HashSet();
            set.add(execID);
            execIDs.put(sessionID, set);
            return false;
        } else {
            if(set.contains(execID))
                return true;
            set.add(execID);
            return false;
        }
    }

    private void send(quickfix.Message message, SessionID sessionID) {
        try {
            Session.sendToTarget(message, sessionID);
        } catch(SessionNotFound e) {
            System.out.println(e);
        }
    }

    public void send(Order order) throws IllegalArgumentException {
        String beginString = order.getSessionID().getBeginString();
        if(beginString.equals("FIX.4.0"))
            send40(order);
        else if(beginString.equals("FIX.4.1"))
            send41(order);
        else if(beginString.equals("FIX.4.2"))
            send42(order);
        else if(beginString.equals("FIX.4.3"))
            send43(order);
        else if(beginString.equals("FIX.4.4"))
            send44(order);
        else if(beginString.equals("FIXT.1.1"))
            send50(order);
        else
        {
          throw new IllegalArgumentException("Unsupported BeginString '"
              + beginString + "' in order");
        }
        
        return;
    }

    public void send40(Order order) {
        quickfix.fix40.NewOrderSingle newOrderSingle =
            new quickfix.fix40.NewOrderSingle
            (new ClOrdID(order.getID()),
             new HandlInst('1'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             new OrderQty(order.getQuantity()),
             typeToFIXType(order.getType()));

        send(populateOrder(order, newOrderSingle), order.getSessionID());
    }

    public void send41(Order order) {
        quickfix.fix41.NewOrderSingle newOrderSingle =
            new quickfix.fix41.NewOrderSingle
            (new ClOrdID(order.getID()),
             new HandlInst('1'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             typeToFIXType(order.getType()));
        newOrderSingle.set(new OrderQty(order.getQuantity()));

        send(populateOrder(order, newOrderSingle), order.getSessionID());
    }

    public void send42(Order order) {
        quickfix.fix42.NewOrderSingle newOrderSingle =
            new quickfix.fix42.NewOrderSingle
            (new ClOrdID(order.getID()),
             new HandlInst('1'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        newOrderSingle.set(new OrderQty(order.getQuantity()));

        send(populateOrder(order, newOrderSingle), order.getSessionID());
    }

    public void send43(Order order) {
        quickfix.fix43.NewOrderSingle newOrderSingle =
            new quickfix.fix43.NewOrderSingle
            (new ClOrdID(order.getID()),
             new HandlInst('1'),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        newOrderSingle.set(new OrderQty(order.getQuantity()));
        newOrderSingle.set(new Symbol(order.getSymbol()));

        send(populateOrder(order, newOrderSingle), order.getSessionID());
    }

    public void send44(Order order) {
        quickfix.fix44.NewOrderSingle newOrderSingle =
            new quickfix.fix44.NewOrderSingle
            (new ClOrdID(order.getID()),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        newOrderSingle.set(new OrderQty(order.getQuantity()));
        newOrderSingle.set(new HandlInst('1'));
        newOrderSingle.set(new Symbol(order.getSymbol()));

        send(populateOrder(order, newOrderSingle), order.getSessionID());
    }   
    
    public void send50(Order order) {
        quickfix.fix50.NewOrderSingle newOrderSingle =
            new quickfix.fix50.NewOrderSingle
            (new ClOrdID(order.getID()),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        newOrderSingle.set(new OrderQty(order.getQuantity()));
        newOrderSingle.set(new HandlInst('1'));
        newOrderSingle.set(new Symbol(order.getSymbol()));

        send(populateOrder(order, newOrderSingle), order.getSessionID());
    }   

    public quickfix.Message populateOrder
    (Order order, quickfix.Message newOrderSingle) {

        OrderType type = order.getType();

        if(type == OrderType.LIMIT)
            newOrderSingle.setField(new Price(order.getLimit().doubleValue()));
        else if(type == OrderType.STOP)
            newOrderSingle.setField(new StopPx(order.getStop().doubleValue()));
        else if(type == OrderType.STOP_LIMIT) {
            newOrderSingle.setField(new Price(order.getLimit().doubleValue()));
            newOrderSingle.setField(new StopPx(order.getStop().doubleValue()));
        }

        newOrderSingle.setField(tifToFIXTif(order.getTIF()))
        ;
        return newOrderSingle;
    }

    public void cancel(Order order) {
        String beginString = order.getSessionID().getBeginString();
        if(beginString.equals("FIX.4.0"))
            cancel40(order);
        else if(beginString.equals("FIX.4.1"))
            cancel41(order);
        else if(beginString.equals("FIX.4.2"))
            cancel42(order);
        else if(beginString.equals("FIX.4.3"))
            cancel42(order);
        else if(beginString.equals("FIX.4.4"))
            cancel42(order);
        else if(beginString.equals("FIXT.1.1"))
            cancel42(order);
        return;
    }

    public void cancel40(Order order) {
        String id = order.generateID();
        quickfix.fix40.OrderCancelRequest message =
            new quickfix.fix40.OrderCancelRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(id),
             new CxlType('F'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             new OrderQty(order.getQuantity()));

        orderTableModel.addID(order, id);
        send(message, order.getSessionID());
    }

    public void cancel41(Order order) {
        String id = order.generateID();
        quickfix.fix41.OrderCancelRequest message =
            new quickfix.fix41.OrderCancelRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(id),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()));
        message.setField(new OrderQty(order.getQuantity()));

        orderTableModel.addID(order, id);
        send(message, order.getSessionID());
    }

    public void cancel42(Order order) {
        String id = order.generateID();
        quickfix.fix42.OrderCancelRequest message =
            new quickfix.fix42.OrderCancelRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(id),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             new TransactTime());
        message.setField(new OrderQty(order.getQuantity()));

        orderTableModel.addID(order, id);
        send(message, order.getSessionID());
    }
     
    public void cancel43(Order order) {
        String id = order.generateID();
        quickfix.fix43.OrderCancelRequest message =
            new quickfix.fix43.OrderCancelRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(id),
             sideToFIXSide(order.getSide()),
             new TransactTime());
        message.setField(new OrderQty(order.getQuantity()));
        message.setField(new Symbol(order.getSymbol()));

        orderTableModel.addID(order, id);
        send(message, order.getSessionID());
    }   
    
    public void cancel44(Order order) {
        String id = order.generateID();
        quickfix.fix44.OrderCancelRequest message =
            new quickfix.fix44.OrderCancelRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(id),
             sideToFIXSide(order.getSide()),
             new TransactTime());
        message.setField(new OrderQty(order.getQuantity()));
        message.setField(new Symbol(order.getSymbol()));

        orderTableModel.addID(order, id);
        send(message, order.getSessionID());
    }

    public void cancel50(Order order) {
        String id = order.generateID();
        quickfix.fix50.OrderCancelRequest message =
            new quickfix.fix50.OrderCancelRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(id),
             sideToFIXSide(order.getSide()),
             new TransactTime());
        message.setField(new OrderQty(order.getQuantity()));
        message.setField(new Symbol(order.getSymbol()));

        orderTableModel.addID(order, id);
        send(message, order.getSessionID());
    }
    
    public void replace(Order order, Order newOrder) {
        String beginString = order.getSessionID().getBeginString();
        if(beginString.equals("FIX.4.0"))
            replace40(order, newOrder);
        else if(beginString.equals("FIX.4.1"))
            replace41(order, newOrder);
        else if(beginString.equals("FIX.4.2"))
            replace42(order, newOrder);
        else if(beginString.equals("FIX.4.3"))
            replace42(order, newOrder);
        else if(beginString.equals("FIX.4.4"))
            replace42(order, newOrder);
        else if(beginString.equals("FIXT.1.1"))
            replace42(order, newOrder);
        return;
    }

    public void replace40(Order order, Order newOrder) {
        quickfix.fix40.OrderCancelReplaceRequest message =
            new quickfix.fix40.OrderCancelReplaceRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(newOrder.getID()),
             new HandlInst('1'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             new OrderQty(newOrder.getQuantity()),
             typeToFIXType(order.getType()));

        orderTableModel.addID(order, newOrder.getID());
        send(populateCancelReplace(order, newOrder,
                                   message), order.getSessionID());
    }

    public void replace41(Order order, Order newOrder) {
        quickfix.fix41.OrderCancelReplaceRequest message =
            new quickfix.fix41.OrderCancelReplaceRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(newOrder.getID()),
             new HandlInst('1'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             typeToFIXType(order.getType()));

        orderTableModel.addID(order, newOrder.getID());
        send(populateCancelReplace(order, newOrder,
                                   message), order.getSessionID());
    }

    public void replace42(Order order, Order newOrder) {
        quickfix.fix42.OrderCancelReplaceRequest message =
            new quickfix.fix42.OrderCancelReplaceRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(newOrder.getID()),
             new HandlInst('1'),
             new Symbol(order.getSymbol()),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));

        orderTableModel.addID(order, newOrder.getID());
        send(populateCancelReplace(order, newOrder,
                                   message), order.getSessionID());
    }

    public void replace43(Order order, Order newOrder) {
        quickfix.fix43.OrderCancelReplaceRequest message =
            new quickfix.fix43.OrderCancelReplaceRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(newOrder.getID()),
             new HandlInst('1'),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        message.setField(new Symbol(order.getSymbol()));

        orderTableModel.addID(order, newOrder.getID());
        send(populateCancelReplace(order, newOrder,
                                   message), order.getSessionID());
    }
    
    public void replace44(Order order, Order newOrder) {
        quickfix.fix44.OrderCancelReplaceRequest message =
            new quickfix.fix44.OrderCancelReplaceRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(newOrder.getID()),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        message.setField(new Symbol(order.getSymbol()));
        message.setField(new HandlInst('1'));

        orderTableModel.addID(order, newOrder.getID());
        send(populateCancelReplace(order, newOrder,
                                   message), order.getSessionID());
    }

    public void replace50(Order order, Order newOrder) {
        quickfix.fix50.OrderCancelReplaceRequest message =
            new quickfix.fix50.OrderCancelReplaceRequest
            (new OrigClOrdID(order.getID()),
             new ClOrdID(newOrder.getID()),
             sideToFIXSide(order.getSide()),
             new TransactTime(),
             typeToFIXType(order.getType()));
        message.setField(new Symbol(order.getSymbol()));
        message.setField(new HandlInst('1'));

        orderTableModel.addID(order, newOrder.getID());
        send(populateCancelReplace(order, newOrder,
                                   message), order.getSessionID());
    }
    
    Message populateCancelReplace(Order order, Order newOrder,
                                  quickfix.Message message) {

        if(order.getQuantity() != newOrder.getQuantity())
            message.setField(new OrderQty(newOrder.getQuantity()));
        if(order.getLimit() != newOrder.getLimit())
            message.setField(new Price(newOrder.getLimit().doubleValue()));
        return message;
    }

    public  Side sideToFIXSide(OrderSide side) {
        return (Side)sideMap.getFirst(side);
    }
    public  OrderSide FIXSideToSide(Side side) {
        return (OrderSide)sideMap.getSecond(side);
    }

    public  OrdType typeToFIXType(OrderType type) {
        return (OrdType)typeMap.getFirst(type);
    }
    public  OrderType FIXTypeToType(OrdType type) {
        return (OrderType)typeMap.getSecond(type);
    }

    public  TimeInForce tifToFIXTif(OrderTIF tif) {
        return (TimeInForce)tifMap.getFirst(tif);
    }
    public  OrderTIF FIXTifToTif(TimeInForce tif) {
        return (OrderTIF)tifMap.getSecond(tif);
    }

    public void addLogonObserver(Observer observer) {
        observableLogon.addObserver(observer);
    }
    public void deleteLogonObserver(Observer observer) {
        observableLogon.deleteObserver(observer);
    }
    public void addOrderObserver(Observer observer) {
        observableOrder.addObserver(observer);
    }
    public void deleteOrderObserver(Observer observer) {
        observableOrder.deleteObserver(observer);
    }

    public class ObservableOrder extends Observable {
        public void update(Order order) {
            setChanged();
            notifyObservers(order);
            clearChanged();
        }
    }

    public class ObservableLogon extends Observable {
        private HashSet set = new HashSet();

        public void logon(SessionID sessionID) {
            set.add(sessionID);
            setChanged();
            notifyObservers(new LogonEvent(sessionID, true));
            clearChanged();
        }
        public void logout(SessionID sessionID) {
            set.remove(sessionID);
            setChanged();
            notifyObservers(new LogonEvent(sessionID, false));
            clearChanged();
        }
    }

    static {
        sideMap.put(OrderSide.BUY, new Side(Side.BUY));
        sideMap.put(OrderSide.SELL, new Side(Side.SELL));
        sideMap.put(OrderSide.SHORT_SELL, new Side(Side.SELL_SHORT));
        sideMap.put(OrderSide.SHORT_SELL_EXEMPT,
                    new Side(Side.SELL_SHORT_EXEMPT));
        sideMap.put(OrderSide.CROSS, new Side(Side.CROSS));
        sideMap.put(OrderSide.CROSS_SHORT, new Side(Side.CROSS_SHORT));

        typeMap.put(OrderType.MARKET, new OrdType(OrdType.MARKET));
        typeMap.put(OrderType.LIMIT, new OrdType(OrdType.LIMIT));
        typeMap.put(OrderType.STOP, new OrdType(OrdType.STOP));
        typeMap.put(OrderType.STOP_LIMIT, new OrdType(OrdType.STOP_LIMIT));

        tifMap.put(OrderTIF.DAY, new TimeInForce(TimeInForce.DAY));
        tifMap.put(OrderTIF.IOC,
                   new TimeInForce(TimeInForce.IMMEDIATE_OR_CANCEL));
        tifMap.put(OrderTIF.OPG, new TimeInForce(TimeInForce.AT_THE_OPENING));
        tifMap.put(OrderTIF.GTC,
                   new TimeInForce(TimeInForce.GOOD_TILL_CANCEL));
        tifMap.put(OrderTIF.GTX,
                   new TimeInForce(TimeInForce.GOOD_TILL_CROSSING));

    }
}
