//**************************************************************************
//*                     This file is part of the                           *
//*                      Mpxplay - audio player.                           *
//*                  The source code of Mpxplay is                         *
//*        (c) copyright 1998-2014 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:sort/order list

//#define MPXPLAY_USE_DEBUGF 1
#define MPXPLAY_DEBUG_OUTPUT stdout

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

#define SORTLIST_MPXTABS_ORDERKEYS_FLAG_DESCENDING  (1<<31)
#define SORTLIST_MPXTABS_ORDERKEYS_KEY_BITS     5
#define SORTLIST_MPXTABS_ORDERKEYS_KEY_MASK     ((1<<SORTLIST_MPXTABS_ORDERKEYS_KEY_BITS) - 1)
#if ((PLAYLIST_MAX_ORDERKEYS * SORTLIST_MPXTABS_ORDERKEYS_KEY_BITS) > 31)
#error Sortlist: SORTLIST_MPXTABS_ORDERKEYS_KEY_MASK
#endif

typedef int (*qsc_t)(const void *,const void *);

static void correct_pfilenums(struct playlist_side_info *psi,struct playlist_entry_info *pei_begin,struct playlist_entry_info *pei_end);
static void playlist_order_dft_block(struct playlist_side_info *psi,struct playlist_entry_info *firstentry,struct playlist_entry_info *lastentry);

extern unsigned int desktopmode, mpxplay_sortcontrol;
extern unsigned int playlist_orderkeys[PLAYLIST_MAX_ORDERKEYS];

static unsigned int sortlist_initialized, sortlist_sortctrl;

#ifdef MPXPLAY_UTF8

 #define check_order_str(strp1,strp2) pds_utf8_stricmp(strp1,strp2)

/*static mpxp_wchar_t order_table[65536+32];

static int check_order_str(mpxp_uint8_t *strp1,mpxp_uint8_t *strp2)
{
 if(!strp1 || !strp1[0])
  if(strp2 && strp2[0])
   return -1;
  else
   return 0;
 if(!strp2 || !strp2[0])
  if(strp1 && strp1[0])
   return 1;
  else
   return 0;

 do{
  mpxp_uint8_t codesize,u08;
  mpxp_uint16_t u16_1,u16_2;

  codesize=0;
  u08=*strp1;
  if(u08&0x80){
   if((u08&0xe0)==0xe0){
    u16_1 = (u08&0x0F) << 12;
    u08 = strp1[1];
    if(u08&0x80){
     u16_1 |= (u08&0x3F) << 6;
     u08 = strp1[2];
     if(u08&0x80){
      u16_1 |= (u08&0x3F);
      codesize=3;
     }
    }
   }else{
    u16_1 = (u08&0x3F) << 6;
    u08 = strp1[1];
    if(u08&0x80){
     u16_1 |= (u08&0x3F);
     codesize=2;
    }
   }
  }
  if(!codesize)
   u16_1=*strp1++;
  else
   strp1+=codesize;
  if(!u16_1)
   break;

  codesize=0;
  u08=*strp2;
  if(u08&0x80){
   if((u08&0xe0)==0xe0){
    u16_2 = (u08&0x0F) << 12;
    u08 = strp2[1];
    if(u08&0x80){
     u16_2 |= (u08&0x3F) << 6;
     u08 = strp2[2];
     if(u08&0x80){
      u16_2 |= (u08&0x3F);
      codesize=3;
     }
    }
   }else{
    u16_2 = (u08&0x3F) << 6;
    u08 = strp2[1];
    if(u08&0x80){
     u16_2 |= (u08&0x3F);
     codesize=2;
    }
   }
  }
  if(!codesize)
   u16_2=*strp2++;
  else
   strp2+=codesize;
  if(!u16_2)
   break;

  //switch(CompareStringW(LOCALE_USER_DEFAULT,NORM_IGNORECASE,&u16_1,1,&u16_2,1)){
  // case CSTR_LESS_THAN:return -1;
  // case CSTR_GREATER_THAN:return 1;
  //}

  u16_1=order_table[u16_1];
  u16_2=order_table[u16_2];

  if(u16_1!=u16_2){
   if(u16_1<u16_2)
    return -1;
   else
    return 1;
  }
 }while(1);

 return 0;
}*/

#else
extern char sortorder_string[256];
static char order_table[256];

static int check_order_str(char *strp1,char *strp2)
{
 char c1,c2;

 if(!strp1 || !strp1[0])
  if(strp2 && strp2[0])
   return -1;
  else
   return 0;
 if(!strp2 || !strp2[0])
  if(strp1 && strp1[0])
   return 1;
  else
   return 0;

 do{
  c1=order_table[*strp1];
  c2=order_table[*strp2];
  if(c1!=c2){
   if(c1<c2)
    return -1;
   else
    return 1;
  }
  strp1++;
  strp2++;
 }while(c1);
 return 0;
}
#endif

static int check_order_id3inf_title(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 char *s0,*s1;

 s0=pei0->id3info[I3I_TITLE];
 if(!s0)
  s0=pei0->id3info[I3I_ARTIST];
 if(!s0)
  s0=pds_getfilename_from_fullname(pei0->filename);

 s1=pei1->id3info[I3I_TITLE];
 if(!s1)
  s1=pei1->id3info[I3I_ARTIST];
 if(!s1)
  s1=pds_getfilename_from_fullname(pei1->filename);

 return check_order_str(s0,s1);
}

static int check_order_id3inf_artist(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 char *s0,*s1;

 s0=pei0->id3info[I3I_ARTIST];
 if(!s0)
  s0=pei0->id3info[I3I_TITLE];
 if(!s0)
  s0=pds_getfilename_from_fullname(pei0->filename);

 s1=pei1->id3info[I3I_ARTIST];
 if(!s1)
  s1=pei1->id3info[I3I_TITLE];
 if(!s1)
  s1=pds_getfilename_from_fullname(pei1->filename);

 return check_order_str(s0,s1);
}

