//**************************************************************************
//*                     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: Download ICEcast server list from http://dir.xiph.org/yp.xml
//          in async mode, open as a drive / directory

//#define MPXPLAY_USE_DEBUGF 1
#define MPXPLAY_DEBUG_OUTPUT NULL// stdout
#define MPXPLAY_DEBUGOUT_TIMER stdout

#include "mpxplay.h"
#include "diskdriv.h"
#include "tcpcomon.h"
#include "control/cntfuncs.h"
#include "display/display.h"
#include <io.h>

#define ICECAST_DIRECTORY_ADDRESS "http://dir.xiph.org/yp.xml"

#define ICECAST_DIRECTORY_YPXML_URLBEGIN "<listen_url>"
#define ICECAST_DIRECTORY_YPXML_URLEND   "</listen_url>"

#define ICECAST_DIRECTORY_ENTRIES_MIN 64
#ifdef __DOS
#define ICECAST_DIRECTORY_ENTRIES_MAX 16384
#else
#define ICECAST_DIRECTORY_ENTRIES_MAX 65535
#endif

#define ICEDRIVE_DOWNLOAD_TIMEOUT     10000 // in ms
#define ICEDRIVE_DOWNLOAD_TIMER_FAST   0
#define ICEDRIVE_DOWNLOAD_TIMER_SLOW   5

#define ICEDRIVE_DRIVE_NAME "ice:"
#define ICEDRIVE_LOCAL_YPXML_NAME "ICECASTYP.XML"

#define ICEDRIVE_ENTRY_DATASIZE 128

#define ICEDRIVE_LINETYPE_SKIP        1
#define ICEDRIVE_LINETYPE_ENTRYBEGIN  2
#define ICEDRIVE_LINETYPE_ENTRYEND    3
#define ICEDRIVE_LINETYPE_FILENAME    4
#define ICEDRIVE_LINETYPE_METADATA    5

extern unsigned int loadid3tag;

enum ice_xml_data_indexes { ICEXMLI_LISTEN_URL, ICEXMLI_SERVER_NAME, ICEXMLI_CURRENT_SONG, ICEXMLI_GENRE, ICEXML_DATATYPE_NUM };
static char *ice_xml_data_names[] = {"<listen_url>", "<server_name>", "<current_song>", "<genre>"};
static unsigned int icexml_data_cmds[] = {MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_NEWDIRENTRY, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_ARTIST, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_TITLE, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_MODDIRENTRY_I3I_GENRE};

typedef struct icedrive_info_s{
 mpxp_int32_t (*control_cb)(void *mdds,mpxp_uint32_t funcnum,void *argp1,void *argp2);
 void *cb_data;
 void *fp_download, *fp_local;
 unsigned int timer_refresh_current;
 mpxp_filesize_t yp_filesize;
 mpxp_uint64_t readwait_endtime;
 char entry_tmp_data[ICEXML_DATATYPE_NUM][ICEDRIVE_ENTRY_DATASIZE];
}icedrive_info_s;

static void icedrive_drive_unmount(void *drivehand_data);
static void icedrive_ypdir_download_timer(void *icis_p);
static unsigned int icedrive_findnext(void *drivehand_data,struct pds_find_t *ffblk);

static const char ice_session_text[] = "Session=Icecast Radio Directory\nProtocol=ice\n";

static void icedrive_get_localxmlfilename(char *strbuf)
{
 if(mpxplay_control_get_ini_path(strbuf) != MPXPLAY_ERROR_OK)
  return;
 pds_filename_assemble_fullname(strbuf,strbuf, ICEDRIVE_LOCAL_YPXML_NAME);
}

static void icedrive_message_write_error(char *message)
{
 char sout[256];
 snprintf(sout,(sizeof(sout)-1),"ICE: %s",message);
 display_timed_message(sout);
}

