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

//#define MPXPLAY_USE_DEBUGF 1
#define MPXPLAY_DEBUG_OUTPUT stdout

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

extern mpxp_uint32_t mpxplay_playlistcontrol;
extern char *drivescanletters,*id3tagset[I3I_MAX+1];
extern unsigned int outmode,playcontrol,playrand,playstartsong;
extern unsigned int refdisp,loadid3tag,writeid3tag,playlistload;
extern unsigned int displaymode,desktopmode,desktopmod_player,desktopmod_commander;
#ifdef MPXPLAY_GUI_CONSOLE
static void playlist_allocate_memworkarea(void);
extern unsigned int playlist_max_filenum_list;
static char *memworkarea;
static unsigned int playlist_max_filenum_browser,playstartsngdb2;
#endif
extern struct mainvars mvps;

static unsigned int playstartsngdb2;

void mpxplay_playlist_init(struct mainvars *mvp)
{
 struct playlist_side_info *psi0,*psi1;
 psi0=playlist_editlist_tabp_get(mvp,0);
 psi1=playlist_editlist_tabp_get(mvp,1);

#ifdef MPXPLAY_GUI_CONSOLE
 if(playlist_max_filenum_list){ // if static playlist memory

  if(playlist_max_filenum_list<512)
   playlist_max_filenum_list=512;
  if(playlist_max_filenum_list>99999999)
   playlist_max_filenum_list=99999999;

#ifdef __DOS__
  playlist_max_filenum_browser=playlist_max_filenum_list/PLAYLIST_MAXFILENUMDIV_DIR;
  if(playlist_max_filenum_browser<512)
   playlist_max_filenum_browser=512;
  if(playlist_max_filenum_browser>4096)
   playlist_max_filenum_browser=4096;
#else
  playlist_max_filenum_browser=playlist_max_filenum_list; // win32 version
#endif

  playlist_allocate_memworkarea();
  playlist_init_pointers(mvp);
 }else
#endif
 {
  unsigned int i;
  struct playlist_side_info *psi=mvp->psi0;
  for(i=0;i<PLAYLIST_MAX_SIDES;i++,psi++){
   struct playlist_entry_info *pei=playlist_editlist_add_entry(psi); // dynamic init
   playlist_editlist_del_entry(psi,pei);
  }
  mvp->aktfilenum=mvp->psil->firstsong;
  mvp->pei0=pds_calloc(1,sizeof(playlist_entry_info));
  if(!mvp->pei0)
   mpxplay_close_program(MPXERROR_XMS_MEM);
  funcbit_enable(mvp->pei0->infobits,PEIF_ALLOCATED);
 }
 mpxplay_playlist_textconv_init();
 playlist_sortlist_init(mvp);
 playlist_close_infile(mvp,mvp->fr_primary);
}

#ifdef MPXPLAY_GUI_CONSOLE
static void playlist_allocate_memworkarea(void)
{
 unsigned int allfilenums,pointerssize,filenamessize,id3memsize,memworkareasize;

 allfilenums  =playlist_max_filenum_browser+playlist_max_filenum_list;      //1999+9999=11998
 pointerssize =allfilenums*sizeof(struct playlist_entry_info);//  11998*64
 filenamessize=allfilenums*FILENAMELENGTH;                    // +11998*64
 id3memsize   =allfilenums*ID3LENGTH;                         // +11998*64
 memworkareasize=pointerssize+filenamessize+id3memsize+32768; // =2336384

 memworkarea=pds_malloc(memworkareasize);
 if(memworkarea==NULL)
  mpxplay_close_program(MPXERROR_XMS_MEM);
 pds_memset(memworkarea,0,memworkareasize);
}
#endif

void mpxplay_playlist_close(struct mainvars *mvp)
{
 playlist_id3list_close();
 mpxplay_playlist_textconv_close();
 playlist_loaddir_diskdrives_unmount();

#ifdef MPXPLAY_GUI_CONSOLE
 if(playlist_max_filenum_list) {
  if(memworkarea){
   pds_free(memworkarea);
   memworkarea=NULL;
  }
 } else
#endif
 {
  if(mvp->pei0){
   playlist_editlist_del_filename(NULL,mvp->pei0);
   playlist_editlist_del_id3_all(NULL,mvp->pei0);
   pds_free(mvp->pei0);
   mvp->pei0=NULL;
  }
 }
 playlist_editlist_tabs_close(mvp);
}

#ifdef MPXPLAY_GUI_CONSOLE
void playlist_init_pointers(struct mainvars *mvp)
{
 struct playlist_side_info *psi0,*psi1;
 char *workarea;

 if(!playlist_max_filenum_list)
  return;

 workarea=memworkarea;
 psi0=mvp->psi0;
 psi1=psi0+1;

 psi0->allfilenum=playlist_max_filenum_browser;
 psi1->allfilenum=playlist_max_filenum_list;

 mvp->pei0=(struct playlist_entry_info *)workarea; // 767872 (11998*64)
  workarea+=(playlist_max_filenum_browser+playlist_max_filenum_list+2)*sizeof(struct playlist_entry_info); // +2 tmp for getting id3tag
  workarea+=1024;

 mvp->pei0->filename=workarea; // current song's infos
  workarea+=MAX_PATHNAMELEN+MAX_ID3LEN+(1024-MAX_PATHNAMELEN); // 2048 (1024+1024)

 psi0->filenamesbeginp=workarea;
 psi0->filenameslastp=workarea;            // 127936 (1999*64)
  workarea+=psi0->allfilenum*FILENAMELENGTH;
 psi0->filenamesendp=workarea-MAX_PATHNAMELEN;

 psi1->filenamesbeginp=workarea;
 psi1->filenameslastp=workarea;            // 639936 (9999*64)
  workarea+=psi1->allfilenum*FILENAMELENGTH;
 psi1->filenamesendp=workarea-MAX_PATHNAMELEN;
  workarea+=1024;

 psi0->id3infobeginp=workarea;
 psi0->id3infolastp=workarea;              // 127936 (1999*64)
  workarea+=psi0->allfilenum*ID3LENGTH;
#ifdef MPXPLAY_UTF8
 psi0->id3infoendp=workarea-MAX_ID3LEN*2;
#else
 psi0->id3infoendp=workarea-MAX_ID3LEN;
#endif

 psi1->id3infobeginp=workarea;
 psi1->id3infolastp=workarea;              // 639936 (9999*64)
  workarea+=psi1->allfilenum*ID3LENGTH;
#ifdef MPXPLAY_UTF8
 psi1->id3infoendp=workarea-MAX_ID3LEN*2;
#else
 psi1->id3infoendp=workarea-MAX_ID3LEN;
#endif
                                           //-----------------
					   //2305664

 psi0->firstentry=psi0->firstsong=psi0->editorhighline=mvp->pei0+1;
 psi0->lastentry=psi0->firstentry-1;
 psi0->endentry=psi0->firstentry+(psi0->allfilenum-2);

 psi1->firstentry=psi1->firstsong=psi1->editorhighline=psi0->firstentry+psi0->allfilenum+1; // +1 tmp for getting id3tag
 psi1->lastentry=psi1->firstentry-1;
 psi1->endentry=psi1->firstentry+(psi1->allfilenum-1);

 mvp->aktfilenum=psi1->lastentry;
}
#endif

