// (c) Copyright 2011, Synapse Wireless, Inc.
// Main javascript file for SNAPpy powered OWI Robotic Arm Edge Web Interface
// This version uses jQuery, and Websockets
               
// Document Loaded callback
$(document).ready(function() {
    wsHub.start();
    initChart();    

    wsHub.register('print', do_print);    
    wsHub.register('light', update_light_chart);
    wsHub.register('timer', control_timer);

    initRobotControls();

    // Initialize periodic timers to update "Game Timer" as well as
    // sending paced RPCs based on control-drag events.
    initTimers();
});

function initTimers()  {
    setInterval(updateGameTimer, 100);
    setInterval(sendRobotControl, 200);
}

var tGameStart;
var isGameRunning = false;
var gameTime = 0;

// Format a 2-digit integer string for game timer display
function dig2(n)  {
    if (n < 10) {
        return '0' + n;
    }
    else if (n < 100)  {
        return n.toString();
    }
    else  {
        return '--';
    }
}

// 100ms tick handler for game timer
function updateGameTimer()  {
    if (isGameRunning)  {
        gameTime = ((new Date().getTime()) - tGameStart)/10;
        displayGameTime('Elapsed');
    }
}

function displayGameTime(name)  {
    var minutes = Math.floor(gameTime/6000);
    var centiseconds = Math.floor(gameTime%6000);
    var seconds = Math.floor(centiseconds/100);
    centiseconds = centiseconds%100;
    var timeStr = dig2(minutes) + ':' + dig2(seconds) + ':' + dig2(centiseconds);
    $('#game_timer p').text(name + ':  ' + timeStr);
}

function control_timer(message) {
    var action = message.args;
    var name;
    if (action === 'start')  {
        name = 'Elapsed';
        tGameStart = new Date().getTime();
        isGameRunning = true;
    }
    else if (action === 'stop')  {
        isGameRunning = false;
        gameTime = ((new Date().getTime()) - tGameStart)/10;
        name = 'Finished';
    }
    else if (action === 'reset')  {
        name = 'Reset';
        gameTime = 0;
    }
    displayGameTime(name);
}

// Current state (applied on tick): 0=stop, +1=up, -1=down
var control_state = [0, 0, 0, 0, 0];

function sendRobotControl()  {
    // Check if there are any "active" motors (nonzero control vals)
    var i;
    for (i = 0; i < control_state.length; i++)  {
        if (control_state[i] != 0)  {
            break;        
        }
    }
    // If active motors, send RPC to refresh pulse value
    if (i < control_state.length)  {
        sendRpc('robot_control', control_state);
    }
}

// wsHub Callback: Debug log function - TEST
function do_print(message)  {
    console.log(message.args);
}

// wsHub Callback: message.args is reported light level
function update_light_chart(message)  {
    plot(message.args);
}

// Plot a new point on the Light Level graph
function plot(val)  {
    var series = chart.series[0];
    var shift = series.data.length > 20; // shift if the series is longer than 20

    // add the point
    var time = (new Date()).getTime();
    chart.series[0].addPoint({x:time, y:val}, true, shift);		
}

// Send an RPC over our WebSocket to E10 server
function sendRpc(funcname, args)  {
    var message = {funcname:funcname, args:args};
    wsHub.socket.send(JSON.stringify(message));
}

// WebSocket Hub: Establish a socket between browser and E10 server. Provide API to send/receive messages
var wsHub = {
    socket: null,
    registry: new Array(),

    register: function(kind, callback)  {
        wsHub.registry.push( {kind:kind, callback:callback} );
    },

    start: function() {
        var host = "ws://" + location.host + "/wshub"

        // Detect WebSocket support
        if ("WebSocket" in window) {
            // Modern browsers
            wsHub.socket = new WebSocket(host);
        } else if ("MozWebSocket" in window) {
            // Firefox 6
            wsHub.socket = new MozWebSocket(host);
        }
        else  {
            $('body').html("<h1>Error</h1><p>Your browser does not support HTML5 Web Sockets.</p>");
            return;
        }
    
        wsHub.socket.onmessage = function(event) {
            message = JSON.parse(event.data);
            var i = 0;
            for (i = 0; i < wsHub.registry.length; i++)  {
                var observer = wsHub.registry[i];
                if (observer.kind === message.kind)  {
                    observer.callback(message);
                }
            }
        }
    },
};


