//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2012 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:m3u,pls,fpl,mxu loading

#include "mpxplay.h"
#include "control\control.h"
#include "display\display.h"
#include "diskdriv\diskdriv.h"
#include <string.h>

#define LOADLIST_MAX_LINELEN 1024 // have to be enough usually

typedef struct listhandler_s{
 char *extension;
 int (*loadlist)(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);
}listhandler_s;

static int open_load_m3u(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);
static int load_m3u(struct playlist_side_info *,void *listfile,char *loaddir,char *filtermask);
static int open_load_pls(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);
static int open_load_mxu(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);
static int open_load_fpl(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);
static int open_load_cue(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);
static int open_load_asx(struct playlist_side_info *,char *listname,char *loaddir,char *filtermask);

static struct listhandler_s listhandlers[]={
 {"m3u", &open_load_m3u },
 {"m3u8",&open_load_m3u },
 {"pls", &open_load_pls },
 {"mxu", &open_load_mxu },
 {"fpl", &open_load_fpl },
 {"cue", &open_load_cue },
 {"asx", &open_load_asx },
 {NULL,NULL}
};

extern unsigned int loadid3tag,id3textconv,playlistsave,uselfn,desktopmode,playrand;

static struct listhandler_s *select_listhandler_by_ext(char *filename)
{
 struct listhandler_s *lih;
 char *ext=pds_strrchr(filename,'.');
 if(ext){
  ext++;
  lih=&listhandlers[0];
  do{
   if(pds_stricmp(lih->extension,ext)==0)
    return lih;
   lih++;
  }while(lih->extension);
 }
 return NULL;
}

unsigned int playlist_loadlist_check_extension(char *filename)
{
 if(select_listhandler_by_ext(filename))
  return 1;
 return 0;
}

unsigned int playlist_loadlist_mainload(struct playlist_side_info *psi,char *listname,unsigned int loadtype,char *filtermask)
{
 struct listhandler_s *listhand;
 struct playlist_entry_info *laste;
 char loaddir[MAX_PATHNAMELEN];

 display_clear_timed_message();

 display_message(0,0,"Loading list (press ESC to stop) ...");

 laste=psi->lastentry;

 if(loadtype&PLL_STDIN){
  load_m3u(psi,stdin,mpxplay_playlist_startdir(),filtermask);
 }else{
  listhand=select_listhandler_by_ext(listname);
  if(listhand){
   pds_getpath_from_fullname(loaddir,listname);
   listhand->loadlist(psi,listname,loaddir,filtermask);
  }
 }

 if(psi->lastentry>=psi->firstentry)
  playlist_enable_side(psi);
 else
  playlist_disable_side_full(psi);

 clear_message();

 if(psi->lastentry>laste)
  return 1;
 return 0;
}

//-------------------------------------------------------------------------

static int open_load_m3u(struct playlist_side_info *psi,char *listname,char *loaddir,char *filtermask)
{
 void *listfile;
 int retcode;

 if((listfile=mpxplay_diskdrive_textfile_open(NULL,listname,(O_RDONLY|O_TEXT)))==NULL)
  return MPXPLAY_ERROR_FILEHAND_CANTOPEN;
 if(psi->lastentry<=psi->firstsong)
  psi->savelist_textcodetype=mpxplay_diskdrive_textfile_config(listfile,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_GET_TEXTCODETYPE_SRC,NULL,NULL);
 retcode=load_m3u(psi,listfile,loaddir,filtermask);
 mpxplay_diskdrive_textfile_close(listfile);
 return retcode;
}