//-------------------------------------------------------------------------
void playlist_peimyself_reset(struct playlist_side_info *psi,struct playlist_entry_info *firstentry,struct playlist_entry_info *lastentry)
{
 struct playlist_entry_info *pei;
 if(!firstentry)
  firstentry=psi->firstentry;
 if(!lastentry)
  lastentry=psi->endentry;
 if(lastentry>=firstentry){
  pei=firstentry;
  do{
   pei->myself=pei;
  }while((++pei)<=lastentry);
 }
}

struct playlist_entry_info *playlist_peimyself_search(struct playlist_side_info *psi,struct playlist_entry_info *pei_src)
{
 struct playlist_entry_info *pei_dest=psi->firstentry,*lastentry=psi->lastentry;
 if(lastentry>=pei_dest){
  do{
   if(pei_dest->myself==pei_src)
    return pei_dest;
  }while((++pei_dest)<=lastentry);
 }
 return NULL;
}

//------------------------------------------------------------------------
void playlist_enable_side(struct playlist_side_info *psi)
{
 if(!(psi->editsidetype&PLT_ENABLED)){
  funcbit_enable(psi->editsidetype,PLT_ENABLED);
  playlist_peimyself_reset(psi,NULL,NULL);
  display_editorside_reset(psi);
 }
}

void playlist_disable_side_partial(struct playlist_side_info *psi)
{
 struct mainvars *mvp=psi->mvp;

 playlist_editorhighline_set(psi,psi->firstentry);

 playlist_editlist_tab_clear(psi);
 playlist_fulltime_clearside(psi);

 if(psi==mvp->psip){
  mvp->aktfilenum=psi->firstentry-1;
  mvp->newfilenum=NULL;
  playlist_randlist_clearall(psi);
  if(!(playcontrol&PLAYC_RUNNING))
   mpxplay_stop_and_clear(mvp,0);
 }
 //if(psi==mvp->psie)
 // playlist_editlist_editside_chg(mvp,-1);
 playlist_chkfile_stop(psi);
 funcbit_disable(psi->editsidetype,(PLT_ENABLED|PLT_EXTENDED));
 funcbit_disable(psi->editloadtype,(PLL_FILTERED|PLL_RESTORED|PLL_FILTCHKSKP|PLL_CHG_ALL));
}

void playlist_disable_side_list(struct playlist_side_info *psi)
{
 struct mainvars *mvp=psi->mvp;
 playlist_loadsub_sublist_clear(psi);
 mpxplay_playlist_sideclear_startup(psi);
 psi->restored_filename[0]=0;
 funcbit_disable(psi->editloadtype,(PLL_TYPE_LISTS|PLL_FILTERED|PLL_RESTORED|PLL_FILTCHKSKP|PLL_CHG_ALL));
 if(psi==mvp->psil)
  playlist_clear_freeopts();
 if(psi==mvp->psip)
  funcbit_disable(playlistload,PLL_TYPE_LISTS);
}

void playlist_disable_side_full(struct playlist_side_info *psi)
{
 playlist_disable_side_partial(psi);
 playlist_disable_side_list(psi);
 playlist_sortlist_clear(psi);
}

void mpxplay_playlist_sideclear_entries(struct playlist_side_info *psi)
{
 struct mainvars *mvp=psi->mvp;

 playlist_editorhighline_set(psi,psi->firstentry);

 playlist_editlist_tab_clear(psi);
 playlist_fulltime_clearside(psi);

 if(psi==mvp->psip){
  mvp->aktfilenum=psi->firstentry-1;
  mvp->newfilenum=NULL;
  playlist_randlist_clearall(psi);
 }
 playlist_chkfile_stop(psi);
 funcbit_disable(psi->editsidetype,(PLT_ENABLED|PLT_EXTENDED));
 funcbit_disable(psi->editloadtype,(PLL_TYPE_CMDL|PLL_CHG_ALL));  // TODO: check PLL_CHG_AUTO -> PLL_CHG_ALL
 psi->firstfile=NULL;
}

void mpxplay_playlist_sideclear_sidetype(struct playlist_side_info *psi)
{
 psi->editsidetype=0;
 funcbit_disable(psi->editloadtype, PLL_CHG_ALL);
}

void mpxplay_playlist_sideclear_search(struct playlist_side_info *psi)
{
 psi->mckic_searchstring[0]=0;
}

void mpxplay_playlist_sideclear_startup(struct playlist_side_info *psi)
{
 playlist_savelist_clear(psi);
 mpxplay_playlist_sideclear_search(psi);
}