static unsigned int icedrive_ypdir_line_process(struct icedrive_info_s *icis, char *line)
{
 char *data_begin = pds_strchr(line, '<');
 unsigned int i, linetype = ICEDRIVE_LINETYPE_SKIP;

 if(!data_begin || !data_begin[1])
  return linetype;
 if(pds_strlicmp(data_begin, "<entry>") == 0){
  pds_memset(icis->entry_tmp_data, 0, sizeof(icis->entry_tmp_data));
  linetype = ICEDRIVE_LINETYPE_ENTRYBEGIN;
 }else if(pds_strlicmp(data_begin, "</entry>") == 0){
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT,"%3d. entry %s", 0, icis->entry_tmp_data[ICEXMLI_LISTEN_URL]);
  linetype = ICEDRIVE_LINETYPE_ENTRYEND;
 }else{
  char *data_end = pds_strstr(data_begin + 1, "</");
  if(!data_end)
   return linetype;
  *data_end = 0;
  for(i = 0; i < ICEXML_DATATYPE_NUM; i++){
   if(pds_strlicmp(data_begin, ice_xml_data_names[i]) == 0) {
    data_begin += pds_strlen(ice_xml_data_names[i]);
    pds_str_clean(data_begin);
    if(i==ICEXMLI_SERVER_NAME)
     pds_str_limitc(data_begin, icis->entry_tmp_data[i], min(MAX_ID3_DISKFILE_ARTIST_LEN, ICEDRIVE_ENTRY_DATASIZE), ' ');
    else
     pds_strncpy(icis->entry_tmp_data[i], data_begin, ICEDRIVE_ENTRY_DATASIZE - 1);
    icis->entry_tmp_data[i][ICEDRIVE_ENTRY_DATASIZE - 1] = 0;
    linetype = (i==ICEXMLI_LISTEN_URL)? ICEDRIVE_LINETYPE_FILENAME : ICEDRIVE_LINETYPE_METADATA;
    break;
   }
  }
 }
 return linetype;
}

static void icedrive_entry_tmp_metadata_sendback(struct icedrive_info_s *icis, unsigned int use_cb_fn, unsigned int load_id3tag)
{
 unsigned int i;
 if(!icis->entry_tmp_data[ICEXMLI_LISTEN_URL][0])
  return;
 for(i = ((use_cb_fn)? 0 : ICEXMLI_SERVER_NAME); i < ((load_id3tag & ID3LOADMODE_LOWLEVEL)? ICEXML_DATATYPE_NUM : ICEXMLI_SERVER_NAME); i++)
  if(icis->entry_tmp_data[i][0]){
   if(icis->control_cb)
    icis->control_cb(icis->cb_data, icexml_data_cmds[i], icis->entry_tmp_data[ICEXMLI_LISTEN_URL], icis->entry_tmp_data[i]);
  }
}

static void icedrive_entry_tmp_metadata_clean(struct icedrive_info_s *icis)
{
 pds_memset(icis->entry_tmp_data, 0, sizeof(icis->entry_tmp_data));
}

static int icedrive_process_local_ypxml(struct icedrive_info_s *icis, struct pds_find_t *ffblk)
{
 int retcode = 1;
 char stmp[MAX_PATHNAMELEN];
 if(!ffblk && !(loadid3tag & ID3LOADMODE_LOWLEVEL))
  return retcode;
 do{
  int readlen = mpxplay_diskdrive_textfile_readline(icis->fp_local, stmp, sizeof(stmp));
  if(readlen <= 0){
   retcode = 1;
   break;
  }
  retcode = icedrive_ypdir_line_process(icis, stmp);
  mpxplay_debugf(MPXPLAY_DEBUG_OUTPUT, "%8.8X %d \"%s\"", (unsigned long)ffblk, retcode, icis->entry_tmp_data[ICEXMLI_LISTEN_URL]);
  if(ffblk && (retcode == ICEDRIVE_LINETYPE_FILENAME)){
   pds_strcpy(ffblk->name, icis->entry_tmp_data[ICEXMLI_LISTEN_URL]);
   retcode = 0;
   break;
  }
  if(!ffblk && (retcode == ICEDRIVE_LINETYPE_ENTRYEND)){
   icedrive_entry_tmp_metadata_sendback(icis, 0, loadid3tag);
   icedrive_entry_tmp_metadata_clean(icis);
   retcode = 0;
   break;
  }
 }while(1);
 return retcode;
}

static void icedrive_ypdir_download_stop(struct icedrive_info_s *icis)
{
 mpxplay_timer_deletefunc(icedrive_ypdir_download_timer, icis);
 mpxplay_diskdrive_textfile_close(icis->fp_download);
 icis->fp_download = NULL;
 mpxplay_diskdrive_textfile_close(icis->fp_local);
 icis->fp_local = NULL;
 if(icis->control_cb)
  icis->control_cb(icis->cb_data, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_PROCPSISTOP, NULL, NULL);
 icedrive_entry_tmp_metadata_clean(icis);
}

