#! /usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Copyright 2021, Nils Hilbricht, Germany ( https://www.hilbricht.net )

This file is part of the Laborejo Software Suite ( https://www.laborejo.org ),

Laborejo2 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 <http://www.gnu.org/licenses/>.
"""

import logging; logger = logging.getLogger(__name__); logger.info("import")

from PyQt5 import QtCore, QtGui, QtWidgets, QtWidgets
from .constantsAndConfigs import constantsAndConfigs
import engine.api as api

pen = QtGui.QPen()
pen.setCapStyle(QtCore.Qt.RoundCap)
pen.setJoinStyle(QtCore.Qt.RoundJoin)
pen.setWidth(2)
#pen.setColor(QtGui.QColor("red"))

class PitchCursor(QtWidgets.QGraphicsRectItem):
    def __init__(self):
        """Does not need the actual dotOnLine.
        this is done by the parent cursor.
        This is just a fancy rect."""
        #super().__init__(constantsAndConfigs.magicPixel, 2, constantsAndConfigs.magicPixel*3, constantsAndConfigs.stafflineGap/3)
        super().__init__(0, 1, constantsAndConfigs.magicPixel*3, 5)
        self.setBrush(QtGui.QColor("cyan"))
        self.setPen(pen)
        self.setEnabled(False)

class PositionCursor(QtWidgets.QGraphicsRectItem):
    def __init__(self):
        """Does not need the actual position.
        this is done by the parent cursor
        This is just a fancy rect."""
        super().__init__(-1.5*constantsAndConfigs.magicPixel, -3.5*constantsAndConfigs.stafflineGap, constantsAndConfigs.magicPixel*1.5, 7*constantsAndConfigs.stafflineGap)
        self.setBrush(QtGui.QColor("cyan"))
        self.setPen(pen)
        self.setScale(0.8)
        self.setEnabled(False)


class Cursor(QtWidgets.QGraphicsItemGroup):
    """A cursor that shows the vertical
    as well as the horizontal position
    """
    def __init__(self):
        super().__init__()
        self.cursorExportObject = None #the last cached cursor object aka the current cursor position
        self.pitch = PitchCursor()
        self.addToGroup(self.pitch)
        self.position = PositionCursor()
        self.addToGroup(self.position)
        api.callbacks.setCursor.append(self.setCursor)
        self.setEnabled(False)

    def clearItemHighlight(self):
        """Gets called before a track changes. Most of the time when a new item is inserted/deleted.
        This means the gui track will be recreated and a current highlight on an item might get
        deleted while still on the item. This results in a qt crash
        """
        Cursor.hightlightEffect = QtWidgets.QGraphicsColorizeEffect() #default strength of the effect is 1.0
        Cursor.hightlightEffect.setColor(QtGui.QColor("cyan"))
        Cursor.hightlightEffect.setStrength(0.7) #opacity of the effect

        ##Cursor.stafflineEffect = QtWidgets.QGraphicsColorizeEffect() #default strength of the effect is 1.0
        ##Cursor.stafflineEffect.setColor(QtGui.QColor("cyan"))
        ##Cursor.stafflineEffect.setStrength(1) #opacity of the effect

        #Cursor.stafflineEffect = QtWidgets.QGraphicsDropShadowEffect()
        #Cursor.stafflineEffect.setColor(QtGui.QColor("black"))
        #Cursor.stafflineEffect.setOffset(0,0)
        #Cursor.stafflineEffect.setBlurRadius(5)

    def setCursor(self, cursorExportObject):
        self.cursorExportObject = cursorExportObject
        self.scene().parentView.setFocus()

        self.pitch.setY(constantsAndConfigs.stafflineGap * cursorExportObject["dotOnLine"] / 2  - 3)   #the same as a notehead

        x = cursorExportObject["tickindex"] / constantsAndConfigs.ticksToPixelRatio
        #Finally shift the Position to the correct track. Trackindex from 0, naturally.
        #self.setPos(x, constantsAndConfigs.timeLineOffsetNoteEditor + cursorExportObject["trackIndex"] * constantsAndConfigs.trackHeight)
        currentGuiTrack = self.scene().tracks[cursorExportObject["trackId"]]
        self.setPos(x, currentGuiTrack.y() )

        try:
            guiItemAtCursor = currentGuiTrack.itemById(cursorExportObject["itemId"])
            if not guiItemAtCursor.staticItem["completeDuration"] > 0:
                #guiItemAtCursor has no duration. is type guiItemAtCursor.staticItem["type"]
                Cursor.hightlightEffect.setEnabled(True)
                guiItemAtCursor.setGraphicsEffect(Cursor.hightlightEffect)
            else:
                Cursor.hightlightEffect.setEnabled(False)

        except StopIteration:
            Cursor.hightlightEffect.setEnabled(False) #happens only on startup or appending (which is the same)

        #Highlight the current staffline
        if currentGuiTrack.staticExportItem["double"] and cursorExportObject["dotOnLine"] in (8, 10, 12, 14, 16):
            lineNumber = int(cursorExportObject["dotOnLine"] / 2) + 1 #gui-stafflines are counted from 0 to n, top to bottom (listindex) while backend stafflines begin on -4
            #currentGuiTrack.staffLines[lineNumber].setGraphicsEffect(Cursor.stafflineEffect)
            #Cursor.stafflineEffect.setEnabled(True)
        elif cursorExportObject["dotOnLine"] in (-4, -2, 0, 2, 4):
            lineNumber = int(cursorExportObject["dotOnLine"] / 2) + 2 #gui-stafflines are counted from 0 to n, top to bottom (listindex) while backend stafflines begin on -4
            #currentGuiTrack.staffLines[lineNumber].setGraphicsEffect(Cursor.stafflineEffect)
            #Cursor.stafflineEffect.setEnabled(True)
        #else:
        #    Cursor.stafflineEffect.setEnabled(False)

class Playhead(QtWidgets.QGraphicsLineItem):
    def __init__(self, parentScoreScene):
        super().__init__(0, 0,  0, 0)        # (x1, y1, x2, y2)
        self.parentScoreScene = parentScoreScene
        p = QtGui.QPen()
        p.setColor(QtGui.QColor("red"))
        p.setCosmetic(True)
        p.setWidth(3)
        self.setPen(p)
        #self.setAcceptHoverEvents(True)
        api.callbacks.setPlaybackTicks.append(self.setCursorPosition)
        api.callbacks.tracksChanged.append(self.setLineToWindowHeigth) #for new tracks
        api.callbacks.updateTempoTrack.append(self.setLineToWindowHeigth)
        self.setFlags(QtWidgets.QGraphicsItem.ItemIsMovable)
        self.setCursor(QtCore.Qt.SizeHorCursor)
        self.setAcceptedMouseButtons(QtCore.Qt.LeftButton)
        self.setZValue(90) #This is relative to the parent, which is the scene.
        #self.parentScoreScene.parentView.verticalScrollBar().valueChanged.connect(self.setLineToWindowHeigth)
        #self.hide()
        #self.maxHeight = QtWidgets.QDesktopWidget().geometry().height() #we really hope the screen resolution does not change during the session.

    def setCursorPosition(self, tickindex:int, playbackStatus:bool):
        """Set the playhead to the right position, but keep the viewport stable.
        Shift the entire "page" if the cursor becomes invisible because its steps outside the viewport"""
        self.setLineToWindowHeigth()
        x = tickindex / constantsAndConfigs.ticksToPixelRatio
        self.setX(x)
        if constantsAndConfigs.followPlayhead and playbackStatus:
            scenePos = self.parentScoreScene.parentView.mapFromScene(self.pos())
            cursorViewPosX = scenePos.x() #the cursor position in View coordinates
            width = self.parentScoreScene.parentView.geometry().width()
            if cursorViewPosX <= 0 or cursorViewPosX >= width: #"pageflip"
                self.parentScoreScene.parentView.horizontalScrollBar().setValue(x * constantsAndConfigs.zoomFactor)


    def _Deprecated__ScrollingVariant___setCursorPosition(self, tickindex):
        """the tickindex to pixel index on the x axis is a fixed 1:n
        relation. What you see is where you are. No jumps, the playhead
        stays on course."""
        self.setLineToWindowHeigth()
        x = tickindex / constantsAndConfigs.ticksToPixelRatio
        self.setX(x)
        if constantsAndConfigs.followPlayhead and api.playbackStatus():
            #self.parentScoreScene.parentView.centerOn(self) Do not use center on. It centers for Y as well which creates a recursion and the score gets taller and taller.
            #self.scene().parentView.horizontalScrollBar().setValue(x - 150)
            #xV = self.parentScoreScene.parentView.mapFromScene(x,0).x()
            self.parentScoreScene.parentView.horizontalScrollBar().setValue(x - 150) #x does not with zoom levels. the bar drifts away.

    def setLineToWindowHeigth(self, *args):
        h = self.parentScoreScene.cachedSceneHeight
        self.setLine(0, 0, 0, h) #(x1, y1, x2, y2)

    def mouseMoveEvent(self, event):
        """Only allow movement in Y direction.
        Only triggered when dragging."""
        #super().mouseMoveEvent(event) allows free movement through Qt. Don't call that.
        p = event.scenePos().x()
        if p < 0:
            p = 0
        #self.setPos(p, self.scene().parentView.mapToScene(0, 0).y())
        self.setX(p)
        api.seek(p * constantsAndConfigs.ticksToPixelRatio)
        event.accept()

    def mouseReleaseEvent(self, event):
        if constantsAndConfigs.snapToGrid:
            x = event.scenePos().x() * constantsAndConfigs.ticksToPixelRatio
            p = round(x / constantsAndConfigs.gridRhythm) * constantsAndConfigs.gridRhythm
            if p < 0:
                p = 0
            api.seek(p)

    #def hoverEnterEvent(self, event):
    #    self.setCursor(QtCore.Qt.SizeHorCursor)
    #    self.update() #the default implementation calls this. event.accept/ignore has no effect.

    #def hoverLeaveEvent(self, event):
    #    self.unsetCursor()
    #    self.update() #the default implementation calls this. event.accept/ignore has no effect.

class Selection(QtWidgets.QGraphicsRectItem):
    """A semi-transparent rectangle that shows the current selection"""
    def __init__(self):
        super().__init__(0, 0, 0, 0)

        self.validBrush = QtGui.QBrush(QtGui.QColor("grey"))
        self.invalidBrush = QtGui.QBrush(QtGui.QColor("grey"))
        self.invalidBrush.setStyle(QtCore.Qt.DiagCrossPattern)

        self._tupleOfCursorExportObjects = None #cache for stretchX

        self.setPen(pen)
        self.setOpacity(0.2)
        self.setZValue(95) #Below playback cursor, but pretty high. This is relative to the parent, which is the scene.
        self.setEnabled(False)
        api.callbacks.setSelection.append(self.setSelection)


    def stretchXCoordinates(self, factor):
        """Reposition the items on the X axis.
        Call goes through all parents/children, starting from ScoreView._stretchXCoordinates.
        Docstring there."""
        self.setSelection(self._tupleOfCursorExportObjects)

    def setSelection(self, tupleOfCursorExportObjects):
        self._tupleOfCursorExportObjects = tupleOfCursorExportObjects
        if tupleOfCursorExportObjects:
            validSelection, topleftCursorObject, bottomRightCursorObject = tupleOfCursorExportObjects
            if validSelection:
                self.setBrush(self.validBrush)
            else:
                self.setBrush(self.invalidBrush)

            topGuiTrack = self.scene().tracks[topleftCursorObject["trackId"]]
            bottomGuiTrack = self.scene().tracks[bottomRightCursorObject["trackId"]]

            #y = constantsAndConfigs.timeLineOffsetNoteEditor + topleftCursorObject["trackIndex"] * constantsAndConfigs.trackHeight - constantsAndConfigs.trackHeight/2
            #h = (bottomRightCursorObject["trackIndex"] - topleftCursorObject["trackIndex"]) * constantsAndConfigs.trackHeight + constantsAndConfigs.trackHeight

            x = topleftCursorObject["tickindex"] / constantsAndConfigs.ticksToPixelRatio
            w = (bottomRightCursorObject["tickindex"] - topleftCursorObject["tickindex"]) / constantsAndConfigs.ticksToPixelRatio
            y = topGuiTrack.y() - constantsAndConfigs.trackHeight/2

            if bottomGuiTrack.staticExportItem["double"]:
                h = bottomGuiTrack.y() - y + constantsAndConfigs.trackHeight
            else:
                h = bottomGuiTrack.y() - y + constantsAndConfigs.trackHeight/2


            self.setRect(0, 0, w-3, h) #substract a few pixels to make it look less ambigious if the last item on the right edge is included or not (it is not)
            self.setPos(x,y)
            self.setVisible(True)
        else: #no selection
            self.setVisible(False)
