var gorobots_render_webgl = function(){
    var my = {};
    var grey = "rgba(0, 0, 0, 0.5)";
    var red = "rgba(200, 0, 0, 0.5)";

    // Tracking last 50 frame lengths
    var d = new Date();
    my.delta_history = [];
    my.last_frame_time = d.getTime();
    my.delta = 0;

    my.width = null;
    my.height = null;
    my.y_base = 16; // The top status bar

    my.scene = null;

    // Tracking the objects in the scene
    my.robots = {};
    my.objects = {};
    my.dom_id = null;
    my.target = null;


    my.init = function(dom_id) {
        my.dom_id = dom_id;
        my.canvas = document.getElementById(dom_id);
        $('body').keypress(function(e){

            if (my.canvas.clientWidth === 0){
                // dont respond to key events when canvas is not in view
                return;
            }

            if(e.which == 118){
                // v
                my.currentcamera += 1;
                if (my.currentcamera >= my.cameras.length) {
                    my.currentcamera = 0;
                }
                my.use_camera = my.cameras[my.currentcamera];
            }

            if (e.which == 45){
                // -
                if (my.use_camera == my.followcamera){
                    my.followcamera.fov = my.followcamera.fov - 5;
                    if (my.followcamera.fov < 5){
                        my.followcamera.fov = 5;
                    }

                    my.followcamera.updateProjectionMatrix();
                }
                if (my.use_camera == my.trackcamera){
                    my.track_area -= 10;
                    if (my.track_area < 50){
                        my.track_area = 50;
                    }
                    my.resize_view();
                }
            }
            if (e.which == 61){
                // =
                if (my.use_camera == my.followcamera){
                    my.followcamera.fov = my.followcamera.fov + 5;
                    if (my.followcamera.fov > 120){
                        my.followcamera.fov = 120;
                    }

                    my.followcamera.updateProjectionMatrix();
                }
                if (my.use_camera == my.trackcamera){
                    my.track_area += 10;
                    if (my.track_area > 1000){
                        my.track_area = 1000;
                    }
                    my.resize_view();
                }

            }
            if (e.which == 100){
                // d
                $(my.stats.domElement).toggle();
            }
        });

        my.stats = new Stats();
        my.stats.setMode(0); // 0: fps, 1: ms

        // Align top-left
        my.stats.domElement.style.position = 'absolute';

        my.stats.domElement.style.left = '5px';
        my.stats.domElement.style.top = '200px';

        document.body.appendChild( my.stats.domElement );
        $(my.stats.domElement).hide();
    };

    var random_color = function () {
        var letters = '0123456789ABCDEF'.split('');
        var color = '#';
        for (var i = 0; i < 6; i++ ) {
            color += letters[Math.round(Math.random() * 15)];
        }
        return color;
    };

    my.setup_cameras = function(){
        console.log("Setting up cameras");
        my.canvas = document.getElementById(my.dom_id);
        my.cameras = [];

        var pos = new THREE.Vector3(my.width / 2, my.height / 2, 0);
        var game_aspect = my.height / my.width;
        var aspect = my.canvas.clientHeight / my.canvas.clientWidth;
        var scale = game_aspect/aspect;
        if (scale < 1){
            var new_scale = (1/game_aspect) / (1/aspect);
            var s = (my.height * new_scale) / 2;
            my.camera = new THREE.OrthographicCamera( 0, my.width , s + (my.height/2), -(s - (my.height/2)), 0.1, 200 );
        } else {
            var s = (my.width * scale) / 2;
            my.camera = new THREE.OrthographicCamera( -(s - (my.width/2)), s + (my.width/2), my.height, 0, 0.1, 200 );
        }

        my.camera.position.z = 99;
        my.camera.updateProjectionMatrix();
        my.cameras.push(my.camera);

        var view_aspect = my.canvas.clientHeight / my.canvas.clientWidth;
        my.track_area = 300;
        my.trackcamera = new THREE.OrthographicCamera( -my.track_area, my.track_area, my.track_area * view_aspect, -my.track_area * view_aspect, -100, 200 );
        my.trackcamera.position.z = 100;
        my.trackcamera.updateProjectionMatrix();
        my.cameras.push(my.trackcamera);

        my.followcamera = new THREE.PerspectiveCamera( 35, my.canvas.clientWidth / my.canvas.clientHeight, 1, 10000 );
        my.followcamera.position = new THREE.Vector3(my.width / 2, -my.height + (my.height/2), (my.height / 2) + 200);
        my.followcamera.up = new THREE.Vector3(0,0,1);
        my.followcamera.lookAt(pos);
        my.followcamera.updateProjectionMatrix();
        my.controls = new THREE.TrackballControls( my.followcamera );

        my.cameras.push(my.followcamera);
    };

    my.setup_render_target = function(map_width, map_height, screen_width, screen_height){
        if (my.renderer){
            console.log("Renderer already initialized");
            my.reset();
            return;
        }

        my.turn = 0;
        my.robots = {};
        my.objects = {};
        my.labels = [];
        my.scene = new THREE.Scene();
        my.quit = false;

        my.width = screen_width;
        my.height = screen_height;

        my.renderer = new THREE.WebGLRenderer({"canvas": my.canvas, antialias: true });

        my.setup_cameras();

        my.floorobj = new THREE.CubeGeometry(my.width, my.height, 0.1);
        var material = new THREE.MeshLambertMaterial( { color: random_color() } );

        my.floor = new THREE.Mesh(my.floorobj, material);
        my.floor.position.x = my.width / 2;
        my.floor.position.y = my.height / 2 ;
        my.floor.position.z = -2;
        my.scene.add(my.floor);

        // Basic primitives
        my.grey_material = new THREE.MeshLambertMaterial( { color: "#999999", transparent: false } );
        my.red_material = new THREE.MeshLambertMaterial( { color: "#ff0000", transparent: true } );
        my.black_material = new THREE.MeshLambertMaterial( { color: "#000000", transparent: false } );

        my.robot_geom = new THREE.CubeGeometry(5,5,5);

        var loader = new THREE.OBJLoader();
        loader.load( '/ui/obj/robot.obj', function ( object ) {
            object.position.x = 100;
            object.position.y = 100;
            object.position.z = 1;
            object.up = new THREE.Vector3(0,0,1);
            object.scale.set(5,5,5);
            my.robot_geom = object;
        } );

        my.bullet_geom = new THREE.CubeGeometry(3,3,3);
        my.splosion_geom = new THREE.SphereGeometry(1,50,50);

        var axis = new THREE.AxisHelper(100);
        my.scene.add(axis);

        my.wallobjx = new THREE.CubeGeometry(my.width, 3, 3);
        my.wallobjy = new THREE.CubeGeometry(3, my.height, 3);
        var material = new THREE.MeshLambertMaterial( { color: "#cccccc" } );

        my.scene.add(my.camera);
        var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.9 );
        directionalLight.position.set( 0, 0, -1 );
        my.scene.add( directionalLight );

        var directionalLight2 = new THREE.DirectionalLight( 0xffffff, 0.9 );
        directionalLight2.position.set( 0, 0.707, 0.707 );
        my.scene.add( directionalLight2 );

        var ambientLight = new THREE.AmbientLight(0x222222, 0.7);
        my.scene.add(ambientLight);

        my.currentcamera = 2;
        my.use_camera = my.cameras[my.currentcamera];

        var render = function render() {
            my.stats.begin();
            my.renderer.render(my.scene, my.use_camera);
            my.update_labels();
            if (my.controls){
                my.controls.update();
            }

            if (!my.quit)
                requestAnimationFrame(render);

            my.stats.end();
        };
        render();

        $(window).resize(function(){
             my.resize_view();
        });

        my.resize_view();
    };

    my.resize_view = function(){
        my.canvas.height = ($(window).height() - $(my.canvas).offset().top);
        my.canvas.width = $(window).width();
        var game_aspect = my.height / my.width;
        var aspect = my.canvas.clientHeight / my.canvas.clientWidth;
        var scale = game_aspect/aspect;
        if (scale < 1){
            var new_scale = (1/game_aspect) / (1/aspect);
            var s = (my.height * new_scale) / 2;
            my.camera.left = 0;
            my.camera.right = my.width;
            my.camera.top = s + (my.height/2);
            my.camera.bottom = -(s - (my.height/2));
        } else {
            var s = (my.width * scale) / 2;
            my.camera.left = -(s - (my.width/2));
            my.camera.right = s + (my.width/2);
            my.camera.top = my.height;
            my.camera.bottom = 0;
        }
        my.camera.updateProjectionMatrix();

        var view_aspect = my.canvas.clientHeight / my.canvas.clientWidth;
        my.trackcamera.left = -my.track_area;
        my.trackcamera.right = my.track_area;
        my.trackcamera.top = my.track_area * view_aspect;
        my.trackcamera.bottom = -my.track_area * view_aspect;
        my.trackcamera.updateProjectionMatrix();

        my.followcamera.aspect = my.canvas.clientWidth / my.canvas.clientHeight;
        my.followcamera.updateProjectionMatrix();

        my.use_camera = my.cameras[my.currentcamera];
        my.renderer.context.viewport(0, 0, my.canvas.width, my.canvas.height);
    };

    my.begin_frame = function(turn){
        my.turn = turn;
        var d = new Date();
        var time = d.getTime();
        my.delta = time - my.last_frame_time;
        my.delta_history.unshift(my.delta);
        if (my.delta_history.length > 50){
            my.delta_history.pop();
        }

        my.last_frame_time = time;

        // Status Bar Text
        var slowest = Math.max.apply(null, my.delta_history);
        var debug_string = slowest + "ms";
    };

    my.render = function(draw_list){
        var obj_count = 0;

        for (var i in draw_list) {
            var robot_data = draw_list[i];
            var key, geometry, material, cube = null;

            if (robot_data['type'] == 'object'){

                key = robot_data['type'] + robot_data.bounds[0] + robot_data.bounds[1];
                if (!(key in my.objects)){
                    obj_count++;
                    console.log(robot_data);
                    x = robot_data.bounds[0];
                    y = robot_data.bounds[1];
                    w = robot_data.bounds[2] - robot_data.bounds[0];
                    h = robot_data.bounds[3] - robot_data.bounds[1];

                    geometry = new THREE.CubeGeometry(w,h,5 + Math.round(Math.random() * 25));
                    var material = new THREE.MeshLambertMaterial( { color: random_color(), transparent: false } );
                    cube = new THREE.Mesh( geometry, material );

                    my.scene.add( cube );
                    cube.position.x = x + (w / 2);
                    cube.position.y = y + (h / 2);
                    cube.position.z = 0;
                    cube.turn = my.turn;

                    my.objects[key] = cube;
                    continue;
                } else {
                    my.objects[key].turn = my.turn;
                }
            }
            else{
                key = robot_data['type'] + robot_data['id'];

                if (!(key in my.robots)){

                    if (robot_data['type'] == 'bullet'){
                        material = my.black_material;
                        geometry = my.bullet_geom;
                        cube = new THREE.Mesh( geometry, material );
                    }
                    else if (robot_data['type'] == 'explosion'){
                        size = robot_data.radius;
                        material = my.red_material;
                        geometry = my.splosion_geom;
                        material.opacity = 0.8;
                        cube = new THREE.Mesh( geometry, material );
                        cube.scale.set(size, size, size);
                    }
                    else {
                        material = new THREE.MeshLambertMaterial( { color: robot_data['color'], transparent: false } );
                        geometry = my.robot_geom;
                        cube = geometry.clone();
                        cube.traverse(function ( geo ) {
                            geo.material = new THREE.MeshLambertMaterial( { color: robot_data['color'] } );
                        } );
                        my.make_bot_label(robot_data.id, robot_data.name, cube);
                    }

                    my.robots[key] = cube;
                    my.scene.add(cube);
                    cube.position.x = robot_data['position']['x'];
                    cube.position.y = robot_data['position']['y'];
                    cube.position.z = -1;
                    cube.turn = my.turn;
                }
                else{
                    my.robots[key].position.x = robot_data['position']['x'];
                    my.robots[key].position.y = robot_data['position']['y'];
                    my.robots[key].turn = my.turn;

                    if ("heading" in robot_data){
                        if (robot_data['heading']['x'] == 0 && robot_data['heading']['y'] == 0) {
                            robot_data['heading']['y'] = 1;
                        }
                        var tmp = new THREE.Vector3(
                            robot_data['heading']['x'] + robot_data.position.x,
                            robot_data['heading']['y'] + robot_data.position.y,
                            -1);
                        my.robots[key].up = new THREE.Vector3(0,0,1);
                        my.robots[key].lookAt( tmp );
                    }

                    if (robot_data.health){
                        var h = Math.round((robot_data.health / 200) * 100) + "%";
                        var x = "#health_" + robot_data.id;
                        $(x).css({
                            "width": h
                        });
                    }
                }
            }
        }

        // Worth noting this list contains robots, bullets and explosions
        // it's badly named, I am sorry!
        for (var q in my.robots){
            if (my.turn - my.robots[q].turn > 1){
                // find and remove label?
                for (var w in my.labels){
                    if (my.labels[w].target == my.robots[q]){
                        my.labels[w].label.remove();
                        my.labels.splice(w, 1);
                    }
                }

                // Can I do this? I dont think so, we dont know
                // if these things will ever be back

                // my.robots[q].traverse(function ( geo ) {
                //     geo.visible = false;
                // });

                my.scene.remove(my.robots[q]);
                delete my.robots[q];
            }
        }

        for (var q in my.objects){
            if (my.turn - my.objects[q].turn > 1){
                my.objects[q].visible = false;
            } else {
                my.objects[q].visible = true;
            }
        }

        for (var p in draw_list){
            if (draw_list[p]['type'] == "robot"){
                if (draw_list[p]['id'] == my.target){
                    var pos = new THREE.Vector3(draw_list[p].position.x, draw_list[p].position.y, 0);
                    my.followcamera.lookAt(pos);
                    my.controls.target = pos;
                    my.trackcamera.position.x = pos.x;
                    my.trackcamera.position.y = pos.y;

                    my.trackcamera.updateProjectionMatrix();

                    break;
                }
            }
        }

    };

    var projector = new THREE.Projector();

    my.update_labels = function(){
        for (var i in my.labels){
            var label = my.labels[i];
            var screen_pos = label.target.position.clone();
            var x = $(my.canvas).position();
            if (my.canvas.clientWidth === 0){
                $(label.label).css({
                display: "none"});
            } else {
                projector.projectVector( screen_pos, my.use_camera );
                if (screen_pos.x > 1 || screen_pos.x < -1 || screen_pos.y > 1 || screen_pos.y < -1){
                    $(label.label).css({
                        display: "none"});
                    continue;
                }

                var sx = (screen_pos.x * ((my.canvas.clientWidth)/2)) + ((my.canvas.clientWidth)/2) + x.left;
                var sy = -(screen_pos.y * ((my.canvas.clientHeight)/2)) + ((my.canvas.clientHeight)/2) + x.top - 30;
                $(label.label).css({
                    position: "absolute",
                    display:"block",
                    marginLeft: 0,
                    marginTop: 0,
                    top: sy,
                    left: sx});
            }
        }
    };

    my.make_bot_label = function(id, text, track_object){
        var obj = $("<div class='label' style='display:none;'>" + text + "<div class='health' id='health_" + id + "'></div></div>");
        $("body").append(obj);
        my.labels.push({
            label: obj,
            target: track_object
        });
    };

    my.debug_labels = [];
    my.debug_lines = [];

    my.clear_debug = function(){
        for (var i in my.debug_labels){
            my.debug_labels[i].remove();
        }

        my.debug_labels = [];

        for (var i in my.debug_lines){
            my.scene.remove(my.debug_lines[i]);
        }
        delete my.debug_lines;
        my.debug_lines = [];
    };

    my.debug_label = function(x, y, msg){
        if (my.canvas.clientWidth > 0){
            var screen_pos = new THREE.Vector3(x, y, 0);
            var canvas_pos = $(my.canvas).position();
            projector.projectVector( screen_pos, my.use_camera );

            if (screen_pos.x > 1 || screen_pos.x < -1 || screen_pos.y > 1 || screen_pos.y < -1){
                return;
            }

            var sx = (screen_pos.x * ((my.canvas.clientWidth)/2)) + ((my.canvas.clientWidth)/2) + canvas_pos.left;
            var sy = -(screen_pos.y * ((my.canvas.clientHeight)/2)) + ((my.canvas.clientHeight)/2) + canvas_pos.top;
            var obj = $("<div class='label'>" + msg + "</div>");
            $("body").append(obj);
            $(obj).css({
                position: "absolute",
                marginLeft: 0,
                marginTop: 0,
                top: sy,
                left: sx});

            my.debug_labels.push(obj);
        }
    };

    my.debug_label_screen = function(x, y, msg){
        if (my.canvas.clientWidth > 0){

            var canvas_pos = $(my.canvas).position();
            var obj = $("<div class='label'>" + msg + "</div>");
            $("body").append(obj);
            $(obj).css({
                position: "absolute",
                marginLeft: 0,
                marginTop: 0,
                top: y + canvas_pos.top,
                left: x + canvas_pos.left});

            my.debug_labels.push(obj);
        }
    };

    my.debug_line = function(x1, y1, x2, y2){
        var material = new THREE.LineBasicMaterial({
                color: 0x0000ff
            });

        var geometry = new THREE.Geometry();
        geometry.vertices.push(new THREE.Vector3(x1, y1, 0));
        geometry.vertices.push(new THREE.Vector3(x2, y2, 0));

        var line = new THREE.Line(geometry, material);
        my.scene.add(line);
        my.debug_lines.push(line);
    };

    my.set_focus_robot = function(id){
        my.target = id;
    };

    my.reset = function(){
        for (var i in my.objects){
            my.scene.remove(my.objects[i]);
        }
        delete my.objects;
        my.objects = {};

        for (i in my.robots){
            my.scene.remove(my.robots[i]);
        }
        delete my.robots;
        my.robots = {};

        my.clear_debug();

        for (i in my.labels){
            my.labels[i].label.remove();
        }
        my.labels = [];
    };

    my.end_frame = function(){

    };

    my.shutdown = function(){
        my.reset();
        my.quit = true;
    };

    return my;
};
