// SPDX-License-Identifier: Unlicense

#ifndef SONG_H
#define SONG_H

#include "type.h"

#include <map>
#include <string>
#include <unordered_map>

namespace Psarc { struct Info; }
namespace Shred { struct Info; }

struct Tuning
{
  i8 string[8]{ -128, -128, -128, -128, -128, -128, -128, -128 };
};

namespace Arrangement
{
  struct Info
  {
    std::u8string persistentId;
    std::u8string arrangementName;

    bool isBass{};
    bool bassPick{};
    i8 capoFret{};
    f32 centOffset{};
    Tuning tuning{};
  };
}

namespace Song
{
  enum struct LoadState
  {
    none,
    cache,
    complete
  };

  struct Info
  {
    LoadState loadState = LoadState::none;
    std::vector<Arrangement::Info> arrangementInfos;

    std::u8string albumName;
    std::u8string artistName;
    std::u8string songName;
    i32 songYear{};
    i32 track{};
    TimeNS songLength{};
    TimeNS shredDelayBegin{};
    TimeNS shredDelayEnd{};
    char spotifyTrackId[23]{};
    i32 psarcAlbumCover256_tocIndex = -1;
  };

  struct Tone
  {
    TimeNS timeNS;
    i32 id;
    std::u8string name;
  };

  struct LeveledSection
  {
    TimeNS startTimeNS;
    TimeNS endTimeNS;
    i32 maxLevel;
    //std::u8string name;
  };

  struct ChordTemplate_deprecated
  {
    std::u8string name;
    //std::string displayName;
    i8 finger[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
    i8 fret[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
  };

  struct Beat
  {
    TimeNS timeNS;
    bool isPrimary;
  };

  struct BendValue
  {
    TimeNS timeNS;
    f32 step;
  };

  struct Note
  {
    TimeNS timeNS;
    i8 string;
    i8 fret;
    bool linkNext_ : 1;
    bool isLinked : 1;
    bool accent : 1;
    f32 maxBend;
    bool hammerOn : 1;
    bool harmonic : 1;
    bool hopo : 1;
    bool ignore : 1;
    i8 finger = -1;
    bool frethandMute : 1;
    bool palmMute : 1;
    bool pluck : 1;
    bool pullOff : 1;
    bool slap : 1;
    i8 slideTo = -1;
    TimeNS sustainNS;
    bool tremolo : 1;
    bool pinchHarmonic : 1;
    bool pickDirection = true;
    bool rightHand : 1;
    i8 slideUnpitchTo = -1;
    bool tap : 1;
    i8 vibrato;
    bool rotation = false; // FIXME: does not work reliable. It is not always false for notes in a chord
    std::vector<BendValue> bendValues;
  };

  struct Chord
  {
    TimeNS timeNS;
    TimeNS sustainNS;
    i32 chordId_deprecated = -1;
    bool linkNext_ : 1;
    bool isLinked : 1;
    bool accent : 1;
    bool fretHandMute : 1;
    bool highDensity : 1;
    bool ignore : 1;
    bool palmMute : 1;
    bool strumUp : 1 = false;

    std::u8string name;

    std::vector<Note> chordNotes;
  };

  struct Anchor
  {
    TimeNS timeNS; // startTimeNS
    //TimeNS endTimeNS;
    i8 fret;
    i8 width;
  };

  struct Sustain
  {
    TimeNS startTimeNS;
    TimeNS endTimeNS;
    i32 chordId_deprecated;
    std::u8string name;
    i8 finger[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
  };

  struct Arpeggio
  {
    TimeNS startTimeNS;
    TimeNS endTimeNS;
    //i32 chordId_deprectated;
    std::u8string name;
    i8 finger[8] = { -1, -1, -1, -1, -1, -1, -1, -1 };
  };

  struct Level
  {
    std::vector<Note> notes;
    std::vector<Chord> chords;
    std::vector<Anchor> anchors;
    std::vector<Sustain> sustains;
    std::vector<Arpeggio> arpeggios;
  };

  struct Track
  {
    std::vector<Tone> tones;
    std::vector<LeveledSection> leveledSections;
    std::vector<ChordTemplate_deprecated> chordTemplates_deprecated;
    std::vector<Beat> beats;
    std::vector<Level> levels;
  };

  struct Vocal
  {
    TimeNS timeNS;
    TimeNS lengthNS;
    std::u8string lyric;
  };

  struct TrackLevelAdjusted
  {
    std::vector<Note> notes;
    std::vector<Chord> chords;
    std::vector<Anchor> anchors;
    std::vector<Sustain> sustains;
    std::vector<Arpeggio> arpeggios;
  };

#ifdef SHR3D_SHRED
  namespace ShredParser
  {
    Info loadSongInfo(const char8_t* shredFilepath, const Shred::Info& shredInfo);
    std::unordered_map<ArrangementIndex, Song::Track> loadTracks(const Shred::Info& shredInfo);
    std::vector<Song::Vocal> loadVocals(const Shred::Info& shredInfo);
    void loadSongTones(const SongIndex songIndex, const Song::Info& songInfo, const Shred::Info& shredInfo);
  }
#endif // SHR3D_SHRED
#ifdef SHR3D_PSARC
  namespace PsarcParser
  {
    Info loadSongInfo(const Psarc::Info& psarcInfo, const std::u8string& psarcFilename);
    std::unordered_map<ArrangementIndex, Song::Track> loadTracks(const Psarc::Info& psarcInfo, const std::vector<Arrangement::Info>& arrangementInfos);
    std::vector<Song::Vocal> loadVocals(const Psarc::Info& psarcInfo);

    // expoesed for shredripper
    std::vector<Arrangement::Info> readPsarcHsan(const std::vector<u8>& hsanData, std::u8string& artistName, std::u8string& songName, std::u8string& albumName, i32& songYear, TimeNS& songLength);
    void patchPsarcArrangementInfo(std::vector<Arrangement::Info>& arrangementInfos, const std::u8string& psarcFilename);
  }
#endif // SHR3D_PSARC

  Song::Info loadSongIni(const std::map<std::u8string, std::map<std::u8string, std::u8string>>& iniContent, const char8_t* filename);
  TrackLevelAdjusted loadTrackLevelAdjusted(const Track& track, const std::vector<i32>& phraseIterationDifficulty);

  const char8_t* tuningName(const Tuning& tuning);

  i32 getCurrentAnchorIndex(ArrangementIndex selectedArrangementIndex);
}

#endif // SONG_H