static int check_order_id3inf_album(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 return check_order_str(pei0->id3info[I3I_ALBUM],pei1->id3info[I3I_ALBUM]);
}

static int check_order_id3inf_year(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 return check_order_str(pei0->id3info[I3I_YEAR],pei1->id3info[I3I_YEAR]);
}

static int check_order_id3inf_comment(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 return check_order_str(pei0->id3info[I3I_COMMENT],pei1->id3info[I3I_COMMENT]);
}

static int check_order_id3inf_genre(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 return check_order_str(pei0->id3info[I3I_GENRE],pei1->id3info[I3I_GENRE]);
}

static int check_order_id3inf_tracknum(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 long t1=pds_atol(pei0->id3info[I3I_TRACKNUM]);
 long t2=pds_atol(pei1->id3info[I3I_TRACKNUM]);
 if(t1<t2)
  return -1;
 if(t1==t2)
  return 0;
 return 1;
}

static int check_order_time(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 unsigned long t1=playlist_entry_get_timemsec(pei0);
 unsigned long t2=playlist_entry_get_timemsec(pei1);
 if(t1<t2)
  return -1;
 if(t1==t2)
  return 0;
 return 1;
}

static int check_order_dft(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 unsigned long head1 = pei0->entrytype;
 unsigned long head2 = pei1->entrytype;
 int res;

 if(mpxplay_sortcontrol & PLAYLIST_SORTCONTROL_NOLISTTOP){
  if(head1 == DFT_SUBLIST)
   head1 = DFT_AUDIOFILE;
  if(head2 == DFT_SUBLIST)
   head2 = DFT_AUDIOFILE;
 }
 if(!(head1 & DFTM_DFT))
  head1 = DFT_AUDIOFILE;
 if(!(head2 & DFTM_DFT))
  head2 = DFT_AUDIOFILE;

 if(head1 == head2)
  return 0;

 if(head1 < head2)
  res = -1;
 else
  res = 1;

 if(sortlist_sortctrl & PLAYLIST_SORTCONTROL_DESCENDING) // to be always ascending
  res = -res;

 return res;
}

static int check_order_filesize(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 mpxp_filesize_t f1=pei0->filesize;
 mpxp_filesize_t f2=pei1->filesize;

 if(f1<f2)
  return -1;
 if(f1==f2)
  return 0;
 return 1;
}

static int check_order_path(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 char path1[MAX_PATHNAMELEN],path2[MAX_PATHNAMELEN];
 pds_getpath_from_fullname(path1,pei0->filename);
 pds_getpath_from_fullname(path2,pei1->filename);
 return check_order_str(path1,path2);
}

static int check_order_filename(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 char *filename1,*filename2;
 filename1=pds_getfilename_from_fullname(pei0->filename);
 filename2=pds_getfilename_from_fullname(pei1->filename);
 return check_order_str(filename1,filename2);
}

static int check_order_fileext(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 char *ext1,*ext2;
 ext1=pds_strrchr(pei0->filename,'.');
 if(ext1)
  ext1++;
 ext2=pds_strrchr(pei1->filename,'.');
 if(ext2)
  ext2++;
 return check_order_str(ext1,ext2);
}

static int check_order_pathfile(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 return check_order_str(pei0->filename,pei1->filename);
}

static int check_order_filedate(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 mpxp_uint32_t date0=PDS_GETB_LE32(&pei0->filedate); // !!! sizeof(pds_fdate_t)==sizeof(mpxp_uint32_t)
 mpxp_uint32_t date1=PDS_GETB_LE32(&pei1->filedate);

 if(date0<date1)
  return -1;
 if(date0==date1)
  return 0;
 return 1;
}

static int check_order_index_pst(struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 if((pei0->infobits&PEIF_INDEXED) && (pei1->infobits&PEIF_INDEXED) && (pds_utf8_stricmp(pei0->filename,pei1->filename)==0)){
  if(pei0->pstime<pei1->pstime)
   return -1;
  if(pei0->pstime>pei1->pstime)
   return 1;
 }
 return 0;
}

static check_order_func_t *check_order_funcs[]={
 &check_order_id3inf_title,
 &check_order_id3inf_artist,
 &check_order_id3inf_album,
 &check_order_id3inf_year,
 &check_order_id3inf_comment,
 &check_order_id3inf_genre,
 &check_order_id3inf_tracknum,
 &check_order_time,
 &check_order_path,
 &check_order_filename,
 &check_order_pathfile,
 &check_order_fileext,
 &check_order_filesize,
 &check_order_filedate,
 &check_order_dft,
 &check_order_index_pst,
 NULL
};

static check_order_func_t **side_orderfuncp;

#if defined(__WATCOMC__) && (PLAYLIST_MAX_ORDERKEYS==5) && (PLAYLIST_SORTCONTROL_DESCENDING==1)

int asm_coqs(const struct playlist_entry_info *pei0,const struct playlist_entry_info *pei1);

#pragma aux asm_coqs=\
 "mov ebx,eax"\
 "mov ecx,edx"\
 "mov edi,5"\
 "mov esi,dword ptr side_orderfuncp"\
 "back1:"\
  "cmp dword ptr [esi],0"\
  "je endnull"\
  "mov eax,ebx"\
  "mov edx,ecx"\
  "call dword ptr [esi]"\
  "test eax,eax"\
  "jz cont1"\
   "test byte ptr sortlist_sortctrl,1"\
   "jz ascending"\
    "neg eax"\
   "ascending:"\
   "jmp end"\
  "cont1:"\
  "add esi,4"\
  "dec edi"\
 "jnz back1"\
 "endnull:xor eax,eax"\
 "end:"\
 parm [eax][edx] value[eax] modify[eax ebx ecx edx edi esi];

