/*
This file is part of
"it's electric": software for storing and viewing home energy monitoring data
Copyright (C) 2009--2011 Robert R. Tupelo-Schneck <schneck@gmail.com>
http://tupelo-schneck.org/its-electric

"it's electric" is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

"it's electric" 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 Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with "it's electric", as legal/COPYING-agpl.txt.
If not, see <http://www.gnu.org/licenses/>.
*/

function ItsElectric(timelineId,busyId,resolutionId,toolbarId,columnCheckboxesId) {
    this.queryPath = "/power";
    this.timelineId = timelineId;
    this.busyId = busyId;
    this.resolutionId = resolutionId;
    this.toolbarId = toolbarId;
    this.columnCheckboxesId = columnCheckboxesId;

    this.div0 = null;
    this.div1 = null;
    this.div2 = null;

    this.ready = false;
    this.firstTime = true;
    this.range = null;
    this.resolution = null;
    this.resolutionString = "";
    this.minimum = 0;
    this.maximum = 0;
    this.columnChecked = [];

    this.querying = false;
    this.pendingQuery = false;
    this.pendingDraw = false;

    this.lastTouched = new Date().getTime();
}

ItsElectric.prototype.configure = function(config) {
    this.datasourceURL = config.datasourceURL;
    this.partialRange = config.partialRange;
    this.initialZoom = config.initialZoom;
    this.realTime = config.realTime;
    this.realTimeUpdateInterval = config.realTimeUpdateInterval;
    this.hasVoltage = config.hasVoltage;
    this.hasKVA = config.hasKVA;
    this.noFlashEvents = config.noFlashEvents; // set true to make it work (somewhat) when
                                               // accessing a file: URL without privileges
}

ItsElectric.prototype.init = function() {
    this.div0 = document.createElement('div');
    this.div0.style.position = 'relative';
    this.div0.style.width = '100%';
    this.div0.style.height = '100%';
    document.getElementById(this.timelineId).appendChild(this.div0);

    this.div1 = document.createElement('div');
    this.div1.style.position = 'absolute';
    this.div1.style.width = '100%';
    this.div1.style.height = '100%';
    this.div1.style.zIndex = 1;
    this.div0.appendChild(this.div1);
    this.annotatedtimeline = new google.visualization.AnnotatedTimeLine(this.div1);
    this.allowRedraw = false;

    this.div2 = document.createElement('div');
    this.div2.style.position = 'absolute';
    this.div2.style.width = '100%';
    this.div2.style.height = '100%';
    this.div2.style.zIndex = 0;
    this.div0.appendChild(this.div2);
    this.annotatedtimeline2 = new google.visualization.AnnotatedTimeLine(this.div2);
    this.allowRedraw2 = false;

    var self = this;
    google.visualization.events.addListener(this.annotatedtimeline,
                                            'ready',
                                            function(e){self.readyHandler(e);});
    google.visualization.events.addListener(this.annotatedtimeline,
                                            'rangechange',
                                            function(e){self.rangeChangeHandler(e);});
    google.visualization.events.addListener(this.annotatedtimeline2,
                                            'ready',
                                            function(e){self.readyHandler(e);});
    google.visualization.events.addListener(this.annotatedtimeline2,
                                            'rangechange',
                                            function(e){self.rangeChangeHandler(e);});
    this.requery();
};

