//**************************************************************************
//*                     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 entry add,delete,move

#include "mpxplay.h"
#include "display\display.h"
#include <malloc.h>

#define EDITLIST_ASM 1

extern char *id3filterkeyword;
extern unsigned int displaymode,desktopmode,refdisp,playlist_max_filenum_list;

struct playlist_entry_info *playlist_editlist_addfileone_postproc(struct playlist_side_info *psi_dest,struct playlist_entry_info *pei_dest)
{
 struct mainvars *mvp=psi_dest->mvp;
 unsigned int entrytype=pei_dest->entrytype;
 struct playlist_entry_info *pei_newpos;

 if((psi_dest==mvp->psip) && !(psi_dest->editsidetype&PLT_DOOMQUEUE) && (mvp->aktfilenum<psi_dest->firstsong) && (pei_dest->entrytype>=DFT_AUDIOFILE) && (pds_utf8_stricmp(pei_dest->filename,mvp->pei0->filename)==0)){
  struct mpxpframe_s *frp=mvp->frp0;
  mvp->aktfilenum=pei_dest;
  playlist_pei0_set(mvp,pei_dest,0);
  playlist_randlist_pushq(psi_dest,pei_dest);
  mpxplay_calculate_index_start_end(frp,mvp,pei_dest); // switch between cue and other playlist
 }
 pei_newpos=playlist_order_entry(psi_dest,pei_dest);
 if(entrytype&(DFTM_DIR|DFTM_PLAYLIST))
  playlist_search_firstsong(psi_dest);
 return pei_newpos;
}

void playlist_editlist_addfile_any(struct playlist_side_info *psi_src,struct playlist_entry_info *pei_src,char *source_filtermask)
{
 struct playlist_side_info *psi_dest=psi_src->psio;
 unsigned int len,loadtype=0;
 int startentrynum;
 char *loc_filtermask,strtmp[MAX_PATHNAMELEN];

 if(!(psi_src->editsidetype&PLT_ENABLED))
  return;

 if(GET_HFT(pei_src->entrytype)==HFT_DFT){  // add drive, dir or playlist
  if(source_filtermask)
   loc_filtermask=source_filtermask;
  else
   loc_filtermask=PDS_DIRECTORY_ALLFILE_STR;
  startentrynum=psi_dest->lastentry-psi_dest->firstentry;
  switch(pei_src->entrytype){
   case DFT_PLAYLIST:
   case DFT_SUBLIST:pds_strcpy(strtmp,pei_src->filename);
                    loadtype=PLL_LOADLIST;
                    break;
   case DFT_UPLIST:pds_strcpy(strtmp,psi_src->sublistnames[psi_src->sublistlevel]);
                   loadtype=PLL_LOADLIST;
                   break;
   case DFT_UPDIR: len=pds_strcpy(strtmp,psi_src->currdir);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLDIR_STR);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                   pds_strcpy(&strtmp[len],loc_filtermask);
                   loadtype=PLL_DIRSCAN;
                   break;
   case DFT_SUBDIR:len=pds_strcpy(strtmp,pei_src->filename);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLDIR_STR);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                   pds_strcpy(&strtmp[len],loc_filtermask);
                   loadtype=PLL_DIRSCAN;
		   break;
   case DFT_DRIVE:len=pds_strcpy(strtmp,PDS_DIRECTORY_DRIVE_STR);
                  len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                  len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLDIR_STR);
                  len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                  pds_strcpy(&strtmp[len],loc_filtermask);
                  strtmp[0]=pei_src->filename[0]; // copy drive label
                  loadtype=PLL_DIRSCAN;
		  break;
   default:return;
  }
  playlist_buildlist_one(psi_dest,strtmp,loadtype,NULL,source_filtermask);
  if((psi_dest->lastentry-psi_dest->firstentry)>startentrynum){
   if(startentrynum<0){
    playlist_loadsub_setnewinputfile(psi_dest,strtmp,loadtype);
   }else{
    playlist_loadsub_setnewinputfile(psi_dest,NULL,0);
    funcbit_enable(psi_dest->editloadtype,PLL_CHG_ENTRY);
   }
   playlist_chkfile_start_norm(psi_dest,psi_dest->firstentry+startentrynum+1);
  }
 }else{
  if(!source_filtermask || pds_utf8_filename_wildcard_cmp(pei_src->filename,source_filtermask))
   if(playlist_editlist_addfile_one(psi_src,psi_dest,pei_src,NULL,EDITLIST_MODE_ALL))
    playlist_editlist_addfileone_postproc(psi_dest,psi_dest->lastentry);
 }
}

void playlist_editlist_copy_entry(struct playlist_side_info *psi_src,struct playlist_entry_info *pei_src)
{
 struct playlist_side_info *psi_dest=psi_src->psio;
 struct playlist_entry_info pei_tmp;
 unsigned int len;
 char strtmp[MAX_PATHNAMELEN];

 if(!(psi_src->editsidetype&PLT_ENABLED))
  return;

 if(GET_HFT(pei_src->entrytype)==HFT_DFT){  // copy drive, dir or playlist
  pds_memset(&pei_tmp,0,sizeof(struct playlist_entry_info));
  switch(pei_src->entrytype){
   case DFT_PLAYLIST:
   case DFT_SUBLIST :pds_strcpy(strtmp,pei_src->filename);
                     pei_tmp.id3info[I3I_ARTIST]=pei_src->id3info[I3I_ARTIST];
                     pei_tmp.id3info[I3I_TITLE]=pei_src->id3info[I3I_TITLE];
		     break;
   case DFT_SUBDIR:len=pds_strcpy(strtmp,pei_src->filename);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLDIR_STR);
                   len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                   pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLFILE_STR);
		   break;
   case DFT_DRIVE:len=pds_strcpy(strtmp,PDS_DIRECTORY_DRIVE_STR);
                  len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                  len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLDIR_STR);
                  len+=pds_strcpy(&strtmp[len],PDS_DIRECTORY_SEPARATOR_STR);
                  pds_strcpy(&strtmp[len],PDS_DIRECTORY_ALLFILE_STR);
                  strtmp[0]=pei_src->filename[0]; // copy drive label
		  break;
   default:return;
  }
  pei_tmp.filename=&strtmp[0];
  if((desktopmode&DTM_EDIT_LOADLIST) && (pei_src->entrytype&DFTM_PLAYLIST) && (psi_dest->editsidetype&PLT_DIRECTORY) && !psi_dest->sublistlevel && !(psi_src->editsidetype&PLT_DIRECTORY)){
   pei_tmp.entrytype=DFT_PLAYLIST;
   if(pds_strcmp(pei_tmp.id3info[I3I_DFT_STORE],DFTSTR_SUBLIST)==0)
    pei_tmp.id3info[I3I_DFT_STORE]=DFTSTR_PLAYLIST;
  }else{
   pei_tmp.entrytype=DFT_SUBLIST;
   if((!pei_tmp.id3info[I3I_ARTIST] && !pei_tmp.id3info[I3I_TITLE]) || pds_strcmp(pei_tmp.id3info[I3I_DFT_STORE],DFTSTR_PLAYLIST)==0)
    pei_tmp.id3info[I3I_DFT_STORE]=DFTSTR_SUBLIST;
  }
  playlist_editlist_addfile_one(psi_src,psi_dest,&pei_tmp,NULL,EDITLIST_MODE_ALL);
  if(pei_tmp.entrytype&DFTM_PLAYLIST){
   playlist_order_entry(psi_dest,psi_dest->lastentry);
   playlist_search_firstsong(psi_dest);
  }
 }else{
  if(playlist_editlist_addfile_one(psi_src,psi_dest,pei_src,NULL,EDITLIST_MODE_ALL))
   playlist_editlist_addfileone_postproc(psi_dest,psi_dest->lastentry);
 }
}

//-------------------------------------------------------------------------
//multiply list-tabs handling

struct playlist_side_info *playlist_editlist_tabs_init(struct mainvars *mvp)
{
 unsigned int s,o;
 struct playlist_side_info *psi=calloc(PLAYLIST_MAX_SIDES,sizeof(*psi));
 if(!psi)
  return psi;
 mvp->editorside_all_tabs=PLAYLIST_MAX_SIDES;
 mvp->psi0=mvp->psie=mvp->psip=mvp->psil=psi;
 psi->editsidetype=PLT_DIRECTORY;
 mvp->psi0->psio=psi+1; // !!!
 (psi+1)->psio=psi;     //
 s=0;
 do{
  mvp->editorsides_num_tabs[s]=1;
  psi->sidenum=s;
  psi->mvp=mvp;
  for(o=0;o<PLAYLIST_MAX_ORDERKEYS;o++)
   *((unsigned int *)(&psi->id3ordertype[o]))=ID3ORDER_DISABLED;
  psi++;
 }while(++s<PLAYLIST_MAX_SIDES);
 return mvp->psi0;
}