static int check_order_func_qs(const struct playlist_entry_info *pei0,const struct playlist_entry_info *pei1)
{
 return asm_coqs(pei0,pei1);
}

#else

static int check_order_func_qs(const struct playlist_entry_info *pei0,const struct playlist_entry_info *pei1)
{
 unsigned int keylevel=0;
 do{
  int result;
  if(!(*(side_orderfuncp+keylevel)))
   break;
  result=(*(side_orderfuncp+keylevel))((struct playlist_entry_info *)pei0,(struct playlist_entry_info *)pei1);
  if(result!=0){
   if(sortlist_sortctrl&PLAYLIST_SORTCONTROL_DESCENDING)
    result=-result;
   return result;
  }
 }while(++keylevel<PLAYLIST_MAX_ORDERKEYS);
 return 0;
}

#endif

static int check_order_func_one(struct playlist_side_info *psi,struct playlist_entry_info *pei0,struct playlist_entry_info *pei1)
{
 unsigned int keylevel=0;
 do{
  int result;
  if(!psi->id3ordertype[keylevel])
   break;
  result=((psi->id3ordertype[keylevel])(pei0,pei1));
  if(result!=0){
   if(sortlist_sortctrl&PLAYLIST_SORTCONTROL_DESCENDING)
    result=-result;
   return result;
  }
 }while(++keylevel<PLAYLIST_MAX_ORDERKEYS);
 return 0;
}

