// 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 (
	"bytes"
	"sort"
	"sync"
	"time"
)

// field stores a slice of TagValues.
type field []TagValue

func fieldTag(f field) Tag {
	return f[0].tag
}

func initField(f field, tag Tag, value []byte) {
	f[0].init(tag, value)
}

func writeField(f field, buffer *bytes.Buffer) {
	for _, tv := range f {
		buffer.Write(tv.bytes)
	}
}

// tagOrder true if tag i should occur before tag j.
type tagOrder func(i, j Tag) bool

type tagSort struct {
	tags    []Tag
	compare tagOrder
}

func (t tagSort) Len() int           { return len(t.tags) }
func (t tagSort) Swap(i, j int)      { t.tags[i], t.tags[j] = t.tags[j], t.tags[i] }
func (t tagSort) Less(i, j int) bool { return t.compare(t.tags[i], t.tags[j]) }

// FieldMap is a collection of fix fields that make up a fix message.
type FieldMap struct {
	tagLookup map[Tag]field
	tagSort
	rwLock *sync.RWMutex
}

// ascending tags.
func normalFieldOrder(i, j Tag) bool { return i < j }

func (m *FieldMap) init() {
	m.initWithOrdering(normalFieldOrder)
}

func (m *FieldMap) initWithOrdering(ordering tagOrder) {
	m.rwLock = &sync.RWMutex{}
	m.tagLookup = make(map[Tag]field)
	m.compare = ordering
}

// Tags returns all of the Field Tags in this FieldMap.
func (m FieldMap) Tags() []Tag {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	tags := make([]Tag, 0, len(m.tagLookup))
	for t := range m.tagLookup {
		tags = append(tags, t)
	}

	return tags
}

// Get parses out a field in this FieldMap. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) Get(parser Field) MessageRejectError {
	return m.GetField(parser.Tag(), parser)
}

// RawValues returns a copy of the underlying TagValue slice for a tag.
// For repeating groups, the first entry is the count and the remaining
// entries are the flattened inner-field values across all repetitions.
func (m FieldMap) RawValues(tag Tag) []TagValue {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	f, ok := m.tagLookup[tag]
	if !ok {
		return nil
	}

	out := make([]TagValue, len(f))
	copy(out, f)

	return out
}

// Has returns true if the Tag is present in this FieldMap.
func (m FieldMap) Has(tag Tag) bool {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	_, ok := m.tagLookup[tag]
	return ok
}

// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) GetField(tag Tag, parser FieldValueReader) MessageRejectError {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	f, ok := m.tagLookup[tag]
	if !ok {
		return ConditionallyRequiredFieldMissing(tag)
	}

	if err := parser.Read(f[0].value); err != nil {
		return IncorrectDataFormatForValue(tag)
	}

	return nil
}

// GetField parses of a field with Tag tag. Returned reject may indicate the field is not present, or the field value is invalid.
func (m FieldMap) getFieldNoLock(tag Tag, parser FieldValueReader) MessageRejectError {
	f, ok := m.tagLookup[tag]
	if !ok {
		return ConditionallyRequiredFieldMissing(tag)
	}

	if err := parser.Read(f[0].value); err != nil {
		return IncorrectDataFormatForValue(tag)
	}

	return nil
}

// GetBytes is a zero-copy GetField wrapper for []bytes fields.
func (m FieldMap) GetBytes(tag Tag) ([]byte, MessageRejectError) {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	f, ok := m.tagLookup[tag]
	if !ok {
		return nil, ConditionallyRequiredFieldMissing(tag)
	}

	return f[0].value, nil
}

// getBytesNoLock is a lock free zero-copy GetField wrapper for []bytes fields.
func (m FieldMap) getBytesNoLock(tag Tag) ([]byte, MessageRejectError) {
	f, ok := m.tagLookup[tag]
	if !ok {
		return nil, ConditionallyRequiredFieldMissing(tag)
	}

	return f[0].value, nil
}

// GetBool is a GetField wrapper for bool fields.
func (m FieldMap) GetBool(tag Tag) (bool, MessageRejectError) {
	var val FIXBoolean
	if err := m.GetField(tag, &val); err != nil {
		return false, err
	}
	return bool(val), nil
}

// GetInt is a GetField wrapper for int fields.
func (m FieldMap) GetInt(tag Tag) (int, MessageRejectError) {
	bytes, err := m.GetBytes(tag)
	if err != nil {
		return 0, err
	}

	var val FIXInt
	if val.Read(bytes) != nil {
		err = IncorrectDataFormatForValue(tag)
	}

	return int(val), err
}

// GetInt is a lock free GetField wrapper for int fields.
func (m FieldMap) getIntNoLock(tag Tag) (int, MessageRejectError) {
	bytes, err := m.getBytesNoLock(tag)
	if err != nil {
		return 0, err
	}

	var val FIXInt
	if val.Read(bytes) != nil {
		err = IncorrectDataFormatForValue(tag)
	}

	return int(val), err
}

// GetTime is a GetField wrapper for utc timestamp fields.
func (m FieldMap) GetTime(tag Tag) (t time.Time, err MessageRejectError) {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	bytes, err := m.GetBytes(tag)
	if err != nil {
		return
	}

	var val FIXUTCTimestamp
	if val.Read(bytes) != nil {
		err = IncorrectDataFormatForValue(tag)
	}

	return val.Time, err
}

