import sys

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from MainWindow import Ui_MainWindow

from solo_tool import SoloTool
from solo_tool.midi_controller_launchpad_mini import MidiController

POSITION_FACTOR = 100000
RATE_FACTOR = 10
UI_REFRESH_PERIOD_MS = 500

CHANGE_GUI = 0
CHANGE_INTERNAL = 1

class PlaylistModel(QAbstractListModel):
    def __init__(self, soloTool, *args, **kwargs):
        super(PlaylistModel, self).__init__(*args, **kwargs)
        self.soloTool = soloTool

    def data(self, index, role):
        if role == Qt.DisplayRole:
            from pathlib import Path
            path = Path(self.soloTool.getSongs()[index.row()])
            return path.name

    def rowCount(self, index):
        return len(self.soloTool.getSongs())

class ABListModel(QAbstractListModel):
    def __init__(self, soloTool, *args, **kwargs):
        super(ABListModel, self).__init__(*args, **kwargs)
        self.soloTool = soloTool

    def data(self, index, role):
        if role == Qt.DisplayRole:
            ab = self.soloTool.getStoredAbLimits()[index.row()]
            return f"{ab[0]} - {ab[1]}"

    def rowCount(self, index):
        return len(self.soloTool.getStoredAbLimits())

class MainWindow(QMainWindow, Ui_MainWindow):
    songChangeSignal = pyqtSignal(int)
    abLimitsChangeSignal = pyqtSignal(int)
    
    def __init__(self, *args, **kwargs):
        super(MainWindow, self).__init__(*args, **kwargs)

        self.setupUi(self)
        
        self.timer = QTimer(self)
        self.timer.setInterval(UI_REFRESH_PERIOD_MS)
        self.timer.timeout.connect(self.timerCallback)

        self.soloTool = SoloTool()
        self.midiController = MidiController(self.soloTool)

        self.playlistModel = PlaylistModel(self.soloTool)
        self.songListView.setModel(self.playlistModel)
        self.songListView.selectionModel().selectionChanged.connect(self.playlistSelectionChanged)
        self.songChangePending = None
        self.songChangeSignal.connect(self.currentSongChanged)
        self.soloTool.registerCurrentSongCallback(self.songChangeSignal.emit)

        self.abListModel = ABListModel(self.soloTool)
        self.abListView.setModel(self.abListModel)
        self.abListView.selectionModel().selectionChanged.connect(self.abListSelectionChanged)
        self.abLimitsChangePending = None
        self.abLimitsChangeSignal.connect(self.currentAbLimitsChanged)
        self.soloTool.registerCurrentAbLimitsCallback(self.abLimitsChangeSignal.emit)

        self.songSlider.setMaximum(POSITION_FACTOR)
        self.songSlider.sliderPressed.connect(self.songSliderPressed)
        self.songSlider.sliderReleased.connect(self.songSliderReleased)

        self.aSlider.setMaximum(POSITION_FACTOR)
        self.aSlider.sliderReleased.connect(self.abSliderReleased)
        self.bSlider.setMaximum(POSITION_FACTOR)
        self.bSlider.sliderReleased.connect(self.abSliderReleased)

        self.rateSlider.setRange(int(0.5 * RATE_FACTOR), int(1.2 * RATE_FACTOR))
        self.rateSlider.setSingleStep(int(0.1 * RATE_FACTOR))
        self.rateSlider.setValue(int(1.0 * RATE_FACTOR))
        self.rateSlider.sliderReleased.connect(self.rateSliderReleased)

        self.playButton.pressed.connect(self.soloTool.play)
        self.pauseButton.pressed.connect(self.soloTool.pause)
        self.storeAbButton.pressed.connect(self.storeAbLimits)
        self.setAButton.pressed.connect(self.setA)
        self.setBButton.pressed.connect(self.setB)
        self.saveSessionButton.pressed.connect(self.saveSession)
        self.loadSessionButton.pressed.connect(self.loadSession)
        self.addSongButton.pressed.connect(self.addSong)
        self.abRepeatCheckBox.clicked.connect(self.toggleAbRepeat)
        self.initMidiButton.pressed.connect(self.initMidi)

        self.timer.start()

        if len(sys.argv) > 1:
            self.loadSession(sys.argv[1])

        self.show()

    def timerCallback(self):
        position = self.soloTool.getPlaybackPosition() * POSITION_FACTOR
        self.songSlider.setValue(int(position))
        self.soloTool.tick()

    def addSong(self):
        path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "mp3 Audio (*.mp3);FLAC audio (*.flac);All files (*.*)")
        if path:
            self.soloTool.addSong(path)
            self.playlistModel.layoutChanged.emit()

    def storeAbLimits(self):
        a = self.aSlider.value() / float(POSITION_FACTOR)
        b = self.bSlider.value() / float(POSITION_FACTOR)
        self.soloTool.storeAbLimits(a, b)
        self.abListModel.layoutChanged.emit()

    def setA(self):
        position = self.songSlider.value()
        self.aSlider.setValue(position)
        self.abSliderReleased()

    def setB(self):
        position = self.songSlider.value()
        self.bSlider.setValue(position)
        self.abSliderReleased()

    def toggleAbRepeat(self):
        enable = self.abRepeatCheckBox.isChecked()
        self.soloTool.setAbLimitEnable(enable)
        
    def saveSession(self):
        path, _ = QFileDialog.getSaveFileName(self, "Open file", "", "session file (*.json)")
        if path:
            self.soloTool.saveSession(path)

    def loadSession(self, path=None):
        if path is None:
            path, _ = QFileDialog.getOpenFileName(self, "Open file", "", "session file (*.json)")
        if path is not None:
            self.soloTool.loadSession(path)
            self.playlistModel.layoutChanged.emit()
            self.abListModel.layoutChanged.emit()

    def songSliderPressed(self):
        self.timer.stop()

    def songSliderReleased(self):
        position = self.songSlider.value() / float(POSITION_FACTOR)
        self.soloTool.setPlaybackPosition(position)
        self.timer.start()

    def clearListViewSelection(self, listView):
        i = listView.selectionModel().currentIndex()
        listView.selectionModel().select(i, QItemSelectionModel.Deselect)

    def abSliderReleased(self):
        a = self.aSlider.value() / float(POSITION_FACTOR)
        b = self.bSlider.value() / float(POSITION_FACTOR)
        self.soloTool.setAbLimits(a, b)
        self.clearListViewSelection(self.abListView)

    def rateSliderReleased(self):
        rate = self.rateSlider.value() / float(RATE_FACTOR)
        self.soloTool.setPlaybackRate(rate)

    def playlistSelectionChanged(self, i):
        if self.songChangePending == CHANGE_INTERNAL:
            self.songChangePending = None
        else:
            assert self.songChangePending is None
            self.songChangePending = CHANGE_GUI
            index = i.indexes()[0].row()
            self.soloTool.setSong(index)
        
        self.clearListViewSelection(self.abListView)
        self.abListModel.layoutChanged.emit()

    def currentSongChanged(self, songIndex):
        if self.songChangePending == CHANGE_GUI:
            self.songChangePending = None
        else:
            assert self.songChangePending is None
            self.songChangePending = CHANGE_INTERNAL
            i = self.playlistModel.createIndex(songIndex, 0)
            self.songListView.selectionModel().select(i, QItemSelectionModel.ClearAndSelect)

    def abListSelectionChanged(self, i):
        if self.abLimitsChangePending == CHANGE_INTERNAL:
            print("Ack internal change")
            self.abLimitsChangePending = None
        else:
            assert self.abLimitsChangePending is None
            if i is not None and not i.isEmpty():
                print("Processing GUI change")
                self.abLimitsChangePending = CHANGE_GUI
                index = i.indexes()[0].row()
                ab = self.soloTool.getStoredAbLimits()[index]
                self.soloTool.loadAbLimits(index)
                self.aSlider.setValue(int(ab[0] * POSITION_FACTOR))
                self.bSlider.setValue(int(ab[1] * POSITION_FACTOR))

    def currentAbLimitsChanged(self, abIndex):
        if self.abLimitsChangePending == CHANGE_GUI:
            print("Ack GUI change")
            self.abLimitsChangePending = None
        else:
            assert self.abLimitsChangePending is None
            print("Processing internal change")
            self.abLimitsChangePending = CHANGE_INTERNAL
            i = self.abListModel.createIndex(abIndex, 0)
            self.abListView.selectionModel().select(i, QItemSelectionModel.ClearAndSelect)
            ab = self.soloTool.getStoredAbLimits()[abIndex]
            self.aSlider.setValue(int(ab[0] * POSITION_FACTOR))
            self.bSlider.setValue(int(ab[1] * POSITION_FACTOR))

    def initMidi(self):
        try:
            self.midiController.connect()
        except Exception as e:
            print("Error: could not connect to MIDI controller")
            print(e)

    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Super_L:
            self.soloTool.jumpToA()

    def closeEvent(self, event):
        self.midiController.disconnect()
        event.accept()

def main():
    app = QApplication([])
    app.setApplicationName("Solo Tool")
    app.setStyle("Fusion")

    # Fusion dark palette from https://gist.github.com/QuantumCD/6245215.
    palette = QPalette()
    palette.setColor(QPalette.Window, QColor(53, 53, 53))
    palette.setColor(QPalette.WindowText, Qt.white)
    palette.setColor(QPalette.Base, QColor(25, 25, 25))
    palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
    palette.setColor(QPalette.ToolTipBase, Qt.white)
    palette.setColor(QPalette.ToolTipText, Qt.white)
    palette.setColor(QPalette.Text, Qt.white)
    palette.setColor(QPalette.Button, QColor(53, 53, 53))
    palette.setColor(QPalette.ButtonText, Qt.white)
    palette.setColor(QPalette.BrightText, Qt.red)
    palette.setColor(QPalette.Link, QColor(42, 130, 218))
    palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
    palette.setColor(QPalette.HighlightedText, Qt.black)
    app.setPalette(palette)
    app.setStyleSheet("QToolTip { color: #ffffff; background-color: #2a82da; border: 1px solid white; }")

    window = MainWindow()
    app.exec_()

if __name__ == '__main__':
    main()