static int load_m3u(struct playlist_side_info *psi,void *listfile,char *loaddir,char *filtermask)
{
 struct playlist_entry_info *pei;
 char *ps,*pa,*pt; // string pointer of timesec,artist,title
 int len,timesec,retcode=MPXPLAY_ERROR_OK;
 unsigned int firstline=1,linecount=0;
 char readbuf[LOADLIST_MAX_LINELEN],extinfo[LOADLIST_MAX_LINELEN];
 char fullname[LOADLIST_MAX_LINELEN];
 extinfo[0]=0;

 funcbit_disable(psi->editsidetype,(PLT_EXTENDED|PLT_EXTENDINC)); // ???

 do{
  if(listfile==stdin){
   if(!fgets(readbuf,sizeof(readbuf)-1,listfile))
    break;
   if(pds_look_extgetch()==KEY_ESC){
    pds_extgetch();
    break;
   }
   len=pds_strlen(readbuf);
   if(len){
    pa=&readbuf[len-1];
    do{
     if((*pa=='\r') || (*pa=='\n'))
      *pa=0;
     else
      break;
     pa--;
    }while(--len);
   }
  }else{
   if(!mpxplay_diskdrive_textfile_readline(listfile,readbuf,sizeof(readbuf)-1))
    break;
  }
  if(!readbuf[0])
   continue;
  if(firstline){
   if(pds_strncmp(readbuf,"#EXTM3U",7)==0)
    funcbit_enable(psi->editsidetype,PLT_EXTENDED);
   firstline=0;
  }
  if(readbuf[0]=='#'){
   if(pds_strncmp(readbuf,"#EXTINF:",8)!=0){
    extinfo[0]=0; // ???
    continue;
   }
   pds_strcpy(extinfo,readbuf);
   continue;
  }

  if(filtermask && !pds_utf8_filename_wildcard_cmp(readbuf,filtermask))
   continue;

  pds_filename_conv_slashes_to_local(readbuf);
  pds_filename_build_fullpath(fullname,loaddir,readbuf);
  if(uselfn&USELFN_AUTO_SFN_TO_LFN){
   sprintf(readbuf,"Loading list: %4d. (press ESC to stop)",++linecount);
   display_message(0,0,readbuf);
   pds_fullpath(fullname,fullname);
  }
#ifndef MPXPLAY_UTF8
  if(id3textconv&ID3TEXTCONV_FILENAME)
   mpxplay_playlist_textconv_by_texttypes(
    MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
    fullname,-1,fullname,sizeof(fullname)-2);
#endif
  pei=playlist_editlist_add_entry(psi);
  if(!pei)
   break;
  len=playlist_editlist_add_filename(psi,pei,fullname);
  if(len<=0){
   playlist_editlist_del_entry(psi,pei);
   if(len<0)
    break;
   extinfo[0]=0;
   continue;
  }

  if(extinfo[0]){
   ps=&extinfo[8]; // begin of timesec
   pa=pds_strchr(ps,',');
   if(pa){
    pa[0]=0;   // end of timesec
    timesec=pds_atol(ps);
    if(timesec>=0)
     pei->timemsec=timesec*1000;
    else
     pei->timemsec=PEI_TIMEMSEC_STREAM;
    funcbit_enable(pei->infobits,PEIF_ENABLED);
    if(loadid3tag&ID3LOADMODE_LIST){
     pa++;      // begin of artist
     pt=pds_strstr(pa," - "); // search for the ' - ' separator (artist-title)
     if(pt){
      pt[0]=0;  // end of artist
      pt+=3;    // begin of title
     }
#ifdef MPXPLAY_UTF8
     if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,pa,-1)>0)
      funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#else
     len=mpxplay_playlist_textconv_by_texttypes(
      MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
      pa,-1,fullname,sizeof(fullname)-2);
     if(len>0)
      if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,fullname,len)>0)
       funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#endif
     if(pt){
#ifdef MPXPLAY_UTF8
      if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,pt,-1)>0)
       funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#else
      len=mpxplay_playlist_textconv_by_texttypes(
       MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
       pt,-1,fullname,sizeof(fullname)-2);
      if(len>0)
       if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,fullname,len)>0)
        funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#endif
     }
    }
   }
  }

  if(psi->editsidetype&PLT_EXTENDED)  // #EXTM3U
   if(!(pei->infobits&PEIF_ENABLED))   // entry has no #EXTINF
    funcbit_enable(psi->editsidetype,PLT_EXTENDINC); // incomplete extm3u

  extinfo[0]=0;
 }while(1);
 return retcode;
}

//------------------------------------------------------------------------

static int open_load_pls(struct playlist_side_info *psi,char *listname,char *loaddir,char *filtermask)
{
 void *listfile;
 struct playlist_entry_info *pei=NULL;
 int len,linecount=0,retcode=MPXPLAY_ERROR_OK;
 char *datap,readbuf[LOADLIST_MAX_LINELEN],fullname[LOADLIST_MAX_LINELEN];

 if((listfile=mpxplay_diskdrive_textfile_open(NULL,listname,(O_RDONLY|O_TEXT)))==NULL)
  return MPXPLAY_ERROR_FILEHAND_CANTOPEN;

 if(psi->lastentry<=psi->firstsong)
  psi->savelist_textcodetype=mpxplay_diskdrive_textfile_config(listfile,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_GET_TEXTCODETYPE_SRC,NULL,NULL);

 funcbit_enable(psi->editsidetype,PLT_EXTENDED);

 while(mpxplay_diskdrive_textfile_readline(listfile,readbuf,sizeof(readbuf)-1)){
  datap=pds_strchr(readbuf,'=');
  if(!datap)
   continue;
  *datap++=0;

  if(pds_strncmp(readbuf,"File",sizeof("File")-1)==0){
   if(pei && !(pei->infobits&PEIF_ENABLED)) // prev file has no Length line
    funcbit_enable(psi->editsidetype,PLT_EXTENDINC);
   if(filtermask && !pds_utf8_filename_wildcard_cmp(datap,filtermask)){
    pei=NULL;
    continue;
   }
   pds_filename_conv_slashes_to_local(datap);
   pds_filename_build_fullpath(fullname,loaddir,datap);
   if(uselfn&USELFN_AUTO_SFN_TO_LFN){
    sprintf(readbuf,"Loading list: %4d. (press ESC to stop)",++linecount);
    display_message(0,0,readbuf);
    pds_fullpath(fullname,fullname);
   }
#ifndef MPXPLAY_UTF8
   if(id3textconv&ID3TEXTCONV_FILENAME)
     mpxplay_playlist_textconv_by_texttypes(
        MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
        fullname,-1,fullname,sizeof(fullname)-2);
#endif
   pei=playlist_editlist_add_entry(psi);
   if(!pei)
    break;
   len=playlist_editlist_add_filename(psi,pei,fullname);
   if(len<=0){
    playlist_editlist_del_entry(psi,pei);
    if(len<0)
     break;
    pei=NULL;
   }
  }else if(pei){
   if(pds_strncmp(readbuf,"Title",sizeof("Title")-1)==0){
    if(loadid3tag&ID3LOADMODE_LIST){
     if(!pei->id3info[I3I_ARTIST] && !pei->id3info[I3I_TITLE]){
      char *pt=pds_strstr(datap," - "); // Winamp style
      if(!pt)
       pt=pds_strstr(datap," / ");      // Sonique style
      if(pt){
       pt[0]=0;  // end of artist
       pt+=3;    // begin of title
      }
#ifdef MPXPLAY_UTF8
      if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,datap,-1)>0)
       funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#else
      len=mpxplay_playlist_textconv_by_texttypes(
        MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
        datap,-1,fullname,sizeof(fullname)-2);
      if(len>0)
       if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,fullname,len)>0)
        funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#endif
      if(pt){
#ifdef MPXPLAY_UTF8
       if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,pt,-1)>0)
        funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#else
       len=mpxplay_playlist_textconv_by_texttypes(
        MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
        pt,-1,fullname,sizeof(fullname)-2);
       if(len>0)
        if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,fullname,len)>0)
         funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#endif
      }
     }
    }
   }else if(pds_strncmp(readbuf,"Length",sizeof("Length")-1)==0){
    int timesec=pds_atol(datap);
    if(timesec>=0){
     pei->timemsec=timesec*1000;
     funcbit_enable(pei->infobits,PEIF_ENABLED);
    }
   }
  }
 }
 mpxplay_diskdrive_textfile_close(listfile);
 return retcode;
}