void playlist_copy_side_infos(struct playlist_side_info *psi_dest,struct playlist_side_info *psi_src)
{
 psi_dest->editsidetype=psi_src->editsidetype;
 psi_dest->editloadtype=psi_src->editloadtype&PLL_TYPE_LISTS; // !!!
 pds_memcpy((char *)&psi_dest->id3ordertype,(char *)&psi_src->id3ordertype,sizeof(psi_dest->id3ordertype));
 psi_dest->mdds=psi_src->mdds;
 if(psi_src->editsidetype&PLT_DIRECTORY){ // !!!
  psi_dest->currdrive=psi_src->currdrive;
  pds_memcpy((char *)&psi_dest->currdir,(char *)&psi_src->currdir,sizeof(psi_dest->currdir));
 }
 psi_dest->sublistlevel=psi_src->sublistlevel;
 pds_memcpy((char *)&psi_dest->sublistnames,(char *)&psi_src->sublistnames,sizeof(psi_dest->sublistnames));
 psi_dest->savelist_type=psi_src->savelist_type;
 psi_dest->savelist_textcodetype=psi_src->savelist_textcodetype;
 pds_memcpy((char *)&psi_dest->savelist_filename,(char *)&psi_src->savelist_filename,sizeof(psi_dest->savelist_filename));
 pds_memcpy((char *)&psi_dest->mckic_searchstring,(char *)&psi_src->mckic_searchstring,sizeof(psi_dest->mckic_searchstring));
}

void playlist_clear_freeopts(void)
{
 unsigned int optcount=OPT_INPUTFILE;
 while((optcount<MAXFREEOPTS) && freeopts[optcount])
  freeopts[optcount++]=NULL;
}

//------------------------------------------------------------------------
char startdir[MAX_PATHNAMELEN];

char *mpxplay_playlist_startdir(void)
{
 if(!startdir[0])
  pds_getcwd(startdir);
 return startdir;
}

void mpxplay_save_startdir(struct mainvars *mvp)
{
 if(!startdir[0])
  pds_getcwd(startdir);
 playlist_loaddir_initbrowser_all(mvp,startdir);
}

void mpxplay_restore_startdir(void)
{
 if(startdir[0])
  pds_chdir(startdir);
}

void mpxplay_playlist_startfile_fullpath(char *fullpath,char *filename)
{
 int drivenum;
 char *s,strtmp[MAX_PATHNAMELEN],currdir[MAX_PATHNAMELEN];

 if(pds_filename_check_absolutepath(filename)){ // d: or ftp:
  drivenum=pds_getdrivenum_from_path(filename);
  if(drivenum>=0){                              // d:
   s=filename;
   if(filename[2]!=PDS_DIRECTORY_SEPARATOR_CHAR){ // d:filename.mp3 (drive without path)
    struct mpxplay_diskdrive_data_s *mdds=playlist_loaddir_drivenum_to_drivemap(drivenum);
    if(mdds){
     playlist_loaddir_getcwd(mdds,currdir,sizeof(currdir));
     pds_filename_build_fullpath(strtmp,currdir,&filename[2]);
     s=&strtmp[0];
    }
   }
   pds_fullpath(fullpath,s); // for snf/lfn conv
  }else{
   pds_strcpy(fullpath,filename); // leave filename as is (possible virtual drive path/filename)
  }
 }else{
  pds_filename_build_fullpath(strtmp,mpxplay_playlist_startdir(),filename);
  pds_fullpath(fullpath,strtmp); // for snf/lfn conv
 }
}

//*************************************************************************
//mpxplay.exe -db2 song.mp3 or list.m3u
static unsigned int playlist_buildlist_db2chk(struct playlist_side_info *psi)
{
 unsigned int retcode=0;
 char inputfilename[MAX_PATHNAMELEN];

 if((mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_DIRBROWSER2) && !(mpxplay_playlistcontrol&MPXPLAY_PLAYLISTC_DB2_LOADED) && freeopts[OPT_INPUTFILE] && !drivescanletters){
  psi->editsidetype=PLT_DIRECTORY;
  mpxplay_playlist_startfile_fullpath(inputfilename,freeopts[OPT_INPUTFILE]);
  pds_getpath_nowildcard_from_filename(psi->currdir,inputfilename);
  if(playlist_loadlist_check_extension(inputfilename) || pds_filename_wildcard_chk(inputfilename)){
   pds_strcpy(psi->sublistnames[0],psi->currdir);
   pds_strcpy(psi->sublistnames[1],inputfilename);
   psi->sublistlevel=1;
   psi->editloadtype=PLL_SUBLISTS;
   psi->mvp->psip=psi;
   if(psi->mvp->psie!=psi)
    playlist_editlist_editside_chg(psi->mvp,-1);
   funcbit_enable(playcontrol,PLAYC_STARTNEXT);
   //playlist_buildlist_all() follows at retcode=0
  }else{
   struct playlist_entry_info *pei;
   playlist_loaddir_buildbrowser(psi);
   pei=playlist_search_filename(psi,inputfilename,-1,NULL,0);
   if(pei){
    psi->mvp->psip=psi;
    if(psi->mvp->psie!=psi)
     playlist_editlist_editside_chg(psi->mvp,-1);
    playstartsngdb2=pei-psi->firstsong+1;
    funcbit_enable(playcontrol,PLAYC_STARTNEXT);
    pds_strcpy(psi->sublistnames[0],inputfilename); //
    freeopts[OPT_INPUTFILE]=psi->sublistnames[0]; // !!! for startup_getstartpos
    retcode=1;
   }
  }
  funcbit_enable(mpxplay_playlistcontrol, MPXPLAY_PLAYLISTC_DB2_LOADED);
 }
 return retcode;
}

