fcf.module({
  name: "fcf:NFSQL/NFilter/SingleRef.js",
  dependencies: ["fcf:NFSQL/NFilter/Filter.js", "fcf:NFSQL/NFilter/Errors.js"],
  module: function(Filter, Errors) {
    var NFilter = fcf.prepareObject(fcf, "NFSQL.NFilter");

    NFilter.SingleRef = class SingleRef extends Filter {

      constructor(){
        super({type: "singleRef"})
        this.comparisons = {
          "*": [],
          "text": ["=", "<", ">", "<>", ">=", "<=", "like"],
        };
      }

      validate(a_description, a_dstErrors) {
        var value = a_description.data;
        if ((a_description.field.notEmpty || a_description.field.notNull) && fcf.empty(value) ) {
          a_dstErrors.push(new fcf.Exception("ERROR_FIELD_VALIDATION_NOT_EMPTY"));
          return;
        }
      }

      getRealFields(a_projection, a_fieldAlias){
        let selfField = a_projection.fieldsMap[a_fieldAlias];
        let refProjection = fcf.application.getProjections().get(selfField.projection);
        let refField =   refProjection.fieldsMap[selfField.refField];
        let result = fcf.append(true, {}, selfField);
        result.type = refField.type;
        return [result];
      }

      processOutputField(a_taskInfo, a_info){
        if (a_info.field.mode == "value") {
          a_taskInfo.setAutoAs(a_info.field);
          var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
          a_info.field.field = a_taskInfo.getQueryField(a_info.field);
          a_info.field.from  = tableAlias;
        } else if (fcf.empty(a_info.field.path)) {
          var fieldInfo = a_taskInfo.getFieldInfo(a_info.field);
          var as = a_taskInfo.setAutoAs(a_info.field);
          var joinAs = a_info.field.from + "@join@" +  as;
          var refProjection = a_taskInfo.projections[fieldInfo.projection];

          this._appendJoin(a_taskInfo, a_info);

          if (a_info.field.mode !== "short") {
            fcf.each(refProjection.fields, function(a_key, a_field){
              var field = {
                field: a_field.alias,
                from:  joinAs,
                as: joinAs + "@"+ a_field.alias,
              };
              var filter = fcf.getFilter(a_field.type);
              a_taskInfo.query.fields.push(field);
              filter.processOutputField(a_taskInfo, {part: "fields", key: a_taskInfo.query.fields.length-1, field: field});
            });
          } else {
            var field = {
              field: fieldInfo.refField,
              from:  joinAs,
              as: joinAs + "@"+ fieldInfo.refField,
            };
            var filter = fcf.getFilter(refProjection.fieldsMap[fieldInfo.refField].type);
            a_taskInfo.query.fields.push(field);
            filter.processOutputField(a_taskInfo, {part: "fields", key: a_taskInfo.query.fields.length-1, field: field});
          }
          a_taskInfo.query.fields.push({function: "key",  from: joinAs,  as: joinAs+"@@key"});
          a_taskInfo.query.fields.push({function: "title", from: joinAs, as: joinAs+"@@title"});

          var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
          a_info.field.field = a_taskInfo.getQueryField(a_info.field);
          a_info.field.from  = tableAlias;

          a_taskInfo.postActions.then(function(){
            fcf.each(a_taskInfo.getResult(), function(a_key, a_record){
              var dataField = {};
              if (a_info.field.mode !== "short") {
                fcf.each(refProjection.fields, function(a_key, a_field){
                  var fieldAs = joinAs + "@"+ a_field.alias;
                  dataField[a_field.alias] = a_record[fieldAs];
                  delete a_record[fieldAs];
                });
              } else {
                var fieldAs = joinAs + "@"+ fieldInfo.refField;
                dataField[fieldInfo.refField] = a_record[fieldAs];
                delete a_record[fieldAs];
              }
              var functionAs = joinAs+"@@key";
              dataField["@key"] = a_record[functionAs];
              delete a_record[functionAs];

              functionAs = joinAs+"@@title";
              dataField["@title"] = a_record[functionAs];
              delete a_record[functionAs];

              a_record[as] = dataField;
            });
          });
        } else if (!fcf.empty(a_info.field.path)) {
          var fieldInfo = a_taskInfo.getFieldInfo(a_info.field);
          var as = a_taskInfo.setAutoAs(a_info.field);
          var joinAs = a_info.field.from + "@join@" +  as;
          var refProjection = a_taskInfo.projections[fieldInfo.projection];

          this._appendJoin(a_taskInfo, a_info);

          var path = fcf.first(a_info.field.path);
          if (path === "@key"){
            delete a_info.field.field;
            a_info.field.function = "key";
            a_info.field.from = joinAs;
          } else if (path === "@title"){
            delete a_info.field.field;
            a_info.field.function = "title";
            a_info.field.from = joinAs;
          } else {
            var subfield = refProjection.fieldsMap[path];
            if (subfield === undefined)
              throw new fcf.Exception("ERROR_FIELD_SUBFIELD_INCORRECT", {field: a_info.field, subfield: path});
            a_info.field.field = subfield.alias;
            a_info.field.from = joinAs;
            if (Array.isArray(a_info.field.path))
              a_info.field.path.shift();
            var filter = fcf.getFilter(subfield.type);
            filter.processOutputField(a_taskInfo, a_info);
          }
        }
      }

      processWhereField(a_taskInfo, a_info){
        if (fcf.empty(a_info.field.path)) {
          var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
          a_info.field.field = a_taskInfo.getQueryField(a_info.field);
          a_info.field.from  = tableAlias;
        } else {
          var fieldInfo = a_taskInfo.getFieldInfo(a_info.field);
          var as = a_taskInfo.setAutoAs(a_info.field);
          var joinAs = this._getJoinAs(a_taskInfo, a_info);
          var refProjection = a_taskInfo.projections[fieldInfo.projection];

          this._appendJoin(a_taskInfo, a_info);

          var path = fcf.first(a_info.field.path);
          if (path === "@key"){
            delete a_info.field.field;
            a_info.field.function = "key";
            a_info.field.from = joinAs;
          } else if (path === "@title") {
            delete a_info.field.field;
            a_info.field.function = "title";
            a_info.field.from = joinAs;
          } else {
            var subfield = refProjection.fieldsMap[path];
            if (subfield === undefined)
              throw new fcf.Exception("ERROR_FIELD_SUBFIELD_INCORRECT", {field: a_info.field, subfield: path});
            a_info.field.field = subfield.alias;
            a_info.field.from = joinAs;
            var filter = fcf.getFilter(subfield.type);
            filter.processOutputField(a_taskInfo, a_info);
          }
        }
      }

      processUpdateField(a_taskInfo, a_info){
        var fieldInfo = a_taskInfo.getFieldInfo(a_info.field);
        var value = typeof a_info.field.value === "object" ? a_info.field.value[fieldInfo.refField] : a_info.field.value;
        var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);

        a_info.field.field = a_taskInfo.getQueryField(a_info.field);
        a_info.field.from  = tableAlias;
        a_info.field.value = value;
      }

      processInsertField(a_taskInfo, a_info){
        var fieldInfo = a_taskInfo.getFieldInfo(a_info.field);
        var value = typeof a_info.field.value === "object" ? a_info.field.value[fieldInfo.refField] : a_info.field.value;
        var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
        a_info.field.field = a_taskInfo.getQueryField(a_info.field);
        a_info.field.from  = tableAlias;
        a_info.field.value = value;
      }

      _getJoinAs(a_taskInfo, a_info){
        var as = a_taskInfo.setAutoAs(a_info.field);
        return a_info.field.from + "@join@" +  as;
      }

      _appendJoin(a_taskInfo, a_info){
        var joinAs = this._getJoinAs(a_taskInfo, a_info);
        var fieldInfo = a_taskInfo.getFieldInfo(a_info.field);
        var refProjection = a_taskInfo.projections[fieldInfo.projection];
        var joinFound = fcf.find(a_taskInfo.query.join, function(a_key, a_join) { return a_join.as ==  joinAs });
        if (joinFound === undefined) {
          a_taskInfo.query.join.push({
            join: "left",
            from: fieldInfo.projection,
            as: joinAs,
            on: [{ type: "=", args:[{field: fieldInfo.field, from: a_taskInfo.getQueryTableByField(a_info.field)}, {from: joinAs, field: refProjection.fieldsMap[fieldInfo.refField].field } ] }],
          });
          a_taskInfo.aliases[joinAs] = fieldInfo.projection;
        }

      }

    }

    return NFilter.SingleRef;
  }
});
