#define __NOLIBBASE__

#include <proto/exec.h>
#include <proto/muimaster.h>
#include <proto/intuition.h>
#include <proto/utility.h>
#include <proto/dos.h>

#include "mp3class.h"

#define reg(a) __asm(#a)

#define GENRES  145     /* how many genres is defined */

struct Library *SysBase, *DOSBase, *MUIMasterBase, *UtilityBase;

static char *genres [GENRES + 1] = {"Blues", "Classic Rock", "Country", "Dance", "Disco", "Funk",
 "Grunge", "Hip-Hop", "Jazz", "Metal", "New Age", "Oldies", "Other", "Pop", "R&B", "Rap", "Reggae",
 "Rock", "Techno", "Industrial", "Alternative", "Ska", "Death Metal", "Pranks", "Soundtrack",
 "Euro-Techno", "Ambient", "Trip-Hop", "Vocal", "Jazz+Funk", "Fusion", "Trance", "Classical",
 "Instrumental", "Acid", "House", "Game", "Sound Clip", "Gospel", "Noise", "AlternRock", "Bass",
 "Soul", "Punk", "Space", "Meditative", "Instrumental Pop", "Instrumental Rock", "Ethnic", "Gothic",
 "Darkwave", "Techno-Industrial", "Electronic", "Pop-Folk", "Euro-Dance", "Dream", "Southern Rock",
 "Comedy", "Cult", "Gangsta", "Top 40", "Christian Rap", "Pop/Funk", "Jungle", "Native American",
 "Cabaret", "New Wave", "Psychadelic", "Rave", "Showtunes", "Trailer", "Lo-Fi", "Tribal", "Acid Punk",
 "Acid Jazz", "Polka", "Retro", "Musical", "Rock & Roll", "Hard Rock", "Folk", "Folk/Rock",
 "National Folk", "Swing", "Fast-Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass",
 "Avantgarde", "Gothic Rock", "Progressive Rock", "Psychedelic Rock", "Symphonic Rock", "Slow Rock",
 "Big Band", "Chorus", "Easy Listening", "Acoustic", "Humour", "Speech", "Chanson", "Opera",
 "Chamber Music", "Sonata", "Symphony", "Booty Bass", "Primus", "Porn Groove", "Satire", "Slow Jam",
 "Club", "Tango", "Samba", "Folklore", "Ballad", "Power Ballad", "Rythmic Soul", "Freestyle", "Duet",
 "Punk Rock", "Drum Solo", "A Capella", "Euro-House", "Dance Hall", "Goa", "Drum & Bass",
 "Club-House", "Hardcore", "Terror", "Indie", "BritPop", "Negerpunk", "Polsk", "PunkBeat",
 "Christian Gansta", "Heavy Metal", "Black Metal", "Crossover", "Contemporary Chris",
 "Christian Rock", "Merengue", "Salsa", "Trash Metal", NULL};

struct MP3Data
 {
  char tagblock[128];
  char title[31];
  char artist[31];
  char album[31];
  char year[5];
  char comment[31];
  unsigned char genre;
  Object *title_string;
  Object *artist_string;
  Object *album_string;
  Object *year_string;
  Object *comment_string;
  Object *genre_text;
  Object *genre_listview;
 };

struct opFile       /* MP3_ReadTags and MP3_WriteTags methods */
 {
  long MethodID;
  char *path;       /* path to MP3 file */
 };

/* Class creation and disposition */

static long MP3Dispatcher (Class *cl reg(a0), Object *obj reg(a2), Msg msg reg(a1));

long CreateMP3Class (void)
 {
  MP3Class = MUI_CreateCustomClass (NULL, MUIC_Group, NULL, sizeof (struct MP3Data), MP3Dispatcher);
  return (long)MP3Class;
 }

void DeleteMP3Class (void)
 {
  if (MP3Class) MUI_DeleteCustomClass (MP3Class);
 }

/* Genre textfield-to-list hook */