ItsElectric.prototype.queryURL = function() {
    var realTimeNeedsAdjust = this.realTime && this.range && this.range.end.getTime() == this.maximum;
    var queryURL = this.datasourceURL;
    if(queryURL.charAt(queryURL.length-1)=='/' && this.queryPath.charAt(0)=='/') {
      queryURL = queryURL + this.queryPath.substring(1);
    }
    else {
      queryURL = queryURL + this.queryPath;
    }
    var extendChar = '?';
    if(!this.isRedraw || !this.allowRedraw) {
        queryURL = queryURL + extendChar + 'extraPoints=2';
        extendChar = '&';
    }
    if(realTimeNeedsAdjust) {
        queryURL = queryURL + extendChar + 'realTimeAdjust=yes';
        extendChar = '&';
    }
    if(this.ready) {
        if(this.range && (this.range.start.getTime() != this.minimum || this.range.end.getTime() != this.maximum)) {
            if(this.isRedraw && this.allowRedraw) {
                var start = Math.floor(this.range.start.getTime()/1000);
                var end = Math.floor(this.range.end.getTime()/1000);
                queryURL = queryURL + extendChar +
                           'rangeStart='+ start +
                           '&rangeEnd=' + end;
                extendChar = '&';
            }
            else {
                var start = Math.floor(this.range.start.getTime()/1000);
                var end = Math.floor(this.range.end.getTime()/1000);
                queryURL = queryURL + extendChar +
                           'start='+ start +
                           '&end=' + end;
                extendChar = '&';
                if(this.partialRange) {
                    var rangeStart = start - (end - start);
                    rangeStart = Math.floor(Math.max(this.minimum/1000, rangeStart));
                    var rangeEnd = end + (end - start);
                    rangeEnd = Math.floor(Math.min(this.maximum/1000, rangeEnd));
                    queryURL = queryURL + extendChar +
                            'rangeStart=' + rangeStart +
                            '&rangeEnd=' + rangeEnd;
                    extendChar = '&';
                }
            }
        }
    }
    else if(this.firstTime) {
    	queryURL = queryURL + extendChar +
            'start='+ 100000000 +
            '&end=' + (100000000 + this.initialZoom) +
            '&realTimeAdjust=yes';
        extendChar = '&';
        this.firstTime = false;
        this.range = { start:new Date(100000000*1000), end:new Date((100000000+this.initialZoom)*1000) };
        this.maximum = (100000000 + this.initialZoom)*1000;
    }
    if(this.resolution) {
        queryURL = queryURL + extendChar +
                   'resolution=' + this.resolution;
        extendChar = '&';
    }
    return queryURL;
};

ItsElectric.prototype.toolbarQueryURL = function() {
    var queryURL = this.datasourceURL;
    if(queryURL.charAt(queryURL.length-1)=='/' && this.queryPath.charAt(0)=='/') {
      queryURL = queryURL + this.queryPath.substring(1);
    }
    else {
      queryURL = queryURL + this.queryPath;
    }
    var extendChar = '?';
    if(this.ready) {
        if(this.range && (this.range.start.getTime() != this.minimum || this.range.end.getTime() != this.maximum)) {
            var start = Math.floor(this.range.start.getTime()/1000);
            var end = Math.floor(this.range.end.getTime()/1000);
            queryURL = queryURL + extendChar +
                       'rangeStart='+ start +
                       '&rangeEnd=' + end;
            extendChar = '&';
        }
    }
    if(this.resolution) {
        queryURL = queryURL + extendChar +
                   'resolution=' + this.resolution;
        extendChar = '&';
    }
    return queryURL;
};

ItsElectric.prototype.requery = function() {
    if(this.querying) {
        this.pendingQuery = true;
        return;
    }
    this.querying = true;
    this.pendingQuery = false;
    this.query = new google.visualization.Query(this.queryURL());
    if(this.busyId) document.getElementById(this.busyId).style.display="";
    this.query.setTimeout(120);
    var self = this;
    this.query.send(function(response) {self.handleQueryResponse(response);});
    delete this.query;
};

ItsElectric.prototype.requeryAfter = function(n) {
    var self = this;
    setTimeout(function(){self.requery();},n);
};

ItsElectric.prototype.redrawAfter = function(n) {
    var self = this;
    setTimeout(function(){self.redraw();},n);
};

ItsElectric.prototype.options = {displayAnnotations: false,
                                 displayExactValues: true,
                                 allValuesSuffix: 'W',
                                 dateFormat: 'yyyy-MM-dd HH:mm:ss',
                                 wmode: 'opaque'};

ItsElectric.setOnclick = function(self,index,node) {
    node.onclick=function(){self.showOrHideColumn(index,node.checked);};
};