static void icedrive_ypdir_download_timer(void *icis_p)
{
 struct icedrive_info_s *icis = icis_p;
 char line[MAX_PATHNAMELEN];

 if(!icis)
  goto err_out_timer;

 do{
  int readlen = mpxplay_diskdrive_textfile_readline(icis->fp_download, line, sizeof(line));
  mpxplay_debugf(MPXPLAY_DEBUGOUT_TIMER, "TIMER size:%d pos:%d rl:%5d", (int)icis->yp_filesize, (int)mpxplay_diskdrive_textfile_tell(icis->fp_download), readlen);
  if(readlen <= 0) {
   if((mpxplay_diskdrive_textfile_tell(icis->fp_download) + 64) >= icis->yp_filesize)
    goto ok_out_timer;
   if(readlen < 0)
    goto err_out_timer;
   if(icis->timer_refresh_current != ICEDRIVE_DOWNLOAD_TIMER_SLOW) {
    mpxplay_timer_modifyfunc(&icedrive_ypdir_download_timer, icis, MPXPLAY_TIMERTYPE_REPEAT, ICEDRIVE_DOWNLOAD_TIMER_SLOW);
    icis->timer_refresh_current = ICEDRIVE_DOWNLOAD_TIMER_SLOW;
    if(icis->yp_filesize != MPXPLAY_DISKDRIV_FILESIZE_UNKNOWN)
     snprintf(line,sizeof(line),"server list download: %d%%", (long)(100.0 * (float)mpxplay_diskdrive_textfile_tell(icis->fp_download) / (float)icis->yp_filesize));
    else
     snprintf(line,sizeof(line),"server list download... ");
    icedrive_message_write_error(line);
   }
   if(!icis->readwait_endtime)
    icis->readwait_endtime = pds_gettimem() + ICEDRIVE_DOWNLOAD_TIMEOUT;
   else if(pds_gettimem() >= icis->readwait_endtime)
    goto err_out_timer;
   break;
  }

  mpxplay_diskdrive_textfile_writeline(icis->fp_local, line);

  if(icedrive_ypdir_line_process(icis, line) == ICEDRIVE_LINETYPE_ENTRYEND){
   icedrive_entry_tmp_metadata_sendback(icis, 1, loadid3tag);
   if(icis->control_cb)
    icis->control_cb(icis->cb_data, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_REFDIRENTRY, icis->entry_tmp_data[ICEXMLI_LISTEN_URL], NULL);
   icedrive_entry_tmp_metadata_clean(icis);
  }

  if(icis->timer_refresh_current != ICEDRIVE_DOWNLOAD_TIMER_FAST){
   mpxplay_timer_modifyfunc(&icedrive_ypdir_download_timer, icis, MPXPLAY_TIMERTYPE_REPEAT, ICEDRIVE_DOWNLOAD_TIMER_FAST);
   icis->timer_refresh_current = ICEDRIVE_DOWNLOAD_TIMER_FAST;
  }
  icis->readwait_endtime = 0;

 }while(1);

 return;

err_out_timer:

ok_out_timer:
 if(icis->yp_filesize != MPXPLAY_DISKDRIV_FILESIZE_UNKNOWN)
  snprintf(line,sizeof(line),"download finished at %d%%", (long)(100.0 * (float)mpxplay_diskdrive_textfile_tell(icis->fp_download) / (float)icis->yp_filesize));
 else
  snprintf(line,sizeof(line),"download finished");
 icedrive_message_write_error(line);
 icedrive_ypdir_download_stop(icis);
}