//-------------------------------------------------------------------------

static int open_load_mxu(struct playlist_side_info *psi,char *listname,char *loaddir,char *filtermask)
{
 void *listfile;
 int len,retcode=MPXPLAY_ERROR_OK;
 struct playlist_entry_info *pei;
 unsigned int timesec,linecount=0,linetype;
 char *listparts[MAX_ID3LISTPARTS+2];
 char readbuf[LOADLIST_MAX_LINELEN],linetmp[LOADLIST_MAX_LINELEN];
 char fullname[LOADLIST_MAX_LINELEN];

 if((listfile=mpxplay_diskdrive_textfile_open(NULL,listname,(O_RDONLY|O_TEXT)))==NULL)
  return MPXPLAY_ERROR_FILEHAND_CANTOPEN;

 if(psi->lastentry<=psi->firstsong)
  psi->savelist_textcodetype=mpxplay_diskdrive_textfile_config(listfile,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_GET_TEXTCODETYPE_SRC,NULL,NULL);

 funcbit_enable(psi->editsidetype,PLT_EXTENDED);

 while(mpxplay_diskdrive_textfile_readline(listfile,readbuf,sizeof(readbuf)-1)){
  if(!readbuf[0] || readbuf[0]=='#')
   continue;
  pds_memset(listparts,0,sizeof(listparts));
  pds_strcpy(linetmp,readbuf);
  pds_listline_slice(&(listparts[0]),"|||",linetmp); //mxu style from v1.59
  if(!listparts[0] || !listparts[1] || !listparts[2] || !listparts[3]){
   pds_memset(listparts,0,sizeof(listparts));
   pds_strcpy(linetmp,readbuf);
   pds_listline_slice(&(listparts[0]),"°±²",linetmp); //mxu style from v1.43 to v1.58
  }
  if(!listparts[0] || !listparts[1] || !listparts[2] || !listparts[3]){
   pds_memset(listparts,0,sizeof(listparts));
   pds_strcpy(linetmp,readbuf);
   pds_listline_slice(&(listparts[0]),"°:±",linetmp); // old mxu style (v1.42)
   linetype=143;
  }else
   linetype=0;
  if(!listparts[0] || !listparts[0][0])
   continue;
  if(filtermask && !pds_utf8_filename_wildcard_cmp(listparts[0],filtermask))
   continue;
  if(uselfn&USELFN_AUTO_SFN_TO_LFN){
   sprintf(fullname,"Loading list %4d (press ESC to stop)",++linecount);
   display_message(0,0,fullname);
   pds_fullpath(fullname,listparts[0]);
  }else
   pds_strcpy(fullname,listparts[0]);
#ifndef MPXPLAY_UTF8
  if(id3textconv&ID3TEXTCONV_FILENAME)
   len=mpxplay_playlist_textconv_by_texttypes(
    MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
    fullname,-1,fullname,sizeof(fullname)-2);
#endif
  pei=playlist_editlist_add_entry(psi);
  if(!pei)
   break;
  len=playlist_editlist_add_filename(psi,pei,fullname);
  if(len<=0){
   playlist_editlist_del_entry(psi,pei);
   if(len<0)
    break;
   continue;
  }
  if(loadid3tag&ID3LOADMODE_LIST){
   if(listparts[1] && listparts[1][0]){
#ifdef MPXPLAY_UTF8
    if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,listparts[1],-1)>0)
     funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#else
    len=mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
     listparts[1],-1,fullname,sizeof(fullname)-2);
    if(len>0)
     if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,fullname,len)>0)
      funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#endif
   }
   if(listparts[2] && listparts[2][0]){
#ifdef MPXPLAY_UTF8
    if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,listparts[2],-1)>0)
     funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#else
    len=mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
     listparts[2],-1,fullname,sizeof(fullname)-2);
    if(len>0)
     if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,fullname,len)>0)
      funcbit_enable(pei->infobits,PEIF_ID3EXIST);
#endif
   }
  }
  if(listparts[3] && listparts[3][0]){
   if(linetype==143){
    funcbit_enable(pei->infobits,PEIF_ENABLED);
   }else{
    timesec=pds_atol16(listparts[3]);
    if(timesec&MXUFLAG_ENABLED){
     pei->timemsec=(timesec&MXUFLAG_TIMEMASK)*1000;
     funcbit_enable(pei->infobits,PEIF_ENABLED);
    }else{
     pei->timemsec=0;
     playlist_loadlist_get_header_by_ext(psi,pei,pei->filename);
    }
   }
  }
 }
 mpxplay_diskdrive_textfile_close(listfile);
 return retcode;
}

