// Package datadictionary provides support for parsing and organizing FIX Data Dictionaries
package datadictionary

import (
	"encoding/xml"
	"io"
	"os"

	"github.com/pkg/errors"
)

// DataDictionary models FIX messages, components, and fields.
type DataDictionary struct {
	FIXType         string
	Major           int
	Minor           int
	ServicePack     int
	FieldTypeByTag  map[int]*FieldType
	FieldTypeByName map[string]*FieldType
	Messages        map[string]*MessageDef
	ComponentTypes  map[string]*ComponentType
	Header          *MessageDef
	Trailer         *MessageDef
}

// MessagePart can represent a Field, Repeating Group, or Component.
type MessagePart interface {
	Name() string
	Required() bool
}

// messagePartWithFields is a MessagePart with multiple Fields.
type messagePartWithFields interface {
	MessagePart
	Fields() []*FieldDef
	RequiredFields() []*FieldDef
}

// ComponentType is a grouping of fields.
type ComponentType struct {
	name           string
	parts          []MessagePart
	fields         []*FieldDef
	requiredFields []*FieldDef
	requiredParts  []MessagePart
}

// NewComponentType returns an initialized component type.
func NewComponentType(name string, parts []MessagePart) *ComponentType {
	comp := ComponentType{
		name:  name,
		parts: parts,
	}

	for _, part := range parts {

		if part.Required() {
			comp.requiredParts = append(comp.requiredParts, part)
		}

		switch f := part.(type) {
		case messagePartWithFields:
			comp.fields = append(comp.fields, f.Fields()...)

			if f.Required() {
				comp.requiredFields = append(comp.requiredFields, f.RequiredFields()...)
			}
		case *FieldDef:
			comp.fields = append(comp.fields, f)

			if f.Required() {
				comp.requiredFields = append(comp.requiredFields, f)
			}
		}
	}

	return &comp
}

// Name returns the name of this component type.
func (c ComponentType) Name() string { return c.name }

// Fields returns all fields contained in this component. Includes fields
// encapsulated in components of this component.
func (c ComponentType) Fields() []*FieldDef { return c.fields }

// RequiredFields returns those fields that are required for this component.
func (c ComponentType) RequiredFields() []*FieldDef { return c.requiredFields }

// RequiredParts returns those parts that are required for this component.
func (c ComponentType) RequiredParts() []MessagePart { return c.requiredParts }

// Parts returns all parts in declaration order contained in this component.
func (c ComponentType) Parts() []MessagePart { return c.parts }

// TagSet is set for tags.
type TagSet map[int]struct{}

// Add adds a tag to the tagset.
func (t TagSet) Add(tag int) {
	t[tag] = struct{}{}
}

// Component is a Component as it appears in a given MessageDef.
type Component struct {
	*ComponentType
	required bool
}

// NewComponent returns an initialized Component instance.
func NewComponent(ct *ComponentType, required bool) *Component {
	return &Component{
		ComponentType: ct,
		required:      required,
	}
}

// Required returns true if this component is required for the containing
// MessageDef.
func (c Component) Required() bool { return c.required }

// Field models a field or repeating group in a message.
type Field interface {
	Tag() int
}

// FieldDef models a field belonging to a message.
type FieldDef struct {
	*FieldType
	required bool

	Parts          []MessagePart
	Fields         []*FieldDef
	requiredParts  []MessagePart
	requiredFields []*FieldDef
}

// NewFieldDef returns an initialized FieldDef.
func NewFieldDef(fieldType *FieldType, required bool) *FieldDef {
	return &FieldDef{
		FieldType: fieldType,
		required:  required,
	}
}

// NewGroupFieldDef returns an initialized FieldDef for a repeating group.
func NewGroupFieldDef(fieldType *FieldType, required bool, parts []MessagePart) *FieldDef {
	field := FieldDef{
		FieldType: fieldType,
		required:  required,
		Parts:     parts,
	}

	for _, part := range parts {
		if part.Required() {
			field.requiredParts = append(field.requiredParts, part)
		}

		if comp, ok := part.(Component); ok {
			field.Fields = append(field.Fields, comp.Fields()...)

			if comp.required {
				field.requiredFields = append(field.requiredFields, comp.requiredFields...)
			}
		} else {
			if child, ok := part.(*FieldDef); ok {
				field.Fields = append(field.Fields, child)

				if child.required {
					field.requiredFields = append(field.requiredFields, child)
				}
			} else {
				panic("unknown part")
			}
		}
	}

	return &field
}