long GenreTextToList (Object *list reg(a2), Object *textfield reg(a1))
 {
  char *entry, *text;
  long i;

  text = (char*)xget (textfield, MUIA_Text_Contents);

  for (i = 0; ;i++)
   {
    DoMethod (list, MUIM_List_GetEntry, i, &entry);
    if (!entry)
     {
      xset (list, MUIA_List_Active, MUIV_List_Active_Off);
      break;
     }
    else if (!Stricmp (entry, text))
     {
      xset (list, MUIA_List_Active, i);
      break;
     }
   }

  return TRUE;
 }

struct Hook h_GenreTextToList = {NULL, NULL, (HOOKFUNC)GenreTextToList, NULL, NULL};

/* Genre list-to-textfield hook */

void GenreListToText (Object *list reg(a2), Object *textfield reg(a1))
 {
  char *text;

  DoMethod (list, MUIM_List_GetEntry, MUIV_List_GetEntry_Active, &text);
  xset (textfield, MUIA_Text_Contents, text);
  return;
 }

struct Hook h_GenreListToText = {NULL, NULL, (HOOKFUNC)GenreListToText, NULL, NULL};

/* Listview activate hook, run before every popup open */

void ListviewActivate (Object *listview reg(a2), Object *popwin reg(a1))
 {
  xset (popwin, MUIA_Window_DefaultObject, listview);
  return;
 }

struct Hook h_ListviewActivate = {NULL, NULL, (HOOKFUNC)ListviewActivate, NULL, NULL};

/* simple label */

static Object *make_label (char *text)
 {
  return (MUI_NewObject (MUIC_Text,
   MUIA_HorizWeight, 0,
   MUIA_Text_Contents, (long)text,
   MUIA_Frame, MUIV_Frame_String,
   MUIA_FramePhantomHoriz, TRUE,
  TAG_END));
 }

/* ==== METHODS =================================================================================== */