//------------------------------------------------------------------------
// FPL loading (only filenames without extended informations)
// for Foobar2000 v1.0.1 saved fpl files

typedef struct fpl_main_header_s{
 unsigned long a;
 unsigned long b;
 unsigned long c;
 unsigned long e;
 unsigned long size_of_entries;
}fpl_main_header_s;

static int open_load_fpl(struct playlist_side_info *psi,char *listname,char *loaddir,char *filtermask)
{
 void *listfile;
 int len,retcode=MPXPLAY_ERROR_FILEHAND_CANTOPEN;
 struct playlist_entry_info *pei;
 unsigned long bytecount=0,filecount=0;
 struct fpl_main_header_s fpl_mainhead;
 char *s,readbuf[LOADLIST_MAX_LINELEN],fullname[LOADLIST_MAX_LINELEN];

 if((listfile=mpxplay_diskdrive_textfile_open(NULL,listname,(O_RDONLY|O_BINARY)))==NULL)
  return retcode;

 len=mpxplay_diskdrive_textfile_readline(listfile,(char *)&fpl_mainhead,sizeof(struct fpl_main_header_s));
 if(len<sizeof(struct fpl_main_header_s))
  goto err_out_fpl;
 mpxplay_diskdrive_textfile_config(listfile,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_SET_SEARCHSTRZ_ENABLE,NULL,NULL);

 do{
  len=mpxplay_diskdrive_textfile_readline(listfile,readbuf,sizeof(readbuf)-1);
  if(!len)
   break;
  bytecount+=len+1;
  if(bytecount>=fpl_mainhead.size_of_entries)
   break;
  s=&readbuf[0];
  if((pds_strncmp(s,"file://",sizeof("file://")-1)!=0) && pds_strncmp(s,"http://",sizeof("http://")-1)!=0)
   continue;
  s+=sizeof("file://")-1;
  if(filtermask && !pds_utf8_filename_wildcard_cmp(s,filtermask))
   continue;
  pds_filename_conv_slashes_to_local(s);
  pds_filename_build_fullpath(fullname,loaddir,s);
  if(uselfn&USELFN_AUTO_SFN_TO_LFN){
   pds_fullpath(fullname,fullname);
   sprintf(readbuf,"Loading list %4d (press ESC to stop)",filecount++);
   display_message(0,0,readbuf);
  }
#ifndef MPXPLAY_UTF8
  if(id3textconv&ID3TEXTCONV_UTF_AUTO)
   mpxplay_playlist_textconv_by_texttypes(
    MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_UTF8,MPXPLAY_TEXTCONV_TYPE_MPXNATIVE),
    fullname,-1,fullname,sizeof(fullname)-2);
  if(id3textconv&ID3TEXTCONV_FILENAME)
   mpxplay_playlist_textconv_by_texttypes(
    MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
    fullname,-1,fullname,sizeof(fullname)-2);
#endif
  pei=playlist_editlist_add_entry(psi);
  if(!pei)
   break;
  len=playlist_editlist_add_filename(psi,pei,fullname);
  if(len<=0){
   playlist_editlist_del_entry(psi,pei);
   if(len<0)
    break;
  }
 }while(1);
 retcode=MPXPLAY_ERROR_OK;

err_out_fpl:
 mpxplay_diskdrive_textfile_close(listfile);
 return retcode;
}

//-------------------------------------------------------------------------
// CUE loading
typedef struct cue_info_s{
 struct playlist_entry_info *pei;
 struct playlist_side_info *psi;
 unsigned int allcount,filecount,validfile,newfile;
 unsigned int trackcount,indexcount;
 char *loaddir,*filtermask;
 char track_artist[MAX_ID3LEN/2],track_title[MAX_ID3LEN/2];
 char album_artist[MAX_ID3LEN/2],album_title[MAX_ID3LEN/2];
 char album_date[128],album_genre[128],album_comment[128];
}cue_info_s;

typedef struct cue_handler_s{
 char *command;
 int (*handler)(cue_info_s *cui,char *comm_arg);
}cue_handler_s;

static char *cue_cut_string(char *comm_arg) // cut " at begin and end
{
 char *begin,*end;

 begin=pds_strchr(comm_arg,'\"');
 if(begin){
  end=pds_strrchr(begin+1,'\"');
  if(end){
   comm_arg=begin+1;
   *end=0;
  }
 }
 return comm_arg;
}

static void cue_check_index(cue_info_s *cui)
{
 struct playlist_entry_info *pei=cui->pei;
 struct playlist_side_info *psi=cui->psi;
 if(cui->trackcount<=1) // if file has only one track/index
  cui->trackcount=0;
 if(cui->indexcount<=1)
  cui->indexcount=0;
 if(pei && !cui->trackcount && !cui->indexcount && (pei>=psi->firstsong) && (pei<=psi->lastentry) && !pei->pstime && !pei->petime)  // then it's not indexed really
  funcbit_disable(pei->infobits,PEIF_INDEXED);
}

