'use strict'

/**
 * @license
 * node-scrapy <https://github.com/eeshi/node-scrapy>
 * Copyright Stefan Maric, Adrian Obelmejias, and other contributors <https://github.com/eeshi/node-scrapy/graphs/contributors>
 * Released under MIT license <https://github.com/eeshi/node-scrapy/blob/master/LICENSE>
 */

// Module dependencies

let cssSelect = require('css-select')
let htmlparser2 = require('htmlparser2')
let getters = require('./getters')
let parseQuery = require('./query-parser')
let utils = {
  isArray: Array.isArray,
  isPlainObject: require('lodash.isplainobject'),
  isString: require('lodash.isstring')
}

// Expose

module.exports = exports = extract

/**
 * Given an `html` string/buffer, extract data as described in the `model`.
 * @static
 * @param  {string}           html    HTML string to parse
 * @param  {Object|string}    model   String or object describing the data to be extracted from the given HTML
 * @return {Object}
 */

function extract (html, model) {
  let dom = htmlparser2.parseDOM(html)
  return getItem(dom, model)
}

/**
 * Given a `dom`, traverse it to get the desired item
 * @param  {Object}                 dom       DOM node
 * @param  {(string|Array|Object)}  item      Data to extract
 * @return {string}                           A string or an array of strings with the extracted data
 */

function getItem (dom, item) {
  let data

  if (utils.isPlainObject(item)) {
    data = {}
    for (let key in item) data[key] = getItem(dom, item[key])
  } else if (utils.isArray(item)) {
    let query = parseQuery(item[0])
    let matches = cssSelect.selectAll(query.selector, dom)

    if (utils.isArray(item[1]) || utils.isPlainObject(item[1])) {
      data = matches.map(context => getItem(context, item[1]))
    } else {
      data = matches.map(resolveGetter(getters, query))
    }
  } else if (utils.isString(item)) {
    let query = parseQuery(item)
    let matches = cssSelect.selectOne(query.selector, dom)

    data = resolveGetter(getters, query)(matches)
  }

  return data
}

/**
 * Given a getters collection and a query, decide what getter function to use
 * @param  {Object}     getters Simple collection of getter functions
 * @param  {Object}     query   Query object, containing a getter property
 * @return {Function}           A getter function
 */

function resolveGetter (getters, query) {
  // Default to $text / $textContent if no getter was provided in query
  if (query.getter === null) return getters.$textContent

  // Use requested getter function if it is part of the getters collection
  if (getters.hasOwnProperty(query.getter)) return getters[query.getter]

  // Simply return node attribute if a getter was requested in query
  // but doesn't exist in the getters collection
  return el => el.attribs[query.getter] || null
}

@@ -365,27 +365,23 @@
 t')%0Alet 
-htmlparser2
+filters
  = requi
@@ -384,27 +384,25 @@
 equire('
-htmlparser2
+./filters
 ')%0Alet g
@@ -427,24 +427,65 @@
 ./getters')%0A
+let htmlparser2 = require('htmlparser2')%0A
 let parseQue
@@ -1810,24 +1810,33 @@
 ta = matches
+%0A        
 .map(resolve
@@ -1859,16 +1859,70 @@
 query))%0A
+        .map(applyFilters.bind(null, filters, query))%0A
     %7D%0A  
@@ -2099,16 +2099,62 @@
 atches)%0A
+    data = applyFilters(filters, query, data)%0A
   %7D%0A%0A  r
@@ -2938,8 +2938,908 @@
  null%0A%7D%0A
+%0A/**%0A * Given a filters collection, a query object, and data to process, apply all%0A * requested filters by query over the data%0A * @param  %7BObject%7D filters Simple collection of filter functions%0A * @param  %7BObject%7D query   Query object, containing a filters array property%0A * @param  %7Bstring%7D data    Data to be transformed%0A * @return %7Bstring%7D         Data after all filters have been applied in order%0A */%0A%0Afunction applyFilters (filters, query, data) %7B%0A  // Apply each filter declared in the query, passing the result of the last as%0A  // the argument of the next%0A  return query.filters.reduce((result, filter) =%3E %7B%0A    // Check if the requested filter exists in the filters collection%0A    return filters.hasOwnProperty(filter.name)%0A      // if so, call it%0A      ? filters%5Bfilter.name%5D.apply(null, %5Bresult, ...filter.args%5D)%0A      // if not, simply return the data as it was%0A      : result%0A  %7D, data)%0A%7D%0A

Implement filters support