// Copyright 2012-present Oliver Eilhard. All rights reserved.
// Use of this source code is governed by a MIT-license.
// See http://olivere.mit-license.org/license.txt for details.

package elastic

// Highlight allows highlighting search results on one or more fields.
// For details, see:
// https://www.elastic.co/guide/en/elasticsearch/reference/7.0/search-request-highlighting.html
type Highlight struct {
	fields                []*HighlighterField
	tagsSchema            *string
	highlightFilter       *bool
	fragmentSize          *int
	numOfFragments        *int
	preTags               []string
	postTags              []string
	order                 *string
	encoder               *string
	requireFieldMatch     *bool
	maxAnalyzedOffset     *int
	boundaryMaxScan       *int
	boundaryChars         *string
	boundaryScannerType   *string
	boundaryScannerLocale *string
	highlighterType       *string
	fragmenter            *string
	highlightQuery        Query
	noMatchSize           *int
	phraseLimit           *int
	options               map[string]interface{}
	forceSource           *bool
	useExplicitFieldOrder bool
}

func NewHighlight() *Highlight {
	hl := &Highlight{
		options: make(map[string]interface{}),
	}
	return hl
}

func (hl *Highlight) Fields(fields ...*HighlighterField) *Highlight {
	hl.fields = append(hl.fields, fields...)
	return hl
}

func (hl *Highlight) Field(name string) *Highlight {
	field := NewHighlighterField(name)
	hl.fields = append(hl.fields, field)
	return hl
}

func (hl *Highlight) TagsSchema(schemaName string) *Highlight {
	hl.tagsSchema = &schemaName
	return hl
}

func (hl *Highlight) HighlightFilter(highlightFilter bool) *Highlight {
	hl.highlightFilter = &highlightFilter
	return hl
}

func (hl *Highlight) FragmentSize(fragmentSize int) *Highlight {
	hl.fragmentSize = &fragmentSize
	return hl
}

func (hl *Highlight) NumOfFragments(numOfFragments int) *Highlight {
	hl.numOfFragments = &numOfFragments
	return hl
}

func (hl *Highlight) Encoder(encoder string) *Highlight {
	hl.encoder = &encoder
	return hl
}

func (hl *Highlight) PreTags(preTags ...string) *Highlight {
	hl.preTags = append(hl.preTags, preTags...)
	return hl
}

func (hl *Highlight) PostTags(postTags ...string) *Highlight {
	hl.postTags = append(hl.postTags, postTags...)
	return hl
}

func (hl *Highlight) Order(order string) *Highlight {
	hl.order = &order
	return hl
}

func (hl *Highlight) RequireFieldMatch(requireFieldMatch bool) *Highlight {
	hl.requireFieldMatch = &requireFieldMatch
	return hl
}

func (hl *Highlight) MaxAnalyzedOffset(maxAnalyzedOffset int) *Highlight {
	hl.maxAnalyzedOffset = &maxAnalyzedOffset
	return hl
}

func (hl *Highlight) BoundaryMaxScan(boundaryMaxScan int) *Highlight {
	hl.boundaryMaxScan = &boundaryMaxScan
	return hl
}

func (hl *Highlight) BoundaryChars(boundaryChars string) *Highlight {
	hl.boundaryChars = &boundaryChars
	return hl
}

func (hl *Highlight) BoundaryScannerType(boundaryScannerType string) *Highlight {
	hl.boundaryScannerType = &boundaryScannerType
	return hl
}

func (hl *Highlight) BoundaryScannerLocale(boundaryScannerLocale string) *Highlight {
	hl.boundaryScannerLocale = &boundaryScannerLocale
	return hl
}

func (hl *Highlight) HighlighterType(highlighterType string) *Highlight {
	hl.highlighterType = &highlighterType
	return hl
}

func (hl *Highlight) Fragmenter(fragmenter string) *Highlight {
	hl.fragmenter = &fragmenter
	return hl
}

func (hl *Highlight) HighlightQuery(highlightQuery Query) *Highlight {
	hl.highlightQuery = highlightQuery
	return hl
}

func (hl *Highlight) NoMatchSize(noMatchSize int) *Highlight {
	hl.noMatchSize = &noMatchSize
	return hl
}

func (hl *Highlight) Options(options map[string]interface{}) *Highlight {
	hl.options = options
	return hl
}

func (hl *Highlight) ForceSource(forceSource bool) *Highlight {
	hl.forceSource = &forceSource
	return hl
}

func (hl *Highlight) UseExplicitFieldOrder(useExplicitFieldOrder bool) *Highlight {
	hl.useExplicitFieldOrder = useExplicitFieldOrder
	return hl
}

