"""
### Count Operation
Slicing corresponds to the `SELECT COUNT(*)` part of an SQL query.

Simply, return the number of items, without returning the items themselves. Just a number. That's it.

Example:

```javascript
$.get('/api/user?query=' + JSON.stringify({
    count: 1,
}))
```

The `1` is the *on* switch. Replace it with `0` to stop counting.

NOTE: In MongoSQL 2.0, there is a way to get both the list of items, *and* their count *simultaneously*.
This would have way better performance than two separate queries.
Please have a look: [CountingQuery](#countingqueryquery) and [MongoQuery.end_count()](#mongoqueryend_count---countingquery).
"""

from sqlalchemy import func
from sqlalchemy import exc as sa_exc

from .base import MongoQueryHandlerBase
from ..exc import InvalidQueryError, InvalidColumnError, InvalidRelationError


class MongoCount(MongoQueryHandlerBase):
    """ MongoDB count query

        Just give it:
        * count=True
    """

    query_object_section_name = 'count'

    def __init__(self, model, bags):
        """ Init a count

        :param model: Sqlalchemy model to work with
        :param bags: Model bags
        """
        super(MongoCount, self).__init__(model, bags)

        # On input
        self.count = None

    def input_prepare_query_object(self, query_object):
        # When we count, we don't care about certain things
        if query_object.get('count', False):
            # Performance: do not sort when counting
            query_object.pop('sort', None)
            # We don't care about projections either
            query_object.pop('project', None)
            # Also, remove all skips & limits
            query_object.pop('skip', None)
            query_object.pop('limit', None)
            # Remove all join, but not joinf (as it may filter)
            query_object.pop('join', None)
            # Finally, when we count, we have to remove `max_items` setting from MongoLimit.
            # Only MongoLimit can do it, and it will do it for us.
            # See: MongoLimit.input_prepare_query_object

        return query_object

    def input(self, count=None):
        super(MongoCount, self).input(count)
        if not isinstance(count, (int, bool, NoneType)):
            raise InvalidQueryError('Count must be either true or false. Or at least a 1, or a 0')

        # Done
        self.count = count
        return self

    def _get_supported_bags(self):
        return None  # not used by this class

    # Not Implemented for this Query Object handler
    compile_columns = NotImplemented
    compile_options = NotImplemented
    compile_statement = NotImplemented
    compile_statements = NotImplemented

    def alter_query(self, query, as_relation=None):
        """ Apply offset() and limit() to the query """
        if self.count:
            # Previously, we used to do counts like this:
            # >>> query = query.with_entities(func.count())
            # However, when there's no WHERE clause set on a Query, it's left without any reference to the target table.
            # In this case, SqlAlchemy will actually generate a query without a FROM clause, which gives a wrong count!
            # Therefore, we have to make sure that there will always be a FROM clause.
            #
            # Normally, we just do the following:
            # >>> query = query.select_from(self.model)
            # This is supposed to indicate which table to select from.
            # However, it can only be applied when there's no FROM nor ORDER BY clauses present.
            #
            # But wait a second... didn't we just assume that there would be no FROM clause?
            # Have a look at this ugly duckling:
            # >>> Query(User).filter_by().select_from(User)
            # This filter_by() would actually create an EMPTY condition, which will break select_from()'s assertions!
            # This is reported to SqlAlchemy:
            # https://github.com/sqlalchemy/sqlalchemy/issues/4606
            # And (is fixed in version x.x.x | is not going to be fixed)
            #
            # Therefore, we'll try to do it the nice way ; and if it fails, we'll have to do something else.
            try:
                query = query.with_entities(func.count()).select_from(self.model)
            except sa_exc.InvalidRequestError:
                query = query.from_self(func.count())

        return query


NoneType = type(None)