/*
 * playlist.c
 * ----------
 * 19/9/03
 * File and Playlist functions.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "kernel.h"
#include "swis.h"
#include "timer1.h"
#include "audio.h"
#include "config.h"
#include "playlist.h"
#include "msg.h"
#include "player.h"
#include "ka_mem.h"
#include "ro_file.h"

extern ka_config_t config;

typedef struct
{
    char * name; // pointer to null terminated full pathname of file.
    unsigned int flags;
} film_t;
// playlist address table structure
typedef struct pl_tab_s
{
  unsigned int num; // number of file records following.
  film_t film[1024];
} pl_tab_t;

#define PL_PLAYED 0x01 // bit 0 of pl_tab->film[n].flags, set when film is played
#define IT_TICKED    1

static int pl_pos = 0;            // index to pl_tab and pl_menu, current filename
static pl_menu_t* pl_menu = NULL; // pointer to menu structure
static pl_tab_t* pl_tab = NULL;   // pointer to address table
static char* pl_buf = NULL;       // pointer to playlist film buffer
static unsigned int pl_rand;      // random number counter;
static char filename[MAXPATH];
static const char* last_filename = NULL;

static int file_types[] =
{ FILETYPE_VOB
, FILETYPE_MPEG
, FILETYPE_AVI
, -1
};

int playlist_accept(int file_type)
{
  for (int* ptypes = file_types; *ptypes != -1; ptypes++)
  {
    if (*ptypes == file_type)
      return 1;
  }

  return 0;
}

/*
 * playlist_open
 * -------------
 * Opens the supplied file.
 * If a directory, creates a temporary playlist of any mpegs found.
 * If a file, checks for a valid playlist.
 * Then creates a table of addresses of filename strings, and a menu.
 * Closes any open playlist.
 */
