 /*
 *  Komplex Wallpaper Engine
 *  Copyright (C) 2025 @DigitalArtifex | github.com/DigitalArtifex
 *
 *  This was originally part of the KDE Shader Wallpaper Project, which was the inspiration for this project.
 *  It has been modified to use the new channel structure and is being used to support
 *  ShaderToy imports.
 *
 *  --------------------------------------------------------------------------------------------------------
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 *  This software uses some of the QML code from JaredTao/jared2020@163.com's ToyShader for Android.
 *  See: https://github.com/jaredtao/TaoShaderToy/
 */

import QtCore
import QtQuick
import QtQuick.Controls
import QtQuick.Dialogs as Dialogs

import org.kde.plasma.core as PlasmaCore

Item
{
    property var screenGeometry
    property real pixelRatio: 1 //This will (hopefully) be set to PlasmaCore.Units.devicePixelRatio in onCompleted
    property vector3d iResolution: Qt.vector3d(wallpaper.configuration.resolution_x || mainItem.width,wallpaper.configuration.resolution_y || mainItem.height,1)//width, height, pixel aspect ratio
    property real iTime: 0 //used by most motion shaders 
    property real iTimeDelta: iTime 
    property var iChannelTime: [iTime, iTime, iTime, iTime] //individual channel time values
    property real iSampleRate: 44100 //used by audio shaders
    property int iFrame: 0
    property real iFrameRate: wallpaper.configuration.framerate_limit // Default frame rate for the shader
    property vector4d iMouse
    property var iDate
    property bool running: windowModel.runShader // Controls whether the wallpaper is running or paused

    // Individual channel resolutions to customize performance and quality
    property var iChannelResolution: [Qt.vector3d(wallpaper.configuration.iChannel0_resolution_x, wallpaper.configuration.iChannel0_resolution_y, pixelRatio),
                                      Qt.vector3d(wallpaper.configuration.iChannel1_resolution_x, wallpaper.configuration.iChannel1_resolution_y, pixelRatio),
                                      Qt.vector3d(wallpaper.configuration.iChannel2_resolution_x, wallpaper.configuration.iChannel2_resolution_y, pixelRatio),
                                      Qt.vector3d(wallpaper.configuration.iChannel3_resolution_x, wallpaper.configuration.iChannel3_resolution_y, pixelRatio)]

    id: mainItem

    // The WindowModel is used to manage the interaction with the desktop environment
    WindowModel
    {
        id: windowModel
        screenGeometry: mainItem.screenGeometry
    }

    Rectangle
    {
        anchors.fill: parent
        color: "black"

        Text
        {
            color: "white"
            text: "<h1>" + wallpaper.configuration.selectedShaderPath + "</h1>"
        }

        // Setup the shader channels (iChannel0, iChannel1, iChannel2, iChannel3)
        // These channels are used to pass channel data to the shader sources and can
        // be configured to use different types of media (Image, Video, Shader, Audio, CubeMap)
        ShaderChannel
        {
            //Fallback to a channel if the output channel is not set or there was an error loading the shader
            //visible: wallpaper.configuration.iChannel0_flag && (channelOutput.source === "" || channelOutput.source === undefined)
            visible: true
            iTime: mainItem.iTime
            iMouse: mainItem.iMouse

            iResolution: [
                wallpaper.configuration.iChannel0_resolution_x,
                wallpaper.configuration.iChannel0_resolution_y
            ]
            
            iDate: mainItem.iDate
            iFrame: mainItem.iFrame

            id: channel0
            width: wallpaper.configuration.iChannel0_resolution_x
            height: wallpaper.configuration.iChannel0_resolution_y
            type: wallpaper.configuration.iChannel0_type

            source: Qt.resolvedUrl(wallpaper.configuration.iChannel0)

            // ShaderToy seems to start at bottom left for 0,0
            wrapMode: wallpaper.configuration.iChannel0_wrap_mode
            textureMirroring: wallpaper.configuration.iChannel0_mirroring_mode
        }

        ShaderChannel
        {
            //Fallback to a channel if the output channel is not set or there was an error loading the shader
            visible: wallpaper.configuration.iChannel1_flag && (channelOutput.source === "" || channelOutput.source === undefined)
            iTime: mainItem.iTime

            iResolution: [
                wallpaper.configuration.iChannel1_resolution_x,
                wallpaper.configuration.iChannel1_resolution_y
            ]
            
            iDate: mainItem.iDate
            iFrame: mainItem.iFrame

            id: channel1
            width: wallpaper.configuration.iChannel1_resolution_x
            height: wallpaper.configuration.iChannel1_resolution_y
            type: wallpaper.configuration.iChannel1_flag ? wallpaper.configuration.iChannel1_type : 0

            source: wallpaper.configuration.iChannel1_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel1) : ""

            // ShaderToy seems to start at bottom left for 0,0
            wrapMode: wallpaper.configuration.iChannel1_wrap_mode
            textureMirroring: wallpaper.configuration.iChannel1_mirroring_mode
        }

        ShaderChannel
        {
            //Fallback to a channel if the output channel is not set or there was an error loading the shader
            visible: wallpaper.configuration.iChannel2_flag && (channelOutput.source === "" || channelOutput.source === undefined)
            iTime: mainItem.iTime

            iResolution: [
                wallpaper.configuration.iChannel2_resolution_x,
                wallpaper.configuration.iChannel2_resolution_y
            ]
            
            iDate: mainItem.iDate
            iFrame: mainItem.iFrame

            id: channel2
            width: wallpaper.configuration.iChannel2_resolution_x
            height: wallpaper.configuration.iChannel2_resolution_y
            type: wallpaper.configuration.iChannel2_flag ? wallpaper.configuration.iChannel2_type : 0

            source: wallpaper.configuration.iChannel2_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel2) : ""

            // ShaderToy seems to start at bottom left for 0,0
            wrapMode: wallpaper.configuration.iChannel2_wrap_mode
            textureMirroring: wallpaper.configuration.iChannel2_mirroring_mode
        }

        ShaderChannel
        {
            //Fallback to a channel if the output channel is not set or there was an error loading the shader
            visible: wallpaper.configuration.iChannel3_flag && (channelOutput.source === "" || channelOutput.source === undefined)
            iTime: mainItem.iTime

            iResolution: [
                wallpaper.configuration.iChannel3_resolution_x,
                wallpaper.configuration.iChannel3_resolution_y
            ]

            iDate: mainItem.iDate
            iFrame: mainItem.iFrame

            id: channel3
            width: wallpaper.configuration.iChannel3_resolution_x
            height: wallpaper.configuration.iChannel3_resolution_y
            type: wallpaper.configuration.iChannel3_flag ? wallpaper.configuration.iChannel3_type : 0

            source: wallpaper.configuration.iChannel3_flag ? Qt.resolvedUrl(wallpaper.configuration.iChannel3) : ""

            // ShaderToy seems to start at bottom left for 0,0
            wrapMode: wallpaper.configuration.iChannel3_wrap_mode
            textureMirroring: wallpaper.configuration.iChannel3_mirroring_mode
        }

        // The output channel that combines all the input channels and displays the final shader output
        // This channel must be set to a shader source file that has been pre-compiled to a QSB Fragment Shader
        ShaderChannel
        {
            iTime: mainItem.iTime
            iMouse: mainItem.iMouse
            iResolution: mainItem.iResolution
            iFrame: mainItem.iFrame

            id: channelOutput
            width: mainItem.iResolution.x
            height: mainItem.iResolution.y
            type: ShaderChannel.Type.ShaderChannel
            source: wallpaper.configuration.selectedShaderPath ? wallpaper.configuration.selectedShaderPath : ""

            iChannel0: channel0
            iChannel1: channel1
            iChannel2: channel2
            iChannel3: channel3

            visible: true // Set to true to display the output
            blending: false
        }

        ShaderEffectSource
        {
            anchors.fill: parent
            sourceItem: channelOutput
            sourceRect: Qt.rect(0,0, mainItem.iResolution.x, mainItem.iResolution.y)
            textureSize: Qt.size(mainItem.iResolution.x, mainItem.iResolution.y)
            hideSource: true
            visible: true
            smooth: true
            live: true

            id: finalSource
        }

        // To save on performance, just use one timer for all channels
        Timer
        {
            property var date: new Date();
            id: channelTimer

            //Not entirely sure if this will actually limit the frame rate
            interval: (1 / mainItem.iFrameRate) * 1000 //fps to ms cycles :: fps = 60 = 1 / 60 = 0.01666 * 1000 = 16
            repeat: true

            running: mainItem.running

            triggeredOnStart: true
            onTriggered: 
            {
                var startOfDay = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
                var secondsSinceMidnight = (date - startOfDay) / 1000;

                mainItem.iTime += (interval / 1000) * (wallpaper.configuration.shaderSpeed ? wallpaper.configuration.shaderSpeed : 1.0)
                mainItem.iDate = Qt.vector4d(date.getFullYear(), date.getMonth() + 1, date.getDate(), secondsSinceMidnight)
                mainItem.iFrame += 1
            }
        }
    }
    
    Component.onCompleted:
    {
        Qt.createQmlObject(`import QtQuick
        MouseArea 
        {
            id: mouseTrackingArea
            propagateComposedEvents: true
            preventStealing: false
            enabled: wallpaper.configuration.mouseAllowed
            anchors.fill: parent
            hoverEnabled: true
            onPositionChanged: (mouse) => {
                mouse.accepted = false
                mainItem.iMouse.x = mouse.x * wallpaper.configuration.mouseSpeedBias
                mainItem.iMouse.y = -mouse.y * wallpaper.configuration.mouseSpeedBias
            }
            onClicked:(mouse) => {
                mouse.accepted = false
                mainItem.iMouse.z = mouse.x
                mainItem.iMouse.w = mouse.y
            }
            // this still doesnt work... guess a C++ wrapper is all that can be done?
            onPressed:(mouse) => {
                mouse.accepted = false
            }
            onPressAndHold:(mouse) => {
                mouse.accepted = false
            }
            onDoubleClicked:(mouse) => {
                mouse.accepted = false
            }
            //cancelled, entered, and exited do not pass mouse events, so we can remove them
            onReleased:(mouse) => {
                mouse.accepted = false
            }
            onWheel: (mouse) => {
                mouse.accepted = false
            }
        }`, parent.parent, "mouseTrackerArea");

        // Initialize pixelRatio for shader use
        pixelRatio = 1 //PlasmaCore.Units.devicePixelRatio was removed in Plasma6
    }
}