static long mNew (Class *cl, Object *obj, struct opSet *msg)
 {
  Object *title_string, *artist_string, *album_string, *year_string, *comment_string, *genre_text,
         *genre_listview, *genre_popup;
  struct MP3Data *data;

  if (obj = (Object*)DoSuperNew (cl, obj,
    MUIA_Frame, MUIV_Frame_Group,
    MUIA_FrameTitle, (long)"File tags",
    MUIA_Background, MUII_GroupBack,
    MUIA_Group_Columns, 2,
    MUIA_Group_Child, make_label ("Song name"),
    MUIA_Group_Child, title_string = MUI_NewObject (MUIC_String,
      MUIA_Frame, MUIV_Frame_String,
      MUIA_String_MaxLen, 31,
      MUIA_CycleChain, TRUE,
      MUIA_String_AdvanceOnCR, TRUE,
    TAG_END),
    MUIA_Group_Child, make_label ("Artist"),
    MUIA_Group_Child, artist_string = MUI_NewObject (MUIC_String,
      MUIA_Frame, MUIV_Frame_String,
      MUIA_String_MaxLen, 31,
      MUIA_CycleChain, TRUE,
      MUIA_String_AdvanceOnCR, TRUE,
    TAG_END),
    MUIA_Group_Child, make_label ("Album"),
    MUIA_Group_Child, album_string = MUI_NewObject (MUIC_String,
      MUIA_Frame, MUIV_Frame_String,
      MUIA_String_MaxLen, 31,
      MUIA_CycleChain, TRUE,
      MUIA_String_AdvanceOnCR, TRUE,
    TAG_END),
    MUIA_Group_Child, make_label ("Year"),
    MUIA_Group_Child, year_string = MUI_NewObject (MUIC_String,
      MUIA_Frame, MUIV_Frame_String,
      MUIA_String_MaxLen, 5,
      MUIA_String_Accept, "0123456789",
      MUIA_CycleChain, TRUE,
      MUIA_String_AdvanceOnCR, TRUE,
    TAG_END),
    MUIA_Group_Child, make_label ("Comment"),
    MUIA_Group_Child, comment_string = MUI_NewObject (MUIC_String,
      MUIA_Frame, MUIV_Frame_String,
      MUIA_String_MaxLen, 31,
      MUIA_CycleChain, TRUE,
      MUIA_String_AdvanceOnCR, TRUE,
    TAG_END),
    MUIA_Group_Child, MUI_NewObject (MUIC_Text,
      MUIA_HorizWeight, 0,
      MUIA_Text_Contents, (long)"Genre",
      MUIA_Frame, MUIV_Frame_String,
      MUIA_FramePhantomHoriz, TRUE,
    TAG_END),
    MUIA_Group_Child, genre_popup = MUI_NewObject (MUIC_Popobject,
      MUIA_Popstring_String, genre_text = MUI_NewObject (MUIC_Text,
        MUIA_Background, MUII_TextBack,
        MUIA_Frame, MUIV_Frame_Text,
        MUIA_Text_Contents, (long)"",
        MUIA_UserData, MP3_GENRE,
      TAG_END),
      MUIA_Popstring_Button, MUI_NewObject (MUIC_Image,
        MUIA_Image_Spec, MUII_PopUp,
        MUIA_Frame, MUIV_Frame_ImageButton,
        MUIA_Background, MUII_ButtonBack,
        MUIA_InputMode, MUIV_InputMode_RelVerify,
        MUIA_CycleChain, TRUE,
      TAG_END),
      MUIA_Popobject_Object, genre_listview = MUI_NewObject (MUIC_Listview,
        MUIA_Listview_List, MUI_NewObject (MUIC_List,
          MUIA_Frame, MUIV_Frame_InputList,
          MUIA_List_AutoVisible, TRUE,
          MUIA_List_SourceArray, (long)genres,
          MUIA_List_ConstructHook, MUIV_List_ConstructHook_String,
          MUIA_List_DestructHook, MUIV_List_DestructHook_String,
        TAG_END),
      TAG_END),
      MUIA_Popobject_StrObjHook, (long)&h_GenreTextToList,
      MUIA_Popobject_ObjStrHook, (long)&h_GenreListToText,
      MUIA_Popobject_WindowHook, (long)&h_ListviewActivate,
    TAG_END),
  TAG_MORE, msg->ops_AttrList))
   {
    data = INST_DATA (cl, obj);

    data->title_string = title_string;
    data->artist_string = artist_string;
    data->album_string = album_string;
    data->year_string = year_string;
    data->comment_string = comment_string;
    data->genre_text = genre_text;
    data->genre_listview = genre_listview;

    DoMethod (genre_listview, MUIM_Notify, MUIA_Listview_DoubleClick, TRUE, genre_popup, 2,
     MUIM_Popstring_Close, TRUE);

    return (long)obj;
   }
  CoerceMethod (cl, obj, OM_DISPOSE);
  return NULL;
 }

/* function strips ending spaces from string */

void strip_spaces (char* string)
 {
  short i = 0;

  while (string[i++]);

  for (i -= 2; i >= 0; i--)
   {
    if (string[i] != 0x20) break;
   }

  string[i + 1] = 0x00;
  return;
 }

/* the method reads tags from given MP3 file */

