/*******************************************************************************
 * Copyright (c) quickfixengine.org  All rights reserved. 
 * 
 * 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;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import quickfix.field.BeginString;

/**
 * Helper class for delegating message types for various FIX versions to type-safe onMessage methods.
 */
public class MessageCracker {

	private static final Logger log = LoggerFactory.getLogger(MessageCracker.class);

	private static Map<String, CrackMethod> crackMethods = new HashMap<String, CrackMethod>();

	/**
	 * Process ("crack") a FIX message and call the type-safe onMessage method for that message type and FIX version.
	 */
	public void crack(quickfix.Message message, SessionID sessionID) throws UnsupportedMessageType, FieldNotFound, IncorrectTagValue {

		BeginString beginString = new BeginString();
		message.getHeader().getField(beginString);
		String version = beginString.getValue();
		String crackerClassname = null;

		if (version.equals("FIX.4.0"))
			crackerClassname = "quickfix.fix40.MessageCracker";

		else if (version.equals("FIX.4.1"))
			crackerClassname = "quickfix.fix41.MessageCracker";

		else if (version.equals("FIX.4.2"))
			crackerClassname = "quickfix.fix42.MessageCracker";

		else if (version.equals("FIX.4.3"))
			crackerClassname = "quickfix.fix43.MessageCracker";

		else if (version.equals("FIX.4.4"))
			crackerClassname = "quickfix.fix44.MessageCracker";
		else
			throw new IncorrectTagValue("Unsupported FIX version : " + version);

		CrackMethod crackMethod;
		synchronized (crackMethods) {
			crackMethod = crackMethods.get(crackerClassname);
			if (crackMethod == null) {
				try {
					Class<?> crackerClass = Class.forName(crackerClassname);
					crackMethod = new CrackMethod(crackerClass);
					crackMethods.put(crackerClassname, crackMethod);
				} catch (Exception e) {
					log.error("could not load message cracker " + crackerClassname, e);
					throw new IncorrectTagValue("Could not load message cracker for FIX version : " + version);
				}
			}
		}
		crackMethod.call(this, message, sessionID);
	}

	private class CrackMethod {
		MessageCracker cracker;
		Method method;

		public CrackMethod(Class<?> crackerClass) throws Exception {
			log.debug("creating new instance of " + crackerClass);
			this.cracker = (MessageCracker) crackerClass.newInstance();
			method = crackerClass.getMethod("crack", new Class[] {MessageCracker.class, Message.class, SessionID.class});
		}

		public void call(MessageCracker instance, quickfix.Message message, SessionID sessionID) throws UnsupportedMessageType, FieldNotFound,
				IncorrectTagValue {
			try {
				method.invoke(cracker, new Object[] {instance, message, sessionID});
			} catch (InvocationTargetException e) {
				try {
					throw e.getCause();
				} catch (UnsupportedMessageType f) {
					throw f;
				} catch (FieldNotFound f) {
					throw f;
				} catch (IncorrectTagValue f) {
					throw f;
				} catch (Throwable f) {
					throw new RuntimeException("Message cracker method failed", f);
				}
			} catch (Exception e) {
				throw new RuntimeException("Generic message cracker invocation failed", e);
			}
		}
	}

	protected void crack(MessageCracker instance, quickfix.Message message, SessionID sessionID) throws UnsupportedMessageType, FieldNotFound, IncorrectTagValue {
		throw new RuntimeException("Method crack(MessageCracker, quickfix.Message, SessionID) must be overriden by MessageCracker");
	}

	protected void onMessage(MessageCracker instance, Class<? extends quickfix.Message> messageClass, quickfix.Message message, SessionID sessionID)
			throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {
		try {
			log.debug("calling method onMessage( " + messageClass + ", SessionID)");			
			Method method = instance.getClass().getMethod("onMessage", new Class[] {messageClass, SessionID.class});
			method.invoke(instance, new Object[] {message, sessionID});

		} catch (NoSuchMethodException e) {
			onMessage(message, sessionID);

		} catch (InvocationTargetException e) {
			try {
				throw e.getCause();
			} catch (UnsupportedMessageType f) {
				throw f;
			} catch (FieldNotFound f) {
				throw f;
			} catch (IncorrectTagValue f) {
				throw f;
			} catch (Throwable f) {
				throw new RuntimeException("Message cracker method failed", f);
			}
		} catch (Exception e) {
			throw new RuntimeException("Generic message cracker invocation failed", e);
		}
	}

	/**
	 * onMessage catch-all method
	 * basic protocol messages are ignored 
	 * any other unhandled messages that should have been overloaded throw UnsupportedMessageType
	 * 
	 * @param message
	 * @param sessionID
	 * @throws FieldNotFound
	 * @throws UnsupportedMessageType
	 * @throws IncorrectTagValue
	 */
	protected void onMessage(quickfix.Message message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {		
		String msgType = message.getHeader().getField(35).getValue();
		if (msgType.equals("0") || // heartbeat
			msgType.equals("A") || // logon
			msgType.equals("1") || // testrequest
			msgType.equals("2") || // resendrequest
			msgType.equals("3") || // reject
			msgType.equals("4") || // sequencereset
			msgType.equals("5") || // logout
			msgType.equals("j"))   // businessreject
			return;
		else
			throw new UnsupportedMessageType();
	}
}

