// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

import QtQuick
import QtQuick3D

Item {
    id: root
    required property Node origin
    required property Camera camera

    property real xSpeed: 0.05
    property real ySpeed: 0.05

    property bool xInvert: false
    property bool yInvert: false

    property bool mouseEnabled: true
    property bool panEnabled: true

    readonly property bool inputsNeedProcessing: status.useMouse || status.isPanning

    readonly property real minimumZoom: 30
    readonly property real maximumZoom: 200

    readonly property real minimumTilt: 0
    readonly property real maximumTilt: 80

    implicitWidth: parent.width
    implicitHeight: parent.height

    Connections {
        target: camera
        Component.onCompleted: {
            onZChanged()
        }

        function onZChanged() {
            // Adjust near/far values based on distance
            let distance = camera.z
            if (distance < 1) {
                camera.clipNear = 0.01
                camera.clipFar = 100
            } else if (distance < 100) {
                camera.clipNear = 0.1
                camera.clipFar = 1000
            } else {
                camera.clipNear = 1
                camera.clipFar = 10000
            }
        }
    }

    DragHandler {
        id: dragHandler
        target: null
        enabled: mouseEnabled
        acceptedModifiers: Qt.NoModifier
        acceptedButtons: Qt.RightButton
        onCentroidChanged: {
            mouseMoved(Qt.vector2d(centroid.position.x, centroid.position.y), false);
        }

        onActiveChanged: {
            if (active)
                mousePressed(Qt.vector2d(centroid.position.x, centroid.position.y));
            else
                mouseReleased(Qt.vector2d(centroid.position.x, centroid.position.y));
        }
    }

    DragHandler {
        id: ctrlDragHandler
        target: null
        enabled: mouseEnabled && panEnabled
        //acceptedModifiers: Qt.ControlModifier
        onCentroidChanged: {
            panEvent(Qt.vector2d(centroid.position.x, centroid.position.y));
        }

        onActiveChanged: {
            if (active)
                startPan(Qt.vector2d(centroid.position.x, centroid.position.y));
            else
                endPan();
        }
    }

    PinchHandler {
        id: pinchHandler
        target: null
        enabled: mouseEnabled

        property real distance: 0.0
        onCentroidChanged: {
            panEvent(Qt.vector2d(centroid.position.x, centroid.position.y))
        }

        onActiveChanged: {
            if (active) {
                startPan(Qt.vector2d(centroid.position.x, centroid.position.y))
                distance = root.camera.z
            } else {
                endPan()
                distance = 0.0
            }
        }
        onScaleChanged: {

            camera.z = distance * (1 / scale)
            camera.z = Math.min(Math.max(camera.z, minimumZoom), maximumZoom)
        }
    }

    TapHandler {
        onTapped: root.forceActiveFocus()
    }

    WheelHandler {
        id: wheelHandler
        orientation: Qt.Vertical
        target: null
        enabled: mouseEnabled
        onWheel: event => {
            let delta = -event.angleDelta.y * 0.01;
            camera.z += camera.z * 0.1 * delta
            camera.z = Math.min(Math.max(camera.z, minimumZoom), maximumZoom)
        }
    }

    function mousePressed(newPos) {
        root.forceActiveFocus()
        status.currentPos = newPos
        status.lastPos = newPos
        status.useMouse = true;
    }

    function mouseReleased(newPos) {
        status.useMouse = false;
    }

    function mouseMoved(newPos: vector2d) {
        status.currentPos = newPos;
    }

    function startPan(pos: vector2d) {
        status.isPanning = true;
        status.currentPanPos = pos;
        status.lastPanPos = pos;
    }

    function endPan() {
        status.isPanning = false;
    }

    function panEvent(newPos: vector2d) {
        status.currentPanPos = newPos;
    }

    FrameAnimation {
        id: updateTimer
        running: root.inputsNeedProcessing
        onTriggered: status.processInput(frameTime * 100)
    }

    QtObject {
        id: status

        property bool useMouse: false
        property bool isPanning: false

        property vector2d lastPos: Qt.vector2d(0, 0)
        property vector2d lastPanPos: Qt.vector2d(0, 0)
        property vector2d currentPos: Qt.vector2d(0, 0)
        property vector2d currentPanPos: Qt.vector2d(0, 0)

        property real rotateAlongZ: 0
        property real rotateAlongXY: 50.0

        function processInput(frameDelta) {
            if (useMouse) {
                // Get the delta
                var delta = Qt.vector2d(lastPos.x - currentPos.x,
                                        lastPos.y - currentPos.y);

                var rotateX = delta.x * xSpeed * frameDelta
                if ( xInvert )
                    rotateX = -rotateX
                rotateAlongZ += rotateX;
                let rotateAlongZRad = rotateAlongZ * (Math.PI / 180.)

                origin.rotate(rotateX, Qt.vector3d(0.0, 0.0, -1.0), Node.SceneSpace)

                var rotateY = delta.y * -ySpeed * frameDelta
                if ( yInvert )
                    rotateY = -rotateY;

                let preRotateAlongXY = rotateAlongXY + rotateY
                if ( preRotateAlongXY <= maximumTilt && preRotateAlongXY >= minimumTilt )
                {
                    rotateAlongXY = preRotateAlongXY
                    origin.rotate(rotateY, Qt.vector3d(Math.cos(rotateAlongZRad), Math.sin(-rotateAlongZRad), 0.0), Node.SceneSpace)
                }

                lastPos = currentPos;
            }

            if (isPanning) {
                let delta = currentPanPos.minus(lastPanPos);
                delta.x = -delta.x

                delta.x = (delta.x / root.width) * camera.z * frameDelta
                delta.y = (delta.y / root.height) * camera.z * frameDelta

                let velocity = Qt.vector3d(0, 0, 0)
                // X Movement
                let xDirection = origin.right
                velocity = velocity.plus(Qt.vector3d(xDirection.x * delta.x,
                                                     xDirection.y * delta.x,
                                                     xDirection.z * delta.x));
                // Z Movement
                let zDirection = origin.right.crossProduct(Qt.vector3d(0.0, 0.0, -1.0))
                velocity = velocity.plus(Qt.vector3d(zDirection.x * delta.y,
                                                     zDirection.y * delta.y,
                                                     zDirection.z * delta.y));

                origin.position = origin.position.plus(velocity)

                lastPanPos = currentPanPos
            }
        }
    }

}
