//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2013 by PDSoft (Attila Padar)                *
//*                http://mpxplay.sourceforge.net                          *
//*                  email: mpxplay@freemail.hu                            *
//**************************************************************************
//*  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.                  *
//*  Please contact with the author (with me) if you want to use           *
//*  or modify this source.                                                *
//**************************************************************************
//function:id3 (tag) list routines (load,assign,save)

#include "mpxplay.h"
#include "diskdriv\diskdriv.h"
#include "display\display.h"
#include <stdlib.h>

typedef int (*qsc_t)(const void *,const void *);

typedef char *ID3LISTTYPE[MAX_ID3LISTPARTS];
static ID3LISTTYPE *id3listtable;
static char *id3listdatafield;
static unsigned int id3listdatasize,id3listmaxlines,id3listlines;
static struct playlist_side_info *id3listloadside;

extern char *id3loadname,*id3savename;
extern mpxp_uint32_t mpxplay_playlistcontrol;
extern unsigned int saveid3list,id3savefields,writeid3tag;
#ifdef MPXPLAY_GUI_CONSOLE
extern unsigned int loadid3tag, id3textconv,playlist_max_filenum_list;
#endif

// sort id3list by filenames (with qsort) (sorted from right!)
static int id3list_order_id3list_qs(char **i1,char **i2)
{
 return pds_strricmp(i1[0],i2[0]); // !!! not unicode/utf8 compatible
}

// logarithmic search in the sorted field
static ID3LISTTYPE *id3list_log_search(char *searchstr)
{
 ID3LISTTYPE *id3lp=id3listtable;
 unsigned int pos,bottom,top,center;
 int result;

 bottom=0;
 top=id3listlines;

 result=1;

 center=id3listlines;
 center>>=1;

 if(center>2){
  do{
   pos=bottom+center;
   result=pds_strricmp(searchstr,id3lp[pos][0]);
   if(result==0)
    break;
   if(result<0)
    top=pos;
   else
    bottom=pos;
   center=(top-bottom);
   center>>=1;
  }while(center>2);
 }

 if(center<=2){
  pos=bottom;
  do{
   result=pds_strricmp(searchstr,id3lp[pos][0]);
   if(result==0)
    break;
   pos++;
  }while(pos<=top);
 }
 if(result==0)
  return &id3lp[pos];

 return NULL;
}

static unsigned int open_id3list(struct playlist_side_info *psi,void **id3loadfile,char *filename)
{
 struct mpxplay_diskdrive_data_s *mdds=NULL;

 id3listloadside=NULL;
 id3listlines=0;
 if(mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_ID3LIST_LOCAL)
  mdds=psi->mdds;
 *id3loadfile=mpxplay_diskdrive_textfile_open(mdds,filename,(O_RDONLY|O_TEXT));
 if(!(*id3loadfile))
  return 0;
 if(!id3listtable){
  id3listtable=pds_malloc(id3listmaxlines*MAX_ID3LISTPARTS*sizeof(char *));
  if(!id3listtable)
   return 0;
 }
 if(id3listdatafield)
  pds_free(id3listdatafield);
 id3listdatasize=mpxplay_diskdrive_textfile_filelength(*id3loadfile)+2048;
 id3listdatafield=pds_malloc(id3listdatasize+1024);
 if(!id3listdatafield)
  return 0;
 pds_memset(id3listtable,0,id3listmaxlines*MAX_ID3LISTPARTS*sizeof(char *));
 return 1;
}

void playlist_id3list_close(void)
{
 if(id3listtable){
  pds_free(id3listtable);
  id3listtable=NULL;
 }
 if(id3listdatafield){
  pds_free(id3listdatafield);
  id3listdatafield=NULL;
 }
}

