'use strict'

/** @module queryBuilder */

var asArray = require('./utils.js').asArray

/** @member {module:extensions} ext */

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query structured query}
 * from a set of sub-queries
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#where
 *
 * @static
 * @param {...Object} queries - sub queries
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query structured query}
 */
function where () {
  var args = asArray.apply(null, arguments)
  return {
    'query': {
      'queries': args
    }
  }
}

/**
 * Builds an {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_83674 `and-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#and
 *
 * @static
 * @param {...Object} queries - sub queries
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_64259 or-query}
 */
function and () {
  var args = asArray.apply(null, arguments)
  return {
    'and-query': {
      'queries': args
    }
  }
}

/**
 * Builds an {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_64259 `or-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#or
 *
 * @static
 * @param {...Object} queries - sub queries
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_64259 or-query}
 */
function or () {
  var args = asArray.apply(null, arguments)
  return {
    'or-query': {
      'queries': args
    }
  }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_39488 `not-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#not
 *
 * @static
 * @param {Object} query - sub query to be negated
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_39488 not-query}
 */
function not (query) {
  return {
    'not-query': query
  }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_30556 `document-fragment-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#documentFragment
 *
 * @static
 * @param {Object} query - sub query to be constrained to document fragments
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_30556 document-fragment-query}
 */
function documentFragment (query) {
  return { 'document-fragment-query': query }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_67222 `properties-fragment-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#propertiesFragment
 *
 * @static
 * @param {Object} query - sub query to be constrained to properties fragments
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_67222 properties-fragment-query}
 */
function propertiesFragment (query) {
  return { 'properties-fragment-query': query }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_53441 `locks-fragment-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#locksFragment
 *
 * @static
 * @param {Object} query - sub query to be constrained to document locks
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_53441 locks-fragment-query}
 */
function locksFragment (query) {
  return { 'locks-fragment-query': query }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_76890 `collection-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#collection
 *
 * @static
 * @param {...String|Array<String>} uris - the collection URIs to query (logical OR)
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_76890 collection-query}
 */
function collection () {
  var args = asArray.apply(null, arguments)
  return {
    'collection-query': {
      'uri': args
    }
  }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_94821 `directory-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#directory
 *
 * @static
 * @param {...String|Array<String>} uris - the directory URIs to query (logical OR)
 * @param {Boolean} [infinite] - whether to query into all sub-directories (defaults to `true`)
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_94821 directory-query}
 */
function directory () {
  var args = asArray.apply(null, arguments)
  var last = args[args.length - 1]
  var infinite = true

  if (last === true || last === false) {
    infinite = last
    args.pop()
  }

  // horrible hack to support an array of URIs
  if (args.length === 1 && Array.isArray(args[0])) {
    args = args[0]
  }

  return {
    'directory-query': {
      'uri': args,
      'infinite': infinite
    }
  }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_27172 `document-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#document
 *
 * @static
 * @param {...String} uris - document URIs to match
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_27172 document-query}
 */
function document () {
  var args = asArray.apply(null, arguments)
  return {
    'document-query': {
      'uri': args
    }
  }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_25949 `boost-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#boost
 *
 * @static
 * @param {Object} matching - matching query
 * @param {Object} boosting - boosting query
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_25949 boost-query}
 */
function boost (matching, boosting) {
  return {
    'boost-query': {
      'matching-query': matching,
      'boosting-query': boosting
    }
  }
}

/**
 *
 * @static
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#qname
 */
function qname (ns, name) {
  var args = asArray.apply(null, arguments)

  if (args.length === 1) {
    return { ns: null, name: args[0] }
  } else {
    return { ns: args[0], name: args[1] }
  }
}

/**
 *
 * @static
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#element
 */
function element () {
  var args = asArray.apply(null, arguments)
  var qname

  if (args.length === 1) {
    if (args[0].ns || args[0].name) {
      qname = args[0]
    } else {
      qname = this.qname(args[0])
    }
  } else {
    qname = this.qname(args[0], args[1])
  }

  return { element: qname }
}

/**
 *
 * @static
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#datatype
 */
function datatype (type, collation) {
  var datatypes = [
    'anyURI',
    'date',
    'dateTime',
    'dayTimeDuration',
    'decimal',
    'double',
    'float',
    'gDay',
    'gMonth',
    'gMonthDay',
    'gYear',
    'gYearMonth',
    'int',
    'long',
    'string',
    'time',
    'unsignedInt',
    'unsignedLong',
    'yearMonthDuration'
  ]

  if (datatypes.indexOf(type) > -1) {
    type = 'xs:' + type
  } else {
    throw new TypeError('Unknown datatype: ' + type)
  }

  return {
    datatype: type,
    collation: collation
  }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_83393 `range-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#range
 *
 * @static
 * @param {Object|String|Array<String>} indexedName - the name of a range index
 * @param {Object} [datatype] - the type/collation of the range index (as returned by {@link MLQueryBuilder#datatype})
 * @param {String} [operator] - the query operator
 * @param {...*} [value] - the values to compare
 * @param {Object} [rangeOptions] - the range query options (as returned by {@link MLQueryBuilder#rangeOptions})
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_83393 range-query}
 */
function range () {
  var comparisons = {
    '<': 'LT',
    '<=': 'LE',
    '>': 'GT',
    '>=': 'GE',
    '=': 'EQ',
    '!=': 'NE'
  }

  var args = asArray.apply(null, arguments)

  var indexedName = args.shift()

  // TODO: attribute/field/path
  if (!indexedName.element) {
    indexedName = { 'json-property': indexedName }
  }

  var datatype = args.shift()
  var operator = null
  var values = []

  if (datatype && datatype.datatype) {
    datatype = { type: datatype.datatype, collation: datatype.collation }
    operator = args.shift()
  } else {
    operator = datatype
    datatype = null
  }

  if (!comparisons[ operator ]) {
    Array.prototype.push.apply(values, asArray(operator))
    operator = null
  }

  var options = []

  args.forEach(function (arg) {
    if (arg['range-option']) {
      Array.prototype.push.apply(options, asArray(arg['range-option']))
    } else {
      Array.prototype.push.apply(values, asArray(arg))
    }
  })

  var query = {
    'range-query': {
      'range-operator': comparisons[ operator ] || 'EQ',
      'value': values,
      'range-option': options
    }
  }

  Object.assign(query['range-query'], indexedName, datatype)

  return query
}

/**
 *
 * @static
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#rangeOptions
 */
function rangeOptions (ns, name) {
  var args = asArray.apply(null, arguments)

  return { 'range-option': args }
}

/**
 * Builds a {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_56027 `term-query`}
 * @see http://docs.marklogic.com/jsdoc/queryBuilder.html#term
 *
 * @static
 * @param {...String} terms - terms to match (logical OR)
 * @return {Object} {@link http://docs.marklogic.com/guide/search-dev/structured-query#id_56027 term-query}
 */
function term () {
  var args = asArray.apply(null, arguments)
  return {
    'term-query': {
      'text': args
    }
  }
}

module.exports = {
  where: where,
  and: and,
  or: or,
  not: not,
  documentFragment: documentFragment,
  propertiesFragment: propertiesFragment,
  locksFragment: locksFragment,
  collection: collection,
  directory: directory,
  document: document,
  boost: boost,
  qname: qname,
  element: element,
  datatype: datatype,
  range: range,
  rangeOptions: rangeOptions,
  term: term
}