ItsElectric.prototype.handleQueryResponse = function(response) {
    if (response.isError()) {
        if(this.busyId) document.getElementById(this.busyId).style.display="none";
        alert('Error in query: ' + response.getMessage() + ' ' + response.getDetailedMessage());
        this.querying = false;
        if(this.pendingQuery) this.requeryAfter(1);
        else if(this.pendingDraw) this.redrawAfter(1);
        return;
    }

    var realTimeNeedsAdjust = this.realTime && this.range && this.range.end.getTime() == this.maximum;

    var data = response.getDataTable();
    var numRows = data.getNumberOfRows();
    var numCols = data.getNumberOfColumns();

for(var i = 0; i < numRows; i++) { data.setValue(i,1,Math.max(0,data.getValue(i,1)-Math.max(0,data.getValue(i,3)-280))); data.setValue(i,2,Math.max(0,data.getValue(i,2)-Math.max(0,data.getValue(i,4)-280))); }
    // INSERT DATA-ADJUSTING CODE

    if(this.columnCheckboxesId!=null) {
        var obj = document.getElementById(this.columnCheckboxesId);
        while(obj.firstChild) obj.removeChild(obj.firstChild);
        for(var i = 1; i < numCols; i++) {
            if(obj.firstChild) {
                obj.appendChild(document.createTextNode("\u00a0"));
            }
            var node = document.createElement("input");
            node.type = 'checkbox';
            if(this.columnChecked.length<i || this.columnChecked[i-1]!=false) {
                node.checked = true;
            }
            var index = i - 1;
            var self = this;
            ItsElectric.setOnclick(self,index,node);
            obj.appendChild(node);
            obj.appendChild(document.createTextNode(" " + data.getColumnLabel(i)));
        }
    }

    if(this.delta && numRows >= 2) {
        var prev = [];
        for(var i = 1; i < numCols; i++) {
            prev[i] = data.getValue(0,i);
            data.setValue(0,i,0);
        }
        for(var i = 1; i < numRows; i++) {
            for(var j = 1; j < numCols; j++) {
                var temp = data.getValue(i,j);
                data.setValue(i,j,temp - prev[j]);
                prev[j] = temp;
            }
        }
    }

    var rangeStart = numRows==0 ? 0 : data.getValue(0,0).getTime();
    if(isNaN(this.minimum) || this.minimum==0) {
        this.minimum = rangeStart;
    }
    var rangeEnd = numRows==0 ? 0 : data.getValue(numRows-1,0).getTime()

    // Make this.maximum be the time at which client's local time is the same clock time as the server's maximum
    this.timeZoneOffset = parseInt(data.getTableProperty('timeZoneOffset'));
    this.maximum = parseInt(data.getTableProperty('maximum'))*1000; // + this.timeZoneOffset*1000;
    if(isNaN(this.maximum) || this.maximum==0) {
        this.maximum = rangeEnd;
    }
    else {
        var maximumDate = new Date(this.maximum);
        maximumDate.setTime(this.maximum + maximumDate.getTimezoneOffset()*60000);
        maximumDate.setTime(this.maximum + maximumDate.getTimezoneOffset()*60000);
        this.maximum = maximumDate.getTime();
    }

    if(this.minimum!=0 && (numRows==0 || this.minimum < rangeStart)) {
        data.insertRows(0,1);
        data.setValue(0,0,new Date(this.minimum));
        for(var i = 1; i < numCols; i++) {
            data.setValue(0,i,numRows==0 ? 0 : data.getValue(1,i));
        }
    }
    if(this.maximum!=0 && (numRows==0 || this.maximum > rangeEnd)) {
        var newRow = data.addRow();
        data.setValue(newRow,0,new Date(this.maximum));
        for(var i = 1; i < numCols; i++) {
            data.setValue(newRow,i,numRows==0 ? 0 : data.getValue(newRow-1,i));
        }
    }

    if(this.range==null) this.range = { start: new Date(this.minimum), end: new Date(this.maximum) };

    if(realTimeNeedsAdjust) {
        var start = this.maximum - (this.range.end.getTime() - this.range.start.getTime());
        var end = this.maximum;
        this.range.start.setTime(start);
        this.range.end.setTime(end);
    }
    this.resolutionString = data.getTableProperty('resolutionString');
    this.currentResolution = parseInt(data.getTableProperty('resolution'));

    if(this.currentResolution > 60 && !this.allowRedraw) this.isRedraw = false;

    this.data = data;
    this.querying = false; // fake reentrant lock
    this.redraw();
    if(this.toolbarId) {
        var toolbarElement = document.getElementById(this.toolbarId);
        var toolbarQueryURL = this.toolbarQueryURL();
        google.visualization.drawToolbar(toolbarElement,
         [{type: 'html', datasource: toolbarQueryURL},
          {type: 'csv', datasource: toolbarQueryURL}])
        ItsElectric.renameChartOptions(toolbarElement);
    }
};

ItsElectric.renameChartOptions = function(element) {
    if(element.nodeType==3 && element.nodeValue=="Chart options") {
        element.nodeValue = "Export data";
        return true;
    }
    for(var i = 0; i < element.childNodes.length; i++) {
        if (ItsElectric.renameChartOptions(element.childNodes[i])) return true;
    }
    return false;
};