static void cue_set_id3info(cue_info_s *cui)
{
 struct playlist_entry_info *pei=cui->pei;
 struct playlist_side_info *psi=cui->psi;
 char strtmp[MAX_ID3LEN/2];

 if(!pei)
  return;
 if(!pei->id3info[I3I_ARTIST] && (cui->track_artist[0] || cui->album_artist[0] || (cui->album_title[0] && cui->track_title[0]))){
  char *p;
  if(cui->track_artist[0])
   p=&cui->track_artist[0];
  else if(cui->album_artist[0])
   p=&cui->album_artist[0];
  else
   p=&cui->album_title[0];
  if(playlist_editlist_add_id3_one(psi,pei,I3I_ARTIST,p,-1)>0)
   funcbit_enable(pei->infobits,PEIF_ID3EXIST);
 }

 if(!pei->id3info[I3I_TITLE]){
  if(cui->track_title[0]){
   if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,cui->track_title,-1)>0)
    funcbit_enable(pei->infobits,PEIF_ID3EXIST);
  }else if((pei->infobits&PEIF_INDEXED) || cui->trackcount){
   char *title;
   if(cui->album_title[0])
    title=&cui->album_title[0];
   else
    title=pds_getfilename_from_fullname(pei->filename);
   if(title){
    int len;
    if(cui->trackcount)
     len=snprintf(strtmp,sizeof(strtmp),"%s%s(track %d.)",title,((title[0])? " ":""),cui->trackcount);
    else
     len=snprintf(strtmp,sizeof(strtmp),"%s%s(index %d.)",title,((title[0])? " ":""),cui->indexcount);
    if(len>0)
     if(playlist_editlist_add_id3_one(psi,pei,I3I_TITLE,strtmp,len)>0)
      funcbit_enable(pei->infobits,PEIF_ID3EXIST);
   }
  }
 }

 if(cui->album_title[0] && !pei->id3info[I3I_ALBUM])
  playlist_editlist_add_id3_one(psi,pei,I3I_ALBUM,cui->album_title,-1);

 if(cui->album_date[0] && !pei->id3info[I3I_YEAR])
  playlist_editlist_add_id3_one(psi,pei,I3I_YEAR,cui->album_date,-1);

 if(cui->album_genre[0] && !pei->id3info[I3I_GENRE])
  playlist_editlist_add_id3_one(psi,pei,I3I_GENRE,cui->album_genre,-1);

 if(cui->album_comment[0] && !pei->id3info[I3I_COMMENT])
  playlist_editlist_add_id3_one(psi,pei,I3I_COMMENT,cui->album_comment,-1);

 cui->track_artist[0]=cui->track_title[0]=0;
}

static int cue_commandhand_file(cue_info_s *cui,char *comm_arg)
{
 char fullname[MAX_PATHNAMELEN];

 if((cui->indexcount>1) || (cui->trackcount>1)) // ??? obsolete main artist & title
  cui->album_artist[0]=cui->album_title[0]=0;

 if(cui->validfile && cui->newfile){
  cue_check_index(cui);
  cue_set_id3info(cui);
 }

 if(uselfn&USELFN_AUTO_SFN_TO_LFN){
  sprintf(fullname,"Loading list: %4d. (press ESC to stop)",++cui->allcount);
  display_message(0,0,fullname);
 }

 comm_arg=cue_cut_string(comm_arg);

 cui->validfile=cui->newfile=0;
 cui->trackcount=cui->indexcount=0;
 cui->track_artist[0]=cui->track_title[0]=0;

 if(!cui->filtermask || pds_utf8_filename_wildcard_cmp(comm_arg,cui->filtermask)){
  struct playlist_entry_info *pei;
  struct playlist_side_info *psi=cui->psi;
  int len;
  pds_filename_conv_slashes_to_local(comm_arg);
  pds_filename_build_fullpath(fullname,cui->loaddir,comm_arg);
  if(uselfn&USELFN_AUTO_SFN_TO_LFN)
   pds_fullpath(fullname,fullname);
#ifndef MPXPLAY_UTF8
  if(id3textconv&ID3TEXTCONV_FILENAME)
   mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_CHAR),
     fullname,-1,fullname,sizeof(fullname)-2);
#endif
  cui->pei=pei=playlist_editlist_add_entry(psi);
  if(!pei)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  len=playlist_editlist_add_filename(psi,pei,fullname);
  if(len<=0){
   playlist_editlist_del_entry(psi,pei);
   cui->pei=NULL;
   if(len<0)
    return MPXPLAY_ERROR_INFILE_MEMORY;
  }else
   cui->validfile=cui->newfile=1;
 }

 cui->filecount++;

 return MPXPLAY_ERROR_OK;
}

static int cue_commandhand_track(cue_info_s *cui,char *comm_arg)
{
 if(cui->trackcount && !cui->indexcount) // if not 1st track
  cue_set_id3info(cui); // then close prev track infos
 if(cui->validfile){
  cui->trackcount++;
  cui->track_artist[0]=cui->track_title[0]=0;
  cui->indexcount=0;
 }
 return MPXPLAY_ERROR_OK;
}

static int cue_commandhand_performer(cue_info_s *cui,char *comm_arg)
{
 if(loadid3tag&ID3LOADMODE_LIST){
  char *destp;
  int len;

  if(!cui->filecount) // PERFORMER before FILE
   destp=&cui->album_artist[0];
  else
   destp=&cui->track_artist[0];

  comm_arg=cue_cut_string(comm_arg);

#ifdef MPXPLAY_UTF8
  len=pds_strncpy(destp,comm_arg,MAX_ID3LEN/2-2);
#else
  len=mpxplay_playlist_textconv_by_texttypes(
   MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
   comm_arg,-1,destp,MAX_ID3LEN/2-2);
#endif
  if(len>=0)
   destp[len]=0;
 }

 return MPXPLAY_ERROR_OK;
}

