//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (C) copyright 1998-2011 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:playlist save

#include "mpxplay.h"
#include "control\control.h"
#include "display\display.h"
#include "diskdriv\diskdriv.h"

#define MPXP_SAVELIST_RETCODE_OK         MPXPLAY_ERROR_FILEHAND_OK
#define MPXP_SAVELIST_RETCODE_ERROR      -1024  // unknown error
#define MPXP_SAVELIST_RETCODE_NONSTANDARD_CUE 4

#define MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC     1
#define MPXP_SAVELIST_SWITCHBIT_REMOTEFULLPATH 2
#define MPXP_SAVELIST_SWITCHBIT_ALLFULLPATH    4

#ifdef MPXPLAY_UTF8
#define MPXP_SAVELIST_SWITCHBITS_DEFAULT (MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC|MPXP_SAVELIST_SWITCHBIT_REMOTEFULLPATH)
#else
#define MPXP_SAVELIST_SWITCHBITS_DEFAULT MPXP_SAVELIST_SWITCHBIT_REMOTEFULLPATH
#endif

#define LOADDIR_MAX_LOCAL_DRIVES   ('Z'-'A'+1)

extern char *m3usavename,*mxusavename,*cuesavename;
extern mpxp_uint32_t mpxplay_playlistcontrol;
extern unsigned int playrand,desktopmode,playlistload,refdisp;
static mpxp_uint32_t savelist_switch_config=MPXP_SAVELIST_SWITCHBITS_DEFAULT;

static char *savelist_get_relative_filename(char *outbuf,unsigned int buflen,char *filename,char *path,unsigned int pathlen)
{
 char csave,*rp=filename;
 struct mpxplay_diskdrive_data_s mdds_tmp;

 csave=filename[pathlen];
 filename[pathlen]=0;
 if(!funcbit_test(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_ALLFULLPATH) && (pds_utf8_stricmp(filename,path)==0)){  // if path of startdir and save-dir is the same
  if(pathlen==(sizeof(PDS_DIRECTORY_ROOTDIR_STR)-1))
   rp=&filename[pathlen];
  else
   rp=&filename[pathlen+1];  // save relative to currdir (subdir\filename.mp3)
  filename[pathlen]=csave;
 }else{               // save with full path      ([d:]\subdir\filename.mp3)
  int drivenum_src,drivenum_dest;
  filename[pathlen]=csave;
  drivenum_src=pds_getdrivenum_from_path(filename);
  if(drivenum_src<0){ // a non local (virtual) filename (like ftp://)
   mdds_tmp.mdfs=mpxplay_diskdrive_search_driver(filename);
   if(mdds_tmp.mdfs){
    mdds_tmp.drive_data=NULL;
    if(mpxplay_diskdrive_drive_config(&mdds_tmp,MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_REALLYFULLPATH,outbuf,filename)==MPXPLAY_DISKDRIV_CFGERROR_SET_OK)
     return outbuf;
   }
   goto sgrf_out;
  }
  drivenum_dest=pds_getdrivenum_from_path(path);
  if(drivenum_src==drivenum_dest){ // drives are same
   if(!funcbit_test(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_ALLFULLPATH) || (drivenum_dest>=LOADDIR_MAX_LOCAL_DRIVES)) // we don't write the drive letter on remote drives
    rp=&filename[2];   // save relative to rootdir (\subdir\filename.mp3)
   goto sgrf_out;
  }
  if(funcbit_test(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_REMOTEFULLPATH)){
   struct mpxplay_diskdrive_data_s *mdds_src=playlist_loaddir_drivenum_to_drivemap(drivenum_src);
   struct mpxplay_diskdrive_data_s *mdds_dest=playlist_loaddir_drivenum_to_drivemap(drivenum_dest);
   if(mdds_src && mdds_dest && (mdds_src!=mdds_dest))
    if(mpxplay_diskdrive_drive_config(mdds_src,MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_REALLYFULLPATH,outbuf,filename)==MPXPLAY_DISKDRIV_CFGERROR_SET_OK)
     return outbuf;
  }
 }
sgrf_out:
 pds_strncpy(outbuf,rp,buflen);
 outbuf[buflen-1]=0;
 return outbuf;
}