void playlist_editlist_tab_clear(struct playlist_side_info *psi)
{
 if(playlist_max_filenum_list){
  psi->filenameslastp=psi->filenamesbeginp;
  psi->id3infolastp=psi->id3infobeginp;
 }else{
  unsigned int i,n=psi->filenum_loaded;
  struct playlist_entry_info *pei=psi->firstentry;
  if(!n || !pei)
   return;
  do{
   char **id3iip;
   if(pei->filename)
    free(pei->filename);
   i=I3I_MAX+1;
   id3iip=&pei->id3info[0];
   do{
    char *id3ip=*id3iip;
    if(id3ip)
     free(id3ip);
    id3iip++;
   }while(--i);
   pei++;
  }while(--n);
  if(psi->filenum_allocated>PLAYLIST_ENTRIES_EXPAND_SIZE){
   free(psi->firstentry);
   psi->firstentry=NULL;
   psi->filenum_allocated=psi->filenum_loaded=0;
   playlist_editlist_del_entry(psi,playlist_editlist_add_entry(psi));
  }
 }
 psi->filenum_loaded=0;
 psi->firstsong=psi->editor_from=psi->firstentry;
 psi->lastentry=psi->firstentry-1;
 psi->selected_files=0;
 psi->editor_from_prev=NULL;
}

void playlist_editlist_tab_close(struct playlist_side_info *psi)
{
 playlist_editlist_tab_clear(psi);
 if(!playlist_max_filenum_list){
  if(psi->firstentry){
   free(psi->firstentry);
   psi->firstentry=NULL;
  }
  psi->filenum_allocated=0;
 }
}

void playlist_editlist_tabs_close(struct mainvars *mvp)
{
 unsigned int t=mvp->editorside_all_tabs;
 struct playlist_side_info *psi=mvp->psi0;
 if(!t || !psi)
  return;
 do{
  playlist_editlist_tab_close(psi);
  psi++;
 }while(--t);
}

static unsigned int playlist_editlist_tabnum_get(struct mainvars *mvp,int side,int tabnum)
{
 unsigned int s,t;
 if((side<0) || (side>=PLAYLIST_MAX_SIDES))
  side=mvp->editorside_selected;
 s=0;t=0;
 do{
  if(s>=side){
   t+=((tabnum>=0) && (tabnum<mvp->editorsides_num_tabs[s]))? tabnum:mvp->editorsides_selected_tab[s];
   break;
  }
  t+=mvp->editorsides_num_tabs[s];
 }while(++s<PLAYLIST_MAX_SIDES);
 return t;
}

struct playlist_side_info *playlist_editlist_tabp_get(struct mainvars *mvp,int side)
{
 struct playlist_side_info *psi=mvp->psi0;
 if(psi)
  psi+=playlist_editlist_tabnum_get(mvp,side,-1);
 return psi;
}

struct playlist_side_info *playlist_editlist_tabpsi_get(struct mainvars *mvp,int side,int tabnum)
{
 struct playlist_side_info *psi=mvp->psi0;
 if(psi)
  psi+=playlist_editlist_tabnum_get(mvp,side,tabnum);
 return psi;
}

void playlist_editlist_tabs_corr_psio(struct mainvars *mvp)
{
 struct playlist_side_info *psi,*psios[PLAYLIST_MAX_SIDES];
 unsigned int s;
 s=0;
 do{
  psios[s]=playlist_editlist_tabp_get(mvp,s);
 }while(++s<PLAYLIST_MAX_SIDES);
 s=0;
 psi=mvp->psi0;
 do{
  unsigned int t=0,ent=mvp->editorsides_num_tabs[s];
  struct playlist_side_info *psio=psios[!s];  // !!! 2 sides only
  do{
   psi->sidenum=s;
   psi->tabnum=t;
   psi->psio=psio;
   psi++;
  }while(++t<ent);
 }while(++s<PLAYLIST_MAX_SIDES);
}

int playlist_editlist_editside_chg(struct mainvars *mvp,int side)
{
 if(side==mvp->editorside_selected)
  return MPXPLAY_ERROR_OK;
 if(side<0)
  side=!mvp->editorside_selected;
 if((side>=PLAYLIST_MAX_SIDES) || !mvp->editorsides_num_tabs[side])
  return -1;
 mvp->editorside_selected=side;
 mvp->psie=mvp->psi0+playlist_editlist_tabnum_get(mvp,side,-1);
 refdisp|=RDT_INIT_EDIT|RDT_EDITOR;
 return MPXPLAY_ERROR_OK;
}

void playlist_editlist_sidetab_select(struct mainvars *mvp,unsigned int side,unsigned int tabnum)
{
 if((side>=PLAYLIST_MAX_SIDES) || !mvp->editorsides_num_tabs[side])
  return;
 if(tabnum<mvp->editorsides_num_tabs[side])
  mvp->editorsides_selected_tab[side]=tabnum;
 if(side==mvp->editorside_selected)
  mvp->psie=mvp->psi0+playlist_editlist_tabnum_get(mvp,side,-1);
 playlist_editlist_tabs_corr_psio(mvp);
 refdisp|=RDT_EDITOR;
}

void playlist_editlist_tab_select(struct mainvars *mvp,unsigned int side,unsigned int tabnum)
{
 if((side>=PLAYLIST_MAX_SIDES) || !mvp->editorsides_num_tabs[side])
  return;
 if(mvp->editorside_selected!=side){
  mvp->editorside_selected=side;
  refdisp|=RDT_INIT_EDIT;
 }
 playlist_editlist_sidetab_select(mvp,side,tabnum);
}

struct playlist_side_info *playlist_editlist_tab_skip(struct mainvars *mvp)
{
 if(playlist_max_filenum_list || (mvp->editorside_all_tabs<=PLAYLIST_MAX_SIDES) || (mvp->editorsides_num_tabs[mvp->editorside_selected]<=1))
  return mvp->psie;
 mvp->editorsides_selected_tab[mvp->editorside_selected]++;
 if(mvp->editorsides_selected_tab[mvp->editorside_selected]>=mvp->editorsides_num_tabs[mvp->editorside_selected])
  mvp->editorsides_selected_tab[mvp->editorside_selected]=0;
 mvp->psie=mvp->psi0+playlist_editlist_tabnum_get(mvp,-1,-1);
 playlist_editlist_tabs_corr_psio(mvp);
 refdisp|=RDT_EDITOR;
 return mvp->psie;
}

struct playlist_side_info *playlist_editlist_tab_add(struct mainvars *mvp,unsigned int mode)
{
 struct playlist_side_info *psi,*psi0_old,*psi0_new,*psi_src;
 struct playlist_entry_info *pei_src;
 unsigned int s,t,insertpoint;
 long songnum;
 //char sout[128];

 if(playlist_max_filenum_list || (mvp->editorsides_num_tabs[mvp->editorside_selected]>=PLAYLIST_MAX_TABS_PER_SIDE))
  return mvp->psie;

 psi0_new=malloc((mvp->editorside_all_tabs+2)*sizeof(*psi0_new));
 if(!psi0_new)
  return mvp->psie;
 psi0_old=mvp->psi0;
 mvp->psi0=psi0_new;

 s=0;
 insertpoint=0;
 do{
  if(s>=mvp->editorside_selected){
   if(mode&EDITLIST_ADDTABMODE_TOEND)
    insertpoint+=mvp->editorsides_num_tabs[s];
   else
    insertpoint+=mvp->editorsides_selected_tab[s]+1;
   break;
  }
  insertpoint+=mvp->editorsides_num_tabs[s];
 }while(++s<PLAYLIST_MAX_SIDES);

 pds_memcpy(psi0_new,psi0_old,insertpoint*sizeof(*psi0_old));
 psi=psi0_new+insertpoint;
 if(insertpoint<mvp->editorside_all_tabs){
  struct playlist_side_info *psc=psi+1;
  pds_memcpy(psc,psi0_old+insertpoint,(mvp->editorside_all_tabs-insertpoint)*sizeof(*psi0_new));
  for(t=insertpoint+1;t<=mvp->editorside_all_tabs;t++,psc++)
   if(psc->editloadtype&PLL_RESTORED)
    funcbit_enable(psc->editloadtype,PLL_CHG_MANUAL);
 }

 mvp->editorside_all_tabs++;

 mvp->psie=psi0_new+(mvp->psie-psi0_old);

 t=mvp->psip-psi0_old;
 if(t>=insertpoint)
  t++;
 mvp->psip=psi0_new+t;

 t=mvp->psil-psi0_old;
 if(t>=insertpoint)
  t++;
 mvp->psil=psi0_new+t;

 free(psi0_old);

