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

import (
	"fmt"
)

// QueryStringQuery uses the query parser in order to parse its content.
//
// For more details, see
// https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-query-string-query.html
type QueryStringQuery struct {
	queryString               string
	defaultField              string
	defaultOperator           string
	analyzer                  string
	quoteAnalyzer             string
	quoteFieldSuffix          string
	autoGeneratePhraseQueries *bool
	allowLeadingWildcard      *bool
	lowercaseExpandedTerms    *bool
	enablePositionIncrements  *bool
	analyzeWildcard           *bool
	locale                    string // Deprecated: Decision is now made by the analyzer.
	boost                     *float64
	fuzziness                 string
	fuzzyPrefixLength         *int
	fuzzyMaxExpansions        *int
	fuzzyRewrite              string
	phraseSlop                *int
	fields                    []string
	fieldBoosts               map[string]*float64
	useDisMax                 *bool
	tieBreaker                *float64
	rewrite                   string
	minimumShouldMatch        string
	lenient                   *bool
	queryName                 string
	timeZone                  string
	maxDeterminizedStates     *int
	escape                    *bool
	splitOnWhitespace         *bool
	useAllFields              *bool
}

// NewQueryStringQuery creates and initializes a new QueryStringQuery.
func NewQueryStringQuery(queryString string) *QueryStringQuery {
	return &QueryStringQuery{
		queryString: queryString,
		fields:      make([]string, 0),
		fieldBoosts: make(map[string]*float64),
	}
}

// DefaultField specifies the field to run against when no prefix field
// is specified. Only relevant when not explicitly adding fields the query
// string will run against.
func (q *QueryStringQuery) DefaultField(defaultField string) *QueryStringQuery {
	q.defaultField = defaultField
	return q
}

// Field adds a field to run the query string against.
func (q *QueryStringQuery) Field(field string) *QueryStringQuery {
	q.fields = append(q.fields, field)
	return q
}

// AllFields tells the query string query to use all fields explicitly,
// even if _all is enabled. If the "default_field" parameter or "fields"
// are specified, they will be ignored.
func (q *QueryStringQuery) AllFields(useAllFields bool) *QueryStringQuery {
	q.useAllFields = &useAllFields
	return q
}

// FieldWithBoost adds a field to run the query string against with a specific boost.
func (q *QueryStringQuery) FieldWithBoost(field string, boost float64) *QueryStringQuery {
	q.fields = append(q.fields, field)
	q.fieldBoosts[field] = &boost
	return q
}

// UseDisMax specifies whether to combine queries using dis max or boolean
// query when more zhan one field is used with the query string. Defaults
// to dismax (true).
func (q *QueryStringQuery) UseDisMax(useDisMax bool) *QueryStringQuery {
	q.useDisMax = &useDisMax
	return q
}

// TieBreaker is used when more than one field is used with the query string,
// and combined queries are using dismax.
func (q *QueryStringQuery) TieBreaker(tieBreaker float64) *QueryStringQuery {
	q.tieBreaker = &tieBreaker
	return q
}

// DefaultOperator sets the boolean operator of the query parser used to
// parse the query string.
//
// In default mode (OR) terms without any modifiers
// are considered optional, e.g. "capital of Hungary" is equal to
// "capital OR of OR Hungary".
//
// In AND mode, terms are considered to be in conjunction. The above mentioned
// query is then parsed as "capital AND of AND Hungary".
func (q *QueryStringQuery) DefaultOperator(operator string) *QueryStringQuery {
	q.defaultOperator = operator
	return q
}

// Analyzer is an optional analyzer used to analyze the query string.
// Note, if a field has search analyzer defined for it, then it will be used
// automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) Analyzer(analyzer string) *QueryStringQuery {
	q.analyzer = analyzer
	return q
}

// QuoteAnalyzer is an optional analyzer to be used to analyze the query string
// for phrase searches. Note, if a field has search analyzer defined for it,
// then it will be used automatically. Defaults to the smart search analyzer.
func (q *QueryStringQuery) QuoteAnalyzer(quoteAnalyzer string) *QueryStringQuery {
	q.quoteAnalyzer = quoteAnalyzer
	return q
}

// AutoGeneratePhraseQueries indicates whether or not phrase queries will
// be automatically generated when the analyzer returns more then one term
// from whitespace delimited text. Set to false if phrase queries should only
// be generated when surrounded by double quotes.
func (q *QueryStringQuery) AutoGeneratePhraseQueries(autoGeneratePhraseQueries bool) *QueryStringQuery {
	q.autoGeneratePhraseQueries = &autoGeneratePhraseQueries
	return q
}

