/* shuffle.c
 *
 * Copyright 1998 daniel risacher
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 dated June, 1991.
 *
 * 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.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 */


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/file.h>
#include <sys/types.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <pwd.h>
#include <limits.h>
#include <sys/time.h>
#include <sys/types.h>
#include <string.h>

#include "config.h"
#include "cdtool.h"
#include "commands.h"
#include "hardware.h"
#include "info.h"
#include "shuffle.h"
#include "util.h"

/* globals for this module */
static int   iDone      = FALSE;
static int   iNext      = FALSE;
static int   iBEVerbose = FALSE;
static char  *pszName   = NULL;

/************************************************************************/
/* Procedure:  huphdl
 * Purpose:    handler for SIGHUP
 * 
 * Inputs:     
 * Outputs:    TRUE to iNext flag -- do next track
 * Returns:    
 * Notes:  
 *   1.  Sending a HANGUP (SIGHUP,1) will cause cdshuffle to skip
 *       to the next track.  This may be used for debug or just if
 *       you don't like a song.  
 */
/************************************************************************/
void huphdl ()
{
    signal (SIGHUP, huphdl);
    iNext = TRUE;
    if (iBEVerbose)
	fprintf (stderr, "%s:  Hangup received....\n", pszName);
}

/************************************************************************/
/* Procedure:  inthdl
 * Purpose:    handler for SIGINT
 * 
 * Inputs:     
 * Outputs:    TRUE to iDone flag -- finished with processing
 * Returns:    
 * Notes:  
 *   1.  Sending an INTERRUPT (SIGINT,2) will cause cdshuffle to 
 *       stop playing, remove its PID file, and exit. 
 */
/************************************************************************/
void inthdl ()
{
    signal (SIGINT, inthdl);
    iDone = TRUE;
    if (iBEVerbose)
	fprintf (stderr, "%s:  Interrupt received....\n", pszName);

}

/************************************************************************/
/* Procedure:  wait_track
 * Purpose:    to wait for a track to finish
 * 
 * Inputs:     program name, CD file des., track number
 * Outputs:    none
 * Returns:    pointer to temp string
 * Notes:  
 *   1.  Polls the CD-ROM at a 1 second interval waiting for a 
 *       track to finish....
 */
/************************************************************************/
int wait_track(int cdfd, int track)
{
  struct cdrom_subchnl subchnl;
  debugmsg("%P:  waiting on track %d", track);

  while (1) {
    
    subchnl.cdsc_format = CDROM_MSF;
    if (ioctl(cdfd, CDROMSUBCHNL, &subchnl) == -1) {
	if (errno == EINTR)
	    return TRUE;

	errormsg("%P: ioctl cdromsubchnl: %s", strerror(errno));
	return (-1);
    }
    
    switch ( subchnl.cdsc_audiostatus ) {
    case CDROM_AUDIO_PAUSED:
    case CDROM_AUDIO_PLAY:
      sleep(1);
      if (iDone == TRUE) {
	  debugmsg("%P: done with audio r=%#x", subchnl.cdsc_audiostatus);
	  return TRUE;
      }

      if (iNext == TRUE) {
	  debugmsg("%P: next track processing r=%#x", subchnl.cdsc_audiostatus);
	  iNext = FALSE;
	  return TRUE;
      }
      break;

    case CDROM_AUDIO_COMPLETED:  
	debugmsg("%P: Audio Completed r=%#x", subchnl.cdsc_audiostatus);
	return TRUE;

    default:
	debugmsg("%P: default r=%#x", subchnl.cdsc_audiostatus);
        return TRUE;
    }
  }
}

/************************************************************************/
/* Procedure:  shuffle_lock
 * Purpose:    create pidfile for shuffle
 ************************************************************************/
int shuffle_lock(int cdnum)
{
    pid_t my_pid = getpid();
    char pidfile[PATH_MAX];
    struct passwd *pw;
    FILE *fp;

    pw = getpwuid(getuid());
    if (pw == NULL) {
        fprintf(stderr, "Username not in passwd database - aborting\n");
        exit(1);
    }

    snprintf(pidfile, sizeof(pidfile), "%s%s%d", pw->pw_dir, PID_FILE, cdnum);
    
    fp = fopen (pidfile, "w");
    if (fp == NULL) {
        errormsg("%P: Error opening pid file %s: %s", pidfile,strerror(errno));
        return(-1);
    }

    verbosemsg("%P: Writing PID file %s\n", pidfile);
    fprintf(fp, "%ld\n", (unsigned long) my_pid);
    fclose (fp);
    return(0);
}

/************************************************************************/
/* Procedure:  shuffle_unlock
 * Purpose:    remove pidfile and (optionally) kill running cdshuffle 
 ************************************************************************/