 mvp->editorsides_num_tabs[s]++;
 if(mode&EDITLIST_ADDTABMODE_BLANK){ // an empty tab
  pds_memset(psi,0,sizeof(*psi));
  psi->mvp=mvp;
  playlist_sortlist_set_orderkeys_from_hexa(psi,0xffffffff);
 }else{ //duplicate tab (copy from selected)
  psi_src=playlist_editlist_tabp_get(mvp,s);
  pds_memcpy(psi,psi_src,sizeof(*psi));
  songnum=psi_src->firstsong-psi_src->firstentry;
  psi->editorhighline=psi->firstentry=psi->firstsong=psi->lastentry=psi->endentry=NULL;
  psi->filenum_loaded=psi->filenum_allocated=0;
  psi->chkfilenum_begin=psi->chkfilenum_curr=psi->chkfilenum_end=NULL;
  psi->editor_from=psi->editor_from_prev=NULL;
  psi->fulltimesec=psi->selected_files=0;
  for(pei_src=psi_src->firstentry;pei_src<=psi_src->lastentry;pei_src++)
   playlist_editlist_addfile_one(psi_src,psi,pei_src,NULL,EDITLIST_MODE_ALL);
  if((psi->lastentry<psi->firstentry) || (songnum>(psi->lastentry-psi->firstentry)))
   songnum=0;
 }
 if(mode&EDITLIST_ADDTABMODE_TOEND)
  mvp->editorsides_selected_tab[s]=mvp->editorsides_num_tabs[s]-1; //
 else
  mvp->editorsides_selected_tab[s]++; // switch to new tab immediately

 if(!psi->firstentry){
  playlist_editlist_del_entry(psi,playlist_editlist_add_entry(psi));
  songnum=0;
 }

 mvp->psie=psi;
 psi->firstsong=psi->firstentry+songnum;
 psi->editor_from=psi->editorhighline=psi->firstentry;
 playlist_order_side(psi);

 /*sprintf(sout,"ip:%d le:%8.8X fe:%8.8X",insertpoint,(long)psi->lastentry,(long)psi->firstentry);
 display_message(0,0,sout);
 sprintf(sout,"ps:%d pfs:%8.8X pfe:%8.8X af:%8.8X",(long)(mvp->psip-mvp->psi0),(long)mvp->psip->firstsong,(long)mvp->psip->firstentry,(long)mvp->aktfilenum);
 display_message(1,0,sout);
 //getch();*/

 playlist_editlist_tabs_corr_psio(mvp);

 refdisp|=RDT_EDITOR;

 return psi;
}

int playlist_editlist_tab_del(struct mainvars *mvp,int selected_side,int selected_tabnum)
{
 struct playlist_side_info *psi_del,*psi0_old,*psi0_new;
 unsigned int s,t,to,delpoint,sidepoint;

 if(playlist_max_filenum_list || (mvp->editorside_all_tabs<=PLAYLIST_MAX_SIDES))
  return MPXPLAY_ERROR_OK;

 if(selected_side<0)
  selected_side=mvp->editorside_selected;
 else if(selected_side>=PLAYLIST_MAX_SIDES)
  return MPXPLAY_ERROR_FILEHAND_MEMORY;

 if(mvp->editorsides_num_tabs[selected_side]<=1)
  return MPXPLAY_ERROR_OK;

 if(selected_tabnum<0)
  selected_tabnum=mvp->editorsides_selected_tab[selected_side];
 else if(selected_tabnum>=mvp->editorsides_num_tabs[selected_side])
  return MPXPLAY_ERROR_INFILE_MEMORY;

 psi0_new=malloc((mvp->editorside_all_tabs-1)*sizeof(*psi0_new));
 if(!psi0_new)
  return MPXPLAY_ERROR_FILEHAND_MEMORY;
 psi0_old=mvp->psi0;
 mvp->psi0=psi0_new;

 s=0;
 delpoint=0;
 do{
  sidepoint=delpoint;
  if(s>=selected_side){
   delpoint+=selected_tabnum;
   mvp->editorsides_num_tabs[s]--;
   break;
  }
  delpoint+=mvp->editorsides_num_tabs[s];
 }while(++s<PLAYLIST_MAX_SIDES);

 psi_del=psi0_old+delpoint;
 playlist_editlist_tab_close(psi_del);

 mvp->editorside_all_tabs--;

 if(delpoint<mvp->editorside_all_tabs){
  struct playlist_side_info *psc=psi_del;
  pds_memcpy(psi_del,psi_del+1,(mvp->editorside_all_tabs-delpoint)*sizeof(*psi_del));
  for(t=delpoint;t<mvp->editorside_all_tabs;t++,psc++)
   if(psc->editloadtype&PLL_RESTORED)
    funcbit_enable(psc->editloadtype,PLL_CHG_MANUAL);
 }
 pds_memcpy(psi0_new,psi0_old,mvp->editorside_all_tabs*sizeof(*psi0_new));

 if(s>=PLAYLIST_MAX_SIDES)
  goto err_out_del;

 if(mvp->editorsides_selected_tab[s]>=mvp->editorsides_num_tabs[s])
  mvp->editorsides_selected_tab[s]=mvp->editorsides_num_tabs[s]-1;

 t=mvp->psie-psi0_old;
 if((t>delpoint) || ((t==delpoint) && (delpoint>=(sidepoint+mvp->editorsides_num_tabs[s]))))
  t--;
 mvp->psie=psi0_new+t;

 t=to=mvp->psip-psi0_old;
 if((t>delpoint) || ((t==delpoint) && (delpoint>=(sidepoint+mvp->editorsides_num_tabs[s]))))
  t--;
 mvp->psip=psi0_new+t;

 if(to==delpoint){
  mvp->aktfilenum=mvp->psip->firstsong-1;
  mvp->newfilenum=mvp->newsong=NULL;
 }

 t=mvp->psil-psi0_old;
 if((t>delpoint) || ((t==delpoint) && (delpoint>=(sidepoint+mvp->editorsides_num_tabs[s]))))
  t--;
 mvp->psil=psi0_new+t;

err_out_del:
 free(psi0_old);

 playlist_editlist_tabs_corr_psio(mvp);

 refdisp|=RDT_EDITOR|RDT_INIT_EDIT;

 return MPXPLAY_ERROR_OK;
}

//------------------------------------------------------------------------
// playlist entry add/del/modify

struct playlist_entry_info *playlist_editlist_add_entry(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei=NULL;
 if(playlist_max_filenum_list){
  if(psi->lastentry>=psi->endentry)
   return pei;
  pei=++psi->lastentry;
 }else{
  if(psi->filenum_loaded>=psi->filenum_allocated){
   struct playlist_entry_info *pei_old=psi->firstentry;
   struct mainvars *mvp=psi->mvp;
   unsigned int new_listnum=psi->filenum_allocated*2+PLAYLIST_ENTRIES_EXPAND_SIZE;
   unsigned int new_listsize=new_listnum*sizeof(struct playlist_entry_info);
   pei=malloc(new_listsize+256);
   if(!pei)
    return pei;
   if(pei_old){
    if(psi->filenum_loaded)
     pds_memcpy(pei,pei_old,psi->filenum_loaded*sizeof(struct playlist_entry_info));
    if(psi==psi->mvp->psip){
     if((mvp->aktfilenum>=pei_old) && (mvp->aktfilenum<=psi->endentry))
      mvp->aktfilenum=pei+(mvp->aktfilenum-pei_old);
     if((mvp->newfilenum>=pei_old) && (mvp->newfilenum<=psi->endentry))
      mvp->newfilenum=pei+(mvp->newfilenum-pei_old);
    }
    psi->firstsong=pei+(psi->firstsong-pei_old);
    psi->editorhighline=pei+(psi->editorhighline-pei_old);
    if(psi->chkfilenum_begin)
     psi->chkfilenum_begin=pei+(psi->chkfilenum_begin-pei_old);
    if(psi->chkfilenum_curr)
     psi->chkfilenum_curr=pei+(psi->chkfilenum_curr-pei_old);
    if(psi->chkfilenum_end)
     psi->chkfilenum_end=pei+(psi->chkfilenum_end-pei_old);
    psi->editor_from=pei+(psi->editor_from-pei_old);
    if(psi->editor_from_prev)
     psi->editor_from_prev=pei+(psi->editor_from_prev-pei_old);
    free(pei_old);
   }else{
    if(psi==psi->mvp->psip)
     mvp->aktfilenum=pei;
    psi->firstsong=psi->editorhighline=psi->editor_from=pei;
   }
   psi->filenum_allocated=new_listnum;
   psi->endentry=pei+new_listnum;
   psi->firstentry=pei;
   playlist_peimyself_reset(psi,pei,pei+psi->filenum_loaded);
  }
  psi->lastentry=pei=psi->firstentry+psi->filenum_loaded;
  psi->filenum_loaded++;
 }
 pds_memset(pei,0,sizeof(struct playlist_entry_info));
 if(!playlist_max_filenum_list)
  funcbit_enable(pei->infobits,PEIF_ALLOCATED);
 pei->myself=pei;
 return pei;
}

