# This file is part of LeanRC.
#
# LeanRC is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# LeanRC is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with LeanRC.  If not, see <https://www.gnu.org/licenses/>.

###
в Ember app это может выглядить так.
```coffee
cucumber = {id: 123}
@store.query 'user',
  $and: [
    '@doc.cucumberId': {$eq: cucumber.id}
    '@doc.price': {$gt: 85}
  ]
```
###

# надо определить символ, который будет говорить об использовании объявленной переменной (ссылки), чтобы в случае когда должна ретюрниться простая строка, в объявлении просто не было символа. можно заиспользовать '@'

###
example

```coffee
new Query()
  .forIn '@doc': 'users'
  .forIn '@tomato': 'tomatos'
  .let
    '@user_cucumbers':
      $forIn: '@cucumber': 'cucumbers'
      $filter:
        $and: [
          '@doc.cucumberId': {$eq: '@cucumber._key'}
          '@cucumber.price': {$gt: 85}
        ]
      $return: '@cucumber'
  .join
    '@doc.tomatoId': {$eq: '@tomato._key'}
    '@tomato.active': {$eq: yes}
  .filter '@doc.active': {$eq: yes}
  .sort '@doc.firstName': 'DESC'
  .limit 10
  .offset 10
  .return {user: '@user', cucumbers: '@user_cucumbers', tomato: '@tomato'}
```

But it equvalent json
```
{
  $forIn: '@doc': 'users', '@tomato': 'tomatos'
  $let:
    '@user_cucumbers':
      $forIn: '@cucumber': 'cucumbers'
      $filter:
        $and: [
          '@doc.cucumberId': {$eq: '@cucumber._key'}
          '@cucumber.price': {$gt: 85}
        ]
       $return: '@cucumber'
  $join: $and: [
    '@doc.tomatoId': {$eq: '@tomato._key'}
    '@tomato.active': {$eq: yes}
  ]
  $filter: '@doc.active': {$eq: yes}
  $sort: ['@doc.firstName': 'DESC']
  $limit: 10
  $offset: 10
  $return: {user: '@user', cucumbers: '@user_cucumbers', tomato: '@tomato'}
}
```

example for collect
```
{
  $forIn: '@doc': 'users'
  $filter: '@doc.active': {$eq: yes}
  $collect: '@country': '@doc.country', '@city': '@doc.city'
  $into: '@groups': {name: '@doc.name', isActive: '@doc.active'}
  $having: '@country': {$nin: ['Australia', 'Ukraine']}
  $return: {country: '@country', city: '@city', usersInCity: '@groups'}
}

# or

{
  $forIn: '@doc': 'users'
  $filter: '@doc.active': {$eq: yes}
  $collect: '@ageGroup': 'FLOOR(@doc.age / 5) * 5'
  $aggregate: '@minAge': 'MIN(@doc.age)', '@maxAge': 'MAX(@doc.age)'
  $return: {ageGroup: '@ageGroup', minAge: '@minAge', maxAge: '@maxAge'}
}
```
###