static void sortlist_config_sortcontrol(struct playlist_side_info *psi, struct playlist_entry_info *firstentry)
{
 if((mpxplay_sortcontrol&PLAYLIST_SORTCONTROL_CMD_DIR) && (psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel && ((psi->id3ordernum_primary==ID3ORDER_ARTIST) || (psi->id3ordernum_primary==ID3ORDER_PATHFILE) || (psi->id3ordernum_primary==ID3ORDER_FILEEXT) || (psi->id3ordernum_primary==ID3ORDER_FILEDATE)))
  funcbit_enable(sortlist_sortctrl, PLAYLIST_SORTCONTROL_CMD_DIR);
 else
  funcbit_disable(sortlist_sortctrl, PLAYLIST_SORTCONTROL_CMD_DIR);
 if((psi->editsidetype&PLT_SORTC_DESCENDING) && ((sortlist_sortctrl&PLAYLIST_SORTCONTROL_CMD_DIR) || (firstentry>=psi->firstsong) || ((mpxplay_sortcontrol&PLAYLIST_SORTCONTROL_NOLISTTOP) && psi->firstfile && (firstentry>=psi->firstfile))))
  funcbit_enable(sortlist_sortctrl, PLAYLIST_SORTCONTROL_DESCENDING);
 else
  funcbit_disable(sortlist_sortctrl, PLAYLIST_SORTCONTROL_DESCENDING);
}

static struct playlist_entry_info *sortlist_order_one_block(struct playlist_side_info *psi,
                                                            struct playlist_entry_info *pei_src,
                                                            struct playlist_entry_info *firstentry,
                                                            struct playlist_entry_info *lastentry)
{
 struct playlist_entry_info *pei_bottom,*pei_top,*pei_pos,*pei_lastgoodpos;
 unsigned int center;
 int result;
 struct playlist_entry_info pei_tmp;

 if(!psi->id3ordertype[0])
  return pei_src;

 pei_bottom=firstentry;
 pei_top=lastentry;
 if(pei_top<=pei_bottom)
  return pei_src;

 sortlist_config_sortcontrol(psi, pei_src);

 result=1;

 center=(pei_top-pei_bottom);
 center>>=1;

 if(center>2){
  do{
   pei_pos=pei_bottom+center;
   if(pei_pos==pei_src)
    pei_pos++;
   result=check_order_func_one(psi,pei_src,pei_pos);
   if(result==0)
    break;
   if(result<0)
    pei_top=pei_pos;
   else
    pei_bottom=pei_pos;
   center=(pei_top-pei_bottom);
   center>>=1;
  }while(center>2);
 }

 if(center<=2){
  pei_pos=pei_lastgoodpos=pei_bottom;
  do{
   result=check_order_func_one(psi,pei_src,pei_pos);
   if(result<0){
    if(pei_pos<pei_src)
     pei_lastgoodpos=pei_pos;
    break;
   }
   pei_lastgoodpos=pei_pos;
   pei_pos++;
  }while(pei_pos<=pei_top);
  pei_pos=pei_lastgoodpos;
 }

 if(pei_pos>lastentry)
  pei_pos=lastentry;

 if(pei_pos!=pei_src){
  pds_memcpy((char *)&pei_tmp,pei_src,sizeof(struct playlist_entry_info));
  if(pei_src>pei_pos)
   pds_qmemcpyr((pei_pos+1),pei_pos,(pei_src-pei_pos)*sizeof(struct playlist_entry_info)/4);
  else
   pds_qmemcpy(pei_src,(pei_src+1),(pei_pos-pei_src)*sizeof(struct playlist_entry_info)/4);
  pds_memcpy(pei_pos,(char *)&pei_tmp,sizeof(struct playlist_entry_info));
  correct_pfilenums(psi,pei_src,pei_pos);
 }
 return pei_pos;
}

static void correct_pfilenums(struct playlist_side_info *psi,struct playlist_entry_info *pei_begin,struct playlist_entry_info *pei_end)
{
 struct mainvars *mvp;

 if(pei_begin==pei_end)
  return;

 mvp=psi->mvp;
 if(psi==mvp->psip){
  if(mvp->aktfilenum){
   if(mvp->aktfilenum==pei_begin){
    mvp->aktfilenum=pei_end;
   }else{
    if(pei_begin<pei_end){
     if(mvp->aktfilenum>pei_begin && mvp->aktfilenum<=pei_end)
      mvp->aktfilenum--;
    }else{
     if(mvp->aktfilenum>=pei_end && mvp->aktfilenum<pei_begin)
      mvp->aktfilenum++;
    }
   }
  }
  if(mvp->newfilenum){
   if(mvp->newfilenum==pei_begin){
    mvp->newfilenum=pei_end;
   }else{
    if(pei_begin<pei_end){
     if(mvp->newfilenum>pei_begin && mvp->newfilenum<=pei_end)
      mvp->newfilenum--;
    }else{
     if(mvp->newfilenum>=pei_end && mvp->newfilenum<pei_begin)
      mvp->newfilenum++;
    }
   }
  }
  if(mvp->newsong){
   if(mvp->newsong==pei_begin){
    mvp->newsong=pei_end;
   }else{
    if(pei_begin<pei_end){
     if(mvp->newsong>pei_begin && mvp->newsong<=pei_end)
      mvp->newsong--;
    }else{
     if(mvp->newsong>=pei_end && mvp->newsong<pei_begin)
      mvp->newsong++;
    }
   }
  }
 }
 if(psi->editsidetype&PLT_SORTC_MAGNETHIGHLINE){
  if(psi->editorhighline==pei_begin){
   playlist_editorhighline_set_nocenter(psi,pei_end);
  }else{
   if(pei_begin<pei_end){
    if(psi->editorhighline>pei_begin && psi->editorhighline<=pei_end)
     playlist_editorhighline_seek(psi,-1,SEEK_CUR);
   }else{
    if(psi->editorhighline>=pei_end && psi->editorhighline<pei_begin)
     playlist_editorhighline_seek(psi,+1,SEEK_CUR);
   }
  }
 }
 if((pei_end->entrytype&DFTM_DFT) && (pei_begin>=psi->firstsong) && (pei_end<=psi->firstsong))
  psi->firstsong++;
 if(pei_end<pei_begin){
  struct playlist_entry_info *tmp;
  tmp=pei_begin;
  pei_begin=pei_end;
  pei_end=tmp;
 }
 playlist_randlist_correctq(psi,pei_begin,pei_end);
 playlist_peimyself_reset(psi,pei_begin,pei_end);
}

//--------------------------------------------------------------------------
static void ordertypes_save(struct playlist_side_info *psi,check_order_func_t *ot_save[PLAYLIST_MAX_ORDERKEYS])
{
 pds_memcpy((void *)&ot_save[0],(void *)&psi->id3ordertype[0],PLAYLIST_MAX_ORDERKEYS*sizeof(check_order_func_t *));
}

static void ordertypes_restore(struct playlist_side_info *psi,check_order_func_t *ot_save[PLAYLIST_MAX_ORDERKEYS])
{
 pds_memcpy((void *)&psi->id3ordertype[0],(void *)&ot_save[0],PLAYLIST_MAX_ORDERKEYS*sizeof(check_order_func_t *));
}

static void ordertype_insert(struct playlist_side_info *psi,unsigned int index,check_order_func_t *func)
{
 if(index<(PLAYLIST_MAX_ORDERKEYS-1))
  pds_qmemcpyr((void *)&psi->id3ordertype[index+1],(void *)&psi->id3ordertype[index],(PLAYLIST_MAX_ORDERKEYS-index-1)*sizeof(check_order_func_t *)/4);
 psi->id3ordertype[index]=func;
}

static void ordertype_append(struct playlist_side_info *psi,check_order_func_t *func)
{
 unsigned int i;
 for(i=0;i<PLAYLIST_MAX_ORDERKEYS;i++){
  if(psi->id3ordertype[i]==func) // !!! we don't put the same key twice
   break;
  if(!psi->id3ordertype[i]){
   psi->id3ordertype[i]=func;
   break;
  }
 }
}

//------------------------------------------------------------------------
/*#ifdef MPXPLAY_UTF8
static int sortlist_compare_wchar(mpxp_wchar_t *u16_1,mpxp_wchar_t *u16_2)
{
 switch(CompareStringW(LOCALE_INVARIANT,NORM_IGNORECASE|SORT_STRINGSORT,u16_1,1,u16_2,1)){
  case CSTR_LESS_THAN:return -1;
  case CSTR_GREATER_THAN:return 1;
  case CSTR_EQUAL:return 0;
 }
 if(*u16_1!=*u16_2){
  if(*u16_1<*u16_2)
   return -1;
  else
   return 1;
 }
 return 0;
}

static void build_ordertable(void)
{
 unsigned int i;
 mpxp_wchar_t *p=order_table;
 for(i=0;i<65536;i++)
  *p++=i;
 qsort((void *)(&order_table[1]),65535,sizeof(mpxp_wchar_t),(qsc_t)sortlist_compare_wchar);
}

#else*/
#ifndef MPXPLAY_UTF8
static void build_ordertable(void)
{
 char *ord=&sortorder_string[0];
 unsigned int i,pos;
 char tmp[256];

 for(i=0;i<256;i++)
  tmp[i]=i;

 pos=0;
 do{
  unsigned int c=*ord;
  if(!c)
   break;
  if(c>32 && c!='\"'){
   if(!pos){
    pos=c;
    pos++;
   }else{
    unsigned int from=33; // bellow 32 are control chars
    do{
     if(tmp[from]==c){
      if(from<pos){
       memmove(&tmp[from],&tmp[from+1],pos-from);
       tmp[pos-1]=c;
      }else{
       if(from>pos)
        memmove(&tmp[pos+1],&tmp[pos],from-pos);
       tmp[pos]=c;
       pos++;
      }
      break;
     }
     from++;
    }while(from<256);
   }
  }
  ord++;
 }while(pos<256);

 for(i=0;i<256;i++)
  order_table[tmp[i]]=i;

 for(i='a';i<='z';i++)
  order_table[i]=order_table[i-('a'-'A')]; // lower to uppercase
}
#endif

void playlist_sortlist_init(struct mainvars *mvp)
{
 struct playlist_side_info *psi;
 unsigned int s,k,type;

 if(sortlist_initialized)
  return;

 for(k=0;k<PLAYLIST_MAX_ORDERKEYS;k++){
  psi=mvp->psi0;
  type=playlist_orderkeys[k];
  if(type>ID3ORDER_DISABLED)
   type=ID3ORDER_DISABLED;
  if(!k)
   psi->id3ordernum_primary=type;
  for(s=0;s<mvp->editorside_all_tabs;s++,psi++) {
   psi->id3ordertype[k]=check_order_funcs[type]; // convert index-value to func-pointer
   if(mpxplay_sortcontrol & PLAYLIST_SORTCONTROL_DESCENDING)
    funcbit_enable(psi->editsidetype, PLT_SORTC_DESCENDING);
  }
 }

 psi=mvp->psi0;   // !!! auto order keys
 for(s=0;s<mvp->editorside_all_tabs;s++,psi++){
  if(psi->id3ordertype[0]){
   ordertype_append(psi,check_order_funcs[ID3ORDER_PATHFILE]);
   ordertype_append(psi,check_order_funcs[ID3ORDER_INDEX]);
  }
 }

 mpxplay_sortcontrol &= PLAYLIST_SORTCONTROL_CONFIG;
 sortlist_sortctrl = mpxplay_sortcontrol;

#ifndef MPXPLAY_UTF8
 build_ordertable();
#endif
 sortlist_initialized=1;
}

static void playlist_sortlist_selectorder(struct playlist_side_info *psi, unsigned int key, unsigned int type)
{
 if(key < PLAYLIST_MAX_ORDERKEYS) {
  psi->id3ordertype[key] = (type < ID3ORDER_DISABLED)? check_order_funcs[type] : NULL;
  if(!key)
   psi->id3ordernum_primary=type;
 }
}

static void playlist_sortlist_clearorder(struct playlist_side_info *psi)
{
 pds_memset(&psi->id3ordertype[0], 0, sizeof(psi->id3ordertype));
 psi->id3ordernum_primary = ID3ORDER_DISABLED;
#ifdef MPXPLAY_GUI_CONSOLE
 funcbit_disable(psi->editsidetype, PLT_SORTC_DESCENDING); // ??? no descending filenames
#endif
}

void playlist_sortlist_clear(struct playlist_side_info *psi)
{
 playlist_sortlist_clearorder(psi);
#ifndef MPXPLAY_GUI_CONSOLE
 funcbit_disable(psi->editsidetype, PLT_SORTC_DESCENDING);
#endif
}

mpxp_uint32_t playlist_sortlist_get_orderkeys_in_hexa(struct playlist_side_info *psi)
{
 mpxp_uint32_t i,value=0;
 for(i=0;i<PLAYLIST_MAX_ORDERKEYS;i++){
  mpxp_uint32_t type=SORTLIST_MPXTABS_ORDERKEYS_KEY_MASK;
  if(psi->id3ordertype[i])
   for(type=0;type<ID3ORDER_DISABLED;type++)
    if(psi->id3ordertype[i]==check_order_funcs[type])
     break;
  if(type>=ID3ORDER_DISABLED)
   type=SORTLIST_MPXTABS_ORDERKEYS_KEY_MASK;
  value|=type<<(i*SORTLIST_MPXTABS_ORDERKEYS_KEY_BITS);
 }
 if(psi->editsidetype & PLT_SORTC_DESCENDING)
  funcbit_enable(value, SORTLIST_MPXTABS_ORDERKEYS_FLAG_DESCENDING);
 return value;
}

void playlist_sortlist_set_orderkeys_from_hexa(struct playlist_side_info *psi,mpxp_uint32_t value)
{
 mpxp_uint32_t i;
 playlist_sortlist_init(psi->mvp);
 if(psi->id3ordertype[0]) // ??? set by -io
  return;
 for(i=0;i<PLAYLIST_MAX_ORDERKEYS;i++){
  mpxp_uint32_t type=(value>>(i*SORTLIST_MPXTABS_ORDERKEYS_KEY_BITS)) & SORTLIST_MPXTABS_ORDERKEYS_KEY_MASK;
  if(type<ID3ORDER_DISABLED)
   psi->id3ordertype[i]=check_order_funcs[type];
  if(!i)
   psi->id3ordernum_primary=type;
 }
 if(value & SORTLIST_MPXTABS_ORDERKEYS_FLAG_DESCENDING)
  funcbit_enable(psi->editsidetype, PLT_SORTC_DESCENDING);
 else
  funcbit_disable(psi->editsidetype, PLT_SORTC_DESCENDING);
}

//------------------------------------------------------------------------
static void ordertypes_set_dft(struct playlist_side_info *psi)
{
 if(!psi->id3ordertype[0]){
  psi->id3ordertype[0]=check_order_funcs[ID3ORDER_DFT];
  psi->id3ordertype[1]=check_order_funcs[ID3ORDER_PATHFILE];
  psi->id3ordertype[2]=check_order_funcs[ID3ORDER_INDEX];
  psi->id3ordertype[3]=NULL;
 }else{
  ordertype_insert(psi,0,check_order_funcs[ID3ORDER_DFT]);
  ordertype_append(psi,check_order_funcs[ID3ORDER_PATHFILE]);
  ordertype_append(psi,check_order_funcs[ID3ORDER_INDEX]);
 }
}

// sort at chkfile
struct playlist_entry_info *playlist_order_entry_block(struct playlist_side_info *psi,
                                                       struct playlist_entry_info *pei_src,
                                                       struct playlist_entry_info *firstentry,
                                                       struct playlist_entry_info *lastentry)
{
 struct playlist_entry_info *pei_pos=NULL;
 if((psi->lastentry>psi->firstentry) && !funcbit_test(pei_src->infobits,PEIF_SORTED)
  && (psi->id3ordertype[0] || ((psi->editsidetype&PLT_DIRECTORY) && !psi->sublistlevel && !(psi->editsidetype&PLT_SORTC_MANUALCOPY) && !(mpxplay_diskdrive_get_mdfs_infobits(psi->mdds) & MPXPLAY_DRIVEHANDFUNC_INFOBIT_LISTTYPEDIR)))
 ){
  struct mainvars *mvp=psi->mvp;
  unsigned int highakt=(psi->editorhighline==mvp->aktfilenum);
  check_order_func_t *ot_save[PLAYLIST_MAX_ORDERKEYS];

  if(firstentry<psi->firstentry)
   firstentry=psi->firstentry;
  if(lastentry>psi->lastentry)
   lastentry=psi->lastentry;
  if(lastentry<=firstentry)
   return pei_pos;

  ordertypes_save(psi,ot_save);
  ordertypes_set_dft(psi);
  pei_pos=sortlist_order_one_block(psi,pei_src,firstentry,lastentry);
  ordertypes_restore(psi,ot_save);

  if((mpxplay_sortcontrol & PLAYLIST_SORTCONTROL_NOLISTTOP) && ((pei_src < psi->firstsong)  || (pei_pos < psi->firstsong)))
   playlist_search_firstsong(psi);
  if(highakt)
   playlist_editorhighline_set(psi,mvp->aktfilenum);
  if(pei_pos==pei_src)                            //
   funcbit_enable(pei_pos->infobits,PEIF_SORTED); // do not sort again sorted entry
 }
 return pei_pos;
}

// sort at rename (diskfile)
struct playlist_entry_info *playlist_order_entry(struct playlist_side_info *psi,struct playlist_entry_info *pei_src)
{
 return playlist_order_entry_block(psi,pei_src,psi->firstentry,psi->lastentry);
}

void playlist_order_block(struct playlist_side_info *psi,
                          struct playlist_entry_info *firstentry,
                          struct playlist_entry_info *lastentry)
{
 struct mainvars *mvp;
 unsigned int highakt;

 if(!psi->id3ordertype[0])
  return;

 if(firstentry<psi->firstentry)
  firstentry=psi->firstentry;
 if(lastentry>psi->lastentry)
  lastentry=psi->lastentry;
 if(lastentry<=firstentry)
  return;

 mvp=psi->mvp;
 highakt=(psi->editorhighline==mvp->aktfilenum);

 side_orderfuncp=&psi->id3ordertype[0];

 sortlist_config_sortcontrol(psi, firstentry);

 //mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"fe:%d fs:%d le:%d ss:%4.4X", (firstentry - psi->firstentry), (psi->firstsong - psi->firstentry), (lastentry - psi->firstentry), sortlist_sortctrl);

 qsort((void *)(firstentry),(lastentry-firstentry+1),sizeof(playlist_entry_info),(qsc_t)check_order_func_qs);

 if((mpxplay_sortcontrol & PLAYLIST_SORTCONTROL_NOLISTTOP) && (firstentry < psi->firstsong))
  playlist_search_firstsong(psi);

 if((mvp->aktfilenum>=firstentry) && (mvp->aktfilenum<=lastentry))
  mvp->aktfilenum=playlist_peimyself_search(psi,mvp->aktfilenum);
 if((mvp->newfilenum>=firstentry) && (mvp->newfilenum<=lastentry))
  mvp->newfilenum=playlist_peimyself_search(psi,mvp->newfilenum);
 if((psi->editorhighline>=firstentry) && (psi->editorhighline<=lastentry)){
  if(psi->editsidetype&PLT_SORTC_MAGNETHIGHLINE)
   playlist_editorhighline_set_nocenter(psi,playlist_peimyself_search(psi,psi->editorhighline));
  else if(highakt)
   playlist_editorhighline_set_nocenter(psi,mvp->aktfilenum);
 }

 playlist_randlist_correctq(psi,firstentry,lastentry);
 playlist_peimyself_reset(psi,firstentry,lastentry);
}

static void playlist_order_side_block(struct playlist_side_info *psi,
                                      struct playlist_entry_info *firstentry,
                                      struct playlist_entry_info *lastentry)
{
 if(lastentry>firstentry){
  playlist_order_block(psi, firstentry, lastentry);

  if(psi->chkfilenum_curr)
   playlist_chkfile_start_norm(psi,0);
  else // ??? PLI_DISPLOAD
   display_editorside_reset(psi);
 }
}

// manual sort or sort at extended playlists
void playlist_order_side(struct playlist_side_info *psi)
{
 if(psi->id3ordertype[0]){
  struct playlist_entry_info *dft_border;
  check_order_func_t *ot_save[PLAYLIST_MAX_ORDERKEYS];
  funcbit_enable(psi->editsidetype,PLT_SORTC_MAGNETHIGHLINE); // !!!
  ordertypes_save(psi,ot_save);
  ordertypes_set_dft(psi);
  dft_border = ((mpxplay_sortcontrol&PLAYLIST_SORTCONTROL_NOLISTTOP) && psi->firstfile)? psi->firstfile : psi->firstsong;
  sortlist_config_sortcontrol(psi, dft_border);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"db:%d fs:%d le:%d ss:%4.4X", (dft_border - psi->firstentry), (psi->firstsong - psi->firstentry), (psi->lastentry - psi->firstentry), sortlist_sortctrl);
  playlist_order_side_block(psi, dft_border, psi->lastentry);
  if(psi->firstentry < (dft_border - 1)) {
   if(sortlist_sortctrl & PLAYLIST_SORTCONTROL_CMD_DIR)
    playlist_order_side_block(psi, psi->firstentry, (dft_border - 1));
   else
    playlist_order_dft_block(psi, psi->firstentry, (dft_border - 1));
  }
  ordertypes_restore(psi,ot_save);
  funcbit_disable(psi->editsidetype,PLT_SORTC_MAGNETHIGHLINE); // !!!
 }
}