void playlist_editlist_del_entry(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 if(!pei || !psi || (pei<psi->firstentry) || (pei>psi->lastentry))
  return;
 playlist_editlist_del_filename(psi,pei);
 playlist_editlist_del_id3_all(psi,pei);
 if(pei<psi->lastentry)
  pds_memcpy(pei,pei+1,(psi->lastentry-pei)*sizeof(struct playlist_entry_info));
 pds_memset(psi->lastentry,0,sizeof(struct playlist_entry_info));
 psi->lastentry--;
 if(psi->filenum_loaded)
  psi->filenum_loaded--;
}

int playlist_editlist_add_filename(struct playlist_side_info *psi,struct playlist_entry_info *pei,char *filename)
{
 unsigned int len;
 if(!pei)
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
 playlist_editlist_del_filename(psi,pei);
 if(funcbit_test(pei->infobits,PEIF_ALLOCATED)){
  len=pds_strlen(filename);
  if(len){
   char *p;
   pei->filename=p=malloc(len+1); // +1:eol
   if(!p)
    return MPXPLAY_ERROR_INFILE_MEMORY;
   pds_memcpy(p,filename,len);
   p[len]=0;
  }
 }else{
  if(!psi)
   return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
  if(psi->filenameslastp>=psi->filenamesendp)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  len=pds_strncpy(psi->filenameslastp,filename,MAX_PATHNAMELEN);
  if(len){
   pei->filename=psi->filenameslastp;
   psi->filenameslastp+=len;
   *(psi->filenameslastp++)=0;
  }
 }
 return len;
}

int playlist_editlist_add_id3_one(struct playlist_side_info *psi,struct playlist_entry_info *pei,unsigned int i,char *str,int len)
{
 if(!pei || (i>I3I_MAX))
  return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
 playlist_editlist_del_id3_one(psi,pei,i);
 if(!str || !str[0])
  return 0;
 if(funcbit_test(pei->infobits,PEIF_ALLOCATED)){
  char *p;
  if(len<0)
   len=pds_strlen(str);
  if(!len)
   return len;
  if(len>MAX_ID3LEN) // !!! ???
   len=MAX_ID3LEN;   //
  pei->id3info[i]=p=malloc(len+1); // +1:eol
  if(!p)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  pds_memcpy(p,str,len);
  p[len]=0;
 }else{
  if(!psi)
   return MPXPLAY_ERROR_CFGFUNC_INVALIDDATA;
  if(psi->id3infolastp>=psi->id3infoendp)
   return MPXPLAY_ERROR_INFILE_MEMORY;
  if((len<0) || (len>(MAX_ID3LEN/2)))
   len=MAX_ID3LEN/2;
  len=pds_strncpy(psi->id3infolastp,str,len);
  if(len){
   pei->id3info[i]=psi->id3infolastp;
   psi->id3infolastp+=len;
   *(psi->id3infolastp++)=0;
  }
 }
 return len;
}

unsigned int playlist_editlist_addfile_one(struct playlist_side_info *psi_src,struct playlist_side_info *psi_dest,struct playlist_entry_info *pei_src,struct playlist_entry_info *pei_dest,unsigned int modify)
{
 struct playlist_entry_info *dest_lastentry;
 unsigned int i;

 if((psi_src && !(psi_src->editsidetype&PLT_ENABLED)) || !pei_src)
  return 0;

 if(!psi_dest)
  if(psi_src)
   psi_dest=psi_src->psio;
  else
   return 0;

 playlist_enable_side(psi_dest);

 dest_lastentry=psi_dest->lastentry;
 if(!pei_dest){
  pei_dest=playlist_editlist_add_entry(psi_dest);
  if(!pei_dest)
   return 0;
 }else{
  if(pei_dest>psi_dest->lastentry)
   return 0;
 }
 if(modify&EDITLIST_MODE_FILENAME)
  if(playlist_editlist_add_filename(psi_dest,pei_dest,pei_src->filename)<=0)
   return 0;
 if(pei_dest>dest_lastentry){
  if(psi_dest->chkfilenum_end && (psi_dest->chkfilenum_end==dest_lastentry))
   psi_dest->chkfilenum_end=pei_dest;
  else if(psi_src && psi_src->chkfilenum_curr && (pei_src>=psi_src->chkfilenum_curr))
   playlist_chkfile_start_norm(psi_dest,pei_dest);
  funcbit_enable(psi_dest->editloadtype,PLL_CHG_ENTRY);
 }
 if(modify&EDITLIST_MODE_INDEX){
  funcbit_disable(pei_dest->infobits,PEIF_INDEXED);
  funcbit_copy(pei_dest->infobits,pei_src->infobits,PEIF_INDEXED);
  pei_dest->pstime=pei_src->pstime;
  pei_dest->petime=pei_src->petime;
 }
 if(modify&(EDITLIST_MODE_HEAD|EDITLIST_MODE_ENTRY)){
  pei_dest->entrytype=pei_src->entrytype;
  funcbit_disable(pei_dest->infobits,PEIF_COPYMASK);
  funcbit_copy(pei_dest->infobits,pei_src->infobits,PEIF_COPYMASK);
  if(pei_src->timemsec>pei_dest->timemsec)
   pei_dest->timemsec=pei_src->timemsec;
  if(pei_src->filesize>pei_dest->filesize)
   pei_dest->filesize=pei_src->filesize;
  pei_dest->mdds=pei_src->mdds;
  pei_dest->infile_funcs=pei_src->infile_funcs;
  if(*((mpxp_uint32_t *)&pei_dest->filedate)<*((mpxp_uint32_t *)&pei_src->filedate))
   *((mpxp_uint32_t *)&pei_dest->filedate)=*((mpxp_uint32_t *)&pei_src->filedate);
  playlist_fulltime_add(psi_dest,pei_dest);
 }
 if(modify&EDITLIST_MODE_ID3){
  i=0;
  do{
   if(!pei_dest->id3info[i]) // !!! get_onefileinfos_from_otherside()
    if(pei_src->id3info[i])
     playlist_editlist_add_id3_one(psi_dest,pei_dest,i,pei_src->id3info[i],-1);
  }while(++i<=I3I_MAX);
  funcbit_disable(pei_dest->infobits,PEIF_ID3MASK);
  funcbit_copy(pei_dest->infobits,pei_src->infobits,PEIF_ID3MASK);
 }
 return modify;
}

#ifdef __WATCOMC__
void asm_delfile_filename(char **,unsigned int,unsigned int,char *,char *);
void asm_delfile_id3infos(char **,unsigned int,unsigned int,char *,char *);
#endif

//delete filename from the filenamesworkarea
static void playlist_editlist_static_del_filename(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 char *pf;
 unsigned int i,memlen_del,memlen_move;
 char **fn,*flp;

 pf=pei->filename;
 pei->filename=NULL;
 if(!psi || (pf<psi->filenamesbeginp) || (pf>psi->filenameslastp))
  return;

 memlen_del=pds_strlen(pf)+1;
 memlen_move=psi->filenameslastp-pf;
 pds_memcpy(pf,pf+memlen_del,memlen_move);

 i=psi->lastentry-psi->firstentry+1;
 fn=&(psi->firstentry->filename);
 flp=psi->filenameslastp;

 // !!! sizeof(struct playlist_entry_info) dependant
#if defined(EDITLIST_ASM) && defined(__WATCOMC__)
 #ifdef MPXPLAY_FSIZE64
  #pragma aux asm_delfile_filename=\
   "back1:"\
    "mov eax,dword ptr [ebx]"\
    "cmp eax,edi"\
    "jbe skip"\
    "cmp eax,esi"\
    "jae skip"\
     "sub eax,edx"\
     "mov dword ptr [ebx],eax"\
    "skip:add ebx,76"\
    "dec ecx"\
    "jnz back1"\
    parm [ebx][ecx][edx][edi][esi] modify[eax ebx ecx];
    asm_delfile_filename(fn,i,memlen_del,pf,flp);
 #else
   #pragma aux asm_delfile_filename=\
   "back1:"\
    "mov eax,dword ptr [ebx]"\
    "cmp eax,edi"\
    "jbe skip"\
    "cmp eax,esi"\
    "jae skip"\
     "sub eax,edx"\
     "mov dword ptr [ebx],eax"\
    "skip:add ebx,72"\
    "dec ecx"\
    "jnz back1"\
    parm [ebx][ecx][edx][edi][esi] modify[eax ebx ecx];
    asm_delfile_filename(fn,i,memlen_del,pf,flp);
 #endif
#else
 do{
  char *fnp=*fn;
  if((fnp>pf) && (fnp<flp)){  // pointer is in filenamesworkarea
   fnp-=memlen_del;             // move down filename pointer
   *fn=fnp;
  }
  fn+=sizeof(struct playlist_entry_info)/sizeof(*fn);
 }while(--i);
#endif
 psi->filenameslastp-=memlen_del;
}