// MaxDeterminizedState protects against too-difficult regular expression queries.
func (q *QueryStringQuery) MaxDeterminizedState(maxDeterminizedStates int) *QueryStringQuery {
	q.maxDeterminizedStates = &maxDeterminizedStates
	return q
}

// AllowLeadingWildcard specifies whether leading wildcards should be allowed
// or not (defaults to true).
func (q *QueryStringQuery) AllowLeadingWildcard(allowLeadingWildcard bool) *QueryStringQuery {
	q.allowLeadingWildcard = &allowLeadingWildcard
	return q
}

// LowercaseExpandedTerms indicates whether terms of wildcard, prefix, fuzzy
// and range queries are automatically lower-cased or not. Default is true.
func (q *QueryStringQuery) LowercaseExpandedTerms(lowercaseExpandedTerms bool) *QueryStringQuery {
	q.lowercaseExpandedTerms = &lowercaseExpandedTerms
	return q
}

// EnablePositionIncrements indicates whether to enable position increments
// in result query. Defaults to true.
//
// When set, result phrase and multi-phrase queries will be aware of position
// increments. Useful when e.g. a StopFilter increases the position increment
// of the token that follows an omitted token.
func (q *QueryStringQuery) EnablePositionIncrements(enablePositionIncrements bool) *QueryStringQuery {
	q.enablePositionIncrements = &enablePositionIncrements
	return q
}

// Fuzziness sets the edit distance for fuzzy queries. Default is "AUTO".
func (q *QueryStringQuery) Fuzziness(fuzziness string) *QueryStringQuery {
	q.fuzziness = fuzziness
	return q
}

// FuzzyPrefixLength sets the minimum prefix length for fuzzy queries.
// Default is 1.
func (q *QueryStringQuery) FuzzyPrefixLength(fuzzyPrefixLength int) *QueryStringQuery {
	q.fuzzyPrefixLength = &fuzzyPrefixLength
	return q
}

func (q *QueryStringQuery) FuzzyMaxExpansions(fuzzyMaxExpansions int) *QueryStringQuery {
	q.fuzzyMaxExpansions = &fuzzyMaxExpansions
	return q
}

func (q *QueryStringQuery) FuzzyRewrite(fuzzyRewrite string) *QueryStringQuery {
	q.fuzzyRewrite = fuzzyRewrite
	return q
}

// PhraseSlop sets the default slop for phrases. If zero, then exact matches
// are required. Default value is zero.
func (q *QueryStringQuery) PhraseSlop(phraseSlop int) *QueryStringQuery {
	q.phraseSlop = &phraseSlop
	return q
}

// AnalyzeWildcard indicates whether to enabled analysis on wildcard and prefix queries.
func (q *QueryStringQuery) AnalyzeWildcard(analyzeWildcard bool) *QueryStringQuery {
	q.analyzeWildcard = &analyzeWildcard
	return q
}

func (q *QueryStringQuery) Rewrite(rewrite string) *QueryStringQuery {
	q.rewrite = rewrite
	return q
}

func (q *QueryStringQuery) MinimumShouldMatch(minimumShouldMatch string) *QueryStringQuery {
	q.minimumShouldMatch = minimumShouldMatch
	return q
}

// Boost sets the boost for this query.
func (q *QueryStringQuery) Boost(boost float64) *QueryStringQuery {
	q.boost = &boost
	return q
}

// QuoteFieldSuffix is an optional field name suffix to automatically
// try and add to the field searched when using quoted text.
func (q *QueryStringQuery) QuoteFieldSuffix(quoteFieldSuffix string) *QueryStringQuery {
	q.quoteFieldSuffix = quoteFieldSuffix
	return q
}

// Lenient indicates whether the query string parser should be lenient
// when parsing field values. It defaults to the index setting and if not
// set, defaults to false.
func (q *QueryStringQuery) Lenient(lenient bool) *QueryStringQuery {
	q.lenient = &lenient
	return q
}

// QueryName sets the query name for the filter that can be used when
// searching for matched_filters per hit.
func (q *QueryStringQuery) QueryName(queryName string) *QueryStringQuery {
	q.queryName = queryName
	return q
}

