//# sourceURL=jobs/CoreController.js
/**
 * $Id: Controller.js 279 2017-10-28 22:33:19Z patrick $
 */

import Controller from '../Controller.js';
import Group from '../entities/Group.js';
import * as util from '../util.js';

var MAX_FAIL = 3;
var ERROR_DELAY = 5000;

var CoreController = function( source ) {
    Controller.call( this, source );

    this.reloads = 0;
    this.reloadBase = new Date().getTime();
    this.reqTime = 0;
    this.lastUpdate = 0;
    this.loadtime = 0;
    this.dataversion = 0;
    this.failCount = 0;

    this.socket = false;
};

CoreController.prototype = Object.create(Controller.prototype);
CoreController.prototype.constructor = CoreController;

CoreController.prototype.getRoomId = function( data, room ) {
    return this.getGroupId( data, room );
};

CoreController.prototype.addToGroup = function( e, groupId ) {
    var g = this.getEntity( Group, groupId );
    g.addMember( e );
};

CoreController.prototype.updateDeviceEntity = function( className, id, data ) {
    var self = this;
    // console.log( "updateDeviceEntity() " + String(className) + ", " + String(id) + ", " + String(data));
    var epath = ( className && "Entity" !== className ) ? ( "entities/" + className ) : "Entity";
    require( [ epath ], function( classdef ) {
        var e = self.getEntity( classdef, id ); /* does not fail */
        e.deferNotifies( true );
        // console.log("updateDeviceEntity() entity " + className + " " + id + ":");
        // console.log(e);
        try {
            if ( data.name )
                e.setName( data.name );
            for ( attrName in ( data.attributes || {} ) ) {
                e.setAttribute( attrName, data.attributes[attrName] );
            }
            if ( data.group )
                self.addToGroup( e, data.group );
            if ( "undefined" === typeof data.attributes.lastupdate )
                e.setAttribute( "lastupdate", Date.now() );
        } catch ( ex ) {
            console.log(ex);
        }
        e.deferNotifies( false );
    },
    function( err ) {
        console.log(err);
    });
};

CoreController.prototype.updateSimpleEntity = function( className, id, data ) {
    var self = this;
    require( [ "entities/" + className ], function( classdef ) {
        var e = self.getEntity( classdef, id ); /* does not fail */
        if ( data.name ) e.setName( data.name );
        for ( var key in data ) {
            if ( data.hasOwnProperty(key) ) {
                var n = key.toLowerCase().replace(/ /g, "_");
                e.setAttribute( n, data[key] );
            }
        }
        if ( data.room ) {
            self.addToGroup( e, data.room );
        }
        e.deferNotifies(false);
    });
};