unsigned int playlist_buildlist_one(struct playlist_side_info *psi,char *listfile,unsigned int loadtype,char *dslp,char *filtermask)
{
 unsigned int result=0;
 if((loadtype&PLL_STDIN) || (playlist_loadlist_check_extension(listfile) && !pds_filename_wildcard_chk(listfile))){
  funcbit_enable(loadtype,PLL_LOADLIST);
  funcbit_disable(loadtype,(PLL_DRIVESCAN|PLL_DIRSCAN));//|PLL_STDIN));
  if(playlist_loadlist_mainload(psi,listfile,loadtype,filtermask))
   result=PLL_LOADLIST;
 }else if((listfile && listfile[0]) || dslp){
  struct playlist_entry_info *beginentry=psi->lastentry+1;
  playlist_loaddir_scandrives(psi,listfile,dslp);
  playlist_order_filenames_block(psi,beginentry,psi->lastentry);
  result=PLL_DIRSCAN;
  funcbit_disable(loadtype,PLL_LOADLIST);
  funcbit_enable(loadtype,PLL_DIRSCAN);
  if(dslp)
   funcbit_enable(loadtype,PLL_DRIVESCAN);
  else
   funcbit_disable(loadtype,PLL_DRIVESCAN);
 }

 if((psi->editsidetype&PLT_DIRECTORY) && psi->sublistlevel)
  funcbit_disable(loadtype,PLL_TYPE_LOAD);

 funcbit_disable(psi->editloadtype,PLL_TYPE_CMDL);
 funcbit_copy(psi->editloadtype,loadtype,PLL_TYPE_CMDL);

 if(funcbit_test(psi->editloadtype,PLL_FILTERED)){
  psi->filtered_files = playlist_editlist_entry_diff(psi,psi->firstentry,psi->lastentry);
  psi->editorhighline = playlist_editlist_entry_seek(psi,psi->editorhighline,0,SEEK_CUR);
 }

 return result;
}

