const libFS   = require("fs");
const libPath = require('path');
const libUtil = require("util");

fcf.module({
  name: "fcf:NSystem/NPackage/packageLoader.js",
  dependencies: ["fcf:NSystem/NPackage/Package.js",
                 "fcf:NSystem/NPackage/ThemePackage.js",
                 "fcf:NSystem/NPackage/tools.js",
                 "fcf:NSystem/Configuration.js"],
  lazy:         ["fcf:NSystem/NPackage/Errors.js"],
  module: function(Package, ThemePackage, libTools, Configuration){
    var NPackage = fcf.prepareObject(fcf, "NSystem/NPackage");

    class PackageLoader {
      constructor(){
        this._packages = {};
        this._themes   = {};
      }

      getPackages() {
        return this._packages;
      }

      getThemes() {
        return this._themes;
      }

      async load(a_packageName) {
        if (this._packages[a_packageName])
          return this._packages[a_packageName];

        let skipErrorWrapper = false;

        try {
          let self = this;
          let basePackageInfo = await this._findPackage(a_packageName);

          await fcf.asyncEach(basePackageInfo.config.dependencies, async (a_key, a_dependence)=>{
            if (self._packages[a_dependence])
              return;
            skipErrorWrapper = true;
            await self.load(a_dependence);
            skipErrorWrapper = false;
          });

          let packageObject = basePackageInfo.isTheme ? new ThemePackage(a_packageName, basePackageInfo.directory, basePackageInfo.config) :
                                                        new Package(a_packageName, basePackageInfo.directory, basePackageInfo.config);
          await packageObject.initialize();

          this._packages[a_packageName] = packageObject;
          if (basePackageInfo.isTheme) {
            this._themes[a_packageName] = packageObject;
          }

          await fcf.application.getEventChannel().send("load_package", {package: packageObject});
        } catch(error) {
          if (skipErrorWrapper)
            throw error;
          else{
            throw new fcf.Exception("ERROR_PACKAGE_INITIALIZE", {package: a_packageName ? a_packageName : "Application"}, error);
          }
        }

        return this._packages[a_packageName];
      }

      async loadConfiguration(a_packageName, _a_configuration, _a_recInfo){

        if (!_a_recInfo)
          _a_recInfo = {};
        if (_a_recInfo[a_packageName])
          return;
        _a_recInfo[a_packageName] = true;

        let skipErrorWrapper = false;

        try {
          if (!_a_configuration){
            _a_configuration = new Configuration();
            _a_configuration._userConfigurationsArr      = fcf.clone(fcf.application.getConfiguration()._userConfigurationsArr);
            _a_configuration._overwriteConfigurationsArr = fcf.clone(fcf.application.getConfiguration()._overwriteConfigurationsArr);
            _a_configuration._build();
          }

          let self = this;
          let basePackageInfo = await this._findPackage(a_packageName);

          if (basePackageInfo.isTheme){
            delete basePackageInfo.config.html;
            delete basePackageInfo.config.aliases;
            delete basePackageInfo.config.views;
          }

          _a_configuration.appendUserConfiguration(basePackageInfo.config);

          await fcf.asyncEach(basePackageInfo.config.dependencies, async (a_key, a_dependence)=>{
            if (_a_recInfo[a_dependence])
              return;
            skipErrorWrapper = true;
            await self.loadConfiguration(a_dependence, _a_configuration, _a_recInfo);
            skipErrorWrapper = false;
          });

        } catch(error) {
          if (skipErrorWrapper)
            throw error;
          else
            throw new fcf.Exception("ERROR_PACKAGE_INFO", {package: a_packageName}, error);
        }


        return _a_configuration;
      }

      async _findPackage(a_packageName){
        let self = this;
        let result = undefined;
        try {
          let packageDirectories = fcf.application.getConfiguration().packageDirectories;
          if (fcf.find(packageDirectories, "fcf:packages"))
            packageDirectories.unshift("fcf:packages");
          await fcf.asyncEach(packageDirectories, async (a_key, a_path)=>{
            a_path = fcf.getPath(a_path);
            let stat = undefined;
            try { stat = await libUtil.promisify(libFS.lstat)(a_path); } catch(e) { }
            if (!stat || !stat.isDirectory())
              return;
            result = await self._findPackageInDirectory(a_packageName, a_path);
            if (result)
              return false;
          })
        } catch(error){
          throw new fcf.Exception("ERROR_PACKAGE_NOT_FOUND", {package:a_packageName}, error);
        }

        if (!result)
          throw new fcf.Exception("ERROR_PACKAGE_NOT_FOUND", {package:a_packageName});

        return result;
      }

      async _findPackageInDirectory(a_packageName, a_directory){
        let self           = this;
        let result         = undefined;
        let directoryItems = await libUtil.promisify(libFS.readdir)(a_directory);
        let directoryName  = fcf.getFileName(a_directory);

        if (!a_packageName) {
          result = {
            name:       "",
            directory:  fcf.getPath(":"),
            configPath: fcf.getPath("settings.package"),
            config:     fcf.application.getConfiguration().getUserConfiguration(),
            isTheme:    false,
          }
        } else if (a_packageName == "fcf") {
          result = {
            name:       "fcf",
            directory:  fcf.getPath("fcf:"),
            configPath: fcf.getPath("fcf:fcf.package"),
            config:     await libTools.loadConfigFile("fcf", "fcf:package.config"),
            isTheme:    false,
          }
        } else {
          await fcf.asyncEach(directoryItems, async (a_key, a_itemName)=>{
            let itemPath = libPath.join(a_directory, a_itemName);
            let itemStat = await libUtil.promisify(libFS.lstat)(itemPath);
            if (itemStat.isDirectory()){
              result = await self._findPackageInDirectory(a_packageName, itemPath);
              if (result)
                return false;
            } else {
              let shortFileName = fcf.getShortFileName(a_itemName);
              let extension     = fcf.getExtension(a_itemName);
              if (directoryName != a_packageName || (shortFileName != "package" && shortFileName != "theme")  || extension != "config")
                return;

              fcf.registerPackagePath(a_packageName, fcf.getPath(a_directory), `fcfpackages/${a_packageName}`);

              result = {
                name:       a_packageName,
                directory:  a_directory,
                configPath: itemPath,
                config:     await libTools.loadConfigFile(a_packageName, itemPath),
                isTheme:    shortFileName == "theme",
              }
              return false;
            }
          });
        }

        return result;
      }

    };

    NPackage.packageLoader = new PackageLoader();

    return NPackage.packageLoader;
  }
});
