package mg.dot.feufaro.midi

// Représente le fichier complet
class MidiSequence(val resolution: Int = 60) {
    // Une liste de pistes, où chaque piste est une liste d'octets (ByteArray)
    val tracks = mutableListOf<MutableList<Byte>>()
    val out: MutableList<Byte> = mutableListOf<Byte>()
    private var lastTick = 0L

    fun createTrack(): MutableList<Byte> {
        val newTrack = mutableListOf<Byte>()
        tracks.add(newTrack)
        return newTrack
    }

    fun MutableList<Byte>.write16Bit(value: Int) {
        this.add((value shr 8 and 0xFF).toByte())
        this.add((value and 0xFF).toByte())
    }

    fun MutableList<Byte>.write32Bit(value: Int) {
        this.add((value shr 24 and 0xFF).toByte())
        this.add((value shr 16 and 0xFF).toByte())
        this.add((value shr 8 and 0xFF).toByte())
        this.add((value and 0xFF).toByte())
    }

    fun MutableList<Byte>.addMidiEvent(deltaTicks: Long, status: Int, data1: Int, data2: Int? = null) {
        this.addAll(deltaTicks.toVLQList())
        this.add(status.toByte())
        this.add(data1.toByte())
        if (data2 != null) {
            this.add(data2.toByte())
        }
    }

    fun addSequence(channel: Int, pitch: Int, currentTick: Long, type: Int = 80, finalVelocity: Int = 100) {
        val myTrack = tracks[0]
        myTrack.addNote(channel, pitch, currentTick, type, finalVelocity)
    }
    fun MutableList<Byte>.addNote(channel: Int, pitch: Int, currentTick: Long, type: Int = 80, finalVelocity: Int = 100) {
        // Calcul du Delta-Time
        val delta = currentTick - lastTick
        lastTick = currentTick

        val status = if (type == 80) (0x80 or channel) else (0x90 or channel)

        // Ajout de l'événement (Velocity à 0 pour OFF, 100 pour ON par défaut)
        val velocity = if (type == 80) 0 else finalVelocity

        this.addMidiEvent(delta, status, pitch, velocity)
    }
    fun Long.toVLQList(): List<Byte> {
        var value = this
        val buffer = mutableListOf<Byte>()

        // On extrait les groupes de 7 bits en commençant par la fin
        // Le premier octet ajouté (le moins significatif) n'a pas le bit de poids fort à 1
        buffer.add((value and 0x7F).toByte())

        while (value > 0x7F) {
            value = value shr 7
            // Pour les octets suivants, on force le bit 7 à 1 (| 0x80)
            buffer.add(0, ((value and 0x7F) or 0x80).toByte())
        }

        return buffer
    }
    private fun writeFileHeader(): MutableList<Byte> {
        val trackCount: Int = tracks.size
        // 1. Signature "MThd" (4 octets)
        out.addAll("MThd".map { it.code.toByte() })

        // 2. Taille du header (toujours 6 octets pour le format standard)
        out.write32Bit(6)

        // 3. Format (16 bits)
        // 0 = piste unique, 1 = pistes multiples synchronisées
        out.write16Bit(1)

        // 4. Nombre de pistes (16 bits)
        out.write16Bit(trackCount)

        // 5. Division / Résolution (16 bits)
        // Ticks par noire (votre variable 'resolution')
        out.write16Bit(resolution)
        return out
    }
    private fun writeTrackChunk(trackData: MutableList<Byte>) {
        // 1. Signature "MTrk" (4 octets)
        out.addAll("MTrk".map { it.code.toByte() })

        // 2. Marqueur de fin de piste obligatoire (si pas déjà présent)
        // Delta 0 (00), Meta (FF), Type Fin (2F), Longueur 0 (00)
        val endMarker = listOf<Byte>(0x00, 0xFF.toByte(), 0x2F, 0x00)

        // 3. Calcul de la taille totale du bloc de données
        val totalSize = trackData.size + endMarker.size

        // 4. Écriture de la taille (32 bits)
        out.write32Bit(totalSize)

        // 5. Copie des données de la piste
        out.addAll(trackData)

        // 6. Ajout du marqueur de fin
        out.addAll(endMarker)
    }
    fun write() {
        writeFileHeader()
        for (track in tracks) {
            writeTrackChunk( track)
        }
    }
    fun addMeta(type: Int, tick: Int, nbData: Int, metaByteString: String) {
        val data = metaByteString.encodeToByteArray()
        if (tracks.isEmpty()) {
            tracks.add(mutableListOf<Byte>(0))
        }
        val outTrack = tracks[0]
        // Un Meta Message commence toujours par FF, puis le type, puis la longueur
        outTrack.add(0x00.toByte())        // Status byte
        outTrack.add(0xFF.toByte())        // Status byte
        outTrack.add(type.toByte())        // Meta-event type
        outTrack.addAll(nbData.toLong().toVLQList())
        // Ajout des données
        outTrack.addAll(data.toList().take(nbData))

    }
}