ItsElectric.prototype.redraw = function() {
    if(!this.isRedraw) {
        this.lastTouched = new Date().getTime();
    }
    if(this.querying) {
        this.pendingDraw = true;
        return;
    }
    this.querying = true;
    this.pendingDraw = false;

    if(!this.data) return;
    if(this.busyId) document.getElementById(this.busyId).style.display="";

    this.canRedraw = this.allowRedraw;
    if(!this.isRedraw || !this.canRedraw) {
        var temp = this.annotatedtimeline;
        this.annotatedtimeline = this.annotatedtimeline2;
        this.annotatedtimeline2 = temp;
        temp = this.allowRedraw;
        this.allowRedraw = this.allowRedraw2;
        this.allowRedraw2 = temp;
    }
    if(this.isRedraw && (this.canRedraw || !this.allowRedraw)) {
        this.options.allowRedraw = true;
        this.allowRedraw = true;
    }
    else {
        this.allowRedraw = false;
        var startDate = new Date();
        var endDate = new Date();
        ItsElectric.setDateAdjusted(startDate, this.range.start.getTime());
        ItsElectric.setDateAdjusted(endDate, this.range.end.getTime());
        this.options.zoomStartTime = startDate;
        this.options.zoomEndTime = endDate;
    }

    this.annotatedtimeline.draw(this.data, this.options);

    if(this.noFlashEvents) this.readyHandler(null);
};

// this horribleness makes things behave close to daylight saving time change
ItsElectric.setDateAdjusted = function(date,time) {
    date.setTime(time);
    date.setTime(time - date.getTimezoneOffset()*60000);
    // yes, again, in case we are close to DST
    date.setTime(time - date.getTimezoneOffset()*60000);
};

ItsElectric.prototype.readyHandler = function(e) {
    delete this.options.zoomStartTime;
    delete this.options.zoomEndTime;
    delete this.options.allowRedraw;

    if(!this.isRedraw || !this.canRedraw) {
        var hiddenColumns = [];
        for(var i = 0; i < this.columnChecked.length; i++) {
            if(this.columnChecked[i]==false) hiddenColumns.push(i);
        }
        if(hiddenColumns.length>0) {
            this.annotatedtimeline.hideDataColumns([]);
            this.annotatedtimeline.hideDataColumns(hiddenColumns);
        }
    }
    else {
        this.annotatedtimeline.hideDataColumns([]);
    }

    if(this.allowRedraw) {
        this.annotatedtimeline.setVisibleChartRange(new Date(this.range.start.getTime()),new Date(this.range.end.getTime()));
    }

    if(this.allowRedraw && (!this.isRedraw || !this.canRedraw)) {
        var self = this;
        setTimeout(function(){self.realReadyHandler(e);},500);
    }
    else {
        this.realReadyHandler(e);
    }
};

ItsElectric.prototype.realReadyHandler = function(e) {
    if(this.resolutionId) {
        var obj = document.getElementById(this.resolutionId);
        while(obj.firstChild) obj.removeChild(obj.firstChild);
        obj.appendChild(document.createTextNode(this.resolutionString));
    }

    if(!this.isRedraw || !this.canRedraw) {
        var temp = this.div2;
        this.div2 = this.div1;
        this.div1 = temp;
        this.div1.style.zIndex = 1;
        this.div2.style.zIndex = 0;
    }
    this.isRedraw = false;

//    var start = this.minimum;
//    var end = this.maximum;
//    if(this.range) {
//        start = this.range.start.getTime();
//        end = this.range.end.getTime();
//    }
//    var numRows = this.data.getNumberOfRows();
//    var numCols = this.data.getNumberOfColumns();
//    var watts = [];
//    for(var j = 1; j < numCols; j++) {
//        watts[j] = 0;
//        var lastWatts = 0;
//        var lastTime = start;
//        var first = false;
//        var second = false;
//        for(var i = 0; i < numRows; i++) {
//            var thisTime = this.data.getValue(i,0).getTime();
//            if(thisTime < start || thisTime > end) continue;
//            if(this)
//            if(this.data.getValue(i,j)!=null) {
//                if(first) second = true;
//                first = true;
//                if(lastWatts!=0) {
//                    if(this.resolutionString=="1h" && j==4) alert("" + lastWatts + " " + thisTime + " " + lastTime);
//                    watts[j] += lastWatts * ((thisTime - lastTime)/1000);
//                }
//                lastWatts = this.data.getValue(i,j);
//            }
//            if(second) {
//                lastTime = thisTime;
//            }
//        }
//        watts[j] = (watts[j]/3600000).toFixed(3);
//        if(j==4) alert("" + j + " "  + watts[j]);
//    }

    if(this.busyId) document.getElementById(this.busyId).style.display="none";

    this.ready = true;
    this.querying = false;
    if(this.firstTime && !this.noFlashEvents) {
        this.firstTime = false;
        this.zoom(this.initialZoom);
    }
    if(this.pendingQuery) this.requeryAfter(1);
    else if(this.pendingDraw) this.redrawAfter(1);
    else if(this.realTimeUpdateInterval) {
        if(isNaN(this.currentResolution)) {
           if(this.resolution!=null) this.currentResolution = this.resolution;
           else this.currentResolution = 1;
        }
        this.setRealTimeUpdater();
    }
};