int shuffle_unlock(int cdnum, int stop)
{
    pid_t shuffle_pid;
    char buf[255];
    char pidfile[PATH_MAX];
    struct passwd *pw;
    FILE *fp;

    pw = getpwuid(getuid());
    if (pw == NULL) {
        fprintf(stderr, "Username not in passwd database - aborting\n");
        exit(1);
    }

    snprintf (pidfile, sizeof(pidfile), "%s%s%d", pw->pw_dir, PID_FILE, cdnum);
    fp = fopen (pidfile, "r");
    if (fp == NULL) {
        verbosemsg("%s: Error opening pid file %s: %s", "cdshuffle",
          pidfile, strerror(errno));
        return(0);
    }

    if (fgets (buf, sizeof(buf), fp) != NULL) {
        shuffle_pid = atoi (buf);
        if (stop)
            kill (shuffle_pid, SIGINT);
    }

    fclose (fp);
    verbosemsg("%s:  removing lock file %s\n", "cdshuffle", pidfile);
    unlink (pidfile);
    return(0);
}

/************************************************************************/
/* Procedure:  do_shuffle
 * Purpose:    to play a CD-ROM in shuffle mode
 * 
 * Inputs:     program name, CD file descriptor, max tracks to play,
 *             count of tracks, verbose flag (T/F), , CD device,
 *             CR/CRLF flag
 * Outputs:    none
 * Returns:    pointer to temp string
 * Notes:  
 *   1.  Calls wait_track to wait for a track to finish playing....
 *   2.  This function should be modified for the following:
 *       A) Additon of a count of times to shuffle and max tracks to play.
 *       B) Change to call read_hw() as read_hw() includes additional 
 *          CD-ROM checks (e.g., if mounted).
 */
/************************************************************************/
void do_shuffle(cdtool_t *cdt, int iCDNum, int iTracks, int iCount)
{
  struct cdrom_tochdr tochdr;
  int tracks[100];
  int nent_tracks;
  int i, j, tmp, r;
  int iSaveTracks;
  int status;

  iSaveTracks = iTracks;
  pszName = getprogname();
  iBEVerbose = cdtool_show_verbose;
  status = 0;

  debugmsg("%P: do_shuffle:  file=%d, num=%d, trk=%d, cnt=%d, V=%d",
    cdt->fd, iCDNum, iTracks, iCount, cdtool_show_verbose);

  if (!check_disc(cdt->hw)) {
      errormsg("%P: can't shuffle (%s)", cd_status_text(cdt->hw->iStatus));
      return;
  }

  /* get number of tracks, if not available, error... */
  if ( ioctl(cdt->fd, CDROMREADTOCHDR, &tochdr) == -1 ) {
      errormsg("%P: ioctl cdromreadtochdr: %s", strerror(errno));
      return;
  }

  nent_tracks = tochdr.cdth_trk1;

  signal (SIGINT, inthdl);
  signal (SIGHUP, huphdl);

  /* update the pid file */
  shuffle_lock(iCDNum);

  /* repeat play N time (if N = -1, do forever) */
  iDone = FALSE;
  while (iDone == FALSE) {

      debugmsg("do_shuf:  tracks=%d repeat=%d verbose=%d",
              iTracks, iCount, cdtool_show_verbose);

      /* generate random list of tracks to play */
      for (i=0; i<nent_tracks; i++)
          tracks[i] = i+1;

      srand(time(NULL));
      for (j=0; j<2; j++) {
          for (i=0; i<nent_tracks; i++) {
              r = (int) (nent_tracks * (rand() / (RAND_MAX+1.0)));

              debugmsg("do_shuf:  j=%d, i=%d, r=%d, tracks=%d",
                      j, i, r, nent_tracks);

              tmp = tracks[j];
              tracks[j] = tracks[r];
              tracks[r] = tmp;
          }
      }

      for (i=0; i<nent_tracks; i++) {
          debugmsg("do_shuf:  I=%02d track=%02d", i, tracks[i]);
      }

      /* play each track in random order */
      for (i=0; i<nent_tracks; i++) {
          debugmsg("do_shuf:  playing %d", tracks[i]);
          if (cdtool_show_verbose)
              infomsg("%P:  playing track %d", tracks[i]);

          /* this plays only this track */
          if (do_play(cdt, tracks[i], tracks[i]) < 0) {
              /* error */
              iDone = TRUE;
              break;
          }

          /* if we are doing 1 track only, exit */
          if ((iCount <= 1) && (iTracks == 1) &&
                  (iSaveTracks == 1))
              break;

          wait_track(cdt->fd, tracks[i]);

          /* if we have played the desired # of tracks, DONE */
          iTracks--;
          if (iTracks <= 0) {
              iTracks = iSaveTracks;
              break;
          }

          if (iDone == TRUE)
              break;
      }

      /* determine if we are to repeat the disc, iCount==0 is forever */
      if (iCount > 0) {
          iCount --;
          if (iCount == 0)
              break;
      }
  }

  shuffle_unlock(iCDNum, 0);

  if (ioctl(cdt->fd, CDROMSTOP) == -1 ) {
      errormsg("%P: ioctl cdromstop: %s", strerror(errno));
  }
}