void playlist_id3list_load(struct mainvars *mvp,struct playlist_side_info *psi)
{
 void *id3loadfile;
 char *p2,**id3lp;
 // 4 different possible id3list format with max. 4 line-parts
 char *separators[4]={"|||","°±²","°:±"," :"},*listparts[MAX_ID3LISTPARTS+2];
 const mpxp_uint8_t sep_lens[4]={4,4,4,3};
#ifndef MPXPLAY_UTF8
 const mpxp_uint8_t do_char_conv[MAX_ID3LISTPARTS]={0,1,1,0}; // parts: filename,artist,title,time/other
#endif
 long listlinecount,listbytecount,i,j,foundparts,i3mlinelen;
 char id3loadfilename[MAX_PATHNAMELEN],strtemp[MAX_ID3LEN];

 switch(mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_MASK_ID3LIST){
  case MPXPLAY_PLAYLISTC_ID3LIST_LOCAL:
    if(!psi || !(psi->editsidetype&PLT_DIRECTORY) || psi->sublistlevel)
     return;
#ifdef MPXPLAY_GUI_CONSOLE
    id3listmaxlines=playlist_max_filenum_list/PLAYLIST_MAXFILENUMDIV_DIR;
#else
    id3listmaxlines=8292/PLAYLIST_MAXFILENUMDIV_DIR;
#endif
    pds_filename_build_fullpath(id3loadfilename,psi->currdir,id3loadname);
    break;
  case MPXPLAY_PLAYLISTC_ID3LIST_GLOBAL:
    if(id3listdatafield)
     return;
    pds_fullpath(id3loadfilename,id3loadname);
#ifdef MPXPLAY_GUI_CONSOLE
    id3listmaxlines=playlist_max_filenum_list;
#else
    id3listmaxlines=32768;
#endif
    break;
  default:return;
 }

 if(!open_id3list(psi,&id3loadfile,id3loadfilename)){
  if(writeid3tag){
   pds_textdisplay_printf("ID3-(info)list loading error! Cannot write ID3-tags...");
   writeid3tag=0;
  }
  return;
 }

 id3lp=&id3listtable[0][0];
 listlinecount=0;
 listbytecount=0;
 do{
  if(mpxplay_diskdrive_textfile_readline(id3loadfile,strtemp,sizeof(strtemp)-1) <= 0)
   break;
#ifdef MPXPLAY_UTF8
  if((listbytecount+sizeof(strtemp))>id3listdatasize){
   char *newid3field=pds_malloc(id3listdatasize+(id3listdatasize>>2));
   if(!newid3field)
    break;
   pds_memcpy(newid3field,id3listdatafield,id3listdatasize);
   pds_free(id3listdatafield);
   id3listdatafield=newid3field;
   id3listdatasize+=id3listdatasize>>2;
  }
#endif
  if(mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_ID3LIST_LOCAL)
   i=3;
  else
   i=0;
  for(;i<4;i++){
   pds_memset(&(listparts[0]),0,MAX_ID3LISTPARTS*sizeof(char *));
   i3mlinelen=pds_strcpy((id3listdatafield+listbytecount),strtemp)+1;
   pds_listline_slice(&(listparts[0]),separators[i],(id3listdatafield+listbytecount));
   foundparts=0;
   for(j=0;j<sep_lens[i];j++)
    foundparts+=(listparts[j])? 1:0;
   if((foundparts==sep_lens[i]) || (i==3)){
    if(listparts[2]==NULL)   // if no 'title'
     if(listparts[1]!=NULL){
      listparts[2]=listparts[1]; // use 'artist' for title
      listparts[1]=NULL;
     }
    for(j=0;j<sep_lens[i];j++){
     p2=listparts[j];
     if(p2){
      unsigned int p2len=pds_strcutspc(p2);
      if(p2len){
#ifndef MPXPLAY_UTF8
       if(do_char_conv[j])
        mpxplay_playlist_textconv_by_texttypes(
         MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
         p2,p2len,p2,p2len);
#endif
       id3lp[j]=p2;
      }
     }
    }
    listbytecount+=i3mlinelen;
    id3lp+=MAX_ID3LISTPARTS;
    listlinecount++;
    break;
   }
  }
 }while(listlinecount<id3listmaxlines);
 if(listlinecount>0){
  id3listloadside=psi;  // ID3LISTTYPE_LOCAL
  qsort((void *)id3listtable,listlinecount,MAX_ID3LISTPARTS*sizeof(char *),(qsc_t)id3list_order_id3list_qs);
 }
 id3listlines=listlinecount;
 mpxplay_diskdrive_textfile_close(id3loadfile);
}