static void playlist_order_dft_block(struct playlist_side_info *psi,
                                     struct playlist_entry_info *firstentry,
                                     struct playlist_entry_info *lastentry)
{
 check_order_func_t *ot_save[PLAYLIST_MAX_ORDERKEYS];

 ordertypes_save(psi, ot_save);
 psi->id3ordertype[0] = NULL; // to set dft always
 ordertypes_set_dft(psi);
 playlist_order_side_block(psi, firstentry, lastentry);
 ordertypes_restore(psi, ot_save);
}

// sort at directory browser (at first dir entry load)
void playlist_order_dft(struct playlist_side_info *psi)
{
 unsigned int est = psi->editsidetype;
 if(!(mpxplay_sortcontrol&PLAYLIST_SORTCONTROL_CMD_DIR))
  funcbit_disable(psi->editsidetype, PLT_SORTC_DESCENDING);  // !!! hack
 playlist_order_dft_block(psi,psi->firstentry,psi->lastentry);
 psi->editsidetype = est;
}


// sort (block of files) at dir-scan
void playlist_order_filenames_block(struct playlist_side_info *psi,
                                    struct playlist_entry_info *firstentry,
                                    struct playlist_entry_info *lastentry)
{
 if(!psi->id3ordertype[0])
  playlist_order_dft_block(psi,firstentry,lastentry);
}