static void savelist_conv_forbidden_char(char *str,char fc,char nc)
{
 if(!str)
  return;
 do{
  char c=str[0];
  if(!c)
   break;
  if(c==fc)
   c=nc;
  *str++=c;
 }while(1);
}

static int save_m3u_playlist(struct playlist_side_info *psi,void *fp,char *path,unsigned int list_type)
{
 struct playlist_entry_info *pei;
 unsigned int pathlen;
 char sout[MAX_ID3LEN];
#ifndef MPXPLAY_UTF8
 char cnvtmp1[MAX_ID3LEN],cnvtmp2[MAX_ID3LEN];
#endif

 pathlen=pds_strlen(path);

 if(pathlen){ // allways have to be
  if(list_type&PLST_EXTM3U){
   sprintf(sout,"#EXTM3U");
   if(mpxplay_diskdrive_textfile_writeline(fp,sout)<=0)
    return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
  }

  for(pei=psi->firstsong;pei<=psi->lastentry;pei++){
   if((list_type&PLST_EXTM3U) && (pei->infobits&PEIF_ENABLED)){
#ifdef MPXPLAY_UTF8
    snprintf(sout,sizeof(sout),"#EXTINF:%d,%.400s - %.400s",(pei->timemsec+500)/1000,
      ((pei->id3info[I3I_ARTIST])? pei->id3info[I3I_ARTIST]:""),
      ((pei->id3info[I3I_TITLE])? pei->id3info[I3I_TITLE]:""));
#else
    mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
     pei->id3info[I3I_ARTIST],-1,cnvtmp1,sizeof(cnvtmp1));
    mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
     pei->id3info[I3I_TITLE],-1,cnvtmp2,sizeof(cnvtmp2));
    snprintf(sout,sizeof(sout),"#EXTINF:%d,%.200s - %.200s",(pei->timemsec+500)/1000,
      cnvtmp1,cnvtmp2);
#endif
    if(mpxplay_diskdrive_textfile_writeline(fp,sout)<=0)
     return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
   }
   savelist_get_relative_filename(sout,sizeof(sout),pei->filename,path,pathlen);
   if(mpxplay_diskdrive_textfile_writeline(fp,sout)<0)
    return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
  }
 }else{ // ???
  for(pei=psi->firstsong;pei<=psi->lastentry;pei++)
   if(mpxplay_diskdrive_textfile_writeline(fp,pei->filename)<0) // save with full path
    return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
 }
 return MPXP_SAVELIST_RETCODE_OK;
}

static int save_mxu_playlist(struct playlist_side_info *psi,void *fp)
{
 struct playlist_entry_info *pei;
 char cnvtmp1[MAX_ID3LEN],cnvtmp2[MAX_ID3LEN],sout[MAX_ID3LEN];

 for(pei=psi->firstsong;pei<=psi->lastentry;pei++){
  cnvtmp1[0]=0;
  cnvtmp2[0]=0;
  if(pei->id3info[I3I_ARTIST]){
   pds_strncpy(cnvtmp1,pei->id3info[I3I_ARTIST],220);
   cnvtmp1[220]=0;
   savelist_conv_forbidden_char(cnvtmp1,'|',',');
  }
  if(pei->id3info[I3I_TITLE]){
   pds_strncpy(cnvtmp2,pei->id3info[I3I_TITLE],220);
   cnvtmp2[220]=0;
   savelist_conv_forbidden_char(cnvtmp2,'|',',');
  }
#ifdef MPXPLAY_UTF8
  snprintf(sout,sizeof(sout),"%.500s|%.220s|%.220s|%8.8X",(pei->filename)? pei->filename:"",
   cnvtmp1,cnvtmp2,(pei->infobits&PEIF_ENABLED)? (MXUFLAG_ENABLED|(((pei->timemsec+500)/1000)&MXUFLAG_TIMEMASK)):0);
#else
  mpxplay_playlist_textconv_by_texttypes(
   MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
   cnvtmp1,-1,cnvtmp1,sizeof(cnvtmp1));
  mpxplay_playlist_textconv_by_texttypes(
   MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
   cnvtmp2,-1,cnvtmp2,sizeof(cnvtmp2));
  snprintf(sout,sizeof(sout),"%.300s|%.200s|%.200s|%8.8X",(pei->filename)? pei->filename:"",
   cnvtmp1,cnvtmp2,(pei->infobits&PEIF_ENABLED)? (MXUFLAG_ENABLED|(((pei->timemsec+500)/1000)&MXUFLAG_TIMEMASK)):0);
#endif
  if(mpxplay_diskdrive_textfile_writeline(fp,sout)<=0)
   return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
 }
 return MPXP_SAVELIST_RETCODE_OK;
}