int playlist_open(char * name)
{
  _kernel_swi_regs regs;
  char *buf;
  unsigned int i, j, n, len, start;
  pl_tab_t *tab;
  pl_menu_t *menu;
  // structures used when creating menu
  static const menu_block_t def_blk = {"Playlist", NULL, 9, 7 , 2, 7, 0, 200, 44, 0};
  static const menu_item_t def_item = {0, NULL, 0x07000121, "", NULL, 0};

  regs.r[0] = 17;
  regs.r[1] = (int)name;
  _kernel_swi(OS_File, &regs, &regs);

  // directory of mpegs
  // ------------------
  // create a temporary 'playlist' file in memory
  if(regs.r[0] == 2)
  {
    int path = strlen(name) + 2; // allow for dot and null
    char *s;
    struct
    {
      int i[5];
      char c[256];
    } b;

    // search directory, calculating memory required to store all mpeg pathnames
    len = n = 0;
    regs.r[0] = 10;
    regs.r[1] = (int)name;
    regs.r[2] = (int)&b;
    regs.r[4] = 0;
    regs.r[5] = sizeof(b);
    regs.r[6] = 0;
    while(regs.r[4] != -1)
    {
      regs.r[3] = 1;
      _kernel_swi(OS_GBPB, &regs, &regs);
      if ((regs.r[3] == 1)
      &&  (b.i[4] == 1)
      &&  ((b.i[0] & 0xfff00000) == 0xfff00000)
      &&  playlist_accept((b.i[0] & 0xfff00) >> 8))
      {
        n++;
        len += path + strlen(b.c);
      }
    }
    if(n == 0) return 0; // no mpeg files in directory

    // allocate memory and store pathnames
    if((buf = ka_mem_alloc(len)) == NULL)
      return 0; // out of memory
    s = buf;
    regs.r[4] = 0;
    while(regs.r[4] != -1)
    {
      regs.r[3] = 1;
      _kernel_swi(OS_GBPB, &regs, &regs);
      if ((regs.r[3] == 1)
      &&  (b.i[4] == 1)
      &&  ((b.i[0] & 0xfff00000) == 0xfff00000)
      &&  playlist_accept((b.i[0] & 0xfff00) >> 8))
      {
        snprintf(s, len + (s - buf), "%s.%s", name, b.c); // add pathname to buffer
        s += path + strlen(b.c);
      }
    }
    start = 0;
  }

  // playlist file
  // -------------
  else if(regs.r[0] == 1)
  {
    len = regs.r[4];
    if((buf = ka_mem_alloc(len)) == NULL)
      return 0; // out of memory

    regs.r[0] = 16;
    regs.r[1] = (int)name;
    regs.r[2] = (int)buf;
    regs.r[3] = 0;
    _kernel_swi(OS_File, &regs, &regs);

    if(strncmp("Playlist", buf, 8) != 0)
    {
      ka_mem_free(buf);
      return 0; // not a playlist
    }

    // replace any control characters with 0
    for(i=0; i<len; i++)
      if(buf[i] < 0x20) buf[i] = 0;

    // count number of entries
    n = i = 0;
    // skip first line
    while(buf[i] && (i < len)) i++;
    start = ++i;
    while(i < len)
    { // skip comments and blank lines
      while((buf[i] == '#') || !buf[i])
      {
        while(buf[i] && (i < len)) i++;
        i++;
      }
      if(i < len)
      {
        n++; // count entry
        while(buf[i] && (i < len)) i++; // skip entry
        i++;
      }
    }
    if(n == 0)
    {
      ka_mem_free(buf);
      return 0; // no entries
    }
  }

  else return 0; // not a file or directory

  // create table of pathname addresses
  if((tab = ka_mem_alloc(((2*n)+1)*sizeof(unsigned int))) == NULL)
  {
    ka_mem_free(buf);
    return 0; // out of memory
  }
  i = start;
  j = 0;
  tab->num = n; // number of addresses following
  for(j=0; j<n; j++)
  { // skip comments and blank lines
    while((buf[i] == '#') || !buf[i])
    {
      while(buf[i]) i++;
      i++;
    }
    // make entry then skip it
    tab->film[j].name = buf + i;
    tab->film[j].flags = 0;
    while(buf[i]) i++;
    i++;
  }

  // create menu structure of playlist films.
  if((menu = ka_mem_alloc(sizeof(menu_block_t) + n*sizeof(menu_item_t))) == NULL)
  {
    ka_mem_free(buf);
    ka_mem_free(tab);
    return 0; // out of memory
  }
  menu->blk = def_blk;
  msg_load_menu("playlist_m", &menu->blk);
  for(i=0; i<n; i++)
  {
    char * leafname = strrchr(tab->film[i].name,'.');
    if(leafname)
      leafname++;
    else
      leafname = tab->film[i].name;
    menu->film[i] = def_item;
    menu->film[i].text = leafname;
    menu->film[i].len = strlen(leafname);
  }
  menu->film[0].item_flags |= 0x100;
  menu->film[n-1].item_flags |= 0x80;

  // replace any current playlist with the new one.
  if(pl_buf) ka_mem_free(pl_buf);
  pl_buf = buf;
  if(pl_tab) ka_mem_free(pl_tab);
  pl_tab = tab;
  pl_pos = 0;
  if(pl_menu) ka_mem_free(pl_menu);
  pl_menu = menu;
  pl_rand = time(NULL) + clock(); // seed the random number sequence

  return 1;
}

/*
 * playist_close
 * -------------
 * closes any current playlist
 */
void playlist_close(void)
{
  if(pl_buf)
  {
    ka_mem_free(pl_buf);
    pl_buf = NULL;
  }
  if(pl_tab)
  {
    ka_mem_free(pl_tab);
    pl_tab = NULL;
  }
  if(pl_menu)
  {
    ka_mem_free(pl_menu);
    pl_menu = NULL;
  }
}

/*
 * playlist_reset
 * --------------
 * resets flags
 */
void playlist_reset(void)
{
  int i;

  if(pl_tab)
    for(i=0; i<pl_tab->num; i++)
      pl_tab->film[i].flags = 0;
}

