var libFS = require("fs");
var libPath = require("path");
var libUtil = require("util");
fcf.module({
  name:         "fcf:NSystem/fs.js",
  dependencies: [],
  lazy:         [],
  module: function(){
    var Namespace = fcf.prepareObject(fcf, "NSystem");

    function prepareEndpoints(a_endpoints){
      a_endpoints = fcf.append([], a_endpoints);

      let endpoints = [];
      if (Array.isArray(a_endpoints) && a_endpoints.length){
        endpoints = a_endpoints;
      } else {
        endpoints = [{server: "*", handler: fcf.application.getConfiguration().handlerName, processPair: 0}];
      }
      fcf.each(endpoints, (a_key, a_endpoint)=>{
        if (!a_endpoint.server) {
          a_endpoint.server = fcf.application.getConfiguration().serverName;
        }
        if (!a_endpoint.handler) {
          a_endpoint.handler = fcf.application.getConfiguration().handlerName;
        }
        a_endpoint.processPair = 0;
      });
      if (fcf.empty(endpoints)) {
        endpoints.push({
          server:      fcf.application.getConfiguration().serverName,
          handler:     fcf.application.getConfiguration().handlerName,
          processPair: 0,
        });
      }

      let isOnlySelfServer    = true;
      let isOnlySelfHandler   = true;
      let isTotalServersCount = Math.max(fcf.count(fcf.application.getConfiguration().servers), 1);
      let isSelfHandlerCount  = Math.max(fcf.count(fcf.application.getConfiguration().selfHandlers), 1);
      if (isTotalServersCount > 1) {
        fcf.each(endpoints, (a_key, a_endpoint) => {
          isOnlySelfServer &= (a_endpoint.server == "*" && isTotalServersCount == 1) ||
                              (a_endpoint.server == fcf.application.getConfiguration().serverName);
          if (!isOnlySelfServer)
            return false;
        });
        fcf.each(endpoints, (a_key, a_endpoint) => {
          isOnlySelfHandler &= (isOnlySelfServer && isSelfHandlerCount == 1 && a_endpoint.handler == "*") ||
                               (a_endpoint.handler == fcf.application.getConfiguration().handlerName);
           if (!isOnlySelfHandler)
             return false;
        });
      }

      return {
        isOnlySelfServer: isOnlySelfServer && isOnlySelfHandler,
        endpoints: endpoints,
      };
    }

    function readFileBlockByFD(a_fd, a_buffer, a_partSize, a_cbPart, a_cbEnd, a_cbError){
      libFS.read(a_fd, a_buffer, 0, a_partSize, null, (a_error, a_size)=>{
        if (a_error){
          libFS.close(a_fd);
          a_cbError(a_error);
          return;
        }
        if (!a_size){
          libFS.close(a_fd);
          a_cbEnd();
          return;
        }
        let buffer;
        if (a_partSize > a_size){
          buffer = a_buffer.slice(0, a_size);
        } else {
          buffer = a_buffer;
        }
        a_cbPart(buffer, (a_error)=>{
          if (a_error){
            libFS.close(a_fd);
            a_cbError(a_error);
            return;
          }
          readFileBlockByFD(a_fd, a_buffer, a_partSize, a_cbPart, a_cbEnd, a_cbError);
        });
      });
    }

    function readFile(a_filePath, a_partSize, a_cbPart, a_cbEnd, a_cbError){
      let buffer = Buffer.alloc(a_partSize);
      libFS.open(a_filePath, "r", (a_error, a_fd)=>{
        if (a_error){
          a_cbError(a_error);
          return;
        }
        readFileBlockByFD(a_fd, buffer, a_partSize, a_cbPart, a_cbEnd, a_cbError);
      });
    }


    class FS {
      constructor(){
      }

      prepareDirectory(a_dir) {
        a_dir = fcf.getPath(a_dir);
        return fcf.actions()
        .then(async ()=>{
          let exist = await libUtil.promisify(libFS.exists)(a_dir);
          if (exist)
            return;
          await libUtil.promisify(libFS.mkdir)(a_dir, { recursive: true });
        })
      }

      prepareDirectorySync(a_dir){
        a_dir = fcf.getPath(a_dir);
        if (libFS.existsSync(a_dir))
          return;
        libFS.mkdirSync(a_dir, { recursive: true });
      }

      deleteFile(a_path, a_dstEndpoints, a_cb){
        try {
          let endpointsInfo = prepareEndpoints(a_dstEndpoints);

          if (endpointsInfo.isOnlySelfServer) {
            let realPath = fcf.getPath(a_path);
            return fcf.application.getSystemActions()
            .then((a_res, a_act)=>{
              libFS.unlink(realPath, function(a_error) {
                if (a_error){
                  a_act.error(new fcf.Exception("ERROR_DELETE_FILE", {file: a_path, error: a_error}));
                } else {
                  a_act.complete();
                }
              })
            })
            .then(()=>{
              if (a_cb)
                a_cb();
            })
            .catch((a_error)=>{
              if (a_cb)
                a_cb(a_error);
            });
          } else {
            return fcf.application.getSystemActions()
            .then(()=>{
              return fcf.application.getEventChannel().send(
                                  "_fcf_file_fix_change",
                                  {
                                    file:   a_path,
                                    action: "delete"
                                  },
                                  {
                                    active:     true,
                                    endpoints:  endpointsInfo.endpoints,
                                    error:      "full",
                                    lock:       "global",
                                    lockKey:    a_path
                                  });
            })
            .then(()=>{
              if (a_cb)
                a_cb();
            })
            .catch((a_error)=>{
              if (a_cb)
                a_cb(a_error);
            });
          }
        } catch (e){
          if (a_cb)
            a_cb(e);
          return fcf.actions().error(e);
        }
      }

      writeFile(a_dstPath, a_data, a_dstEndpoints, a_cb){
        try {
          let data;
          if (a_data instanceof Buffer){
            data = a_data;
          } else {
            data = new Buffer.from(a_data);
          }

          let endpointsInfo = prepareEndpoints(a_dstEndpoints);
          if (endpointsInfo.isOnlySelfServer) {
            let realDestinationPath = fcf.getPath(a_dstPath);
            return fcf.application.getSystemActions()
            .then((a_res, a_act)=>{
              libFS.writeFile(realDestinationPath, data, (a_error)=>{
                if (a_error){
                  a_act.error(new fcf.Exception("ERROR_WRITE_FILE", {file: a_dstPath, error: a_error}));
                } else {
                  a_act.complete();
                }
              });
            })
            .then(()=>{
              if (a_cb)
                a_cb();
            })
            .catch((a_error)=>{
              if (a_cb)
                a_cb(a_error);
            });
          } else {
            let id = fcf.id();
            let partsInfo = [];
            for(let i = 0; i * 100000 < data.length; ++i){
              partsInfo.push({
                start: i * 100000,
                end:   Math.min((i+1) * 100000, data.length)
              });
            }
            return fcf.actions()
            .each(partsInfo, (a_key, a_partInfo)=>{
              let part = Buffer.from(data, a_partInfo.start + data.byteOffset, a_partInfo.end - a_partInfo.start);
              return fcf.application.getEventChannel().send(
                              "_fcf_copy_file_write_part",
                              {
                                id:         id,
                                file:       a_dstPath,
                                eventItems: [part]
                              },
                              {
                                active:     true,
                                endpoints:  endpointsInfo.endpoints,
                                error:      "full",
                              });
            })
            .then(()=>{
              return fcf.application.getEventChannel().send(
                                  "_fcf_file_fix_change",
                                  {
                                    id:     id,
                                    file:   a_dstPath,
                                    action: "copy"
                                  },
                                  {
                                    active:     true,
                                    endpoints:  endpointsInfo.endpoints,
                                    error:      "full",
                                    lock:       "global",
                                    lockKey:    a_dstPath
                                  });
            })
            .then(()=>{
              if (a_cb)
                a_cb();
            })
            .catch((a_error)=>{
              if (a_cb)
                a_cb(a_error);
            });
          }
        } catch (e){
          if (a_cb)
            a_cb(e);
          return fcf.actions().error(e);
        }
      }


      copyFile(a_srcPath, a_dstPath, a_dstEndpoints, a_cb){
        try {
          let endpointsInfo = prepareEndpoints(a_dstEndpoints);
          if (endpointsInfo.isOnlySelfServer) {
            let realSourcePath = fcf.getPath(a_srcPath);
            let realDestinationPath = fcf.getPath(a_dstPath);
            return fcf.application.getSystemActions()
            .then((a_res, a_act)=>{
              libFS.copyFile(realSourcePath, realDestinationPath, (a_error)=>{
                if (a_error){
                  a_act.error(new fcf.Exception("ERROR_COPY_FILE", {source: a_srcPath, destination: a_dstPath, error: a_error}));
                } else {
                  a_act.complete();
                }
              });
            })
            .then(()=>{
              if (a_cb)
                a_cb();
            })
            .catch((a_error)=>{
              if (a_cb)
                a_cb(a_error);
            });
          } else {
            let realSourcePath = fcf.getPath(a_srcPath);
            let id = fcf.id();
            return fcf.application.getSystemActions()
            .then((a_res, a_act)=>{
              readFile(
                realSourcePath,
                100000,
                (a_buffer, a_next)=>{
                  fcf.application.getEventChannel().send(
                                  "_fcf_copy_file_write_part",
                                  {
                                    id:         id,
                                    file:       a_dstPath,
                                    eventItems: [a_buffer]
                                  },
                                  {
                                    active:     true,
                                    endpoints:  endpointsInfo.endpoints,
                                    error:      "full",
                                  })
                  .then(()=>{
                    a_next();
                  })
                  .catch((a_error)=>{
                    a_next(a_error);
                  })
                },
                ()=>{
                  a_act.complete();
                },
                (a_error)=>{
                  a_act.error(new fcf.Exception("ERROR_COPY_FILE", {source: a_srcPath, destination: a_dstPath, error: a_error}));
                },
              );
            })
            .then(()=>{
              return fcf.application.getEventChannel().send(
                                  "_fcf_file_fix_change",
                                  {
                                    id:     id,
                                    file:   a_dstPath,
                                    action: "copy"
                                  },
                                  {
                                    active:     true,
                                    endpoints:  endpointsInfo.endpoints,
                                    error:      "full",
                                    lock:       "global",
                                    lockKey:    a_dstPath
                                  });
            })
            .then(()=>{
              if (a_cb)
                a_cb();
            })
            .catch((a_error)=>{
              if (a_cb)
                a_cb(a_error);
            });
          }
        } catch (e) {
          if (a_cb)
            a_cb(e);
          return fcf.actions().error(e);
        }
      }

    };

    Namespace.fs = new FS();

    return Namespace.fs;
  }
});