unsigned int icedrive_ypdir_download_start(struct icedrive_info_s *icis)
{
 unsigned long cfg;
 char stmp[MAX_PATHNAMELEN];

 icedrive_ypdir_download_stop(icis);

 icis->fp_download = mpxplay_diskdrive_textfile_open(NULL, ICECAST_DIRECTORY_ADDRESS, O_TEXT | O_RDONLY);
 if(!icis->fp_download)
  return MPXPLAY_ERROR_FILEHAND_CANTOPEN;
 icis->yp_filesize = mpxplay_diskdrive_textfile_filelength(icis->fp_download);
 if(!icis->yp_filesize)
  return MPXPLAY_ERROR_FILEHAND_CANTOPEN;

 cfg=MPXPLAY_TEXTCONV_TYPE_UTF8;
 mpxplay_diskdrive_textfile_config(icis->fp_download, MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_SET_TEXTCODETYPE_SRC, &cfg, NULL);
 cfg=0;
 mpxplay_diskdrive_textfile_config(icis->fp_download, MPXPLAY_DISKFILE_CFGFUNCNUM_SET_READWAIT, &cfg, NULL);
 cfg=1;
 mpxplay_diskdrive_textfile_config(icis->fp_download, MPXPLAY_DISKFILE_CFGFUNCNUM_SET_NOKEEPALIVE, &cfg, NULL);
 cfg=1;
 mpxplay_diskdrive_textfile_config(icis->fp_download, MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_SET_HTMLTEXT_SRC, &cfg, NULL);

 snprintf(stmp,sizeof(stmp),"download started, filesize: %d kB", (unsigned long)(icis->yp_filesize / 1024));
 icedrive_message_write_error(stmp);

 icedrive_get_localxmlfilename(stmp);
 icis->fp_local = mpxplay_diskdrive_textfile_open(NULL, stmp, O_TEXT | O_WRONLY | O_CREAT);
 if(icis->fp_local){
  cfg=MPXPLAY_TEXTCONV_TYPE_MPXNATIVE;
  mpxplay_diskdrive_textfile_config(icis->fp_local, MPXPLAY_DISKTEXTFILE_CFGFUNCNUM_SET_TEXTCODETYPE_DEST, &cfg, NULL);
 }

 if(icis->control_cb)
  icis->control_cb(icis->cb_data, MPXPLAY_DISKDRIVE_CFGFUNCNUM_CBEVENT_PROCPSISTART, NULL, NULL);

 mpxplay_timer_addfunc(&icedrive_ypdir_download_timer, icis, MPXPLAY_TIMERTYPE_REPEAT, ICEDRIVE_DOWNLOAD_TIMER_FAST);

 return MPXPLAY_ERROR_FILEHAND_OK;
}

static unsigned int icedrive_drive_check(char *pathname)
{
 if(pds_strlicmp(pathname, ICEDRIVE_DRIVE_NAME) == 0)
  return 1;
 return 0;
}

static long icedrive_drive_config(void *drivehand_data,unsigned long funcnum,void *argp1,void *argp2)
{
 struct icedrive_info_s *icis;
 char strtmp[256];

 switch(funcnum){
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_CONSTSESSIONS:
   if(!argp1 || !argp2)
    return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;
   *(const char **)argp1 = &ice_session_text[0];
   *((unsigned long *)argp2) = sizeof(ice_session_text);
   return MPXPLAY_DISKDRIV_CFGERROR_GET_OK;
 }

 if(!drivehand_data)
  return MPXPLAY_DISKDRIV_CFGERROR_INVALID_DRIVE;

 icis=drivehand_data;

 switch(funcnum){
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_CMD_RESETDRIVE:
   if(icedrive_ypdir_download_start(icis) != MPXPLAY_ERROR_FILEHAND_OK)
    return MPXPLAY_DISKDRIV_CFGERROR_CANNOT_DO;
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_ISDIREXISTS:
   return 1;
 }

 if(!argp1)
  return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;

 switch(funcnum){
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_SET_CALLBACKPSIFN:
   icis->control_cb = argp1;
   icis->cb_data = argp2;  // can be NULL
   return MPXPLAY_DISKDRIV_CFGERROR_SET_OK;
 }

 if(!argp2)
  return MPXPLAY_DISKDRIV_CFGERROR_ARGUMENTMISSING;

 switch(funcnum){
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_DRVTYPENAME:
   pds_strncpy((char *)argp1,"Icecast",*((unsigned long *)argp2));
   return 1;
  case MPXPLAY_DISKDRIV_CFGFUNCNUM_GET_REALLYFULLPATH:
   tcpcomon_str_localname_to_remote(strtmp,(char *)argp1,sizeof(strtmp));
   if(!strtmp[0] || (strtmp[0]==PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP))   // assumed directory name
    snprintf((char *)argp1, MAX_PATHNAMELEN, "%s%s%s%s", ICEDRIVE_DRIVE_NAME, PDS_DIRECTORY_SEPARATOR_STR_UNXFTP, ((strtmp[0]==PDS_DIRECTORY_SEPARATOR_CHAR_UNXFTP)? "" : PDS_DIRECTORY_SEPARATOR_STR_UNXFTP), strtmp);
   else
    pds_strcpy((char *)argp1,(char *)argp2);
   return MPXPLAY_DISKDRIV_CFGERROR_GET_OK;
 }
 return MPXPLAY_DISKDRIV_CFGERROR_UNSUPPFUNC;
}

