if (fcf.isServer()) {
  var libFS = require('fs');
  var libUtil = require('util');
}

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

    NFilter.File = class File extends Filter {
      constructor() {
        super({type: "file"});
      }

      getSelectHandlers() {
        return {
          "file": function(a_taskInfo, a_info) {
            let tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
            let fieldInfo  = a_taskInfo.getFieldInfo(a_info.field);
            let as         = a_info.field.as ? a_info.field.as : a_info.field.field;
            return {
              field:         a_taskInfo.getQueryField(a_info.field),
              from:          tableAlias,
              recommendedAs: as + "@" + fieldInfo.alias + "->file",
            };
          },
          "alias": function(a_taskInfo, a_info) {
            let tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
            let fieldInfo  = a_taskInfo.getFieldInfo(a_info.field);
            let as         = a_info.field.as ? a_info.field.as : a_info.field.field;
            return {
              field:         fieldInfo.fieldAlias ? fieldInfo.fieldAlias : fieldInfo.alias + "->alias",
              from:          tableAlias,
              recommendedAs: as + "@" + fieldInfo.alias + "->alias",
            };
          },
        };
      };

      getRealFields(a_projection, a_fieldAlias){
        let realFieldInfo = fcf.append({}, a_projection.fieldsMap[a_fieldAlias]);
        if (!realFieldInfo.field)
          realFieldInfo.field = fieldInfo.alias;
        realFieldInfo.type = "string";
        return [
          realFieldInfo,
          {
            field: realFieldInfo.fieldAlias ? realFieldInfo.fieldAlias : realFieldInfo.field+"->alias",
            type: "string",
          },
        ]
      }

      getUpdateHandlers(){
        return {
          "file": async function(a_taskInfo, a_info) {
            let tableAlias      = a_taskInfo.getQueryTableByField(a_info.field);
            let fieldInfo       = a_taskInfo.getFieldInfo(a_info.field);
            let originInfo      = fcf.append(true, {}, a_info);
            let filePath        = a_info.field.value && typeof a_info.field.value === "object" 
                                      ? a_info.field.value.path 
                                      : a_info.field.value;
            let fileName        = a_info.field.value && typeof a_info.field.value === "object" && a_info.field.value.name 
                                      ? a_info.field.value.name
                                      : fcf.getFileName(filePath);
            let directory       = fieldInfo.directory ? fieldInfo.directory+"/" : "@path:files/";
            let fieldProjection = a_taskInfo.getProjectionField(a_info.field);
            let newPath         = filePath ? directory + fcf.getShortFileName(fileName) + 
                                             (fcf.getExtension(fileName)!== "" ? "." + fcf.getExtension(fileName) : "")
                                           : null;

            let records         = await a_taskInfo.loadRecords();

            if (records.length == 1 && records[0][a_info.field.field] && records[0][a_info.field.field].file == filePath){
              return {
                field:         a_taskInfo.getQueryField(a_info.field),
                from:          tableAlias,
                value:         newPath,
              };
            }

            try {
              if (newPath)
                await libUtil.promisify(libFS.writeFile)(fcf.getPath(newPath), "", { flag: 'wx' });
            } catch(e) {
              newPath = directory + fcf.getShortFileName(fileName) + "-" + fcf.uuid() + "." + fcf.getExtension(fileName);
            }

            let resultField     = {
              field:         a_taskInfo.getQueryField(a_info.field),
              from:          tableAlias,
              value:         newPath,
            };

            a_taskInfo.postActions.each(records, async function(a_key, a_record, a_res){
              if (a_key == 0)
                return;
              let query = fcf.append(true, {}, a_taskInfo.originQuery);
              query.where.push({ logic: "and", type: "=", args: [{field: a_taskInfo.projection.key}, {value: a_record[a_taskInfo.projection.key]}]})
              query.values = [{
                field: originInfo.field.field,
                path:  ["file"],
                value: { path: filePath, name: fileName },
              }];
              let queryOptions = fcf.append({}, a_taskInfo.options, {query: query, _fieldFile_subFileUpdate: true});
              await a_taskInfo.storage.query(queryOptions);
            });

            a_taskInfo.postActions.then(function(a_res, a_act){
              if (filePath)
                libFS.copyFile(fcf.getPath(filePath), fcf.getPath(newPath), function(a_error) { a_act.complete(); });
              else 
                a_act.complete();
            });

            if (!a_taskInfo.options._fieldFile_subFileUpdate) {
              a_taskInfo.postActions.each(records, (a_key, a_record, a_res, a_act)=>{
                let oldFile = a_record[originInfo.field.field].file;
                if (oldFile)
                  libFS.unlink(fcf.getPath(oldFile), function(a_error) { a_act.complete(); });
                else
                  a_act.complete();
              });
            }

            return resultField;
          },
          "alias": function(a_taskInfo, a_info) {
            let tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
            let fieldInfo  = a_taskInfo.getFieldInfo(a_info.field);
            let fieldAlias = fieldInfo.fieldAlias ? fieldInfo.fieldAlias : 
                             fieldInfo.field      ? fieldInfo.field + "->alias" :
                                                    fieldInfo.alias + "->alias";
            return {
              field: fieldAlias,
              from:  tableAlias,
              value: a_info.field.value,
            };
          },
        };
      };

      processOutputField(a_taskInfo, a_info){
        var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
        var originInfo = fcf.append(true, {}, a_info);
        var as         = a_taskInfo.setAutoAs(a_info.field, a_taskInfo.query.from);
        var subfields  = {};
        originInfo.as = as;

        if (fcf.empty(a_info.field.path)){
          var firstField = true;
          fcf.each(this.getSelectHandlers(), function(a_key, a_handler){

            var field = firstField ? a_info.field : {};
            fcf.append(field, a_handler(a_taskInfo, originInfo));
            if (!firstField)
              a_taskInfo.query.fields.push(field);
            subfields[a_key] = a_taskInfo.setAutoAs(field, a_taskInfo.query.from);
            firstField = false;
          });

          a_taskInfo.postActions.then(function(a_res, a_act){
            fcf.each(a_taskInfo.getResult(), function(a_key, a_record) {
              var fieldData = {};
              fcf.each(subfields, function(a_subField, a_alias) {
                fieldData[a_subField] = a_record[a_alias];
                delete a_record[a_alias];
              });
              a_record[as] = fieldData;
            });

            a_act.complete();
          });
        } else {
          var field = this.getSelectHandlers()[a_info.field.path[0]](a_taskInfo, originInfo);
          fcf.append(a_info.field, field);

        }
      }

      processWhereField(a_taskInfo, a_info){
        var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
        var originInfo = fcf.append(true, {}, a_info);
        var as         = a_taskInfo.setAutoAs(a_info.field, a_taskInfo.query.from);
        var subfields  = {};

        if (fcf.empty(a_info.field.path)){
        } else {
          var field = this.getSelectHandlers()[a_info.field.path[0]](a_taskInfo, originInfo);
          fcf.append(a_info.field, field);

        }
      }

      async processUpdateField(a_taskInfo, a_info){
        var tableAlias = a_taskInfo.getQueryTableByField(a_info.field);
        var originInfo = fcf.append(true, {}, a_info);
        var as         = a_taskInfo.setAutoAs(a_info.field, a_taskInfo.query.from);
        var subfields  = {};
        originInfo.as  = as;

        if (fcf.empty(a_info.field.path)){
          var firstField = true;
          let handlers = this.getUpdateHandlers();
          for(let key in handlers){
            let handler = handlers[key];
            let field = firstField ? a_info.field : {};
            let subInfo = fcf.append(true, {}, originInfo);
            subInfo.field.value = originInfo.field.value && typeof originInfo.field.value == "object" ? originInfo.field.value[key] : null;
            fcf.append(field, await handler(a_taskInfo, subInfo));
            if (!firstField)
              a_taskInfo.query.values.push(field);
            subfields[key] = a_taskInfo.setAutoAs(field, a_taskInfo.query.from);
            firstField = false;
          }

        } else {
          var field = await this.getUpdateHandlers()[a_info.field.path[0]](a_taskInfo, originInfo);
          fcf.append(a_info.field, field);
        }
      }

      async processInsertField(a_taskInfo, a_info){
        let tableAlias  = a_taskInfo.getQueryTableByField(a_info.field);
        let fieldInfo   = a_taskInfo.getFieldInfo(a_info.field);
        let oldFilePath = a_info.field.value && typeof a_info.field.value === "object" ? a_info.field.value.file : a_info.field.value;
        if (oldFilePath && typeof oldFilePath == "object")
          oldFilePath = oldFilePath.path;
        let fileAlias   = a_info.field.value && typeof a_info.field.value === "object" && a_info.field.value.alias  ? a_info.field.value.alias : fcf.getFileName(oldFilePath);
        let fileName    = a_info.field.value && typeof a_info.field.value === "object" && a_info.field.value.file && typeof a_info.field.value.file === "object" && a_info.field.value.file.name ? fcf.getFileName(a_info.field.value.file.name) : fcf.getFileName(oldFilePath);
        let directory   = fieldInfo.directory ? fieldInfo.directory+"/" : "@path:files/";
        let newFilePath = oldFilePath ? directory + fcf.getShortFileName(fileName) + 
                                        (fcf.getExtension(fileName) != "" ? "." + fcf.getExtension(fileName) : "")
                                      : null;
        try {
          if (newFilePath)
            await libUtil.promisify(libFS.writeFile)(fcf.getPath(newFilePath), "", { flag: 'wx' });
        } catch(e) {
          newFilePath = directory + fcf.getShortFileName(fileName) + "-" + fcf.uuid() + "." + fcf.getExtension(fileName);
        }

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

        let fieldAlias = fieldInfo.fieldAlias ? fieldInfo.fieldAlias : 
                         fieldInfo.field      ? fieldInfo.field + "->alias" :
                                                fieldInfo.alias + "->alias";

        a_taskInfo.query.values.push({
          field: fieldAlias,
          from:  tableAlias,
          value: fileAlias,
        });

        a_taskInfo.postActions.then(function(a_res, a_act) {
          if (oldFilePath)
            libFS.copyFile(fcf.getPath(oldFilePath), fcf.getPath(newFilePath), function(a_error) { a_act.complete(); });
          else 
            a_act.complete();
        });
      }

      async processDeleteField(a_taskInfo, a_info) {
        var rmFiles = [];

        let records = await a_taskInfo.loadRecords();
        fcf.each(records, function(a_key, a_record) { rmFiles.push(a_record[a_info.field.field].file); });

        a_taskInfo.postActions.then(function(a_res, a_act) {
          return fcf.actions()
          .each(rmFiles, function(a_key, a_filePath, a_res, a_subact){
            if (!a_filePath){
              a_subact.complete();
              return;
            }

            libFS.unlink(fcf.getPath(a_filePath), function(err) {
              a_subact.complete();
            })
          })
          .then(function(a_res, a_subact) {
            a_act.complete();
            a_subact.complete();
          });
        });
      }


    }

    return NFilter.File;
  }
});
