/**
* @namespace fcf::NRender
*lng_en @brief Rendering namespace
*lng_ru @brief Пространство имен рендеринга
**/


fcf.module({
  name: "fcf:NRender/Render.js",
  dependencies: ["fcf:NRender/NDetails/Loader.js",
                 "fcf:NRender/NDetails/TemplateProcessor.js",
                 "fcf:NRender/NDetails/ArgsBuilder.js",
                 "fcf:NRender/Template.js"
               ],
  module: function(Loader, TemplateProcessor, ArgsBuilder, Template) {
    var NRender = fcf.prepareObject(fcf, "NRender");

    NRender.Render = function(a_initializeOptions) {
      var self = this;

      this._options = fcf.append({}, a_initializeOptions);
      this._options.template = this._options.template;

      this._loader = new Loader();

      this.setFileСaching = function(a_value){
        this._options.fileСaching = a_value;
        this._loader.setSettings({fileСaching: a_value});
      }

      /*
      * @fn void render(object a_options)
      * @brief Выполняет рендеринг шаблона
      * @param object a_options
      *     - string template
      *     - bool   external          флаг внешнего запорса на отрисовку шаблона
      *     - object args [optional]
      *     - bool   update [optional] флаг обновления повторной отрисовки на клиенте
      *     - object state             внутренняя информация рекурсивного рендеринга
      *     - function onResult(a_error, a_template)
      **/
      this.render = function(a_options) {
        if (a_options.context)
          fcf.setContext(a_options.context);

        if (a_options.template[0] == ":")
          a_options.template = a_options.template.substr(1);

        var argsBuilder       = new ArgsBuilder({});
        var templateProcessor = new TemplateProcessor({
          render: this,
          loader: this._loader,
        });

        var fcfId        = a_options.args.fcfId ? a_options.args.fcfId :
                                                  fcf.genId();
        fcf.getContext().currentTemplate = { id: fcfId };
        var needOnServer = !fcf.isServer() && fcf.getContext().get("needBabel");
        needOnServer = !fcf.isServer();
        var breakExecution = needOnServer;
        var part = a_options.template.split("+")[1] ? a_options.template.split("+")[1] : "";
        var resultContext = "";
        var fullTemplate = undefined;
        var rawTemplate = undefined;
        var resultArgs = {};
        var resultSources = {};
        var srcArgs = {};

        var defaultThemeName = a_options.theme ? a_options.theme : "defaultTheme";
        var defaultTheme     = fcf.application.getThemes().getTheme(defaultThemeName, true);
        var externalArgs     = typeof a_options.externalArgs == "object" ? a_options.externalArgs : {};
        var isExternalEmpty = true;
        for(let k in externalArgs){
          externalArgs[k].__innerFcfPostRemove = true;
          isExternalEmpty = false;
        }
        var isRootCall       = !a_options.state;
        var state            = a_options.state ? a_options.state : { 
                                                                      id:                 fcfId,
                                                                      root:               undefined,
                                                                      theme:              defaultTheme, 
                                                                      defaultTheme:       defaultTheme,
                                                                      themes:             {},
                                                                      include:            {}, 
                                                                      renderRestore:      {},
                                                                      renderStorage:      {},
                                                                      reqursionCounter:   0, 
                                                                      args:               externalArgs, 
                                                                      sources:            {},
                                                                      resets:             {},
                                                                      originSourcesKeys:  {},
                                                                      page:               {
                                                                                            header: {
                                                                                              include:  [],
                                                                                              title:    "", 
                                                                                              keywords: [], 
                                                                                              header:   []
                                                                                            }
                                                                                          },
                                                                    };
        if (a_options.root)
          state.root = { template: a_options.template, id: fcfId };

        var action = new fcf.Actions();

        var theme = undefined;

        //loading template
        action.then(function(a_res, a_act) {
          if (needOnServer){
            a_act.complete();
            return;
          }

          theme = state.theme;
          self._loader.load(a_options.template, state, fcfId, function(a_error, a_fullTemplate) {
            if (a_error) {
              a_act.error(a_error);
              return;
            }

            fullTemplate = a_fullTemplate;
            rawTemplate  = fullTemplate.templates[part];

            for(let i = 0; i < a_fullTemplate.options.include.length; ++i)
              state.include[a_fullTemplate.options.include[i]] = a_fullTemplate.options.include[i];
            for(let i = 0; i < a_fullTemplate.options.webInclude.length; ++i)
              state.include[a_fullTemplate.options.webInclude[i]] = a_fullTemplate.options.webInclude[i];

            if (!rawTemplate){
              a_act.error(new fcf.Exception("ERROR_RENDER_TEMPLATE_NOT_FOUND", {template: a_options.template}));
              return;
            }

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

        // Detect serverOnly
        action.then(function(a_res, a_act) {
          if (needOnServer || (fullTemplate.options.serverOnly && !fcf.isServer())){
            breakExecution = true;

            fcf.loadObject({
              path:  "@url:fcfRender",
              post: { 
                template: a_options.template, 
                args: a_options.args,
                update: a_options.update,
                url: window.location.href,
                externalArgs: a_options.externalArgs,
              },
              onResult: function(a_error, a_response) {
                if (a_error){
                  a_act.error(a_error);
                  return;
                }

                var resultTemplate = new Template({content: a_response.content, id: a_response.id, args: a_response.args, state: a_response.state});
                fcf.append(state.args, a_response.state.args);
                fcf.append(state.sources, a_response.state.sources);
                fcf.append(state.resets, a_response.state.resets);

                if (!resultTemplate.args.fcfError) {
                  if (a_options.onResult)
                    a_options.onResult(undefined, resultTemplate);

                  a_act.complete(resultTemplate);
                } else {
                  a_act.error(resultTemplate.args.fcfError);
                }

              }
            });
          } else {
            a_act.complete();
          }
        });

        //build arguments
        action.then(function(a_result, a_act) {
          if(breakExecution){
            a_act.complete(a_result);
            return;
          }

          if ("fcfId" in rawTemplate.arguments){
            fcfId      = rawTemplate.arguments.fcfId;
            fcf.getContext().currentTemplate.id = fcfId;
            if (a_options.root)
              state.root.id = fcfId;
          }

          var args = typeof a_options.args == "object" ? a_options.args : {};
          argsBuilder.build({
            id:           fcfId,
            render:       self,
            request:      a_options.request,
            context:      a_options.context ? a_options.context : fcf.getContext(),
            state:        state,
            reqursion:    a_options.reqursion,
            templatePath: a_options.template,
            route:        a_options.route ? a_options.route : fcf.getContext().route,
            loader:       self._loader,
            projections:  a_options.projections,
            args:         rawTemplate.arguments,
            template:     rawTemplate,
            update:       a_options.update,
            inputArgs:    args,
            onResult:     function (a_error, a_args, a_sources) {
              if (a_error) {
                a_act.error(a_error);
                return;
              }
              resultArgs = a_args;
              resultSources = a_sources;
              a_act.complete();
            }
          });
        });

        //build template
        action.then(function(a_result, a_act) {
          if(breakExecution){
            a_act.complete(a_result);
            return;
          }

          var params = {
            options:          fullTemplate.options,
            render:           self,
            request:          a_options.request,
            context:          a_options.context ? a_options.context : fcf.getContext(),
            template:         a_options.template,
            rawTemplate:      rawTemplate,
            route:            a_options.route ? a_options.route : fcf.getContext().route,
            request:          a_options.request,
            args:             resultArgs,
            sources:          resultSources,
            projections:      a_options.projections,
            state:            state,
            loader:           self._loader,
            onResult:         function(a_error, a_content) {
              if (a_error) {
                a_act.error(a_error)
                return;
              }
              resultContext = a_content;
              a_act.complete();
            },
          };
          templateProcessor.build(params);
        });

        //create result template
        action.then(function(a_result, a_act) {
          if(breakExecution){
            a_act.complete(a_result);
            return;
          }

          if (!isExternalEmpty && isRootCall) {
            let rm = [];
            for(let k in state.args)
              if (state.args[k].__innerFcfPostRemove)
                rm.push(k);
            for(let i = 0; i < rm.length; ++i)
              delete state.args[rm[i]];
          }

          var args = resultArgs;
          delete args.fcfChildsArgs;

          var resultTemplate = new Template({
                                    content: resultContext, 
                                    id: resultArgs.fcfId, 
                                    args: args, 
                                    state: {
                                      args:              state.args, 
                                      sources:           state.sources, 
                                      originSourcesKeys: state.originSourcesKeys,
                                      include:           state.include,
                                      resets:            state.resets,
                                      page:              state.page,
                                    }
                                  });
          
          if (!resultTemplate.args.fcfError){
            if (a_options.onResult)
              a_options.onResult(undefined, resultTemplate);

            a_act.complete(resultTemplate);
          } else {
            a_act.error(resultTemplate.args.fcfError);
          }
        });

        action.catch(function(a_error){
          if (a_options.onResult)
            a_options.onResult(a_error, new Template({}));
        });

        return action;
      }

      this.getLoader = function(){
        return this._loader;
      }

      this.getStorage = function() {
        return this._options.storage;
      }
    }

    return NRender.Render;
  }
});