// Locale specifies the locale to be used for string conversions.
//
// Deprecated: Decision is now made by the analyzer.
func (q *QueryStringQuery) Locale(locale string) *QueryStringQuery {
	q.locale = locale
	return q
}

// TimeZone can be used to automatically adjust to/from fields using a
// timezone. Only used with date fields, of course.
func (q *QueryStringQuery) TimeZone(timeZone string) *QueryStringQuery {
	q.timeZone = timeZone
	return q
}

// Escape performs escaping of the query string.
func (q *QueryStringQuery) Escape(escape bool) *QueryStringQuery {
	q.escape = &escape
	return q
}

// SplitOnWhitespace indicates whether query text should be split on whitespace
// prior to analysis.
func (q *QueryStringQuery) SplitOnWhitespace(splitOnWhitespace bool) *QueryStringQuery {
	q.splitOnWhitespace = &splitOnWhitespace
	return q
}

// Source returns JSON for the query.
func (q *QueryStringQuery) Source() (interface{}, error) {
	source := make(map[string]interface{})
	query := make(map[string]interface{})
	source["query_string"] = query

	query["query"] = q.queryString

	if q.defaultField != "" {
		query["default_field"] = q.defaultField
	}

	if len(q.fields) > 0 {
		var fields []string
		for _, field := range q.fields {
			if boost, found := q.fieldBoosts[field]; found {
				if boost != nil {
					fields = append(fields, fmt.Sprintf("%s^%f", field, *boost))
				} else {
					fields = append(fields, field)
				}
			} else {
				fields = append(fields, field)
			}
		}
		query["fields"] = fields
	}

	if q.tieBreaker != nil {
		query["tie_breaker"] = *q.tieBreaker
	}
	if q.useDisMax != nil {
		query["use_dis_max"] = *q.useDisMax
	}
	if q.defaultOperator != "" {
		query["default_operator"] = q.defaultOperator
	}
	if q.analyzer != "" {
		query["analyzer"] = q.analyzer
	}
	if q.quoteAnalyzer != "" {
		query["quote_analyzer"] = q.quoteAnalyzer
	}
	if q.autoGeneratePhraseQueries != nil {
		query["auto_generate_phrase_queries"] = *q.autoGeneratePhraseQueries
	}
	if q.maxDeterminizedStates != nil {
		query["max_determinized_states"] = *q.maxDeterminizedStates
	}
	if q.allowLeadingWildcard != nil {
		query["allow_leading_wildcard"] = *q.allowLeadingWildcard
	}
	if q.lowercaseExpandedTerms != nil {
		query["lowercase_expanded_terms"] = *q.lowercaseExpandedTerms
	}
	if q.enablePositionIncrements != nil {
		query["enable_position_increments"] = *q.enablePositionIncrements
	}
	if q.fuzziness != "" {
		query["fuzziness"] = q.fuzziness
	}
	if q.boost != nil {
		query["boost"] = *q.boost
	}
	if q.fuzzyPrefixLength != nil {
		query["fuzzy_prefix_length"] = *q.fuzzyPrefixLength
	}
	if q.fuzzyMaxExpansions != nil {
		query["fuzzy_max_expansions"] = *q.fuzzyMaxExpansions
	}
	if q.fuzzyRewrite != "" {
		query["fuzzy_rewrite"] = q.fuzzyRewrite
	}
	if q.phraseSlop != nil {
		query["phrase_slop"] = *q.phraseSlop
	}
	if q.analyzeWildcard != nil {
		query["analyze_wildcard"] = *q.analyzeWildcard
	}
	if q.rewrite != "" {
		query["rewrite"] = q.rewrite
	}
	if q.minimumShouldMatch != "" {
		query["minimum_should_match"] = q.minimumShouldMatch
	}
	if q.quoteFieldSuffix != "" {
		query["quote_field_suffix"] = q.quoteFieldSuffix
	}
	if q.lenient != nil {
		query["lenient"] = *q.lenient
	}
	if q.queryName != "" {
		query["_name"] = q.queryName
	}
	if q.locale != "" {
		query["locale"] = q.locale
	}
	if q.timeZone != "" {
		query["time_zone"] = q.timeZone
	}
	if q.escape != nil {
		query["escape"] = *q.escape
	}
	if q.splitOnWhitespace != nil {
		query["split_on_whitespace"] = *q.splitOnWhitespace
	}
	if q.useAllFields != nil {
		query["all_fields"] = *q.useAllFields
	}

	return source, nil
}
