/*******************************************************************************
*                         Goggles Music Manager                                *
********************************************************************************
*           Copyright (C) 2006-2010 by Sander Jansen. All Rights Reserved      *
*                               ---                                            *
* 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.           *
********************************************************************************/
#include <math.h>
#include "common.h"
#include <fileref.h>
#include <tstring.h>
#include <id3v2tag.h>
#include <id3v2framefactory.h>

#include <mpegfile.h>
#include <vorbisfile.h>
#include <flacfile.h>
#include <apetag.h>
#include <textidentificationframe.h>
#include <attachedpictureframe.h>

#ifdef HAVE_TAGLIB_16
#ifndef DISABLE_ASF_SUPPORT
#if !defined(TAGLIB_WITH_ASF) || (TAGLIB_WITH_ASF==0)
#define DISABLE_ASF_SUPPORT=0
#endif
#endif

#ifndef DISABLE_MP4_SUPPORT
#if !defined(TAGLIB_WITH_MP4) || (TAGLIB_WITH_MP4==0)
#define DISABLE_MP4_SUPPORT=0
#endif
#endif
#endif


#ifndef DISABLE_ASF_SUPPORT
  #ifndef HAVE_TAGLIB_16
    #ifdef HAVE_TAGLIB_EXTRAS
      #include "asffiletyperesolver.h"
    #else
      #include "asfresolve.h"
    #endif
  #endif
#endif

#ifndef DISABLE_MP4_SUPPORT
  #ifdef HAVE_TAGLIB_EXTRAS
    #include "mp4file.h"
    #include "mp4tag.h"
    #include "mp4filetyperesolver.h"
  #else
    #include "mp4file.h"
    #include "mp4tag.h"
    #ifndef HAVE_TAGLIB_16
      #include "mp4resolve.h"
    #endif
  #endif
#endif

#include "common.h"
#include "GMTag.h"
#include "FXPNGIcon.h"
#include "FXJPGIcon.h"


void GMTrack::reset() {
  path.clear();
  title.clear();
  artist.clear();
  album.clear();
  album_artist.clear();
  genre.clear();
  year=0;
  no=0;
  time=0;
  bitrate=0;
  album_gain=NAN;
  album_peak=NAN;
  track_gain=NAN;
  track_peak=NAN;
  }


