// 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 (
	"fmt"
	"math"
	"strconv"
)

// GroupItem interface is used to construct repeating group templates.
type GroupItem interface {
	// Tag returns the tag identifying this GroupItem.
	Tag() Tag

	// Read Parameter to Read is tagValues.  For most fields, only the first tagValue will be required.
	// The length of the slice extends from the tagValue mapped to the field to be read through the
	// following fields. This can be useful for GroupItems made up of repeating groups.
	//
	// The Read function returns the remaining tagValues not processed by the GroupItem. If there was a
	// problem reading the field, an error may be returned.
	Read([]TagValue) ([]TagValue, error)

	// Clone makes a copy of this GroupItem.
	Clone() GroupItem
}

type protoGroupElement struct {
	tag Tag
}

func (t protoGroupElement) Tag() Tag { return t.tag }
func (t protoGroupElement) Read(tv []TagValue) ([]TagValue, error) {
	if tv[0].tag == t.tag {
		return tv[1:], nil
	}

	return tv, nil
}

func (t protoGroupElement) Clone() GroupItem { return t }

// GroupElement returns a GroupItem made up of a single field.
func GroupElement(tag Tag) GroupItem {
	return protoGroupElement{tag: tag}
}

// GroupTemplate specifies the group item order for a RepeatingGroup.
type GroupTemplate []GroupItem

// Clone makes a copy of this GroupTemplate.
func (gt GroupTemplate) Clone() GroupTemplate {
	clone := make(GroupTemplate, len(gt))
	for i := range gt {
		clone[i] = gt[i].Clone()
	}

	return clone
}

// Group is a group of fields occurring in a repeating group.
type Group struct{ FieldMap }

// RepeatingGroup is a FIX Repeating Group type.
type RepeatingGroup struct {
	tag      Tag
	template GroupTemplate
	groups   []*Group
}

// NewRepeatingGroup returns an initilized RepeatingGroup instance.
func NewRepeatingGroup(tag Tag, template GroupTemplate) *RepeatingGroup {
	return &RepeatingGroup{
		tag:      tag,
		template: template,
	}
}

// Tag returns the Tag for this repeating Group.
func (f RepeatingGroup) Tag() Tag {
	return f.tag
}

// Clone makes a copy of this RepeatingGroup (tag, template).
func (f RepeatingGroup) Clone() GroupItem {
	return &RepeatingGroup{
		tag:      f.tag,
		template: f.template.Clone(),
	}
}

// Len returns the number of Groups in this RepeatingGroup.
func (f RepeatingGroup) Len() int {
	return len(f.groups)
}

// Get returns the ith group in this RepeatingGroup.
func (f RepeatingGroup) Get(i int) *Group {
	return f.groups[i]
}

// Add appends a new group to the RepeatingGroup and returns the new Group.
func (f *RepeatingGroup) Add() *Group {
	g := new(Group)
	g.initWithOrdering(f.groupTagOrder())

	f.groups = append(f.groups, g)
	return g
}

// Write returns tagValues for all Items in the repeating group ordered by
// Group sequence and Group template order.
func (f RepeatingGroup) Write() []TagValue {
	tvs := make([]TagValue, 1)
	tvs[0].init(f.tag, []byte(strconv.Itoa(len(f.groups))))

	for _, group := range f.groups {
		tags := group.sortedTags()
		group.rwLock.RLock()
		for _, tag := range tags {
			if fields, ok := group.tagLookup[tag]; ok {
				tvs = append(tvs, fields...)
			}
		}
		group.rwLock.RUnlock()
	}

	return tvs
}

func (f RepeatingGroup) findItemInGroupTemplate(t Tag) (item GroupItem, ok bool) {
	for _, templateField := range f.template {
		if t == templateField.Tag() {
			ok = true
			item = templateField.Clone()
			break
		}
	}

	return
}

func (f RepeatingGroup) groupTagOrder() tagOrder {
	tagMap := make(map[Tag]int)
	for i, f := range f.template {
		tagMap[f.Tag()] = i
	}

	return func(i, j Tag) bool {
		orderi := math.MaxInt32
		orderj := math.MaxInt32

		if iIndex, ok := tagMap[i]; ok {
			orderi = iIndex
		}

		if jIndex, ok := tagMap[j]; ok {
			orderj = jIndex
		}

		return orderi < orderj
	}
}

func (f RepeatingGroup) delimiter() Tag {
	return f.template[0].Tag()
}

func (f RepeatingGroup) isDelimiter(t Tag) bool {
	return t == f.delimiter()
}

func (f *RepeatingGroup) Read(tv []TagValue) ([]TagValue, error) {
	expectedGroupSize, err := atoi(tv[0].value)
	if err != nil {
		return tv, err
	}

	if expectedGroupSize == 0 {
		return tv[1:], nil
	}

	tv = tv[1:cap(tv)]
	tagOrdering := f.groupTagOrder()
	group := new(Group)
	group.initWithOrdering(tagOrdering)
	for len(tv) > 0 {
		gi, ok := f.findItemInGroupTemplate(tv[0].tag)
		if !ok {
			break
		}

		tvRange := tv
		if tv, err = gi.Read(tv); err != nil {
			return tv, err
		}

		if f.isDelimiter(gi.Tag()) {
			group = new(Group)
			group.initWithOrdering(tagOrdering)

			f.groups = append(f.groups, group)
		}

		group.rwLock.Lock()
		group.tagLookup[tvRange[0].tag] = tvRange
		group.tags = append(group.tags, gi.Tag())
		group.rwLock.Unlock()
	}

	if len(f.groups) != expectedGroupSize {
		return tv, repeatingGroupFieldsOutOfOrder(f.tag, fmt.Sprintf("group %v: template is wrong or delimiter %v not found: expected %v groups, but found %v", f.tag, f.delimiter(), expectedGroupSize, len(f.groups)))
	}

	return tv, err
}