//delete id3info from the id3infoworkarea
static void playlist_editlist_static_del_id3_one(struct playlist_side_info *psi,struct playlist_entry_info *pei,unsigned int i)
{
 char *pf,**id3iip,*ilp;
 unsigned int k,memlen_del,memlen_move;

 pf=pei->id3info[i];
 pei->id3info[i]=NULL;
 if(!psi || (pf<psi->id3infobeginp) || (pf>psi->id3infolastp))
  return;

 memlen_del=pds_strlen(pf)+1;
 memlen_move=psi->id3infolastp-pf;
 pds_memcpy(pf,pf+memlen_del,memlen_move);

 k=psi->lastentry-psi->firstentry+1;
 id3iip=&(psi->firstentry->id3info[0]);
 ilp=psi->id3infolastp;

 // !!! sizeof(struct playlist_entry_info) dependant
#if defined(EDITLIST_ASM) && defined(__WATCOMC__) && (I3I_MAX==6)
 #ifdef MPXPLAY_FSIZE64
    #pragma aux asm_delfile_id3infos=\
    "push ebp"\
    "back_k:"\
     "mov ebp,7"\
     "back_j:"\
      "mov eax,dword ptr [ebx]"\
      "cmp eax,edi"\
      "jbe skip"\
      "cmp eax,esi"\
      "jae skip"\
       "sub eax,edx"\
       "mov dword ptr [ebx],eax"\
      "skip:add ebx,4"\
      "dec ebp"\
     "jnz back_j"\
     "add ebx,48"\
     "dec ecx"\
    "jnz back_k"\
    "pop ebp"\
    parm [ebx][ecx][edx][edi][esi] modify[eax ebx ecx];
    asm_delfile_id3infos(id3iip,k,memlen_del,pf,ilp);
 #else
    #pragma aux asm_delfile_id3infos=\
    "push ebp"\
    "back_k:"\
     "mov ebp,7"\
     "back_j:"\
      "mov eax,dword ptr [ebx]"\
      "cmp eax,edi"\
      "jbe skip"\
      "cmp eax,esi"\
      "jae skip"\
       "sub eax,edx"\
       "mov dword ptr [ebx],eax"\
      "skip:add ebx,4"\
      "dec ebp"\
     "jnz back_j"\
     "add ebx,44"\
     "dec ecx"\
    "jnz back_k"\
    "pop ebp"\
    parm [ebx][ecx][edx][edi][esi] modify[eax ebx ecx];
    asm_delfile_id3infos(id3iip,k,memlen_del,pf,ilp);
 #endif
#else
 do{
  int j=I3I_MAX+1;
  do{
   char *idp=*id3iip;
   if((idp>pf) && (idp<ilp)){ // pointer is in id3infoworkarea
    idp-=memlen_del;          //move down id3info[] pointer
    *id3iip=idp;
   }
   id3iip++;
  }while(--j);
  id3iip+=(sizeof(struct playlist_entry_info)-sizeof(pei->id3info))/sizeof(*id3iip);
 }while(--k);
#endif
 psi->id3infolastp-=memlen_del;
}

void playlist_editlist_del_filename(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 if(!pei || !pei->filename)
  return;
 if(funcbit_test(pei->infobits,PEIF_ALLOCATED)){
  free(pei->filename);
  pei->filename=NULL;
 }else
  playlist_editlist_static_del_filename(psi,pei);
}

void playlist_editlist_del_id3_one(struct playlist_side_info *psi,struct playlist_entry_info *pei,unsigned int i)
{
 if(!pei || (i>I3I_MAX) || !pei->id3info[i])
  return;
 if(funcbit_test(pei->infobits,PEIF_ALLOCATED)){
  free(pei->id3info[i]);
  pei->id3info[i]=NULL;
 }else
  playlist_editlist_static_del_id3_one(psi,pei,i);
}

void playlist_editlist_del_id3_all(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 unsigned int i=0;
 do{
  playlist_editlist_del_id3_one(psi,pei,i);
 }while(++i<=I3I_MAX);
}

void playlist_editlist_delfile_one(struct playlist_side_info *psi,struct playlist_entry_info *pei,unsigned int modify)
{
 if(modify&EDITLIST_MODE_FILENAME)
  playlist_editlist_del_filename(psi,pei);

 if(modify&EDITLIST_MODE_ID3)
  playlist_editlist_del_id3_all(psi,pei);

 if((psi->lastentry<psi->firstentry) || (pei<psi->firstentry) || (pei>psi->lastentry)
    || (pei->entrytype&DFTM_DRIVE) || (pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_UPLIST))
  return;

 if(modify&EDITLIST_MODE_HEAD)
  playlist_fulltime_del(psi,pei);

 if(modify&EDITLIST_MODE_ENTRY){
  if((pei->infobits&PEIF_SELECTED) && psi->selected_files)
   psi->selected_files--;
  if((pei->entrytype&DFTM_DFT) && (pei<psi->firstsong) && (psi->firstsong>psi->firstentry))
   psi->firstsong--;

  playlist_randlist_delete(pei);

  //move down entries
  if(pei<psi->lastentry)
   pds_memcpy(pei,pei+1,(char *)(psi->lastentry)-(char *)pei);

  //clear last entry
  pds_memset(psi->lastentry,0,sizeof(struct playlist_entry_info));

  //correct pointers in random queue and at pei->myself
  playlist_randlist_correctq(psi,pei,psi->lastentry);
  playlist_peimyself_reset(psi,pei,psi->lastentry);

  //correct some variables (chkfilenum,aktfilenum,newfilenum)
  if(psi->filenum_loaded)
   psi->filenum_loaded--;
  psi->lastentry--;
  if(psi->lastentry<psi->firstentry){
   playlist_disable_side_full(psi);
  }else{
   struct mainvars *mvp=psi->mvp;
   if(psi->chkfilenum_curr){
    if(psi->chkfilenum_begin>=pei)
     psi->chkfilenum_begin--;
    if(psi->chkfilenum_curr>=pei)
     psi->chkfilenum_curr--;
    psi->chkfilenum_end--;
   }
   if(psi==mvp->psip){
    if(mvp->newfilenum){
     if(mvp->newfilenum>pei)
      mvp->newfilenum--;
     if(mvp->newfilenum>psi->lastentry)
      mvp->newfilenum=psi->lastentry;
     if(mvp->newfilenum<psi->firstsong)
      mvp->newfilenum=NULL;
    }
    if(mvp->aktfilenum>=psi->firstsong){
     if(mvp->aktfilenum>pei)
      mvp->aktfilenum--;
     else{
      if(mvp->aktfilenum==pei){
       mvp->aktfilenum=psi->firstsong-1;
       if(!mvp->newfilenum && (pei>psi->firstsong)){
        if(pei>psi->lastentry)
         pei=psi->lastentry;
        mvp->newfilenum=pei;
       }
      }
     }
    }
   }
   if(pei<psi->editorhighline)
    psi->editorhighline--;
   playlist_editorhighline_check(psi);
   funcbit_enable(psi->editloadtype,PLL_CHG_ENTRY);
  }
 }
}

//--------------------------------------------------------------------------
unsigned int playlist_editlist_allocated_copy_entry(struct playlist_entry_info *pei_dest,struct playlist_entry_info *pei_src)
{
 unsigned int i;
 if(!pei_src || !pei_dest)
  return 0;
 if(funcbit_test(pei_dest->infobits,PEIF_ALLOCATED))
  playlist_editlist_allocated_clear_entry(pei_dest);
 pds_memcpy(pei_dest,pei_src,sizeof(*pei_dest));
 funcbit_enable(pei_dest->infobits,PEIF_ALLOCATED);
 pei_dest->filename=NULL;
 pds_memset(pei_dest->id3info,0,sizeof(pei_dest->id3info));
 if(playlist_editlist_add_filename(NULL,pei_dest,pei_src->filename)<0)
  goto err_out_dup;
 for(i=0;i<=I3I_MAX;i++)
  if(playlist_editlist_add_id3_one(NULL,pei_dest,i,pei_src->id3info[i],-1)<0)
   goto err_out_dup;
 return 1;

err_out_dup:
 playlist_editlist_allocated_clear_entry(pei_dest);
 return 0;
}

void playlist_editlist_allocated_clear_entry(struct playlist_entry_info *pei)
{
 if(funcbit_test(pei->infobits,PEIF_ALLOCATED)){
  playlist_editlist_del_filename(NULL,pei);
  playlist_editlist_del_id3_all(NULL,pei);
 }
}

//--------------------------------------------------------------------------
void playlist_editlist_delete_entry_manual(struct playlist_side_info *psi,struct playlist_entry_info *pei)
{
 if(!(pei->entrytype&DFTM_DRIVE) && !(pei->entrytype&DFTM_DIR) && ((psi->lastentry>psi->firstentry) || (psi->psio->editsidetype&PLT_ENABLED))){
  playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_ALL);
  display_editorside_reset(psi);
 }
}