void playlist_swap_entries(struct playlist_side_info *psi,struct playlist_entry_info *e1,struct playlist_entry_info *e2)
{
 struct mainvars *mvp;

 pds_memxch((char *)e1,(char *)e2,sizeof(playlist_entry_info));

 mvp=psi->mvp;
 if(psi==mvp->psip){
  if(mvp->aktfilenum==e1)
   mvp->aktfilenum=e2;
  else
   if(mvp->aktfilenum==e2)
    mvp->aktfilenum=e1;
  if(mvp->newfilenum==e1)
   mvp->newfilenum=e2;
  else
   if(mvp->newfilenum==e2)
    mvp->newfilenum=e1;
  playlist_randlist_xchq(e1,e2);
 }
 if(psi->chkfilenum_curr){
  struct playlist_entry_info *lower=min(e1,e2);
  struct playlist_entry_info *higher=max(e1,e2);
  if(psi->chkfilenum_curr>=lower && psi->chkfilenum_curr<=higher)
   psi->chkfilenum_curr=lower;
  if(psi->chkfilenum_curr<psi->chkfilenum_begin)
   psi->chkfilenum_begin=psi->chkfilenum_curr;
 }
 e1->myself=e1;
 e2->myself=e2;
}

unsigned int playlist_sortlist_is_preordered_type(struct playlist_side_info *psi)
{
 unsigned int id3order_num;

 if((psi->editsidetype&PLT_EXTENDED) || (mpxplay_diskdrive_get_mdfs_infobits(psi->mdds) & MPXPLAY_DRIVEHANDFUNC_INFOBIT_LISTTYPEDIR))
  return 1;

 id3order_num = psi->id3ordernum_primary;
 switch(id3order_num){
  case ID3ORDER_PATH:
  case ID3ORDER_FILENAME:
  case ID3ORDER_PATHFILE:
  case ID3ORDER_FILEEXT:
  case ID3ORDER_FILESIZE:
  case ID3ORDER_FILEDATE: return 1;
 }
 return 0;
}

