//
//   Project Horus - Browser-Based Chase Mapper - Balloon Telemetry Handlers
//
//   Copyright (C) 2019  Mark Jessop <vk5qi@rfhead.net>
//   Released under GNU GPL v3 or later
//


function add_new_balloon(data){
    // Add a new balloon to the telemetry store.
    // This function accepts a dictionary which conttains:
    //  telem: Latest telemetry dictionary, containing:
    //      callsign:
    //      position: [lat, lon, alt]
    //      vel_v
    //  path: Flight path so far.
    //  pred_path: Predicted flight path (can be empty)
    //  pred_landing: [lat, lon, alt] coordinate for predicted landing.
    //  abort_path: Abort prediction path
    //  abort_landing: Abort prediction landing location


    var telem = data.telem;
    var callsign = data.telem.callsign;

    balloon_positions[callsign] = {
        latest_data: telem,
        age: 0,
        colour: colour_values[colour_idx],
        snr: -255.0,
        visible: true
    };
    // Balloon Path
    balloon_positions[callsign].path = L.polyline(data.path,{title:callsign + " Path", color:balloon_positions[callsign].colour}).addTo(map);
    // Balloon position marker
    balloon_positions[callsign].marker = L.marker(telem.position,{title:callsign, icon: balloonAscentIcons[balloon_positions[callsign].colour]})
        .bindTooltip(callsign,{permanent:false,direction:'right'})
        .addTo(map);

    // Set the balloon icon to a parachute if it is descending.
    if (telem.vel_v < 0){
            balloon_positions[callsign].marker.setIcon(balloonDescentIcons[balloon_positions[callsign].colour]);
    }
    
    // If we have 'landed' (this is a bit of a guess), set the payload icon.
    if (telem.position[2] < parachute_min_alt){
        balloon_positions[callsign].marker.setIcon(balloonPayloadIcons[balloon_positions[callsign].colour]);
    }


    // If the balloon is in descent, or is above the burst altitude, clear out the abort path and marker
    // so they don't get shown.
    if (telem.position[2] > chase_config.pred_burst || telem.vel_v < 0.0){
        balloon_positions[callsign].abort_path = [];
        balloon_positions[callsign].abort_landing = [];
    }

    // Add predicted landing path
    balloon_positions[callsign].pred_path = L.polyline(data.pred_path,{title:callsign + " Prediction", color:balloon_positions[callsign].colour, opacity:prediction_opacity}).addTo(map);

    // Landing position marker
    // Only add if there is data to show
    if (data.pred_landing.length == 3){
        var _landing_text = callsign + " Landing " + data.pred_landing[0].toFixed(5) + ", " + data.pred_landing[1].toFixed(5);
        balloon_positions[callsign].pred_marker = L.marker(data.pred_landing,{title:callsign + " Landing", icon: balloonLandingIcons[balloon_positions[callsign].colour]})
            .bindTooltip(_landing_text,{permanent:false,direction:'right'})
            .addTo(map);
        // Add listener to copy prediction coords to clipboard.
        // This is also duplicated in prediction.js, until I rearrange things...
        balloon_positions[callsign].pred_marker.on('click', function(e) {
            var _landing_pos_text = e.latlng.lat.toFixed(5) + ", " + e.latlng.lng.toFixed(5);
            textToClipboard(_landing_pos_text);
        });
    } else{
        balloon_positions[callsign].pred_marker = null;
    }

    // Burst position marker
    // Only add if there is data to show
    if (data.burst.length == 3){
        balloon_positions[callsign].burst_marker = L.marker(data.burst,{title:callsign + " Burst", icon: burstIcon})
            .bindTooltip(callsign + " Burst",{permanent:false,direction:'right'})
            .addTo(map);
    } else{
        balloon_positions[callsign].burst_marker = null;
    }

    // Abort path
    balloon_positions[callsign].abort_path = L.polyline(data.abort_path,{title:callsign + " Abort Prediction", color:'red', opacity:prediction_opacity});

    if ((chase_config.show_abort == true) && (balloon_positions[callsign].visible == true)){
        balloon_positions[callsign].abort_path.addTo(map);
    }

    // Abort position marker
    if (data.abort_landing.length == 3){
        balloon_positions[callsign].abort_marker = L.marker(data.abort_landing,{title:callsign + " Abort", icon: abortIcon})
            .bindTooltip(callsign + " Abort Landing",{permanent:false,direction:'right'});
        if( (chase_config.show_abort == true) && (balloon_positions[callsign].visible == true)){
            balloon_positions[callsign].abort_marker.addTo(map);
        }
    }else{
        balloon_positions[callsign].abort_marker = null;
    }

    
    colour_idx = (colour_idx+1)%colour_values.length; 

}