unsigned int get_onefileinfos_from_id3list(struct playlist_side_info *psi,struct playlist_entry_info *pei,unsigned int id3loadflags)
{
 ID3LISTTYPE *id3lp;
 unsigned int found=0;

 if(!id3listtable || !id3listlines || !(id3loadflags&ID3LOADMODE_LIST))
  return found;

 switch(mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_MASK_ID3LIST){
  case MPXPLAY_PLAYLISTC_ID3LIST_LOCAL:
    if(psi!=id3listloadside)
     return found;
    break;
  case MPXPLAY_PLAYLISTC_ID3LIST_GLOBAL:
   break;
  default:return found;
 }

 if((pei>=psi->firstsong) && (pei<=psi->lastentry)){
  id3lp=id3list_log_search(pei->filename);
  if(id3lp){
   if(id3lp[0][1] || id3lp[0][2]){
    if( playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,id3lp[0][1],-1)>0
     && playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,id3lp[0][2],-1)>0){
     pei->infobits|=PEIF_ID3EXIST;
     found|=EDITLIST_MODE_ID3;
    }
   }
   if(id3lp[0][3] && (pei->entrytype==DFT_NOTCHECKED || pei->entrytype==DFT_UNKNOWN) && (id3loadflags&ID3LOADMODE_FASTSEARCH)){
    if(id3lp[0][3][8]!='|'){ // if not old mxu style (v1.42)
     unsigned int timesec=pds_atol16(id3lp[0][3]);
     if(timesec&MXUFLAG_ENABLED){
      pei->timemsec=(timesec&MXUFLAG_TIMEMASK)*1000;
      pei->infobits|=PEIF_ENABLED;
      pei->entrytype=DFT_NOTCHECKED;
      found|=EDITLIST_MODE_HEAD;
     }
    }
   }
  }
 }
 return found;
}

