// SPDX-License-Identifier: GPL-3.0-or-later
/*
 * Copyright (C) 2020, 2022 - 2024  Andrej Trnkóci
 *
 * This program 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; version 3.
 *
 * eartrainer 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/>.
 */

#include <QGuiApplication>
#include <QString>
#include "notePlayer.h"

NotePlayer::NotePlayer()
{
    for (int x = 0; x < NOTES_COUNT; x++)
    {
        noteSounds[x].setSource(QUrl("qrc:/" + QString::number(x + 1) + ".wav"));
        noteSounds[x].setLoopCount(1);
        noteSounds[x].setCategory(QString::fromStdString("alert"));
        // Following line of code most likely are not necessary. MoveToThread function tries to make sure 
        // that QSoundEffect instances are in the same thread as NotePlayer. Now that noteSounds is a member of
        // NotePlayer class, the QSoundEffects should probably be on the same thread by default.
        noteSounds[x].moveToThread(QGuiApplication::instance()->thread());
        // Specifying the connection as Qt::DirectConnection would cause the code to fail if noteSounds were on other thread
        // which is probably better than random crashes that were occuring when noteSounds were in a global variable independent
        // from the NotePlayer class.
        connect(&(noteSounds[x]), SIGNAL(playingChanged()), this, SLOT(soundPlayingChanged()), Qt::DirectConnection);
    }
    noteSounds[0].setVolume(1.0f);
}

void NotePlayer::playNote(int noteID)
{
    if (noteID >= 0 && noteID < NOTES_COUNT)
    {
        noteSounds[noteID].play();
    }
}

void NotePlayer::playInterval(int firstNoteID, int intervalIndex, IntervalTypes intervalType)
{
    if (intervalType == IntervalTypes::Ascending)
    {
        playIntervalAscending(firstNoteID, intervalIndex);
    }
    else if (intervalType == IntervalTypes::Descending)
    {
        playIntervalDescending(firstNoteID, intervalIndex);
    }
    else if (intervalType == IntervalTypes::Harmonic)
    {
        playIntervalHarmonic(firstNoteID, intervalIndex);
    }

}

void NotePlayer::playIntervalAscending(int firstNoteID, int intervalIndex)
{
    int higherNoteId = firstNoteID + intervalIndex;
    notesToPlay[0] = firstNoteID;
    notesToPlay[1] = higherNoteId;
    nextNoteIndex = 0;
    numberOfNotesToPlay = 2;
    playNextNoteInSequence();
}

void NotePlayer::playIntervalDescending(int firstNoteID, int intervalIndex)
{
    int higherNoteId = firstNoteID + intervalIndex;
    notesToPlay[0] = higherNoteId;
    notesToPlay[1] = firstNoteID;
    nextNoteIndex = 0;
    numberOfNotesToPlay = 2;
    playNextNoteInSequence();
}

void NotePlayer::playIntervalHarmonic(int firstNoteID, int intervalIndex)
{
    nextNoteIndex = firstNoteID + intervalIndex;
    playNote(firstNoteID);
    playNote(nextNoteIndex);
}

void NotePlayer::playChord(int firstNoteID, ChordQualities chordQuality, ChordPlayStyle chordPlayStyle)
{
    if (chordPlayStyle == ChordPlayStyle::Harmonic)
    {
        playChordHarmonic(firstNoteID, chordQuality);
    }
    else if (chordPlayStyle == ChordPlayStyle::AscendingArpeggio)
    {
        playChordAscendingArpeggio(firstNoteID, chordQuality);
    }
    else if (chordPlayStyle == ChordPlayStyle::DescendingArpeggio)
    {
        playChordDescendingArpeggio(firstNoteID, chordQuality);
    }
}

void NotePlayer::playChordHarmonic(int firstNoteID, ChordQualities chordQuality)
{
    for (int x = 0; x < 3; x++)
    {
        playNote(getNthNoteOfChord(firstNoteID, chordQuality, x));
    }
}

void NotePlayer::playChordAscendingArpeggio(int firstNoteID, ChordQualities chordQuality)
{
    for (int x = 0; x < 3; x++)
    {
        notesToPlay[x] = getNthNoteOfChord(firstNoteID, chordQuality, x);
    }
    nextNoteIndex = 0;
    numberOfNotesToPlay = 3;
    playNextNoteInSequence();
}

void NotePlayer::playChordDescendingArpeggio(int firstNoteID, ChordQualities chordQuality)
{
    for (int x = 0; x < 3; x++)
    {
        notesToPlay[x] = getNthNoteOfChord(firstNoteID, chordQuality, 2 - x);
    }
    nextNoteIndex = 0;
    numberOfNotesToPlay = 3;
    playNextNoteInSequence();
}

int NotePlayer::getNthNoteOfChord(int firstNoteID, ChordQualities chordQuality, int n)
{
    if (n > 2)
    {
        return 0;
    }

    if (chordQuality == ChordQualities::Major)
    {
        return firstNoteID + major_chord_structure[n];
    }
    else if (chordQuality == ChordQualities::Minor)
    {
        return firstNoteID + minor_chord_structure[n];
    }
    else if (chordQuality == ChordQualities::Diminished)
    {
        return firstNoteID + diminished_chord_structure[n];
    }
    else if (chordQuality == ChordQualities::Augmented)
    {
        return firstNoteID + augmented_chord_structure[n];
    }
    else
    {
    	return 0;
    }
}

void NotePlayer::nextNote()
{
	playNote(nextNoteIndex);
}

void NotePlayer::soundPlayingChanged()
{
    QSoundEffect *senderSE = qobject_cast<QSoundEffect*> (sender());
    if (!senderSE->isPlaying())
    {
        playNextNoteInSequence();
    }
}

void NotePlayer::playNextNoteInSequence()
{
    if (nextNoteIndex < 4 && nextNoteIndex < numberOfNotesToPlay)
    {
        playNote(notesToPlay[nextNoteIndex]);
        nextNoteIndex++;
    }
}

int NotePlayer::getNotesCount()
{
	return NOTES_COUNT;
}
