import sys
import os
import numpy as np
import librosa
import librosa.display
from pydub import AudioSegment
import hashlib

from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QFileDialog, QPushButton, QLabel,
    QVBoxLayout, QWidget, QHBoxLayout, QMessageBox, QSlider
)
from PyQt5.QtCore import Qt
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtGui import QPixmap, QIcon

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

class WaveformCanvas(FigureCanvas):
    def __init__(self, parent=None):
        fig = Figure(figsize=(8, 4))
        self.axes = fig.add_subplot(111)
        super(WaveformCanvas, self).__init__(fig)
        self.setParent(parent)

class BMSKeysoundTool(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('BMS Keysound Tool')
        self.setGeometry(100, 100, 1000, 700)
        self.audio_data = None
        self.sample_rate = None
        self.current_file = None
        self.bpm = None
        self.beats = None
        self.keysound_markers = []
        self.keysound_files = []
        self.output_dir = "keysounds"
        os.makedirs(self.output_dir, exist_ok=True)

        self.init_ui()

    def init_ui(self):
        # Central widget
        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        # Layouts
        main_layout = QVBoxLayout()
        button_layout = QHBoxLayout()

        # Buttons
        load_button = QPushButton('Load Stem')
        load_button.clicked.connect(self.load_stem)

        add_marker_button = QPushButton('Add Marker')
        add_marker_button.clicked.connect(self.add_marker)
        add_marker_button.setEnabled(False)

        export_button = QPushButton('Export Keysounds and BMS')
        export_button.clicked.connect(self.export)
        export_button.setEnabled(False)

        # Slider for navigation
        self.slider = QSlider(Qt.Horizontal)
        self.slider.setEnabled(False)
        self.slider.valueChanged.connect(self.slider_moved)

        # Labels
        self.status_label = QLabel('Load an audio stem to begin.')

        # Waveform canvas
        self.canvas = WaveformCanvas(self)

        # Add widgets to layouts
        button_layout.addWidget(load_button)
        button_layout.addWidget(add_marker_button)
        button_layout.addWidget(export_button)

        main_layout.addLayout(button_layout)
        main_layout.addWidget(self.canvas)
        main_layout.addWidget(self.slider)
        main_layout.addWidget(self.status_label)

        central_widget.setLayout(main_layout)

        # Assign buttons to instance variables for enabling/disabling
        self.load_button = load_button
        self.add_marker_button = add_marker_button
        self.export_button = export_button

    def load_stem(self):
        options = QFileDialog.Options()
        file_name, _ = QFileDialog.getOpenFileName(
            self,
            "Open Audio File",
            "",
            "Audio Files (.wav .mp3 .ogg);;All Files ()",
            options=options
        )
        if file_name:
            self.current_file = file_name
            self.audio_data, self.sample_rate = librosa.load(file_name, sr=None)
            self.plot_waveform()
            self.detect_bpm()
            self.add_marker_button.setEnabled(True)
            self.slider.setEnabled(True)
            self.slider.setMaximum(len(self.audio_data))
            self.status_label.setText('Audio loaded. BPM detected: {:.2f}'.format(self.bpm))
            # Reset markers and keysounds
            self.keysound_markers = []
            self.keysound_files = []
            self.export_button.setEnabled(False)

    def plot_waveform(self):
        self.canvas.axes.clear()
        self.canvas.axes.plot(self.audio_data, color='blue')
        self.canvas.axes.set_title('Waveform')
        self.canvas.draw()

    def detect_bpm(self):
        tempo, beat_frames = librosa.beat.beat_track(y=self.audio_data, sr=self.sample_rate)
        self.bpm = tempo
        self.beats = librosa.frames_to_samples(beat_frames)
        # Draw beat grid
        for beat in self.beats:
            self.canvas.axes.axvline(x=beat, color='red', linestyle='--', alpha=0.5)
        self.canvas.draw()

    def add_marker(self):
        # Get current position from slider
        position = self.slider.value()
        self.keysound_markers.append(position)
        # Mark on waveform
        self.canvas.axes.axvline(x=position, color='green', linestyle='-', alpha=0.7)
        self.canvas.draw()
        self.status_label.setText('Marker added at sample: {}'.format(position))
        self.export_button.setEnabled(True)

    def export(self):
        # Sort markers
        self.keysound_markers.sort()
        # Cut keysounds
        for idx, start_sample in enumerate(self.keysound_markers):
            if idx < len(self.keysound_markers) - 1:
                end_sample = self.keysound_markers[idx + 1]
            else:
                end_sample = len(self.audio_data)
            start_time = start_sample / self.sample_rate
            end_time = end_sample / self.sample_rate
            output_path = os.path.join(self.output_dir, 'keysound_{:03d}.wav'.format(idx))
            self.cut_keysound(start_time, end_time, output_path)
            self.keysound_files.append(output_path)
        # Optimize keysounds
        self.optimize_keysounds()
        # Export BMS file
        bms_output = 'output.bms'
        self.export_to_bms(bms_output)
        QMessageBox.information(self, 'Export Complete', 'Keysounds and BMS file exported.')
        self.status_label.setText('Export complete.')

    def cut_keysound(self, start_time, end_time, output_path):
        audio_seg = AudioSegment.from_file(self.current_file)
        keysound = audio_seg[start_time*1000:end_time*1000]  # pydub works in milliseconds
        keysound.export(output_path, format="wav")

    def optimize_keysounds(self):
        hash_dict = {}
        optimized_files = []
        for path in self.keysound_files:
            with open(path, 'rb') as f:
                file_hash = hashlib.sha256(f.read()).hexdigest()
                if file_hash in hash_dict:
                    # Duplicate found, use existing file
                    print(f"Duplicate keysound detected: {path}")
                    optimized_files.append(hash_dict[file_hash])
                    os.remove(path)  # Remove duplicate file
                else:
                    hash_dict[file_hash] = path
                    optimized_files.append(path)
        self.keysound_files = optimized_files

    def export_to_bms(self, output_path):
        note_data = {}
        measure = 1
        channel = 11  # Key assignment
        # Simple note data generation
        for idx, ks in enumerate(self.keysound_files):
            if measure not in note_data:
                note_data[measure] = {}
            # Placeholder: assign notes evenly across measures
            value = "{:02X}".format(idx)
            note_data[measure][channel] = value
            measure += 1

        with open(output_path, 'w') as bms_file:
            bms_file.write("---------------------- HEADER FIELD\n")
            bms_file.write("#PLAYER 1\n#GENRE YourGenre\n#TITLE YourTitle\n#ARTIST YourArtist\n#BPM {:.2f}\n".format(self.bpm))
            # Write keysound definitions
            for idx, ks in enumerate(self.keysound_files):
                wav_key = "{:02X}".format(idx)
                ks_relative = os.path.relpath(ks, os.path.dirname(output_path))
                bms_file.write("#WAV{} {}\n".format(wav_key, ks_relative))
            bms_file.write("---------------------- MAIN DATA FIELD\n")
            # Write note data
            for measure, channels in note_data.items():
                for channel, value in channels.items():
                    bms_file.write("#{0:03d}{1:02d}:{2}\n".format(measure, channel, value))

    def slider_moved(self):
        position = self.slider.value()
        self.status_label.setText('Slider moved to sample: {}'.format(position))
        # Optionally, update any real-time visualizations or playback

if __name__ == '__main__':
    app = QApplication(sys.argv)
    tool = BMSKeysoundTool()
    tool.show()
    sys.exit(app.exec_())