unsigned char mpxplay_sortlist_column_to_initial_descending[2][PLAYLIST_MAX_COLUMNS_PER_TAB] =
{
 { 0, 0, 0, 1 },  // player mode (filename, artist, title, duration)
 { 0, 0, 1, 1 }   // commander mode (filename, ext, size, date)
};

void playlist_sortlist_do_sort_col_n(struct playlist_side_info *psi, unsigned int col, int control)
{
 if(!psi || (col >= PLAYLIST_MAX_COLUMNS_PER_TAB))
  return;

 if(control >= 0) { // called from GUI
  if(funcbit_test(control, PLAYLIST_SORTCONTROL_DESCENDING))
   funcbit_smp_enable(psi->editsidetype, PLT_SORTC_DESCENDING);
  else
   funcbit_smp_disable(psi->editsidetype, PLT_SORTC_DESCENDING);
 } else { // called from keyboard.c */
  unsigned int prev_col = playlist_sortlist_get_editorcolumn_from_priordernum(psi);
  if((col < PLAYLIST_MAX_COLUMNS_PER_TAB) && (col == prev_col))
   funcbit_smp_inverse(psi->editsidetype, PLT_SORTC_DESCENDING);
  else if(mpxplay_sortcontrol & PLAYLIST_SORTCONTROL_NOAUTODESC)
   funcbit_smp_disable(psi->editsidetype, PLT_SORTC_DESCENDING);
  else
   funcbit_smp_copy(psi->editsidetype, ((mpxplay_sortlist_column_to_initial_descending[((desktopmode&DTM_MASK_COMMANDER) == DTM_MASK_COMMANDER)? 1:0][col])? PLT_SORTC_DESCENDING : 0), PLT_SORTC_DESCENDING);
 }

 switch(col) {
  case 0:
   playlist_sortlist_selectorder(psi,0,ID3ORDER_PATHFILE);
   playlist_sortlist_selectorder(psi,1,ID3ORDER_INDEX);
   break;
  case 1:
   if((desktopmode&DTM_MASK_COMMANDER)==DTM_MASK_COMMANDER){
    playlist_sortlist_selectorder(psi,0,ID3ORDER_FILEEXT);
    playlist_sortlist_selectorder(psi,1,ID3ORDER_FILENAME);
    playlist_sortlist_selectorder(psi,2,ID3ORDER_PATHFILE);
    playlist_sortlist_selectorder(psi,3,ID3ORDER_INDEX);
   }else{
    playlist_sortlist_selectorder(psi,0,ID3ORDER_ARTIST);
    playlist_sortlist_selectorder(psi,1,ID3ORDER_TITLE);
    playlist_sortlist_selectorder(psi,2,ID3ORDER_PATHFILE);
   }
   break;
  case 2:
   if((desktopmode&DTM_MASK_COMMANDER)==DTM_MASK_COMMANDER){
    playlist_sortlist_selectorder(psi,0,ID3ORDER_FILESIZE);
    playlist_sortlist_selectorder(psi,1,ID3ORDER_PATHFILE);
    playlist_sortlist_selectorder(psi,2,ID3ORDER_INDEX);
   }else{
    playlist_sortlist_selectorder(psi,0,ID3ORDER_TITLE);
    playlist_sortlist_selectorder(psi,1,ID3ORDER_ARTIST);
    playlist_sortlist_selectorder(psi,2,ID3ORDER_PATHFILE);
   }
   break;
  case 3:
   if((desktopmode&DTM_MASK_COMMANDER)==DTM_MASK_COMMANDER){
    playlist_sortlist_selectorder(psi,0,ID3ORDER_FILEDATE);
    playlist_sortlist_selectorder(psi,1,ID3ORDER_PATHFILE);
    playlist_sortlist_selectorder(psi,2,ID3ORDER_INDEX);
   }else{
    playlist_sortlist_selectorder(psi,0,ID3ORDER_TIME);
    playlist_sortlist_selectorder(psi,1,ID3ORDER_ARTIST);
    playlist_sortlist_selectorder(psi,2,ID3ORDER_TITLE);
    playlist_sortlist_selectorder(psi,3,ID3ORDER_PATHFILE);
   }
   break;
 }
 playlist_order_side(psi);
 if(col==0)
  playlist_sortlist_clearorder(psi);
}