function updateSummaryDisplay(){
    
    if (chase_config['unitselection'] == "imperial") {updateSummaryDisplayImperial() ; return ; } // else do everything in metric
    // Update the 'Payload Summary' display.
    var _summary_update = {id:1};
    // See if there is any payload data.
    if (balloon_positions.hasOwnProperty(balloon_currently_following) == true){
        // There is balloon data!
        var _latest_telem = balloon_positions[balloon_currently_following].latest_data;
        
        _summary_update.alt = _latest_telem.position[2].toFixed(0) + "m (" + _latest_telem.max_alt.toFixed(0) + ")";
        var _speed = _latest_telem.speed*3.6;
        _summary_update.speed = _speed.toFixed(0) + " kph";
        _summary_update.vel_v = _latest_telem.vel_v.toFixed(1) + " m/s";


        // Work out if we have data to calculate look-angles from.
        if (chase_car_position.latest_data.length == 3){
            // Chase car position available - use that.
            var _car = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]};
        } else if (home_marker !== "NONE") {
            // Home marker is on the map - use the home marker position
            var _car = {lat:chase_config.default_lat, lon:chase_config.default_lon, alt:chase_config.default_alt};
        } else {
            // Otherwise, nothing we can use 
            var _car = null;
        }

        if(_car !== null){
            var _bal = {lat:_latest_telem.position[0], lon:_latest_telem.position[1], alt:_latest_telem.position[2]};
            var _look_angles = calculate_lookangles(_car, _bal);
            _summary_update.elevation = _look_angles.elevation.toFixed(0) + "°";
            _summary_update.azimuth = _look_angles.azimuth.toFixed(0) + "°";
            _summary_update.range = (_look_angles.range/1000).toFixed(1) + "km";
        }else{
            // No Chase car position data - insert dummy values
            _summary_update.azimuth = "---°";
            _summary_update.elevation = "--°";
            _summary_update.range = "----m";
        }

    }else{
        // No balloon data!
        _summary_update = {id: 1, alt:'-----m', speed:'---kph', vel_v:'-.-m/s', azimuth:'---°', elevation:'--°', range:'----m'}
    }
    // Update table
    $("#summary_table").tabulator("setData", [_summary_update]);
    if (summary_enlarged == true){
        var row = $("#summary_table").tabulator("getRow", 1);
        row.getElement().addClass("largeTableRow");
        $("#summary_table").tabulator("redraw", true);
    }
}
function updateSummaryDisplayImperial(){
    
    // Update the 'Payload Summary' display.
    var _summary_update = {id:1};
    // See if there is any payload data.
    if (balloon_positions.hasOwnProperty(balloon_currently_following) == true){
        // There is balloon data!
        var _latest_telem = balloon_positions[balloon_currently_following].latest_data;
        
        _summary_update.alt = (_latest_telem.position[2]*3.28084).toFixed(0) + "ft (" + (_latest_telem.max_alt*3.28084).toFixed(0) + "ft)";
        var _speed = _latest_telem.speed*3.6 ;
        _summary_update.speed = (_speed*0.621371).toFixed(0) + " mph";
        _summary_update.vel_v = (_latest_telem.vel_v*3.28084*60).toFixed(0) + " ft/min";


        // Work out if we have data to calculate look-angles from.
        if (chase_car_position.latest_data.length == 3){
            // Chase car position available - use that.
            var _car = {lat:chase_car_position.latest_data[0], lon:chase_car_position.latest_data[1], alt:chase_car_position.latest_data[2]};
        } else if (home_marker !== "NONE") {
            // Home marker is on the map - use the home marker position
            var _car = {lat:chase_config.default_lat, lon:chase_config.default_lon, alt:chase_config.default_alt};
        } else {
            // Otherwise, nothing we can use 
            var _car = null;
        }

        if(_car !== null){
            // We have a chase car position! Calculate relative position.
            var _bal = {lat:_latest_telem.position[0], lon:_latest_telem.position[1], alt:_latest_telem.position[2]};
            var _look_angles = calculate_lookangles(_car, _bal);

            _summary_update.elevation = _look_angles.elevation.toFixed(0) + "°";
            _summary_update.azimuth = _look_angles.azimuth.toFixed(0) + "°";
            if (_look_angles.range > chase_config['switch_miles_feet']) {
              _summary_update.range = (_look_angles.range*0.621371/1000).toFixed(1) + " miles";
            } else {
              _summary_update.range = (_look_angles.range*3.28084).toFixed(1) + "ft";
            }
        }else{
            // No Chase car position data - insert dummy values
            _summary_update.azimuth = "---°";
            _summary_update.elevation = "--°";
            _summary_update.range = "----m";
        }

    }else{
        // No balloon data!
        _summary_update = {id: 1, alt:'-----m', speed:'---kph', vel_v:'-.-m/s', azimuth:'---°', elevation:'--°', range:'----m'}
    }
    // Update table
    $("#summary_table").tabulator("setData", [_summary_update]);
    if (summary_enlarged == true){
        var row = $("#summary_table").tabulator("getRow", 1);
        row.getElement().addClass("largeTableRow");
        $("#summary_table").tabulator("redraw", true);
    }
}