var robot_controls = [{name:'grip', x:87, y:292, radius:50},
                      {name:'wrist', x:250, y:167, radius:70},
                      {name:'elbow', x:521, y:149, radius:80},
                      {name:'shoulder', x:532, y:310, radius:80},
                      {name:'base', x:420, y:582, radius:100},
                      {name:'lamp', x:154, y:229, radius:30},
];               

// [x,y,touch_id] of start-pos of any touches in progress
var control_drag_starts = [null, null, null, null, null];

// Mouse can only drag one at a time. Store [index,dir] of control under mouse
var mouse_drag_control = null;

function find_control(x,y)  {
    for (i = 0; i < robot_controls.length; i++)  {
        var c = robot_controls[i];
        var dist = Math.sqrt(Math.pow(c.x-x, 2)+Math.pow(c.y-y, 2));
        if (dist < c.radius)  {
            return i;
        }
    }
    return null;
}

function getTouchIndex(touch_id)  {
    var i = 0;
    for (i = 0; i < control_drag_starts.length; i++)  {
        var c = control_drag_starts[i];        
        if (c != null && c[2] == touch_id)  {
          return i;
        }
    }
    return null;  // no control-index associated with this touch_id
}

// User input event: start touch control
function startControl(touch_id, x, y)  {
    var i = find_control(x,y);
    console.log('startControl: (' + x + ',' + y + ') id=' + touch_id);
    if (i != null)  {
        var c = robot_controls[i];
        if (c.name === 'lamp')  {
           sendRpc('toggle_lamp', []);
        }
        else  {
            $('#' + c.name + '_1').show();
            $('#' + c.name + '_2').show();
            
            control_drag_starts[i] = [x,y,touch_id];        
        }
    }
}

function stopControl(touch_id)  {
    i = getTouchIndex(touch_id);
    if (i != null)  {
        $('#' + robot_controls[i].name + '_1').hide();
        $('#' + robot_controls[i].name + '_2').hide();
        control_state[i] = 0;
        control_drag_starts[i] = null;
    }
}

function dragControl(touch_id, x, y)  {
    i = getTouchIndex(touch_id);
    if (i != null && control_drag_starts[i] != null)  {
        var c = robot_controls[i];
        var img1 = c.name + '_1'; 
        var img2 = c.name + '_2';     
        var drag_margin = 5;
        var start_y = control_drag_starts[i][1];
        
        if (y < (start_y - drag_margin))  {
            // Drag UP    
            $('#' + img1).show();
            $('#' + img2).hide();
            control_state[i] = +3;
        }
        else if (y > (start_y + drag_margin))  {
            // Drag DOWN    
            $('#' + img1).hide();
            $('#' + img2).show();
            control_state[i] = -3;
        }
    }
}

function commonMouseUp(event)  {
    if (mouse_drag_control != null)  {
        var i = mouse_drag_control[0];
        var c = robot_controls[i];
        var img1 = c.name + '_1'; 
        var img2 = c.name + '_2';     
        $('#' + img1).hide();
        $('#' + img2).hide();
        if (c.name != 'lamp')  {        
            control_state[i] = 0;
        }
        mouse_drag_control = null;
    }
}

function hideAllControls()  {
    var i;
    for (i = 0; i < robot_controls.length-1; i++)  {
        $('#' + robot_controls[i].name + '_1').hide();
        $('#' + robot_controls[i].name + '_2').hide();
    }
}

