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

    NConnections.BaseBuilder = function() {

      this._quote = "`";

      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);


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

        if (join.query) {
          var subquery = this._buildSelect(join.query, result.args.length);
          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);
        }

        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 field in a_query.values){
          if (a_query.values[field].except)
            continue;

          if (!isFirst)
            result.query += ", ";
          else
            result.query += " ";

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

        result.query += " ) VALUES (";

        var isFirst = true;
        for(var field in a_query.values) {

          if (a_query.values[field].except)
            continue;

          if (!isFirst)
            result.query += ", ";
          else
            result.query += " ";

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

        result.query += " )";


        delete result.details;
        return result;
      }

      this._buildUpdate = function(a_query, a_startArgCounter){
        a_query = fcf.append({join: []}, a_query);

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


        for(var i = 0; i < a_query.join.length; ++i) {
          var join = a_query.join[i];
          result.query += !join.join                         ? " LEFT JOIN " :
                          join.join.toLowerCase() == "left"  ? " LEFT JOIN " :
                          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 field in a_query.values) {
          if (a_query.values[field].except)
            continue;

          if (!isFirst)
            result.query += ", ";
          else
            result.query += " ";

          var from = a_query.values[field].from ? a_query.values[field].from : a_query.from;
          this._buildSafeElement(result, from);
          result.query += ".";
          this._buildSafeElement(result, a_query.values[field].field);
          result.query += " = ";
          this._buildArg(result, a_query.values[field], true);

          isFirst = false;
        }

        this._buildWhere(result, a_query.where);

        delete result.details;

        return result;
      }

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

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

        if (join.query) {
          var subquery = this._buildSelect(join.query, result.args.length);
          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);

        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 += " ";
            if (a_where[i].args[0].value === null){
              this._buildArg(a_result, a_where[i].args[1]);
              a_result.query += " IS ";
              if (type != "=")
                a_result.query += " NOT ";
              a_result.query += " NULL ";
            } else if (a_where[i].args[1].value === null){
              this._buildArg(a_result, a_where[i].args[0]);
              a_result.query += " IS ";
              if (type != "=")
                a_result.query += " NOT ";
              a_result.query += " NULL ";
            } else if (a_where[i].args[0].value === null && a_where[i].args[1].value === null){
              a_result.query += " TRUE ";
            } else {
              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, this._quote, "\\" + this._quote);
        a_result.query += this._quote + a_elem + this._quote;
      }

      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);
      }

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

      this._buildResult = function(a_result, a_arg) {
        var args = fcf.append(true, {}, a_arg);
        a_result.args.push(a_arg);
        a_result.query += "?";
      }

      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) {
          a_result.query += a_arg.function;
          a_result.query += "(";
          if (Array.isArray(a_arg.args)){
            for(var i = 0; i < a_arg.args.length; ++i){
              if (i != 0)
                a_result.query += ", ";
              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 NConnections.BaseBuilder;
  }
});