function handleTelemetry(data){
    // Telemetry Event messages contain a dictionary of position data.
    // It should have the fields:
    //  callsign: string
    //  position: [lat, lon, alt]
    //  vel_v: float
    //  time_to_landing: String
    // If callsign = 'CAR', the lat/lon/alt will be considered to be a car telemetry position.

    if(initial_load_complete == false){
        // If we have not completed our initial load of telemetry data, discard this data.
        return;
    }

    // Handle chase car position updates.
    if (data.callsign == 'CAR'){
        // Update car position.
        chase_car_position.latest_data = data.position;
        chase_car_position.heading = data.heading; // degrees true
        chase_car_position.heading_valid = data.heading_valid;
        chase_car_position.speed = data.speed; // m/s

        // Update range rings, if they are enabled.
        recenterRangeRings(data.position);

        // Update Detailed GPS / Heading Info
        if(data.hasOwnProperty('heading_status')){
            $("#headingStatus").text(data.heading_status);
        }

        if(data.hasOwnProperty('numSV')){
            $("#numSVStatus").text(data.numSV.toFixed(0));
        }

        //console.log(data);

        // Update Chase Car Speed
        if (document.getElementById("showCarSpeed").checked){
            if (chase_config['unitselection'] == "imperial") {
		$("#chase_car_speed").text( (chase_car_position.speed*3.6*0.621371).toFixed(0) + " mph");
                } else {
		$("#chase_car_speed").text( (chase_car_position.speed*3.6).toFixed(0) + " kph");
                }
            $("#chase_car_speed_header").text("Chase Car Speed");
        } else {
            $("#chase_car_speed").text("");
            $("#chase_car_speed_header").text("");
        }

        if(data.hasOwnProperty('replay_time')){
            // Data is coming from a log file, display the time.
            $("#log_time").text(data.replay_time);
        }

        // Update heading information
        if (document.getElementById("showCarHeading").checked){
            $("#chase_car_heading").text(chase_car_position.heading.toFixed(0) + "˚");
            $("#chase_car_heading_header").text("Heading");
        } else {
            $("#chase_car_heading").text("");
            $("#chase_car_heading_header").text("");
        }

        if (chase_car_position.marker == 'NONE'){
            // Create marker!
            chase_car_position.marker = L.marker(chase_car_position.latest_data,{title:"Chase Car", icon: carIcon, rotationOrigin: "center center"})
                    .addTo(map);
            chase_car_position.path = L.polyline([chase_car_position.latest_data],{title:"Chase Car", color:'black', weight:1.5});
            // If the user wants the chase car tail, add it to the map.
            if (document.getElementById("chaseCarTrack").checked == true){
                chase_car_position.path.addTo(map);
            }
        } else {
            chase_car_position.path.addLatLng(chase_car_position.latest_data);
            chase_car_position.marker.setLatLng(chase_car_position.latest_data).update();
        }

        var _car_heading = chase_car_position.heading - 90.0;
        if (_car_heading<=90.0){
            chase_car_position.marker.setIcon(carIcon);
            chase_car_position.marker.setRotationAngle(_car_heading);
        }else{
            // We are travelling West - we need to use the flipped car icon.
            _car_heading = _car_heading - 180.0;
            chase_car_position.marker.setIcon(carIconFlip);
            chase_car_position.marker.setRotationAngle(_car_heading);
        }
        car_data_age = 0.0;
    }else{

        // Otherwise, we have a balloon
        // Have we seen this ballon before? 
        if (balloon_positions.hasOwnProperty(data.callsign) == false){

            // Convert the incoming data into a format suitable for adding into the telem store.
            var temp_data = {};
            temp_data.telem = data;
            temp_data.path = [data.position];
            temp_data.burst = [];
            temp_data.pred_path = [];
            temp_data.pred_landing = [];
            temp_data.abort_path = [];
            temp_data.abort_landing = [];

            // Add it to the telemetry store and create markers.
            add_new_balloon(temp_data);

            // Update data age to indicate current time.
            balloon_positions[data.callsign].age = Date.now();

        } else {
            // Yep - update the sonde_positions entry.
            balloon_positions[data.callsign].latest_data = data;
            balloon_positions[data.callsign].age = Date.now();
            balloon_positions[data.callsign].path.addLatLng(data.position);
            balloon_positions[data.callsign].marker.setLatLng(data.position).update();

            if (data.vel_v < 0){
                balloon_positions[data.callsign].marker.setIcon(balloonDescentIcons[balloon_positions[data.callsign].colour]);
            }else{
                balloon_positions[data.callsign].marker.setIcon(balloonAscentIcons[balloon_positions[data.callsign].colour]);
            }

            if (data.position[2] < parachute_min_alt){
                balloon_positions[data.callsign].marker.setIcon(balloonPayloadIcons[balloon_positions[data.callsign].colour]);
            }

            if(data.hasOwnProperty('snr') == true){
                balloon_positions[data.callsign].snr = data.snr;
            }

        }

        // Update the telemetry table display
        updateTelemetryTable();

        // Are we currently following any other sondes?
        if (balloon_currently_following === "none"){
            // If not, follow this one!
            balloon_currently_following = data.callsign;
        }

        // Update the Summary and time-to-landing displays
        if (balloon_currently_following === data.callsign){
            $('#time_to_landing').text(data.time_to_landing);
            payload_data_age = 0.0;
        }
    }

    // Auto Pan selection between balloon or car.
    var _current_follow = $('input[name=autoFollow]:checked').val();
    if ((_current_follow == 'payload') && (data.callsign == balloon_currently_following)){
        map.panTo(data.position);
    } else if (_current_follow == 'car' && data.callsign == 'CAR'){
        map.panTo(data.position);
    }else{
        // Don't pan to anything.
    }

    // Update the summary display.
    updateSummaryDisplay();
}