namespace GMTag {

#ifndef HAVE_TAGLIB_16
#ifndef DISABLE_ASF_SUPPORT
static ASFFileTypeResolver asfresolver;
#endif
#ifndef DISABLE_MP4_SUPPORT
static MP4FileTypeResolver mp4resolver;
#endif
#endif



void init(){
#ifndef HAVE_TAGLIB_16
#ifndef DISABLE_ASF_SUPPORT
  TagLib::FileRef::addFileTypeResolver(&asfresolver);
#endif
#ifndef DISABLE_MP4_SUPPORT
  TagLib::FileRef::addFileTypeResolver(&mp4resolver);
#endif
#endif
  TagLib::ID3v2::FrameFactory::instance()->setDefaultTextEncoding(TagLib::String::UTF16);
  }


FXbool sync(GMTrack & info){
  if (!FXStat::isWritable(info.mrl)) return false;

  TagLib::FileRef file(info.mrl.text(),false);
  if (file.isNull() || !file.tag())
    return false;

  TagLib::Tag * tag = file.tag();

  tag->setTitle(TagLib::String(info.title.text(),TagLib::String::UTF8));
  tag->setArtist(TagLib::String(info.artist.text(),TagLib::String::UTF8));
  tag->setAlbum(TagLib::String(info.album.text(),TagLib::String::UTF8));
  tag->setGenre(TagLib::String(info.genre.text(),TagLib::String::UTF8));
  tag->setYear(info.year);
  tag->setTrack(GMTRACKNO(info.no));

  TagLib::MPEG::File        * mpgfile = NULL;
  TagLib::Ogg::Vorbis::File * oggfile = NULL;
  TagLib::FLAC::File        * flacfile = NULL;
#ifndef DISABLE_MP4_SUPPORT
  TagLib::MP4::File         * mp4file = NULL;
  TagLib::MP4::Tag          * mp4tag = NULL;
#endif

  TagLib::Ogg::XiphComment * vorbiscomment=NULL;
  TagLib::ID3v2::Tag       * id3v2tag=NULL;
//  TagLib::ID3v1::Tag       * id3v1tag=NULL;
//  TagLib::APE::Tag         * apetag=NULL;


  /// Determine Tags
  if ((oggfile = dynamic_cast<TagLib::Ogg::Vorbis::File*>(file.file()))) {
    vorbiscomment=oggfile->tag();
    }
  else if ((flacfile = dynamic_cast<TagLib::FLAC::File*>(file.file()))){
    vorbiscomment=flacfile->xiphComment();
    id3v2tag=flacfile->ID3v2Tag();
    //id3v1tag=flacfile->ID3v1Tag();
    }
  else if ((mpgfile = dynamic_cast<TagLib::MPEG::File*>(file.file()))){
    id3v2tag=mpgfile->ID3v2Tag();
    //apetag=mpgfile->APETag();
    }
#ifndef DISABLE_MP4_SUPPORT
  else if ((mp4file = dynamic_cast<TagLib::MP4::File*>(file.file()))){
    mp4tag=mp4file->tag();
    }
#endif

  FXString disc = GMStringVal(GMDISCNO(info.no));

  if (vorbiscomment) {

    if (GMDISCNO(info.no)==0)
      vorbiscomment->removeField("DISCNUMBER");
    else
      vorbiscomment->addField("DISCNUMBER",TagLib::String(disc.text(),TagLib::String::UTF8),true);

    if (info.album_artist!=info.artist && !info.album_artist.empty())
      vorbiscomment->addField("ALBUMARTIST",TagLib::String(info.album_artist.text(),TagLib::String::UTF8),true);
    else
      vorbiscomment->removeField("ALBUMARTIST");

    }
  else if (id3v2tag) {

    /// Disc Number
    if (GMDISCNO(info.no)==0) {
      id3v2tag->removeFrames("TPOS");
      }
    else if (!id3v2tag->frameListMap()["TPOS"].isEmpty()){
      id3v2tag->frameListMap()["TPOS"].front()->setText(TagLib::String(disc.text(),TagLib::String::UTF8));
      }
    else {
      TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame("TPOS",TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding());
      frame->setText(TagLib::String(disc.text(),TagLib::String::UTF8) );
      id3v2tag->addFrame(frame);
      }

    /// Album Artist
    if (info.album_artist!=info.artist && !info.album_artist.empty()){
      if (!id3v2tag->frameListMap()["TPE2"].isEmpty()){
        id3v2tag->frameListMap()["TPE2"].front()->setText(TagLib::String(info.album_artist.text(),TagLib::String::UTF8));
        }
      else {
        TagLib::ID3v2::TextIdentificationFrame *frame = new TagLib::ID3v2::TextIdentificationFrame("TPE2",TagLib::ID3v2::FrameFactory::instance()->defaultTextEncoding());
        frame->setText(TagLib::String(info.album_artist.text(),TagLib::String::UTF8) );
        id3v2tag->addFrame(frame);
        }
      }
    else {
      id3v2tag->removeFrames("TPE2");
      }
    }
#ifndef DISABLE_MP4_SUPPORT
  else if (mp4tag) {
    /// Album Artist
    mp4tag->itemListMap()["aART"] = TagLib::StringList(TagLib::String(info.album_artist.text(),TagLib::String::UTF8));

    /// Disk Number
    if (GMDISCNO(info.no)==0)
      mp4tag->itemListMap().erase("disk");
    else
      mp4tag->itemListMap()["disk"] = TagLib::MP4::Item(GMDISCNO(info.no),0);

    }
#endif
  return file.save();
  }






#define FLAC_LAST_BLOCK(h) (h&0x80)
#define FLAC_BLOCK_TYPE(h) (h&0x7f)
#define FLAC_BLOCK_SIZE(h) ( (((h&0xFF00)>>8)<<16) | (((h&0xFF0000)>>16)<<8) | ((h&0xFF000000)>>24) )

enum {
  FLAC_BLOCK_STREAMINFO = 0,
  FLAC_BLOCK_PICTURE = 6
  };

enum {
  FLAC_STREAMINFO_BLOCK = 0,
  FLAC_VORBIS_COMMENT_BLOCK = 4,
  FLAC_PICTURE_BLOCK = 6
  };

FXIcon * load_flac_picture(FXStream & store,FXint size) {
  FXbool swap=store.swapBytes();
  store.setBigEndian(true);
  FXuint picture_type;
  FXuint picture_width;
  FXuint picture_height;
  FXuint picture_bps;
  FXuint picture_ncolors;
  FXString mimetype;
  FXString description;
  FXuint nlength;

  store >> picture_type;

  store >> nlength;
  mimetype.length(nlength);
  store.load(&mimetype[0],nlength);

  store >> nlength;
  description.length(nlength);
  store.load(&description[0],nlength);

  store >> picture_width;
  store >> picture_height;
  store >> picture_bps;
  store >> picture_bps;
  store >> picture_ncolors;

  store.swapBytes(swap);

  FXIconSource src(FXApp::instance());

  if ((comparecase(mimetype,"image/jpg")==0) || (comparecase(mimetype,"image/jpeg")==0)) {
    return src.loadScaledIconStream(store,size,1,FXJPGIcon::fileExt);
    }
  else if (comparecase(mimetype,FXPNGIcon::mimeType)==0) {
    return src.loadScaledIconStream(store,size,1,FXPNGIcon::fileExt);
    }
  else if (comparecase(mimetype,FXGIFIcon::mimeType)==0) {
    return src.loadScaledIconStream(store,size,1,FXGIFIcon::fileExt);
    }
  else if ((comparecase(mimetype,"image/bmp")==0) || (comparecase(mimetype,"image/x-bmp")==0)) {
    return src.loadScaledIconStream(store,size,1,FXBMPIcon::fileExt);
    }
  return NULL;
  }

FXIcon * load_flac_albumart(const FXString & mrl,FXint size) {
  FXchar  marker[4];
  FXuint  header;
  FXint   nread;
  FXFile io;

  if (io.open(mrl,FXIO::Reading)) {

    nread = io.readBlock(marker,4);
    if (nread!=4 || compare(marker,"fLaC",4)) return NULL;

    nread = io.readBlock(&header,4);
    if (nread!=4 || FLAC_BLOCK_TYPE(header)!=FLAC_BLOCK_STREAMINFO || FLAC_BLOCK_SIZE(header)!=34  || FLAC_LAST_BLOCK(header)) return NULL;

    io.position(34,FXIO::Current);

    while(!FLAC_LAST_BLOCK(header)) {

      nread = io.readBlock(&header,4);
      if (nread!=4) return false;

      if (FLAC_BLOCK_TYPE(header)==FLAC_BLOCK_PICTURE) {
        FXuchar * pictureblock = NULL;
        allocElms(pictureblock,FLAC_BLOCK_SIZE(header));
        nread = io.readBlock(pictureblock,FLAC_BLOCK_SIZE(header));
        if (nread!=(FXint)FLAC_BLOCK_SIZE(header)) {
          freeElms(pictureblock);
          return false;
          }
        FXMemoryStream stream;
#if FOXVERSION < FXVERSION(1,7,18)
        stream.open(FXStreamLoad,FLAC_BLOCK_SIZE(header),pictureblock);
#else
        stream.open(FXStreamLoad,pictureblock,FLAC_BLOCK_SIZE(header));
#endif
        FXIcon * icon = load_flac_picture(stream,size);
        stream.close();
        freeElms(pictureblock);
        if (icon) return icon;
        }
      else if (!FLAC_LAST_BLOCK(header)){
        io.position(FLAC_BLOCK_SIZE(header),FXIO::Current);
        }
      }
    }
  return NULL;
  }





FXIcon * albumart(const FXString & mrl,FXint size) {
  FXString mime;
  FXString extension = FXPath::extension(mrl);

  if (comparecase(extension,"flac")==0){
    FXIcon * icon = load_flac_albumart(mrl,size);
    if (icon) return icon;
    }
  else if (comparecase(extension,"ogg")==0) {
    return NULL;
    }

  TagLib::ID3v2::Tag * id3v2tag=NULL;
  TagLib::MPEG::File * mpgfile = NULL;
  TagLib::FLAC::File * flacfile = NULL;

  TagLib::FileRef file(mrl.text(),false);
  if (file.isNull() || !file.tag()) {
    return NULL;
    }

  if ((flacfile = dynamic_cast<TagLib::FLAC::File*>(file.file()))){
    id3v2tag=flacfile->ID3v2Tag();
    }
  else if ((mpgfile = dynamic_cast<TagLib::MPEG::File*>(file.file()))){
    id3v2tag=mpgfile->ID3v2Tag();
    }
  else {
    return NULL;
    }

  FXIconSource src(FXApp::instance());

  if (id3v2tag) {
    TagLib::ID3v2::FrameList framelist = id3v2tag->frameListMap()["APIC"];
    if(!framelist.isEmpty()){
      for(TagLib::ID3v2::FrameList::Iterator it = framelist.begin(); it != framelist.end(); it++) {
        TagLib::ID3v2::AttachedPictureFrame * frame = dynamic_cast<TagLib::ID3v2::AttachedPictureFrame*>(*it);
        FXASSERT(frame);
        mime = frame->mimeType().toCString(true);
        if ((comparecase(mime,"image/jpg")==0) || (comparecase(mime,"image/jpeg")==0)) {
          return src.loadScaledIconData(frame->picture().data(),size,1,FXJPGIcon::fileExt);
          }
        else if (comparecase(mime,FXPNGIcon::mimeType)==0) {
          return src.loadScaledIconData(frame->picture().data(),size,1,FXPNGIcon::fileExt);
          }
        else if ((comparecase(mime,"image/bmp")==0) || (comparecase(mime,"image/x-bmp")==0) ) {
          return src.loadScaledIconData(frame->picture().data(),size,1,FXBMPIcon::fileExt);
          }
        else if (comparecase(mime,FXGIFIcon::mimeType)==0) {
          return src.loadScaledIconData(frame->picture().data(),size,1,FXGIFIcon::fileExt);
          }
        else {
          }
        }
      }
    }
  return NULL;
  }


/// Return Track Information
FXbool parse(GMTrack & info) {

  TagLib::FileRef file(info.mrl.text());
  if (file.isNull() || !file.tag()) {
    info.reset();
    return false;
    }

  /// This is a really dirty and quick hack that is not garanteed to work. We need
  /// it for proper floating point to string conversion.
  /// xine will set the LC_NUMERIC locale in its threads, so we're not sure,
  /// when xine will change it. Luckily xine is already shutdown when we call this.
  setlocale(LC_NUMERIC,"C");


  TagLib::Tag * tag = file.tag();
  TagLib::AudioProperties *properties = file.audioProperties();

  info.artist = tag->artist().toCString(true);
  info.album  = tag->album().toCString(true);
  info.title  = tag->title().toCString(true);
  info.genre  = tag->genre().toCString(true);
  info.year   = tag->year();
  info.no		  =	GMALBUMNO(0,FXMIN(tag->track(),0xFFFF));

  info.album_artist = info.artist	= info.artist.trim();
  info.album 	= info.album.trim();
  info.title 	= info.title.trim();
  info.genre	= info.genre.trim();

  if (properties)
    info.time = properties->length();
  else
    info.time = 0;

  /// Init to NaN
  info.album_gain=NAN;
  info.album_peak=NAN;
  info.track_gain=NAN;
  info.track_peak=NAN;

  FXString disc,value;
  TagLib::MPEG::File * mpgfile = NULL;
  TagLib::Ogg::Vorbis::File * oggfile = NULL;
  TagLib::FLAC::File  * flacfile = NULL;
#ifndef DISABLE_MP4_SUPPORT
  TagLib::MP4::File  * mp4file = NULL;
  TagLib::MP4::Tag * mp4tag = NULL;
#endif

  TagLib::Ogg::XiphComment * vorbiscomment=NULL;
  TagLib::ID3v2::Tag       * id3v2tag=NULL;
//  TagLib::ID3v1::Tag       * id3v1tag=NULL;
  TagLib::APE::Tag         * apetag=NULL;


  /// Determine Tags
  if ((oggfile = dynamic_cast<TagLib::Ogg::Vorbis::File*>(file.file()))) {
    vorbiscomment=oggfile->tag();
    }
  else if ((flacfile = dynamic_cast<TagLib::FLAC::File*>(file.file()))){
    vorbiscomment=flacfile->xiphComment();
    id3v2tag=flacfile->ID3v2Tag();
    //id3v1tag=flacfile->ID3v1Tag();
    }
  else if ((mpgfile = dynamic_cast<TagLib::MPEG::File*>(file.file()))){
    id3v2tag=mpgfile->ID3v2Tag();
    apetag=mpgfile->APETag();
    }
#ifndef DISABLE_MP4_SUPPORT
  else if ((mp4file = dynamic_cast<TagLib::MP4::File*>(file.file()))){
    mp4tag=mp4file->tag();
    }
#endif

  /// First the vorbis comment for Ogg and Flac Files
  if (vorbiscomment) {

    /// disc number
    if (!vorbiscomment->fieldListMap()["DISCNUMBER"].isEmpty())
      disc = vorbiscomment->fieldListMap()["DISCNUMBER"].front().toCString(true);

    /// album artist
    if (!vorbiscomment->fieldListMap()["ALBUMARTIST"].isEmpty())
      info.album_artist =vorbiscomment->fieldListMap()["ALBUMARTIST"].front().toCString(true);

    /// replay gain
    if (!vorbiscomment->fieldListMap()["REPLAYGAIN_TRACK_GAIN"].isEmpty()){
      value=vorbiscomment->fieldListMap()["REPLAYGAIN_TRACK_GAIN"].front().toCString(true);
      value.scan("%lf",&info.track_gain);
      if (!vorbiscomment->fieldListMap()["REPLAYGAIN_TRACK_PEAK"].isEmpty()){
        value=vorbiscomment->fieldListMap()["REPLAYGAIN_TRACK_PEAK"].front().toCString(true);
        value.scan("%lf",&info.track_peak);
        }
      }

    if (!vorbiscomment->fieldListMap()["REPLAYGAIN_ALBUM_GAIN"].isEmpty()){
      value=vorbiscomment->fieldListMap()["REPLAYGAIN_ALBUM_GAIN"].front().toCString(true);
      value.scan("%lf",&info.album_gain);

      if (!vorbiscomment->fieldListMap()["REPLAYGAIN_ALBUM_PEAK"].isEmpty()){
        value=vorbiscomment->fieldListMap()["REPLAYGAIN_ALBUM_PEAK"].front().toCString(true);
        value.scan("%lf",&info.album_peak);
        }
      }

    /// Old Style vorbisgain tags (which didn't have the album peak info)
    if (isnan(info.track_peak) && isnan(info.album_gain)) {

      if (!vorbiscomment->fieldListMap()["RG_RADIO"].isEmpty()){
        value=vorbiscomment->fieldListMap()["RG_RADIO"].front().toCString(true);
        value.scan("%lf",&info.track_gain);

        if (!vorbiscomment->fieldListMap()["RG_PEAK"].isEmpty()){
          value=vorbiscomment->fieldListMap()["RG_PEAK"].front().toCString(true);
          value.scan("%lf",&info.track_peak);
          }
        }

      if (!vorbiscomment->fieldListMap()["RG_AUDIOPHILE"].isEmpty()){
        value=vorbiscomment->fieldListMap()["RG_AUDIOPHILE"].front().toCString(true);
        value.scan("%lf",&info.album_gain);
        }
      }
    }
  else if (id3v2tag) { /// Flac and MPG
    if ( !id3v2tag->frameListMap()["TPOS"].isEmpty() )
      disc = id3v2tag->frameListMap()["TPOS"].front()->toString().toCString(true);
    if ( !id3v2tag->frameListMap()["TPE2"].isEmpty() )
      info.album_artist = id3v2tag->frameListMap()["TPE2"].front()->toString().toCString(true);
    }
  else if (apetag) {
    if (!apetag->itemListMap()["REPLAYGAIN_TRACK_GAIN"].isEmpty()){
      value=apetag->itemListMap()["REPLAYGAIN_TRACK_GAIN"].toString().toCString(true);
      value.scan("%lf",&info.track_gain);

      if (!apetag->itemListMap()["REPLAYGAIN_TRACK_PEAK"].isEmpty()){
        value=apetag->itemListMap()["REPLAYGAIN_TRACK_PEAK"].toString().toCString(true);
        value.scan("%lf",&info.track_peak);
        }
      }

    if (!apetag->itemListMap()["REPLAYGAIN_ALBUM_GAIN"].isEmpty()){
      value=apetag->itemListMap()["REPLAYGAIN_ALBUM_GAIN"].toString().toCString(true);
      value.scan("%lf",&info.album_gain);

      if (!apetag->itemListMap()["REPLAYGAIN_ALBUM_PEAK"].isEmpty()){
        value=apetag->itemListMap()["REPLAYGAIN_ALBUM_PEAK"].toString().toCString(true);
        value.scan("%lf",&info.album_peak);
        }
      }

    }

#ifndef DISABLE_MP4_SUPPORT
  else if (mp4tag) { /// MP4

    // Disc Number
    if (mp4tag->itemListMap().contains("disk")) {
      FXint num =mp4tag->itemListMap()["disk"].toIntPair().first;
      info.no = GMALBUMNO(FXMIN(num,0xFFFF),GMTRACKNO(info.no));
      }

    // Album Artist
    if (mp4tag->itemListMap().contains("aART"))
      info.album_artist = mp4tag->itemListMap()["aART"].toStringList().toString(", ").toCString(true);

    }
#endif

  if (!disc.empty()) {
    disc.trim();
    disc = disc.before('/');
    if (!disc.empty()) {
#if FOXVERSION >= FXVERSION(1,7,12)
      info.no = GMALBUMNO(FXMIN(disc.toUInt(),0xFFFF),GMTRACKNO(info.no));
#else
      info.no = GMALBUMNO(FXMIN(FXUIntVal(disc),0xFFFF),GMTRACKNO(info.no));
#endif
      }
    }

  return true;
  }



FXbool length(GMTrack & info) {
  TagLib::FileRef file(info.mrl.text());
  if (file.isNull())
    return false;
  TagLib::AudioProperties *prop = file.audioProperties();
  if (prop)
    info.time = prop->length();
  else
    info.time = 0;
  return true;
  }


FXbool properties(const FXString & mrl,Properties & p) {
  p.bitrate=-1;
  p.samplerate=-1;
  p.channels=-1;

  TagLib::FileRef file(mrl.text());
  if (file.isNull())
    return false;

  TagLib::AudioProperties *prop = file.audioProperties();

  if (!prop)
    return false;

  p.bitrate    = prop->bitrate();
  p.samplerate = prop->sampleRate();
  p.channels   = prop->channels();
  return true;
  }
}
