'use strict';

var bucketNameComposer = require('./bucket_name_composer');

var escapeRegExp = function(str) {
  return str.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
};

// see http://lucene.apache.org/core/3_0_3/queryparsersyntax.html#Escaping%20Special%20Characters
var SOLR_ESCAPE_CHARACTERS = ["\\", "+", "-", "!", "(", ")", ":", ";", "^", "[", "]", "{", "}", "~", "*", "?"];
var SOLR_ESCAPE_REGEXP = new RegExp([
  "(",
  SOLR_ESCAPE_CHARACTERS.map(function(character){
    return escapeRegExp(character);
  }).join("|"),
  ")"
].join(""), "gi");

// TODO: convert data types to appropriate format for solr
// TODO: support range queries
module.exports = function(riak, bucketName, where){
  var modelName = bucketNameComposer.decompose(riak, bucketName);

  var properties = riak._models[modelName] && riak._models[modelName].properties;
  if (!properties) return;

  return componentizeQueries().join(" AND ");

  /* helpers */

  function hasProperty(propertyName){
    return !!properties[propertyName];
  }

  function componentizeQueries(){
    var queryComponents = [];

    for (var propertyName in where){
      if (hasProperty(propertyName)){
        queryComponents.push(
          componentizeQuery(propertyName, where[propertyName])
        );
      }
    };

    return queryComponents;
  }

  function formatQuery(propertyName, propertyQuery, escape){
    return [
      fieldName(propertyName),
      ":",
      escapeQuery(propertyQuery)
    ].join("");
  }

  function splitQuery(query){
    // FIXME: this is dumb but a regexp wizard I am not. the desired
    // outcome is to split on commas but not on escaped commas
    return query.
           split('').
           reverse().
           join('').                                    // reverse the query
           split(/,(?!\\)/).                            // split on non-escaped commas
           map(function(str){
             return str.split("").reverse("").join(""). // unreverse the components
                    replace(/\\,/gi, ",");              // re-comma-ify
           }).reverse();                                // unreverse the query
  }

  function componentizeQuery(propertyName, propertyQuery){
    switch(propertyQuery.constructor){
    case String:
      return splitQuery(propertyQuery).map(function(property){
        return formatQuery(propertyName, property);
      }).join(" AND ");
      break;
    case Date:
      return formatQuery(propertyName, propertyQuery.toISOString());
      break;
    default:
      return formatQuery(propertyName, property);
      break;
    }
  }

  // you can map the property to a custom field name by specifying a
  // "yzField" key in the "riak" namespace of the field definition in
  // the Loopback model JSON spec.
  //
  // Example:
  //  "file_location": {
  //     "type": "string",
  //     "required": true,
  //     "riak": {
  //       "yzField": "file_url"
  //     }
  //   }
  function fieldName(propertyName){
    propertyName = escapeQuery(propertyName);

    return (properties[propertyName] &&
           properties[propertyName].riak &&
           properties[propertyName].riak.yzField) || propertyName;
  }

  function escapeQuery(query){
    // TODO: this is safe, right? you can't send some crazy non-string
    // thing in and make it evaluate to an unsafe string, right?
    // asking for a friend here
    if (typeof(query) !== "string") return query;

    return query.replace(SOLR_ESCAPE_REGEXP, "\\$1"). // escape Solr query characters
                 replace(/(\s)/gi, "\\$1");            // escape white space
  }

  function range(propertyName, from, to){
    from = from || "*";
    to   = to   || "*";

    return [ "[", from, " TO ", to, "]" ].join("");
  }
}

@@ -2229,24 +2229,122 @@
 nstructor)%7B%0A
+    case Object:%0A      return parseObjectPropertyQuery(propertyName, propertyQuery);%0A      break;%0A
     case Str
@@ -2348,16 +2348,16 @@
 String:%0A
-
       re
@@ -2653,26 +2653,1174 @@
 me, property
-);
+Query);%0A      break;%0A    %7D%0A  %7D%0A%0A  function parseObjectPropertyQuery(propertyName, propertyQuery)%7B%0A    // yeah, this doesn't allow multiple operators but, upon%0A    // investigation, neither do the other adapters (MongoDB, etc)%0A    var operator = Object.keys(propertyQuery)%5B0%5D;%0A    var query    = propertyQuery%5Boperator%5D;%0A%0A    // MongoDB connector implementation reference:%0A    //%0A    // if (spec === 'between') %7B%0A    //   query%5Bk%5D = %7B $gte: cond%5B0%5D, $lte: cond%5B1%5D%7D;%0A    // %7D else if (spec === 'inq') %7B%0A    //   query%5Bk%5D = %7B $in: cond.map(function (x) %7B%0A    //     if ('string' !== typeof x) return x;%0A    //     return ObjectID(x);%0A    //   %7D)%7D;%0A    // %7D else if (spec === 'like') %7B%0A    //   query%5Bk%5D = %7B$regex: new RegExp(cond, options)%7D;%0A    // %7D else if (spec === 'nlike') %7B%0A    //   query%5Bk%5D = %7B$not: new RegExp(cond, options)%7D;%0A    // %7D else if (spec === 'neq') %7B%0A    //   query%5Bk%5D = %7B$ne: cond%7D;%0A    // %7D%0A    switch(operator)%7B%0A    case %22between%22:%0A      return %5B // example: price:%5B1 TO 10%5D%0A        fieldName(propertyName),%0A        %22:%5B%22,%0A        escapeQuery(query%5B0%5D),%0A        %22 TO %22,%0A        escapeQuery(query%5B1%5D),%0A        %22%5D%22%0A      %5D.join(%22%22);%0A
 %0A      break

range query support for loopback riak adapter