/*
 * Copyright 2015 Ben Ashford
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

//! Implementation of the ElasticSearch Query DSL.
//!
//! Warning! autogenerated at: `<%= Time.now %>`
//! Do not edit query.rs directly.  The template file is query.rs.erb and the
//! script to generate it is generate_query_dsl.rb

use std::collections::BTreeMap;

use rustc_serialize::json::{Json, ToJson};

use units::{DistanceType,
            DistanceUnit,
            Duration,
            GeoBox,
            JsonVal,
            Location,
            OneOrMany};
use util::StrJoin;

<%= enums['Query'] %>

<%= enums['Filter'] %>

// Match queries

<%= simple_value_enum('ZeroTermsQuery', ['none', 'all']) %>

#[derive(Debug)]
pub enum Fuzziness {
    Auto,
    LevenshteinDistance(i64),
    Proportionate(f64)
}

from!(i64, Fuzziness, LevenshteinDistance);
from!(f64, Fuzziness, Proportionate);

impl ToJson for Fuzziness {
    fn to_json(&self) -> Json {
        use self::Fuzziness::{Auto, LevenshteinDistance, Proportionate};
        match self {
            &Auto                      => "auto".to_json(),
            &LevenshteinDistance(dist) => dist.to_json(),
            &Proportionate(prop)       => prop.to_json()
        }
    }
}

<%= simple_value_enum('MatchType', ['phrase', 'phrase_prefix']) %>

<%= simple_value_enum('MatchQueryType', ['best_fields',
                                         'most_fields',
                                         'cross_fields',
                                         'phrase',
                                         'phrase_prefix']) %>

// Option structs for Query(ies)

<%= structs['MatchAllQuery'] %>
<%= to_json_impl('MatchAllQuery') %>

<%= structs['MatchQuery'] %>
<%= to_json_inner_impl('MatchQuery') %>

<%= structs['MultiMatchQuery'] %>
<%= to_json_impl('MultiMatchQuery') %>

<%= structs['BoolQuery'] %>
<%= to_json_impl('BoolQuery') %>

<%= structs['BoostingQuery'] %>
<%= to_json_impl('BoostingQuery') %>

<%= structs['CommonQuery'] %>

#[derive(Debug)]
pub struct CombinationMinimumShouldMatch {
    first: MinimumShouldMatch,
    second: MinimumShouldMatch
}

impl CombinationMinimumShouldMatch {
    pub fn new<A, B>(first: A, second: B) -> CombinationMinimumShouldMatch
        where A: Into<MinimumShouldMatch>,
              B: Into<MinimumShouldMatch>
    {
        CombinationMinimumShouldMatch {
            first:  first.into(),
            second: second.into()
        }
    }
}

impl ToString for CombinationMinimumShouldMatch {
    fn to_string(&self) -> String {
        format!("{}<{}", self.first.to_string(), self.second.to_string())
    }
}

impl ToJson for CombinationMinimumShouldMatch {
    fn to_json(&self) -> Json {
        self.to_string().to_json()
    }
}

#[derive(Debug)]
pub enum MinimumShouldMatch {
    Integer(i64),
    Percentage(f64),
    Combination(Box<CombinationMinimumShouldMatch>),
    MultipleCombination(Vec<CombinationMinimumShouldMatch>),
    LowHigh(i64, i64)
}

from!(i64, MinimumShouldMatch, Integer);
from!(f64, MinimumShouldMatch, Percentage);
from_exp!(CombinationMinimumShouldMatch,
          MinimumShouldMatch,
          from,
          MinimumShouldMatch::Combination(Box::new(from)));
from!(Vec<CombinationMinimumShouldMatch>, MinimumShouldMatch, MultipleCombination);
from_exp!((i64, i64),
          MinimumShouldMatch,
          from,
          MinimumShouldMatch::LowHigh(from.0, from.1));

impl ToString for MinimumShouldMatch {
    fn to_string(&self) -> String {
        match self {
            &MinimumShouldMatch::Integer(val) => val.to_string(),
            &MinimumShouldMatch::Percentage(val) => {
                format!("{}%", val)
            },
            _ => panic!("Can't convert {:?} to String", self)
        }
    }
}

impl ToJson for MinimumShouldMatch {
    fn to_json(&self) -> Json {
        match self {
            &MinimumShouldMatch::Integer(val) => val.to_json(),
            &MinimumShouldMatch::Percentage(_) => {
                self.to_string().to_json()
            },
            &MinimumShouldMatch::Combination(ref comb) => {
                comb.to_json()
            },
            &MinimumShouldMatch::MultipleCombination(ref combs) => {
                Json::String(combs.iter().map(|c| c.to_string()).join(" "))
            }
            &MinimumShouldMatch::LowHigh(low, high) => {
                let mut d = BTreeMap::new();
                d.insert("low_freq".to_owned(), low.to_json());
                d.insert("high_freq".to_owned(), high.to_json());
                Json::Object(d)
            }
        }
    }
}

impl ToJson for CommonQuery {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        let mut inner = BTreeMap::new();
        inner.insert("query".to_owned(), self.query.to_json());
        self.add_optionals(&mut inner);
        d.insert("body".to_owned(), inner.to_json());
        Json::Object(d)
    }
}

<%= structs['ConstantScoreQuery'] %>
<%= to_json_impl('ConstantScoreQuery') %>

<%= structs['DisMaxQuery'] %>
<%= to_json_impl('DisMaxQuery') %>

<%= structs['FilteredQuery'] %>

#[derive(Debug)]
pub enum Strategy {
    LeapFrogQueryFirst,
    LeapFrogFilterFirst,
    QueryFirst,
    RandomAccess(i64),
    RandomAccessAlways
}

from!(i64, Strategy, RandomAccess);

impl ToJson for Strategy {
    fn to_json(&self) -> Json {
        match self {
            &Strategy::LeapFrogQueryFirst  => "leap_frog_query_first".to_json(),
            &Strategy::LeapFrogFilterFirst => "leap_frog_filter_first".to_json(),
            &Strategy::QueryFirst          => "query_first".to_json(),
            &Strategy::RandomAccess(amt)   => format!("random_access_{}", amt).to_json(),
            &Strategy::RandomAccessAlways  => "random_access_always".to_json()
        }
    }
}

<%= to_json_impl('FilteredQuery') %>

<%= structs['FuzzyLikeThisQuery'] %>
<%= to_json_impl('FuzzyLikeThisQuery') %>

<%= structs['FuzzyLikeThisFieldQuery'] %>
<%= to_json_inner_impl('FuzzyLikeThisFieldQuery') %>

<%= structs['FunctionScoreQuery'] %>

<%= simple_value_enum('ScoreMode', ['multiply',
                                    'sum',
                                    'avg',
                                    'first',
                                    'max',
                                    'min']) %>
<%= simple_value_enum('BoostMode', ['multiply',
                                    'replace',
                                    'sum',
                                    'avg',
                                    'max',
                                    'min']) %>

<%= to_json_impl('FunctionScoreQuery') %>

<%= structs['FuzzyQuery'] %>
<%= to_json_inner_impl('FuzzyQuery') %>

// Required for GeoShape

#[derive(Debug)]
pub struct Shape {
    shape_type: String,
    coordinates: Vec<(f64, f64)>
}

impl Shape {
    pub fn new<A: Into<String>>(shape_type: A, coordinates: Vec<(f64, f64)>) -> Shape {
        Shape {
            shape_type:  shape_type.into(),
            coordinates: coordinates
        }
    }
}

impl ToJson for Shape {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        let mut inner = BTreeMap::new();

        inner.insert("type".to_owned(), self.shape_type.to_json());

        let coordinates:Vec<Vec<f64>> = self.coordinates
            .iter()
            .map (|&(a, b)| vec![a, b])
            .collect();
        inner.insert("coordinates".to_owned(), coordinates.to_json());

        d.insert("shape".to_owned(), Json::Object(inner));
        Json::Object(d)
    }
}

#[derive(Debug)]
pub struct IndexedShape {
    id:       String,
    doc_type: String,
    index:    String,
    path:     String
}

impl IndexedShape {
    pub fn new<A, B, C, D>(id: A, doc_type: B, index: C, path: D) -> IndexedShape
        where A: Into<String>,
              B: Into<String>,
              C: Into<String>,
              D: Into<String>
    {
        IndexedShape {
            id: id.into(),
            doc_type: doc_type.into(),
            index: index.into(),
            path: path.into()
        }
    }
}

impl ToJson for IndexedShape {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        let mut inner = BTreeMap::new();
        inner.insert("id".to_owned(), self.id.to_json());
        inner.insert("type".to_owned(), self.doc_type.to_json());
        inner.insert("index".to_owned(), self.index.to_json());
        inner.insert("path".to_owned(), self.path.to_json());
        d.insert("indexed_shape".to_owned(), Json::Object(inner));
        Json::Object(d)
    }
}

<%= structs['GeoShapeQuery'] %>
<%= to_json_inner_impl('GeoShapeQuery') %>

<%= structs['HasChildQuery'] %>
<%= to_json_impl('HasChildQuery') %>

<%= structs['HasParentQuery'] %>
<%= to_json_impl('HasParentQuery') %>

<%= structs['IdsQuery'] %>
<%= to_json_impl('IdsQuery') %>

<%= structs['IndicesQuery'] %>
<%= to_json_impl('IndicesQuery') %>

// A document can be provided as an example
#[derive(Debug)]
pub struct Doc {
    index:    String,
    doc_type: String,
    doc:      Option<Json>,
    id:       Option<String>
}

impl Doc {
    pub fn from_doc<A, B>(index: A, doc_type: B, doc: Json) -> Doc
        where A: Into<String>, B: Into<String>
    {
        Doc {
            index:    index.into(),
            doc_type: doc_type.into(),
            doc:      Some(doc),
            id:       None
        }
    }

    pub fn id<A, B, C>(index: A, doc_type: B, id: C) -> Doc
        where A: Into<String>, B: Into<String>, C: Into<String>
    {
        Doc {
            index:    index.into(),
            doc_type: doc_type.into(),
            doc:      None,
            id:       Some(id.into())
        }
    }
}

impl ToJson for Doc {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert("_index".to_owned(), self.index.to_json());
        d.insert("_type".to_owned(), self.doc_type.to_json());

        optional_add!(d, self.doc, "doc");
        optional_add!(d, self.id, "_id");

        Json::Object(d)
    }
}

<%= structs['MoreLikeThisQuery'] %>
<%= to_json_impl('MoreLikeThisQuery') %>

<%= structs['NestedQuery'] %>
<%= to_json_impl('NestedQuery') %>

<%= structs['PrefixQuery'] %>

#[derive(Debug)]
pub enum Rewrite {
    ConstantScoreAuto,
    ScoringBoolean,
    ConstantScoreBoolean,
    ConstantScoreFilter,
    TopTerms(i64),
    TopTermsBoost(i64)
}

impl ToJson for Rewrite {
    fn to_json(&self) -> Json {
        match self {
            &Rewrite::ConstantScoreAuto    => "constant_score_auto".to_json(),
            &Rewrite::ScoringBoolean       => "scoring_boolean".to_json(),
            &Rewrite::ConstantScoreBoolean => "constant_score_boolean".to_json(),
            &Rewrite::ConstantScoreFilter  => "constant_score_filter".to_json(),
            &Rewrite::TopTerms(n)          => format!("top_terms_{}", n).to_json(),
            &Rewrite::TopTermsBoost(n)     => format!("top_terms_boost_{}", n).to_json()
        }
    }
}

<%= to_json_inner_impl('PrefixQuery') %>

<%= structs['QueryStringQuery'] %>
<%= to_json_impl('QueryStringQuery') %>

<%= structs['SimpleQueryStringQuery'] %>
<%= to_json_impl('SimpleQueryStringQuery') %>

<%= structs['RangeQuery'] %>
<%= to_json_inner_impl('RangeQuery') %>

<%= structs['RegexpQuery'] %>

#[derive(Debug)]
pub enum Flag {
    All,
    AnyString,
    Complement,
    Intersection,
    Interval,
    None
}

impl ToString for Flag {
    fn to_string(&self) -> String {
        match self {
            &Flag::All => "ALL",
            &Flag::AnyString => "ANYSTRING",
            &Flag::Complement => "COMPLEMENT",
            &Flag::Intersection => "INTERSECTION",
            &Flag::Interval => "INTERVAL",
            &Flag::None => "NONE"
        }.to_owned()
    }
}

#[derive(Debug)]
pub struct Flags {
    flags: Vec<Flag>
}

impl Flags {
    pub fn new() -> Flags {
        Flags {
            flags: vec![]
        }
    }

    pub fn add_flag(mut self, flag: Flag) -> Self {
        self.flags.push(flag);
        self
    }
}

impl ToJson for Flags {
    fn to_json(&self) -> Json {
        Json::String(self.flags.iter().map(|f| f.to_string()).join("|"))
    }
}

<%= to_json_inner_impl('RegexpQuery') %>

<%= structs['SpanFirstQuery'] %>
<%= to_json_impl('SpanFirstQuery') %>

<%= structs['SpanMultiQuery'] %>
<%= to_json_impl('SpanMultiQuery') %>

<%= structs['SpanNearQuery'] %>
<%= to_json_impl('SpanNearQuery') %>

<%= structs['SpanNotQuery'] %>
<%= to_json_impl('SpanNotQuery') %>

<%= structs['SpanOrQuery'] %>
<%= to_json_impl('SpanOrQuery') %>

<%= structs['SpanTermQuery'] %>
<%= to_json_inner_impl('SpanTermQuery') %>

<%= structs['TermQuery'] %>
<%= to_json_inner_impl('TermQuery') %>

<%= structs['TermsQuery'] %>

impl ToJson for TermsQuery {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.values.to_json());

        Json::Object(d)
    }
}

<%= structs['WildcardQuery'] %>
<%= to_json_inner_impl('WildcardQuery') %>

// Filters

<%= structs['AndFilter'] %>
<%= to_json_impl('AndFilter') %>

<%= structs['BoolFilter'] %>
<%= to_json_impl('BoolFilter') %>

<%= structs['ExistsFilter'] %>
<%= to_json_impl('ExistsFilter') %>

<%= structs['GeoBoundingBoxFilter'] %>

impl ToJson for GeoBoundingBoxFilter {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.geo_box.to_json());
        self.add_optionals(&mut d);
        self.add_core_optionals(&mut d);
        Json::Object(d)
    }
}

<%= structs['GeoDistanceFilter'] %>

#[derive(Debug)]
pub struct Distance {
    amt: f64,
    unit: DistanceUnit
}

impl Distance {
    pub fn new(amt: f64, unit: DistanceUnit) -> Distance {
        Distance {
            amt:  amt,
            unit: unit
        }
    }
}

impl ToJson for Distance {
    fn to_json(&self) -> Json {
        Json::String(format!("{}{}", self.amt, self.unit.to_string()))
    }
}

<%= simple_value_enum('OptimizeBbox', ['memory', 'indexed', 'none']) %>

impl ToJson for GeoDistanceFilter {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.location.to_json());
        d.insert("distance".to_owned(), self.distance.to_json());
        self.add_optionals(&mut d);
        self.add_core_optionals(&mut d);
        Json::Object(d)
    }
}

<%= structs['GeoPolygonFilter'] %>
<%= to_json_inner_impl('GeoPolygonFilter') %>

<%= structs['GeoShapeFilter'] %>
<%= to_json_inner_impl('GeoShapeFilter') %>

<%= structs['GeohashCellFilter'] %>

#[derive(Debug)]
pub enum Precision {
    Geohash(u64),
    Distance(Distance)
}

from!(u64, Precision, Geohash);
from!(Distance, Precision, Distance);

impl ToJson for Precision {
    fn to_json(&self) -> Json {
        match self {
            &Precision::Geohash(geohash_precision) => Json::U64(geohash_precision),
            &Precision::Distance(ref distance)     => distance.to_json()
        }
    }
}

impl ToJson for GeohashCellFilter {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.location.to_json());
        self.add_optionals(&mut d);
        self.add_core_optionals(&mut d);
        Json::Object(d)
    }
}

<%= structs['HasChildFilter'] %>
<%= to_json_impl('HasChildFilter') %>

<%= structs['HasParentFilter'] %>
<%= to_json_impl('HasParentFilter') %>

<%= structs['IdsFilter'] %>
<%= to_json_impl('IdsFilter') %>

<%= structs['IndicesFilter'] %>

#[derive(Debug)]
pub enum NoMatchFilter {
    None,
    All,
    Filter(Box<Filter>)
}

from_exp!(Filter, NoMatchFilter, from, NoMatchFilter::Filter(Box::new(from)));

impl ToJson for NoMatchFilter {
    fn to_json(&self) -> Json {
        match self {
            &NoMatchFilter::None               => "none".to_json(),
            &NoMatchFilter::All                => "all".to_json(),
            &NoMatchFilter::Filter(ref filter) => filter.to_json()
        }
    }
}

<%= to_json_impl('IndicesFilter') %>

<%= structs['MatchAllFilter'] %>
<%= to_json_impl('MatchAllFilter') %>

<%= structs['MissingFilter'] %>
<%= to_json_impl('MissingFilter') %>

<%= structs['NestedFilter'] %>
<%= to_json_impl('NestedFilter') %>

<%= structs['NotFilter'] %>
<%= to_json_impl('NotFilter') %>

<%= structs['OrFilter'] %>
<%= to_json_impl('OrFilter') %>

<%= structs['PrefixFilter'] %>

impl ToJson for PrefixFilter {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.value.to_json());
        self.add_optionals(&mut d);
        self.add_core_optionals(&mut d);
        Json::Object(d)
    }
}

<%= structs['QueryFilter'] %>
<%= to_json_impl('QueryFilter') %>

<%= structs['RangeFilter'] %>
<%= to_json_inner_impl('RangeFilter') %>

<%= structs['RegexpFilter'] %>
<%= to_json_inner_impl('RegexpFilter') %>

<%= structs['ScriptFilter'] %>
<%= to_json_impl('ScriptFilter') %>

<%= structs['TermFilter'] %>

impl ToJson for TermFilter {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.value.to_json());
        self.add_optionals(&mut d);
        self.add_core_optionals(&mut d);
        Json::Object(d)
    }
}

<%= structs['TermsFilter'] %>

<%= simple_value_enum('Execution', ['plain',
                                    'fielddata',
                                    'bool',
                                    'bool_nocache',
                                    'and',
                                    'and_nocache',
                                    'or',
                                    'or_nocache']) %>

impl ToJson for TermsFilter {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        d.insert(self.field.clone(), self.values.to_json());
        self.add_optionals(&mut d);
        self.add_core_optionals(&mut d);
        Json::Object(d)
    }
}

<%= structs['TypeFilter'] %>
<%= to_json_impl('TypeFilter') %>

// Functions for use in `FunctionScoreQuery`

<%= function_enum('Func', ['script_score',
                           'random_score',
                           'field_value_factor',
                           'linear',
                           'exp',
                           'gauss']) %>

<%= structs['ScriptScoreFunc'] %>
<%= to_json_impl('ScriptScoreFunc') %>

<%= structs['RandomScoreFunc'] %>
<%= to_json_impl('RandomScoreFunc') %>

<%= structs['FieldValueFactorFunc'] %>

<%= simple_value_enum('Modifier', ['none',
                                   'log',
                                   'log1p',
                                   'log2p',
                                   'ln',
                                   'ln1p',
                                   'ln2p',
                                   'square',
                                   'sqrt',
                                   'reciprocal']) %>

<%= to_json_impl('FieldValueFactorFunc') %>

<%= structs['LinearFunc'] %>

#[derive(Debug)]
enum Scale {
    I64(i64),
    U64(u64),
    F64(f64),
    Distance(Distance),
    Duration(Duration)
}

from!(i64, Scale, I64);
from!(u64, Scale, U64);
from!(f64, Scale, F64);
from!(Distance, Scale, Distance);
from!(Duration, Scale, Duration);

impl ToJson for Scale {
    fn to_json(&self) -> Json {
        match self {
            &Scale::I64(s)          => Json::I64(s),
            &Scale::U64(s)          => Json::U64(s),
            &Scale::F64(s)          => Json::F64(s),
            &Scale::Distance(ref s) => s.to_json(),
            &Scale::Duration(ref s) => s.to_json()
        }
    }
}

#[derive(Debug)]
enum Origin {
    I64(i64),
    U64(u64),
    F64(f64),
    Location(Location),
    Date(String)
}

from!(i64, Origin, I64);
from!(u64, Origin, U64);
from!(f64, Origin, F64);
from!(Location, Origin, Location);
from!(String, Origin, Date);

impl ToJson for Origin {
    fn to_json(&self) -> Json {
        match self {
            &Origin::I64(orig)          => Json::I64(orig),
            &Origin::U64(orig)          => Json::U64(orig),
            &Origin::F64(orig)          => Json::F64(orig),
            &Origin::Location(ref orig) => orig.to_json(),
            &Origin::Date(ref orig)     => Json::String(orig.clone())
        }
    }
}

macro_rules! decay_func_json_impl {
    ($df:ident) => {
        impl ToJson for $df {
            fn to_json(&self) -> Json {
                let mut d = BTreeMap::new();
                let mut inner = BTreeMap::new();
                inner.insert("origin".to_owned(), self.origin.to_json());
                optional_add!(inner, self.scale, "scale");
                optional_add!(inner, self.decay, "decay");
                optional_add!(inner, self.offset, "offset");
                d.insert(self.field.clone(), Json::Object(inner));
                optional_add!(d, self.multi_value_mode, "multi_value_mode");
                Json::Object(d)
            }
        }
    }
}

<%= simple_value_enum('MultiValueMode', ['min', 'max', 'avg', 'sum']) %>

decay_func_json_impl!(LinearFunc);

<%= structs['ExpFunc'] %>
decay_func_json_impl!(ExpFunc);

<%= structs['GaussFunc'] %>
decay_func_json_impl!(GaussFunc);

#[derive(Debug)]
pub struct Function {
    filter: Option<Filter>,
    function: Func,
    weight: Option<f64>
}

impl Function {
    pub fn new(function: Func) -> Function {
        Function {
            filter:   None,
            function: function,
            weight:   None
        }
    }

    pub fn with_filter(mut self, filter: Filter) -> Function {
        self.filter = Some(filter);
        self
    }

    pub fn with_weight(mut self, weight: f64) -> Function {
        self.weight = Some(weight);
        self
    }
}

impl ToJson for Function {
    fn to_json(&self) -> Json {
        let mut d = BTreeMap::new();
        optional_add!(d, self.filter, "filter");
        optional_add!(d, self.weight, "weight");
        d.insert(self.function.name(), self.function.to_json());
        Json::Object(d)
    }
}
