// 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

import (
	"errors"
	"fmt"
)

// ErrDoNotSend is a convenience error to indicate a DoNotSend in ToApp.
var ErrDoNotSend = errors.New("Do Not Send")

// rejectReason enum values.
const (
	rejectReasonInvalidTagNumber                          = 0
	rejectReasonRequiredTagMissing                        = 1
	rejectReasonTagNotDefinedForThisMessageType           = 2
	rejectReasonUnsupportedMessageType                    = 3
	rejectReasonTagSpecifiedWithoutAValue                 = 4
	rejectReasonValueIsIncorrect                          = 5
	rejectReasonConditionallyRequiredFieldMissing         = 5
	rejectReasonIncorrectDataFormatForValue               = 6
	rejectReasonCompIDProblem                             = 9
	rejectReasonSendingTimeAccuracyProblem                = 10
	rejectReasonInvalidMsgType                            = 11
	rejectReasonTagAppearsMoreThanOnce                    = 13
	rejectReasonTagSpecifiedOutOfRequiredOrder            = 14
	rejectReasonRepeatingGroupFieldsOutOfOrder            = 15
	rejectReasonIncorrectNumInGroupCountForRepeatingGroup = 16
)

// MessageRejectError is a type of error that can correlate to a message reject.
type MessageRejectError interface {
	error

	// RejectReason, tag 373 for session rejects, tag 380 for business rejects.
	RejectReason() int
	BusinessRejectRefID() string
	RefTagID() *Tag
	IsBusinessReject() bool
}

// RejectLogon indicates the application is rejecting permission to logon. Implements MessageRejectError.
type RejectLogon struct {
	Text string
}

func (e RejectLogon) Error() string { return e.Text }

// RefTagID implements MessageRejectError.
func (RejectLogon) RefTagID() *Tag { return nil }

// RejectReason implements MessageRejectError.
func (RejectLogon) RejectReason() int { return 0 }

// BusinessRejectRefID implements MessageRejectError.
func (RejectLogon) BusinessRejectRefID() string { return "" }

// IsBusinessReject implements MessageRejectError.
func (RejectLogon) IsBusinessReject() bool { return false }

type messageRejectError struct {
	rejectReason        int
	text                string
	businessRejectRefID string
	refTagID            *Tag
	isBusinessReject    bool
}

func (e messageRejectError) Error() string               { return e.text }
func (e messageRejectError) RefTagID() *Tag              { return e.refTagID }
func (e messageRejectError) RejectReason() int           { return e.rejectReason }
func (e messageRejectError) BusinessRejectRefID() string { return e.businessRejectRefID }
func (e messageRejectError) IsBusinessReject() bool      { return e.isBusinessReject }

// NewMessageRejectError returns a MessageRejectError with the given error message, reject reason, and optional reftagid.
func NewMessageRejectError(err string, rejectReason int, refTagID *Tag) MessageRejectError {
	return messageRejectError{text: err, rejectReason: rejectReason, refTagID: refTagID}
}

// NewBusinessMessageRejectError returns a MessageRejectError with the given error mesage, reject reason, and optional reftagid.
// Reject is treated as a business level reject.
func NewBusinessMessageRejectError(err string, rejectReason int, refTagID *Tag) MessageRejectError {
	return messageRejectError{text: err, rejectReason: rejectReason, refTagID: refTagID, isBusinessReject: true}
}

// NewBusinessMessageRejectErrorWithRefID returns a MessageRejectError with the given error mesage, reject reason, refID, and optional reftagid.
// Reject is treated as a business level reject.
func NewBusinessMessageRejectErrorWithRefID(err string, rejectReason int, businessRejectRefID string, refTagID *Tag) MessageRejectError {
	return messageRejectError{text: err, rejectReason: rejectReason, refTagID: refTagID, businessRejectRefID: businessRejectRefID, isBusinessReject: true}
}

// IncorrectDataFormatForValue returns an error indicating a field that cannot be parsed as the type required.
func IncorrectDataFormatForValue(tag Tag) MessageRejectError {
	return NewMessageRejectError("Incorrect data format for value", rejectReasonIncorrectDataFormatForValue, &tag)
}