// Creates the query source for the bool query.
func (hl *Highlight) Source() (interface{}, error) {
	// Returns the map inside of "highlight":
	// "highlight":{
	//   ... this ...
	// }
	source := make(map[string]interface{})
	if hl.tagsSchema != nil {
		source["tags_schema"] = *hl.tagsSchema
	}
	if hl.preTags != nil && len(hl.preTags) > 0 {
		source["pre_tags"] = hl.preTags
	}
	if hl.postTags != nil && len(hl.postTags) > 0 {
		source["post_tags"] = hl.postTags
	}
	if hl.order != nil {
		source["order"] = *hl.order
	}
	if hl.highlightFilter != nil {
		source["highlight_filter"] = *hl.highlightFilter
	}
	if hl.fragmentSize != nil {
		source["fragment_size"] = *hl.fragmentSize
	}
	if hl.numOfFragments != nil {
		source["number_of_fragments"] = *hl.numOfFragments
	}
	if hl.encoder != nil {
		source["encoder"] = *hl.encoder
	}
	if hl.requireFieldMatch != nil {
		source["require_field_match"] = *hl.requireFieldMatch
	}
	if hl.maxAnalyzedOffset != nil {
		source["max_analyzed_offset"] = *hl.maxAnalyzedOffset
	}
	if hl.boundaryMaxScan != nil {
		source["boundary_max_scan"] = *hl.boundaryMaxScan
	}
	if hl.boundaryChars != nil {
		source["boundary_chars"] = *hl.boundaryChars
	}
	if hl.boundaryScannerType != nil {
		source["boundary_scanner"] = *hl.boundaryScannerType
	}
	if hl.boundaryScannerLocale != nil {
		source["boundary_scanner_locale"] = *hl.boundaryScannerLocale
	}
	if hl.highlighterType != nil {
		source["type"] = *hl.highlighterType
	}
	if hl.fragmenter != nil {
		source["fragmenter"] = *hl.fragmenter
	}
	if hl.highlightQuery != nil {
		src, err := hl.highlightQuery.Source()
		if err != nil {
			return nil, err
		}
		source["highlight_query"] = src
	}
	if hl.noMatchSize != nil {
		source["no_match_size"] = *hl.noMatchSize
	}
	if hl.phraseLimit != nil {
		source["phrase_limit"] = *hl.phraseLimit
	}
	if hl.options != nil && len(hl.options) > 0 {
		source["options"] = hl.options
	}
	if hl.forceSource != nil {
		source["force_source"] = *hl.forceSource
	}

	if hl.fields != nil && len(hl.fields) > 0 {
		if hl.useExplicitFieldOrder {
			// Use a slice for the fields
			var fields []map[string]interface{}
			for _, field := range hl.fields {
				src, err := field.Source()
				if err != nil {
					return nil, err
				}
				fmap := make(map[string]interface{})
				fmap[field.Name] = src
				fields = append(fields, fmap)
			}
			source["fields"] = fields
		} else {
			// Use a map for the fields
			fields := make(map[string]interface{})
			for _, field := range hl.fields {
				src, err := field.Source()
				if err != nil {
					return nil, err
				}
				fields[field.Name] = src
			}
			source["fields"] = fields
		}
	}

	return source, nil
}

// HighlighterField specifies a highlighted field.
type HighlighterField struct {
	Name string

	preTags           []string
	postTags          []string
	fragmentSize      int
	fragmentOffset    int
	numOfFragments    int
	highlightFilter   *bool
	order             *string
	requireFieldMatch *bool
	boundaryMaxScan   int
	boundaryChars     []rune
	highlighterType   *string
	fragmenter        *string
	highlightQuery    Query
	noMatchSize       *int
	matchedFields     []string
	phraseLimit       *int
	options           map[string]interface{}
	forceSource       *bool

	/*
		Name              string
		preTags           []string
		postTags          []string
		fragmentSize      int
		numOfFragments    int
		fragmentOffset    int
		highlightFilter   *bool
		order             string
		requireFieldMatch *bool
		boundaryMaxScan   int
		boundaryChars     []rune
		highlighterType   string
		fragmenter        string
		highlightQuery    Query
		noMatchSize       *int
		matchedFields     []string
		options           map[string]interface{}
		forceSource       *bool
	*/
}

func NewHighlighterField(name string) *HighlighterField {
	return &HighlighterField{
		Name:            name,
		preTags:         make([]string, 0),
		postTags:        make([]string, 0),
		fragmentSize:    -1,
		fragmentOffset:  -1,
		numOfFragments:  -1,
		boundaryMaxScan: -1,
		boundaryChars:   make([]rune, 0),
		matchedFields:   make([]string, 0),
		options:         make(map[string]interface{}),
	}
}