static long mReadTags (Class *cl, Object *obj, struct opFile *msg)
 {
  struct MP3Data *data = INST_DATA (cl, obj);
  BPTR mp3_file;

  if (mp3_file = Open (msg->path, MODE_OLDFILE))
   {
    if (Seek (mp3_file, -128, OFFSET_END) != -1)
     {
      if (Read (mp3_file, data->tagblock, 128) == 128)
       {
        if ((data->tagblock[0] == 'T') && (data->tagblock[1] == 'A') && (data->tagblock[2] == 'G'))
         {
          CopyMem (&data->tagblock[3], data->title, 30);
          data->title[30] = 0x00;
          CopyMem (&data->tagblock[33], data->artist, 30);
          data->artist[30] = 0x00;
          CopyMem (&data->tagblock[63], data->album, 30);
          data->album[30] = 0x00;
          CopyMem (&data->tagblock[93], data->year, 4);
          data->year[4] = 0x00;
          CopyMem (&data->tagblock[97], data->comment, 30);
          data->comment[30] = 0x00;
          data->genre = (unsigned char)data->tagblock[127];

          strip_spaces (data->title);
          strip_spaces (data->artist);
          strip_spaces (data->album);
          strip_spaces (data->year);
          strip_spaces (data->comment);
         }
        else
         {
          strcpy (data->title, "no MP3 tag found");
          data->artist[0] = 0x00;
          data->album[0] = 0x00;
          data->year[0] = 0x00;
          data->comment[0] = 0x00;
          data->genre = 0xFF;
         }
        xset (data->title_string, MUIA_String_Contents, data->title);
        xset (data->artist_string, MUIA_String_Contents, data->artist);
        xset (data->album_string, MUIA_String_Contents, data->album);
        xset (data->year_string, MUIA_String_Contents, data->year);
        xset (data->comment_string, MUIA_String_Contents, data->comment);
        if (data->genre >= GENRES) xset (data->genre_text, MUIA_Text_Contents, "unknown");
        else xset (data->genre_text, MUIA_Text_Contents, genres[data->genre]);
       }
     }
    Close (mp3_file);
   }
 }

// The method writes tags from given MP3 file. Tag block os overwritten if exists and appended if
// doesn't exist.

static long mWriteTags (Class *cl, Object *obj, struct opFile *msg)
 {
  struct MP3Data *data = INST_DATA (cl, obj);
  BPTR mp3_file;
  char sample[3];
  short i;
  char *genre;

  if (mp3_file = Open (msg->path, MODE_OLDFILE))
   {
    if (Seek (mp3_file, -128, OFFSET_END) != -1)
     {
      /* Let's check if tag exists already. If so, we write tag at the end of the file. */

      if (Read (mp3_file, sample, 3) == 3)
       {
        if ((sample[0] == 'T') && (sample[1] == 'A') && (sample[2] == 'G'))
          Seek (mp3_file, -128, OFFSET_END);
        else Seek (mp3_file, 0, OFFSET_END);

        /* Copying tag contents. Strings lenghts are already limited by MUI. Ending zero of every */
        /* string copied is overwritten in the next line. All the tagblock is initially filled by */
        /* zeros, after filling every zero (except genre byte) is replaced by space (0x20). */

        memset (data->tagblock, 0, 128);
        strcpy (data->tagblock, "TAG");     
        strcpy (&data->tagblock[3], (char*)xget (data->title_string, MUIA_String_Contents));
        strcpy (&data->tagblock[33], (char*)xget (data->artist_string, MUIA_String_Contents));
        strcpy (&data->tagblock[63], (char*)xget (data->album_string, MUIA_String_Contents));
        strcpy (&data->tagblock[93], (char*)xget (data->year_string, MUIA_String_Contents));
        strcpy (&data->tagblock[97], (char*)xget (data->comment_string, MUIA_String_Contents));

        /* convert genre name to a number */

        genre = (char*)xget (data->genre_text, MUIA_Text_Contents);
        for (i = 0; i < GENRES; i++) if (!strcmp (genre, genres[i])) break;
        (i < GENRES) ? (data->genre = i) : (data->genre = 0xFF);
        data->tagblock[127] = data->genre;

        for (i = 0; i < 127; i++) if (!data->tagblock[i]) data->tagblock[i] = 0x20;

        Write (mp3_file, data->tagblock, 128);
       }
     }
    Close (mp3_file);
   }
 }

/* ==== DISPATCHER =================================================================================*/

static long MP3Dispatcher (Class *cl reg(a0), Object *obj reg(a2), Msg msg reg(a1))
 {
  switch (msg->MethodID)
   {
    case OM_NEW:            return (mNew (cl, obj, (struct opSet*)msg));
    case MP3_ReadTags:      return (mReadTags (cl, obj, (struct opFile*) msg));
    case MP3_WriteTags:     return (mWriteTags (cl, obj, (struct opFile*) msg));
    default:                return (DoSuperMethodA (cl, obj, msg));
   }
 }
