/**************************************************************************
 **                                                                      **
 ** Copyright (C) 2011-2026 Lukas Spies                                  **
 ** Contact: https://photoqt.org                                         **
 **                                                                      **
 ** This file is part of PhotoQt.                                        **
 **                                                                      **
 ** PhotoQt 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 2 of the License, or    **
 ** (at your option) any later version.                                  **
 **                                                                      **
 ** PhotoQt 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 PhotoQt. If not, see <http://www.gnu.org/licenses/>.      **
 **                                                                      **
 **************************************************************************/
pragma ComponentBehavior: Bound

import QtQuick
import PhotoQt

Item {

    id: image_top

    x: extraX + integratedXOffset
    y: extraY
    width: PQCConstants.availableWidth - lessW
    height: PQCConstants.availableHeight - lessH

    property bool thumbnailsHoldVisible: (PQCSettings.thumbnailsVisibility===1 || (PQCSettings.thumbnailsVisibility===2 && (imageIsAtDefaultScale || PQCConstants.currentImageScale < PQCConstants.currentImageDefaultScale)))

    property int extraX: (thumbnailsHoldVisible && PQCSettings.interfaceEdgeLeftAction==="thumbnails") ? PQCConstants.thumbnailsBarWidth : 0
    property int extraY: (thumbnailsHoldVisible && PQCSettings.interfaceEdgeTopAction==="thumbnails") ? PQCConstants.thumbnailsBarHeight : 0
    property int lessW: (thumbnailsHoldVisible && PQCSettings.interfaceEdgeRightAction==="thumbnails") ? PQCConstants.thumbnailsBarWidth : 0
    property int lessH: (thumbnailsHoldVisible && PQCSettings.interfaceEdgeBottomAction==="thumbnails") ? PQCConstants.thumbnailsBarHeight : 0

    property bool integratedInterface: PQCSettings.generalInterfaceVariant==="integrated"
    property int integratedXOffset: ((integratedInterface&&PQCSettings.metadataSideBar&&PQCSettings.metadataSideBarLocation==="left") ? PQCSettings.metadataSideBarWidth : 0)

    Item {
        id: image_top_geo
        anchors.fill: parent
    }

    // this mouse area will catch mouse movements AFTER a file has been selected and BEFORE it is loaded
    // that time gap can be long, e.g., for very large archives with very many files
    PQMouseArea {
        anchors.fill: parent
        anchors.topMargin: PQCSettings.interfaceWindowMode && !PQCSettings.interfaceWindowDecoration ? 30 : 0
        hoverEnabled: true
        enabled: !PQCConstants.imageInitiallyLoaded
        acceptedButtons: Qt.AllButtons
        doubleClickThreshold: PQCSettings.interfaceDoubleClickThreshold

        property bool holdTrigger: false
        property point touchPos: Qt.point(-1,-1)
        onPositionChanged: (mouse) => {
            var pos = mapToItem(PQGlobalItems.toplevelItem, mouse.x, mouse.y)
            PQCNotify.mouseMove(pos.x, pos.y)
        }
        onWheel: (wheel) => {
            wheel.accepted = true
            PQCNotify.mouseWheel(Qt.point(wheel.x, wheel.y), wheel.angleDelta, wheel.modifiers)
        }
        onPressed: (mouse) => {
            holdTrigger = false
            var pos = mapToItem(PQGlobalItems.toplevelItem, mouse.x, mouse.y)
            touchPos = pos
            PQCNotify.mousePressed(mouse.modifiers, mouse.button, pos)
        }
        onMouseDoubleClicked: (mouse) => {
            var pos = mapToItem(PQGlobalItems.toplevelItem, mouse.x, mouse.y)
            PQCNotify.mouseDoubleClicked(mouse.modifiers, mouse.button, pos)
        }
        onReleased: (mouse) => {
            if(holdTrigger) {
                holdTrigger = false
                return
            }

            // a context menu is open -> don't continue
            if(PQCConstants.whichContextMenusOpen.length > 0) {
                PQCNotify.closeAllContextMenus()
                return
            }

            if(mouse.button === Qt.LeftButton)
                PQCNotify.loaderShow("FileDialog")
            else {
                var pos = mapToItem(PQGlobalItems.toplevelItem, mouse.x, mouse.y)
                PQCNotify.mouseReleased(mouse.modifiers, mouse.button, pos)
            }
        }
        onPressAndHold: (mouse) => {
            holdTrigger = true
            var pos = mapToItem(PQGlobalItems.toplevelItem, mouse.x, mouse.y)
            if(Math.abs(pos.x - touchPos.x) < 20 && Math.abs(pos.y - touchPos.y) < 20)
                PQCScriptsShortcuts.executeInternalCommandWithMousePos("__contextMenuTouch", pos)
        }
    }

    property list<string> visibleSourcePrevCur: ["",""]
    Connections{

        target: PQCConstants

        function onCurrentImageSourceChanged() {
            image_top.visibleSourcePrevCur[1] = image_top.visibleSourcePrevCur[0]
            image_top.visibleSourcePrevCur[0] = PQCConstants.currentImageSource
            image_top.visibleSourcePrevCurChanged()
            PQCNotify.currentImageLoadedAndDisplayed(PQCConstants.currentImageSource)
        }

    }

    property bool isSomeVideoLoaded: false

    property bool imageIsAtDefaultScale: Math.abs(PQCConstants.currentImageScale-PQCConstants.currentImageDefaultScale) < 1e-6

    property string randomAnimation: "opacity"

    property point extraControlsLocation: Qt.point(-1,-1)

    property var rememberChanges: ({})
    property list<var> reuseChanges: []

    property int howManyLoaders: 2*PQCSettings.imageviewPreloadInBackground+2
    property int bgOffset: 0
    property list<string> bgFiles: []

    clip: true

    Item {

        x: PQCSettings.imageviewMargin
        y: PQCSettings.imageviewMargin
        width: parent.width-2*PQCSettings.imageviewMargin
        height: parent.height-2*PQCSettings.imageviewMargin

        Repeater {

            id: repeaterimage

            property list<bool> allactive: [false,false,false,false,
                                            false,false,false,false,
                                            false,false,false,false]

            // we set this to the max number so that the delegates don't get reloaded when the number of images to be preloaded is changed
            model: 12

            PQImageDisplay {

                required property int modelData

                rememberChanges: image_top.rememberChanges
                reuseChanges: image_top.reuseChanges
                imageTopGeometryItem: image_top_geo
                visibleSourcePrevCur: image_top.visibleSourcePrevCur
                randomAnimation: image_top.randomAnimation

                onTellAboutReuseChanged: (changes) => {
                    image_top.reuseChanges = changes
                }
                onTellAboutRememberChanged: (changes) => {
                    image_top.rememberChanges[imageSource] = changes
                }
                onDeleteRememberChanges: {
                    delete image_top.rememberChanges[imageSource]
                }

                onReloadMinimap: {
                    minimap_loader.active = false
                    minimap_loader.active = true
                }

                onActiveChanged: {
                    repeaterimage.allactive[modelData] = active
                }

                onIAmReady: {
                    image_top.newMainImageReady(modelData)
                }
                onImageLoadedAndReadyChanged: {
                    if(PQCConstants.currentImageSource !== imageSource && PQCFileFolderModel.currentFile !== imageSource) {
                        if(image_top.bgOffset < image_top.bgFiles.length)
                            timer_loadbg.restart()
                    }
                }
            }
        }

    }

    // this accepts files dragged into the window
    DropArea {

        anchors.fill: parent
        enabled: !PQCConstants.isModalOpen

        onDropped: (event) => {

            var found = false;

            if(event.hasUrls) {

                const suffixes = PQCImageHandler.getEnabledSuffixes()
                const mimetypes = PQCImageHandler.getEnabledMimetypes()

                for(let i = 0; i < event.urls.length; ++i) {

                    var suffix1 = PQCScriptsFilesPaths.getSuffixLowerCase(event.urls[i])
                    var suffix2 = PQCScriptsFilesPaths.getCompleteSuffixLowerCase(event.urls[i])
                    var mimetype = PQCScriptsImages.getMimetypeForFile(event.urls[i])

                    if(suffixes.indexOf(suffix1) > -1 || suffixes.indexOf(suffix2) > -1 || mimetypes.indexOf(mimetype) > -1) {
                        PQCFileFolderModel.fileInFolderMainView = PQCScriptsFilesPaths.cleanPath(PQCScriptsFilesPaths.fromPercentEncoding(event.urls[i]))
                        PQCFileFolderModel.folderFileDialog = PQCScriptsFilesPaths.getDir(PQCFileFolderModel.fileInFolderMainView)
                        found = true
                        break;
                    }

                }

            }

            event.accepted = found

        }

        onEntered: (event) => {

            // do we accept the event?
            var found = false;

            // if there are urls:
            if(event.hasUrls) {

                const suffixes = PQCImageHandler.getEnabledSuffixes()
                const mimetypes = PQCImageHandler.getEnabledMimetypes()

                // we look for the first url that is supported and load that one
                for(var i in event.urls) {

                    // suffix and mimetype
                    var suffix1 = PQCScriptsFilesPaths.getSuffixLowerCase(event.urls[i])
                    var suffix2 = PQCScriptsFilesPaths.getCompleteSuffixLowerCase(event.urls[i])
                    var mimetype = PQCScriptsImages.getMimetypeForFile(event.urls[i])

                    if(suffixes.indexOf(suffix1) > -1 || suffixes.indexOf(suffix2) > -1 || mimetypes.indexOf(mimetype) > -1) {
                        found = true
                        break;
                    }

                }

            }

            event.accepted = found

        }
    }

    /*********************************************************/
    // different controls for various image items

    PQAnimatedImageControls {}
    PQPhotoSphereControls {}
    PQArchiveControls {}
    PQDocumentControls {}
    PQVideoControls {}
    PQMotionPhotoControls {}

    /*********************************************************/

    Loader {
        id: minimap_loader
        active: PQCSettings.imageviewShowMinimap && !PQCConstants.showingPhotoSphere
        asynchronous: true
        sourceComponent:
            PQMinimap {}
    }

    Component.onCompleted: {

        // this takes the first value unless it is empty
        var fpath = PQCConstants.startupFilePath || PQCFileFolderModel.currentFile

        // we only trigger this is BOTH possible values for fpath are empty
        if(fpath === "")
            return

        var img = repeaterimage.itemAt(0)

        const isFolder = PQCScriptsFilesPaths.isFolder(fpath)

        if(img === null || (isFolder && PQCFileFolderModel.countMainView === 0)) {
            loadFirstImage.counter = 0
            loadFirstImage.start()
            return
        }

        if(isFolder)
            fpath = (PQCFileFolderModel.countMainView > 0 ? PQCFileFolderModel.entriesMainView[0] : "")

        if(PQCConstants.startupFilePath !== "")
            PQCConstants.startupFilePath = fpath

        img.containingFolder = PQCScriptsFilesPaths.getDir(fpath)
        img.lastModified = PQCScriptsFilesPaths.getFileModified(fpath).toLocaleString()
        img.imageSource = fpath
        img.thisIsStartupFile = true

    }

    Timer {
        id: loadFirstImage
        interval: 10
        property int counter: 0
        onTriggered: {

            var fpath = PQCConstants.startupFilePath || PQCFileFolderModel.currentFile

            // we only trigger this is BOTH possible values for fpath are empty
            if(fpath === "")
                return

            var img = repeaterimage.itemAt(0)

            const isFolder = PQCScriptsFilesPaths.isFolder(fpath)

            if(img === null || (isFolder && PQCFileFolderModel.countMainView === 0 && counter < 50)) {
                counter += 1
                loadFirstImage.restart()
                return
            }

            if(isFolder)
                fpath = (PQCFileFolderModel.countMainView > 0 ? PQCFileFolderModel.entriesMainView[0] : "")

            if(PQCConstants.startupFilePath !== "")
                PQCConstants.startupFilePath = fpath

            img.containingFolder = PQCScriptsFilesPaths.getDir(fpath)
            img.lastModified = PQCScriptsFilesPaths.getFileModified(fpath).toLocaleString()
            img.imageSource = fpath
            img.thisIsStartupFile = true

        }
    }

    // make sure next/prev image is loaded in background
    // we make sure this doesn't start until the main image is fully shown
    Timer {
        id: timer_loadbg
        interval: PQCSettings.imageviewAnimationDuration*100
        onTriggered: {

            var nexttwo = [image_top.bgFiles[image_top.bgOffset], image_top.bgFiles[image_top.bgOffset+1]]
            image_top.bgOffset += 2

            // get the filepath of the previous/next files
            var prevFile = nexttwo[0]
            var nextFile = nexttwo[1]

            // the current folder and the modified timestamps
            var curFolder = PQCScriptsFilesPaths.getDir(PQCFileFolderModel.currentFile)
            var prevModified = ""
            var nextModified = ""

            // first check whether images already loaded
            var foundPrev = -1
            var foundNext = -1

            const prevFileIsVideo = PQCScriptsImages.isMpvVideo(prevFile) || PQCScriptsImages.isQtVideo(prevFile)
            const nextFileIsVideo = PQCScriptsImages.isMpvVideo(nextFile) || PQCScriptsImages.isQtVideo(nextFile)

            // look for previous image
            if(!prevFileIsVideo) {
                prevModified = PQCScriptsFilesPaths.getFileModified(prevFile).toLocaleString()
                for(var i = 0; i < image_top.howManyLoaders; ++i) {
                    var previmg = repeaterimage.itemAt(i)
                    if(previmg.imageSource === prevFile && previmg.containingFolder === curFolder && previmg.lastModified === prevModified) {
                        foundPrev = i
                        break;
                    }
                }
            }

            // look for next image
            if(!nextFileIsVideo) {
                nextModified = PQCScriptsFilesPaths.getFileModified(nextFile).toLocaleString()
                for(var j = 0; j < image_top.howManyLoaders; ++j) {
                    var nextimg = repeaterimage.itemAt(j)
                    if(nextimg.imageSource === nextFile && nextimg.containingFolder === curFolder && nextimg.lastModified === nextModified) {
                        foundNext = j
                        break;
                    }
                }
            }

            // previous image not yet setup
            if(foundPrev == -1 && !prevFileIsVideo) {

                var thenextimg = repeaterimage.itemAt(foundNext)

                for(var k = 0; k < image_top.howManyLoaders; ++k) {

                    var curprevimg = repeaterimage.itemAt(k)

                    // k not the current main image and not the next image
                    if(curprevimg.imageSource !== PQCFileFolderModel.currentFile && (foundNext === -1 || curprevimg.imageSource !== thenextimg.imageSource)) {
                        foundPrev = k
                        curprevimg.containingFolder = curFolder
                        curprevimg.lastModified = prevModified
                        curprevimg.imageSource = prevFile
                        break;
                    }

                }

            }

            // next image not yet setup
            if(foundNext == -1 && !nextFileIsVideo) {

                for(var l = 0; l < image_top.howManyLoaders; ++l) {

                    var curnextimg = repeaterimage.itemAt(l)

                    // l not the current main image and not the next image
                    if(curnextimg.imageSource !== PQCFileFolderModel.currentFile && foundPrev != l) {
                        curnextimg.containingFolder = curFolder
                        curnextimg.lastModified = nextModified
                        curnextimg.imageSource = nextFile
                        break;
                    }

                }

            }


        }
    }

    Connections {

        target: PQCScriptsShortcuts

        function onSendShortcutShowNextImage() {
            image_top.showNext()
        }

        function onSendShortcutShowPrevImage() {
            image_top.showPrev()
        }

        function onSendShortcutShowNextArcDocImage() {
            image_top.showNextArchiveDocument()
        }

        function onSendShortcutShowPrevArcDocImage() {
            image_top.showPreviousArchiveDocument()
        }

        function onSendShortcutShowFirstImage() {
            image_top.showFirst()
        }

        function onSendShortcutShowLastImage() {
            image_top.showLast()
        }

        function onSendShortcutShowRandomImage() {
            image_top.showRandom()
        }

        function onSendShortcutShowFile(path : string) {
            PQCFileFolderModel.fileInFolderMainView = path
        }

    }

    Connections {

        target: PQCFileFolderModel

        function onCurrentIndexChanged() {

            if(PQCConstants.ignoreFileFolderChangesTemporary || PQCConstants.faceTaggingMode) {
                console.debug("Ignoring new currentIndex:", PQCFileFolderModel.currentIndex)
                return
            }

            PQCConstants.currentStarRating = -1

            const loaderCount = image_top.howManyLoaders

            if(PQCFileFolderModel.countMainView === 0) {
                for(var i = 0; i < loaderCount; ++i) {
                    var curimg = repeaterimage.itemAt(i)
                    if(curimg.item !== null)
                        curimg.item.hideImage()
                }
                return
            }

            timer_loadbg.stop()

            var showItem = -1

            var newFile = PQCFileFolderModel.entriesMainView[PQCFileFolderModel.currentIndex]
            var newFolder = PQCScriptsFilesPaths.getDir(newFile)
            var newModified = PQCScriptsFilesPaths.getFileModified(newFile).toLocaleString()

            // if the current image is already loaded we only need to show it
            for(var h = 0; h < loaderCount; ++h) {

                var img = repeaterimage.itemAt(h)

                if(img.imageSource === newFile && img.containingFolder === newFolder && img.lastModified === newModified) {
                    showItem = h
                    break;
                }

            }

            // these need to be loaded
            var cur_showing = PQCFileFolderModel.currentIndex

            image_top.bgFiles = []
            for(var b = 0; b < PQCSettings.imageviewPreloadInBackground; ++b) {

                var newp = (cur_showing-(b+1)+PQCFileFolderModel.countMainView)%PQCFileFolderModel.countMainView
                var newn = (cur_showing+(b+1))%PQCFileFolderModel.countMainView

                var newpF = PQCFileFolderModel.entriesMainView[newp]
                var newnF = PQCFileFolderModel.entriesMainView[newn]

                // we don't preload neighboring archives as this can be somewhat slow
                if(!PQCScriptsImages.isArchive(newpF))
                    image_top.bgFiles.push(newpF)
                if(!PQCScriptsImages.isArchive(newnF))
                    image_top.bgFiles.push(newnF)
            }

            // image not already loaded
            if(showItem == -1) {

                for(var j = 0; j < loaderCount; ++j) {

                    var spare = repeaterimage.itemAt(j)

                    // this is a spare item
                    if((image_top.bgFiles.indexOf(spare.imageSource) === -1 || spare.containingFolder !== newFolder || spare.lastModified !== newModified || spare.imageSource !== newFile) && (!spare.active || !spare.item.visible)) {
                        spare.containingFolder = newFolder
                        spare.lastModified = newModified
                        spare.imageSource = newFile
                        spare.thisIsStartupFile = false
                        showItem = j
                        break;
                    }

                }
            }

            // show item
            var newimg = repeaterimage.itemAt(showItem)
            newimg.item.showImage()
            newimg.thisIsStartupFile = false

        }

    }

    Connections {

        target: PQCNotify

        function onImageShowNext() {
            image_top.showNext()
        }

        function onImageShowPrev() {
            image_top.showPrev()
        }

        function onImageSetRandomAnimation(val) {
            image_top.randomAnimation = val
        }

    }

    function newMainImageReady(curIndex : int) {

        // hide images that should not be visible
        for(var i = 0; i < howManyLoaders; ++i) {
            var curimg = repeaterimage.itemAt(i)
            if(curIndex !== i && repeaterimage.allactive[i]) {
                curimg.item.hideImage()
                curimg.thisIsStartupFile = false
            }
        }

        // check the star rating of the current image
        checkStarRating.restart()

        // start the timer to load images in background
        bgOffset = 0
        if(bgFiles.length > 0)
            timer_loadbg.restart()

    }

    // Find star rating
    Timer {
        id: checkStarRating
        interval: 10
        onTriggered: {
            PQCConstants.currentStarRating = PQCScriptsImages.getStarRating(PQCFileFolderModel.currentFile)
        }
    }

    // some global handlers
    function showNext() {

        if(PQCFileFolderModel.countMainView !== 0) {
            if(PQCSettings.imageviewLoopThroughFolder && PQCFileFolderModel.currentIndex === PQCFileFolderModel.countMainView-1)
                PQCFileFolderModel.currentIndex = 0
            else
                PQCFileFolderModel.currentIndex = Math.min(PQCFileFolderModel.currentIndex+1, PQCFileFolderModel.countMainView-1)
        }

    }

    function showPrev() {

        if(PQCFileFolderModel.countMainView !== 0) {
            if(PQCSettings.imageviewLoopThroughFolder &&PQCFileFolderModel.currentIndex === 0)
                PQCFileFolderModel.currentIndex = PQCFileFolderModel.countMainView-1
            else
                PQCFileFolderModel.currentIndex = Math.max(PQCFileFolderModel.currentIndex-1, 0)
        }
    }

    function showFirst() {
        if(PQCFileFolderModel.countMainView !== 0)
            PQCFileFolderModel.currentIndex = 0
    }

    function showLast() {
        if(PQCFileFolderModel.countMainView !== 0)
            PQCFileFolderModel.currentIndex = PQCFileFolderModel.countMainView-1
    }

    function showRandom() {

        if(PQCFileFolderModel.countMainView === 0 || PQCFileFolderModel.countMainView === 1)
            return

        // special case: load other image
        if(PQCFileFolderModel.countMainView === 2) {
            PQCFileFolderModel.currentIndex = (PQCFileFolderModel.currentIndex+1)%2
            return
        }

        // find new image that's not the current one
        var ran = Math.floor(Math.random() * (PQCFileFolderModel.countMainView - 1))
        // this >= check effectively creates a "hole" at currentIndex
        if(ran >= PQCFileFolderModel.currentIndex)
            ran++
        PQCFileFolderModel.currentIndex = ran
    }

    function showNextArchiveDocument() {

        if(PQCFileFolderModel.isARC || PQCFileFolderModel.isPDF)
            PQCFileFolderModel.disableViewerMode(false)

        for(var i = PQCFileFolderModel.currentIndex+1; i < PQCFileFolderModel.countMainView; ++i) {
            const cur = PQCFileFolderModel.entriesMainView[i]
            if(PQCScriptsImages.isArchive(cur) || PQCScriptsImages.isPDFDocument(cur)) {
                PQCFileFolderModel.currentIndex = i
                return
            }
        }

        if(PQCSettings.imageviewLoopThroughFolder) {
            for(var j = 0; j < PQCFileFolderModel.currentIndex; ++j) {
                const cur = PQCFileFolderModel.entriesMainView[j]
                if(PQCScriptsImages.isArchive(cur) || PQCScriptsImages.isPDFDocument(cur)) {
                    PQCFileFolderModel.currentIndex = j
                    return
                }
            }
        }

    }

    function showPreviousArchiveDocument() {

        if(PQCFileFolderModel.isARC || PQCFileFolderModel.isPDF)
            PQCFileFolderModel.disableViewerMode(false)

        for(var i = PQCFileFolderModel.currentIndex-1; i >= 0; --i) {
            const cur = PQCFileFolderModel.entriesMainView[i]
            if(PQCScriptsImages.isArchive(cur) || PQCScriptsImages.isPDFDocument(cur)) {
                PQCFileFolderModel.currentIndex = i
                return
            }
        }

        if(PQCSettings.imageviewLoopThroughFolder) {
            for(var j = PQCFileFolderModel.countMainView-1; j > PQCFileFolderModel.currentIndex; --j) {
                const cur = PQCFileFolderModel.entriesMainView[j]
                if(PQCScriptsImages.isArchive(cur) || PQCScriptsImages.isPDFDocument(cur)) {
                    PQCFileFolderModel.currentIndex = j
                    return
                }
            }
        }

    }

}