// Required returns true if this FieldDef is required for the containing
// MessageDef.
func (f FieldDef) Required() bool { return f.required }

// IsGroup is true if the field is a repeating group.
func (f FieldDef) IsGroup() bool {
	return len(f.Fields) > 0
}

// RequiredParts returns those parts that are required for this FieldDef. IsGroup
// must return true.
func (f FieldDef) RequiredParts() []MessagePart { return f.requiredParts }

// RequiredFields returns those fields that are required for this FieldDef. IsGroup
// must return true.
func (f FieldDef) RequiredFields() []*FieldDef { return f.requiredFields }

func (f FieldDef) childTags() []int {
	tags := make([]int, 0, len(f.Fields))

	for _, f := range f.Fields {
		tags = append(tags, f.Tag())
		tags = append(tags, f.childTags()...)
	}

	return tags
}

// FieldType holds information relating to a field.  Includes Tag, type, and enums, if defined.
type FieldType struct {
	name  string
	tag   int
	Type  string
	Enums map[string]Enum
}

// NewFieldType returns a pointer to an initialized FieldType.
func NewFieldType(name string, tag int, fixType string) *FieldType {
	return &FieldType{
		name: name,
		tag:  tag,
		Type: fixType,
	}
}

// Name returns the name for this FieldType.
func (f FieldType) Name() string { return f.name }

// Tag returns the tag for this fieldType.
func (f FieldType) Tag() int { return f.tag }

// Enum is a container for value and description.
type Enum struct {
	Value       string
	Description string
}

// MessageDef can apply to header, trailer, or body of a FIX Message.
type MessageDef struct {
	Name    string
	MsgType string
	Fields  map[int]*FieldDef
	// Parts are the MessageParts of contained in this MessageDef in declaration order.
	Parts         []MessagePart
	requiredParts []MessagePart

	RequiredTags TagSet
	Tags         TagSet
}

// RequiredParts returns those parts that are required for this Message.
func (m MessageDef) RequiredParts() []MessagePart { return m.requiredParts }

// NewMessageDef returns a pointer to an initialized MessageDef.
func NewMessageDef(name, msgType string, parts []MessagePart) *MessageDef {
	msg := MessageDef{
		Name:         name,
		MsgType:      msgType,
		Fields:       make(map[int]*FieldDef),
		RequiredTags: make(TagSet),
		Tags:         make(TagSet),
		Parts:        parts,
	}

	processField := func(field *FieldDef, allowRequired bool) {
		msg.Fields[field.Tag()] = field
		msg.Tags.Add(field.Tag())
		for _, t := range field.childTags() {
			msg.Tags.Add(t)
		}

		if allowRequired && field.Required() {
			msg.RequiredTags.Add(field.Tag())
		}
	}

	for _, part := range parts {
		if part.Required() {
			msg.requiredParts = append(msg.requiredParts, part)
		}

		switch pType := part.(type) {
		case messagePartWithFields:
			for _, f := range pType.Fields() {
				// Field if required in component is required in message only if
				// component is required.
				processField(f, pType.Required())
			}

		case *FieldDef:
			processField(pType, true)

		default:
			panic("Unknown Part")
		}
	}

	return &msg
}

// Parse loads and build a datadictionary instance from an xml file.
func Parse(path string) (*DataDictionary, error) {
	var xmlFile *os.File
	var err error
	xmlFile, err = os.Open(path)
	if err != nil {
		return nil, errors.Wrapf(err, "problem opening file: %v", path)
	}
	defer xmlFile.Close()

	return ParseSrc(xmlFile)
}

// ParseSrc loads and build a datadictionary instance from an xml source.
func ParseSrc(xmlSrc io.Reader) (*DataDictionary, error) {
	doc := new(XMLDoc)
	decoder := xml.NewDecoder(xmlSrc)
	decoder.CharsetReader = func(_ string, input io.Reader) (io.Reader, error) {
		return input, nil
	}

	if err := decoder.Decode(doc); err != nil {
		return nil, errors.Wrapf(err, "problem parsing XML file")
	}

	b := new(builder)
	dict, err := b.build(doc)
	if err != nil {
		return nil, err
	}

	return dict, nil
}
