////////////////// /   Html class //////////////////
function Html(args) {
        this._vm = null;

        if (debugMode && !isBrowser) {
            this._vm = require('vm');
            this._sandbox = Object.create(null);
            this._vm.createContext(this._sandbox);
        }

        // function (process,...){...}() prevents [this] to exist for the 'vm.runInNewContext()' method
        this._js = `
        'use strict';
(function (process, window, global, module, require, compilePage, compilePageSync, navigator, undefined) {
    delete Html._js;
    delete Html._vm;
    delete Html._sandbox;
    ${args.js}
}).call();`;

        // User section.
        if (debugMode)
            this.__dbg = { viewName: args.filePath, template: args.template, pos: [] }

        this.$ =
            this.layout = null;
        // Private
        let sectionName = null;
        let sections = args.parsedSections;

        this.__val = function (i) {
            return args.jsValues.getAt(i);
        };

        this.__renderLayout = (done) => {
            if (!this.layout) // if the layout is not defined..
                return Promise.resolve().then(() => done(null, args.html)), null;

            // looking for the `Layout`..
            args.er.isLayout = true; // the crutch
            args.findPartial(this.layout, args.filePath, args.er, (err, result) => {
                args.er.isLayout = false;
                if (err) return done(err);
                let compileOpt = {
                    scope: args.scope,
                    template: result.data,
                    filePath: result.filePath,
                    model: args.model,
                    bodyHtml: args.html,
                    findPartial: args.findPartial,
                    findPartialSync: args.findPartialSync,
                    parsedSections: args.parsedSections,
                    partialsCache: args.partialsCache,
                    viewData: args.viewData
                };
                compile(compileOpt, done);
            });
        };

        this.__sec = function (name) { // in section
            if (!sectionName) {
                sectionName = name;
            }
            else if (sectionName === name) {
                sections[sectionName][args.filePath].compiled = true;
                sectionName = null;
            }
            else {
                throw new Error(`Unexpected section name = '${name}'.`); // Cannot be tested via user-inputs.
            }
        };

        this.raw = function (val) { // render
            if (!isVisibleValue(val)) // 'undefined' can be passed when `Html.raw()` is used by user in the view, in this case it will be wrapped into `Html.ecnode()` anyway an it will call `Html.raw` passing 'undefined' to it.
                return;

            if (sectionName) {
                let sec = sections[sectionName][args.filePath];
                if (!sec.compiled) // it could have been compiled already if it's defined in a partial view which is rendred more than once
                    sec.html += val;
            }
            else {
                args.html += val;
            }
        };

        this.encode = function (val) {
            var encoded = this.getEncoded(val);
            this.raw(encoded);
        };

        this.getEncoded = function (val) {
            if (!isVisibleValue(val))
                return '';

            if (typeof val === "number" || val instanceof Number || val instanceof HtmlString)
                return val;

            if (String.is(val))
                return htmlEncode(val);

            return htmlEncode(val.toString());
        };

        this.body = function () {
            return new HtmlString(args.bodyHtml);
        };

        this.section = function (name, required) {
            if (!args.filePath)
                throw new Error("'args.filePath' is not set.");

            let secGroup = sections[name];

            if (secGroup) {
                if (secGroup.renderedBy)
                    throw args.er.sectionsAlreadyRendered(name, secGroup.renderedBy, args.filePath); // TESTME:

                let html = '';

                for (var key in secGroup) {
                    if (secGroup.hasOwnProperty(key)) {
                        let sec = secGroup[key];

                        if (!sec.compiled)
                            throw args.er.sectionIsNotCompiled(name, args.filePath); // [#3.2]

                        html += sec.html;
                    }
                }

                secGroup.renderedBy = args.filePath;
                return new HtmlString(html);
            }
            else {
                if (required)
                    throw args.er.sectionIsNotFound(name, args.filePath); // [#3.3]
            }

            return '';
        };

        this.getPartial = function (viewName, viewModel) {
            let compileOpt = {
                scope: args.scope,
                model: viewModel === undefined ? args.model : viewModel, // if is not set explicitly, set default (parent) model
                findPartial: args.findPartial,
                findPartialSync: args.findPartialSync,
                sections,
                parsedSections: args.parsedSections,
                partialsCache: args.partialsCache,
                viewData: args.viewData
            };

            // Read file and complie to JS.
            let partial = args.findPartialSync(viewName, args.filePath, args.er, args.partialsCache);
            compileOpt.template = partial.data;
            compileOpt.filePath = partial.filePath;

            if (partial.js) { // if it's taken from cache
                compileOpt.js = partial.js;
                compileOpt.jsValues = partial.jsValues;
            }

            let { html, precompiled } = compileSync(compileOpt);
            partial.js = precompiled.js; // put to cache
            partial.jsValues = precompiled.jsValues; // put to cache

            return html;
        };

        this.partial = function (viewName, viewModel) {
            var partialHtml = this.getPartial(viewName, viewModel);
            this.raw(partialHtml)
        };
    }