function handleModemStats(data){
    // Update balloon positions store with incoming modem statistics data (SNR).
    if (balloon_positions.hasOwnProperty(data.callsign) == true){
        balloon_positions[data.callsign].snr = data.snr;
    }
}

function hideBalloon(callsign){
    if (balloon_positions.hasOwnProperty(callsign) == true){
            balloon_positions[callsign].visible = false;
            // Remove the layers for this balloon from the map.
            if(map.hasLayer(balloon_positions[callsign].marker) == true){
                // Balloon is currently on the map, so remove it.
                // These two will always be on the map together.
                balloon_positions[callsign].marker.remove();
                balloon_positions[callsign].path.remove();
            }
            if(map.hasLayer(balloon_positions[callsign].burst_marker) == true){
                // Burst marker might not always be visible, i.e. after burst.
                balloon_positions[callsign].burst_marker.remove();
            }

            if(map.hasLayer(balloon_positions[callsign].pred_marker) == true){
                // Prediction marker and path will always be shown together.
                balloon_positions[callsign].pred_marker.remove();
                balloon_positions[callsign].pred_path.remove();
            }
            if(map.hasLayer(balloon_positions[callsign].abort_marker) == true){
                // The same is true for the abort marker and path.
                balloon_positions[callsign].abort_marker.remove();
                balloon_positions[callsign].abort_path.remove();
            }
    }
}

function showBalloon(callsign){
    if (balloon_positions.hasOwnProperty(callsign) == true){
            balloon_positions[callsign].visible = true;
            // We can safely just add the balloon marker and path back onto the map.
            balloon_positions[callsign].marker.addTo(map);
            balloon_positions[callsign].path.addTo(map);
            
            if(balloon_positions[callsign].burst_marker != null){
                // The burst marker might not always be present.
                balloon_positions[callsign].burst_marker.addTo(map);
            }

            if(balloon_positions[callsign].pred_marker != null){
                balloon_positions[callsign].pred_marker.addTo(map);
                balloon_positions[callsign].pred_path.addTo(map);
            }

            if(balloon_positions[callsign].abort_marker != null){
                balloon_positions[callsign].abort_marker.addTo(map);
                balloon_positions[callsign].abort_path.addTo(map);
            }

    }
}