static int cue_commandhand_title(cue_info_s *cui,char *comm_arg)
{
 if(loadid3tag&ID3LOADMODE_LIST){
  char *destp;
  int len;

  if(!cui->filecount)        // TITLE before FILE
   destp=&cui->album_title[0];
  else
   destp=&cui->track_title[0];

  comm_arg=cue_cut_string(comm_arg);

#ifdef MPXPLAY_UTF8
  len=pds_strncpy(destp,comm_arg,MAX_ID3LEN/2-2);
#else
  len=mpxplay_playlist_textconv_by_texttypes(
   MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXNATIVE,MPXPLAY_TEXTCONV_TYPE_MPXPLAY),
   comm_arg,-1,destp,MAX_ID3LEN/2-2);
#endif
  if(len>=0)
   destp[len]=0;
 }

 return MPXPLAY_ERROR_OK;
}

static int cue_commandhand_index(cue_info_s *cui,char *comm_arg)
{
 struct playlist_entry_info *pei=cui->pei;
 struct playlist_side_info *psi=cui->psi;
 struct playlist_entry_info *plx;
 unsigned long hextime,pt;

 if(!cui->validfile || !pei) // first we need a filename (we haven't got it) ...
  return MPXPLAY_ERROR_OK;

 plx=(cui->newfile)? NULL:pei;

 pt=PDS_GETB_LE16(comm_arg);
 if((pt==PDS_GET4C_LE32('E','E',0,0)) || (pt==PDS_GET4C_LE32('0','0',0,0))){ // end of the last index or length of the track
  if(plx){
   comm_arg+=2;          // skip EE/00
   while(*comm_arg==' ') // skip spaces after EE/00
    comm_arg++;
   hextime=pds_strtime_to_hextime(comm_arg,1); // convert MM:SS:FF to a hex number (xxMMSSFF)
   plx->petime=((hextime>>16)&0x3fff)*60*1000+((hextime>>8)&0xff)*1000+((hextime&0xff)*1000+75/2)/75; // convert hex number to millisecs
   if(pt==PDS_GET4C_LE32('0','0',0,0))
    plx->timemsec=plx->petime;
   funcbit_enable(plx->infobits,PEIF_ENABLED);
  }
 }else{
  if(!cui->trackcount || !cui->indexcount){ // !!! to merge indexes in one track (or using INDEXes only without TRACKs)

   while((*comm_arg!=' ') && (*comm_arg!=0)) // skip index number
    comm_arg++;
   pt=0;
   if(*comm_arg){
    while(*comm_arg==' ')                    // skip spaces after index number
     comm_arg++;
    hextime=pds_strtime_to_hextime(comm_arg,1); // convert MM:SS:FF to a hex number (xxMMSSFF)
    pt=((hextime>>16)&0x3fff)*60*1000+((hextime>>8)&0xff)*1000+((hextime&0xff)*1000+75/2)/75; // convert hex number to millisecs
   }

   if(!cui->newfile){
    int len;
    if(!plx->petime) // if no INDEX EE
     if(pt>plx->pstime)// probably sequential indexes
      plx->petime=pt;   // then end of prev index = start of curr index
    if(plx->petime)
     funcbit_enable(plx->infobits,(PEIF_ENABLED|PEIF_INDEXED));
    cui->pei=pei=playlist_editlist_add_entry(psi); // create a new (index) entry
    if(!pei)
     goto err_out_index;
    len=playlist_editlist_add_filename(psi,pei,plx->filename); // copy filename from previos track/index
    if(len<=0){
     playlist_editlist_del_entry(psi,pei);
     cui->pei=NULL;
     goto err_out_index;
    }
   }
   pei->pstime=pt; // set the start of index
   if(pt)
    funcbit_enable(pei->infobits,PEIF_INDEXED);

   cui->allcount++;
   cui->indexcount++;
   if((pei->infobits&PEIF_INDEXED) || cui->track_title[0])
    cue_set_id3info(cui);
  }
  cui->newfile=0;
 }

 return MPXPLAY_ERROR_OK;

err_out_index:
 cui->newfile=cui->validfile=0;
 return MPXPLAY_ERROR_INFILE_MEMORY;
}

// for backward Mpxplay compatibility only
static int cue_commandhand_length(cue_info_s *cui,char *comm_arg)
{
 struct playlist_entry_info *pei=cui->pei;
 unsigned long hextime;

 if(!cui->validfile || !pei) // first we need a filename (we haven't got it) ...
  return MPXPLAY_ERROR_OK;

 hextime=pds_strtime_to_hextime(comm_arg,1); // convert MM:SS:FF to a hex number (xxMMSSFF)
 pei->timemsec=((hextime>>16)&0x3fff)*60*1000+((hextime>>8)&0xff)*1000+((hextime&0xff)*1000+75/2)/75; // convert hex number to millisecs (with rounding?)
 if(pei->timemsec)
  funcbit_enable(pei->infobits,PEIF_ENABLED);

 return MPXPLAY_ERROR_OK;
}

// detto
static int cue_commandhand_mpxpeflags(cue_info_s *cui,char *comm_arg)
{
 struct playlist_entry_info *pei=cui->pei;
 unsigned long flags;

 if(!cui->validfile || !pei) // first we need a filename (we haven't got it) ...
  return MPXPLAY_ERROR_OK;

 funcbit_disable(pei->infobits,PEIF_CUESAVEMASK);
 flags=pds_atol16(comm_arg);
 funcbit_copy(pei->infobits,flags,(PEIF_CUESAVEMASK&(~PEIF_RNDPLAYED)));
 if((flags&PEIF_RNDPLAYED) && playrand)
  playlist_randlist_pushq(cui->psi,pei);

 return MPXPLAY_ERROR_OK;
}