void playlist_editlist_addfile_ins_ehl(struct playlist_side_info *psi_src,struct playlist_entry_info *pei_src)
{
 if(GET_HFT(pei_src->entrytype)==HFT_DFT)
  playlist_editlist_addfile_any(psi_src,pei_src,NULL);
 else{
  struct playlist_side_info *psi_dest;
  struct playlist_entry_info *pei_dest;
  if(!playlist_editlist_addfile_one(psi_src,NULL,pei_src,NULL,EDITLIST_MODE_ALL))
   return;
  psi_dest=psi_src->psio;
  pei_dest=psi_dest->editorhighline;
  if(pei_dest<psi_dest->firstsong)
   pei_dest=psi_dest->firstsong;
  if(pei_dest<(psi_dest->lastentry-1)){
   playlist_editorhighline_set(psi_dest,psi_dest->lastentry);
   do{
    playlist_editlist_shiftfile(psi_dest,-1);
   }while(psi_dest->editorhighline>pei_dest);
  }else{
   playlist_editorhighline_set(psi_dest,pei_dest);
  }
  playlist_editorhighline_seek(psi_dest,+1,SEEK_CUR);
 }
}

// move/shift entry (up or down) in the playlist (ie: at ctrl-up/ctrl-down)
void playlist_editlist_shiftfile(struct playlist_side_info *psi,int direction)
{
 struct playlist_entry_info *pei_dest;

 pei_dest=psi->editorhighline+direction;
 if((psi->editorhighline>=psi->firstsong) && (pei_dest>=psi->firstsong) && (pei_dest<=psi->lastentry)){
  playlist_swap_entries(psi,psi->editorhighline,pei_dest);
  playlist_editorhighline_set(psi,pei_dest);
  funcbit_enable(psi->editloadtype,PLL_CHG_ENTRY);
 }
}

//move playlist entry via the mouse (drag & move)
void playlist_editlist_mouse_shiftfile(struct mainvars *mvp,struct playlist_entry_info *ehls)
{
 int mfd;
 unsigned int i;
 struct playlist_side_info *psi=mvp->psie;
 struct playlist_entry_info *ehld=psi->editorhighline;

 if(ehls!=ehld){
  playlist_editorhighline_set_nocenter(psi,ehls);
  if(ehld>ehls){
   i=ehld-ehls;
   mfd=1;
  }else{
   i=ehls-ehld;
   mfd=-1;
  }
  for(;i;i--)
   playlist_editlist_shiftfile(psi,mfd);

  if(psi==mvp->psip)
   refdisp|=RDT_EDITOR|RDT_BROWSER;
  else
   refdisp|=RDT_EDITOR;
 }
}

//----------------------------------------------------------------------
//group functions

// copy (add) all playlist entries to the other side
void playlist_editlist_copyside(struct playlist_side_info *psi_src)
{
 struct playlist_entry_info *pei_src;

 for(pei_src=psi_src->firstsong;pei_src<=psi_src->lastentry;pei_src++)
  if(GET_HFT(pei_src->entrytype)!=HFT_DFT)
   playlist_editlist_addfile_one(psi_src,NULL,pei_src,NULL,EDITLIST_MODE_ALL);

 playlist_order_side(psi_src->psio);

 refdisp|=RDT_EDITOR;
 if(psi_src->psio==psi_src->mvp->psip)
  refdisp|=RDT_BROWSER;
 display_editorside_reset(psi_src->psio);
}

typedef struct mpxp_easg_s{
 struct playlist_side_info *psi;
 mpxp_char_t source_filtermask[48];
}mpxp_easg_s;

static display_textwin_button_t addfile_group_buttons[]={
 {"[ Add ]"   ,KEY_ENTER1},// gray enter
 {""          ,KEY_ENTER2},// white enter
 {"[ Cancel ]",0x2e63},    // 'c'
 {""          ,0x2e43},    // 'C'
 {""          ,0x3f00},    // F5
 {""          ,KEY_ESC},   // ESC
 {NULL,0}
};

static char source_default_filter_addfiles[48]=PDS_DIRECTORY_ALLFILE_STR;

static void editlist_addfile_group_start(struct mpxp_easg_s *ag)
{
 struct playlist_side_info *psi_src=ag->psi;
 struct playlist_entry_info *pei;

 if(psi_src->selected_files){
  for(pei=psi_src->firstentry;pei<=psi_src->lastentry;pei++)
   if(pei->infobits&PEIF_SELECTED){
    playlist_editlist_addfile_any(psi_src,pei,ag->source_filtermask);
    funcbit_disable(pei->infobits,PEIF_SELECTED);
   }
  psi_src->selected_files=0;
 }else
  playlist_editlist_addfile_any(psi_src,psi_src->editorhighline,ag->source_filtermask); // !!!

 refdisp|=RDT_EDITOR;
 if(psi_src->psio==psi_src->mvp->psip)
  refdisp|=RDT_BROWSER;
 display_editorside_reset(psi_src->psio);

 pds_strcpy(source_default_filter_addfiles,ag->source_filtermask);
 free(ag);
}

void playlist_editlist_addfile_selected_group(struct playlist_side_info *psi_src)
{
 struct playlist_entry_info *pei=psi_src->editorhighline;
 struct mpxp_easg_s *ag;
 void *tw;

 if(!psi_src->selected_files && (GET_HFT(pei->entrytype)!=HFT_DFT)){
  playlist_editlist_addfile_any(psi_src,pei,NULL);
  return;
 }

 ag=calloc(1,sizeof(*ag));
 if(!ag)
  return;

 ag->psi=psi_src;

 if(!source_default_filter_addfiles[0] || !pds_strlenc(source_default_filter_addfiles,' '))
  pds_strcpy(source_default_filter_addfiles,PDS_DIRECTORY_ALLFILE_STR);

 pds_strcpy(ag->source_filtermask,source_default_filter_addfiles);

 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_MSGCENTERALIGN|TEXTWIN_FLAG_CONFIRM," Copy entries ",editlist_addfile_group_start,ag);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,"Add/load selected entries to the other side?");
 display_textwin_additem_separatorline(tw,1);
 display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,2,"Source filter: ");
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,sizeof("Source filter: ")-1,2,8,ag->source_filtermask,sizeof(ag->source_filtermask)-1);
 display_textwin_additem_separatorline(tw,3);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,4,&addfile_group_buttons[0],NULL);
 display_textwin_openwindow_items(tw,0,0,0);
}

void playlist_editlist_copy_selected_group(struct playlist_side_info *psi_src)
{
 struct playlist_entry_info *pei;

 if(psi_src->selected_files){
  for(pei=psi_src->firstentry;pei<=psi_src->lastentry;pei++)
   if(pei->infobits&PEIF_SELECTED){
    playlist_editlist_copy_entry(psi_src,pei);
    funcbit_disable(pei->infobits,PEIF_SELECTED);
   }
  psi_src->selected_files=0;
 }else
  playlist_editlist_copy_entry(psi_src,psi_src->editorhighline); // !!!

 refdisp|=RDT_EDITOR;
 if(psi_src->psio==psi_src->mvp->psip)
  refdisp|=RDT_BROWSER;
 display_editorside_reset(psi_src->psio);
}

void playlist_editlist_move_selected_group(struct playlist_side_info *psi_src)
{
 struct playlist_entry_info *pei;

 if(psi_src->selected_files){
  for(pei=psi_src->firstentry;pei<=psi_src->lastentry;pei++)
   if(pei->infobits&PEIF_SELECTED)
    playlist_editlist_copy_entry(psi_src,pei);
 }else
  playlist_editlist_copy_entry(psi_src,psi_src->editorhighline); // !!!

 playlist_editlist_delfile_selected_group(psi_src);

 refdisp|=RDT_EDITOR;
 if(psi_src->psio==psi_src->mvp->psip)
  refdisp|=RDT_BROWSER;
}

void playlist_editlist_delfile_selected_group(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei;
 unsigned int delcount,delfilenum,allfilenum;
 void *tw=NULL;
 char sout[128];

 if(psi->selected_files){
  pei=psi->lastentry;
  delcount=0;
  delfilenum=psi->selected_files;
  allfilenum=(psi->lastentry-psi->firstentry+1);
  if(!playlist_max_filenum_list || (delfilenum==allfilenum)){
   snprintf(sout,sizeof(sout),"Deleting %d entries",delfilenum);
   display_timed_message(sout);
  }
  if((delfilenum==allfilenum) && !(psi->editsidetype&PLT_DIRECTORY) && !(psi->psio->editsidetype&PLT_DOOMQUEUE)){
   playlist_clear_side(psi);
   playlist_disable_side_full(psi);
  }else{
   do{
    if(pei->infobits&PEIF_SELECTED){
     if((psi->psio->editsidetype&PLT_DOOMQUEUE) && (psi->lastentry<=psi->firstentry)) // !!!
      break;
     if(pei->entrytype!=DFT_SUBDIR){
      playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_ALL);
      funcbit_disable(pei->infobits,PEIF_SELECTED);
      delcount++;
      if(playlist_max_filenum_list && !(delcount&31)){
       if(pds_kbhit())
        if(pds_extgetch()==KEY_ESC)
         break;
       snprintf(sout,sizeof(sout),"\nRemoving entries: %3d/%d\n",delcount,delfilenum);
       tw=display_textwin_openwindow_message(tw,NULL,sout);
      }
     }
    }
    pei--;
   }while(pei>=psi->firstentry);
   if(delcount>=psi->selected_files)
    psi->selected_files=0;
   else
    psi->selected_files-=delcount;
   if(playlist_max_filenum_list)
    display_textwin_closewindow_message(tw);
   display_editorside_reset(psi);
  }
  display_clear_timed_message();
 }else
  playlist_editlist_delete_entry_manual(psi,psi->editorhighline);

 refdisp|=RDT_EDITOR;
 if(psi==psi->mvp->psip)
  refdisp|=RDT_BROWSER;
}