static unsigned char sortlist_pri_ordernum_to_colnum[ 2 ][ (ID3ORDER_DISABLED + 1) ] =
{
 { 2 , 1, 0xff,0xff,0xff,0xff,0xff, 3,       0xff,0xff, 0, 0xff,0xff,0xff,0xff,0xff, 0}, // title (2), artist (1), time (3), pathfile(0), disabled (0)
 { 0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff,  0xff,0xff, 0, 1, 2, 3, 0xff,0xff, 0}        // pathfile (0), ext(1), filesize (2), filedate (3), disabled (0)
};

unsigned int playlist_sortlist_get_editorcolumn_from_priordernum(struct playlist_side_info *psi)
{
 int cmd;
 unsigned int id3_pri;
 if(!psi)
  return 0xff;
 id3_pri = psi->id3ordernum_primary;
 if(id3_pri > ID3ORDER_DISABLED) {
  id3_pri = ID3ORDER_DISABLED;
  psi->id3ordernum_primary = id3_pri;
 }
 cmd = ((desktopmode&DTM_MASK_COMMANDER) == DTM_MASK_COMMANDER)? 1:0;
 return (unsigned int)sortlist_pri_ordernum_to_colnum[cmd][id3_pri];
}

#ifndef MPXPLAY_GUI_CONSOLE

static unsigned char sortlist_column_to_priordernum[2][PLAYLIST_MAX_COLUMNS_PER_TAB] =
{
 { ID3ORDER_DISABLED, ID3ORDER_ARTIST, ID3ORDER_TITLE, ID3ORDER_TIME },        // player mode (filename, artist, title, duration)
 { ID3ORDER_DISABLED, ID3ORDER_FILEEXT, ID3ORDER_FILESIZE, ID3ORDER_FILEDATE } // commander mode (filename, ext, size, date)
};

void playlist_sortlist_set_psi_sortcontrol(struct playlist_side_info *psi, unsigned int column, unsigned int control)
{
 int cmd;
 if(!psi || (column >= PLAYLIST_MAX_COLUMNS_PER_TAB))
  return;
 cmd = ((desktopmode&DTM_MASK_COMMANDER) == DTM_MASK_COMMANDER)? 1:0;
 funcbit_smp_int32_put(psi->id3ordernum_primary, (unsigned int)sortlist_column_to_priordernum[cmd][column]);
 if(control & PLAYLIST_SORTCONTROL_DESCENDING)
  funcbit_smp_enable(psi->editsidetype, PLT_SORTC_DESCENDING);
 else
  funcbit_smp_disable(psi->editsidetype, PLT_SORTC_DESCENDING);
}
#endif // !MPXPLAY_GUI_CONSOLE
