// MIT License
//
// Copyright (c) 2016-2018, Alexander I. Chebykin
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

/**
 * CAI CP v.0.9.1
 *
 * @module     : Core
 * @author     : Alexander I. Chebykin <alex.chebykin@gmail.com>
 * @copyright  : Copyright (c) 2016-2018 Alexander I. Chebykin
 * @version    : 0.9.1
 * @build date : 2018-06-05
 * @license    : MIT
 * @link       : https://github.com/CAI79/CAI-CP
 ******************************************************************/

(function () {
    "use strict";

    /**
    * Return GET parameter
    *
    * @param  {string} key Key name
    *
    * returns {string}
    */
    function $_GET(key) {
        var s = window.location.search;

        s = s.match(new RegExp(key + "=([^&=]+)"));

        return s ? s[1] : false;
    }

    /**
     * Check value is numeric
     * 
     * @param   {variant} value Value to be checked
     * 
     * @returns {Boolean}
     */
    function isNumeric(value) {
      return !isNaN(parseFloat(value)) && isFinite(value);
    }

    /**
     * Return string with formatted numeric value. ex: 12345,6 -> 12'345,6
     * 
     * @param {variant}   value     Source value
     * @param {separator} separator Character used to separate thousands
     * 
     * @returns {String}
     */
    function numFormatted(value, separator) {
        var result = "",
            splitted = value.toString().split(" ");
            for (var i = 0; i < splitted.length; i++) {
                if (isNumeric(splitted[i])) {
                    result += " " + 
                            splitted[i].toString().replace(/\B(?=(\d{3})+(?!\d))/g, 
                                                            separator);
                } else {
                    result += " " + splitted[i].toString();
                }
            }
        return result;
    }

    /**
     * Add message with timestamp to browser's console 
     * 
     * @param   {string}    log_message Message text
     * 
     * @returns {undefined}
     */
    function console_log(log_message) {
        var now = new Date();
        console.log(now + ": " + log_message);
    }

    /**
     * Application constructor
     *
     * @returns {undefined}
     */
    function Application() {
        /* Object types */
        this.OT_TABLE_H = 1;
        this.OT_TABLE_V = 2;
        this.OT_IFRAME  = 3;
        this.OT_CANVAS  = 4;
    //  this.OT_DIV     = 5;

        this.timers    = [];
        this.apps_list = {};
        this.lang      = {};
        this.menu      = {};
        this.settings  = {};
        this.widgets   = {};

        this.blocks_rotation = ["system",
                                "system_pulse",
                                "storage",
                                "network",
                                "smb"];
        this.cur_block = 0;

        /* Monitoring variables */
        this.mon_users = 0;
        this.mon_users_info = {
            "count"    : 0,
            "last_user": "",
            "last_addr": ""
        };

        /* Ctrl pressed flag */
        this.ctrl_on = false;

        /* Down point for swipe event on header */
        this.hdr_x_down = null;
        this.hdr_y_down = null;

        this.init(1);
    }

    /**
     * Add events handlers
     *
     * @returns {undefined}
     */
    Application.prototype.add_evt_handlers = function () {
        var app_instance = this;

        /* Add swipe handler to header */
        document.getElementById("app_header").addEventListener("touchstart",
            function (event) {
                app_instance.hdr_x_down = event.touches[0].clientX;
                app_instance.hdr_y_down = event.touches[0].clientY;
            },
            false);
        document.getElementById("app_header").addEventListener("touchmove",
            function (event) {
                if (!app_instance.hdr_x_down || !app_instance.hdr_y_down) {
                    return;
                }
                var x_up = event.touches[0].clientX,
                    y_up = event.touches[0].clientY,
                    delta_x = app_instance.hdr_x_down - x_up,
                    delta_y = app_instance.hdr_y_down - y_up;

                if (Math.abs(delta_x) > Math.abs(delta_y)) {
                    if (delta_x > 0) {
                        /* left swipe */
                        app_instance.cur_block += 1;
                        if (app_instance.cur_block > app_instance.blocks_rotation.length - 1) {
                            app_instance.cur_block = 0;
                        }
                    } else {
                        /* right swipe */
                        app_instance.cur_block -= 1;
                        if (app_instance.cur_block < 0) {
                            app_instance.cur_block = app_instance.blocks_rotation.length - 1;
                        }
                    }
                    app_instance.widgets_run(app_instance.blocks_rotation[app_instance.cur_block]);
                    if (app_instance.blocks_rotation[app_instance.cur_block] === "system") {
                        app_instance.os_logo_draw();
                    }
            //    } else {
            //        if (delta_y > 0) {
                        /* up swipe */
            //        } else {
                        /* down swipe */
            //        }
                }
                /* reset values */
                app_instance.hdr_x_down = null;
                app_instance.hdr_y_down = null;
            },
            false);

        /* Add ctrl pressed flag switcher */
        document.addEventListener("keydown", function (event) {
            if ((event.which === 17) && !app_instance.ctrl_on) {
                app_instance.ctrl_on = true;
            }
            if (app_instance.ctrl_on) {
                /* Temporary turn off ctrl flag */
                app_instance.ctrl_on = false;
                switch (event.which) {
                case 37: // Ctrl + <-
                    app_instance.cur_block -= 1;
                    if (app_instance.cur_block < 0) {
                        app_instance.cur_block = app_instance.blocks_rotation.length - 1;
                    }
                    app_instance.widgets_run(app_instance.blocks_rotation[app_instance.cur_block]);
                    if (app_instance.blocks_rotation[app_instance.cur_block] === "system") {
                        app_instance.os_logo_draw();
                    }
                    event.preventDefault();
                    break;
                case 39: // Ctrl + ->
                    app_instance.cur_block += 1;
                    if (app_instance.cur_block > app_instance.blocks_rotation.length - 1) {
                        app_instance.cur_block = 0;
                    }
                    app_instance.widgets_run(app_instance.blocks_rotation[app_instance.cur_block]);
                    if (app_instance.blocks_rotation[app_instance.cur_block] === "system") {
                        app_instance.os_logo_draw();
                    }
                    event.preventDefault();
                    break;
                case 49: // Ctrl + 1
                    app_instance.widgets_run("system");
                    app_instance.os_logo_draw();
                    event.preventDefault();
                    break;
                case 50: // Ctrl + 2
                    app_instance.widgets_run("system_pulse");
                    event.preventDefault();
                    break;
                case 51: // Ctrl + 3
                    app_instance.widgets_run("storage");
                    event.preventDefault();
                    break;
                case 52: // Ctrl + 4
                    app_instance.widgets_run("network");
                    event.preventDefault();
                    break;
                case 53: // Ctrl + 5
                    app_instance.widgets_run("smb");
                    event.preventDefault();
                    break;
                case 112: // Ctrl + F1
                    app_instance.help_show(false);
                    event.preventDefault();
                    break;
                }

                app_instance.ctrl_on = true;
            }
        });
        document.addEventListener("keyup", function (event) {
            if ((event.which === 17) && app_instance.ctrl_on) {
                app_instance.ctrl_on = false;
            }
        });
    };

    /**
     * Get data by uri
     *
     * @param  {string}    uri Data uri
     *
     * returns {undefined}
     */
    Application.prototype.get_data = function (uri) {
        return new Promise(function(resolve, reject) {
            var xhr = new XMLHttpRequest();
            xhr.onload = function() {
                resolve(this.responseText);
            };
            xhr.onerror = reject;
            xhr.open("GET", uri);
            xhr.send();
        });
    };

    /**
     * Initialize application
     *
     * @param   {numeric} init_level Initialization level: 
     *                                  1 - Load settings,
     *                                  2 - Load localization
     *                                  3 - Initializa interface
     *
     * @returns {Boolean}
     */
    Application.prototype.init = function (init_level) {
        var app_instance = this,
            json_file;

        switch (init_level) {
        case 1:
            /* Add events handlers */
            this.add_evt_handlers();

            /* Load user defined settings */
            this.get_data("system/json/settings.json")
            .then(function(result) {
                app_instance.settings = JSON.parse(result);

                app_instance.init(2);
            })
            .catch(function(result) {
                console_log("init 1 error: " + result);
            });

            break;
        case 2:
            /* Load general settings */
            this.get_data("system/json/general_settings/")
            .then(function(result) {
                var general_settings = JSON.parse(result);

                app_instance.settings.shutdown_enabled =
                        general_settings.shutdown_enabled;
                app_instance.settings.reboot_enabled =
                        general_settings.reboot_enabled;
                app_instance.settings.w_transmission_enabled =
                        general_settings.w_transmission_enabled;

                app_instance.init(3);
            })
            .catch(function(result) {
                console_log("init 2 error: " + result);
            });

            break;
        case 3:
            /* Load locale */
            if ($_GET("lang")) {
                json_file = "system/json/locale/" + $_GET("lang") + ".json";
            } else {
                json_file = "system/json/locale/" + this.settings.lang + ".json";
                window.history.pushState(null, null, "?lang=" + this.settings.lang);
            }

            this.get_data(json_file)
            .then(function(result) {
                app_instance.lang = JSON.parse(result);

                app_instance.init(4);
            })
            .catch(function(result) {
                console_log("init 3 error: " + result);
            });

            break;
        case 4:
            /* Files rights check */
            if (this.settings.check_files_rights) {
                this.get_data("system/scripts/check_files.php")
                .then(function(result) {
                    if (JSON.parse(result).length > 0) {
                        var div_info = document.createElement("div"),
                            text_info = document.createTextNode(app_instance.lang.fs_errors);

                        div_info.style.cursor = "pointer";
                        div_info.appendChild(text_info);
                        div_info.addEventListener("click", function () {
                            app_instance.page_clear();
                            app_instance.widget_run(app_instance.w_table_files_check);
                        });
                        document.getElementById("div_attention_content").appendChild(div_info);
                        document.getElementById("div_attention").classList.remove("hidden");
                    }

                    app_instance.init(5);
                })
                .catch(function(result) {
                    console_log("init 4 error: " + result);
                });
            } else {
                this.init(5);
                return;
            }
            break;
        case 5:
            /* Interface init */
            /* Menu elements */
            this.menu = {
                "div_menu_info": {
                    "id"        : "div_menu_info",
                    "caption"   : this.lang.information,
                    "visible"   : true,
                    "separator" : false,
                    "elements": {
                        "info_system": {
                            "icon"     : "gfx/icons/server.png",
                            "caption"  : this.lang.system,
                            "callback" : function () {
                                app_instance.widgets_run("system");
                                app_instance.os_logo_draw();
                            }
                        },
                        "info_pulse": {
                            "icon"     : "gfx/icons/sysinfo.png",
                            "caption"  : this.lang.system_pulse,
                            "callback" : function () {
                                app_instance.widgets_run("system_pulse");
                            }
                        },
                        "info_storage": {
                            "icon"     : "gfx/icons/storage.png",
                            "caption"  : this.lang.storage,
                            "callback" : function () {
                                app_instance.widgets_run("storage");
                            }
                        },
                        "info_network": {
                            "icon"     : "gfx/icons/network.png",
                            "caption"  : this.lang.network,
                            "callback" : function () {
                                app_instance.widgets_run("network");
                            }
                        },
                        "info_smb": {
                            "icon"     : "gfx/icons/samba.png",
                            "caption"  : this.lang.smb,
                            "callback" : function () {
                                app_instance.widgets_run("smb");
                            }
                        }
                    }
                },
                "div_menu_app": {
                    "id"        : "div_menu_app",
                    "caption"   : this.lang.applications,
                    "visible"   : false,
                    "separator" : true,
                    "elements": {}
                },
                "div_settings": {
                    "id"        : "div_menu_settings",
                    "caption"   : "",
                    "visible"   : true,
                    "separator" : true,
                    "elements"  : {
                        "cp_settings": {
                            "icon"     : "gfx/icons/settings.png",
                            "caption"  : this.lang.settings,
                            "callback" : function () {
                                app_instance.settings_show();
                            }
                        },
                        "cp_help_about": {
                            "icon"     : "gfx/icons/about.png",
                            "caption"  : this.lang.about,
                            "callback" : function () {
                                app_instance.help_show(true);
                            }
                        },
                        "cp_help": {
                            "icon"     : "gfx/icons/help.png",
                            "caption"  : this.lang.help,
                            "callback" : function () {
                                app_instance.help_show(false);
                            }
                        }
                    }
                },
                "div_shutdown": {
                    "id"        : "div_menu_shutdown",
                    "caption"   : "",
                    "visible"   : true,
                    "separator" : true,
                    "elements": {
                        "srv_reboot": {
                            "icon"     : "gfx/icons/reboot.png",
                            "caption"  : this.lang.reboot,
                            "callback" : function () {
                                app_instance.shutdown(true);
                            }
                        },
                        "srv_shutdown": {
                            "icon"     : "gfx/icons/shutdown.png",
                            "caption"  : this.lang.shutdown,
                            "callback" : function () {
                                app_instance.shutdown(false);
                            }
                        }
                    }
                }
            };
            if (!this.settings.shutdown_enabled && !this.settings.reboot_enabled) {
                delete this.menu.div_shutdown;
            } else {
                if (!this.settings.shutdown_enabled) {
                    delete this.menu.div_shutdown.elements.srv_shutdown;
                }
                if (!this.settings.reboot_enabled) {
                    delete this.menu.div_shutdown.elements.srv_reboot;
                }
            }

            /* Widgets declarations */
            this.w_table_drv_temp = {
                parent     : "page_content",
                id         : "drv_temp_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.drv_temp_info,
                class_name : "tbl_info",
                json_uri   : "system/scripts/?widget=hdd_temp",
                raw_num    : true,
                refresh    : 60
            };
            this.w_table_drv_smart = {
                parent     : "page_content",
                id         : "drv_smart_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.drv_smart_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=hdd_smart",
                raq_num    : true,
                refresh    : 3600
            };
            this.w_table_fs_info = {
                parent     : "page_content",
                id         : "fs_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.fs_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=fs",
                raw_num    : true,
                refresh    : 30
            };
            this.w_table_iostat_info = {
                parent     : "page_content",
                id         : "iostat_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.iostat_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=io_stat",
                raw_num    : false,
                refresh    : 30
            };
            this.w_table_general_info = {
                parent     : "page_content",
                id         : "general_info",
                type       : this.OT_TABLE_V,
                label      : this.lang.general_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=general_info",
                raw_num    : true,
                refresh    : 10
            };
            this.w_table_cpu_info = {
                parent     : "page_content",
                id         : "cpu_info",
                type       : this.OT_TABLE_V,
                label      : this.lang.cpu_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=cpu_info",
                raw_num    : false,
                refresh    : 0
            };
            this.w_table_memory_info = {
                parent     : "page_content",
                id         : "memory_info",
                type       : this.OT_TABLE_V,
                label      : this.lang.memory_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=mem_info",
                raw_num    : false,
                refresh    : 0
            };
            this.w_table_processes = {
                parent     : "page_content",
                id         : "proc_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.proc_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=processes",
                raw_num    : false,
                refresh    : 15
            };
            this.w_table_arp_cache = {
                parent     : "page_content",
                id         : "net_arp_cache_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_arp_cache_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=arp_cache",
                raw_num    : true,
                refresh    : 0
            };
            this.w_table_ip_addr = {
                parent     : "page_content",
                id         : "net_ip_addr_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_ip_addr_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=ip_addr",
                raw_num    : true,
                refresh    : 0
            };
            this.w_table_ip_route = {
                parent     : "page_content",
                id         : "net_ip_route_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_ip_route_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=ip_route",
                raw_num    : true,
                refresh    : 0
            };
            this.w_table_net_band = {
                parent     : "page_content",
                id         : "net_band_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_band_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=net_band",
                raw_num    : false,
                refresh    : 15
            };
            this.w_table_net_conn = {
                parent     : "page_content",
                id         : "net_conn_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_conn_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=net_conn",
                raw_num    : true,
                refresh    : 15
            };
            this.w_table_net_mcast = {
                parent     : "page_content",
                id         : "net_mcast_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_mcast_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=net_multicast",
                raw_num    : true,
                refresh    : 0
            };
            this.w_table_lstn_socks = {
                parent     : "page_content",
                id         : "net_lstn_socks_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_lstn_socks_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=listen_socks",
                raw_num    : true,
                refresh    : 0
            };
            this.w_table_unix_socks = {
                parent     : "page_content",
                id         : "net_unix_socks_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.net_unix_socks_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=active_unix_socks",
                raw_num    : true,
                refresh    : 0
            };
            this.w_table_transmission = {
                parent     : "page_content",
                id         : "transmission_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.transmission_info,
                class_name : "tbl_info",
                json_uri   : "system/scripts/json_transmission.php",
                raw_num    : false,
                refresh    : 30
            };
            this.w_table_users_online = {
                parent     : "page_content",
                id         : "users_online_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.users_online_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=users_online",
                raw_num    : true,
                refresh    : 30
            };
            this.w_table_smb_shares = {
                parent     : "page_content",
                id         : "smb_shares_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.smb_shares_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=smb_shares",
                raw_num    : true,
                refresh    : 30
            };
            this.w_table_smb_proc = {
                parent     : "page_content",
                id         : "smb_proc_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.smb_proc_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=smb_proc",
                raw_num    : true,
                refresh    : 30
            };
            this.w_table_smb_locks = {
                parent     : "page_content",
                id         : "smb_locks_info",
                type       : this.OT_TABLE_H,
                label      : this.lang.smb_locks_info,
                class_name : "tbl_info",
                json_uri   : "system/json/?widget=smb_locks",
                raw_num    : true,
                refresh    : 30
            };
            this.w_canvas_cpu_graph = {
                parent     : "page_content",
                id         : "cpu_graph",
                type       : this.OT_CANVAS,
                label      : this.lang.cpu_info,
                class_name : "canvas_info",
                json_uri   : "system/json/?widget=cpu_load",
                refresh    : 5,
                min_val    : 0,
                max_val    : 100,
                raw_num    : true,
                measure    : "%"
            };
            this.w_canvas_mem_graph = {
                parent     : "page_content",
                id         : "mem_graph",
                type       : this.OT_CANVAS,
                label      : this.lang.memory_info,
                class_name : "canvas_info",
                json_uri   : "system/json/?widget=mem",
                refresh    : 5,
                min_val    : 0,
                max_val    : "auto",
                raw_num    : true,
                measure    : "MB"
            };
            this.w_canvas_net_down_graph = {
                parent     : "page_content",
                id         : "net_down_graph",
                type       : this.OT_CANVAS,
                label      : this.lang.net_down_info,
                class_name : "canvas_info",
                json_uri   : "system/json/?widget=net_download_transfer_rate",
                refresh    : 5,
                min_val    : 0,
                max_val    : "auto",
                raw_num    : true,
                measure    : "KB/s"
            };
            this.w_canvas_net_up_graph = {
                parent     : "page_content",
                id         : "net_up_graph",
                type       : this.OT_CANVAS,
                label      : this.lang.net_up_info,
                class_name : "canvas_info",
                json_uri   : "system/json/?widget=net_upload_transfer_rate",
                refresh    : 5,
                min_val    : 0,
                max_val    : "auto",
                raw_num    : true,
                measure    : "KB/s"
            };
            this.w_canvas_swap_graph = {
                parent     : "page_content",
                id         : "swap_graph",
                type       : this.OT_CANVAS,
                label      : this.lang.swap_info,
                class_name : "canvas_info",
                json_uri   : "system/json/?widget=swap",
                refresh    : 5,
                min_val    : 0,
                max_val    : "auto",
                raw_num    : true,
                measure    : "MB"
            };

            this.w_table_files_check = {
                parent     : "page_content",
                id         : "files_check",
                type       : this.OT_TABLE_H,
                label      : this.lang.fs_check,
                class_name : "tbl_info",
                raw_num    : true,
                json_uri   : "system/scripts/check_files.php"
            };

            this.w_reboot_iframe = {
                parent     : "page_content",
                id         : "reboot_iframe",
                type       : this.OT_IFRAME,
                label      : this.lang.reboot,
                class_name : "app_widget",
                json_uri   : "system/action/?lang=" + $_GET("lang") + "&do=reboot"
            };
            this.w_shutdown_iframe = {
                parent     : "page_content",
                id         : "shutdown_iframe",
                type       : this.OT_IFRAME,
                label      : this.lang.shutdown,
                class_name : "app_widget",
                json_uri   : "system/action/?lang=" + $_GET("lang") + "&do=shutdown"
            };

            this.w_settings_iframe = {
                parent     : "page_content",
                id         : "iframe_settings",
                type       : this.OT_IFRAME,
                label      : this.lang.settings,
                class_name : "app_widget",
                json_uri   : "system/ui/forms/settings/?lang=" + $_GET("lang")
            };

            this.w_about_iframe = {
                parent     : "page_content",
                id         : "iframe_cp_about",
                type       : this.OT_IFRAME,
                label      : this.lang.help,
                class_name : "app_widget",
                json_uri   : "system/help/?lang=" + $_GET("lang") + "#about"
            };
            this.w_help_iframe = {
                parent     : "page_content",
                id         : "iframe_cp_help",
                type       : this.OT_IFRAME,
                label      : this.lang.help,
                class_name : "app_widget",
                json_uri   : "system/help/?lang=" + $_GET("lang")
            };

            /* Widgets lists */
            this.widgets_lists = {
                "storage": [
                    {
                        "page_title" : this.lang.storage
                    },
                    this.w_table_drv_temp,
                    this.w_table_drv_smart,
                    this.w_table_fs_info,
                    this.w_table_iostat_info
                ],
                "network": [
                    {
                        "page_title" : this.lang.network
                    },
                    this.w_canvas_net_down_graph,
                    this.w_canvas_net_up_graph,
                    {
                        "break" : true
                    },
                    this.w_table_ip_addr,
                    this.w_table_net_band,
                    this.w_table_ip_route,
                    this.w_table_users_online,
                    this.w_table_net_mcast,
                    this.w_table_arp_cache,
                    this.w_table_lstn_socks,
                    {
                        "widget_css" : "div_widget_full_width"
                    },
                    this.w_table_unix_socks,
                    {
                        "widget_css" : "div_widget_full_width"
                    },
                    this.w_table_net_conn,
                    this.w_table_transmission
                ],
                "smb": [
                    {
                        "page_title" : this.lang.smb
                    },
                    this.w_table_smb_proc,
                    this.w_table_smb_shares,
                    {
                        "widget_css" : "div_widget_full_width"
                    },
                    this.w_table_smb_locks
                ],
                "system": [
                    {
                        "page_title" : this.lang.system,
                        "widget_css" : "div_widget_type_a"
                    },
                    this.w_table_general_info,
                    this.w_table_memory_info,
                    this.w_table_cpu_info
                ],
                "system_pulse": [
                    {
                        "page_title" : this.lang.system_pulse
                    },
                    this.w_canvas_cpu_graph,
                    this.w_canvas_mem_graph,
                    this.w_canvas_swap_graph,
                    this.w_canvas_net_down_graph,
                    this.w_canvas_net_up_graph,
                    {
                        "widget_css" : "div_widget_full_width"
                    },
                    this.w_table_processes
                ]
            };

            this.server_name_fill();
            this.language_selector_add();

            var m_menu = new MainMenu(this.lang);

            m_menu.clear_menu(m_menu.main_menu);
            m_menu.build_menu(this.menu);

            this.apps_list_load();
  //        app.check_apps();
            if (window.location.hash.substr(1) !== "") {
                if (window.location.hash.substr(1) === "settings") {
                    this.settings_show();
                } else if (window.location.hash.substr(1) === "cp_help") {
                    this.help_show(false);
                } else if (window.location.hash.substr(1) === "cp_about") {
                    this.help_show(true);
                } else if (window.location.hash.substr(1, 3) !== "app") {
                    this.widgets_run(window.location.hash.substr(1));
                    if (window.location.hash.substr(1) === "system") {
                        this.os_logo_draw();
                    }
                } else {
                    this.app_execute(window.location.hash.substr(1));
                }
            } else {
                this.widgets_run("system");
                this.os_logo_draw();
            }

            /* Enable S.M.A.R.T and temperature monitoring */
            this.monitoring_enable();

            return true;
        }
    };

    /**
     * Add language selector to page's header
     *
     * @returns {undefined}
     */
    Application.prototype.language_selector_add = function () {
        var header = document.getElementById("app_header"),
            select_lang = document.createElement("select");

        select_lang.id = "lang";
        select_lang.classList.add("select_lang");

        if ($_GET("lang")) {
            select_lang.classList.add("input_" + $_GET("lang"));
        } else {
            select_lang.classList.add("input_" + this.settings.lang);
        }

        select_lang.onchange = function () {
            /*
             * window.open(URL,name,specs,replace): where name:
             *
             * _blank  - URL is loaded into a new window. This is default
             * _parent - URL is loaded into the parent frame
             * _self   - URL replaces the current page
             * _top    - URL replaces any framesets that may be loaded
             * name    - The name of the window (Note: the name does not specify
             *           the title of the new window)
             */
            window.open(this.options[this.selectedIndex].value, "_self");
        };

        for (var language in this.settings.langs) {
            if (this.settings.langs.hasOwnProperty(language)) {
                var opt_lang = document.createElement("option");

                opt_lang.value = "?lang=" + language;
                opt_lang.appendChild(document.createTextNode(this.settings.langs[language]));

                if ($_GET("lang") && $_GET("lang") === language) {
                    opt_lang.selected = true;
                } else if (!$_GET("lang") && this.settings.lang === language){
                    opt_lang.selected = true;
                }

                select_lang.appendChild(opt_lang);
            }
        }
        header.appendChild(select_lang);
    };

    /**
     * Change page title
     *
     * @param   {string}    new_title New page title
     *
     * @returns {undefined}
     */
    Application.prototype.page_title_change = function (new_title) {
        var srv_title = document.getElementById("s_header").textContent;

        document.title = document.getElementById("s_header").textContent =
            srv_title.substring(0, srv_title.indexOf("#") + 1) + " " + new_title;
    };

    /**
     * Check application existance and remove from menu if unexisted
     *
     * @param   {string}    app_name Application name
     * @param   {string}    app_exe  Application executable
     *
     * @returns {undefined}
     */
    Application.prototype.app_remove_unexisted = function (app_name, app_exe) {
        this.get_data("system/json/?widget=check_app&param=" + app_exe)
        .then(function(result) {
            var element = document.getElementById("div_menu_" + app_name),
                json_data = JSON.parse(result);

            if ((json_data.exec !== "") && (!json_data.installed)) {
                element.parentNode.removeChild(element);
            } else {
                element.classList.remove("hidden");
            }
        })
        .catch(function(result) {
            console_log("app_remove_unexisted error: " + result);
        });
    };

    /**
     * Draw OS distro logo
     *
     * @returns {undefined}
     */
    Application.prototype.os_logo_draw = function () {
        this.get_data("system/json/?widget=os_distr")
        .then(function(result) {
            var json_data = JSON.parse(result),
                div_gi = document.getElementById("general_info");

            if (!div_gi) {
                return;
            }

            var img_logo = document.createElement("img");

            img_logo.classList.add("distro_logo");

            if (json_data.Distr.match(/opensuse/i)) {
                img_logo.src = "gfx/distros/opensuse.png";
            } else if (json_data.Distr.match(/arch/i)) {
                img_logo.src = "gfx/distros/arch.png";
            } else if (json_data.Distr.match(/bsd/i)) {
                img_logo.src = "gfx/distros/bsd.png";
            } else if (json_data.Distr.match(/debian/i)) {
                img_logo.src = "gfx/distros/debian.png";
            } else if (json_data.Distr.match(/fedora/i)) {
                img_logo.src = "gfx/distros/fedora.png";
            } else if (json_data.Distr.match(/ubuntu/i)) {
                img_logo.src = "gfx/distros/ubuntu.png";
            } else if (json_data.Family.match(/linux/i)) {
                img_logo.src = "gfx/distros/linux.png";
            } else if (json_data.Family.match(/darwin/i)) {
                img_logo.src = "gfx/distros/apple.png";
            } else {
                img_logo.src = "gfx/distros/unknown.png";
            }

            div_gi.appendChild(img_logo);
        })
        .catch(function(result) {
            console_log("Can't get disto info: " + result);
        });
    };

    /**
     * Clear page content
     *
     * returns {undefined}
     */
    Application.prototype.page_clear = function () {
        var parent_node = document.getElementById("page_content");

        /* Clear timers */
        for (var key in this.timers) {
            if (this.timers.hasOwnProperty(key)) {
                clearInterval(this.timers[key]);
            }
        }
        this.timers = [];

        /* Remove widgets */
        while (parent_node.firstChild) {
            parent_node.removeChild(parent_node.firstChild);
        }
    };

    /**
     * Open control panel with selected application or widgets screen in new 
     * window
     *
     * @param   {string}   hash URL hash
     *
     * @returns {undefined}
     */
    Application.prototype.app_new_window = function (hash) {
        var new_location = location.href.replace(location.hash,""),
            new_win = window.open(new_location + "#" + hash, "_blank");

        new_win.focus();

        return;
    };

    /**
     * Draw application widget
     *
     * @param   {object}    w_info Widget info
     *
     * @returns {undefined}
     */
    Application.prototype.app_widget_draw = function (w_info) {
        var app_hash = w_info.id.substring(7);

        if (this.ctrl_on) {
            this.app_new_window(app_hash);
            return;
        }

        var app_widget = new Widget(this, w_info);

        this.page_clear();
        this.page_title_change(w_info.label);

        app_widget.draw();

        location.hash = app_hash;

        document.getElementById(w_info.id).classList.remove("slightly_visible");
    };

    /**
     * Fill server name on HTML page
     *
     * @returns {undefined}
     */
    Application.prototype.server_name_fill = function () {
        this.get_data("system/json/?widget=srv_name")
        .then(function(result) {
            var s_header = document.getElementById("s_header"),
                json_data = JSON.parse(result);

            if (s_header.textContent !== "Loading...") {
                var temp = s_header.textContent;

                s_header.removeChild(s_header.childNodes[0]);
                s_header.appendChild(document.createTextNode(
                                        json_data.server_name + " #" + temp));
            } else {
                s_header.removeChild(s_header.childNodes[0]);
                s_header.appendChild(document.createTextNode(
                                        json_data.server_name + " #"));
            }

            document.title = s_header.textContent;
        })
        .catch(function(result) {
            console_log("server_name_fill error: " + result);
        });
    };

    /**
     * Execute application
     *
     * @param   {string}    app_name application name
     *
     * @returns {undefined}
     */
    Application.prototype.app_execute = function (app_name) {
        var app_instance = this;

        this.get_data("system/apps/" + app_name.substr(4) + "/app.json")
        .then(function(result) {
// TODO: Check why https_uri
            var json_data = JSON.parse(result),
                uri = json_data.https_uri,
                app_iframe   = {
                    parent     : "page_content",
                    id         : "iframe_" + json_data.app_name,
                    type       : app_instance.OT_IFRAME,
                    label      : json_data.caption,
                    class_name : "app_widget",
                    json_uri   : uri.replace("[server_name]",
                                             window.location.hostname)
            };

            app_instance.app_widget_draw(app_iframe);
        })
        .catch(function(result) {
            console_log("app_execute error: " + result);
        });
    };

    /**
     * Enable HDD S.M.A.R.T. monitoring
     *
     * @param   {numeric} timeout Timeout in seconds
     * 
     * @returns {undefined}
     */
    Application.prototype.monitor_hdd_smart = function (timeout) {
        var app_instance = this;

        setTimeout(function () {
            app_instance.get_data("system/json/?widget=hdd_smart")
            .then(function(result) {
                var hdd_smart = JSON.parse(result),
                    cur_date = new Date();

                for (var hdd in hdd_smart) {
                    if ((hdd_smart[hdd].status.toLowerCase() !== "passed") &&
                            (hdd_smart[hdd].status.toLowerCase() !== "ok")) {
                        app_instance.notify(app_instance.lang.attention,
                            {body: cur_date.toLocaleString() + ": " +
                                    app_instance.lang.hdd_smart_error + " " +
                                    hdd_smart[hdd].drive + " (" +
                                    hdd_smart[hdd].status + ")",
                            tag: "cai_cp",
                            icon: "../gfx/icons/attention.png",
                            dir: "auto"},
                            function () {
                                app_instance.widgets_run("storage");
                            });
                    }
                }

                console_log("HDD SMART monitor");
                app_instance.monitor_hdd_smart(timeout);
            })
            .catch(function(result) {
                console_log("monitor_hdd_smart error: " + result);
                app_instance.monitor_hdd_smart(timeout);
            });
        },
        timeout * 1000);
    };

    /**
     * Enable HDD temperature monitoring
     *
     * @param   {numeric} timeout Timeout in seconds
     * 
     * @returns {undefined}
     */
    Application.prototype.monitor_hdd_temp = function (timeout) {
        var app_instance = this;

        setTimeout(function () {
            app_instance.get_data("system/scripts/?widget=hdd_temp")
            .then(function(result) {
                var hdd_temp = JSON.parse(result),
                    cur_date = new Date();

                for (var hdd in hdd_temp) {
                    if (hdd_temp[hdd].temperature.substring(0,
                        hdd_temp[hdd].temperature.length - 2) > app_instance.settings.max_hdd_temp) {
                        app_instance.notify(app_instance.lang.attention,
                            {body: cur_date.toLocaleString() + ": " +
                                    app_instance.lang.hdd_overheat + " " +
                                    hdd_temp[hdd].drive + " (" +
                                    hdd_temp[hdd].model + "): " +
                                    hdd_temp[hdd].temperature,
                            tag: "cai_cp",
                            icon: "../gfx/icons/attention.png",
                            dir: "auto"},
                            function () {
                                app_instance.widgets_run("storage");
                            });
                    }
                }

                console_log("HDD temperature monitor");
                app_instance.monitor_hdd_temp(timeout);
            })
            .catch(function(result) {
                console_log("monitor_hdd_temp error: " + result);
                app_instance.monitor_hdd_temp(timeout);
            });
        },
        timeout * 1000);
    };

    /**
     * Enable online users monitoring
     *
     * @param   {numeric} timeout Timeout in seconds
     * 
     * @returns {undefined}
     */
    Application.prototype.monitor_users_online = function (timeout) {
        var app_instance = this;

        setTimeout(function () {
            app_instance.get_data("system/scripts/?widget=users_online")
            .then(function(result) {
                var users_online = JSON.parse(result),
                    cur_date = new Date();

                if (users_online.length > app_instance.mon_users_info.count ||
                    users_online[users_online.length - 1].from !== app_instance.mon_users_info.last_addr ||
                    users_online[users_online.length - 1].user !== app_instance.mon_users_info.last_user) {
                    app_instance.notify(app_instance.lang.attention,
                    {body: cur_date.toLocaleString() + ": " +
                            app_instance.lang.new_user_connected +
                            "  (" +
                            users_online[users_online.length - 1].user +
                            ", " +
                            users_online[users_online.length - 1].from +
                            ")",
                    tag: "cai_cp",
                    icon: "../gfx/icons/attention.png",
                    dir: "auto"},
                    function () {
                        app_instance.widgets_run("network");
                    });
                }
                app_instance.mon_users_info.count     = users_online.length;
                app_instance.mon_users_info.last_user = users_online[users_online.length - 1].user;
                app_instance.mon_users_info.last_addr = users_online[users_online.length - 1].from;

                console_log("Online users monitor");
                app_instance.monitor_users_online(timeout);
            })
            .catch(function(result) {
                console_log("monitor_users_online error: " + result);
                app_instance.monitor_users_online(timeout);
            });
        },
        timeout * 1000);
    };

    /**
     * Enable monitoring functions
     *
     * @returns {undefined}
     */
    Application.prototype.monitoring_enable = function () {
        /* Set HDD temperature monitoring */
        if (isNaN(this.settings.check_hdd_temp_interval)) {
            this.settings.check_hdd_temp_interval = 0;
        } else {
            this.monitor_hdd_temp(this.settings.check_hdd_temp_interval);
        }

        /* Set S.M.A.R.T. monitoring */
        if (isNaN(this.settings.check_smart_interval)) {
            this.settings.check_smart_interval = 0;
        } else {
            this.monitor_hdd_smart(this.settings.check_smart_interval);
        }

        /* Set connected users monitoring */
        if (isNaN(this.settings.check_users_online_interval)) {
            this.settings.check_users_online_interval = 0;
        } else {
            this.monitor_users_online(this.settings.check_users_online_interval);
        }
    };

    /**
     * Show browser notification
     *
     * @param {string}   title    Notigication title
     * @param {object}   options  Notification options
     * @param {function} callback Callback function
     *
     * @returns {undefined}
     */
    Application.prototype.notify = function (title, options, callback) {
        if (!("Notification" in window) ||
                (Notification.permission.toLowerCase() === "denied")) {
            /* Notifications not supported or denied */
            return;
        }

        if (Notification.permission === "granted") { // Notifications allowed
            var notification = new Notification(title, options);

            notification.onclick = callback;/* function () {
                console.log("notification clicked");
            }; */
        } else { // Trying to get permissions
            Notification.requestPermission(function (permission) {
                if (permission === "granted") {
                    var notification = new Notification(title, options);
                } /*else {
                    console.log("notifications prohibited");
                } */
            });
        }
    };

    /**
     * Register applications in menu
     *
     * @returns {undefined}
     */
    Application.prototype.apps_register = function () {
        var m_menu = new MainMenu(this.lang);

        for (var key in this.apps_list) {
            if (this.apps_list.hasOwnProperty(key)) {
                m_menu.register_app(this, this.apps_list[key]);
                document.getElementById("div_menu_app").classList.remove("hidden");
            }
        }
    };

    /**
     * Load applications list
     *
     * @returns {undefined}
     */
    Application.prototype.apps_list_load = function () {
        var app_instance = this;

        this.get_data("system/apps/apps.json")
        .then(function(result) {
            var json_data = JSON.parse(result);
            app_instance.apps_list = json_data;
            app_instance.apps_register();
        })
        .catch(function(result) {
            console_log("apps_list_load error:" + result);
        });
    };

    /**
     * Run widget
     *
     * @param   {object}    widget_info Widget description
     *
     * @returns {undefined}
     */
    Application.prototype.widget_run = function (widget_info) {
        Object.size = function(obj) {
            var size = 0, key;
            for (key in obj) {
                if (obj.hasOwnProperty(key)) size++;
            }
            return size;
        };

        var w_index = Object.size(this.widgets); //this.widgets.length;

        this.widgets[w_index] = new Widget(this, widget_info);
        this.widgets[w_index].draw(this.widgets[w_index]);

        if (widget_info.type === this.OT_TABLE_H ||
            widget_info.type === this.OT_TABLE_V) {
            this.widgets[w_index].update_table_data(this, w_index);
            if (widget_info.refresh > 0) {
                this.timers[widget_info.id] = 
                        setInterval(this.widgets[w_index].update_table_data,
                                    widget_info.refresh * 1000,
                                    this, w_index);
            }
        } else if (widget_info.type === this.OT_CANVAS) {
            this.widgets[w_index].update_canvas_data(this, w_index);
            if (widget_info.refresh > 0) {
                this.timers[widget_info.id] = 
                        setInterval(this.widgets[w_index].update_canvas_data,
                                    widget_info.refresh * 1000,
                                    this, w_index);
            }
        }
    };

    /**
     * Draws application widgets
     *
     * @param   {array}     block_name Name of widgets block
     *
     * @returns {undefined}
     */
    Application.prototype.widgets_run = function (block_name) {
        /* Open in new window if ctrl is pressed */
        if (this.ctrl_on) {
            this.app_new_window(block_name);
            return;
        }

        if (typeof block_name === "undefined") {
            return;
        }

        this.cur_block = this.blocks_rotation.indexOf(block_name);

        var class_name = "div_widget",
            no_widgets = false;

        this.page_clear();

        location.hash = block_name;

        this.widgets = {};

        for (var i = 0; i < this.widgets_lists[block_name].length; i++) {
            no_widgets = false;

            if (this.widgets_lists[block_name][i].widget_css !== undefined) {
                /* Apply widget css */
                no_widgets = true;
                class_name = this.widgets_lists[block_name][i].widget_css;
            } else if (this.widgets_lists[block_name][i].break !== undefined &&
                       this.widgets_lists[block_name][i].break === true) {
                /* Insert break */
                var div_break       = document.createElement("div");
                div_break.className = "div_clear";

                document.getElementById("page_content").appendChild(div_break);

                no_widgets = true;
            }

            if (this.widgets_lists[block_name][i].page_title !== undefined) {
                this.page_title_change(this.widgets_lists[block_name][i].page_title);

                no_widgets = true;
            }

            /* If widget info */
            if (!no_widgets) {
                if (!this.settings.w_transmission_enabled &&
                    this.widgets_lists[block_name][i].id === "transmission_info") {
                        /* Skip transmission widget if disabled */
                } else {
                    this.widget_run(this.widgets_lists[block_name][i]);
                    /* Apply widget style class */
                    if (class_name !== "div_widget") {
                        document.getElementById(this.widgets_lists[block_name][i].id).classList.remove("div_widget");
                        document.getElementById(this.widgets_lists[block_name][i].id).classList.add(class_name);
                    }
                }
            }
        }
    };

    /**
     * Show help
     *
     * @param   {bool}      about_info Show "About" info
     *
     * @returns {undefined}
     */
    Application.prototype.help_show = function (about_info) {
        if (!this.ctrl_on) {
            this.page_clear();
        }

        if (about_info) {
            this.app_widget_draw(this.w_about_iframe);
        } else {
            this.app_widget_draw(this.w_help_iframe);
        }
    };

    /**
     * Show settings form
     *
     * @returns {undefined}
     */
    Application.prototype.settings_show = function () {
        if (!this.ctrl_on) {
            this.page_clear();
        }

        this.app_widget_draw(this.w_settings_iframe);
    };

    /**
     * Shutdown server
     *
     * @param   {bool}      reboot Reboot system flag
     *
     * @returns {undefined}
     */
    Application.prototype.shutdown = function (reboot) {
        if (reboot && window.confirm (this.lang.reboot_confirm)) {
            this.page_clear();
            this.app_widget_draw(this.w_reboot_iframe);
        } else if (!reboot && window.confirm (this.lang.shutdown_confirm)) {
            this.page_clear();
            this.app_widget_draw(this.w_shutdown_iframe);
        }
    };

    /**
     * Main menu constructor
     *
     * @param {string} lang Language
     *
     * @returns {undefined}
     */
    function MainMenu(lang) {
        this.main_menu     = document.getElementById("div_menu");
        this.info_menu     = document.getElementById("div_menu_info");
        this.app_menu      = document.getElementById("div_menu_app");
        this.settings_menu = document.getElementById("div_menu_settings");
        this.lang          = lang;
    }

    /**
     * Add icons block to menu
     *
     * @param   {array} block_info Menu block information
     *
     * @returns {object}           Menu block's div
     */
    MainMenu.prototype.add_block = function (block_info) {
        var div_menu_block         = document.createElement("div"),
            div_menu_block_caption = document.createElement("div");

        div_menu_block.id = block_info.id;
        div_menu_block.classList.add("div_menu_block");

        if (!block_info.visible) {
            div_menu_block.classList.add("hidden");
        }

        this.main_menu.appendChild(div_menu_block);

        div_menu_block_caption.classList.add("div_menu_block_caption");
        div_menu_block_caption.appendChild(document.createTextNode(block_info.caption));
        div_menu_block.appendChild(div_menu_block_caption);

        if (block_info.separator) {
            div_menu_block.appendChild(document.createElement("hr"));
        }

        return div_menu_block;
    };

    /**
     * Add menu item to menu block
     *
     * @param   {object}    parent_block      Parent block for icon
     * @param   {string}    id                Menu item id
     * @param   {string}    icon_src          Path to icon file
     * @param   {string}    caption           Menu caption
     * @param   {bool}      hidden            Add menu in hidden state
     * @param   {object}    callback_function OnClick callback function
     *
     * @returns {undefined}
     */
    MainMenu.prototype.add_item = function (parent_block,
                                            id,
                                            icon_src,
                                            caption,
                                            hidden,
                                            callback_function) {
        var m_item_div        = document.createElement("div"),
            m_item_img        = document.createElement("img"),
            m_item_footer_div = document.createElement("div");

        m_item_div.id = "div_" + id;
        m_item_div.classList.add("div_m_item");
        m_item_div.onclick = callback_function;
/*
m_item_div.addEventListener("contextmenu", function(e) {
    e.preventDefault();
});
*/
        if (hidden) {
            m_item_div.classList.add("hidden");
        }

        m_item_img.classList.add("menu_icon");
        m_item_img.src = icon_src;
        m_item_div.appendChild(m_item_img);

        m_item_footer_div.classList.add("div_m_item_footer");
        m_item_footer_div.appendChild(document.createTextNode(caption));
        m_item_div.appendChild(m_item_footer_div);

        parent_block.appendChild(m_item_div);
    };

    /**
     * Build menu structure
     *
     * @param {object} menu_items menu structure
     *
     * @returns {undefined}
     */
    MainMenu.prototype.build_menu = function (menu_items) {
        var div_ctrl_hint = document.createElement("div");

        div_ctrl_hint.classList.add("top_right_hint");
        div_ctrl_hint.appendChild(document.createTextNode(this.lang.ctrl_click_hint));

        this.main_menu.appendChild(div_ctrl_hint);

        for (var menu_block in menu_items) {
            if (menu_items.hasOwnProperty(menu_block)) {
                var div_menu_block = this.add_block(menu_items[menu_block]);

                for (var menu_item in menu_items[menu_block].elements) {
                    if (menu_items[menu_block].elements.hasOwnProperty(menu_item)) {
                        this.add_item(
                            div_menu_block,
                            menu_item,
                            menu_items[menu_block].elements[menu_item].icon,
                            menu_items[menu_block].elements[menu_item].caption,
                            false,
                            menu_items[menu_block].elements[menu_item].callback
                        );
                    }
                }

                var div_clear = document.createElement("div");

                div_clear.classList.add("div_clear");
                div_menu_block.appendChild(div_clear);

                this.main_menu.appendChild(div_menu_block);
            }
        }
    };

    /**
     * Clear menu or submenu
     *
     * @param {object} menu Menu object to clear
     *
     * @returns {undefined}
     */
    MainMenu.prototype.clear_menu = function (menu) {
        while (menu.firstChild) {
            menu.removeChild(menu.firstChild);
        }
    };

    /**
     * Register application in menu
     *
     * @param {object} app_instance Application instance
     * @param {string} app_dir      Directory containing app.json file with 
     *                              application info
     *
     * @returns {undefined}
     */
    MainMenu.prototype.register_app = function (app_instance, app_dir) {
        var menu_obj = this;

        app_instance.get_data("system/apps/" + app_dir + "/app.json")
        .then(function(result) {
            var json_data = JSON.parse(result),
                parent_block = document.getElementById("div_menu_app"),
                uri,
                app_iframe;

            switch (window.location.protocol) {
                case "https:":
                    if (json_data.use_https && json_data.https_uri !== "") {
                        if (json_data.frame_support) {
                            uri = json_data.https_uri;
                            /* run in frame */
                            app_iframe = {
                                parent     : "page_content",
                                id         : "iframe_" + json_data.app_name,
                                type       : app_instance.OT_IFRAME,
                                label      : json_data.caption,
                                class_name : "app_widget",
                                json_uri   : uri.replace("[server_name]",
                                              window.location.hostname)
                            };
                            menu_obj.add_item(parent_block,
                                    "menu_" + json_data.app_name,
                                    "system/apps/" + app_dir + "/icon.png",
                                    json_data.caption,
                                    json_data.exec !== "",
                                    function () {
                                        app_instance.app_widget_draw(app_iframe);
                                    }
                            );
                        } else {
                            menu_obj.add_item(parent_block,
                                    "menu_" + json_data.app_name,
                                    "system/apps/" + app_dir + "/icon.png",
                                    json_data.caption,
                                    json_data.exec !== "",
                                    function () {
                                        var uri = json_data.https_uri;

                                        window.open(uri.replace("[server_name]",
                                                    window.location.hostname),
                                                    "_blank");
                                    }
                            );
                        }
                    } else if (json_data.use_http && json_data.http_uri !== "") {
                        menu_obj.add_item(parent_block,
                                "menu_" + json_data.app_name,
                                "system/apps/" + app_dir + "/icon.png",
                                json_data.caption,
                                json_data.exec !== "",
                                function () {
                                    var uri = json_data.http_uri;

                                    window.open(uri.replace("[server_name]",
                                                window.location.hostname),
                                                "_blank");
                                }
                        );
                    }
                    break;
                case "http:":
                    if (json_data.use_http && json_data.http_uri !== "") {
                        if (json_data.frame_support) {
                            uri = json_data.http_uri;
                            /* run in frame */
                            app_iframe = {
                                    parent     : "page_content",
                                    id         : "iframe_" + json_data.app_name,
                                    type       : app_instance.OT_IFRAME,
                                    label      : json_data.caption,
                                    class_name : "app_widget",
                                    json_uri   : uri.replace("[server_name]",
                                                window.location.hostname)
                            };
                            menu_obj.add_item(parent_block,
                                    "menu_" + json_data.app_name,
                                    "system/apps/" + app_dir + "/icon.png",
                                    json_data.caption,
                                    json_data.exec !== "",
                                    function () {
                                        app_instance.app_widget_draw(app_iframe);
                                    }
                            );
                        } else {
                            menu_obj.add_item(parent_block,
                                    "menu_" + json_data.app_name,
                                    "system/apps/" + app_dir + "/icon.png",
                                    json_data.caption,
                                    json_data.exec !== "",
                                    function () {
                                        var uri = json_data.http_uri;

                                        window.open(uri.replace("[server_name]",
                                                    window.location.hostname),
                                                    "_blank");
                                    }
                            );
                        }
                    } else if (json_data.use_https && json_data.https_uri !== "") {
                        menu_obj.add_item(parent_block,
                                "menu_" + json_data.app_name,
                                "system/apps/" + app_dir + "/icon.png",
                                json_data.caption,
                                json_data.exec !== "",
                                function () {
                                    var uri = json_data.https_uri;

                                    window.open(uri.replace("[server_name]",
                                                window.location.hostname),
                                                "_blank");
                                }
                        );
                    }
                    break;
            }
            for (var i = 0; i < json_data.require.length; i++) {
                app_instance.app_remove_unexisted(json_data.app_name,
                                            json_data.require[i].data);
            }
        })
        .catch(function(result) {
            console_log("register_app error: " + result);
        });
    };

    /**
     * Widgets constructor
     *
     * @param   {object}    caller Caller object
     * @param   {array}     w_info Array with Widget information
     * @param   {string}    w_data Widget data
     *
     * @returns {undefined}
     */
    function Widget(caller, w_info, w_data) {
        this.parent_app = caller;
        this.info       = w_info;
        this.data       = w_data || null;
        this.parent_el  = document.getElementById(w_info.parent);

        /* Double tap on header handler vars */
        this.dt_hdr_timeout  = null;
        this.dt_hdr_tap_time = 0;
    }

    /**
     * Toggle maximized widget state
     *
     * @returns {undefined}
     */
    Widget.prototype.toggle_maximized = function () {
        var parent_node = document.getElementById(this.parentNode.id);

        if (parent_node.classList.contains("canvas_info")) {
            return;
        }

        document.getElementById(this.parentNode.id).classList.toggle("div_maximized");

        for (var i = 0; i < parent_node.childNodes.length; i++) {
            if (parent_node.childNodes[i].classList.contains("div_w_content_container")) {
                parent_node.childNodes[i].classList.toggle("div_maximized_content");
            }
        }
    };

    /**
     * Draw widget
     *
     * @returns {undefined}
    */
    Widget.prototype.draw = function () {
        var widget_instance = this,
            widget_node,
            widget_c_container;

        if (document.getElementById(this.info.id) !== null) {
            widget_node = document.getElementById(this.info.id);

            widget_c_container = widget_node.childNodes[1];
            widget_c_container.removeChild(widget_c_container.childNodes[0]);
        } else {
            var widget_head = document.createElement("div");

            widget_node = document.createElement("div");
            widget_node.classList.add("div_widget");

            if (this.parent_app.settings.dim_on_create) {
                widget_node.classList.add("slightly_visible");
            }

            widget_node.id = this.info.id;

            if (this.info.class_name !== "") {
                widget_node.classList.add(this.info.class_name);
            }

            widget_head.classList.add("div_w_head");
            widget_head.appendChild(document.createTextNode(this.info.label));
            widget_head.addEventListener("dblclick", this.toggle_maximized);
            /* Add double tap event */
            widget_head.addEventListener("touchend", function(event) {
                var cur_time = new Date().getTime(),
                    tap_len  = cur_time - this.dt_hdr_tap_time;

                clearTimeout(widget_instance.dt_hdr_timeout);

                if (tap_len < 500 && tap_len > 0) {
                    event.preventDefault();
                    widget_instance.toggle_maximized();
                } else {
                    widget_instance.dt_hdr_timeout = setTimeout(function() {
                        clearTimeout(widget_instance.dt_hdr_timeout);
                    }, 500);
                }

                widget_instance.dt_hdr_tap_time = cur_time;
            });

            widget_c_container = document.createElement("div");
            widget_c_container.classList.add("div_w_content_container");

            widget_node.appendChild(widget_head);
            widget_node.appendChild(widget_c_container);

            this.parent_el.appendChild(widget_node);
        }

        var widget_content = document.createElement("div");

        widget_content.classList.add("div_w_content");

        widget_c_container.appendChild(widget_content);

        switch (this.info.type) {
            case this.parent_app.OT_TABLE_H:
            case this.parent_app.OT_TABLE_V:
                var table_node = this.html_table(this.info.type === this.parent_app.OT_TABLE_V);

                if (this.info.class_name !== "") {
                    table_node.classList.add(this.info.class_name);
                }
                widget_content.appendChild(table_node);

                break;
            case this.parent_app.OT_IFRAME:
                var iframe_node = document.createElement("iframe");

                iframe_node.src = this.info.json_uri;
                widget_content.appendChild(iframe_node);

                break;
            case this.parent_app.OT_CANVAS:
                var canvas_node = document.createElement("canvas"),
                    s_settings = {interpolation: this.info.interpolation,
                                //interpolation : "linear",
                                //interpolation : "step",
                                grid : {sharpLines       : true,
                                        borderVisible    : false,
                                        verticalSections : 4,
                                        millisPerLine    : 10000},
                                labels : {disabled : false},
                                timestampFormatter : SmoothieChart.timeFormatter,
                                millisPerPixel     : 100 //tooltip : true
                                };

                canvas_node.id     = "canvas_" + this.info.id;
                canvas_node.width  = 400;
                canvas_node.height = 154;

                widget_content.appendChild(canvas_node);

                if (this.info.max_val !== "auto") {
                    s_settings.maxValue = this.info.max_val;
                }

                if (this.info.min_val !== "auto") {
                    s_settings.minValue = this.info.min_val;
                }

                this.smoothie = new SmoothieChart(s_settings);
                this.lines = [];
                this.smoothie.streamTo(document.getElementById(canvas_node.id), 
                                        this.info.refresh * 1000);
        }
    };

    /**
     * Fill table headers
     *
     * @param   {object} table Parent table
     *
     * @returns {object}
     */
    Widget.prototype.fill_table_headers = function (table) {
        var col_set = [];

        if (this.data !== null) {
            var tr = document.createElement("tr");
            for (var i = 0, l = this.data.length; i < l; i++) {
                for (var key in this.data[i]) {
                    if (this.data[i].hasOwnProperty(key) && col_set.indexOf(key) === -1) {
                        col_set.push(key);

                        var th = document.createElement("th");

                        th.appendChild(document.createTextNode(key));
                        tr.appendChild(th);
                    }
                }
            }
            table.appendChild(tr);
        }

        return col_set;
    };

    /**
     * Return HTML table filled with data
     *
     * @returns {object}
     */
    Widget.prototype.html_table = function () {
        var table = document.createElement("table"),
            tr;

        if (this.data !== null) {
            if (this.info.type === this.parent_app.OT_TABLE_V) {
                var even_line = false;
                for (var key in this.data[0]) {
                    if (this.data[0].hasOwnProperty(key)) {
                        var td_key = document.createElement("td"),
                            td_val = document.createElement("td");

                        tr = document.createElement("tr");

                        td_key.appendChild(document.createTextNode(key));
                        td_key.classList.add("table_header");
                        if (this.info.raw_num) {
                            td_val.appendChild(document.createTextNode(this.data[0][key] || ""));
                        } else {
                            td_val.appendChild(
                                    document.createTextNode(
                                        numFormatted(this.data[0][key], "'") || ""
                                    )
                                );
                        }

                        tr.appendChild(td_key);
                        tr.appendChild(td_val);

                        if (even_line) {
                            tr.classList.add("even_line");
                        }

                        even_line = !even_line;
                        table.appendChild(tr);
                    }
                }
            } else {
                var columns = this.fill_table_headers(table);

                for (var i = 0, max_i = this.data.length; i < max_i; ++i) {
                    tr = document.createElement("tr");

                    for (var j = 0, max_j = columns.length; j < max_j ; ++j) {
                        var td = document.createElement("td");

                        if (this.info.raw_num) {
                            td.appendChild(
                                    document.createTextNode(
                                            this.data[i][columns[j]] || ""
                                        )
                                    );
                        } else {
                            td.appendChild(
                                    document.createTextNode(
                                            numFormatted(this.data[i][columns[j]], "'") || ""
                                        )
                                    );
                        }
                        tr.appendChild(td);
                    }

                    if (i % 2 !== 0) {
                        tr.classList.add("even_line");
                    }
                    table.appendChild(tr);
                }
            }
        }
        return table;
    };

    /**
     * Update canvas widget data
     *
     * @param   {object}    app_instance Application object instance
     * @param   {numeric}   index        Widget index
     *
     * @returns {undefined}
     */
    Widget.prototype.update_canvas_data = function (app_instance, index) {
        var w_instance = app_instance.widgets[index],
            colors = [
                ["rgb(0,   255, 0)",   "rgba(0,   255, 0,   0.4)"], // green
                ["rgb(255, 0,   0)",   "rgba(255, 0,   0,   0.4)"], // red
                ["rgb(255, 255, 0)",   "rgba(255, 255, 0,   0.4)"], // yellow
                ["rgb(0,   255, 255)", "rgba(0,   255, 255, 0.4)"],
                ["rgb(0,   0,   255)", "rgba(0,   0,   255, 0.4)"], // blue
                ["rgb(255, 255, 255)", "rgba(255, 255, 255, 0.4)"],
                ["rgb(128, 255, 255)", "rgba(128, 255, 255, 0.4)"],
                ["rgb(255, 128, 255)", "rgba(255, 128, 255, 0.4)"],
                ["rgb(255, 255, 128)", "rgba(255, 255, 128, 0.4)"],
                ["rgb(128, 128, 255)", "rgba(128, 128, 255, 0.4)"],
                ["rgb(255, 128, 128)", "rgba(255, 128, 128, 0.4)"],
                ["rgb(128, 255, 128)", "rgba(128, 255, 128, 0.4)"],
                ["rgb(0,   128, 255)", "rgba(0,   128, 255, 0.4)"],
                ["rgb(255, 0,   128)", "rgba(255, 0,   128, 0.4)"],
                ["rgb(128, 255, 0)",   "rgba(128, 255, 0,   0.4)"],
                ["rgb(0,   0,   128)", "rgba(0,   0,   128, 0.4)"]
            ];

        app_instance.get_data(w_instance.info.json_uri)
        .then(function(result) {
            var json_data = JSON.parse(result),
                i = 0;

            if (document.getElementById(w_instance.info.id) === null) {
                return;
            }

            var content_container = document.getElementById(w_instance.info.id).children[1],
                div_legend;

            for (var key in json_data) {
                if (json_data.hasOwnProperty(key)) {
                    if (w_instance.lines.length < i + 1) {
                        div_legend             = document.createElement("div");
                        div_legend.id          = w_instance.info.id + "_" + key;
                        div_legend.style.color = colors[i][0];
                        div_legend.classList.add("canvas_legend");
                        div_legend.appendChild(document.createTextNode (key));

                        content_container.appendChild(div_legend);

                        w_instance.lines.push(new TimeSeries());

                        w_instance.smoothie.addTimeSeries(
                                w_instance.lines[i],
                                {strokeStyle : colors[i][0],
                                fillStyle   : colors[i][1],
                                lineWidth   : 3}
                        );
                    } else {
                        div_legend = document.getElementById(
                                w_instance.info.id + "_" + key
                        );

                        if (div_legend === null) {
                            return;
                        }
                    }
                    div_legend.textContent = key + ": " 
                                            + json_data[key] 
                                            + w_instance.info.measure;
                    div_legend.classList.remove("slightly_visible");

                    w_instance.lines[i].append(new Date().getTime(), 
                                                Math.floor(json_data[key]));
                    document.getElementById(w_instance.info.id).classList.remove("slightly_visible");
                    i++;
                }
            }
        })
        .catch(function(result) {
            console_log("update_canvas_data error: " + result);
        });
    };

    /**
     * Draw table widget
     *
     * @param   {object}    app_instance Application object instance
     * @param   {numeric}   index        Widget index
     *
     * @returns {undefined}
     */
    Widget.prototype.update_table_data =  function (app_instance, index) {
        var w_instance = app_instance.widgets[index];

        app_instance.get_data(w_instance.info.json_uri)
        .then(function(result) {
            var json_data = JSON.parse(result);

            if (document.getElementById(w_instance.info.id) !== null) {
                w_instance.data = json_data;
                w_instance.draw();
                document.getElementById(w_instance.info.id).classList.remove("slightly_visible");
            }
        })
        .catch(function(result) {
            console_log("update_table_data error: " + result);
        });
    };

    document.addEventListener("DOMContentLoaded", function () {
        /**
         * Initialize application object
         */
        var app = new Application();
    });

}());
