fcf.module({
  name: "fcf:NFSQL/NDetails/SingleBuilder.js",
  dependencies: ["fcf:NFSQL/NDetails/Errors.js"],
  module: function(NFSQLErrors) {
    var NDetails = fcf.prepareObject(fcf, "NFSQL.NDetails");

    NDetails.SingleBuilder = function() {
      /**
      * @fn struct build(a_options)
      * @brief Выполняет сборку строкового FSQL запроса на базе объектного
      * @param struct a_query Объектный запрос FSQL
      **/
      this.build = function(a_query, a_startArgCounter){
        if (typeof a_query !== "object")
          throw new fcf.Exception("ERROR_NFSQL_INCORRECT_QUERY_OBJECT", []);

        a_startArgCounter = a_startArgCounter ? a_startArgCounter : 0;

        if (a_query.type === "delete") {
          return this._buildDelete(a_query, a_startArgCounter);
        } else if (a_query.type === "update") {
          return this._buildUpdate(a_query, a_startArgCounter);
        } else if (a_query.type === "insert") {
          return this._buildInsert(a_query, a_startArgCounter);
        } else if (a_query.type === "select") {
          return this._buildSelect(a_query, a_startArgCounter);
        } else {
          throw new fcf.Exception("ERROR_NFSQL_INCORRECT_TYPE_QUERY", {type: a_query.type});
        }
      }

      this._buildSelect = function(a_query, a_startArgCounter){
        var result = {query: "SELECT", args: [], details: {count: a_startArgCounter}};

        var isFirst = true;
        for(var i = 0; i < a_query.fields.length; ++i) {
          result.query += isFirst ? " ": ", ";
          this._buildArgResult(result, a_query.fields[i]);

          isFirst = false;
        }

        result.query += " FROM ";
        this._buildFrom(result, a_query.from);


        if (Array.isArray(a_query.join)) {
          for(var i = 0; i < a_query.join.length; ++i) {
            var join = a_query.join[i];
            result.query += typeof join.join == "string" && join.join.toLowerCase() == "left"  ? " LEFT JOIN " :
                            typeof join.join == "string" && join.join.toLowerCase() == "right" ? " RIGHT JOIN " :
                                                                                                 " JOIN ";
           if (typeof join.query == "object"){
             var subquery = this.build(join.query, result.details.count);
             result.query += "(";
             result.query += subquery.query;
             result.query += ")";
             fcf.append(result.args, subquery.args);
             result.details.count += subquery.args.length;
           } else {
             this._buildSafeElement(result, join.from);
           }


           if (!fcf.empty(join.as)){
             result.query += " AS ";
             this._buildSafeElement(result, join.as);
           }

           result.query += " ON";

           this._buildWhere(result, join.on, true);
          }
        }

        this._buildWhere(result, a_query.where);

        if (!fcf.empty(a_query.group)) {
          result.query += " GROUP BY ";
          var isFirst = true;
          for(var i = 0; i < a_query.group.length; ++i) {
            result.query += isFirst ? "": ", ";
            isFirst = false;
            this._buildSafeElement(result, a_query.group[i]);
          }
        }

        if (!fcf.empty(a_query.order)){
          result.query += " ORDER BY";
          var isFirst = true;
          for(var i = 0; i < a_query.order.length; ++i){
            result.query += isFirst ? " ": ", ";
            isFirst = false;
            this._buildField(result, a_query.order[i]);

            result.query += a_query.order[i].order.toLowerCase() == "desc" ? " DESC" : " ASC";
          }
        }

        if (!fcf.empty(a_query.limit)){
          result.query += " LIMIT ";
          this._buildValue(result, a_query.limit);
        }

        if (!fcf.empty(a_query.offset)){
          result.query += " OFFSET ";
          this._buildValue(result, a_query.offset);
        }

        if (!fcf.empty(a_query.language)){
          result.query += " LANGUAGE " + a_query.language;
          if (a_query.defaultLanguage)
            result.query += " DEFAULT ";
        }

        delete result.details;
        return result;
      }

      this._buildInsert = function(a_query, a_startArgCounter){
        var result = {query: "INSERT INTO ", args: [], details: {count: a_startArgCounter}};
        this._buildFrom(result, a_query.from);
        result.query += " (";

        var isFirst = true;
        for(var i = 0; i < a_query.values.length; ++i) {
          if (!isFirst)
            result.query += ", ";
          else
            result.query += " ";

          this._buildSafeElement(result, a_query.values[i].field);
          isFirst = false;
        }

        result.query += " ) VALUES (";

        var isFirst = true;
        for(var i = 0; i < a_query.values.length; ++i) {
          if (!isFirst)
            result.query += ", ";
          else
            result.query += " ";

          this._buildArg(result, a_query.values[i], true);
          isFirst = false;
        }

        result.query += " )";

        if (!fcf.empty(a_query.language)){
          result.query += " LANGUAGE " + a_query.language;
          if (a_query.defaultLanguage)
            result.query += " DEFAULT ";
        }

        delete result.details;
        return result;
      }

      this._buildUpdate = function(a_query, a_startArgCounter){
        var result = {query: "UPDATE ", args: [], details: {count: a_startArgCounter}};
        this._buildFrom(result, a_query.from);


        if (Array.isArray(a_query.join)) {
          for(var i = 0; i < a_query.join.length; ++i) {
            var join = a_query.join[i];
            result.query += typeof join.join == "string" && join.join.toLowerCase() == "left"  ? " LEFT JOIN " :
                            typeof join.join == "string" && join.join.toLowerCase() == "right" ? " RIGHT JOIN " :
                                                                                                 " JOIN ";

           this._buildSafeElement(result, join.from);


           if (!fcf.empty(join.as)){
             result.query += " AS ";
             this._buildSafeElement(result, join.as);
           }

           result.query += " ON";

           this._buildWhere(result, join.on, true);
          }
        }

        result.query += " SET";

        var isFirst = true;
        for(var i = 0; i < a_query.values.length; ++i){
          if (!isFirst)
            result.query += ", ";
          else
            result.query += " ";

          this._buildSafeElement(result, a_query.values[i].field);
          result.query += " = ";
          this._buildArg(result, a_query.values[i], true);

          isFirst = false;
        }

        this._buildWhere(result, a_query.where);

        if (!fcf.empty(a_query.language)){
          result.query += " LANGUAGE " + a_query.language;
          if (a_query.defaultLanguage)
            result.query += " DEFAULT ";
        }

        delete result.details;
        return result;
      }

      this._buildDelete = function(a_query, a_startArgCounter) {
        var result = {query: "DELETE FROM ", args: [], details: {count: a_startArgCounter}};
        this._buildFrom(result, a_query.from);

        this._buildWhere(result, a_query.where);

        if (!fcf.empty(a_query.language)){
          result.query += " LANGUAGE " + a_query.language;
        }

        delete result.details;
        return result;
      }

      this._buildWhere = function(a_result, a_where, a_isNotRoot){
        if (fcf.empty(a_where))
          return;
        var startLength = a_result.query.length;
        var isEmpty = true;

        if (!a_isNotRoot)
          a_result.query += " WHERE";

        for(var i = 0; i < a_where.length; ++i) {
          if (i !== 0){
            var logic = a_where[i].logic ? a_where[i].logic.toUpperCase() : "AND";
            a_result.query += " " + logic;
          }

          if (a_where[i].not) {
            a_result.query += " NOT";
          }

          var type = a_where[i].type;
          if (type == "true") {
            a_result.query += " TRUE ";
            isEmpty = false;
          } else if (type == "false") {
            a_result.query += " FALSE ";
            isEmpty = false;
          } else if (type != "block") {
            a_result.query += " ";
            this._buildArg(a_result, a_where[i].args[0]);
            a_result.query += " ";
            a_result.query += type;
            a_result.query += " ";
            this._buildArg(a_result, a_where[i].args[1]);
            isEmpty = false;
          } else {
            if (!fcf.empty(a_where[i].args)) {
              a_result.query += " (";
              this._buildWhere(a_result, a_where[i].args, true);
              a_result.query += " )";
              isEmpty = false;
            }
          }
        }

        if (isEmpty)
          a_result.query = a_result.query.substr(0, startLength);
      }

      this._buildSafeElement = function(a_result, a_elem) {
        a_elem = fcf.replaceAll(a_elem, "\"", "\\\"");
        a_result.query += "\"" + a_elem + "\"";
      }

      this._buildFrom = function(a_result, a_from) {
        this._buildSafeElement(a_result, a_from);
      }

      this._buildArgResult = function(a_result, a_fieldInfo) {
        this._buildArg(a_result, a_fieldInfo);
        if (!fcf.empty(a_fieldInfo.as)) {
          a_result.query += " AS "
          this._buildSafeElement(a_result, a_fieldInfo.as);
        }
      }

      this._buildField = function(a_result, a_fieldInfo) {
        if (a_fieldInfo.from){
          this._buildSafeElement(a_result, a_fieldInfo.from);
          a_result.query += ".";
        }
        if (a_fieldInfo.field == "*")
          a_result.query += "*";
        else
          this._buildSafeElement(a_result, a_fieldInfo.field);

        if (a_fieldInfo.mode) {
          a_result.query += ":";
          a_result.query += a_fieldInfo.mode;
        }

        fcf.each(a_fieldInfo.path, function(a_key, a_value){
          a_result.query += "->\"";
          a_result.query += fcf.escapeQuotes(a_value);
          a_result.query += "\"";
        });
      }

      this._buildValue = function(a_result, a_value) {
        a_result.args.push(a_value);
        a_result.query += "${" + (++a_result.details.count) + "}";
      }

      this._buildResult = function(a_result, a_arg){
        a_result.query += "$[" + a_arg.result + "][" + a_arg.record + "][\"" + a_arg.item + "\"]";
      }


      this._buildArg = function(a_result, a_arg, a_isNotField) {
        if (!a_isNotField && "field" in a_arg){
          this._buildField(a_result, a_arg);
        } else if ("function" in a_arg){
          this._buildSafeElement(a_result, a_arg.function);
          a_result.query += "(";
          if (Array.isArray(a_arg.args)){
            for(var i = 0; i < a_arg.args.length; ++i){
              this._buildArg(a_result, a_arg.args[i], a_isNotField);
            }
          }
          a_result.query += ")";
        } else if ("value" in a_arg) {
          this._buildValue(a_result, a_arg.value);
        } else if ("result" in a_arg) {
          this._buildResult(a_result, a_arg);
        }
      }

    }

    return NDetails.SingleBuilder;
  }
});