function initRobotControls()  {
    hideAllControls();
    
    $('#control_region').mouseup( function(event) {
        commonMouseUp(event);
        return false;
    } );

    $('#control_region').mouseout( function(event) {
        //commonMouseUp(event);
        return false;
    } );

    $('#control_region').mousedown( function(event) {
        if (mouse_drag_control != null)  {
            var c = robot_controls[mouse_drag_control[0]];
            if (c.name === 'lamp')  {
                sendRpc('toggle_lamp', []);                
            }
            else  {
                control_state[mouse_drag_control[0]] = mouse_drag_control[1];
            }
        }
        return false;
    } );

    $('#control_region').mousemove( function(event) {
        hideAllControls();
        var x = event.pageX - this.offsetLeft;
        var y = event.pageY - this.offsetTop;
        var i = find_control(x,y);
        if (i != null)  {
            var c = robot_controls[i];
            if (c.name === 'lamp')  {
                mouse_drag_control = [i, 0];
            }
            else  {
                var img1 = c.name + '_1'; 
                var img2 = c.name + '_2';     
                if (y < (c.y - 5))  {
                    // UP    
                    $('#' + img1).show();
                    mouse_drag_control = [i, +3];
                } 
                else if (y > (c.y + 5))  {
                    // DOWN    
                    $('#' + img2).show();                            
                    mouse_drag_control = [i, -3];
                }
            }
        }
        return false;
    } );

    $('#control_region').bind('touchstart', function(event) {
        event.preventDefault();
        var i = 0;
        for (i = 0; i < event.originalEvent.changedTouches.length; i++)  {
            var touch = event.originalEvent.changedTouches[i];
            //console.log('touchstart[' + i + ']: ' + touch.pageX + ',' + touch.pageY);
            var x = touch.pageX - this.offsetLeft;
            var y = touch.pageY - this.offsetTop;
            startControl(touch.identifier, x, y);
        }
        return false;
    } );

    $('#control_region').bind('touchend', function(event) {
        event.preventDefault();
        var i = 0;
        for (i = 0; i < event.originalEvent.changedTouches.length; i++)  {
            var touch = event.originalEvent.changedTouches[i];
            //console.log('touchend[' + i + ']: ' + touch.pageX + ',' + touch.pageY);
            stopControl(touch.identifier);
        }
        return false;
    } );

    $('#control_region').bind('touchmove', function(event) {
        event.preventDefault();
        var i = 0;
        for (i = 0; i < event.originalEvent.changedTouches.length; i++)  {
            var touch = event.originalEvent.changedTouches[i];
            //console.log('touchmove[' + i + ']: ' + touch.pageX + ',' + touch.pageY);
            var x = touch.pageX - this.offsetLeft;
            var y = touch.pageY - this.offsetTop;
            dragControl(touch.identifier, x, y);
        }
        return false;
    } );
}

// HighCharts "strip chart" displaying Light Levels
var chart;

function initChart()  {
    Highcharts.setOptions({
        global: {
            useUTC: false
        }
    });

    chart = new Highcharts.Chart({
        chart: {
            renderTo: 'light_plot',
            defaultSeriesType: 'spline',
            marginRight: 10,
            events: {
                load: function() {
                }
            }
        },
        title: {
            text: 'Light Level'
        },
        xAxis: {
            type: 'datetime',
            tickPixelInterval: 150
        },
        yAxis: {
            //max: 4000,
            //min: 0,
            //tickInterval: 1000,
            title: {
                text: 'Light Sensor'
            },
            plotLines: [{
                value: 0,
                width: 1,
                color: '#808080'
            }]
        },
        tooltip: {
            formatter: function() {
                return '<b>'+ this.series.name +'</b><br/>'+
                Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', this.x) +'<br/>'+ 
                Highcharts.numberFormat(this.y, 2);
            }
        },
        legend: {
            enabled: false
        },
        exporting: {
            enabled: false
        },
        credits: {
            enabled: false
        },
        series: [{
            name: 'CDS Cell',
            data: []
        }]
    });

}
