/*****************************************************************************
 *
 * View.js - Base class for all NagVis view classes
 *
 * Copyright (c) 2004-2016 NagVis Project (Contact: info@nagvis.org)
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/

const View = Base.extend({
    id: null,
    dom_obj: null,
    sum_obj: null,
    objects: null,

    constructor: function (id) {
        this.id = id;
        this.objects = {};
    },

    update: function (args) {
        const show = isset(args.show) ? "&show=" + args.show : "";

        const data = [];
        for (let i = 0, len = args.data.length; i < len; i++) data.push("i[]=" + args.data[i]);

        // Get the updated objects via bulk request
        call_ajax(
            oGeneralProperties.path_server +
                "?mod=" +
                args.mod +
                "&act=getObjectStates" +
                show +
                "&ty=state" +
                getViewParams() +
                this.getFileAgeParams(),
            {
                response_handler: this.handleUpdate.bind(this),
                method: "POST",
                post_data: data.join("&")
            }
        );

        // Need to re-raise repeated events?
        this.handleRepeatEvents();
    },

    render: function () {
        addEvent(window, "resize", function () {
            g_view.scaleView();
        });
        addEvent(window, "scroll", function () {
            g_view.scaleView();
        });
        this.scaleView();
    },

    /**
     * This callback function handles the ajax response of bulk object
     * status updates
     */
    handleUpdate: function (o) {
        // Stop processing these informations when the current view should not be
        // updated at the moment e.g. when reparsing the map after a changed mapcfg
        if (this.blockUpdates) {
            eventlog("ajax", "info", "Throwing new object information away since the view is blocked");
            return false;
        }

        if (!o) {
            eventlog("ajax", "info", "handleUpdate: got empty object. Terminating.");
            return false;
        }

        // Procees the "config changed" responses
        if (isset(o["status"]) && o["status"] == "CHANGED") {
            const oChanged = o["data"];

            for (const key in oChanged) {
                if (key == "maincfg") {
                    eventlog("worker", "info", "Main configuration file was updated. Need to reload the page");
                    // Clear the scheduling timeout to prevent problems with FF4 bugs
                    if (g_worker_id) window.clearTimeout(g_worker_id);
                    window.location.reload(true);
                    return;
                } else {
                    if (g_view.hasUnlocked()) {
                        eventlog("worker", "info", "Map config updated, but having unlocked objects - not reloading.");
                    } else {
                        eventlog("worker", "info", "Map configuration file was updated. Rerendering the map.");
                        g_view.render();
                        g_view.updateFileAges(oChanged);
                        return;
                    }
                }
            }
        }

        // I don't think empty maps make any sense. So when no objects are present:
        // Try to fetch them continously
        if (oLength(g_view.objects) === 0) {
            eventlog("worker", "info", "No objects found, reparsing...");
            g_view.init();
            return;
        }

        /*
         * Now proceed with real actions when everything is OK
         */

        if (o.length > 0) this.updateObjects(o, true);
    },

    updateFileAges: function (data) {
        for (const key in data) oFileAges[key] = data[key];
    },

    // Bulk update object states (and possibly configs) and then visualize changes
    updateObjects: function (attrs, only_state) {
        let at_least_one_changed = false;

        // Loop all object which have new information
        for (let i = 0, len = attrs.length; i < len; i++) {
            const objectId = attrs[i].object_id;

            // Object not found. This should only be happen for new objects which have
            // just been added to the map by the user. In this case we always have "full"
            // object information, not only state attrs
            if (!isset(this.objects[objectId])) {
                // this is only relevant for ViewMap at the moment
                this.addObject(attrs[i]);
                this.renderObject(objectId);
                at_least_one_changed = true; // always reload summary state
            } else {
                at_least_one_changed |= this.objects[objectId].updateAttrs(attrs[i], only_state);
            }
        }

        if (at_least_one_changed) this.handleStateChanged();
    },

    handleStateChanged: function () {},

    // Is called by an object to add the rendered dom object to the map
    drawObject: function (obj) {},
    eraseObject: function (obj) {},

    /**
     * Is called by the worker function to check all objects for repeated
     * events to be triggered independent of the state updates.
     */
    handleRepeatEvents: function () {
        eventlog("worker", "debug", "handleRepeatEvents: Start");
        for (const i in this.objects) {
            // Trigger repeated event checking/raising for eacht stateful object which
            // has event_time_first set (means there was an event before which is
            // required to be repeated some time)
            if (this.objects[i].has_state && this.objects[i].event_time_first !== null) {
                this.objects[i].checkRepeatEvents();
            }
        }
        eventlog("worker", "debug", "handleRepeatEvents: End");
    },

    getFileAgeParams: function () {
        let addParams = "";
        if (g_view.type === "map" && this.id !== false) addParams = "&f[]=map," + this.id + "," + oFileAges[this.id];
        return "&f[]=maincfg,maincfg," + oFileAges["maincfg"] + addParams;
    },

    // Detects objects with deprecated state information
    getObjectsToUpdate: function () {
        eventlog("worker", "debug", "getObjectsToUpdate: Start");
        const stateful = [],
            stateless = [];

        // Assign all object which need an update indexes to return Array
        for (const i in this.objects) {
            if (this.objects[i].lastUpdate <= iNow - oWorkerProperties.worker_update_object_states) {
                if (this.objects[i] instanceof NagVisStatefulObject) {
                    stateful.push(i);
                } else if (this.objects[i].conf.enable_refresh && this.objects[i].conf.enable_refresh == "1") {
                    // Do not update stateless objects where enable_refresh=0
                    stateless.push(i);
                }
            }
        }

        eventlog(
            "worker",
            "debug",
            "getObjectsToUpdate: Stateful: " + stateful.length + " Stateless: " + stateless.length
        );
        return [stateful, stateless];
    },

    // Renders basic information like favicon and page title
    renderPageBasics: function () {
        if (this.sum_obj) favicon.change(this.getFaviconImage(this.sum_obj.conf));

        document.title = oPageProperties.page_title;

        if (this.sum_obj) document.body.style.backgroundColor = this.getBackgroundColor(this.sum_obj.conf);
    },

    // Gets the background color of the map by the summary state of the map
    getBackgroundColor: function (oObj) {
        if (!oPageProperties.event_background || oPageProperties.event_background != "1")
            return oPageProperties.background_color;

        let sColor;
        // When state is PENDING, OK, UP, set default background color
        if (
            !oObj.summary_state ||
            oObj.summary_state == "PENDING" ||
            oObj.summary_state == "OK" ||
            oObj.summary_state == "UP"
        )
            sColor = oPageProperties.background_color;
        else {
            sColor = oStates[oObj.summary_state].bgcolor;

            // Ack or downtime?
            if (oObj.summary_in_downtime && oObj.summary_in_downtime == 1)
                if (
                    isset(oStates[oObj.summary_state]["downtime_bgcolor"]) &&
                    oStates[oObj.summary_state]["downtime_bgcolor"] != ""
                )
                    sColor = oStates[oObj.summary_state]["downtime_bgcolor"];
                else sColor = lightenColor(sColor, 100, 100, 100);
            else if (oObj.summary_problem_has_been_acknowledged && oObj.summary_problem_has_been_acknowledged == 1)
                if (
                    isset(oStates[oObj.summary_state]["ack_bgcolor"]) &&
                    oStates[oObj.summary_state]["ack_bgcolor"] != ""
                )
                    sColor = oStates[oObj.summary_state]["ack_bgcolor"];
                else sColor = lightenColor(sColor, 100, 100, 100);
        }
        return sColor;
    },

    // Gets the favicon of the page representating the state of the view
    getFaviconImage: function (oObj) {
        let sFavicon;

        if (oObj.summary_in_downtime && oObj.summary_in_downtime == 1) sFavicon = "downtime";
        else if (oObj.summary_problem_has_been_acknowledged && oObj.summary_problem_has_been_acknowledged == 1)
            sFavicon = "ack";
        else if (oObj.summary_state.toLowerCase() == "unreachable") sFavicon = "down";
        else sFavicon = oObj.summary_state.toLowerCase();

        return oGeneralProperties.path_images + "internal/favicon_" + sFavicon + ".png";
    },

    /**
     * Hack to scale elements which should fill 100% of the windows viewport. The
     * problem here is that some map objects might increase the width of the map
     * so that the viewport is not enough to display the whole map. In that case
     * elements with a width of 100% don't scale to the map width. Instead of this
     * the elements are scaled to the viewports width.
     * This changes the behaviour and resizes the 100% elements to the map size.
     *
     * @author  Lars Michelsen <lm@larsmichelsen.com>
     */
    scaleView: function () {
        const header = document.getElementById("header");
        const content = this.dom_obj;
        if (!content) return;

        let headerSpacer = document.getElementById("headerspacer");
        if (header) {
            header.style.width = pageWidth() + "px";
            if (headerSpacer) {
                headerSpacer.style.height = header.clientHeight + "px";
                headerSpacer = null;
            }
        }

        content.style.top = getHeaderHeight() + "px";

        if (typeof sidebarUpdatePosition == "function") {
            sidebarUpdatePosition();
        }
    },

    // Transforms the view specific coordinates to browser x/y coordinates.
    // This is executed when loading object coordinates from persistance.
    project: function (x, y) {
        return [x, y];
    },

    // Transforms the X/Y coordinates to view specific coords, like
    // on worldmaps to lat/long coordinates. This is executed when
    // persisting the object coordinates after a change
    unproject: function (x, y) {
        return [x, y];
    }
});