void playlist_id3list_save(struct mainvars *mvp)
{
 FILE *id3savefile;
 unsigned int songcounter,isf;
 struct mpxpframe_s *frp;
 struct mpxplay_audio_decoder_info_s *adi;
 struct playlist_side_info *psi;
 struct playlist_entry_info *pei;
 char *shortfname,souttext[300],strtemp[300],convtmp[300],lastdir[300];
 const char *formats[3][2]={{"%s","%s"},{"%.64s","%.30s"},{"%-28.28s","%-19.19s"}};

 if(!saveid3list || (id3savefile=pds_fopen(id3savename,"wt"))==NULL)
  return;

 pds_textdisplay_printf("Extended playlist save ...");

 isf=id3savefields;
 frp=mvp->fr_check;
 adi=frp->infile_infos->audio_decoder_infos;
 psi=mvp->psil;
 songcounter=1;
 lastdir[0]=0;

 for(pei=psi->firstentry;pei<=psi->lastentry;pei++){
  playlist_chkentry_get_onefileinfos_is(psi,pei);
  shortfname=pds_getpath_from_fullname(strtemp,pei->filename);
  if(isf&IST_DIRECTORY){
   if(pds_utf8_stricmp(lastdir,strtemp)!=0){
    pds_strcpy(lastdir,strtemp);
#ifndef MPXPLAY_UTF8
    if(id3textconv&ID3TEXTCONV_FILENAME)
#endif
     mpxplay_playlist_textconv_by_texttypes(
         MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
         strtemp,-1,strtemp,sizeof(strtemp));
    fprintf(id3savefile,"***** %.62s **********\n",strtemp);
    songcounter=1;
   }
  }
  if(isf&IST_FILENAME){
   char *fn=(isf&IST_FULLPATH)? pei->filename:shortfname;
#ifndef MPXPLAY_UTF8
   if(id3textconv&ID3TEXTCONV_FILENAME){
#endif
    mpxplay_playlist_textconv_by_texttypes(
      MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
      fn,-1,convtmp,sizeof(convtmp));
    fn=convtmp;
#ifndef MPXPLAY_UTF8
   }
#endif
   sprintf(souttext,"%-12.12s ",fn);
  }else
   sprintf(souttext,"%2d. ",songcounter++);

  if(!pei->id3info[I3I_ARTIST] && !pei->id3info[I3I_TITLE]){
   if((isf&(IST_TIME|IST_BITRATE|IST_FILESIZE)) || !(isf&IST_FILENAME)){
#ifndef MPXPLAY_UTF8
    if(id3textconv&ID3TEXTCONV_FILENAME){
#endif
     mpxplay_playlist_textconv_by_texttypes(
      MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
      shortfname,-1,convtmp,sizeof(convtmp));
     shortfname=convtmp;
#ifndef MPXPLAY_UTF8
    }
#endif
    sprintf(strtemp,"%-48.48s",(isf&IST_FILENAME)? "":shortfname);
    pds_strcat(souttext,strtemp);
   }
  }else{
   int form;
   if(isf&IST_AT_FIXED)
    form=2;
   else
    if(isf&(IST_TIME|IST_BITRATE|IST_FILESIZE))
     form=1;
    else
     form=0;
   mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
     pei->id3info[I3I_ARTIST],-1,convtmp,sizeof(convtmp));
   sprintf(strtemp,formats[form][1],convtmp);
   pds_strcat(souttext,strtemp);
   if(isf&(IST_TIME|IST_BITRATE|IST_FILESIZE)){
    pds_strcat(souttext,":");
    mpxplay_playlist_textconv_by_texttypes(
      MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
      pei->id3info[I3I_TITLE],-1,convtmp,sizeof(convtmp));
    sprintf(strtemp,formats[form][0],convtmp);
    pds_strcat(souttext,strtemp);
   }else{
    pds_strcat(souttext," : ");
    if(pei->id3info[I3I_TITLE]){
     mpxplay_playlist_textconv_by_texttypes(
       MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
       pei->id3info[I3I_TITLE],-1,convtmp,sizeof(convtmp));
     sprintf(strtemp,formats[form][0],convtmp);
     pds_strcat(souttext,strtemp);
    }
   }
  }

  if(isf&(IST_TIME|IST_BITRATE|IST_FILESIZE))
   pds_strcat(souttext," ");
  if((pei->infobits&PEIF_ENABLED) || frp->infile_infos->timemsec){
   if(isf&IST_TIME){
    long timesec=(playlist_entry_get_timemsec(pei)+500)/1000;
    sprintf(strtemp,"%d:%2.2d",timesec/60,timesec%60);
    pds_strcat(souttext,strtemp);
   }
   if(isf&IST_BITRATE){
    sprintf(strtemp,"%s%3d",(isf&IST_TIME)? "-":"",(adi->bitrate)? adi->bitrate:adi->bits);
    pds_strcat(souttext,strtemp);
   }
   if(isf&IST_FILESIZE){
    sprintf(strtemp,"%s%1.1fMB",(isf&(IST_TIME|IST_BITRATE))? "-":"",(float)frp->filesize/1048576.0);
    pds_strcat(souttext,strtemp);
   }
  }
  pds_strcat(souttext,"\n");
  fprintf(id3savefile,souttext);
 }
 mpxplay_close_program(MPXERROR_UNDEFINED);
}