/*
 * playlist_num
 * ------------
 * Returns the number of filem in the current playlist or zero for no playlist.
 */
unsigned int playlist_num(void)
{
  if(pl_tab)
    return pl_tab->num;
  else
    return 0;
}

/*
 * playlist_play
 * -------------
 * Selects a specified film, or next/previous/random in list.
 * Returns 1 if filename updated else returns 0.
 * This must only set the flags to trigger a restart because it may be called
 * from within mpeg2decode(). The flags are picked up on return to main().
 */
int playlist_play(player_t* player, int index)
{
  unsigned int i, j, n;
  const char* name;

  if (index == PL_SINGLE)
    name = filename;
  else if(!pl_tab)
    return 0;
  else
  {
    switch(index)
    {
      case PL_NEXT:
      {
        if(pl_pos >= (pl_tab->num - 1))
        {
          if(config.control & cfg_ctrl_loop)
            pl_pos = 0;
          else
            return 0; // at end of list
        }
        else
          pl_pos++;
      }
      break;
      case PL_PREVIOUS:
      {
        if(pl_pos <= 0)
        {
          if(config.control & cfg_ctrl_loop)
            pl_pos = pl_tab->num - 1;
          else if (last_filename != filename)
            return 0; // at start of list
        }
        else
          pl_pos--;
      }
      break;
      case PL_RANDOM: // choose a film at random from those not yet played
      {
        // count number of films left to play
        for(i=n=0; i<pl_tab->num; i++)
          if(!(pl_tab->film[i].flags & PL_PLAYED))
            n++;

        if(n == 0)
          return 0; // all played

        if(n > 1)
        { // generate the next random number between 1 and n
          pl_rand = pl_rand * 1103515245 + 12345;
          n = (pl_rand % n) + 1;
        }

        // find film n
        for(i=j=0; i<n; i++)
        {
          while(pl_tab->film[j].flags & PL_PLAYED) j++;
          j++;
        }
        pl_pos = j - 1;
      }
      break;
      default: // select directly
      {
        if((index >= 0) && (index < pl_tab->num))
          pl_pos = index;
        else
          return 0; // out of range
      }
    }

    // Copy filename. Set flags to trigger action.
    snprintf(filename, sizeof(filename), "%s", pl_tab->film[pl_pos].name);
    pl_tab->film[pl_pos].flags |= PL_PLAYED;
    name = pl_tab->film[pl_pos].name;
  }

  // Update menu
  if(pl_menu)
  {
    for(i=0; i<playlist_num(); i++)
      pl_menu->film[i].item_flags &= ~(IT_TICKED); // clear any ticks
    pl_menu->film[pl_pos].item_flags |= IT_TICKED; // tick current film
  }

  // Set flags to trigger action.
  if (last_filename != name)
  {
    last_filename = name;
    player_restartPlay(player, player_status_new);
  }
  else
    player_restartPlay(player, player_status_restart);

  return 1;
}

/*
 * file_dropped
 * ------------
 * Called when a file is dropped on the player or control panel windows.
 * This must only set the flags to trigger a restart because it is called
 * from within mpeg2decode(). The flags are picked up on return to main().
 */
void file_dropped(player_t* player, int type, char *name)
{
  switch(type)
  {
    case 0x1000: // directory, if contains mpeg's create a temporary playlist file
    case FILETYPE_TEXT:   // text file, check for a playlist
    {
      if(playlist_open(name))
        playlist_play(player, (config.control & cfg_ctrl_random) ? PL_RANDOM : 0);
    }
    break;
    default: // check file, play if ok, ignore if not
    {
      last_filename = NULL;
      snprintf(filename, sizeof(filename), "%s", name);
      playlist_play(player, PL_SINGLE);
    }
  }
}

const char* playlist_getcurrentfilm(void)
{
  return filename;
}

pl_menu_t* playlist_getmenu(void)
{
  return pl_menu;
}
