#/********************************************************************
# Cayenue/cayenue/panels/file/filepanel.py 
#
# Copyright (c) 2023  Stephen Rhodes
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
#*********************************************************************/

import os
import sys
from PyQt6.QtWidgets import QGridLayout, QWidget, QLabel, \
    QMessageBox, QMenu, QApplication
from PyQt6.QtGui import QAction, QFileSystemModel
from PyQt6.QtCore import Qt, QStandardPaths
from cayenue.components import Progress, InfoDialog
from loguru import logger
import avio
from cayenue.components.directoryselector import DirectorySelector
from cayenue.panels.file import FileControlPanel, FileSortProxy, TreeView


class FilePanel(QWidget):
    def __init__(self, mw):
        super().__init__()
        self.mw = mw
        self.alarmSoundVolume = 80
        self.expandedPaths = []
        self.loadedCount = 0
        self.restorationPath = None
        self.restorationHeader = None
        self.verticalScrollBarPosition = 0
        self.dlgInfo = InfoDialog(mw)

        tmp_dir = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.MoviesLocation)[0]
        if self.mw.parent_window:
            tmp_dir = self.mw.parent_window.settingsPanel.storage.dirArchive.text()
        self.dirArchive = DirectorySelector(mw, self.mw.settingsPanel.storage.archiveKey, "", tmp_dir)
        self.video_dir = self.dirArchive.text()
        self.dirArchive.signals.dirChanged.connect(self.dirChanged)

        self.model = QFileSystemModel()
        self.tree = TreeView(mw)

        self.proxy = FileSortProxy()
        self.proxy.setSourceModel(self.model)
        self.proxy.setDynamicSortFilter(True)

        self.tree.setModel(self.proxy)
        self.model.fileRenamed.connect(self.onFileRenamed)
        self.model.directoryLoaded.connect(self.loaded)

        self.tree.doubleClicked.connect(self.treeDoubleClicked)
        self.restoreHeader()
        self.tree.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
        self.tree.customContextMenuRequested.connect(self.showContextMenu)

        self.progress = Progress(mw)
        self.control = FileControlPanel(mw, self)

        lytMain = QGridLayout(self)
        lytMain.addWidget(self.dirArchive,  0, 0, 1, 1)
        lytMain.addWidget(self.tree,       1, 0, 1, 1)
        lytMain.addWidget(self.progress,   2, 0, 1, 1)
        lytMain.addWidget(QLabel(),        3, 0, 1, 1)
        lytMain.addWidget(self.control,    4, 0, 1, 1)
        lytMain.setRowStretch(1, 10)

        self.dirChanged(self.dirArchive.text())

        self.menu = QMenu("Context Menu", self)
        self.remove = QAction("Delete", self)
        self.rename = QAction("Rename", self)
        self.info = QAction("Info", self)
        self.play = QAction("Play", self)
        self.stop = QAction("Stop", self)
        self.remove.triggered.connect(self.onMenuRemove)
        self.rename.triggered.connect(self.onMenuRename)
        self.info.triggered.connect(self.onMenuInfo)
        self.play.triggered.connect(self.onMenuPlay)
        self.stop.triggered.connect(self.onMenuStop)
        self.menu.addAction(self.remove)
        self.menu.addAction(self.rename)
        self.menu.addAction(self.info)
        self.menu.addAction(self.play)
        self.menu.addAction(self.stop)

    def loaded(self, path):
        self.loadedCount += 1
        #self.model.sort(0)
        self.tree.sortByColumn(1, Qt.SortOrder.AscendingOrder)
        for i in range(self.model.rowCount(self.model.index(path))):
            idx = self.model.index(i, 0, self.model.index(path))
            if idx.isValid():
                if self.model.filePath(idx) in self.expandedPaths:
                    self.tree.setExpanded(idx, True)

        if len(self.expandedPaths):
            if self.loadedCount == len(self.expandedPaths) + 1:
                if self.verticalScrollBarPosition:
                    QApplication.processEvents()
                    self.tree.verticalScrollBar().setValue(self.verticalScrollBarPosition)
                self.expandedPaths.clear()
                self.restoreSelectedPath()
        else:
            self.restoreSelectedPath()

    def restoreSelectedPath(self):
        if self.restorationPath:
            model_idx = self.model.index(self.restorationPath)
            if model_idx.isValid():
                proxy_idx = self.proxy.mapFromSource(model_idx)
                self.tree.setCurrentIndex(proxy_idx)
            self.restorationPath = None

    def refresh(self):
        try:
            self.loadedCount = 0
            self.expandedPaths = []
            proxy_idx = self.tree.currentIndex()
            if proxy_idx.isValid():
                model_idx = self.proxy.mapToSource(proxy_idx)
                self.restorationPath = self.model.filePath(model_idx)
            path = self.dirArchive.txtDirectory.text()
            self.model.sort(0)
            for i in range(self.model.rowCount(self.model.index(path))):
                model_idx = self.model.index(i, 0, self.model.index(path))
                if model_idx.isValid():
                    proxy_idx = self.proxy.mapFromSource(model_idx)
                    if self.tree.isExpanded(proxy_idx):
                        self.expandedPaths.append(self.model.filePath(model_idx))
            self.verticalScrollBarPosition = self.tree.verticalScrollBar().value()
            self.restorationHeader = self.tree.header().saveState()
            self.proxy.setSourceModel(None)
            self.model = QFileSystemModel()
            self.model.setRootPath(path)
            self.model.fileRenamed.connect(self.onFileRenamed)
            self.model.directoryLoaded.connect(self.loaded)
            self.proxy.setSourceModel(self.model)
            self.tree.setModel(self.proxy)
            self.tree.setRootIndex(self.proxy.mapFromSource(self.model.index(path)))
            self.restoreSelectedPath()
            if self.restorationHeader:
                self.tree.header().restoreState(self.restorationHeader)
                key = f'File/Header'
                self.mw.settings.setValue(key, self.tree.header().saveState())

        except Exception as ex:
            logger.error(f"File Panel refresh exception: {ex}")

    def dirChanged(self, path):
        if len(path) > 0:
            self.video_dir = path
            self.model.setRootPath(path)
            self.tree.setRootIndex(self.proxy.mapFromSource(self.model.index(self.video_dir)))

    def treeDoubleClicked(self, proxy_index):
        if proxy_index.isValid():
            model_index = self.proxy.mapToSource(proxy_index)
            fileInfo = self.model.fileInfo(model_index)
            if fileInfo.isDir():
                self.tree.setExpanded(proxy_index, self.tree.isExpanded(proxy_index))
            else:
                self.mw.closeAnyPlayingFiles()
                if uri := self.getCurrentFileURI():
                    self.mw.playMedia(uri)
                    self.mw.glWidget.focused_uri = uri

    def onMediaStarted(self, duration):
        if self.mw.tab.currentIndex() == 1:
            self.tree.setFocus()
        self.control.setBtnPlay()
        self.control.setBtnMute()
        self.control.setSldVolume()

    def onMediaStopped(self, uri):
        self.control.setBtnPlay()
        self.progress.duration = 0
        self.progress.updateProgress(0.0)

        # this is needed to prevent overwriting another file duration
        another = None
        self.mw.pm.lock()
        for player in self.mw.pm.players.values():
            if not player.isCameraStream():
                another = player
                break
        self.mw.pm.unlock()
        if not another:
            self.progress.lblDuration.setText("0:00")

    def onMediaProgress(self, pct, uri):
        if player := self.mw.pm.getPlayer(uri):
            player.file_progress = pct
            self.progress.updateDuration(player.duration())

        #if pct >= 0.0 and pct <= 1.0 and uri == self.mw.glWidget.focused_uri:
        if pct >= 0.0 and pct <= 1.0:
            self.progress.updateProgress(pct)

    def showContextMenu(self, pos):
        player = self.mw.pm.getPlayer(self.getCurrentFileURI())
        self.remove.setDisabled(bool(player))
        self.rename.setDisabled(bool(player))
        proxy_index = self.tree.indexAt(pos)
        if proxy_index.isValid():
            model_index = self.proxy.mapToSource(proxy_index)
            fileInfo = self.model.fileInfo(model_index)
            if fileInfo.isFile():
                self.menu.exec(self.mapToGlobal(pos))

    def onMenuRemove(self):
        proxy_index = self.tree.currentIndex()
        if proxy_index.isValid():
            model_index = self.proxy.mapToSource(proxy_index)
            if self.mw.pm.getPlayer(self.model.filePath(model_index)):
                QMessageBox.warning(self, "Warning",
                                        "Camera is currently playing. Please stop before deleting.",
                                        QMessageBox.StandardButton.Ok)
                return
            
            ret = QMessageBox.warning(self, "Cayenue",
                                        "You are about to ** PERMANENTLY ** delete this file.\n"
                                        "Are you sure you want to continue?",
                                        QMessageBox.StandardButton.Ok | QMessageBox.StandardButton.Cancel)

            if ret == QMessageBox.StandardButton.Ok:
                try:
                    proxy_idxAbove = self.tree.indexAbove(proxy_index)
                    proxy_idxBelow = self.tree.indexBelow(proxy_index)

                    self.model.remove(model_index)
                    
                    resolved = False
                    if proxy_idxAbove.isValid():
                        model_idxAbove = self.proxy.mapToSource(proxy_idxAbove)
                        if os.path.isfile(self.model.filePath(model_idxAbove)):
                            self.tree.setCurrentIndex(proxy_idxAbove)
                            resolved = True
                    if not resolved:
                        if proxy_idxBelow.isValid():
                            model_idxBelow = self.proxy.mapToSource(proxy_idxBelow)
                            if os.path.isfile(self.model.filePath(model_idxBelow)):
                                self.tree.setCurrentIndex(proxy_idxBelow)
                
                except Exception as e:
                    logger.error(f'File delete error: {e}')

    def onMenuRename(self):
        player = self.mw.pm.getPlayer(self.mw.filePanel.getCurrentFileURI())
        if player:
            self.mw.onError("Please stop the file playing in order to rename")
            return
        proxy_index = self.tree.currentIndex()
        if proxy_index.isValid():
            model_index = self.proxy.mapToSource(proxy_index)
            self.model.setReadOnly(False)
            self.tree.edit(proxy_index)

    def onFileRenamed(self, path, oldName, newName):
        self.model.setReadOnly(True)

    def onMenuInfo(self):
        if not self.isVisible():
            return
        
        strInfo = ""
        try:
            proxy_index = self.tree.currentIndex()
            if (proxy_index.isValid()):
                model_index = self.proxy.mapToSource(proxy_index)
                info = self.model.fileInfo(model_index)
                strInfo += "Filename: " + info.fileName()
                strInfo += "\nCreated: " + info.birthTime().toString()
                strInfo += "\nModified: " + info.lastModified().toString()

                reader = avio.Reader(info.absoluteFilePath())
                duration = reader.duration()
                time_in_seconds = int(duration / 1000)
                hours = int(time_in_seconds / 3600)
                minutes = int((time_in_seconds - (hours * 3600)) / 60)
                seconds = int((time_in_seconds - (hours * 3600) - (minutes * 60)))
                strInfo += "\nDuration: " + str(minutes) + ":" + "{:02d}".format(seconds)
                #title = reader.metadata("title")
                #if len(title):
                #    strInfo += "\nTitle: " + reader.metadata("title")

                if (reader.has_video()):
                    strInfo += "\n\nVideo Stream:"
                    strInfo += "\n    Resolution:  " + str(reader.width()) + " x " + str(reader.height())
                    strInfo += "\n    Frame Rate:  " + f'{reader.frame_rate().num / reader.frame_rate().den:.2f}'
                    strInfo += "  (" + str(reader.frame_rate().num) + " / " + str(reader.frame_rate().den) +")"
                    strInfo += "\n    Time Base:  " + str(reader.video_time_base().num) + " / " + str(reader.video_time_base().den)
                    strInfo += "\n    Video Codec:  " + reader.str_video_codec()
                    strInfo += "\n    Pixel Format:  " + reader.str_pix_fmt()
                    strInfo += "\n    Bitrate:  " + f'{reader.video_bit_rate():,}'
                
                if (reader.has_audio()):
                    strInfo += "\n\nAudio Stream:"
                    strInfo += "\n    Channel Layout:  " + reader.str_channel_layout()
                    strInfo += "\n    Audio Codec:  " + reader.str_audio_codec()
                    strInfo += "\n    Sample Rate:  " + str(reader.sample_rate())
                    strInfo += "\n    Sample Size:  " + str(reader.frame_size())
                    strInfo += "\n    Time Base:  " + str(reader.audio_time_base().num) + " / " + str(reader.audio_time_base().den)
                    strInfo += "\n    Sample Format:  " + reader.str_sample_format()
                    strInfo += "\n    Bitrate:  " + f'{reader.audio_bit_rate():,}'
                
            else:
                strInfo = "Invalid Index"
        except Exception as ex:
            strInfo = f'Unable to read video file info: {ex}'

        #msgBox = QMessageBox(self)
        #msgBox.setWindowTitle("File Info")
        #msgBox.setText(strInfo)
        #msgBox.exec()
        self.dlgInfo.lblMessage.setText(strInfo)
        self.dlgInfo.exec()

    def onMenuPlay(self):
        self.mw.filePanel.control.btnPlayClicked()

    def onMenuStop(self):
        self.mw.filePanel.control.btnStopClicked()

    def fastForward(self):
        try:
            pct = self.progress.sldProgress.value() / 1000
            if duration := self.progress.duration:
                interval = 10000 / duration
                tgt = pct + interval
                if tgt < 1.0:
                    if player := self.getCurrentlyPlayingFile():
                        player.seek(tgt)
        except Exception as ex:
            logger.error(f"FilePanel fastForward exception: {ex}")

    def rewind(self):
        try:
            pct = self.progress.sldProgress.value() / 1000
            if duration := self.progress.duration:
                interval = 10000 / duration
                tgt = max(pct - interval, 0.0)
                if player := self.getCurrentlyPlayingFile():
                    player.seek(tgt)
        except Exception as ex:
            logger.error(f"FilePanel rewind exception: {ex}")

    def getCurrentFileURI(self):
        result = None
        proxy_index = self.tree.currentIndex()
        if proxy_index.isValid():
            model_index = self.proxy.mapToSource(proxy_index)
            info = self.model.fileInfo(model_index)
            if info.isFile():
                result = info.absoluteFilePath()
        return result
    
    def getCurrentlyPlayingFile(self):
        result = None
        self.mw.pm.lock()
        for player in self.mw.pm.players.values():
            if not player.isCameraStream():
                result = player
        self.mw.pm.unlock()
        return result
            
    def setCurrentFile(self, uri):
        model_index = self.model.index(uri)
        proxy_index = self.proxy.mapFromSource(model_index)
        self.tree.setCurrentIndex(proxy_index)
        self.control.setBtnPlay()
        self.control.setBtnMute()
        self.control.setSldVolume()
        if player := self.mw.pm.getPlayer(uri):
            self.onMediaProgress(player.file_progress, uri)

    #def showEvent(self, event):
    #    self.restoreHeader()

    def headerChanged(self, a, b, c):
        key = f'File/Header'
        self.mw.settings.setValue(key, self.tree.header().saveState())

    def restoreHeader(self):
        key = f'File/Header'
        data = self.mw.settings.value(key)
        if data:
            self.tree.header().restoreState(data)
        self.tree.update()
        self.tree.header().sectionResized.connect(self.headerChanged)
        self.tree.header().sectionMoved.connect(self.headerChanged)

    def getDirectory(self):
        try:
            if sys.platform == "win32":
                path = os.environ["HOMEPATH"]
            else:
                path = os.environ["HOME"]
            key = f'File/Directory'
            dirs = QStandardPaths.standardLocations(QStandardPaths.StandardLocation.MoviesLocation)
            path = self.mw.settings.value(key, dirs[0])
            os.makedirs(path, exist_ok=True)
        except Exception as ex:
            logger.error(f'Unable to find Videos directory: {ex}')
        return path

    def setDirectory(self, path):
        key = f'File/Directory'
        self.mw.settings.setValue(key, path)

    def getMute(self):
        key = f'File/Mute'
        return bool(int(self.mw.settings.value(key, 0)))
    
    def setMute(self, state):
        key = f'File/Mute'
        self.mw.settings.setValue(key, int(state))

    def getVolume(self):
        key = f'File/Volume'
        return int(self.mw.settings.value(key, 80))
    
    def setVolume(self, volume):
        key = f'File/Volume'
        self.mw.settings.setValue(key, volume)

    def hup(self):
        proxy_index = self.tree.currentIndex()
        if not proxy_index.isValid():
            return

        model_index = self.proxy.mapToSource(proxy_index)
        info = self.model.fileInfo(model_index)
        if not info.isFile():
            return

        if not self.mw.client:
            logger.error("HUP failed, client not initialized")
            return

        self.mw.client.transmit(bytearray(f"HUP\n\n{info.dir().dirName()}\r\n", 'utf-8'))