static int save_cue_playlist(struct playlist_side_info *psi,void *fp,char *path,unsigned int fullinfo)
{
 struct playlist_entry_info *pei,*ppn;
 pds_fdate_t *d;
 unsigned int trackcount=1,nonstandard=0,len,pathlen=pds_strlen(path);
 unsigned int save_peif=(fullinfo && ((playrand==1) || (psi->editloadtype&PLL_FILTERED)))? 1:0;
 char cnvtmp[MAX_ID3LEN],lastfile[MAX_PATHNAMELEN],sout[MAX_ID3LEN];
 lastfile[0]=0;

 for(pei=psi->firstsong;pei<=psi->lastentry;pei++){
  if(!(pei->infobits&PEIF_INDEXED) || (pds_strcmp(pei->filename,lastfile)!=0)){
   if(!fullinfo){
    char *ext=pds_strrchr(pei->filename,'.');
    if(ext){
     ext++;
     if(pds_stricmp(ext,"wav")==0)
      ext="WAVE";
     else
      ext="MP3";
    }else
     ext="MP3";
    snprintf(sout,sizeof(sout),"FILE \"%s\" %s",savelist_get_relative_filename(cnvtmp,sizeof(cnvtmp),pei->filename,path,pathlen),ext);
   }else
    snprintf(sout,sizeof(sout),"FILE \"%s\"",savelist_get_relative_filename(cnvtmp,sizeof(cnvtmp),pei->filename,path,pathlen));
   if(mpxplay_diskdrive_textfile_writeline(fp,sout)<=0)
    return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
   pds_strcpy(lastfile,pei->filename);
  }
  if(!fullinfo){
   sprintf(sout," TRACK %2.2d AUDIO",trackcount++);
   if(mpxplay_diskdrive_textfile_writeline(fp,sout)<=0)
    return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
   if(trackcount>99)
    trackcount=99;
  }
  if(pei->id3info[I3I_ARTIST]){
   char *s;
#ifdef MPXPLAY_UTF8
   s=pei->id3info[I3I_ARTIST];
#else
   mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
     pei->id3info[I3I_ARTIST],-1,cnvtmp,sizeof(cnvtmp));
   s=&cnvtmp[0];
#endif
   savelist_conv_forbidden_char(s,'\"','\'');
   snprintf(sout,sizeof(sout),"  PERFORMER \"%.200s\"",s);
   mpxplay_diskdrive_textfile_writeline(fp,sout);
  }
  if(pei->id3info[I3I_TITLE]){
   char *s;
#ifdef MPXPLAY_UTF8
   s=pei->id3info[I3I_TITLE];
#else
   mpxplay_playlist_textconv_by_texttypes(
     MPXPLAY_TEXTCONV_TYPES_PUT(MPXPLAY_TEXTCONV_TYPE_MPXPLAY,MPXPLAY_TEXTCONV_TYPE_CHAR),
     pei->id3info[I3I_TITLE],-1,cnvtmp,sizeof(cnvtmp));
   s=&cnvtmp[0];
#endif
   savelist_conv_forbidden_char(s,'\"','\'');
   snprintf(sout,sizeof(sout),"  TITLE \"%.200s\"",s);
   mpxplay_diskdrive_textfile_writeline(fp,sout);
  }
  if(!fullinfo || (pei->infobits&PEIF_INDEXED)){
   //if(pei->infobits&PEIF_INDEXED){
   sprintf(sout,"  INDEX 01 %2.2d:%2.2d:%2.2d",(pei->pstime/60000),((pei->pstime/1000)%60),((pei->pstime%1000)*75/1000));
   mpxplay_diskdrive_textfile_writeline(fp,sout);
   //}else if(pei->infobits&PEIF_ENABLED){ // not supported by VLC nor Foobar
   // sprintf(sout,"  INDEX 00 %2.2d:%2.2d:%2.2d",(pei->timemsec/60000),((pei->timemsec/1000)%60),((pei->timemsec%1000)*75/1000));
   // mpxplay_diskdrive_textfile_writeline(fp,sout);
   //}
  }

  // saving Mpxplay's inside infos
  len=pds_strcpy(sout,"REM MPXPINFO ");
  d=&pei->filedate;
  if(d->month){
   sprintf(cnvtmp,"FIDA=%4.4d%2.2d%2.2d%2.2d%2.2d;",((unsigned long)d->year+1980),(unsigned long)d->month,(unsigned long)d->day,(unsigned long)d->hours,(unsigned long)d->minutes);
   len+=pds_strcpy(&sout[len],cnvtmp);
  }
  if(pei->filesize){
#ifdef MPXPLAY_FSIZE64
   sprintf(cnvtmp,"FISI=%lld;",pei->filesize);
#else
   sprintf(cnvtmp,"FISI=%d;",pei->filesize);
#endif
   len+=pds_strcpy(&sout[len],cnvtmp);
  }
  if(pei->infobits&PEIF_ENABLED){
   sprintf(cnvtmp,"LNMS=%d;",pei->timemsec);
   len+=pds_strcpy(&sout[len],cnvtmp);
  }
  if(pei->infobits&PEIF_INDEXED){
   if(pei->pstime){
    sprintf(cnvtmp,"INDB=%d;",pei->pstime);
    len+=pds_strcpy(&sout[len],cnvtmp);
   }
   ppn=pei+1;
   if((ppn>psi->lastentry) || !(ppn->infobits&PEIF_INDEXED) || !pei->petime || (pei->petime!=ppn->pstime) || (pds_utf8_stricmp(pei->filename,ppn->filename)!=0))
    ppn=NULL; // the next entry is not a continuous index
   if(pei->petime && (pei->petime!=pei->timemsec) && !ppn){
    sprintf(cnvtmp,"INDE=%d;",pei->petime);
    len+=pds_strcpy(&sout[len],cnvtmp);
    nonstandard=1;
   }
  }
  if(save_peif){
   sprintf(cnvtmp,"PEIF=%8.8X;",(pei->infobits&PEIF_CUESAVEMASK));
   len+=pds_strcpy(&sout[len],cnvtmp);
  }
  if(len>sizeof("REM MPXPINFO ")){
   sout[len-1]=0; // to clear last ';'
   if(mpxplay_diskdrive_textfile_writeline(fp,sout)<=0)
    return MPXPLAY_ERROR_FILEHAND_CANTWRITE;
  }
 }
 if(nonstandard)
  return MPXP_SAVELIST_RETCODE_NONSTANDARD_CUE;
 return MPXP_SAVELIST_RETCODE_OK;
}

