/* id3v1.pk - ID3v1 implementation for GNU poke */

/* Copyright (C) 2019, 2020, 2021, 2022, 2023, 2024 Jose E. Marchesi.  */

/* 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, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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/>.
 */

/* Implemented as specified in http://id3.org/id3v1.html  */

/* Table containing the names of the different supported genres.  Note
   that there are 255 possible genres, because the code 255 denotes
   no-genre according to the spec.  */

var id3v1_genres =
  ["blues", "classic rock", "country", "dance", "disco", "funk",
   "grunge", "hip-hop", "jazz", "metal", "new age", "oldies",
   "other", "pop", "rhythm and blues", "rap", "reggae", "rock",
   "techno", "industrial", "alternative", "ska", "death metal",
   "pranks", "soundtrack", "euro-techno", "ambient", "trip-hop",
   "vocal", "jazz and funk", "fusion",  "trance", "classical",
   "instrumental", "acid", "house", "game", "sound clip", "gospel",
   "noise", "alternative rock", "bass", "soul", "punk", "space",
   "meditative", "instrumental pop", "instrumental rock", "ethnic",
   "gothic", "darkwave", "techno-industrial", "electronic",
   "pop-folk", "eurodance", "dream", "southern rock", "comedy",
   "cult", "gangsta", "top 40", "christian rap", "pop/funk",
   "jungle music", "native US", "cabaret", "new wave",
   "psychedelic", "rave",   "showtunes", "trailer", "lo-fi",
   "tribal", "acid punk", "acid jazz", "polka", "retro",
   "musical", "rock n roll", "hard rock",
   .[254] = ""];

/* Given a genre name, return its code.  Raise E_inval if the genre is
   not found.  */

fun id3v1_search_genre = (string name) uint<8>:
{
  for (var i = 0; i < id3v1_genres'length; i++)
      if (id3v1_genres[i] == name)
        return i;

  raise Exception { code = EC_inval,
                    name = "invalid ID3V1 genre" };
}

/* The ID3V1_Tag type denotes an ID3 tag.  */

type ID3V1_Tag =
  struct
  {
    uint<8>[3] id == ['T', 'A', 'G'];

    uint<8>[30] title;
    uint<8>[30] artist;
    uint<8>[30] album;
    uint<8>[4] year;

    union
    {
      /* ID3v1.1  */
      struct
      {
        uint<8>[28] comment;
        uint<8> zero = 0;
        uint<8> track : track != 0;
      } extended;
      /* ID3v1  */
      uint<8>[30] comment;
    } data;

    uint<8> genre;

    /* Setters and getters.  */

    computed string title_str;
    computed string artist_str;
    computed string album_str;
    computed int<32> year_int;
    computed string comment_str;
    computed string genre_str;

    method get_title_str = string: { return rtrim (catos (title)); }
    method set_title_str = (string val) void: { stoca (val, title, ' '); }
    method get_artist_str = string: { return rtrim (catos (artist)); }
    method set_artist_str = (string val) void: { stoca (val, artist, ' '); }
    method get_album_str = string: { return rtrim (catos (album)); }
    method set_album_str = (string val) void: { stoca (val, album, ' '); }
    method get_year_int = int<32>: { return atoi (catos (year)); }
    method set_year_int = (int<32> val) void: { stoca (ltos (val), year, ' '); }

    method get_comment_str = string:
      {
        try return rtrim (catos (data.comment));
        catch if E_elem { return rtrim (catos (data.extended.comment)); };
      }
    method set_comment_str = (string val) void:
      {
        try stoca (val, data.comment, ' ');
        catch if E_elem { stoca (val, data.extended.comment, ' '); }
      }

    method get_genre_str = string:
      {
        if (genre == 255)
          raise E_generic;
        return id3v1_genres[genre];
      }

    method set_genre_str = (string val) void:
      {
        genre = id3v1_search_genre (val);
      }

    /* Pretty printer.  */
    method _print = void:
      {
        print "#<\n";
        if (genre == 255)
          printf "  genre: %<integer:%u8d%>\n", genre;
        else
          printf "  genre: %s\n", id3v1_genres[genre];
        printf "  title: %<string:%s%>\n", get_title_str;
        printf "  artist: %<string:%s%>\n", get_artist_str;
        printf "  album: %<string:%s%>\n", get_album_str;
        printf "  year: %<integer:%i32d%>\n", get_year_int;
        try printf "  comment:%<string:%s%>\n", catos (data.comment);
        catch if E_elem
        {
          printf "  comment: %<string:%s%>\n", catos (data.extended.comment);
          printf "  track: %<integer:%u8d%>\n", data.extended.track;
        }
        print ">";
      }
  };