module.exports = (Module)->
  {
    AnyT
    FuncG, SubsetG, UnionG, MaybeG
    QueryInterface
    CoreObject
    Utils: { _ }
  } = Module::

  class Query extends CoreObject
    @inheritProtected()
    @implements QueryInterface
    @module Module

    # TODO: это надо переделать на нормальную проверку типа в $filter
    @public @static operatorsMap: Object,
      default:
        $and: Array
        $or: Array
        $not: Object
        $nor: Array # not or # !(a||b) === !a && !b

        # без вложенных условий и операторов - value конечное значение для сравнения
        $eq: AnyT # ==
        $ne: AnyT # !=
        $lt: AnyT # <
        $lte: AnyT # <=
        $gt: AnyT # >
        $gte: AnyT # >=

        $in: Array # check value present in array
        $nin: Array # ... not present in array

        # field has array of values
        $all: Array # contains some values
        $elemMatch: Object # conditions for complex item
        $size: Number # condition for array length

        $exists: Boolean # condition for check present some value in field
        $type: String # check value type

        $mod: Array # [divisor, remainder] for example [4,0] делится ли на 4
        $regex: UnionG RegExp, String # value must be string. ckeck it by RegExp.

        $td: Boolean # this day (today)
        $ld: Boolean # last day (yesterday)
        $tw: Boolean # this week
        $lw: Boolean # last week
        $tm: Boolean # this month
        $lm: Boolean # last month
        $ty: Boolean # this year
        $ly: Boolean # last year

    @public $forIn: MaybeG Object
    @public $join: MaybeG Object
    @public $let: MaybeG Object
    @public $filter: MaybeG Object
    @public $collect: MaybeG Object
    @public $into: MaybeG UnionG String, Object
    @public $having: MaybeG Object
    @public $sort: MaybeG Array
    @public $limit: MaybeG Number
    @public $offset: MaybeG Number
    @public $avg: MaybeG String # '@doc.price'
    @public $sum: MaybeG String # '@doc.price'
    @public $min: MaybeG String # '@doc.price'
    @public $max: MaybeG String # '@doc.price'
    @public $count: MaybeG Boolean # yes or not present
    @public $distinct: MaybeG Boolean # yes or not present
    @public $remove: MaybeG UnionG String, Object
    @public $patch: MaybeG Object
    @public $return: MaybeG UnionG String, Object

    @public forIn: FuncG(Object, QueryInterface),
      default: (aoDefinitions)->
        for own k, v of aoDefinitions
          @$forIn[k] = v
        return @
    @public join: FuncG(Object, QueryInterface), # критерии связывания как в SQL JOIN ... ON
      default: (aoDefinitions)->
        @$join = aoDefinitions
        return @
    @public filter: FuncG(Object, QueryInterface),
      default: (aoDefinitions)->
        @$filter = aoDefinitions
        return @
    @public let: FuncG(Object, QueryInterface),
      default: (aoDefinitions)->
        @$let ?= {}
        for own k, v of aoDefinitions
          @$let[k] = v
        return @
    @public collect: FuncG(Object, QueryInterface),
      default: (aoDefinition)->
        @$collect = aoDefinition
        return @
    @public into: FuncG([UnionG String, Object], QueryInterface),
      default: (aoDefinition)->
        @$into = aoDefinition
        return @
    @public having: FuncG(Object, QueryInterface),
      default: (aoDefinition)->
        @$having = aoDefinition
        return @
    @public sort: FuncG(Object, QueryInterface),
      default: (aoDefinition)->
        @$sort ?= []
        @$sort.push aoDefinition
        return @
    @public limit: FuncG(Number, QueryInterface),
      default: (anValue)->
        @$limit = anValue
        return @
    @public offset: FuncG(Number, QueryInterface),
      default: (anValue)->
        @$offset = anValue
        return @
    @public distinct: FuncG([], QueryInterface),
      default: ->
        @$distinct = yes
        return @
    @public remove: FuncG([UnionG String, Object], QueryInterface),
      default: (expr = yes)->
        @$remove = expr
        return @
    @public patch: FuncG(Object, QueryInterface),
      default: (aoDefinition)->
        @$patch = aoDefinition
        return @
    @public return: FuncG([UnionG String, Object], QueryInterface),
      default: (aoDefinition)->
        @$return = aoDefinition
        return @
    @public count: FuncG([], QueryInterface),
      default: ->
        @$count = yes
        return @
    @public avg: FuncG(String, QueryInterface),
      default: (asDefinition)->
        @$avg = asDefinition
        return @
    @public min: FuncG(String, QueryInterface),
      default: (asDefinition)->
        @$min = asDefinition
        return @
    @public max: FuncG(String, QueryInterface),
      default: (asDefinition)->
        @$max = asDefinition
        return @
    @public sum: FuncG(String, QueryInterface),
      default: (asDefinition)->
        @$sum = asDefinition
        return @

    @public @static @async restoreObject: FuncG([SubsetG(Module), Object], QueryInterface),
      default: (_Module, replica)->
        if replica?.class is @name and replica?.type is 'instance'
          instance = @new replica.query
          yield return instance
        else
          return yield @super _Module, replica

    @public @static @async replicateObject: FuncG(QueryInterface, Object),
      default: (instance)->
        replica = yield @super instance
        replica.query = instance.toJSON()
        yield return replica

    @public init: FuncG([MaybeG Object]),
      default: (aoQuery)->
        @super arguments...
        @$forIn = {}
        if aoQuery?
          for own key, value of aoQuery
            do (key, value)=>
              @[key] = value
        return

    @public toJSON: FuncG([], Object),
      default: ->
        res = {}
        for k in [
          '$forIn', '$join', '$let', '$filter', '$collect', '$into', '$having'
          '$sort', '$limit', '$offset', '$avg', '$sum', '$min', '$max', '$count'
          '$distinct', '$remove', '$patch', '$return'
        ] when @[k]?
          res[k] = @[k]
        return res


    @initialize()