char *playlist_savelist_get_savename(unsigned int list_type)
{
 switch(list_type&PLST_LISTS){
  case PLST_MXU:return mxusavename;
  case PLST_CUE:return cuesavename;
  default:return m3usavename;
 }
}

static void playlist_savelist_create_path_for_manualsavename(struct playlist_side_info *psi,char *path)
{
 path[0]=0;
 if(psi->editsidetype&PLT_DIRECTORY)
  pds_strcpy(path,psi->currdir);
 else if(psi->psio->editsidetype&PLT_DIRECTORY)
  pds_strcpy(path,psi->psio->currdir);
 else if(mpxplay_diskdrive_checkdir(psi->mdds,psi->currdir))
  pds_strcpy(path,psi->currdir);
 else
  playlist_loaddir_getcwd(psi->mdds,path,sizeof(path));

 if(!path[0])
  pds_getpath_from_fullname(path,freeopts[OPT_PROGNAME]);
}

static char *playlist_savelist_create_manual_savename(struct playlist_side_info *psi,unsigned int list_type,char *file_name,char *savename)
{
 char path[MAX_PATHNAMELEN];

 if(!savename)
  return savename;

 playlist_savelist_create_path_for_manualsavename(psi,path);

 if(file_name && file_name[0] && !pds_filename_wildcard_chk(file_name))
  pds_filename_build_fullpath(savename,path,pds_getfilename_from_fullname(file_name));
 else{
  char *updir;
  if(psi->editsidetype&PLT_DIRECTORY)
   updir=pds_getfilename_from_fullname(psi->currdir);
  else if(psi->psio->editsidetype&PLT_DIRECTORY)
   updir=pds_getfilename_from_fullname(psi->psio->currdir);
  else
   updir=NULL;
  if(updir && *updir){ // create playlistname from up-dir name
   pds_filename_build_fullpath(savename,path,updir);
   pds_strcat(savename,".m3u");
  }else
   pds_filename_build_fullpath(savename,path,playlist_savelist_get_savename(list_type));
 }

 return savename;
}

