fcf.module({
  name: "fcf:NTheme/Theme.js",
  dependencies: ["fcf:NServer/Package.js"],
  module: function(Package) {
    var NRender = fcf.prepareObject(fcf, "NRender");

    /*
    * @class  fcf::NRender::Theme
    * @brief <b>[client only]</b> Объект темы
    **/
    NRender.Theme = function(a_initializeOptions) {
      /*
      * @constructor
      * @param a_settings
      *         - obejct actions
      *         - string package
      *         - array  sources
      *         - array  extension
      *         - object configuration
      *         - object info [client only]
      **/
      var initializeOptions = fcf.append({}, a_initializeOptions, {extension: "theme", setConfiguration: false});
      var self = this;
      Package.call(this, initializeOptions);
      this.isTheme = true;
      this._isInitializedAliases  = false;
      this._selfAliases           = {};
      this._aliases               = {};
      this._views = {
        parts: {},
        all:   {},
        cur:   {},
      };

      fcf.prepareObject(this.getInfo(), "html.include");

      this.clientInitialize = function() {
      }

      var parentInitialize = this.initialize;
      this.initialize = function() {
        if (!fcf.isServer())
          return this._clientInitialize();
        else
          return this._serverInitialize();
      }

      this.getDecor = function(){
        return this._info.decor;
      }

      this._clientInitialize = function() {
        this.setAliases(initializeOptions.configuration.aliases);
        if (initializeOptions.info.aliases)
          this.setAliases(initializeOptions.info.aliases);
        this.setViews(initializeOptions.configuration.views);
        if (initializeOptions.info.views)
          this.setViews(initializeOptions.info.views);
      }

      this._serverInitialize = function() {
        return parentInitialize.call(this)
        .then(function(a_res, a_act){
          var extendsNames = Array.isArray(self._info.extends) ?  self._info.extends :
                                  self._info.extends  ? [self._info.extends] :
                                                        [];
          var exts = [];

          fcf.actions()
          .catch((a_error)=>{
            a_act.error(a_error);
          })
          .each(extendsNames, function(a_key, a_item, a_res, a_subact){
            if (fcf.application.getThemes().getThemes[a_item]){
              exts.push(fcf.application.getThemes().getThemes[a_item]);
              a_subact.complete();
            } else {
              var subTheme = new NRender.Theme({
                actions:   a_initializeOptions.actions,
                package:   a_item,
                sources:   a_initializeOptions.sources,
                extension: a_initializeOptions.extension,
                configuration: fcf.application.getConfiguration(),
              });
              subTheme.initialize()
              .then(function(){
                exts.push(subTheme);
                a_subact.complete();
              })
              .catch(function(a_error){
                a_subact.error(a_error)
              })
            }
          })
          .then(function(){
            self._applyExtends(exts);

            var packs = fcf.application.getPackages();
            for (var pm in  packs) {
              if (typeof packs[pm].aliases == "object") {
                self.setAliases(packs[pm].aliases);
              }
            }

            if (typeof self._info.aliases === "object") {
              self.setAliases(self._info.aliases);
            }

            if (typeof self._info.views === "object") {
              self.setViews(self._info.views);
            }

            self._appendIncludes();

            a_act.complete();
          });
        });
      }

      this._applyExtends = function(a_extends){
        var info = this.getInfo();
        if (!info.html)
          info.html = {};
        if (!info.html.include)
          info.html.include = {};

        var originalInclude = {};
        var originalDecor = {};

        fcf.each(a_extends, function(a_key, a_extend){
          var einfo = a_extend.getInfo();
          if (einfo.html && einfo.html.include){
            fcf.append(originalInclude, einfo.html.include);
          }
          if (einfo.decor){
            fcf.append(originalDecor, einfo.decor);
          }
        });

        fcf.append(originalInclude, info.html.include);
        fcf.append(originalDecor, info.decor);

        info.html.include = originalInclude;
        info.decor = originalDecor;
      }

      this.setViews = function(a_objectViews) {
        for(var part in a_objectViews)
          for(var view in a_objectViews[part])
            this.setView(part, view, a_objectViews[part][view]);
      }

      this.setView = function(a_part, a_view, a_template) {
        var partArr = a_part.split(".");
        var currentParts = this._views;
        for (var i = 0; i < partArr.length; ++i) {
          if (partArr[i] != "*") {
            if (!(partArr[i] in currentParts.parts))
              currentParts.parts[partArr[i]] = { parts: {}, cur: {}, all: {} };
            currentParts = currentParts.parts[partArr[i]];
          } else {
            currentParts.all[a_view] = a_template;
            return;
          }
        }
        currentParts.cur[a_view] = a_template;
      }

      this.getView = function(a_part, a_type) {
        if (typeof a_type === "object") {
          var view = fcf.buildView(a_type, a_part);
          if (view.template){
            return { template: view.template, found: true };
          }
          a_type = a_type.type;
        }

        var partArr = a_part.split(".");
        var currentParts = this._views;
        var result = undefined;

        for(var i = 0; i < partArr.length; ++i) {
          if (a_type in currentParts.all) {
            result = { template:  currentParts.all[a_type], found: true }
          }

          if (partArr[i] in currentParts.parts) {
            currentParts = currentParts.parts[partArr[i]];
          } else {
            currentParts = undefined;
            break;
          }
        }

        if (currentParts && a_type in currentParts.cur) {
          result = { template:  currentParts.cur[a_type], found: true }
        } else if (currentParts && a_type in currentParts.all) {
          result = { template:  currentParts.all[a_type], found: true }
        }

        if (!result){
          var inheritanceMode = fcf.application.getInheritanceModes()[a_part];
          if (inheritanceMode)
            return this.getView(inheritanceMode, a_type);
        } else {
          return result;
        }

        return { template: "@controls:text", found: false };

      }

      this.setAliases = function(a_aliases) {
        for(let key in a_aliases){
          let alias = key[0] == "@" ? key.substr(1) : key;
          this._selfAliases[alias] = a_aliases[key];
        }
      }

      this.getAliases = function() {
        if (!fcf.isServer())
          return this._selfAliases;

        let resultAliases = undefined;
        if (!this._isInitializedAliases || !fcf.application.isRunning()) {
          resultAliases = fcf.append({}, fcf.application.getConfiguration().aliases);
          resultAliases = fcf.append(resultAliases, this._selfAliases);
          this._aliases = resultAliases;
        } else {
          resultAliases = this._aliases;
        }

        if (!this._isInitializedAliases && fcf.application.isRunning()) {
          this._isInitializedAliases = true;
        }

        return resultAliases;
      }

      this.resolveAlias = function(a_path) {
        if (a_path.charAt(0) != "@")
          return a_path;
        let subpart = a_path.split("+")[1];
        a_path = a_path.split("+")[0];
        let alias = a_path.substr(1);
        let aliases = this.getAliases();
        let result = alias in aliases ? aliases[alias] : a_path;
        if (subpart)
          result += "+" + subpart;
        return result;
      }

      this._appendIncludes = function() {
        var packs = fcf.application.getPackages();
        for(var packName in packs){
          if (packs[packName].isTheme)
            continue;
          var inf = packs[packName].getInfo();
          if (inf.html && inf.html.include)
            fcf.append(this.getInfo().html.include, inf.html.include);
        }
      }
    }

    return NRender.Theme;
  }
});