void playlist_editlist_group_invert_selection(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei;
 unsigned int selected=0;
 char sout[64];

 for(pei=psi->firstentry;pei<=psi->lastentry;pei++){
  if((pei->entrytype&DFTM_DFT) && ((pei->entrytype&DFTM_DRIVE) || (pei->entrytype&DFTM_DIR) || (pei->entrytype==DFT_UPLIST)))
   continue;
  funcbit_inverse(pei->infobits,PEIF_SELECTED);
  if(funcbit_test(pei->infobits,PEIF_SELECTED))
   selected++;
 }

 psi->selected_files=selected;

 sprintf(sout,"Selected %d file%c",selected,((selected>1)? 's':' '));
 display_timed_message(sout);
}

void playlist_editlist_group_select_all(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei;
 unsigned int selected=0;
 char sout[64];

 for(pei=psi->firstentry;pei<=psi->lastentry;pei++){
  if((pei->entrytype&DFTM_DFT) && ((pei->entrytype&DFTM_DRIVE) || (pei->entrytype&DFTM_DIR) || (pei->entrytype==DFT_UPLIST)))
   continue;
  funcbit_enable(pei->infobits,PEIF_SELECTED);
  selected++;
 }

 psi->selected_files=selected;

 sprintf(sout,"Selected all (%d) file%c",selected,((selected>1)? 's':' '));
 display_timed_message(sout);
}

void playlist_editlist_group_unselect_all(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei;
 char sout[64];

 for(pei=psi->firstentry;pei<=psi->lastentry;pei++)
  funcbit_disable(pei->infobits,PEIF_SELECTED);

 sprintf(sout,"Unselected %d (all) file%c",psi->selected_files,((psi->selected_files>1)? 's':' '));
 display_timed_message(sout);
 psi->selected_files=0;
}

//------------------------------------------------------------------------
#define METASELECT_WINSIZE 40

#define SELECTTYPE_UNKNOWN 0
#define SELECTTYPE_DIRONLY 1

static unsigned int selecthand_unselect;
static char selectstring[METASELECT_WINSIZE+20]=PDS_DIRECTORY_ALLFILE_STR;

static void editlist_groupselect_metadata(struct playlist_side_info *psi)
{
 struct playlist_entry_info *pei;
 unsigned int ts_len,as_len,selected,selecttype=SELECTTYPE_UNKNOWN,found;
 char *p,*s_titlep,temps[sizeof(selectstring)],sout[50];

 pds_strcpy(temps,selectstring);

 s_titlep=pds_strchr(temps,':');
 if(s_titlep){
  *s_titlep++=0;
  ts_len=pds_strlen(s_titlep);
 }else
  ts_len=0;
 as_len=pds_strlen(temps);

 p=pds_strchr(temps,'.');
 if(p && !p[1])
  selecttype=SELECTTYPE_DIRONLY;

 selected=0;
 pei=psi->firstentry;
 do{
  found=0;
  switch(selecttype){
   case SELECTTYPE_UNKNOWN:
    if((pei->entrytype&DFTM_DRIVE) || (pei->entrytype==DFT_UPDIR) || (pei->entrytype==DFT_UPLIST))
     break;
    if(!s_titlep){
     unsigned int i;
     for(i=0;i<=I3I_MAX;i++){
      if(pei->id3info[i] && pds_utf8_wildcard_is_strstri(pei->id3info[i],temps)){
       found=1;
       break;
      }
     }
     if(!found)
      if(pds_utf8_wildcard_is_strstri(pei->filename,temps) || pds_utf8_filename_wildcard_cmp(pei->filename,temps))
       found=1;
    }else{
     char *ipa,*ipt;
     if(GET_HFT(pei->entrytype)==HFT_DFT || (!pei->id3info[I3I_ARTIST] && !pei->id3info[I3I_TITLE])){
      ipa=ipt=pds_getfilename_from_fullname(pei->filename);
     }else{
      ipa=pei->id3info[I3I_ARTIST];
      ipt=pei->id3info[I3I_TITLE];
      if(!ipa)
       ipa=ipt;
      if(!ipt)
       ipt=ipa;
     }
     if(!as_len || pds_utf8_wildcard_is_strstri(ipa,temps)) // title search only || artist found
      if(!ts_len || pds_utf8_wildcard_is_strstri(ipt,s_titlep)) // artist search only || title found
       found=1;
    }
    break;
   case SELECTTYPE_DIRONLY:
    if((pei->entrytype==DFT_SUBDIR) && pds_utf8_filename_wildcard_cmp(pei->filename,temps))
     found=1;
    break;
  }
  if(found){
   if(selecthand_unselect){
    if(pei->infobits&PEIF_SELECTED){
     funcbit_disable(pei->infobits,PEIF_SELECTED);
     psi->selected_files--;
     selected++;
    }
   }else{
    if(!(pei->infobits&PEIF_SELECTED)){
     funcbit_enable(pei->infobits,PEIF_SELECTED);
     psi->selected_files++;
    }
    selected++;
   }
  }
 }while(++pei<=psi->lastentry);
 sprintf(sout,"%s %d file%c",((selecthand_unselect)? "Unselected":"Selected"),selected,((selected>1)? 's':' '));
 display_timed_message(sout);
 if(selected)
  refdisp|=RDT_EDITOR;
}

void playlist_editlist_groupselect_open(struct playlist_side_info *psi,unsigned int unselect)
{
 void *tw;
 selecthand_unselect=unselect;
 tw=display_textwin_allocwindow_items(NULL,TEXTWIN_FLAG_MSGCENTERALIGN|TEXTWIN_FLAG_CONFIRM|TEXTWIN_FLAG_NOWINMINSIZE,((unselect)? " Unselect files by filemask or metadata ":" Select files by filemask or metadata "),editlist_groupselect_metadata,psi);
 display_textwin_additem_editline(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,0,-1,METASELECT_WINSIZE,&selectstring[0],sizeof(selectstring)-1);
 display_textwin_openwindow_items(tw,0,0,0);
}

//--------------------------------------------------------------------------
void playlist_editlist_id3filter(struct mainvars *mvp)
{
 struct playlist_side_info *psi=mvp->psip;
 struct playlist_entry_info *pei;
 unsigned int i,found,countf,counti,allfilenums;
 char sout[64];

 if(id3filterkeyword && (psi->editsidetype&PLT_ENABLED)){
  counti=countf=0;
  allfilenums=psi->lastentry-psi->firstsong+1;
  display_clear_timed_message();
  for(pei=psi->lastentry;pei>=psi->firstsong;pei--){
   found=0;
   if(pei->infobits&PEIF_ID3EXIST){
    for(i=0;i<=I3I_MAX;i++){
     if(pei->id3info[i]){
      if(pds_utf8_is_strstri(pei->id3info[i],id3filterkeyword)){
       found=1;
       break;
      }
     }
    }
   }else{
    if(pds_utf8_is_strstri(pei->filename,id3filterkeyword))
     found=1;
   }

   if(!found)
    playlist_editlist_delfile_one(psi,pei,EDITLIST_MODE_ALL);
   else
    countf++;

   counti++;
   if(!(counti&31)){
    sprintf(sout,"Filtering: %4d/%d, Found: %d",counti,allfilenums,countf);
    display_message(0,0,sout);
    if(pds_look_extgetch()==KEY_ESC){
     pds_extgetch();
     break;
    }
   }
  }
  clear_message();
 }
}