unsigned int playlist_savelist_gettype_from_filename(struct playlist_side_info *psi,char *filename)
{
 char *ext;
 if(pds_filename_wildcard_chk(filename)) // it's not a playlist-filename (ie: *.m3u)
  return 0;
 ext=pds_strrchr(filename,'.');
 if(ext){
  if(pds_stricmp(ext,".m3u")==0)
   return PLST_EXTM3U; // cannot detect M3U/EXTM3U diff
  if(pds_stricmp(ext,".mxu")==0)
   return PLST_MXU;
  if(pds_stricmp(ext,".cue")==0)
   return PLST_CUE;
 }
 return 0;
}

static int savelist_open_and_write_list(struct playlist_side_info *psi,char *filename,unsigned int list_type,unsigned int cuefullinfo)
{
 void *fp;
 struct mpxplay_diskdrive_data_s *mdds;
 int retcode=MPXP_SAVELIST_RETCODE_ERROR;
 long dest_textenctype;
 char path[MAX_PATHNAMELEN];

 mdds=playlist_loaddir_drivenum_to_drivemap(pds_getdrivenum_from_path(filename));
 if(!mdds)
  mdds=psi->mdds;

 fp=mpxplay_diskdrive_textfile_open(mdds,filename,(O_WRONLY|O_CREAT|O_TEXT));
 if(fp==NULL){
  retcode=MPXPLAY_ERROR_FILEHAND_CANTCREATE;
  goto err_out_open;
 }

 dest_textenctype=psi->savelist_textcodetype;
 if(!funcbit_test(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC))
  dest_textenctype=MPXPLAY_TEXTCONV_TYPE_CHAR;
 else if(dest_textenctype==MPXPLAY_TEXTCONV_TYPE_CHAR)
  dest_textenctype=MPXPLAY_TEXTCONV_TYPE_UTF8; // !!! default forced utftype

 mpxplay_diskdrive_textfile_config(fp,MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_SET_TEXTCODETYPE_DEST,((void *)&dest_textenctype),NULL);

 pds_getpath_from_fullname(path,filename);

 switch(list_type&PLST_LISTS){
  case PLST_MXU:retcode=save_mxu_playlist(psi,fp);break;
  case PLST_CUE:retcode=save_cue_playlist(psi,fp,path,cuefullinfo);break;
  default:retcode=save_m3u_playlist(psi,fp,path,list_type);break;
 }
 mpxplay_diskdrive_textfile_close(fp);

err_out_open:
 return retcode;
}

int playlist_savelist_save_playlist(struct mainvars *mvp,struct playlist_side_info *psi,char *file_name,unsigned int list_type)
{
 char filename[MAX_PATHNAMELEN];

 if(!list_type)
  return 0;
 if(!psi)
  psi=mvp->psil;
 if(!file_name){
  pds_filename_build_fullpath(filename,mpxplay_playlist_startdir(),playlist_savelist_get_savename(list_type));
  file_name=filename;
 }

 return savelist_open_and_write_list(psi,file_name,list_type,0);
}