ItsElectric.prototype.setRealTimeUpdater = function() {
    var self = this;
    setTimeout(function(){self.realTimeUpdate();},Math.max(this.currentResolution*1000,this.realTimeUpdateInterval));
}

ItsElectric.prototype.rangeChangeHandler = function(e) {
    var oldRange = 0;
    if(this.range) {
        oldRange = this.range.end.getTime() - this.range.start.getTime();
    }
    this.range = this.annotatedtimeline.getVisibleChartRange();
    if(this.range.end.getTime() - this.range.start.getTime() != oldRange) {
        this.resolution = null;
    }
    this.requery();
};

ItsElectric.prototype.zoom = function(t) {
    if(this.noFlashEvents) alert("This won't work with noFlashEvents=true.");
    if(!this.ready || this.noFlashEvents) return;
    var range = this.annotatedtimeline.getVisibleChartRange();
    if(range) this.range = range;
    this.resolution = null;
    var newStart = new Date();
    var newEnd = new Date();
    if(t==null) {
        newStart.setTime(this.minimum);
        this.range.start.setTime(this.minimum);
        newEnd.setTime(this.maximum);
        this.range.end.setTime(this.maximum);
    }
    else {
        newStart.setTime(this.range.end.getTime() - t*1000);
        if(newStart.getTime()<this.minimum) newStart.setTime(this.minimum);
        this.range.start.setTime(newStart.getTime());
        newEnd.setTime(this.range.end.getTime());
    }
    this.annotatedtimeline.setVisibleChartRange(newStart,newEnd);
    this.requeryAfter(500);
};

ItsElectric.prototype.scrollToPresent = function() {
    if(this.noFlashEvents) alert("This won't work with noFlashEvents=true.");
    if(!this.ready || this.noFlashEvents) return;
    var range = this.annotatedtimeline.getVisibleChartRange();
    if(range) this.range = range;
    var size = this.range.end.getTime() - this.range.start.getTime();
    var newStart = new Date();
    var newEnd = new Date();
    newEnd.setTime(this.maximum);
    this.range.end.setTime(this.maximum);
    newStart.setTime(this.maximum - size);
    this.range.start.setTime(this.maximum - size);
    this.annotatedtimeline.setVisibleChartRange(newStart,newEnd);
    this.requeryAfter(500);
};

ItsElectric.prototype.showOrHideColumn = function(col,show) {
    if(show && this.columnChecked.length<=col) return;
    this.columnChecked[col] = show;
    if(!this.ready) return;
    var self = this;
    setTimeout(function(){
        if(show) {
            var tohide = [];
            for(var i = 0; i < col && i < self.columnChecked.length; i++) {
                if(false!==self.columnChecked[i]) tohide.push(i);
            }
            if(tohide.length>0) {
                self.annotatedtimeline.hideDataColumns([]);
                self.annotatedtimeline.hideDataColumns(tohide);
            }
            tohide.push(col);
            self.annotatedtimeline.showDataColumns(tohide.reverse());
        }
        else {
            self.annotatedtimeline.hideDataColumns([]);
            self.annotatedtimeline.hideDataColumns(col);
        }
    },1);
};

ItsElectric.prototype.setResolution = function(t) {
    if(!this.ready) return;
    this.resolution = t;
    this.requery();
};

ItsElectric.prototype.realTimeUpdate = function() {
    if(!this.ready || !this.realTime || this.noFlashEvents) return;
    if(this.range && this.range.end.getTime() < this.maximum) return;
    if(this.range && this.range.end.getTime() - this.range.start.getTime() == this.initialZoom*1000) {
        var now = new Date().getTime();
        if(now - this.lastTouched > 60*60*1000) {
            history.go(0);
            return;
        }
    }
    if(!this.querying) {
        this.isRedraw = true;
        this.requery();
    }
};