// GetString is a GetField wrapper for string fields.
func (m FieldMap) GetString(tag Tag) (string, MessageRejectError) {
	var val FIXString
	if err := m.GetField(tag, &val); err != nil {
		return "", err
	}
	return string(val), nil
}

// GetString is a GetField wrapper for string fields.
func (m FieldMap) getStringNoLock(tag Tag) (string, MessageRejectError) {
	var val FIXString
	if err := m.getFieldNoLock(tag, &val); err != nil {
		return "", err
	}
	return string(val), nil
}

// GetGroup is a Get function specific to Group Fields.
func (m FieldMap) GetGroup(parser FieldGroupReader) MessageRejectError {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	f, ok := m.tagLookup[parser.Tag()]
	if !ok {
		return ConditionallyRequiredFieldMissing(parser.Tag())
	}

	if _, err := parser.Read(f); err != nil {
		if msgRejErr, ok := err.(MessageRejectError); ok {
			return msgRejErr
		}
		return IncorrectDataFormatForValue(parser.Tag())
	}

	return nil
}

// SetField sets the field with Tag tag.
func (m *FieldMap) SetField(tag Tag, field FieldValueWriter) *FieldMap {
	return m.SetBytes(tag, field.Write())
}

// SetBytes sets bytes.
func (m *FieldMap) SetBytes(tag Tag, value []byte) *FieldMap {
	f := m.getOrCreate(tag)
	initField(f, tag, value)
	return m
}

// SetBool is a SetField wrapper for bool fields.
func (m *FieldMap) SetBool(tag Tag, value bool) *FieldMap {
	return m.SetField(tag, FIXBoolean(value))
}

// SetInt is a SetField wrapper for int fields.
func (m *FieldMap) SetInt(tag Tag, value int) *FieldMap {
	v := FIXInt(value)
	return m.SetBytes(tag, v.Write())
}

// SetString is a SetField wrapper for string fields.
func (m *FieldMap) SetString(tag Tag, value string) *FieldMap {
	return m.SetBytes(tag, []byte(value))
}

// Remove removes a tag from field map.
func (m *FieldMap) Remove(tag Tag) {
	m.rwLock.Lock()
	defer m.rwLock.Unlock()

	delete(m.tagLookup, tag)
}

// Clear purges all fields from field map.
func (m *FieldMap) Clear() {
	m.rwLock.Lock()
	defer m.rwLock.Unlock()

	m.tags = m.tags[0:0]
	for k := range m.tagLookup {
		delete(m.tagLookup, k)
	}
}

func (m *FieldMap) clearNoLock() {
	m.tags = m.tags[0:0]
	for k := range m.tagLookup {
		delete(m.tagLookup, k)
	}
}

// CopyInto overwrites the given FieldMap with this one.
func (m *FieldMap) CopyInto(to *FieldMap) {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	to.tagLookup = make(map[Tag]field)
	for tag, f := range m.tagLookup {
		clone := make(field, 1)
		clone[0] = f[0]
		to.tagLookup[tag] = clone
	}
	to.tags = make([]Tag, len(m.tags))
	copy(to.tags, m.tags)
	to.compare = m.compare
}

func (m *FieldMap) add(f field) {
	t := fieldTag(f)
	if _, ok := m.tagLookup[t]; !ok {
		m.tags = append(m.tags, t)
	}

	m.tagLookup[t] = f
}

func (m *FieldMap) getOrCreate(tag Tag) field {
	m.rwLock.Lock()
	defer m.rwLock.Unlock()

	if f, ok := m.tagLookup[tag]; ok {
		f = f[:1]
		return f
	}

	f := make(field, 1)
	m.tagLookup[tag] = f
	m.tags = append(m.tags, tag)
	return f
}

// Set is a setter for fields.
func (m *FieldMap) Set(field FieldWriter) *FieldMap {
	f := m.getOrCreate(field.Tag())
	initField(f, field.Tag(), field.Write())
	return m
}

// SetGroup is a setter specific to group fields.
func (m *FieldMap) SetGroup(field FieldGroupWriter) *FieldMap {
	m.rwLock.Lock()
	defer m.rwLock.Unlock()

	_, ok := m.tagLookup[field.Tag()]
	if !ok {
		m.tags = append(m.tags, field.Tag())
	}
	m.tagLookup[field.Tag()] = field.Write()
	return m
}

func (m *FieldMap) sortedTags() []Tag {
	sort.Sort(m)
	return m.tags
}

func (m FieldMap) write(buffer *bytes.Buffer) {
	m.rwLock.Lock()
	defer m.rwLock.Unlock()

	for _, tag := range m.sortedTags() {
		if f, ok := m.tagLookup[tag]; ok {
			writeField(f, buffer)
		}
	}
}

func (m FieldMap) total() int {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	total := 0
	for _, fields := range m.tagLookup {
		for _, tv := range fields {
			switch tv.tag {
			case tagCheckSum: // Tag does not contribute to total.
			default:
				total += tv.total()
			}
		}
	}

	return total
}

func (m FieldMap) length() int {
	m.rwLock.RLock()
	defer m.rwLock.RUnlock()

	length := 0
	for _, fields := range m.tagLookup {
		for _, tv := range fields {
			switch tv.tag {
			case tagBeginString, tagBodyLength, tagCheckSum: // Tags do not contribute to length.
			default:
				length += tv.length()
			}
		}
	}

	return length
}