int playlist_savelist_save_editedside(struct playlist_side_info *psi)
{
 int retcode=MPXP_SAVELIST_RETCODE_ERROR;
 char savename[16],fullname[MAX_PATHNAMELEN];

 sprintf(savename,"MPXP%1.1d%3.3d.CUE",psi->sidenum,psi->tabnum);
 pds_getpath_from_fullname(fullname,freeopts[OPT_PROGNAME]); // save in the directory of mpxplay.exe
 pds_filename_assemble_fullname(fullname,fullname,savename);

 if(!(psi->editsidetype&PLT_ENABLED) || ((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel)){
  pds_unlink(fullname);
  goto err_out_se;
 }

 if( !(psi->editloadtype&(PLL_CHG_ENTRY|PLL_CHG_MANUAL|PLL_CHG_FILTER))
  && !((psi->editloadtype&(PLL_CHG_LEN|PLL_CHG_ID3)) && (playlistload&PLL_RESTORED))
  && !(psi->editloadtype&(PLL_DIRSCAN|PLL_DRIVESCAN)) && !playrand )
  goto err_out_se;

 funcbit_disable(psi->editloadtype,(PLL_TYPE_LOAD|PLL_RESTORED)); // !!!

 savelist_switch_config=MPXP_SAVELIST_SWITCHBITS_DEFAULT;// !!!

 pds_strcpy(&psi->sublistnames[psi->sublistlevel][0],fullname);

 retcode=savelist_open_and_write_list(psi,fullname,PLST_CUE,1);

 if(retcode==MPXPLAY_ERROR_OK){
  if(!(psi->editsidetype&PLT_DIRECTORY))
   funcbit_enable(psi->editloadtype,PLL_LOADLIST);
  funcbit_enable(psi->editloadtype,PLL_RESTORED);
  pds_strcpy(psi->restored_filename,fullname);
 }

err_out_se:
 return retcode;
}

//-------------------------------------------------------------------------
struct manualsavelist_s{
 struct playlist_side_info *psi;
 int retcode,retcsend;
 char savelist_manual_savename[MAX_PATHNAMELEN];
#ifdef MPXPLAY_WIN32
 struct mpxp_credentials_s cri;
#endif
};

static void playlist_savelist_manualsave(struct manualsavelist_s *msi);

static void savelist_manualsave_dealloc(struct manualsavelist_s *msi)
{
#ifdef __DOS__
 //void *tw;
 //tw=display_textwin_openwindow_message(NULL,NULL,"Flushing disk caches ...");
 pds_drives_flush();
 //display_textwin_closewindow_message(tw);
#endif
 if(msi){
#ifdef MPXPLAY_WIN32
  playlist_diskfile_credentials_logoff(&msi->cri);
#endif
  free(msi);
 }
}

#ifdef MPXPLAY_WIN32
static unsigned int savelist_manualsave_credentials_init(struct manualsavelist_s *msi)
{
 struct mpxp_credentials_s *cri=&msi->cri;
 unsigned int do_cr;
 msi->retcsend=msi->retcode;
 cri->retcodep=&msi->retcsend; // to not overwrite efi->retcode
 cri->filename=&msi->psi->savelist_filename[0];
 cri->passdata=msi;
 cri->passfunc_retry=(void *)(playlist_savelist_manualsave);
 cri->passfunc_dealloc=(void *)savelist_manualsave_dealloc;
 do_cr=(msi->retcode==MPXPLAY_ERROR_FILEHAND_CANTCREATE)? 1:0;
 return playlist_diskfile_credentials_errorhand(cri,do_cr);
}
#endif

static void playlist_savelist_manualsave(struct manualsavelist_s *msi)
{
 struct playlist_side_info *psi=msi->psi;
 char *se,*fe,*fd,msg[MAX_PATHNAMELEN+80];

 if(psi->savelist_type){
  // correct file-extension for list_type (.M3U,.MXU,.CUE)
  se=playlist_savelist_get_savename(psi->savelist_type);
  se=pds_strrchr(se,'.');
  fd=pds_strrchr(psi->savelist_filename,PDS_DIRECTORY_SEPARATOR_CHAR);
  fe=pds_strrchr(psi->savelist_filename,'.');
  if(fe>fd) // the dot is in the filename
   pds_strcpy(fe,se);
  else
   pds_strcat(psi->savelist_filename,se);
 }else{
  // correct list_type for file-extension
  psi->savelist_type=playlist_savelist_gettype_from_filename(psi,psi->savelist_filename);
  if(!psi->savelist_type){
   fe=pds_strrchr(psi->savelist_filename,'.');
   sprintf(msg,"Invalid savelist filetype \"%s\" at\n",((fe)? (fe+1):"n/a"));
   pds_strcat(msg,pds_getfilename_from_fullname(psi->savelist_filename));
   pds_strcat(msg,"\n(use M3U,MXU,CUE file extensions)");
   display_textwin_openwindow_errormsg_ok(NULL,msg);
   goto err_out_finish;
  }
 }

 pds_strcpy(msg,"Saving playlist to\n");
 pds_strcat(msg,psi->savelist_filename);
 display_timed_message(msg);

 pds_sfn_limit(psi->savelist_filename);

 msi->retcode=playlist_savelist_save_playlist(psi->mvp,psi,psi->savelist_filename,(psi->savelist_type|PLST_MANUAL));
 if((msi->retcode==MPXP_SAVELIST_RETCODE_OK) || (msi->retcode==MPXP_SAVELIST_RETCODE_NONSTANDARD_CUE)){
  pds_strcpy(msg,"Playlist is saved to\n");
  pds_strcat(msg,psi->savelist_filename);
  display_timed_message(msg);
  playlist_editlist_updatesides_add_dft(psi->mvp,psi->savelist_filename,DFT_PLAYLIST);
  if(pds_utf8_stricmp(psi->savelist_filename,psi->sublistnames[psi->sublistlevel])==0)
   funcbit_disable(psi->editloadtype,PLL_CHG_ALL);
  else if(psi->mvp->editorsides_num_tabs[psi->sidenum]>1)
   funcbit_enable(refdisp,RDT_EDITOR);
  funcbit_disable(psi->editloadtype,PLL_RESTORED);
  playlist_loadsub_setnewinputfile(psi,psi->savelist_filename,PLL_LOADLIST);
  if((msi->retcode==MPXP_SAVELIST_RETCODE_NONSTANDARD_CUE) && !(mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_NOLISTWARNINGS)){
   pds_strcpy(msg,"Warning!\n The saved CUE contains non standard end-of-index (INDE) setting!\n");
   pds_strcat(msg,psi->savelist_filename);
   display_textwin_openwindow_errormsg_ok(NULL,msg);
  }
 }else{
#ifdef MPXPLAY_WIN32
  if(savelist_manualsave_credentials_init(msi))
   return;
#endif
  display_clear_timed_message();
  pds_strcpy(msg,"Couldn't save playlist to\n");
  pds_strcat(msg,psi->savelist_filename);
  display_textwin_openwindow_errormsg_ok(NULL,msg);
 }
err_out_finish:
 savelist_manualsave_dealloc(msi);
}

static display_textwin_button_t savelist_buttons[]={
 {""          ,0xffff}, // enter in edit-field executes the first button (savetype == extension of filename)
 {"[ EXTM3U ]",0x1265}, // 'e'
 {""          ,0x1245}, // 'E'
 {"[ M3U ]"   ,0x326d}, // 'm'
 {""          ,0x324d}, // 'M'
 {"[ CUE ]"   ,0x2e63}, // 'c'
 {""          ,0x2e43}, // 'C'
 {"[ MXU ]"   ,0x2d78}, // 'x'
 {""          ,0x2d58}, // 'X'
 {""          ,0x3c00}, // F2
 {"[ Cancel ]",KEY_ESC},// ESC
 {NULL,0}
};

static void manual_savelist_keyhand(struct manualsavelist_s *msi,unsigned int extkey)
{
 struct playlist_side_info *psi=msi->psi;
 char path[MAX_PATHNAMELEN];

 switch(extkey){
  case 0xffff:psi->savelist_type=0;break;
  case 0x1265:
  case 0x1245:psi->savelist_type=PLST_EXTM3U;break;
  case 0x326d:
  case 0x324d:psi->savelist_type=PLST_M3U;break;
  case 0x2e63:
  case 0x2e43:psi->savelist_type=PLST_CUE;break;
  case 0x2d78:
  case 0x2d58:psi->savelist_type=PLST_MXU;break;
  default:savelist_manualsave_dealloc(msi);return;
 }
 playlist_savelist_create_path_for_manualsavename(psi,path);
 pds_filename_build_fullpath(psi->savelist_filename,path,msi->savelist_manual_savename);
 playlist_savelist_manualsave(msi);
}

void playlist_savelist_manual_save(struct mainvars *mvp)
{
 void *tw;
 struct manualsavelist_s *msi;
 unsigned int listtype,texttype;
 struct playlist_side_info *psi;
 struct display_textwin_button_t *btsel;
 char strtmp[MAX_PATHNAMELEN],msg[50];

 msi=(struct manualsavelist_s *)calloc(1,sizeof(*msi));
 if(!msi)
  return;

 msi->psi=psi=mvp->psie;

 if(psi->savelist_filename[0]){
  playlist_savelist_create_manual_savename(psi,psi->savelist_type,psi->savelist_filename,msi->savelist_manual_savename);
 }else{ // 1st calling or after directory/sublist change
  char *inpfile=NULL;
  if(psi->sublistlevel)
   inpfile=psi->sublistnames[psi->sublistlevel];
  else if(psi==mvp->psil)
   inpfile=playlist_loadsub_getinputfile(psi);
  if(inpfile){
   listtype=playlist_savelist_gettype_from_filename(psi,inpfile);
   if(listtype){
    psi->savelist_type=listtype;
    if(psi->sublistlevel)
     pds_strcpy(msi->savelist_manual_savename,inpfile);
    else
     playlist_savelist_create_manual_savename(psi,psi->savelist_type,inpfile,msi->savelist_manual_savename);
   }
  }
  if(!msi->savelist_manual_savename[0]){
   if(!psi->savelist_type){
    psi->savelist_type=PLST_DEFAULT;
    psi->savelist_textcodetype=0;
   }
   playlist_savelist_create_manual_savename(psi,psi->savelist_type,psi->savelist_filename,msi->savelist_manual_savename);
  }
 }

 pds_sfn_limit(msi->savelist_manual_savename);

 texttype=psi->savelist_textcodetype;

 switch(psi->savelist_type&PLST_LISTS){
  case PLST_EXTM3U:btsel=&savelist_buttons[1];break;
  case PLST_CUE:btsel=&savelist_buttons[5];break;
  case PLST_MXU:btsel=&savelist_buttons[7];texttype=MPXPLAY_TEXTCONV_TYPE_CHAR;break;
  default:btsel=&savelist_buttons[3];
 }

 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_MSGCENTERALIGN," Savelist ",manual_savelist_keyhand,msi);
 sprintf(msg," Save playlist (%s side) to",((mvp->editorside_selected)? "RIGHT":"LEFT"));
 display_textwin_additem_msg_alloc(tw,0,0,-1,msg);
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGLEFTALIGN,0,1,-1,46,msi->savelist_manual_savename,MAX_PATHNAMELEN-2);
 //display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGLEFTALIGN,TEXTWIN_EDITFLAG_STARTEND,1,-1,46,msi->savelist_manual_savename,MAX_PATHNAMELEN-2);
 display_textwin_additem_separatorline(tw,-1);
 switch(texttype){
  case MPXPLAY_TEXTCONV_TYPE_UTF16LE:pds_strcpy(msg,"UTF-16LE");funcbit_enable(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC);break;
  case MPXPLAY_TEXTCONV_TYPE_UTF16BE:pds_strcpy(msg,"UTF-16BE");funcbit_enable(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC);break;
  case MPXPLAY_TEXTCONV_TYPE_UTF8:funcbit_enable(savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC);
  default:pds_strcpy(msg,"UTF-8"); // !!! default forced type (if user enables the switch)
 }
 sprintf(strtmp,"%s text encoding",msg);
 display_textwin_additem_switchline(tw,0,1,-1,&savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_UTFTEXTENC,strtmp);
 display_textwin_additem_switchline(tw,0,1,-1,&savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_REMOTEFULLPATH,"Full path for virtual filenames (0: -> ftp://)");
 display_textwin_additem_switchline(tw,0,1,-1,&savelist_switch_config,MPXP_SAVELIST_SWITCHBIT_ALLFULLPATH,"Full path for all filenames (else relative)");
 display_textwin_additem_separatorline(tw,-1);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,savelist_buttons,btsel);
 display_textwin_openwindow_items(tw,0,0,0);
}

// clear savelist filename at loading of new playlist (in directory browser)
void playlist_savelist_clear(struct playlist_side_info *psi)
{
 psi->savelist_type=0;
 psi->savelist_textcodetype=0;
 psi->savelist_filename[0]=0;
}