unsigned int playlist_buildlist_all(struct playlist_side_info *psi,unsigned int multiply_inputs)
{
 unsigned int result;
 char inputfile[MAX_PATHNAMELEN];

 if((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel){
  playlist_loaddir_buildbrowser(psi);
  return 1;
 }

 mpxplay_playlist_sideclear_entries(psi);
 playlist_loadsub_addsubdots(psi);

 // first input file
 if(psi->sublistlevel)
  result=playlist_buildlist_one(psi,psi->sublistnames[psi->sublistlevel],0,NULL,NULL);
 else
  result=playlist_buildlist_one(psi,playlist_loadsub_getinputfile(psi),(playlistload&PLL_TYPE_LISTS),drivescanletters,NULL);

 if(result && psi->savelist_filename[0])
  pds_strcpy(psi->sublistnames[psi->sublistlevel],psi->savelist_filename);
 if(psi->sublistlevel || !result)
  return result;

 // multiply input files
 if(multiply_inputs && !(outmode&OUTMODE_TYPE_FILE) && freeopts[OPT_INPUTFILE+1]){
  unsigned int optcount=OPT_INPUTFILE+1;
  unsigned int allextended=psi->editsidetype&PLT_EXTENDED;
  while((optcount<MAXFREEOPTS) && freeopts[optcount]){
   unsigned int result;
   mpxplay_playlist_startfile_fullpath(inputfile,freeopts[optcount]);
   result=playlist_buildlist_one(psi,inputfile,0,NULL,NULL);
   if(result&PLL_LOADLIST)
    allextended&=(psi->editsidetype&PLT_EXTENDED);
   else
    allextended=0;
   optcount++;
  }
  funcbit_disable(psi->editsidetype,PLT_EXTENDED);
  funcbit_copy(psi->editsidetype,allextended,PLT_EXTENDED); // playlist (side) is extended if all loaded lists are extended (then we don't check the entries)
 }

 if((playrand&2) && (psi->editsidetype&PLT_ENABLED)){
  playlist_randlist_randomize_side(psi);
  playrand=0;
 }
 return 1;
}

void playlist_get_allfilenames(struct mainvars *mvp)
{
 struct playlist_side_info *psi=mvp->psi0;
 unsigned int tabnum;

 for(tabnum=0;tabnum<mvp->editorside_all_tabs;tabnum++,psi++){
  unsigned int multiply=(psi==mvp->psil)? 1:0;
  if(multiply){
   if(!playlist_buildlist_db2chk(psi))
    playlist_buildlist_all(psi,multiply);
  }else
   playlist_buildlist_all(psi,multiply);
 }

 if(!(mvp->psil->editsidetype&PLT_DIRECTORY))
  playlist_loadlist_load_autosaved_list(mvp->psil);

 if(mvp->editorside_all_tabs<=2){
  struct playlist_side_info *psi0,*psi1;
  psi0=playlist_editlist_tabp_get(mvp,0);
  psi1=playlist_editlist_tabp_get(mvp,1);
  if(!(psi0->editsidetype&PLT_ENABLED) && !(psi1->editsidetype&PLT_ENABLED))
   if(psi0->editsidetype&PLT_DIRECTORY)
    playlist_loaddir_select_drive_retry(psi0);
   else
    mpxplay_close_program(MPXERROR_NOFILE);
 }
}

//*************************************************************************
//set starting playlist side & entry
void playlist_init_playside(struct mainvars *mvp)
{
 struct playlist_side_info *psi0,*psi1;
 psi0=playlist_editlist_tabp_get(mvp,0);
 psi1=playlist_editlist_tabp_get(mvp,1);

 if((psi1->editsidetype&PLT_ENABLED) && (psi1->editloadtype&PLL_TYPE_LOAD) && (!(playlistload&PLL_RESTORED) || (playlistload&PLL_TYPE_LOAD))){
  playlist_editlist_editside_chg(mvp,1);
  mvp->psip=psi1;
  funcbit_enable(playcontrol,PLAYC_STARTNEXT);
 }else if(!(psi1->editsidetype&PLT_ENABLED)){
  playlist_editlist_editside_chg(mvp,0);
  mvp->psip=psi0;
 }else if(!(psi0->editsidetype&PLT_ENABLED)){
  playlist_editlist_editside_chg(mvp,1);
  mvp->psip=psi1;
 }
}

void playlist_init_playsong(struct mainvars *mvp)
{
 struct playlist_side_info *psip=mvp->psip;

 //if(!(playcontrol&PLAYC_STARTNEXT) && !playstartsong && !playstartsngdb2)
 // return;

 mvp->aktfilenum=psip->firstsong;//playlist_editlist_entry_seek(psip,psip->firstsong,0,SEEK_CUR);
 if(psip->lastentry>=psip->firstsong){
  if(!playstartsong)
   playstartsong=playstartsngdb2;
  if(playstartsong){
   if((mvp->aktfilenum+playstartsong-1)<=psip->lastentry)
    mvp->aktfilenum+=playstartsong-1;
  }else{
   if(playrand){
    struct playlist_entry_info *newfilenum=playlist_randlist_getnext(psip); // start with a random file
    if(newfilenum)
     mvp->aktfilenum=newfilenum;
   }
  }
 }else{
  funcbit_enable(playcontrol,PLAYC_PAUSENEXT); // ???
 }
 playlist_editorhighline_set(psip,((mvp->aktfilenum<=psip->lastentry)? mvp->aktfilenum:psip->firstentry));
 playstartsngdb2=0;
}

void playlist_start_sideplay(struct mainvars *mvp,struct playlist_side_info *psi)
{
 if(!(playcontrol&PLAYC_RUNNING) && (psi->editsidetype&PLT_ENABLED)){
  mvp->psip=psi;
  if(psi->editloadtype&PLL_JUKEBOX){
   playlist_editorhighline_set(psi,psi->firstentry);
   mvp->aktfilenum=psi->firstentry-1;
   mvp->adone=ADONE_RESTART;
  }else{
   if(playrand)
    mvp->newsong=playlist_randlist_getnext(psi);
   else
    mvp->newsong=psi->firstsong;
  }
  playcontrol|=PLAYC_STARTNEXT;
 }
}

//**************************************************************************

int playlist_open_infile(struct playlist_side_info *psi,struct playlist_entry_info *pei,struct mpxpframe_s *frp)
{
 struct mpxplay_infile_info_s *miis=frp->infile_infos;
 int retcode;
 char sout[128];

 mpxplay_infile_close(frp);

 if(pei<psi->firstsong || pei>psi->lastentry)
  return MPXPLAY_ERROR_ARGS;
 if(!pei->filename || !pei->filename[0])
  return MPXPLAY_ERROR_ARGS;

#if defined(MPXPLAY_GUI_QT) || defined(MPXPLAY_LINK_ORIGINAL_FFMPEG)
#if !defined(MPXPLAY_LINK_ORIGINAL_FFMPEG)
 if(!pei->infile_funcs)
#endif
 {
  retcode = mpxplay_infile_get_infilefuncs_by_ext(frp,pei->mdds,pei->filename,MPXPLAY_INFILE_OPENMODE_PLAY); // for video files FIXME: MPXPLAY_INFILE_OPENMODE_LOAD_VIDEO
  if(MPXPLAY_ERROR_IS_DISKDRIV(retcode))
   return retcode;
  pei->infile_funcs=frp->infile_funcs;
 }
#endif

 frp->filetype=GET_HFT(pei->entrytype);
 frp->filesize=pei->filesize;
 frp->mdds=pei->mdds;
 frp->infile_funcs=pei->infile_funcs;
 frp->psi=psi;
 frp->pei=pei;
 miis->filesize=pei->filesize;
 miis->timemsec=pei->timemsec;

 retcode = playlist_chkentry_get_onefileinfos_play(psi,pei,frp);
 if(retcode == MPXPLAY_ERROR_DISKDRIV_CONNECT_TERMINATED)
  return retcode;
 if(pei->entrytype<DFT_AUDIOFILE){
  snprintf(sout,sizeof(sout),"Can't play (unknown) file:\n%s",pei->filename);
  display_timed_message(sout);
  return retcode;
 }

 return MPXPLAY_ERROR_OK;
}

void playlist_close_infile(struct mainvars *mvp,struct mpxpframe_s *frp)
{
 struct playlist_entry_info *pei0=mvp->pei0;
 unsigned int i;

 mpxplay_infile_close(frp);
 //pei0 reset
 if(pei0->infobits&PEIF_ALLOCATED){
  playlist_editlist_add_filename(NULL,pei0,"No file");
  playlist_editlist_del_id3_all(NULL,pei0);
 }else{
  pds_strcpy(pei0->filename,"No file");
  for(i=0;i<=I3I_MAX;i++)
   pei0->id3info[i]=NULL;
 }
 pei0->pstime=0;
 pei0->petime=0;
 funcbit_disable(pei0->infobits,(~PEIF_ALLOCATED));
 pei0->timemsec=0;
 pei0->filesize=0;
}

//save infos of current song from playlist (entry)
void playlist_pei0_set(struct mainvars *mvp,struct playlist_entry_info *pei,unsigned long update_mode)
{
 struct playlist_entry_info *pei0=mvp->pei0;
 unsigned int i;
 char *sp;

 if(!(update_mode&(EDITLIST_MODE_ID3|EDITLIST_MODE_INDEX))
  ||((update_mode&EDITLIST_MODE_ID3) && !pei0->id3info[I3I_ARTIST] && !pei0->id3info[I3I_TITLE])
  ||((update_mode&EDITLIST_MODE_INDEX) && ((pei0->infobits&PEIF_INDEXED) || (pei->infobits&PEIF_INDEXED)))){
  if(pei0->infobits&PEIF_ALLOCATED)
   playlist_editlist_add_filename(NULL,pei0,pei->filename);
  else
   sp=pei0->filename+pds_strcpy(pei0->filename,pei->filename)+1;
  for(i=0;i<=I3I_MAX;i++){
   if(pei0->infobits&PEIF_ALLOCATED){
    if(id3tagset[i])
     playlist_editlist_add_id3_one(NULL,pei0,i,id3tagset[i],-1);
    else if(pei->id3info[i] || !update_mode)
     playlist_editlist_add_id3_one(NULL,pei0,i,pei->id3info[i],-1);
   }else if(id3tagset[i]){
    pei0->id3info[i]=id3tagset[i];
   }else{
    if(pei->id3info[i] && (sp<(pei0->filename+MAX_PATHNAMELEN+MAX_ID3LEN))){
     pei0->id3info[i]=sp;
     sp+=pds_strcpy(sp,pei->id3info[i])+1;
    }else
     pei0->id3info[i]=NULL;
   }
  }
  pei0->entrytype=pei->entrytype;
  funcbit_copy(pei0->infobits,pei->infobits,(~PEIF_ALLOCATED));
  pei0->timemsec=pei->timemsec;
  pei0->filesize=pei->filesize;
  pei0->mdds=pei->mdds;
  pei0->infile_funcs=pei->infile_funcs;
  pei0->pstime=pei->pstime;
  pei0->petime=pei->petime;
  refdisp|=RDT_HEADER|RDT_ID3INFO; // !!!
 }
}

//*************************************************************************

void playlist_editorhighline_check(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei=psi->editorhighline;

 if((psi->editloadtype&PLL_FILTERED) && (pei->infobits&PEIF_FILTEROUT)){
  struct playlist_entry_info *eh=playlist_editlist_entry_seek(psi,pei,0,SEEK_CUR);
  if(eh->infobits&PEIF_FILTEROUT)
   eh=playlist_editlist_entry_seek(psi,pei,-1,SEEK_CUR);
  pei=eh;
 }
 if(pei>psi->lastentry)
  pei=psi->lastentry;
 if(pei<psi->firstentry)
  pei=psi->firstentry;
 psi->editorhighline=pei;
 mpxplay_display_center_editorhighline(psi,0);
}

void playlist_editorhighline_seek(struct playlist_side_info *psi,long offset,int whence)
{
 psi->editorhighline=playlist_editlist_entry_seek(psi,psi->editorhighline,offset,whence);
 mpxplay_display_center_editorhighline(psi,0);
}

void playlist_editorhighline_set(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 psi->editorhighline=pei;
 playlist_editorhighline_check(psi);
}

void playlist_editorhighline_set_nocenter(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 if(pei>psi->lastentry)
  pei=psi->lastentry;
 if(pei<psi->firstentry)
  pei=psi->firstentry;
 psi->editorhighline=pei;
}

//*************************************************************************
playlist_entry_info *playlist_search_filename(struct playlist_side_info *psi,char *filename,long timempos,struct playlist_entry_info *pei,unsigned long flags)
{
 int direction;
 int (*strcmp_func)(char *str1,char *str2);
 if(!filename || !psi->firstentry)
  return NULL;
 while(*filename==' ')
  filename++;
 if(!*filename)
  return NULL;
 if(flags & PLAYLIST_SEARCHFLAG_BACKWARD){
  direction = -1;
  if(!pei || (pei>psi->lastentry))
   pei=psi->lastentry;
 }else{
  direction = +1;
  if(pei<psi->firstentry)
   pei=psi->firstentry;
 }
 if(flags & PLAYLIST_SEARCHFLAG_FASTSRCH)
  strcmp_func=pds_strcmp;
#ifdef MPXPLAY_UTF8
 else if(pds_strlen(filename)!=pds_utf8_strlen((mpxp_uint8_t *)filename)) // filename does not contain unicode chars (a trick to speed up strcmp)
  strcmp_func=pds_utf8_stricmp;
#endif
 else
  strcmp_func=pds_stricmp;
 while((pei>=psi->firstentry) && (pei<=psi->lastentry)){
  if(strcmp_func(pei->filename,filename)==0){
   if((timempos<0) || !(pei->infobits&PEIF_INDEXED))
    return pei;
   if((timempos>=pei->pstime) && (!pei->petime || (timempos<pei->petime)))
    return pei;
  }
  pei += direction;
 }
 return NULL;
}

//move the cursor to the subdir/list where you came from
void playlist_search_lastdir(struct playlist_side_info *psi,char *lastdir)
{
 struct playlist_entry_info *pei_set;

 pei_set=playlist_search_filename(psi,lastdir,-1,NULL,0);
 if(!pei_set)
  pei_set=psi->firstentry;
 playlist_editorhighline_set(psi,pei_set);
}

void playlist_search_firstsong(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei;
 psi->firstfile=NULL;
 psi->firstsong=psi->lastentry+1;
 for(pei=psi->firstentry;pei<=psi->lastentry;pei++){
  unsigned int et=pei->entrytype;
  if(GET_HFT(et)==HFT_DFT){
   if(!(et&DFTM_DRIVE) && !(et&DFTM_DIR) && !psi->firstfile)
    psi->firstfile=pei;
  }else{
   psi->firstsong=pei;
   break;
  }
 }
 if(!psi->firstfile)
  psi->firstfile=psi->firstsong;
}

//-------------------------------------------------------------------------
void playlist_change_sublist_or_directory(struct playlist_side_info *psi,unsigned long head)
{
 if(GET_HFT(head)==HFT_DFT){
  if(head&DFTM_PLAYLIST)
   psi=playlist_loadsub_sublist_change(psi,head);
  else
   psi=playlist_loaddir_changedir(psi,head);
  playlist_chkfile_start_norm(psi,NULL);
 }
}

//-------------------------------------------------------------------------
void playlist_reload_side(struct mainvars *mvp,struct playlist_side_info *psi)
{
 mpxplay_playlist_sideclear_search(psi);
 if(psi->editloadtype&PLL_FILTERED){ // the 1st ctrl-r clears the filtering only
  if(psi->editloadtype&PLL_FILTCHKSKP)
   playlist_chkfile_start_norm(psi,NULL);
  funcbit_disable(psi->editloadtype,(PLL_FILTERED|PLL_FILTCHKSKP|PLL_CHG_FILTER));
  return;
 }
 if(!(psi->editloadtype&PLL_JUKEBOX) && ((psi->editsidetype&PLT_DIRECTORY) || playlist_loadsub_getinputfile(psi) || (drivescanletters && (psi==mvp->psil)) || (playlistload&PLL_TYPE_LISTS))){
  struct playlist_entry_info *ehl_save=psi->editorhighline; // reload complete list/dir
  if((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel)
   mpxplay_diskdrive_drive_config(psi->mdds,MPXPLAY_DISKDRIV_CFGFUNCNUM_CMD_RESETDRIVE,NULL,NULL);
  funcbit_disable(psi->editloadtype, PLL_CHG_ALL);
  playlist_buildlist_all(psi,((psi==mvp->psil)? 1:0));
  playlist_chkfile_start_norm(psi,NULL);
  playlist_editorhighline_set(psi,ehl_save);
  display_editorside_reset(psi);
 }else{ // clear/reload id3info only
  struct playlist_entry_info *pei;
  playlist_chkfile_stop(psi);
  playlist_fulltime_clearside(psi);
  for(pei=psi->firstentry;pei<=psi->lastentry;pei++)
   if(GET_HFT(pei->entrytype)!=HFT_DFT){
    playlist_editlist_del_id3_all(psi,pei);
    funcbit_disable(pei->infobits,(PEIF_ENABLED|PEIF_ID3LOADED|PEIF_ID3EXIST|PEIF_FULLTIMEADDED|PEIF_SORTED));
   }
  playlist_chkfile_start_norm(psi,NULL);
 }
}

void playlist_reload_dirs(struct mainvars *mvp)
{
 struct playlist_side_info *psi=mvp->psi0;
 unsigned int tabnum;

 for(tabnum=0;tabnum<mvp->editorside_all_tabs;tabnum++,psi++){
  struct playlist_entry_info *ehl_save=psi->editorhighline;
  if((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel)
   playlist_loaddir_buildbrowser(psi);
  playlist_chkfile_start_norm(psi,NULL);
  playlist_editorhighline_set(psi,ehl_save);
 }
}

void playlist_commandermode_switch(struct mainvars *mvp)
{
 unsigned long dtm_old=desktopmode, selected;
 funcbit_inverse(displaymode,DISP_DESKTOPMODEC);
 if(funcbit_test(displaymode,DISP_DESKTOPMODEC)){
  desktopmod_player=desktopmode;
  desktopmode=desktopmod_commander;
 }else{
  desktopmod_commander=desktopmode;
  desktopmode=desktopmod_player;
 }
 funcbit_copy(desktopmode, dtm_old, DTM_MASK_CMDSWITCHCOPY);
 playlist_reload_dirs(mvp);
 selected=RDT_BROWSER|RDT_EDITOR|RDT_INIT_EDIT;
 if((desktopmode&DTM_MASK_CHG_GLOBAL)!=(dtm_old&DTM_MASK_CHG_GLOBAL))
  selected|=RDT_INIT_FULL|RDT_OPTIONS|RDT_HEADER|RDT_ID3INFO;
 refdisp|=selected;
}

mpxp_int32_t mpxplay_playlist_modify_entry_alltab_by_mdds_and_filename(void *p_mdds, unsigned int diskdrv_cb_cmd, void *data1, void *data2)
{
 struct mainvars *mvp = &mvps;
 struct playlist_side_info *psi = mvp->psi0;
 struct mpxpframe_s *frp;
 struct mpxplay_diskdrive_data_s *mdds = (struct mpxplay_diskdrive_data_s *)p_mdds;  // !!! mdds can be NULL
 char *filename = data1, *str_data = data2;
 unsigned int tabnum, exclusive_update = 0;

 mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"mod %8.8X \"%s\" \"%s\"", (unsigned long)mdds, filename, str_data);
 for(tabnum = 0; tabnum < mvp->editorside_all_tabs; tabnum++, psi++){
  if(((mdds && (psi->mdds==mdds)) || (!mdds && (psi == mvp->psip))) && (psi->editsidetype & PLT_LOADDIR_PROCESS)){
   exclusive_update = 1;
   break;
  }
 }
 if(!exclusive_update){
  tabnum = 0;
  psi = mvp->psi0;
 }
 for(; tabnum < mvp->editorside_all_tabs; tabnum++, psi++){
  if((mdds && (psi->mdds == mdds)) || (!mdds && (psi == mvp->psip))) {
   struct playlist_entry_info *pei;
   switch(diskdrv_cb_cmd){
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_PROCPSISTART:
     if(psi == mvp->psie)        // FIXME: false at GUI tab Reload dir function
      funcbit_enable(psi->editsidetype, PLT_LOADDIR_PROCESS);
     refdisp|=RDT_EDITOR;
     goto check_next;            // FIXME: ???
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_PROCPSISTOP:
     playlist_order_side(psi);
     funcbit_disable(psi->editsidetype, PLT_LOADDIR_PROCESS);
     refdisp|=RDT_EDITOR;
     goto check_next;
   }

   if(!filename || !filename[0])
    return MPXPLAY_ERROR_CFGFUNC_ARGUMENTMISSING;

   switch(diskdrv_cb_cmd){
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_NEWDIRENTRY:
     pei = playlist_editlist_add_entry(psi);
     playlist_editlist_add_filename(psi, pei, filename);
     if((psi==mvp->psip) && ((mvp->aktfilenum < psi->firstentry) || (mvp->aktfilenum > psi->lastentry)) && (mpxplay_control_startup_getstartpos_delayed(psi,pei)<0) && (pds_stricmp(mvp->pei0->filename,filename)==0)){
      mvp->aktfilenum=pei;
      playlist_editlist_addfile_one(NULL,psi,mvp->pei0,pei,(EDITLIST_MODE_HEAD|EDITLIST_MODE_ID3));
      playlist_randlist_pushq(psi,pei);
      playlist_editorhighline_set(psi,pei);
      refdisp|=RDT_EDITOR|RDT_BROWSER;
      mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"Entry t:%d e:%d \"%s\"", tabnum, (pei-psi->firstentry+1), filename);
     }
     goto check_next;
   }

   if((mvp->aktfilenum >= psi->firstsong) && (mvp->aktfilenum <= psi->lastentry) && (pds_strcmp(mvp->aktfilenum->filename, filename)==0))
    pei = mvp->aktfilenum;
   else{
    pei = playlist_search_filename(psi, filename, -1, NULL, (PLAYLIST_SEARCHFLAG_BACKWARD | PLAYLIST_SEARCHFLAG_FASTSRCH));
    if(!pei){
     pei = mvp->pei0; // !!! ???
     if(!(pei->infobits&PEIF_ALLOCATED) || (pds_strcmp(pei->filename, filename) != 0))
      goto check_next;
    }
   }

   switch(diskdrv_cb_cmd){
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_REFDIRENTRY: display_draw_editor_oneline(psi, pei, -1); continue;
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_DELDIRENTRY:
     playlist_editlist_del_entry(psi, pei);
     funcbit_enable(psi->editloadtype, PLL_CHG_ENTRY);
     goto check_next;
   }

   if(!data2)
    return MPXPLAY_ERROR_CFGFUNC_ARGUMENTMISSING;

   frp = mvp->fr_primary;
   switch(diskdrv_cb_cmd){
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_TIMELENMS:
     if(pei->timemsec != *((unsigned long *)data2)){
      pei->timemsec = *((unsigned long *)data2);
      if((psi==mvp->psip) && (frp->allframes > 1) && frp->infile_infos->timemsec && (pds_stricmp(pei->filename, mvp->pei0->filename) == 0)){
       frp->infile_infos->timemsec = pei->timemsec;
      // frp->allframes =     // FIXME: missing
      }
      funcbit_enable(psi->editloadtype, PLL_CHG_LEN);
     }
     goto check_next;
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_TIMEPOSMS:
     if((psi==mvp->psip) && (frp->allframes > 1) && frp->infile_infos->timemsec && (pds_stricmp(pei->filename, mvp->pei0->filename) == 0))
      frp->frameNum = (long)((float) *((unsigned long *)data2) * (float)frp->allframes / (float)frp->infile_infos->timemsec);
     goto check_next;
   }

   if(!str_data[0])
    return MPXPLAY_ERROR_CFGFUNC_ARGUMENTMISSING;

   switch(diskdrv_cb_cmd){
    case MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_FILENAME:
     if(pds_strcmp(pei->filename, str_data) != 0){
      playlist_editlist_add_filename(psi, pei, str_data);
      funcbit_enable(psi->editloadtype, PLL_CHG_ENTRY);
     }
     break;
    default:
     if((diskdrv_cb_cmd & (~MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_MASK)) == MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_BASE){
      if((loadid3tag & ID3LOADMODE_LOWLEVEL) && ((diskdrv_cb_cmd != MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_TITLE) || (GET_HFT(pei->entrytype) != HFT_DFT))){  // I3I_DFT_STORE
       unsigned int infile_cb_cmd = MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_PACKFUNCTTI3I(MPXPLAY_CFGFUNCNUM_INFILE_ENTRY_TAG_UPDATE, MPXPLAY_TEXTCONV_TYPE_MPXNATIVE, (diskdrv_cb_cmd & MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_MASK));
       mpxplay_infile_controlcb_modify_pei_by_filename(infile_cb_cmd, psi, pei, filename, str_data, -1);
      }
     }else
      return MPXPLAY_ERROR_CFGFUNC_UNSUPPFUNC;
     break;
   }
   //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"Found t:%d", tabnum);
check_next:
   if(exclusive_update)
    break;
  }
 }
 return MPXPLAY_ERROR_OK;
}

//-------------------------------------------------------------------------
void playlist_write_id3tags(struct mainvars *mvp)
{
 struct mpxpframe_s *frp;
 struct playlist_side_info *psi;
 struct playlist_entry_info *pei;
 int error;
 char *shortfname;
 char sout[128];

 if(!writeid3tag)
  return;

 frp=mvp->fr_check;
 psi=mvp->psil;

 for(pei=psi->firstsong;pei<=psi->lastentry;pei++){
  playlist_chkentry_get_onefileinfos_allagain(psi,pei,frp,ID3LOADMODE_ALL|ID3LOADMODE_PREFER_LIST,MPXPLAY_INFILE_OPENMODE_CHECK);
  mpxplay_infile_close(frp);
  frp->filetype=GET_HFT(pei->entrytype);
  frp->filesize=pei->filesize;
  frp->mdds=pei->mdds;
  frp->infile_funcs=pei->infile_funcs;
  frp->infile_infos->timemsec=pei->timemsec;
  frp->psi=psi;
  frp->pei=pei;
  error=mpxplay_infile_write_id3tag(frp,pei->filename,&pei->id3info[0],MPXPLAY_WRITETAG_CNTRL_DUPFILE);
  shortfname=pds_getfilename_any_from_fullname(pei->filename);
  switch(error){
   case MPXPLAY_ERROR_INFILE_OK               :sprintf(sout,"%.15s  %-30.30s : %-30.30s",shortfname,pei->id3info[I3I_ARTIST],pei->id3info[I3I_TITLE]);break;
   case MPXPLAY_ERROR_INFILE_CANTOPEN         :sprintf(sout,"ID3TAG write error at %.25s (read-only or not exists)!",shortfname);break;
   case MPXPLAY_ERROR_INFILE_WRITETAG_FILETYPE:sprintf(sout,"ID3TAG write is not supported for this filetype (%.25s)",shortfname);break;
   case MPXPLAY_ERROR_INFILE_WRITETAG_TAGTYPE :sprintf(sout,"ID3TAG wrong or unsupported tagtype (%.20s)",shortfname);break;
   default:sprintf(sout,"ID3TAG write error at %.20s (unknown error)!",shortfname);break;
  }
  pds_textdisplay_printf(sout);
 }
 mpxplay_close_program(MPXERROR_UNDEFINED);
}