CoreController.prototype.ZZZupdate = function() {
    var self = this;
    self.reqTime = new Date().getTime();

//console.log(self.toString() + " sending request with loadtime=" + self.loadtime + " dataversion=" + self.dataversion);
    var reqData = { id: "status" };
    if ( self.dataversion > 0 && self.loadtime > 0 ) {
        self.log.debug(5, "Requesting delta update")
        reqData.DataVersion = self.dataversion;
        reqData.LoadTime = self.loadtime;
        reqData.Timeout = 15; /* yes, seconds */
        reqData.MinimumDelay = 200;
    } else {
        reqData.id = "user_data";
    }
    self.verarequest( reqData, { xhrFields: { withCredentials: false }, timeout: reqData.Timeout ? (reqData.Timeout * 1200) : 15000 } )
        .then( function( data ) {
//console.log(self.toString() + " response with loadtime=" + data.LoadTime + " dataversion=" + data.DataVersion + " full=" + data.full);
            /* Sanity-check response */
            if ( undefined == data.LoadTime || undefined == data.DataVersion ) {
                self.log.warn("%1: unparseable response from server", this);
                if ( ++self.failCount >= MAX_FAIL ) {
                    self.fail();
                }
                self.startDelay( ERROR_DELAY );
                return;
            }
            if ( self.loadtime > 0 && self.loadtime !== data.LoadTime ) {
                ++self.reloads;
            }

            // Device data
            if ( data.devices ) {
                // console.log("Update contains "+data.devices.length)
                var ndev = data.devices.length;
                for (var ix=0; ix<ndev; ++ix) {
                    var d = data.devices[ix];
                    var deviceid = String(d.id);
                    // console.log("   > "+deviceid+" "+String(d.name));
                    var className;
                    if ( self.deviceMap[deviceid] ) {
                        className = self.deviceMap[deviceid].getTypeName();
                    } else {
                        className = {
                            /* 1:"Interface", */
                            2:"Light",
                            3:"Switch",
                            4:"BinarySensor",
                            5:"Thermostat",
                            /* 6:"Camera", */
                            7:"Lock",
                            8:"Cover",
                            11:"Switch", /* Generic I/O */
                            12:"ValueSensor", /* Generic sensor */
                            /* 13:"SceneCtrl", /* Scene controller */
                            15:"MediaPlayer",
                            16:"ValueSensor", /* Humidity */
                            17:"ValueSensor", /* Temp */
                            18:"ValueSensor", /* Light (lux) */
                            24:"Switch",      /* Siren */
                            28:"ValueSensor", /* UV */
                            33:"ValueSensor", /* Flow */
                            34:"ValueSensor"  /* Voltage */
                        }[d.category_num||0];
                    }
                    if ( className ) {
                        try {
                            self.updateDeviceEntity( className, deviceid, d );
                        } catch( e ) {
                            console.log("Failed to update device #"+deviceid);
                            console.log(e);
                        }
                    } else {
                        self.log.debug(9, "%1 skipping vera device %2 category %3, no Entity defined.", self, deviceid, d.category_num);
                    }
                }
            }
            if ( data.scenes ) {
                var nscenes = data.scenes.length;
                for (var ix=0; ix<nscenes; ++ix) {
                    var d = data.scenes[ix];
                    var scid = String(d.id);
                    self.log.debug(9, "%1: Updating scene %2", self, scid);
                    d.state = parseInt( d.modeStatus ) || 0;
                    self.updateSimpleEntity( "Script", scid, d );
                }
            }
            if ( data.rooms ) {
                var nrooms = data.rooms.length;
                for (var ix=0; ix<nrooms; ++ix) {
                    var d = data.rooms[ix];
                    var rid = String(d.id);
                    self.log.debug(9, "%1: Updating room %2", self, rid);
                    d.state = 0;
                    self.updateSimpleEntity( "Group", rid, d );
                }
            }

            self.failCount = 0;
            self.lastUpdate = self.reqTime;
            self.loadtime = data.LoadTime;
            self.dataversion = data.DataVersion;

            /* Update the system entity */
            self.system.deferNotifies( true );
            // self.system.setAttribute("vera", data);
            self.system.setAttribute("state", true);
            self.system.setAttribute("error", false);
            self.system.setAttribute("message", data.comment || "");
            self.system.setAttribute("lastupdate", self.reqTime);
            self.system.setAttribute("reloads", self.reloads);
            self.system.setAttribute("reloadbase", self.reloadBase);
            self.system.setAttribute("uptime", Math.floor(new Date().getTime()/1000) - self.loadtime);
            self.system.deferNotifies( false );

            /* Notify for the controller Entity. Alternative to System entity. */
            self.notifyObservers( self );

            /* Schedule next request. */
            self.startDelay( 250 ); /* quick turn */
        }).catch( function( err ) {
            self.dataversion = 0; /* force full load next */
            if ( ++self.failCount >= MAX_FAIL ) {
                self.fail();
            }
            self.startDelay( ERROR_DELAY );
        });
};

CoreController.prototype.fail = function() {
    this._invalidate_structure();
    this.system.setAttributes({ state: false, error: true, message:"Lost communication" });
};

CoreController.prototype._invalidate_structure = function() {
    this.structure = { valid: false, controllers: {}, entities: {} };
};

CoreController.prototype._handle_socket_error = function( err ) {
    this._invalidate_structure();
    console.log("API: wssocket error: " + err);
    console.error(err);
    /* ??? close/restart? */
};

CoreController.prototype._handle_socket_close = function( event ) {
    this._invalidate_structure();
    if ( event.wasclean ) {
        console.log("api: clean close of socket");
    } else {
        console.log("api: wssocket died");
    }
    this.socket = false;

    /* attempt restart */
    this.start();
};

CoreController.prototype._handle_socket_message = function( event ) {
    var self = this;
    var data = JSON.parse( event.data );
    console.log("API: received from wssocket: " + data.type);
    if ( "structure" === data.type ) {
        console.log("API: structure received");
        for ( k in ( data.entities || {} ) ) {
            if ( data.entities.hasOwnProperty( k ) ) {
                var e = data.entities[k];
                self.updateDeviceEntity( e.type, e.canonical_id, e );
            }
        }
    } else if ( "entities" === data.type ) {
        var nel = data.entities.length;
        console.log("API: update for "+nel+" entities");
        for ( var k=0; k<nel; k++ ) {
            var e = data.entities[k];
            self.updateDeviceEntity( e.type, e.canonical_id, e );
        }
    } else {
        console.error("API: ignoring unsupported message type "+String(data.type));
        console.log(event.data);
    }
};

CoreController.prototype.start = function() {
    return Promise.resolve( this );
};

CoreController.prototype.run = function() {
    /* Nada */
};

CoreController.prototype.doEntityAction = function( entity, action, args ) {
    // Package message
    var msg = [];
    for ( var key in args ) {
        if ( args.hasOwnProperty(key) )
            msg.push( key + "=" + encodeURIComponent(String(args[key])) );
    }
    msg = "perform " + entity.getId() + " " + action + " " + msg.join(" ") + "\n";
    console.log("CoreController.doEntityAction(): sending "+msg);
    this.socket.send( msg );
};

export default CoreController;
