/****************************************************************************
**
** Copyright (C) 2019-2023 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
** Copyright (C) 2019-2023 General Magic B.V., info@generalmagic.com
**
** BSD License Usage
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of Klarälvdalens Datakonsult AB nor the names of
**     its contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
****************************************************************************/

//! [MapView code]
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import GeneralMagic 2.0
import FpsCounter 1.0

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("MapCoverageMultiPointInteractive")
    property var updater: ServicesManager.contentUpdater(ContentItem.Type.RoadMap)
    Component.onCompleted: {
        //! [Set token safely]
        ServicesManager.settings.token = __my_secret_token;
        //! [Set token safely]

        ServicesManager.logLevel = ServicesManager.Error;
        ServicesManager.settings.allowInternetConnection = true; // enable connection to online services

        updater.autoApplyWhenReady = true;
        updater.update();
    }

    FpsCounter {
        id: fpsCounter
    }

    /////////////////////////////////
    //the following block is used only to enable autocenter on landmark group
    //and to render a route between the individual landmarks/waypoints/icons;
    //also, the map coverage check uses this list of landmarks;
    LandmarkList {
        id: routingWaypoints
    }
    /////////////////////////////////

    /////////////////////////////////
    //a RoutingService is defined, used in this example only to
    //compute and render a route plotted using list of waypoints/landmarks
    //in the order in which they were added to the list;
    //this block is not required to autocenter on the landmark group;
    //set internet connection to true in the Component.onCompleted block
    //for route computation and rendering to work;
    RoutingService {
        id: routingService
        type: Route.Type.Fastest
        transportMode: Route.TransportMode.Car
        // The list of waypoints is made available to the routing service.
        waypoints: routingWaypoints
        onFinished: {
            mapView.routeCollection.set(routeList);
            mapView.centerOnRouteList(routeList);
        }
    }
    /////////////////////////////////

    SearchService {

        // Reverse geocoding takes as input a location on the map (lon,lat coordinates),
        // in this case, interactively selected by the user, and gives as output a list
        // of points of interest located near the selected position, 3 items in this example.

        id: reverseGeocoding
        searchMapPOIs: true
        searchAddresses: true
        limit: 10
        thresholdDistance: 500

        // The list of 3 points of interest located near the selected position
        // is shown here in a popup, giving the user the option to select one of them
        // to be added as a waypoint for a route, or simply click outside the popup to close it.

        onSearchCompleted: addWaypointPopup.open()
    }

    Popup {
        // This shows the results of the reverse geocoding in a popup.

        id: addWaypointPopup
        modal: true
        focus: true
        closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
        width: 300
        height: 200

        ListView {
            id: searchList
            anchors.fill: parent
            anchors.margins: 1
            clip: true
            model: reverseGeocoding

            delegate: RowLayout {
                id: row
                height: 48
                width: searchList.width
                IconView {
                    iconSource: landmark.icon
                    Layout.maximumHeight: row.height
                    Layout.maximumWidth: row.height
                    width: height
                    height: row.height
                }
                ColumnLayout {
                    Layout.fillHeight: true
                    Layout.fillWidth: true
                    Text {
                        Layout.fillWidth: true
                        text: landmark.name + " (" + distance(landmark.coordinates.distance(reverseGeocoding.coordinates)) + ")"
                        wrapMode: Text.WrapAnywhere
                    }
                    Text {
                        Layout.fillWidth: true
                        text: landmark.description
                        font.italic: true
                        wrapMode: Text.WrapAnywhere
                    }
                }
                TapHandler {
                    target: row
                    onTapped: {
                        // This adds a waypoint to the route by selecting
                        // an item from the popup list of reverse geocoding results.
                        addWaypointPopup.close()
                        routingWaypoints.append(landmark)
                        show_waypoint_landmarks_on_map();
                    }
                }
            }
        }
    }

    function distance(meters)
    {
        return meters >= 1000 ? (meters / 1000.).toFixed(3) + " Km" :  meters.toFixed(0) + " m";
    }

    function show_waypoint_landmarks_on_map()
    {
        //show landmarks on map as point markers
        //a markercoordlist is created, and the coordinates of each interactively selected
        //waypoint/landmark is added to the markercoordlist which is used to render the
        //sketches-type markers on the map for visual feedback;
        let markercoordlist = ServicesManager.createMarker();
        for (let n = 0; n < routingWaypoints.length; n++) {
            let lmkit = routingWaypoints.get(n)
            markercoordlist.append(lmkit.coordinates);
        }
        //the marker render settings contain configuration such as size and icon image
        let markerRenderSettings = ServicesManager.createMarkerRenderSettings();
        //sketches-type markers - cannot be grouped;
        //let marker = ServicesManager.createMarker();
        markerRenderSettings.imageSize = 32;
        //the following line sets a custom icon image;
        //choose one icon image line and uncomment it;
        //the icon image is configured in qml.qrc, and located in the project directory;
        //if an icon image is not set, markers will be shown as blue dots;
        markerRenderSettings.icon = ServicesManager.createIconFromFile("qrc:/violetcircle.png");
        //markerRenderSettings.icon = ServicesManager.createIconFromId(Icon.RedBall);
        //markerRenderSettings.icon = ServicesManager.createIconFromId(Icon.GreenBall);
        //markerRenderSettings.icon = ServicesManager.createIconFromId(Icon.BlueBall);
        console.log(markerRenderSettings);

        //SKETCHES-type markers - cannot be grouped, all markers are rendered at all zoom levels;
        //render on the map the coordinate points for which map coverage is checked
        let sketchlist = mapView.markerCollection.getExtendedList(MarkerList.Type.Point);
        sketchlist.clear(); //clear previous markers so list does not grow with duplicates
        sketchlist.append(markercoordlist, markerRenderSettings);

        //print some debug info to the console
        console.log("marker sketchlist len:" + sketchlist.length + " icon coords:" + sketchlist.get(0).length);
        console.log("marker len:"+markercoordlist.length+" renderset:"+markerRenderSettings);

        //center on list of waypoints/landmarks
        if (routingWaypoints.length > 1)
        {
            mapView.centerOnGeographicArea(routingWaypoints.boundingBox)
        }
        return markercoordlist;
    }

    MapView {
        id: mapView
        anchors.fill: parent
        viewAngle: 25
        zoomLevel: 69
        cursorVisibility: false
        viewPerspective: MapView.ViewPerspective.View3D
        buildingsVisibility: MapView.BuildingsVisibility.Show3D
        detailsQualityLevel: MapView.DetailsQualityLevel.Medium

        //////////////////////////////////////////////////////////////////////////////////
        //display x,y pixel position and lon,lat coordinate of clicked position on map
        //////////////////////////////////////////////////////////////////////////////////
        TapHandler {
            grabPermissions: PointerHandler.CanTakeOverFromAnything
            onTapped: {
                let coordinates = mapView.wgsForScreen(point.pressPosition);
                console.log("x,y (" + point.pressPosition.x + ", " + point.pressPosition.y + ")"
                            + " lon, lat " + coordinates.longitude + ", " + coordinates.latitude);
                lonlatPositionDisplay.text = "x,y (" + point.pressPosition.x + ", " + point.pressPosition.y + ")"
                        + " lon, lat " + coordinates.longitude + ", " + coordinates.latitude;
            }
            onLongPressed: {
                if (!useReverseGeocoding.checked) {

                    // This adds a waypoint to the route by long-tapping
                    // (tapping and holding for about a second)
                    // a given location directly on the map.
                    // First, create a landmark that can hold a route waypoint.

                    //Creates a landmark that is owned by the routingWaypoints, for efficiency.
                    //This is required because
                    //routingWaypoints.append(lmk);
                    //will clone the landmark unless the owner is the routingWaypoints list itself.

                    let lmk = ServicesManager.createLandmark(routingWaypoints);

                    // Next, add the map coordinates from the tap location to the waypoint.

                    lmk.coordinates = mapView.wgsForScreen(point.pressPosition);

                    // Finally, add the waypoint to the list from which the route is generated.

                    console.log("Waypoint added #########");
                    routingWaypoints.append(lmk);
                    show_waypoint_landmarks_on_map();

                    return;
                }

                if (point.pressPosition.x + addWaypointPopup.width > mapView.width)
                    addWaypointPopup.x = point.pressPosition.x - addWaypointPopup.width;
                else
                    addWaypointPopup.x = point.pressPosition.x

                if (point.pressPosition.y + addWaypointPopup.height > mapView.height)
                    addWaypointPopup.y = point.pressPosition.y - addWaypointPopup.height;
                else
                    addWaypointPopup.y = point.pressPosition.y

                reverseGeocoding.coordinates = mapView.wgsForScreen(point.pressPosition);
                reverseGeocoding.search();
            }
        }
        //////////////////////////////////////////////////////////////////////////////////
        //4-line display: 1) fps 2) lon,lat 3) single point map coverage 4) multi point map coverage
        //////////////////////////////////////////////////////////////////////////////////
        ColumnLayout {
            anchors.right: parent.right
            anchors.top: parent.top
            Text {
                id: fpsDisplay
                text: "fps " + fpsCounter.fps
            }
            Text {
                id: lonlatPositionDisplay
                text: "Click on map to see lon,lat"
            }
            Label {
                id: infoLabel
                text: "Long press to add a waypoint"
            }
            CheckBox {
                id: useReverseGeocoding
                checked: true
                text: "Show reverse geocoding list"
            }
            Text {
                id: singleCoordMapCoverage
                text: ""
                color: "#0000ff"
            }
            Text {
                id: multiCoordsMapCoverage
                text: ""
                color: "#0000ff"
            }
        }
        Button {
            anchors.left: parent.left
            anchors.bottom: parent.bottom
            enabled: routingWaypoints.length > 1
            text: qsTr("Render route")
            background: Rectangle {
                opacity: parent.hovered ? 1 : 0.5
                color: enabled ? parent.down ? "#aa00aa" :
                                               (parent.hovered ? "#0000ff" : "#2000ff") : "#aaaaaa"
            }
            palette { buttonText: "#ffffff"; }
            onClicked: {
                //render route when button is clicked only if there are min 2 waypoints in the list;
                if (routingWaypoints.length > 1)
                {
                    //compute and render a route plotted using list of waypoints/landmarks
                    //in the order in which they were added
                    //center on route plotted from list of waypoints/landmarks
                    routingService.update()
                }
            }
        }
        Button {
            anchors.right: parent.right
            anchors.bottom: parent.bottom
            text: qsTr("Clear route")
            background: Rectangle {
                opacity: parent.hovered ? 1 : 0.5
                color: enabled ? parent.down ? "#aa00aa" :
                                               (parent.hovered ? "#0000ff" : "#2000ff") : "#aaaaaa"
            }
            palette { buttonText: "#ffffff"; }
            onClicked: {
                //delete route when button is clicked;
                //compute and render a route plotted using list of waypoints/landmarks
                //in the order in which they were added
                routingWaypoints.clear()
                routingService.update()
            }
        }
        ColumnLayout {
            Button {
                text: "Center on/check coordlist map coverage"
                enabled: ServicesManager.settings.connected && routingWaypoints.length > 1
                background: Rectangle {
                    opacity: parent.hovered ? 1 : 0.5
                    color: enabled ? parent.down ? "#aa00aa" :
                                                   (parent.hovered ? "#0000ff" : "#2000ff") : "#aaaaaa"
                }
                palette { buttonText: "#ffffff"; }
                onClicked: {
                    let markercoordlist = show_waypoint_landmarks_on_map();

                    //sketches-type markers - cannot be grouped;
                    //latitude and longitude of each icon to be added to the map

                    //////////////////////////////////////////////////////////////////////////////////
                    //check map coverage for one point - (latitude,longitude) coordinate
                    //////////////////////////////////////////////////////////////////////////////////
                    let onecoord = markercoordlist.get(0) //the first coord in the list, at index 0:lat,lon,alt
                    let mapcover = ServicesManager.mapDetails.coverage(onecoord);
                    let coverageResult = "Single coordinate point map coverage result: ";
                    switch ( mapcover )
                    {
                    case MapDetails.Unknown:
                        coverageResult = coverageResult + "MapDetails.Unknown"; break;
                    case MapDetails.Offline:
                        coverageResult = coverageResult + "MapDetails.Offline"; break;
                    case MapDetails.OnlineNoData:
                        coverageResult = coverageResult + "MapDetails.OnlineNoData"; break;
                    case MapDetails.OnlineTile:
                        coverageResult = coverageResult + "MapDetails.OnlineTile"; break;
                    default:
                        coverageResult = coverageResult + "default case - undefined"; break;
                    }
                    singleCoordMapCoverage.text = coverageResult;
                    console.log(coverageResult);

                    //////////////////////////////////////////////////////////////////////////////////
                    //check map coverage for multiple coordinates
                    //////////////////////////////////////////////////////////////////////////////////
                    let mapcovermulti = ServicesManager.mapDetails.coverageCoordList(markercoordlist)
                    coverageResult = "Multiple coordinate points map coverage result: ";
                    switch ( mapcovermulti )
                    {
                    case MapDetails.Unknown:
                        coverageResult = coverageResult + "MapDetails.Unknown"; break;
                    case MapDetails.Offline:
                        coverageResult = coverageResult + "MapDetails.Offline"; break;
                    case MapDetails.OnlineNoData:
                        coverageResult = coverageResult + "MapDetails.OnlineNoData"; break;
                    case MapDetails.OnlineTile:
                        coverageResult = coverageResult + "MapDetails.OnlineTile"; break;
                    default:
                        coverageResult = coverageResult + "default case - undefined"; break;
                    }
                    multiCoordsMapCoverage.text = coverageResult;
                    console.log(coverageResult);

                    //////////////////////////////////////////////////////////////////////////////////
                    // for each coordinate in the list, print in the console output:
                    // index, map coverage status, country code, coordinate(lon,lat), country name
                    //////////////////////////////////////////////////////////////////////////////////
                    let countrycode = "";
                    for (let idx = 0; idx < markercoordlist.length; idx++) {
                        countrycode = ServicesManager.mapDetails.countryCode(markercoordlist.get(idx))
                        let mapcountrycover = ServicesManager.mapDetails.countryCoverage(countrycode)
                        coverageResult = "Country coverage result by coordinate point: ";
                        switch ( mapcountrycover )
                        {
                        case MapDetails.Unknown:
                            coverageResult = coverageResult + "MapDetails.Unknown"; break;
                        case MapDetails.Offline:
                            coverageResult = coverageResult + "MapDetails.Offline"; break;
                        case MapDetails.OnlineNoData:
                            coverageResult = coverageResult + "MapDetails.OnlineNoData"; break;
                        case MapDetails.OnlineTile:
                            coverageResult = coverageResult + "MapDetails.OnlineTile"; break;
                        default:
                            coverageResult = coverageResult + "default case - undefined"; break;
                        }
                        console.log("[" + idx + "] " + coverageResult + " " + countrycode + " ("
                                    + markercoordlist.get(idx).longitude + ", " + markercoordlist.get(idx).latitude
                                    + ") " + ServicesManager.mapDetails.countryName(countrycode))
                    }

                    //the following block is used only to autocenter on the group of coordinates;
                    {
                        mapView.centerOnGeographicArea(routingWaypoints.boundingBox)
                    }
                }
            }
            Button {
                text: "Clear sketches-type markers/waypoints"
                background: Rectangle {
                    opacity: parent.hovered ? 1 : 0.5
                    color: enabled ? parent.down ? "#aa00aa" :
                                                   (parent.hovered ? "#0000ff" : "#2000ff") : "#aaaaaa"
                }
                palette { buttonText: "#ffffff"; }
                onClicked: {
                    //show how to clear sketches-type marker icons from the map;
                    //clear the list of points used for centering on the bounding box
                    //containing all points/landmarks/waypoints/markers/icons;
                    routingWaypoints.clear();
                    //also clear the markerlist used for rendering the points on the map;
                    //the same set of coordinates is in each of these lists, so both must
                    //be cleared;
                    let sketchlist = mapView.markerCollection.getExtendedList(MarkerList.Type.Point);
                    sketchlist.clear();
                }
            }
            Button {
                text: "Center on landmark/waypoint list"
                enabled: ServicesManager.settings.connected && routingWaypoints.length > 1
                background: Rectangle {
                    opacity: parent.hovered ? 1 : 0.5
                    color: enabled ? parent.down ? "#aa00aa" :
                                                   (parent.hovered ? "#0000ff" : "#2000ff") : "#aaaaaa"
                }
                palette { buttonText: "#ffffff"; }
                onClicked: {
                    show_waypoint_landmarks_on_map();
                    //center on list of waypoints/landmarks
                    if (routingWaypoints.length === 1)
                    {
                        mapView.centerOnGeographicArea(routingWaypoints.boundingBox)
                    }
                }
            }
        }
    }
}
//! [MapView code]