static int cue_commandhand_rem(cue_info_s *cui,char *comm_arg)
{
 struct playlist_entry_info *pei=cui->pei,*plx;
 struct playlist_side_info *psi=cui->psi;
 char *data,*next;
 unsigned long year,month,day,hour,minute;

 if(pds_strncmp(comm_arg,"MPXPINFO",sizeof("MPXPINFO")-1)==0){
  if(!cui->validfile || !pei) // first we need a filename (we haven't got it) ...
   return MPXPLAY_ERROR_OK;

  comm_arg+=sizeof("MPXPINFO");

  do{
   next=pds_strchr(comm_arg,';');
   if(next)
    *next++=0;
   while(*comm_arg==' ') comm_arg++;
   if(!*comm_arg)
    break;
   data=pds_strchr(comm_arg,'=');
   if(data && data[1]){
    unsigned long type=PDS_GETB_LE32(comm_arg),diff;
    data++;
    switch(type){
     case PDS_GET4C_LE32('F','I','D','A'):{
      pds_fdate_t *d=&pei->filedate;
      sscanf(data,"%4d%2d%2d%2d%2d",&year,&month,&day,&hour,&minute);
      d->year=year-1980;d->month=month;d->day=day;d->hours=hour,d->minutes=minute;
      break;}
#ifdef MPXPLAY_FSIZE64
     case PDS_GET4C_LE32('F','I','S','I'):pei->filesize=pds_atoi64(data);break;
#else
     case PDS_GET4C_LE32('F','I','S','I'):pei->filesize=pds_atol(data);break;
#endif
     case PDS_GET4C_LE32('L','N','M','S'):
      pei->timemsec=pds_atol(data);
      funcbit_enable(pei->infobits,PEIF_ENABLED);
      break;
     case PDS_GET4C_LE32('I','N','D','B'):
      minute=pds_atol(data);
      if(minute>pei->pstime)
       diff=minute-pei->pstime;
      else
       diff=pei->pstime-minute;
      if(!pei->pstime || (diff<150)){ // !!! INDEX command has higher priority (INDB value has to be in precision loss 1/75s) (at the case of manual cue modification)
       if(cui->indexcount){
        plx=pei-1;
        if((plx>=psi->firstentry) && (plx->infobits&PEIF_INDEXED) && plx->petime && (plx->petime==pei->pstime))
         plx->petime=minute;
       }
       pei->pstime=minute;
      }
      funcbit_enable(pei->infobits,(PEIF_ENABLED|PEIF_INDEXED));
      break;
     case PDS_GET4C_LE32('I','N','D','E'):
      pei->petime=pds_atol(data);
      funcbit_enable(pei->infobits,(PEIF_ENABLED|PEIF_INDEXED));
      break;
     case PDS_GET4C_LE32('P','E','I','F'):{
      unsigned long flags=pds_atol16(data);
      funcbit_disable(pei->infobits,PEIF_CUESAVEMASK);
      funcbit_copy(pei->infobits,flags,(PEIF_CUESAVEMASK&(~PEIF_RNDPLAYED)));
      if((flags&PEIF_RNDPLAYED) && playrand){
       struct playlist_entry_info *le=psi->lastentry;
       psi->lastentry=pei;
       playlist_randlist_pushq(psi,pei);
       psi->lastentry=le;
      }
      break;}
    }
   }
   comm_arg=next;
  }while(comm_arg);

 }else if(pds_strncmp(comm_arg,"DATE",sizeof("DATE")-1)==0){
  comm_arg+=sizeof("DATE");
  pds_strncpy(cui->album_date,cue_cut_string(comm_arg),sizeof(cui->album_date)-1);
  cui->album_date[sizeof(cui->album_date)-1]=0;
 }else if(pds_strncmp(comm_arg,"GENRE",sizeof("GENRE")-1)==0){
  comm_arg+=sizeof("GENRE");
  pds_strncpy(cui->album_genre,cue_cut_string(comm_arg),sizeof(cui->album_genre)-1);
  cui->album_genre[sizeof(cui->album_genre)-1]=0;
 }else if(pds_strncmp(comm_arg,"COMMENT",sizeof("COMMENT")-1)==0){
  comm_arg+=sizeof("COMMENT");
  pds_strncpy(cui->album_comment,cue_cut_string(comm_arg),sizeof(cui->album_comment)-1);
  cui->album_comment[sizeof(cui->album_comment)-1]=0;
 }

 return MPXPLAY_ERROR_OK;
}

static struct cue_handler_s cue_handlers[]=
{
 {"FILE",     &cue_commandhand_file},
 {"TRACK",    &cue_commandhand_track},
 {"PERFORMER",&cue_commandhand_performer},
 {"TITLE",    &cue_commandhand_title},
 {"INDEX",    &cue_commandhand_index},
 {"LENGTH",   &cue_commandhand_length},
 {"MPXPEFLAGS",&cue_commandhand_mpxpeflags},
 {"REM",      &cue_commandhand_rem},
 {NULL,NULL}
};

