//**************************************************************************
//*                     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:modify file/entry infos (ID3 tag,index)

#include "mpxplay.h"
#include "control\control.h"
#include "display\display.h"
#include "deparser\tagging.h"
#include "mpxinbuf.h"

#define EDITFILEINFOS_FLAG_IGNORE_ERRORS  (1<<0)
#define EDITFILEINFOS_FLAG_WRC_DUP_ALL    (1<<1)
#define EDITFILEINFOS_FLAG_WRC_TRIM_ALL   (1<<2)
#define EDITFILEINFOS_FLAG_WRC_MOVE_ALL   (1<<3)

typedef struct editfileinfo_s{
 unsigned long flags;
 unsigned long writetag_control;
 int retcode,retcsend;
 struct mainvars *mvp;
 unsigned long i3i_end;
 unsigned long old_pstime,old_petime;
 unsigned long filenum_curr,filenum_all;
 void *statwin_tw;
 int msg_itemnum;
 struct playlist_side_info *psi;
 struct playlist_entry_info *pei_loop;
 struct playlist_entry_info pei;
 struct mpxpframe_s fr_data;
 char filename[MAX_PATHNAMELEN];
#ifdef MPXPLAY_UTF8
 char id3infos[I3I_MAX+1][512];
#else
 char id3infos[I3I_MAX+1][256];
#endif
 char pstimestr_origi[16];
 char pstimestr_edited[16];
 char petimestr_origi[16];
 char petimestr_edited[16];
 char pltimestr_origi[16];
 char pltimestr_edited[16];
#ifdef MPXPLAY_LINK_CREDENTIALS
 struct mpxp_credentials_s cri;
#endif
}editfileinfo_s;

extern unsigned int refdisp,loadid3tag;

unsigned int playlist_fileinfo_id3order[7]={I3I_ARTIST,I3I_TITLE,I3I_ALBUM,I3I_YEAR,I3I_GENRE,I3I_TRACKNUM,I3I_COMMENT};
char *playlist_fileinfo_id3text[7]={"Artist  : ","Title   : ","Album   : ","Year    : ","Genre   : ","Track   : ","Comment : "};
static char *tagtypetext[3]={"ID3v1","ID3v2","APETag"};

static unsigned int fileinfo_build_tagtype(char *sout,struct mpxplay_infile_info_s *miis,unsigned int control_write)
{
 unsigned int i,len,tag_types=MPXPLAY_TAGTYPE_GET_FOUND(miis->standard_id3tag_support);
 unsigned int nb_tagtypes=0;
 if(tag_types){
  if(control_write)
   len=pds_strcpy(sout,"Updated tag-type(s): ");
  else
   len=0;
 }else{
  if(!control_write)
   return 0;
  tag_types=MPXPLAY_TAGTYPE_GET_PRIMARY(miis->standard_id3tag_support);
  if(!tag_types)
   return 0;
  len=pds_strcpy(sout,"Target (new) tag-type: ");
 }

 for(i=0;i<MPXPLAY_TAGTYPES_NUM;i++){
  if(tag_types&(1<<i)){
   if(nb_tagtypes)
    len+=pds_strcpy(&sout[len],", ");
   len+=pds_strcpy(&sout[len],tagtypetext[i]);
   nb_tagtypes++;
  }
 }

 return 1;
}