//------------------------------------------------------------------------
void playlist_editlist_compare_directories(struct mainvars *mvp)
{
 struct playlist_side_info *psil,*psir;
 struct playlist_entry_info *peil,*peir;
 unsigned long lc,rc,right_entrynum,bothdir,unselected_files[PLAYLIST_MAX_SIDES];
 char **rfilenames,**rfns,sout[64];

 psil=playlist_editlist_tabp_get(mvp,0);
 psir=playlist_editlist_tabp_get(mvp,1);

 if(!psil || !psir || (psil->lastentry<psil->firstentry) || (psir->lastentry<psir->firstentry))
  return;

 right_entrynum=(unsigned long)(psir->lastentry-psir->firstentry+1);
 rfilenames=malloc(right_entrynum*sizeof(*rfilenames));
 if(!rfilenames)
  return;

 display_timed_message("Comparing sides... (Press ESC to terminate)");

 peir=psir->firstentry;
 rfns=rfilenames;
 rc=right_entrynum;
 do{
  if((peir->entrytype!=DFT_DRIVE) && (peir->entrytype!=DFT_SUBDIR) && (peir->entrytype!=DFT_UPDIR) && (peir->entrytype!=DFT_UPLIST)){
   *rfns=pds_strrchr(peir->filename,PDS_DIRECTORY_SEPARATOR_CHAR);
   if(!**rfns)
    *rfns=NULL;
   else
    funcbit_enable(peir->infobits,PEIF_SELECTED);
  }else
   *rfns=NULL;
  peir++;rfns++;
 }while(--rc);

 psil->selected_files=0;
 unselected_files[0]=0;
 bothdir=((psil->editsidetype&PLT_DIRECTORY) && !psil->sublistlevel && (psir->editsidetype&PLT_DIRECTORY) && !psir->sublistlevel)? 1:0;
 peil=psil->firstentry;
 lc=(unsigned long)(psil->lastentry-psil->firstentry+1);
 do{
  if((peil->entrytype!=DFT_DRIVE) && (peil->entrytype!=DFT_SUBDIR) && (peil->entrytype!=DFT_UPDIR) && (peil->entrytype!=DFT_UPLIST)){
   char *filename;
   if(bothdir)
    filename=pds_strrchr(peil->filename,PDS_DIRECTORY_SEPARATOR_CHAR);
   if(!bothdir || (filename && filename[0])){
    mpxp_filesize_t filesize=peil->filesize;
    funcbit_enable(peil->infobits,PEIF_SELECTED);
    peir=psir->firstentry;
    if(bothdir){
     unsigned long i=right_entrynum;
     char **rfn=rfilenames;
     do{
      if(*rfn && (peir->filesize==filesize) &&
//#ifdef MPXPLAY_UTF8
//       pds_utf8_stricmp(*rfn,filename) // !!! ???
//#else
       pds_stri_compare(*rfn,filename)
//#endif
      ){
       funcbit_disable(peil->infobits,PEIF_SELECTED);
       funcbit_disable(peir->infobits,PEIF_SELECTED);
      }
      peir++;rfn++;
     }while(--i);
    }else{
     do{
      peir=playlist_search_filename(psir,peil->filename,-1,peir);
      if(!peir)
       break;
      funcbit_disable(peil->infobits,PEIF_SELECTED);
      funcbit_disable(peir->infobits,PEIF_SELECTED);
      peir++;
     }while(1);
    }
    if(funcbit_test(peil->infobits,PEIF_SELECTED))
     psil->selected_files++;
    else
     unselected_files[0]++;
    if(!(lc&0xf) && pds_look_extgetch()==KEY_ESC)
     break;
   }
  }
  peil++;
 }while(--lc);

 psir->selected_files=0;
 peir=psir->firstentry;
 rfns=rfilenames;
 rc=right_entrynum;
 if(pds_look_extgetch()==KEY_ESC){
  pds_extgetch();
  do{
   if(*rfns)
    funcbit_disable(peir->infobits,PEIF_SELECTED);
   peir++;rfns++;
  }while(--rc);
 }else{
  unselected_files[1]=0;
  do{
   if(*rfns){
    if(funcbit_test(peir->infobits,PEIF_SELECTED))
     psir->selected_files++;
    else
     unselected_files[1]++;
   }
   peir++;rfns++;
  }while(--rc);

  if(!psil->selected_files && !psir->selected_files)
   display_timed_message("No difference found between the sides");
  else{
   snprintf(sout,sizeof(sout),"%d different, %d same on this side",mvp->psie->selected_files,unselected_files[mvp->editorside_selected]);
   display_timed_message(sout);
  }
 }

 free(rfilenames);
}

//------------------------------------------------------------------------
void playlist_editlist_insert_index(struct mainvars *mvp)
{
 struct playlist_side_info *psi=mvp->psip;
 struct playlist_entry_info *pei;
 struct mpxpframe_s *frp;
 struct mpxplay_infile_info_s *miis;
 if(!mvp->aktfilenum || (mvp->aktfilenum<psi->firstsong) || (mvp->aktfilenum>psi->lastentry))
  return;
 if(!playlist_editlist_addfile_one(psi,psi,mvp->aktfilenum,NULL,EDITLIST_MODE_ALL))
  return;
 pei=psi->lastentry;
 while(pei>(mvp->aktfilenum+1)){
  playlist_swap_entries(psi,pei,pei-1);
  pei--;
 }
 playlist_fulltime_del(psi,pei);
 frp=mvp->frp0;
 miis=frp->infile_infos;
 pei->pstime=(long)((float)frp->frameNum*(float)miis->timemsec/(float)frp->allframes);
 funcbit_enable(pei->infobits,PEIF_INDEXED);
 mvp->aktfilenum->petime=pei->pstime;
 funcbit_enable(mvp->aktfilenum->infobits,PEIF_INDEXED);
 mvp->aktfilenum=pei;
 playlist_pei0_set(mvp,pei,0);
 mpxplay_calculate_index_start_end(frp,mvp,mvp->pei0);
 playlist_editorhighline_set(psi,pei);
}

void playlist_editlist_delete_index(struct mainvars *mvp)
{
 struct playlist_side_info *psi=mvp->psie;
 struct playlist_entry_info *pei0=psi->editorhighline,*pei1,*aktfi=NULL;

 if(!(pei0->infobits&PEIF_INDEXED))
  return;
 pei1=pei0+1;

 if((pei1<=psi->lastentry) && pei0->petime && (pei0->petime==pei1->pstime) && (pds_strcmp(pei0->filename,pei1->filename)==0)){
  pei1->pstime=pei0->pstime;
  if((pei0==mvp->aktfilenum) || (pei1==mvp->aktfilenum))
   aktfi=pei0;
  playlist_editlist_delfile_one(psi,pei0,EDITLIST_MODE_ALL);
 }else{
  pei1=pei0-1;
  if((pei1>=psi->firstsong) && (pei1->infobits&PEIF_INDEXED) && pei0->pstime && (pei0->pstime==pei1->petime) && (pds_strcmp(pei0->filename,pei1->filename)==0)){
   if(pei0->petime)
    pei1->petime=pei0->petime;
   else if(pei0->timemsec){        //
    pei1->timemsec=pei0->timemsec; // ???
    pei1->petime=0;
   }
   if((pei0==mvp->aktfilenum) || (pei1==mvp->aktfilenum))
    aktfi=pei1;
   playlist_editlist_delfile_one(psi,pei0,EDITLIST_MODE_ALL);
   playlist_editorhighline_set(psi,pei1);
  }
 }

 if(!pei1->pstime && !pei1->petime)
  funcbit_disable(pei1->infobits,PEIF_INDEXED);
 if(aktfi){
  mvp->newfilenum=NULL;
  mvp->aktfilenum=aktfi;
  playlist_pei0_set(mvp,aktfi,0);
  mpxplay_calculate_index_start_end(mvp->frp0,mvp,mvp->pei0);
 }
}

//----------------------------------------------------------------------
struct playlist_entry_info *playlist_editlist_updatesides_add_dft(struct mainvars *mvp,char *fullname,unsigned int dft_type)
{
 struct playlist_side_info *psi=mvp->psi0;
 struct playlist_entry_info *pei,*pei_pos=NULL,pei_tmp;
 unsigned int tabnum;
 char path[MAX_PATHNAMELEN];

 pds_getpath_from_fullname(path,fullname);

 for(tabnum=0;tabnum<mvp->editorside_all_tabs;tabnum++,psi++){
  if((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel && (pds_utf8_stricmp(psi->currdir,path)==0)){
   pei=playlist_search_filename(psi,fullname,-1,NULL);
   if(!pei){
    pds_memset(&pei_tmp,0,sizeof(struct playlist_entry_info));
    funcbit_enable(pei_tmp.infobits,PEIF_ALLOCATED); // ???
    pei_tmp.filename=&fullname[0];
    pei_tmp.mdds=psi->mdds;
    if(dft_type&DFTM_PLAYLIST){
     if((desktopmode&DTM_EDIT_LOADLIST) && !(psi->psio->editsidetype&PLT_DIRECTORY))
      pei_tmp.entrytype=DFT_PLAYLIST;
     else
      pei_tmp.entrytype=DFT_SUBLIST;
    }else
     pei_tmp.entrytype=dft_type;
    pei_tmp.id3info[I3I_DFT_STORE]=playlist_loadsub_get_dftstr(pei_tmp.entrytype);
    playlist_editlist_addfile_one(NULL,psi,&pei_tmp,NULL,EDITLIST_MODE_ALL);
    pei=playlist_editlist_addfileone_postproc(psi,psi->lastentry);
    refdisp|=RDT_EDITOR;
   }
   if(psi==mvp->psie)
    pei_pos=pei;
  }
 }
 return pei_pos;
}