func (f *HighlighterField) PreTags(preTags ...string) *HighlighterField {
	f.preTags = append(f.preTags, preTags...)
	return f
}

func (f *HighlighterField) PostTags(postTags ...string) *HighlighterField {
	f.postTags = append(f.postTags, postTags...)
	return f
}

func (f *HighlighterField) FragmentSize(fragmentSize int) *HighlighterField {
	f.fragmentSize = fragmentSize
	return f
}

func (f *HighlighterField) FragmentOffset(fragmentOffset int) *HighlighterField {
	f.fragmentOffset = fragmentOffset
	return f
}

func (f *HighlighterField) NumOfFragments(numOfFragments int) *HighlighterField {
	f.numOfFragments = numOfFragments
	return f
}

func (f *HighlighterField) HighlightFilter(highlightFilter bool) *HighlighterField {
	f.highlightFilter = &highlightFilter
	return f
}

func (f *HighlighterField) Order(order string) *HighlighterField {
	f.order = &order
	return f
}

func (f *HighlighterField) RequireFieldMatch(requireFieldMatch bool) *HighlighterField {
	f.requireFieldMatch = &requireFieldMatch
	return f
}

func (f *HighlighterField) BoundaryMaxScan(boundaryMaxScan int) *HighlighterField {
	f.boundaryMaxScan = boundaryMaxScan
	return f
}

func (f *HighlighterField) BoundaryChars(boundaryChars ...rune) *HighlighterField {
	f.boundaryChars = append(f.boundaryChars, boundaryChars...)
	return f
}

func (f *HighlighterField) HighlighterType(highlighterType string) *HighlighterField {
	f.highlighterType = &highlighterType
	return f
}

func (f *HighlighterField) Fragmenter(fragmenter string) *HighlighterField {
	f.fragmenter = &fragmenter
	return f
}

func (f *HighlighterField) HighlightQuery(highlightQuery Query) *HighlighterField {
	f.highlightQuery = highlightQuery
	return f
}

func (f *HighlighterField) NoMatchSize(noMatchSize int) *HighlighterField {
	f.noMatchSize = &noMatchSize
	return f
}

func (f *HighlighterField) Options(options map[string]interface{}) *HighlighterField {
	f.options = options
	return f
}

func (f *HighlighterField) MatchedFields(matchedFields ...string) *HighlighterField {
	f.matchedFields = append(f.matchedFields, matchedFields...)
	return f
}

func (f *HighlighterField) PhraseLimit(phraseLimit int) *HighlighterField {
	f.phraseLimit = &phraseLimit
	return f
}

func (f *HighlighterField) ForceSource(forceSource bool) *HighlighterField {
	f.forceSource = &forceSource
	return f
}

func (f *HighlighterField) Source() (interface{}, error) {
	source := make(map[string]interface{})

	if f.preTags != nil && len(f.preTags) > 0 {
		source["pre_tags"] = f.preTags
	}
	if f.postTags != nil && len(f.postTags) > 0 {
		source["post_tags"] = f.postTags
	}
	if f.fragmentSize != -1 {
		source["fragment_size"] = f.fragmentSize
	}
	if f.numOfFragments != -1 {
		source["number_of_fragments"] = f.numOfFragments
	}
	if f.fragmentOffset != -1 {
		source["fragment_offset"] = f.fragmentOffset
	}
	if f.highlightFilter != nil {
		source["highlight_filter"] = *f.highlightFilter
	}
	if f.order != nil {
		source["order"] = *f.order
	}
	if f.requireFieldMatch != nil {
		source["require_field_match"] = *f.requireFieldMatch
	}
	if f.boundaryMaxScan != -1 {
		source["boundary_max_scan"] = f.boundaryMaxScan
	}
	if f.boundaryChars != nil && len(f.boundaryChars) > 0 {
		source["boundary_chars"] = f.boundaryChars
	}
	if f.highlighterType != nil {
		source["type"] = *f.highlighterType
	}
	if f.fragmenter != nil {
		source["fragmenter"] = *f.fragmenter
	}
	if f.highlightQuery != nil {
		src, err := f.highlightQuery.Source()
		if err != nil {
			return nil, err
		}
		source["highlight_query"] = src
	}
	if f.noMatchSize != nil {
		source["no_match_size"] = *f.noMatchSize
	}
	if f.matchedFields != nil && len(f.matchedFields) > 0 {
		source["matched_fields"] = f.matchedFields
	}
	if f.phraseLimit != nil {
		source["phrase_limit"] = *f.phraseLimit
	}
	if f.options != nil && len(f.options) > 0 {
		source["options"] = f.options
	}
	if f.forceSource != nil {
		source["force_source"] = *f.forceSource
	}

	return source, nil
}
