def elasticsearch(serializer, catalog):
    """
    https://www.elastic.co/guide/en/elasticsearch/reference/current/_the_search_api.html
    :param serializer:
    :return:
    """

    search_engine_endpoint = "{0}/{1}/_search".format(SEARCH_URL, catalog.slug)

    q_text = serializer.validated_data.get("q_text")
    q_time = serializer.validated_data.get("q_time")
    q_geo = serializer.validated_data.get("q_geo")
    q_user = serializer.validated_data.get("q_user")
    d_docs_sort = serializer.validated_data.get("d_docs_sort")
    d_docs_limit = int(serializer.validated_data.get("d_docs_limit"))
    d_docs_page = int(serializer.validated_data.get("d_docs_page"))
    a_text_limit = serializer.validated_data.get("a_text_limit")
    a_user_limit = serializer.validated_data.get("a_user_limit")
    a_time_gap = serializer.validated_data.get("a_time_gap")
    a_time_limit = serializer.validated_data.get("a_time_limit")
    original_response = serializer.validated_data.get("original_response")

    # Dict for search on Elastic engine
    must_array = []
    filter_dic = {}
    aggs_dic = {}

    # get ES version to make the query builder to be backward compatible with
    # diffs versions.
    # TODO: move this to a proper place. maybe ES client?.
    # TODO: cache it to avoid overwhelm ES with this call.
    # TODO: ask for ES_VERSION when building queries with an elegant way.
    ES_VERSION = 2
    response = requests.get(SEARCH_URL)
    if response.ok:
        # looks ugly but will work on normal ES response for "/".
        ES_VERSION = int(response.json()["version"]["number"][0])

    # String searching
    if q_text:
        # Wrapping query string into a query filter.

        if ES_VERSION >= 2:
            query_string = {
                "query_string": {
                    "query": q_text
                }
            }
        else:
            query_string = {
                "query": {
                    "query_string": {
                        "query": q_text
                    }
                }
            }

        # add string searching
        must_array.append(query_string)

    if q_time:
        # check if q_time exists
        q_time = str(q_time)  # check string
        shortener = q_time[1:-1]
        shortener = shortener.split(" TO ")
        gte = shortener[0]  # greater than
        lte = shortener[1]  # less than
        layer_date = {}
        if gte == '*' and lte != '*':
            layer_date["lte"] = lte
            range_time = {
                "layer_date": layer_date
            }
            range_time = {"range": range_time}
            must_array.append(range_time)
        if gte != '*' and lte == '*':
            layer_date["gte"] = gte
            range_time = {
                "layer_date": layer_date
            }
            range_time = {"range": range_time}
            must_array.append(range_time)
        if gte != '*' and lte != '*':
            layer_date["gte"] = gte
            layer_date["lte"] = lte
            range_time = {
                "layer_date": layer_date
            }
            range_time = {"range": range_time}
            must_array.append(range_time)
    # geo_shape searching
    if q_geo:
        q_geo = str(q_geo)
        q_geo = q_geo[1:-1]
        Ymin, Xmin = q_geo.split(" TO ")[0].split(",")
        Ymax, Xmax = q_geo.split(" TO ")[1].split(",")
        geoshape_query = {
            "layer_geoshape": {
                "shape": {
                    "type": "envelope",
                    "coordinates": [[Xmin, Ymax], [Xmax, Ymin]]
                },
                "relation": "intersects"
            }
        }
        filter_dic["geo_shape"] = geoshape_query

    if q_user:
        # Using q_user
        user_searching = {
            "match": {
                "layer_originator": q_user
            }
        }
        must_array.append(user_searching)

    if ES_VERSION >= 2:
        dic_query = {
            "query": {
                "bool": {
                    "must": must_array,
                    "filter": filter_dic
                }
            }
        }
    else:
        dic_query = {
            "query": {
                "filtered": {
                    "filter": {
                        "bool": {
                            "must": must_array,
                            "should": filter_dic
                        }
                    }
                }
            }
        }

    # Page
    if d_docs_limit:
        dic_query["size"] = d_docs_limit

    if d_docs_page:
        dic_query["from"] = d_docs_limit * d_docs_page - d_docs_limit

    if d_docs_sort == "score":
        dic_query["sort"] = {"_score": {"order": "desc"}}

    if d_docs_sort == "time":
        dic_query["sort"] = {"layer_date": {"order": "desc"}}

    if d_docs_sort == "distance":
        if q_geo:
            # distance_x = float(((float(Xmin) - float(Xmax)) ** 2.0) ** (0.5))
            # distance_y = float(((float(Ymin) - float(Ymax)) ** 2.0) ** (0.5))
            msg = ("Sorting by distance is different on ElasticSearch than Solr, because this"
                   "feature on elastic is unavailable to geo_shape type.ElasticSearch docs said:"
                   "Due to the complex input structure and index representation of shapes,"
                   "it is not currently possible to sort shapes or retrieve their fields directly."
                   "The geo_shape value is only retrievable through the _source field."
                   " Link: https://www.elastic.co/guide/en/elasticsearch/reference/current/geo-shape.html")
            return {"error": {"msg": msg}}

        else:
            msg = "q_qeo MUST BE NO ZERO if you wanna sort by distance"
            return {"error": {"msg": msg}}

    if a_text_limit:
        # getting most frequently occurring users.
        text_limit = {
            "terms": {
                "field": "abstract",
                "size": a_text_limit
            }
        }
        aggs_dic['popular_text'] = text_limit

    if a_user_limit:
        # getting most frequently occurring users.
        users_limit = {

            "terms": {
                "field": "layer_originator",
                "size": a_user_limit
            }
        }
        aggs_dic['popular_users'] = users_limit

    if a_time_limit:
        # TODO: Work in progress, a_time_limit is incomplete.
        # TODO: when times are * it does not work. also a a_time_gap is not required.
        if q_time:
            if not a_time_gap:
                # getting time limit histogram.
                time_limt = {
                    "date_range": {
                        "field": "layer_date",
                        "format": "yyyy-MM-dd'T'HH:mm:ssZ",
                        "ranges": [
                            {"from": gte, "to": lte}
                        ]
                    }
                }
                aggs_dic['range'] = time_limt
            else:
                pass

        else:
            msg = "If you want to use a_time_limit feature, q_time MUST BE initialized"
            return {"error": {"msg": msg}}

    if a_time_gap:
        interval = gap_to_elastic(a_time_gap)
        time_gap = {
            "date_histogram": {
                "field": "layer_date",
                "format": "yyyy-MM-dd'T'HH:mm:ssZ",
                "interval": interval
            }
        }
        aggs_dic['articles_over_time'] = time_gap

    # adding aggreations on body query
    if aggs_dic:
        dic_query['aggs'] = aggs_dic
    try:
        res = requests.post(search_engine_endpoint, data=json.dumps(dic_query))
    except Exception as e:
        return 500, {"error": {"msg": str(e)}}

    es_response = res.json()

    if original_response:
        return es_response

    data = {}

    if 'error' in es_response:
        data["error"] = es_response["error"]
        return 400, data

    data["request_url"] = res.url
    data["request_body"] = json.dumps(dic_query)
    data["a.matchDocs"] = es_response['hits']['total']
    docs = []
    # aggreations response: facets searching
    if 'aggregations' in es_response:
        aggs = es_response['aggregations']
        # getting the most frequently occurring users.
        if 'popular_users' in aggs:
            a_users_list_array = []
            users_resp = aggs["popular_users"]["buckets"]
            for item in users_resp:
                temp = {}
                temp['count'] = item['doc_count']
                temp['value'] = item['key']
                a_users_list_array.append(temp)
            data["a.user"] = a_users_list_array

        # getting most frequently ocurring words
        if 'popular_text' in aggs:
            a_text_list_array = []
            text_resp = es_response["aggregations"]["popular_text"]["buckets"]
            for item in text_resp:
                temp = {}
                temp['count'] = item['doc_count']
                temp['value'] = item['key']
                a_text_list_array.append(temp)
            data["a.text"] = a_text_list_array

        if 'articles_over_time' in aggs:
            gap_count = []
            a_gap = {}
            gap_resp = aggs["articles_over_time"]["buckets"]

            start = "*"
            end = "*"

            if len(gap_resp) > 0:
                start = gap_resp[0]['key_as_string'].replace('+0000', 'z')
                end = gap_resp[-1]['key_as_string'].replace('+0000', 'z')

            a_gap['start'] = start
            a_gap['end'] = end
            a_gap['gap'] = a_time_gap

            for item in gap_resp:
                temp = {}
                if item['doc_count'] != 0:
                    temp['count'] = item['doc_count']
                    temp['value'] = item['key_as_string'].replace('+0000', 'z')
                    gap_count.append(temp)
            a_gap['counts'] = gap_count
            data['a.time'] = a_gap

        if 'range' in aggs:
            # Work in progress
            # Pay attention in the following code lines: Make it better!!!!
            time_count = []
            time_resp = aggs["range"]["buckets"]
            a_time = {}
            a_time['start'] = gte
            a_time['end'] = lte
            a_time['gap'] = None

            for item in time_resp:
                temp = {}
                if item['doc_count'] != 0:
                    temp['count'] = item['doc_count']
                    temp['value'] = item['key'].replace('+0000', 'z')
                    time_count.append(temp)
            a_time['counts'] = time_count
            data['a.time'] = a_time

    if not int(d_docs_limit) == 0:
        for item in es_response['hits']['hits']:
            # data
            temp = item['_source']['abstract']
            temp = temp.replace(u'\u201c', "\"")
            temp = temp.replace(u'\u201d', "\"")
            temp = temp.replace('"', "\"")
            temp = temp.replace("'", "\'")
            temp = temp.replace(u'\u2019', "\'")
            item['_source']['abstract'] = temp
            docs.append(item['_source'])

    data["d.docs"] = docs

    return data