//# sourceURL=jobs/HassController.js
/**
 * $Id: HassController.js 279 2017-10-28 22:33:19Z patrick $
 */
/* globals define,require,$,Promise,HassController */

define( [ 'Controller', 'util' ], function( Controller, util ) {

    var MAX_FAIL = 3;       /* Max number of query errors before failure */    
    var ERROR_DELAY = 5000; /* Delay on query error */

    HassController = function( source ) {
        // console.log("Controller constructor! " + source);
        Controller.call( this, source );
        this.reqTime = 0;
        this.lastUpdate = 0;
    };
    
    HassController.prototype = Object.create(Controller.prototype);
    HassController.prototype.constructor = HassController;

    HassController.prototype.updateEntity = function( e, d ) {
        for ( var k in d ) {
            if ( d.hasOwnProperty(k) && e.hasAttribute(k) ) {
                e.setAttribute(k, d[k]);
            }
        }
    };
    
    HassController.prototype.processEntity = function( className, id, d, updating ) {
        var self = this;
        // self.log.debug(8,"Loading entity %1 id %2", className, id);
        require( [ 'entities/' + className ], function( classdef ) {
            var dobj = self.getEntity( classdef, id ); /* does not fail */
            dobj.deferNotifies( true );
            if ( undefined !== d.state ) {
                dobj.setState( d.state );
            }
            if ( undefined !== d.attributes.friendly_name ) {
                dobj.setName( d.attributes.friendly_name );
            } else if ( !updating ) {
                dobj.setName( id );
            }
            if ( dobj.getTypeName() == "Light" ) {
                if ( d.state == "off" ) {
                    d.attributes.brightness = 0;
                } else if ( undefined !== d.attributes.brightness ) {
                    d.attributes.brightness = Math.floor( d.attributes.brightness * 100 / 255 + 0.5 );
                } else {
                    d.attributes.brightness = 100;
                }
            }
            for ( var k in d.attributes ) {
                if ( "id" !== k && d.attributes.hasOwnProperty(k) ) {
                    var n = k.toLowerCase().replace(/ /g, "_");
                    dobj.setAttribute(n, d.attributes[k]);
                }
            }
            dobj.setAttribute("lastupdate", self.reqTime);
            dobj.deferNotifies( false );
        });        
    }
    
    HassController.prototype.processStates = function( data, updating ) {
        // Map state data into our format.
        var self = this;
        for (var ix=0; ix<data.length; ++ix) {
            var d = data[ix];
            var id = String(d.entity_id);
            d.id = id;
            var k = id.indexOf('.');
            var cat = id.substr(0,k);
            
            var className = { light:'Light', 
                    'switch':'Switch', 
                    binary_sensor:'BinarySensor', 
                    sensor:'ValueSensor', 
                    script:'Script',
                    group:'Group'
                }[cat];
            if ( className ) {
                self.processEntity( className, id, d, updating );
            } else {
                self.log.debug(9, self.toString(), ":processStates() no entity for ", cat);
            }
        }

        /* Go through again, looking for groups and applying members */
        for ( var ig=0; ig<data.length; ++ig ) {
            if ( data[ig].id.match(/^group\./) ) {
                var grp = self.findEntity( "Group=" + data[ig].id, false );
                if ( grp ) {
                    grp.deferNotifies( true );
                    for ( var im=0; im<data[ig].entity_id.length; ++im ) {
                        var mem = self.findEntity( data[ig].entity_id[im], false );
                        if ( mem ) {
                            grp.addMember( mem );
                        }
                    }
                    grp.deferNotifies( false );
                }
            }
        }
    };

    HassController.prototype.update = function() {
        var self = this;
        var uri = "ajax/hassproxy.php";
        var reqData = { unit: self.unit };
        self.reqTime = new Date().getTime();
        if ( self.lastUpdate == 0 ) {
            self.log.debug(8,"Full data query");
            reqData.path = "/api/states";
            $.ajax({
                url: uri,
                data: reqData,
                timeout: 15000,
                dataType: 'json'
            }).done( function( data, textStat, jqXHR ) {
                /* Returns array of states */
                self.log.debug(8,"Processing response");
                self.processStates(data, false);
                self.lastUpdate = self.reqTime;
                
                self.system.setAttributes( { state:true, error:false, lastupdate:self.reqTime, message:"" } );
                
                self.startDelay( 2000 );
            }).fail( function( jqXHR, textStatus, errorThrown ) {
                self.log.warn("query failed, %1", textStatus);
                if ( ++self.failCount >= MAX_FAIL ) {
                    self.fail();
                }
                self.startDelay( ERROR_DELAY );
            });
        } else {
            var qt = new Date(self.lastUpdate).toISOString().replace(/\.[0-9]+Z$/, "+00:00");
            self.log.debug("update query as of %1", qt);
            reqData.path = "/api/history/period/" + qt;
            self.log.debug(8, "Update query: %1", reqData.path);
            $.ajax({
                url: uri,
                data: reqData,
                timeout: 10000,
                dataType: 'json'
            }).done( function( data, textStat, jqXHR ) {
                self.log.debug(8,"Processing response");
                
                /* Returns array of arrays of states */
                for (var ix=0; ix<data.length; ++ix) {
                    self.processStates(data[ix], true);
                }
                self.lastUpdate = self.reqTime;

                self.system.setAttributes( { state:true, error:false, lastupdate:self.reqTime, message:"" } );

                self.startDelay( 1000 );
            }).fail( function( jqXHR, textStatus, errorThrown ) {
                self.log.warn("query failed, %1", textStatus);
                if ( ++self.failCount >= MAX_FAIL ) {
                    self.fail();
                }
                self.startDelay( ERROR_DELAY );
            });
        }
    };
    
    HassController.prototype.fail = function() {
        this.system.setAttributes( { state:false, error:true, message:"Lost communication" } );
    };
    
    HassController.prototype.start = function() {
        var self = this;
        return new Promise( function( resolve, reject ) {
            self.lastUpdate = 0;
            self.startDelay(100);
            resolve( self );
        });
    };

    HassController.prototype.run = function() {
        //this.lastUpdate = 0; // ??? Update queries not working right now
        //this.log.setLevel(8);
        return this.update();
    };

    HassController.prototype.hassrequest = function(p) {
        var self = this;
        self.log.debug(5, this.toString() + " sending request " + util.dump(p));
        $.getJSON("ajax/hassproxy.php", p, function( data, status ) {
            util.log(self.toString() + " request returned " + status + ": " + util.dump(data));
            self.processStates( data, true );
        });
    };

    HassController.prototype.action_setState = function( entity, state ) {
        var typ = entity.getTypeName();
        if ( "Lock" == typ ) {
            this.hassRequest({
                path: "/api/services/lock/" + ( state ? "lock" : "unlock" ),
                __body: JSON.stringify( { entity_id: entity.getId() } )
            });
        } else if ( "script" == typ ) {
            this.hassrequest({
                path: "/api/services/script/turn_on",
                __body: JSON.stringify( { entity_id: entity.getId() } )
            });
        } else {
            /* OK for group, light, switch */
            this.hassrequest({ 
                path: "/api/services/homeassistant/turn_" + state,
                __body: JSON.stringify( { entity_id: entity.getId() } )
            });
        }
    };
    
    HassController.prototype.action_setLevel = function( entity, level ) {
        this.hassrequest({
                path: "/api/services/light/turn_on",
                __body: JSON.stringify( { entity_id: entity.getId(), brightness_pct: level } )
        });
    };
    
    HassController.prototype.action_runScene = function( entity ) {
        if ( undefined === scene || "" === entity.getId() ) throw new ReferenceError("Invalid (empty) scene cannot be run");
        this.hassrequest({
                path: "/api/services/script/turn_on",
                __body: JSON.stringify( { entity_id: entity.getId() } )
        });
    };
    
    return HassController;

}); // define
