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

type routeKey struct {
	FIXVersion string
	MsgType    string
}

// FIX ApplVerID string values.
const (
	ApplVerIDFIX27    = "0"
	ApplVerIDFIX30    = "1"
	ApplVerIDFIX40    = "2"
	ApplVerIDFIX41    = "3"
	ApplVerIDFIX42    = "4"
	ApplVerIDFIX43    = "5"
	ApplVerIDFIX44    = "6"
	ApplVerIDFIX50    = "7"
	ApplVerIDFIX50SP1 = "8"
	ApplVerIDFIX50SP2 = "9"
)

// A MessageRoute is a function that can process a fromApp/fromAdmin callback.
type MessageRoute func(msg *Message, sessionID SessionID) MessageRejectError

// A MessageRouter is a mutex for MessageRoutes.
type MessageRouter struct {
	routes map[routeKey]MessageRoute
}

// NewMessageRouter returns an initialized MessageRouter instance.
func NewMessageRouter() *MessageRouter {
	return &MessageRouter{routes: make(map[routeKey]MessageRoute)}
}

// AddRoute adds a route to the MessageRouter instance keyed to begin string and msgType.
func (c MessageRouter) AddRoute(beginString string, msgType string, router MessageRoute) {
	c.routes[routeKey{beginString, msgType}] = router
}

// Route may be called from the fromApp/fromAdmin callbacks. Messages that cannot be routed will be rejected with UnsupportedMessageType.
func (c MessageRouter) Route(msg *Message, sessionID SessionID) MessageRejectError {
	beginString, err := msg.Header.GetBytes(tagBeginString)
	if err != nil {
		return nil
	}

	msgType, err := msg.Header.GetBytes(tagMsgType)
	if err != nil {
		return err
	}

	return c.tryRoute(string(beginString), string(msgType), msg, sessionID)
}

func (c MessageRouter) tryRoute(beginString string, msgType string, msg *Message, sessionID SessionID) MessageRejectError {
	fixVersion := beginString
	isAdminMsg := isAdminMessageType([]byte(msgType))

	if beginString == BeginStringFIXT11 && !isAdminMsg {
		var applVerID FIXString
		if err := msg.Header.GetField(tagApplVerID, &applVerID); err != nil {
			session, _ := lookupSession(sessionID)
			applVerID = FIXString(session.TargetDefaultApplicationVersionID())
		}

		switch applVerID {
		case ApplVerIDFIX40:
			fixVersion = BeginStringFIX40
		case ApplVerIDFIX41:
			fixVersion = BeginStringFIX41
		case ApplVerIDFIX42:
			fixVersion = BeginStringFIX42
		case ApplVerIDFIX43:
			fixVersion = BeginStringFIX43
		case ApplVerIDFIX44:
			fixVersion = BeginStringFIX44
		default:
			fixVersion = string(applVerID)
		}
	}

	if route, ok := c.routes[routeKey{fixVersion, msgType}]; ok {
		return route(msg, sessionID)
	}

	switch {
	case isAdminMsg || msgType == "j":
		return nil
	}

	return UnsupportedMessageType()
}