static display_textwin_button_t buttons_writetag[]={
 {"[ Write tags ]",0x1177}, // 'w'
 {""              ,0x1157}, // 'W'
 {"[ Cancel ]"    ,0x2e63}, // 'c'
 {""              ,0x2e43}, // 'C'
 {""              ,0x3e00}, // F4
 {""              ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t buttons_selectcontrol_dup[]={
 {"[ Dup File ]",0x2064}, // 'd'
 {""            ,0x2044}, // 'D'
 {"[ Dup All ]" ,0x1e61}, // 'a'
 {""            ,0x1e41}, // 'A'
 {"[ Trim Tag ]",0x1474}, // 't'
 {""            ,0x1454}, // 'T'
 {"[ Trim All ]",0x266c}, // 'l'
 {""            ,0x264c}, // 'L'
 {"[ Skip File ]",0x1f73}, // 's'
 {""            ,0x1f53}, // 'S'
 {"[ Cancel ]"  ,0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
 {""            ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t buttons_selectcontrol_mm_1[]={
 {"[ Dup File ]",0x2064}, // 'd'
 {""            ,0x2044}, // 'D'
 {"[ Dupl All ]",0x1e61}, // 'a'
 {""            ,0x1e41}, // 'A'
 {"[ Trim Tag ]",0x1474}, // 't'
 {""            ,0x1454}, // 'T'
 {"[ Trim All ]",0x266c}, // 'l'
 {""            ,0x264c}, // 'L'
 {NULL,0}
};

static display_textwin_button_t buttons_selectcontrol_mm_2[]={
 {"[ MoveMeta ]",0x326d}, // 'm'
 {""            ,0x324d}, // 'M'
 {"[ Move All ]",0x2f76}, // 'v'
 {""            ,0x2f56}, // 'V'
 {"[ SkipFile ]",0x1f73}, // 's'
 {""            ,0x1f53}, // 'S'
 {"[  Cancel  ]",0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
 {""            ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t buttons_errorhand[]={
 {" Ignore "    ,0x1769}, // 'i'
 {""            ,0x1749}, // 'I'
 {" Ignore All ",0x1e61}, // 'a'
 {""            ,0x1e41}, // 'A'
 {" Cancel "    ,0x2e63}, // 'c'
 {""            ,0x2e43}, // 'C'
 {""            ,KEY_ESC},// ESC
 {NULL,0}
};

static display_textwin_button_t button_errorhand_ok[]={
 {"[ Ok ]"    ,KEY_ESC}, //
 {NULL,0}
};

static int  editfileinfos_writetag_to_file(struct editfileinfo_s *efi);
static void editfileinfos_update_one(struct editfileinfo_s *efi);
static void editfileinfos_updateall_skipfile(struct editfileinfo_s *efi);
static void editfileinfos_update_all_loop(struct editfileinfo_s *efi);
#ifdef MPXPLAY_LINK_CREDENTIALS
static unsigned int fileinfo_credentials_init(struct editfileinfo_s *efi);
#endif

static void editfileinfos_statwin_close(struct editfileinfo_s *efi)
{
 if(efi && efi->statwin_tw){
  display_textwin_closewindow_buttons(efi->statwin_tw);
  efi->statwin_tw=NULL;
 }
}

static void editfileinfos_dealloc(struct editfileinfo_s *efi)
{
#ifdef __DOS__
 void *tw;
#endif
 editfileinfos_statwin_close(efi);
#ifdef __DOS__
 tw=display_textwin_openwindow_message(NULL,NULL,"Flushing disk caches ...");
 pds_drives_flush();
 display_textwin_closewindow_message(tw);
#endif

 if(efi && efi->psi)
  display_editorside_reset(efi->psi);
 else
  refdisp|=RDT_RESET_EDIT;

 if(efi){
#ifdef MPXPLAY_LINK_CREDENTIALS
  playlist_diskfile_credentials_logoff(&efi->cri);
#endif
  playlist_editlist_allocated_clear_entry(&efi->pei);
  mpxplay_infile_frame_free(&efi->fr_data);
  pds_free(efi);
 }
}

static void editfileinfos_selectcontrol_errorhandler(struct editfileinfo_s *efi,unsigned int extkey)
{
 switch(extkey){
  case 0x1e61: // 'a'
  case 0x1e41:funcbit_enable(efi->flags,EDITFILEINFOS_FLAG_WRC_DUP_ALL);
  case 0x2064: // 'd'
  case 0x2044:efi->writetag_control=MPXPLAY_WRITETAG_CNTRL_DUPFILE;break;
  case 0x266c: // 'l'
  case 0x264c:funcbit_enable(efi->flags,EDITFILEINFOS_FLAG_WRC_TRIM_ALL);
  case 0x1474: // 't'
  case 0x1454:efi->writetag_control=MPXPLAY_WRITETAG_CNTRL_TRIMTAGS;break;
  case 0x2f76: // 'v'
  case 0x2f56:funcbit_enable(efi->flags,EDITFILEINFOS_FLAG_WRC_MOVE_ALL);
  case 0x326d: // 'm'
  case 0x324d:efi->writetag_control=MPXPLAY_WRITETAG_CNTRL_MOVEMETA;break;
  case 0x1f73: // 's'
  case 0x1f53:if(efi->pei_loop){
               editfileinfos_updateall_skipfile(efi);
               return;
              }
  default:editfileinfos_dealloc(efi);return;
 }
 mpxplay_timer_addfunc(((efi->pei_loop)? editfileinfos_update_all_loop:editfileinfos_update_one),efi,(MPXPLAY_TIMERTYPE_WAKEUP|MPXPLAY_TIMERFLAG_INDOS),0);
}

static unsigned int editfileinfos_select_writecontrol(struct editfileinfo_s *efi)
{
 //const unsigned int flags=(TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN);
 const unsigned int flags=TEXTWIN_FLAG_MSGCENTERALIGN;
 void *tw;
 char sout[MAX_PATHNAMELEN+256];

 switch(efi->retcode){
  case MPXPLAY_ERROR_INFILE_WRITETAG_NOSPACE:
  case MPXPLAY_ERROR_INFILE_WRITETAG_NOSPMMRQ:break;
  default:efi->writetag_control=0;return 0;
 }

 if(!efi->writetag_control){
  if(efi->flags&EDITFILEINFOS_FLAG_WRC_DUP_ALL){
   efi->writetag_control=MPXPLAY_WRITETAG_CNTRL_DUPFILE;
   return 1;
  }else if(efi->flags&EDITFILEINFOS_FLAG_WRC_TRIM_ALL){
   efi->writetag_control=MPXPLAY_WRITETAG_CNTRL_TRIMTAGS;
   return 1;
  }else if(efi->flags&EDITFILEINFOS_FLAG_WRC_MOVE_ALL){
   efi->writetag_control=MPXPLAY_WRITETAG_CNTRL_MOVEMETA;
   return 1;
  }
 }
 efi->writetag_control=0;

 editfileinfos_statwin_close(efi);
 mpxplay_timer_deletefunc(editfileinfos_update_all_loop,efi);

 snprintf(sout,sizeof(sout),"This tag update requires file duplication\nto store all (new) informations in file\n%s",pds_getfilename_any_from_fullname(efi->filename));

 tw=display_textwin_allocwindow_items(NULL,flags," Select mode ",editfileinfos_selectcontrol_errorhandler,efi);
 display_textwin_additem_msg_alloc(tw,flags,0,-1,sout);
 display_textwin_additem_separatorline(tw,-1);
 if(efi->retcode==MPXPLAY_ERROR_INFILE_WRITETAG_NOSPMMRQ){
  display_textwin_additem_buttons(tw,flags,0,-1,buttons_selectcontrol_mm_1,NULL);
  display_textwin_additem_buttons(tw,flags,0,-1,buttons_selectcontrol_mm_2,NULL);
 }else
  display_textwin_additem_buttons(tw,flags,0,-1,buttons_selectcontrol_dup,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
 return 1;
}

static void editfileinfos_tagwrite_errorhandler(struct editfileinfo_s *efi,unsigned int extkey)
{
 switch(extkey){
  case 0x1e61:
  case 0x1e41:funcbit_enable(efi->flags,EDITFILEINFOS_FLAG_IGNORE_ERRORS);
  case 0x1769:
  case 0x1749:if(efi->pei_loop){
               editfileinfos_updateall_skipfile(efi);
               break;
              }
  default:editfileinfos_dealloc(efi);break;
 }
}

static void editfileinfos_display_errormsg(struct editfileinfo_s *efi,int error)
{
 const unsigned int flags=(TEXTWIN_FLAG_ERRORMSG|TEXTWIN_FLAG_MSGCENTERALIGN);
 unsigned int i;
 void *tw;
 char *shortfname,sout[MAX_PATHNAMELEN+256];

 editfileinfos_statwin_close(efi);
 mpxplay_timer_deletefunc(editfileinfos_update_all_loop,efi);

 shortfname=pds_getfilename_any_from_fullname(efi->filename);
 switch(error){
  case MPXPLAY_ERROR_INFILE_CANTOPEN:i=pds_strcpy(sout,"Open/write error (read-only or not exists) at");break;
  case MPXPLAY_ERROR_INFILE_READONLYFS:i=pds_strcpy(sout,"Read only filesystem / filetype at");break;
  case MPXPLAY_ERROR_INFILE_WRITETAG_FILETYPE:i=pds_strcpy(sout,"Tag-write is not supported for this filetype");break;
  case MPXPLAY_ERROR_INFILE_WRITETAG_TAGTYPE:i=pds_strcpy(sout,"Wrong or unsupported tagtype in");break;
  case MPXPLAY_ERROR_FILEHAND_CANTWRITE:i=pds_strcpy(sout,"Cannot write to file");break;
  case MPXPLAY_ERROR_FILEHAND_RENAME:i=pds_strcpy(sout,"Tag-write failed at rename!\nCheck the input file before you continue!");break;
  case MPXPLAY_ERROR_FILEHAND_USERABORT:i=pds_strcpy(sout,"Aborted by user at");break;
  default:i=sprintf(sout,"Tag-write (%d) error at",error);break;
 }
 i+=pds_strcpy(&sout[i],"\n");
 i+=pds_strcpy(&sout[i],shortfname);
 switch(error){
  case MPXPLAY_ERROR_FILEHAND_RENAME:break;
  default:pds_strcat(&sout[i],"\nTags are not saved in the file!");break;
 }

 tw=display_textwin_allocwindow_items(NULL,flags," Error ",editfileinfos_tagwrite_errorhandler,efi);
 display_textwin_additem_msg_alloc(tw,flags,0,-1,sout);
 switch(error){
  case MPXPLAY_ERROR_FILEHAND_RENAME:
  case MPXPLAY_ERROR_FILEHAND_USERABORT:display_textwin_additem_buttons(tw,flags,0,-1,button_errorhand_ok,NULL);break;
  default:
   if(efi->pei_loop){
    display_textwin_additem_msg_static(tw,flags,0,-1,"");
    display_textwin_additem_buttons(tw,flags,0,-1,buttons_errorhand,NULL);
   }else
    display_textwin_additem_buttons(tw,flags,0,-1,button_errorhand_ok,NULL);
   break;
 }
 display_textwin_openwindow_items(tw,0,0,0);
}

static void editfileinfos_statwin_keycheck(struct editfileinfo_s *efi,unsigned int extkey)
{
 if(extkey==KEY_ESC)
  efi->retcode=MPXPLAY_ERROR_FILEHAND_USERABORT;
}

static void editfileinfos_display_statwin(struct editfileinfo_s *efi)
{
 long percent_group=(long)(100.0*((float)(efi->filenum_curr+1))/(float)efi->filenum_all);
 char barstr_group[DISKFILE_FILECOPY_BARBUFLEN];
 char filename[MAX_PATHNAMELEN];
 char msg[MAX_PATHNAMELEN+64+DISKFILE_FILECOPY_BARBUFLEN];

 pds_strcpy(filename,pds_getfilename_any_from_fullname(efi->filename));
 pds_utf8_str_centerize(filename,DISKFILE_FILECOPY_BARLEN,0);

 playlist_diskfile_filecopy_makebar(barstr_group,percent_group);
 snprintf(msg,sizeof(msg),"Writing the tag in\n%s\nTotal %d/%d\n%s",filename,
  efi->filenum_curr+1,efi->filenum_all,barstr_group);

 if(efi->statwin_tw)
  display_textwin_update_msg(efi->statwin_tw,efi->msg_itemnum,msg);
 else{
  efi->statwin_tw=display_textwin_allocwindow_items(efi->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN|TEXTWIN_FLAG_DONTCLOSE," Edit TAGs ",editfileinfos_statwin_keycheck,efi);
  efi->msg_itemnum=display_textwin_additem_msg_table(efi->statwin_tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,msg);
  display_textwin_openwindow_items(efi->statwin_tw,0,0,0);
 }
}

static void editfileinfos_maketimemstr(char *str,unsigned long msec)
{
 sprintf(str,"%3d:%2.2d.%2.2d",(unsigned long)(msec/(60*1000)),(unsigned long)(msec%(60*1000)/1000),(unsigned long)(msec%1000/10));
}

static void editfileinfos_update_index(struct editfileinfo_s *efi)
{
 if(efi->pei.infobits&PEIF_INDEXED){
  struct playlist_entry_info *pei=&efi->pei;
  unsigned long hextime;
  long diff;

  if(pds_strcmp(efi->pltimestr_origi,efi->pltimestr_edited)!=0){
   unsigned long lentime;
   hextime=pds_strtime_to_hexhtime(efi->pltimestr_edited);
   lentime=(((hextime>>24)&0xff)*(60*60*1000))+(((hextime>>16)&0xff)*(60*1000))
          +(((hextime>> 8)&0xff)*1000)+((hextime&0xff)*10);
   pei->petime=pei->pstime+lentime;
  }
  if(pds_strcmp(efi->petimestr_origi,efi->petimestr_edited)!=0){
   hextime=pds_strtime_to_hexhtime(efi->petimestr_edited);
   pei->petime=(((hextime>>24)&0xff)*(60*60*1000))+(((hextime>>16)&0xff)*(60*1000))
                  +(((hextime>> 8)&0xff)*1000)+((hextime&0xff)*10);
   if(efi->old_petime)
    diff=(long)efi->old_petime-(long)pei->petime;
   else
    diff=(long)pei->timemsec-(long)pei->petime;
   if((diff<10) && (diff>=0)) // small diff (inside rounding error), petime is not modified
    pei->petime=efi->old_petime;
  }
  if(pei->petime){
   if(pei->petime>pei->timemsec)
    pei->petime=0;
  }
  if(pds_strcmp(efi->pstimestr_origi,efi->pstimestr_edited)!=0){
   unsigned long new_pstime;
   hextime=pds_strtime_to_hexhtime(efi->pstimestr_edited);
   new_pstime=(((hextime>>24)&0xff)*(60*60*1000))+(((hextime>>16)&0xff)*(60*1000))
            +(((hextime>> 8)&0xff)*1000)+((hextime&0xff)*10);
   diff=(long)efi->old_pstime-(long)new_pstime;
   if((diff>=10) || (diff<0)){
    if((new_pstime<=pei->timemsec) && (!pei->petime || (new_pstime<=pei->petime)))
     pei->pstime=new_pstime;
   }
  }
 }
}

static void editfileinfos_set_id3info(struct editfileinfo_s *efi,unsigned int clear_blank)
{
 unsigned int i;
 struct playlist_entry_info *pei=&efi->pei;
 playlist_editlist_add_filename(NULL,pei,efi->filename);
 for(i=0;i<=I3I_MAX;i++)
  if(efi->id3infos[i][0] || clear_blank)
   playlist_editlist_add_id3_one(NULL,pei,playlist_fileinfo_id3order[i],&efi->id3infos[i][0],-1);
 efi->old_pstime=pei->pstime;
 efi->old_petime=pei->petime;
}

static void editfileinfos_update_aktfile(struct editfileinfo_s *efi)
{
 struct playlist_entry_info *pei=efi->mvp->pei0;
 if(pds_utf8_stricmp(pei->filename,efi->filename)==0 && (pei->pstime==efi->old_pstime) && (pei->petime==efi->old_petime)){
  playlist_pei0_set(efi->mvp,&efi->pei,0);
  if(efi->pei.infobits&PEIF_INDEXED)
   mpxplay_calculate_index_start_end(efi->mvp->fr_primary,efi->mvp,&efi->pei);
 }
}

static void editfileinfos_update_editor(struct editfileinfo_s *efi)
{
 struct playlist_side_info *psi=efi->mvp->psi0;
 struct playlist_entry_info *pei,*lastpei;
 unsigned int tabnum,modify;

 for(tabnum=0;tabnum<efi->mvp->editorside_all_tabs;tabnum++,psi++){
  pei=lastpei=NULL;
  do{
   pei=playlist_search_filename(psi,efi->filename,((efi->old_pstime)? efi->old_pstime:-1),pei,0);
   if(!pei)
    break;
   if((pei->pstime!=efi->old_pstime) || (pei->petime!=efi->old_petime)){
    pei++;
    continue;
   }
   if((pei->infobits&PEIF_SELECTED) && (psi==efi->psi) && (pei!=efi->pei_loop)){
    funcbit_disable(pei->infobits,PEIF_SELECTED);
    if(efi->psi->selected_files)
     efi->psi->selected_files--;
    efi->filenum_curr++;
   }
   modify=EDITLIST_MODE_ID3;
   if(pei->infobits&PEIF_INDEXED)
    modify|=EDITLIST_MODE_HEAD|EDITLIST_MODE_INDEX;
   playlist_editlist_delfile_one(psi,pei,modify);
   playlist_editlist_addfile_one(NULL,psi,&efi->pei,pei,modify);
   lastpei=pei;
   pei++;
  }while(1);

  // !!! auto correct index end/start of prev/next entry (end of prev index eq to start of curr index)
  if(lastpei && (lastpei->infobits&PEIF_INDEXED)){
   if(lastpei>psi->firstsong){
    struct playlist_entry_info *ppn=lastpei-1;
    if((ppn->infobits&PEIF_INDEXED) && (ppn->petime==efi->old_pstime) && (pds_utf8_stricmp(ppn->filename,efi->filename)==0))
     ppn->petime=efi->pei.pstime;
   }
   if(lastpei<psi->lastentry){
    struct playlist_entry_info *ppn=lastpei+1;
    if((ppn->infobits&PEIF_INDEXED) && (ppn->pstime==efi->old_petime) && (pds_utf8_stricmp(ppn->filename,efi->filename)==0))
     ppn->pstime=efi->pei.petime;
   }
  }
 }
 refdisp|=RDT_EDITOR;
}

static int editfileinfos_writetag_to_file(struct editfileinfo_s *efi)
{
 struct mpxpframe_s *frp=&efi->fr_data;
 struct playlist_entry_info *pei=&efi->pei;
 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=efi->psi;
 frp->pei=pei;
 return mpxplay_infile_write_id3tag(frp,efi->filename,&pei->id3info[0],efi->writetag_control);
}

static void editfileinfos_update_one(struct editfileinfo_s *efi)
{
 int error;

 if(efi->retcode==MPXPLAY_ERROR_FILEHAND_USERABORT){
  editfileinfos_display_errormsg(efi,efi->retcode);
  return;
 }

 efi->retcode=error=MPXPLAY_ERROR_INFILE_OK;

 if(efi->pei_loop){
  editfileinfos_display_statwin(efi);
  editfileinfos_set_id3info(efi,0);
 }else
  editfileinfos_set_id3info(efi,1);

 if(efi->pei.infobits&PEIF_INDEXED)
  goto err_out_uo;

 error=editfileinfos_writetag_to_file(efi);

 if((efi->flags&EDITFILEINFOS_FLAG_IGNORE_ERRORS) && (error!=MPXPLAY_ERROR_INFILE_OK) && (error!=MPXPLAY_ERROR_INFILE_WRITETAG_NOSPACE) && (error!=MPXPLAY_ERROR_INFILE_WRITETAG_NOSPMMRQ))
  goto err_out_uo;

 efi->retcode=error;

 if(editfileinfos_select_writecontrol(efi))
  goto err_out_uo;

 if(efi->pei_loop && (error!=MPXPLAY_ERROR_INFILE_OK))
  mpxplay_timer_deletefunc(editfileinfos_update_all_loop,efi);

#ifdef MPXPLAY_LINK_CREDENTIALS
 if(fileinfo_credentials_init(efi))
  goto err_out_uo;
#endif

 if(error!=MPXPLAY_ERROR_INFILE_OK)
  editfileinfos_display_errormsg(efi,error);

err_out_uo:
 if((efi->retcode==MPXPLAY_ERROR_INFILE_OK) || (efi->retcode==MPXPLAY_ERROR_INFILE_READONLYFS) || (efi->retcode==MPXPLAY_ERROR_INFILE_WRITETAG_FILETYPE)){
  editfileinfos_update_index(efi);
  editfileinfos_update_aktfile(efi);
  editfileinfos_update_editor(efi);
 }

 if(!efi->pei_loop && (efi->retcode==MPXPLAY_ERROR_INFILE_OK)){
  funcbit_enable(efi->psi->editloadtype,(PLL_CHG_MANUAL|PLL_CHG_ID3));
  editfileinfos_dealloc(efi);
 }
}

static void editfileinfos_update_all_loop(struct editfileinfo_s *efi)
{
 struct playlist_side_info *psi=efi->psi;
 struct playlist_entry_info *pei=efi->pei_loop;

 if(pei>psi->lastentry){
  mpxplay_timer_deletefunc(editfileinfos_update_all_loop,efi);
  editfileinfos_dealloc(efi);
  return;
 }
 if((pei->infobits&PEIF_SELECTED) && (GET_HFT(pei->entrytype)!=HFT_DFT)){
  playlist_editlist_allocated_copy_entry(&efi->pei,pei);
  playlist_chkentry_get_onefileinfos_allagain(psi,&efi->pei,&efi->fr_data,((loadid3tag)? (loadid3tag|ID3LOADMODE_FILE|ID3LOADMODE_CLFN):ID3LOADMODE_ALL),MPXPLAY_INFILE_OPENMODE_CHECK);
  mpxplay_infile_close(&efi->fr_data);
  pds_strcpy(efi->filename,pei->filename);
  editfileinfos_update_one(efi);
  if(efi->retcode!=MPXPLAY_ERROR_INFILE_OK)
   return;
 }
 editfileinfos_updateall_skipfile(efi);
}

static void editfileinfos_updateall_skipfile(struct editfileinfo_s *efi)
{
 struct playlist_entry_info *pei=efi->pei_loop;
 if(pei){
  if(pei->infobits&PEIF_SELECTED){
   funcbit_disable(pei->infobits,PEIF_SELECTED);
   if(efi->psi->selected_files)
    efi->psi->selected_files--;
   refdisp|=RDT_EDITOR;
   efi->filenum_curr++;
  }
  pei++;
  efi->pei_loop=pei;
  mpxplay_timer_addfunc(editfileinfos_update_all_loop,efi,MPXPLAY_TIMERTYPE_REPEAT|MPXPLAY_TIMERFLAG_LOWPRIOR,0);
 }
}

#ifdef MPXPLAY_LINK_CREDENTIALS
static unsigned int fileinfo_credentials_init(struct editfileinfo_s *efi)
{
 struct mpxp_credentials_s *cri=&efi->cri;
 unsigned int do_cr;
 efi->retcsend=efi->retcode;
 cri->retcodep=&efi->retcsend; // to not overwrite efi->retcode
 cri->filename=efi->filename;
 cri->passdata=efi;
 cri->passfunc_retry=(void *)((efi->pei_loop)? editfileinfos_update_all_loop:editfileinfos_update_one);
 cri->passfunc_skip=(void *)editfileinfos_updateall_skipfile;
 cri->passfunc_dealloc=(void *)editfileinfos_dealloc;
 cri->passfunc_statwinclose=(void *)editfileinfos_statwin_close;
 do_cr=( (efi->retcode==MPXPLAY_ERROR_FILEHAND_DELETE) || (efi->retcode==MPXPLAY_ERROR_FILEHAND_RENAME)
  || (efi->retcode==MPXPLAY_ERROR_FILEHAND_CANTCREATE) || (efi->retcode==MPXPLAY_ERROR_FILEHAND_CANTWRITE)
  || (efi->retcode==MPXPLAY_ERROR_FILEHAND_CHANGEATTR) || (efi->retcode==MPXPLAY_ERROR_INFILE_CANTOPEN) )? 1:0;
 return playlist_diskfile_credentials_errorhand(cri,do_cr);
}
#endif

static void editfileinfos_update_start_all(struct editfileinfo_s *efi,unsigned int extkey)
{
 switch(extkey){
  case 0x1177:           // 'w'
  case 0x1157:break;     // 'W'
  default:
   mpxplay_infile_frame_free(&efi->fr_data);
   pds_free(efi);
   return;
 }
 funcbit_enable(efi->psi->editloadtype,(PLL_CHG_MANUAL|PLL_CHG_ID3));
 mpxplay_timer_addfunc(editfileinfos_update_all_loop,efi,MPXPLAY_TIMERTYPE_REPEAT,0);
}

static void editfileinfos_update_start_one(struct editfileinfo_s *efi,unsigned int extkey)
{
 switch(extkey){
  case 0x1177:           // 'w'
  case 0x1157:editfileinfos_update_one(efi);break;
  default:editfileinfos_dealloc(efi);break;
 }
}

void playlist_fileinfo_edit_infos(struct mainvars *mvp)
{
 struct playlist_side_info *psi=mvp->psie;
 struct playlist_entry_info *pei=psi->editorhighline;
 struct mpxpframe_s *frp;
 struct mpxplay_infile_info_s *miis;
 struct editfileinfo_s *efi;
 unsigned int i,y,error=0;
 void *tw;
 mpxp_uint8_t peichecked[I3I_MAX+1];
 char *shortfname,sout[128];

 if(!psi->selected_files){
  if((pei->entrytype&DFTM_DFT) && ((pei->entrytype&DFTM_DRIVE) || (pei->entrytype&DFTM_DIR) || (pei->entrytype==DFT_UPLIST))){
   display_textwin_openwindow_errormessage("Cannot edit id3 info of drive/directory/uplist!");
   return;
  }
 }

 if(psi->id3infolastp>psi->id3infoendp){
  display_textwin_openwindow_errormsg_head_ok(" Edit ID3 ","No free space to edit id3-tags!\nTry to delete some files from the playlist...");
  return;
 }

 efi=(editfileinfo_s *)pds_calloc(1,sizeof(struct editfileinfo_s));
 if(!efi)
  return;
 efi->mvp=mvp;
 efi->psi=psi;
 frp=&efi->fr_data;
 if(!mpxplay_infile_frame_alloc(frp))
  goto err_out_fei;
 miis=frp->infile_infos;

 if(psi->selected_files){
  efi->filenum_all=psi->selected_files;
  efi->pei_loop=psi->firstentry;
  efi->i3i_end=I3I_MAX;

  tw=display_textwin_allocwindow_items(NULL,0," Edit multiply ID3s ",editfileinfos_update_start_all,efi);
  sprintf(sout," Selected %d file(s)",psi->selected_files);
  display_textwin_additem_msg_alloc(tw,0,0,0,sout);
  y=2;

  pds_memset(peichecked,0,sizeof(peichecked));
  pei=psi->firstentry;
  do{
   if(pei->infobits&PEIF_SELECTED){
    unsigned int c=0;
    for(i=0;i<=I3I_MAX;i++){
     unsigned int index=playlist_fileinfo_id3order[i];
     if(!peichecked[i]){
      if(!efi->id3infos[i][0] && pei->id3info[index]){
       unsigned int maxlen=sizeof(efi->id3infos[i])-1;
       pds_strncpy(&efi->id3infos[i][0],pei->id3info[index],maxlen);
       efi->id3infos[i][maxlen]=0;
       peichecked[i]=1;
      }
     }else if(!efi->id3infos[i][0])
      c++;
     else if(pei->id3info[index] && (pds_stricmp(efi->id3infos[i],pei->id3info[index])!=0))
      efi->id3infos[i][0]=0;
    }
    if(c==i)
     break;
   }
  }while(++pei<=psi->lastentry);

  for(i=0;i<=efi->i3i_end;i++){
   display_textwin_additem_msg_static(tw,0,0,y,playlist_fileinfo_id3text[i]);
   display_textwin_additem_editline(tw,0,0,pds_strlen(playlist_fileinfo_id3text[i]),y,50,efi->id3infos[i],sizeof(efi->id3infos[i])-1);
   y+=2;
  }

  display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y,"note: blank tags are not modified in the file(s)");

 }else{

  if(pei->infobits&PEIF_INDEXED){
   tw=display_textwin_allocwindow_items(NULL,0," Edit index infos ",editfileinfos_update_start_one,efi);
   efi->i3i_end=1;
  }else{
   playlist_chkentry_get_onefileinfos_allagain(psi,pei,frp,((loadid3tag)? (loadid3tag|ID3LOADMODE_FILE):ID3LOADMODE_ALL),MPXPLAY_INFILE_OPENMODE_CHECK);
   tw=display_textwin_allocwindow_items(NULL,0," Edit ID3 ",editfileinfos_update_start_one,efi);
   efi->i3i_end=I3I_MAX;
   if((pei->entrytype<DFT_AUDIOFILE) || (!pei->infile_funcs->write_id3tag && !MPXPLAY_TAGTYPE_GET_SUPPORT(miis->standard_id3tag_support)))
    error=1;
  }
  shortfname=pds_getfilename_any_from_fullname(pei->filename);
  display_textwin_additem_msg_static(tw,0,0,0,"Filename: ");
  display_textwin_additem_msg_alloc(tw,0,sizeof("Filename: ")-1,0,shortfname);
  y=2;

  playlist_editlist_allocated_copy_entry(&efi->pei,pei);

  pds_strcpy(efi->filename,pei->filename);
  for(i=0;i<=I3I_MAX;i++){
   unsigned int index=playlist_fileinfo_id3order[i];
   if(pei->id3info[index])
    pds_strncpy(efi->id3infos[i],pei->id3info[index],sizeof(efi->id3infos[i])-1);
  }

  for(i=0;i<=efi->i3i_end;i++){
   display_textwin_additem_msg_static(tw,0,0,y,playlist_fileinfo_id3text[i]);
   display_textwin_additem_editline(tw,0,0,pds_strlen(playlist_fileinfo_id3text[i]),y,50,efi->id3infos[i],sizeof(efi->id3infos[i])-1);
   y+=2;
  }
  if(pei->infobits&PEIF_INDEXED){
#ifdef MPXPLAY_GUI_CONSOLE
   const unsigned int sflags=0;
   const unsigned int eflags=TEXTWIN_EDITFLAG_OVERWRITE|TEXTWIN_EDITFLAG_NUMERIC|TEXTWIN_EDITFLAG_NOSTRZ;
   unsigned int x=1;
#else
   const unsigned int sflags=TEXTWIN_FLAG_MSGCENTERALIGN;
   const unsigned int eflags=TEXTWIN_FLAG_MSGCENTERALIGN|TEXTWIN_EDITFLAG_OVERWRITE;
   unsigned int x=0;
#endif
   unsigned long endtime=((pei->petime)? pei->petime:pei->timemsec);
   unsigned long lentime=endtime-((pei->pstime<=endtime)? pei->pstime:endtime);

   display_textwin_additem_msg_static(tw,sflags,x,y,"Start time:");
   editfileinfos_maketimemstr(efi->pstimestr_origi,pei->pstime);
   pds_strcpy(efi->pstimestr_edited,efi->pstimestr_origi);
#ifdef MPXPLAY_GUI_CONSOLE
   x+=sizeof("Start time:")-1;
   display_textwin_additem_editline(tw,0,eflags,x,y,3,efi->pstimestr_edited,3); x+=3;
   display_textwin_additem_msg_static(tw,0,x,y,&efi->pstimestr_edited[3]);      x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,2,&efi->pstimestr_edited[4],2); x+=2;
   display_textwin_additem_msg_static(tw,0,x,y,&efi->pstimestr_edited[6]);      x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,2,&efi->pstimestr_edited[7],2); x+=2+2;
#else
   x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,8,efi->pstimestr_edited,sizeof(efi->pstimestr_edited)-1); x+=15;
#endif

   display_textwin_additem_msg_static(tw,sflags,x,y,"Length:");
   editfileinfos_maketimemstr(efi->pltimestr_origi,lentime);
   pds_strcpy(efi->pltimestr_edited,efi->pltimestr_origi);
#ifdef MPXPLAY_GUI_CONSOLE
   x+=sizeof("Length:")-1;
   display_textwin_additem_editline(tw,0,eflags,x,y,3,efi->pltimestr_edited,3); x+=3;
   display_textwin_additem_msg_static(tw,0,x,y,&efi->pltimestr_edited[3]);      x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,2,&efi->pltimestr_edited[4],2); x+=2;
   display_textwin_additem_msg_static(tw,0,x,y,&efi->pltimestr_edited[6]);      x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,2,&efi->pltimestr_edited[7],2); x+=2+2;
#else
   x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,8,efi->pltimestr_edited,sizeof(efi->pltimestr_edited)-1); x+=15;
#endif

   display_textwin_additem_msg_static(tw,sflags,x,y,"End time:");
   editfileinfos_maketimemstr(efi->petimestr_origi,endtime);
   pds_strcpy(efi->petimestr_edited,efi->petimestr_origi);
#ifdef MPXPLAY_GUI_CONSOLE
   x+=sizeof("End time:")-1;
   display_textwin_additem_editline(tw,0,eflags,x,y,3,efi->petimestr_edited,3); x+=3;
   display_textwin_additem_msg_static(tw,0,x,y,&efi->petimestr_edited[3]);      x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,2,&efi->petimestr_edited[4],2); x+=2;
   display_textwin_additem_msg_static(tw,0,x,y,&efi->petimestr_edited[6]);      x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,2,&efi->petimestr_edited[7],2); x+=2+2;
#else
   x++;
   display_textwin_additem_editline(tw,0,eflags,x,y,8,efi->petimestr_edited,sizeof(efi->petimestr_edited)-1);
#endif

   y++;
  }else{
   if(error)
    display_textwin_additem_msg_static(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y,"Warning: cannot save the modifications in the (audio)file!");
   else if(fileinfo_build_tagtype(sout,miis,1))
    display_textwin_additem_msg_alloc(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,y,sout);
   mpxplay_infile_close(frp);
  }
 }
 display_textwin_additem_separatorline(tw,-1);
 display_textwin_additem_buttons(tw,TEXTWIN_FLAG_MSGCENTERALIGN,0,-1,buttons_writetag,NULL);
 display_textwin_openwindow_items(tw,0,0,0);
 return;

err_out_fei:
 editfileinfos_dealloc(efi);
}