// repeatingGroupFieldsOutOfOrder returns an error indicating a problem parsing repeating groups fields.
func repeatingGroupFieldsOutOfOrder(tag Tag, reason string) MessageRejectError {
	if reason != "" {
		reason = fmt.Sprintf("Repeating group fields out of order (%s)", reason)
	} else {
		reason = "Repeating group fields out of order"
	}
	return NewMessageRejectError(reason, rejectReasonRepeatingGroupFieldsOutOfOrder, &tag)
}

// ValueIsIncorrect returns an error indicating a field with value that is not valid.
func ValueIsIncorrect(tag Tag) MessageRejectError {
	return NewMessageRejectError("Value is incorrect (out of range) for this tag", rejectReasonValueIsIncorrect, &tag)
}

// ConditionallyRequiredFieldMissing indicates that the requested field could not be found in the FIX message.
func ConditionallyRequiredFieldMissing(tag Tag) MessageRejectError {
	return NewBusinessMessageRejectError(fmt.Sprintf("Conditionally Required Field Missing (%d)", tag), rejectReasonConditionallyRequiredFieldMissing, &tag)
}

// valueIsIncorrectNoTag returns an error indicating a field with value that is not valid.
// FIXME: to be compliant with legacy tests, for certain value issues, do not include reftag? (11c_NewSeqNoLess).
func valueIsIncorrectNoTag() MessageRejectError {
	return NewMessageRejectError("Value is incorrect (out of range) for this tag", rejectReasonValueIsIncorrect, nil)
}

// InvalidMessageType returns an error to indicate an invalid message type.
func InvalidMessageType() MessageRejectError {
	return NewMessageRejectError("Invalid MsgType", rejectReasonInvalidMsgType, nil)
}

// UnsupportedMessageType returns an error to indicate an unhandled message.
func UnsupportedMessageType() MessageRejectError {
	return NewBusinessMessageRejectError("Unsupported Message Type", rejectReasonUnsupportedMessageType, nil)
}

// TagNotDefinedForThisMessageType returns an error for an invalid tag appearing in a message.
func TagNotDefinedForThisMessageType(tag Tag) MessageRejectError {
	return NewMessageRejectError("Tag not defined for this message type", rejectReasonTagNotDefinedForThisMessageType, &tag)
}

// tagAppearsMoreThanOnce return an error for multiple tags in a message not detected as a repeating group.
func tagAppearsMoreThanOnce(tag Tag) MessageRejectError {
	return NewMessageRejectError("Tag appears more than once", rejectReasonTagAppearsMoreThanOnce, &tag)
}

// RequiredTagMissing returns a validation error when a required field cannot be found in a message.
func RequiredTagMissing(tag Tag) MessageRejectError {
	return NewMessageRejectError("Required tag missing", rejectReasonRequiredTagMissing, &tag)
}

// incorrectNumInGroupCountForRepeatingGroup returns a validation error when the num in group value for a group does not match actual group size.
func incorrectNumInGroupCountForRepeatingGroup(tag Tag) MessageRejectError {
	return NewMessageRejectError("Incorrect NumInGroup count for repeating group", rejectReasonIncorrectNumInGroupCountForRepeatingGroup, &tag)
}

// tagSpecifiedOutOfRequiredOrder returns validation error when the group order does not match the spec.
func tagSpecifiedOutOfRequiredOrder(tag Tag) MessageRejectError {
	return NewMessageRejectError("Tag specified out of required order", rejectReasonTagSpecifiedOutOfRequiredOrder, &tag)
}

// TagSpecifiedWithoutAValue returns a validation error for when a field has no value.
func TagSpecifiedWithoutAValue(tag Tag) MessageRejectError {
	return NewMessageRejectError("Tag specified without a value", rejectReasonTagSpecifiedWithoutAValue, &tag)
}

// InvalidTagNumber returns a validation error for messages with invalid tags.
func InvalidTagNumber(tag Tag) MessageRejectError {
	return NewMessageRejectError("Invalid tag number", rejectReasonInvalidTagNumber, &tag)
}

// compIDProblem creates a reject for msg where msg has invalid comp id values.
func compIDProblem() MessageRejectError {
	return NewMessageRejectError("CompID problem", rejectReasonCompIDProblem, nil)
}

// sendingTimeAccuracyProblem creates a reject for a msg with stale or invalid sending time.
func sendingTimeAccuracyProblem() MessageRejectError {
	return NewMessageRejectError("SendingTime accuracy problem", rejectReasonSendingTimeAccuracyProblem, nil)
}