static int open_load_cue(struct playlist_side_info *psi,char *listname,char *loaddir,char *filtermask)
{
 cue_info_s *cui;
 void *listfile;
 char strtmp[LOADLIST_MAX_LINELEN];

 if((listfile=mpxplay_diskdrive_textfile_open(NULL,listname,(O_RDONLY|O_TEXT)))==NULL)
  return MPXPLAY_ERROR_FILEHAND_CANTOPEN;

 funcbit_enable(psi->editsidetype,(PLT_EXTENDED|PLT_EXTENDINC)); // !!!

 if(psi->lastentry<=psi->firstsong)
  psi->savelist_textcodetype=mpxplay_diskdrive_textfile_config(listfile,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_GET_TEXTCODETYPE_SRC,NULL,NULL);

 cui=calloc(1,sizeof(cue_info_s));
 if(!cui)
  goto err_out_cue;
 cui->psi=psi;
 cui->loaddir=loaddir;
 cui->filtermask=filtermask;

 while(mpxplay_diskdrive_textfile_readline(listfile,strtmp,sizeof(strtmp)-1)){
  struct cue_handler_s *cuh=&cue_handlers[0];
  char *commp=&strtmp[0],*argp;

  while(*commp==' ') // skip spaces before command
   commp++;

  argp=commp;
  while((*argp!=' ') && (*argp!=0)) // skip command (search for the argument)
   argp++;

  if(*argp!=0){
   *argp=0;
   argp++;
   while(*argp==' ')  // skip spaces before argument
    argp++;
   do{
    if(pds_stricmp(commp,cuh->command)==0){
     if(cuh->handler(cui,argp)<0)
      goto err_out_cue;
     break;
    }
    cuh++;
   }while(cuh->command);
  }
 }

 if(cui->validfile && cui->newfile){
  cue_check_index(cui);
  cue_set_id3info(cui); // if last FILE entry has no TRACK nor INDEX
 }

err_out_cue:
 mpxplay_diskdrive_textfile_close(listfile);
 if(cui)
  free(cui);

 return MPXPLAY_ERROR_OK;
}

//-------------------------------------------------------------------------
static int open_load_asx(struct playlist_side_info *psi,char *listname,char *loaddir,char *filtermask)
{
 void *listfile;
 int len,retcode=MPXPLAY_ERROR_FILEHAND_CANTOPEN;
 struct playlist_entry_info *pei;
 char *s,*e,readbuf[LOADLIST_MAX_LINELEN];//,fullname[LOADLIST_MAX_LINELEN];

 if((listfile=mpxplay_diskdrive_textfile_open(NULL,listname,(O_RDONLY|O_BINARY)))==NULL)
  return retcode;

 mpxplay_diskdrive_textfile_config(listfile,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_SET_SEARCHSTRZ_ENABLE,NULL,NULL);

 do{
  len = mpxplay_diskdrive_textfile_readline(listfile,readbuf,sizeof(readbuf)-1);
  if(!len)
   break;
  s = strstr(readbuf, "<ref href=\"");
  if(!s)
   continue;
  s += sizeof("<ref href=\"") - 1 ;
  e = pds_strchr(s, '\"');
  if(!e)
   continue;
  *e = 0;
  //pds_filename_conv_slashes_to_local(s);
  //pds_filename_build_fullpath(fullname,loaddir,s);
  pei = playlist_editlist_add_entry(psi);
  if(!pei)
   break;
  len = playlist_editlist_add_filename(psi, pei, s);
  if(len <= 0){
   playlist_editlist_del_entry(psi, pei);
   if(len < 0)
    break;
  }
 }while(1);

 retcode = MPXPLAY_ERROR_OK;

 mpxplay_diskdrive_textfile_close(listfile);
 return retcode;
}

//-------------------------------------------------------------------------
// assign playlist entrytype/id3-name
unsigned int playlist_loadlist_get_header_by_ext(struct playlist_side_info *psi,struct playlist_entry_info *pei,char *filename)
{
 if(!playlist_loadsub_check_extension(psi,filename))
  return 0;

 if((desktopmode&DTM_EDIT_LOADLIST) && (psi->editsidetype&PLT_DIRECTORY) && !(psi->psio->editsidetype&PLT_DIRECTORY))
  pei->entrytype=DFT_PLAYLIST;
 else
  pei->entrytype=DFT_SUBLIST;
 if(!pei->id3info[I3I_ARTIST] && !pei->id3info[I3I_TITLE])
  switch(pei->entrytype){
   case DFT_PLAYLIST:playlist_editlist_add_id3_one(psi,pei,I3I_DFT_STORE,DFTSTR_PLAYLIST,sizeof(DFTSTR_PLAYLIST)-1);break;
   case DFT_SUBLIST :playlist_editlist_add_id3_one(psi,pei,I3I_DFT_STORE,DFTSTR_SUBLIST,sizeof(DFTSTR_SUBLIST)-1);break;
  }
 return 1;
}

//-------------------------------------------------------------------------
void playlist_loadlist_load_autosaved_list(struct playlist_side_info *psi)
{
 if((playlistsave&PLST_AUTO) && !(psi->editsidetype&PLT_ENABLED)){
  unsigned int len;
  char filename[MAX_PATHNAMELEN];
  pds_getpath_from_fullname(filename,freeopts[OPT_PROGNAME]);
  len=pds_strlen(filename);
  if(len && (filename[len-1]!=PDS_DIRECTORY_SEPARATOR_CHAR))
   len+=pds_strcpy(&filename[len],PDS_DIRECTORY_SEPARATOR_STR);
  pds_strcpy(&filename[len],playlist_savelist_get_savename(playlistsave));
  if(playlist_loadlist_mainload(psi,filename,0,NULL))
   //funcbit_enable(psi->editloadtype,PLL_LOADLIST);
   playlist_loadsub_setnewinputfile(psi,filename,PLL_LOADLIST);// for ctrl-R
  //}
 }
}