static void *icedrive_drive_mount(char *pathname)
{
 struct icedrive_info_s *icis = (struct icedrive_info_s *)pds_calloc(1,sizeof(*icis));
 char stmp[MAX_PATHNAMELEN];

 if(!icis)
  return icis;

 icedrive_get_localxmlfilename(stmp);
 icis->fp_local = mpxplay_diskdrive_textfile_open(NULL, stmp, O_TEXT | O_RDONLY);
 if(!icis->fp_local || (mpxplay_diskdrive_textfile_filelength(icis->fp_local) < 65535)){
  if(icedrive_ypdir_download_start(icis) != MPXPLAY_ERROR_FILEHAND_OK)
   goto err_out_mount;
 }else{
  mpxplay_diskdrive_textfile_close(icis->fp_local);
  icis->fp_local = NULL;
 }

 return icis;

err_out_mount:
 icedrive_drive_unmount(icis);
 return NULL;
}

static void icedrive_drive_unmount(void *drivehand_data)
{
 struct icedrive_info_s *icis = drivehand_data;
 if(icis){
  icedrive_ypdir_download_stop(icis);
  pds_free(icis);
 }
}

static unsigned int icedrive_findfirst(void *drivehand_data, char *pathname, unsigned int attrib, struct pds_find_t *ffblk)
{
 struct icedrive_info_s *icis=drivehand_data;
 char stmp[MAX_PATHNAMELEN];

 if(!icis || icis->fp_download || (attrib&_A_SUBDIR))
  return 1;

 icedrive_get_localxmlfilename(stmp);
 icis->fp_local = mpxplay_diskdrive_textfile_open(NULL, stmp, O_TEXT | O_RDONLY);
 if(!icis->fp_local){
  icedrive_ypdir_download_start(icis);
  return 1;
 }

 return icedrive_findnext(icis,ffblk);
}

static unsigned int icedrive_findnext(void *drivehand_data, struct pds_find_t *ffblk)
{
 struct icedrive_info_s *icis = drivehand_data;
 unsigned int retcode = 1;
 if(!icis)
  return retcode;

 if(icis->entry_tmp_data[ICEXMLI_LISTEN_URL][0]) // update metadata of previous entry
  icedrive_process_local_ypxml(icis, NULL);

 retcode = icedrive_process_local_ypxml(icis, ffblk);

 if(retcode && icis->entry_tmp_data[ICEXMLI_LISTEN_URL][0])
  icedrive_process_local_ypxml(icis, NULL);

 return retcode;
}

static void icedrive_findclose(void *drivehand_data,struct pds_find_t *ffblk)
{
 struct icedrive_info_s *icis = drivehand_data;
 if(!icis || icis->fp_download)
  return;
 mpxplay_diskdrive_textfile_close(icis->fp_local);
 icis->fp_local = NULL;
 icedrive_entry_tmp_metadata_clean(icis);
}

static char *icedrive_getcwd(void *drivehand_data,char *localpathbuf,unsigned int buflen)
{
 if(!localpathbuf || !buflen)
  return localpathbuf;
 //*localpathbuf=0;
 snprintf(localpathbuf,buflen,"\\");
 return localpathbuf;
}

//------------------------------------------------------------------------

struct mpxplay_drivehand_func_s ICECASTDIR_drivehand_funcs={
 "ICECASTDIR",
 MPXPLAY_DRIVEHANDFUNC_INFOBIT_SLOWACCESS | MPXPLAY_DRIVEHANDFUNC_INFOBIT_DISCONNECT | MPXPLAY_DRIVEHANDFUNC_INFOBIT_READONLY | MPXPLAY_DRIVEHANDFUNC_INFOBIT_LISTTYPEDIR | MPXPLAY_DRIVEHANDFUNC_INFOBIT_ASYNCDIR,
 &icedrive_drive_config,
 &icedrive_drive_check,
 &icedrive_drive_mount,
 &icedrive_drive_unmount,
 &icedrive_findfirst,
 &icedrive_findnext,
 &icedrive_findclose,
 &icedrive_getcwd,
 NULL, // chdir
 NULL, // mkdir
 NULL, // rmdir
 NULL, // rename
 NULL, // unlink
 NULL, // r15
 NULL, // r16
 NULL, // r17
 NULL, // r18
 NULL, // r19
 NULL, // r20

 NULL, // file_check
 NULL, // file_open
 NULL, // file_close
 NULL, // file_read
 NULL, // file_write
 NULL, // file_seek
 NULL, // file_tell
 NULL, // file_length
 NULL, // file_eof
 NULL, // file_chsize
 NULL // r31
};
