/* 
    cdplay - A Commandline CD Player         
    Copyright (C) 2000 Robin Redeker

    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; either version 2 of the License, or
    (at your option) any later version.

    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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>
#include <stdlib.h>
#include <linux/cdrom.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <getopt.h>
#include <fcntl.h>
#include <time.h>
#include <string.h>

#include "cdplay.h"

float version = 1.0;
int fd;
char *device;

extern char *optarg;
extern int optind, opterr, optopt;
struct option options[] = {
  {"back",   no_argument,         NULL, 'b'},
  {"device", required_argument,   NULL, 'd'},
  {"eject",  no_argument,         NULL, 'j'},
  {"file",   required_argument,   NULL, 'f'},
  {"help",   no_argument,         NULL, 'h'},
  {"loop",   no_argument,         NULL, 'l'},
  {"listtracks", no_argument,     NULL, 'i'},
  {"pause",  no_argument,         NULL, 'p'},
  {"play",   optional_argument,   NULL, 'c'},
  {"playtrack", required_argument,NULL, 't'},
  {"playstatus", no_argument,     NULL, 'I'},
  {"random",  no_argument,        NULL, 'r'},
  {"readvol", no_argument,        NULL, 'o'},
  {"reset",   no_argument,        NULL, 'e'},
  {"resume",  no_argument,        NULL, 'u'},
  {"setvol",  no_argument,        NULL, 'O'},
  {"stop",    no_argument,        NULL, 's'},
  {"mcn",     no_argument,        NULL, 'm'},
  {"next",    no_argument,        NULL, 'n'},
  {"version", no_argument,        NULL, 'V'},
  {"verbose", no_argument,        NULL, 'v'},
  {NULL, 0, NULL, 0}
};

void 
print_help()
{
    printf("Usage: cdplay <OPTIONS>...\n\
Plays Audio CD from commandline.\n\n\
  -c, --play[=STARTTRACK]    play whole CD in right order and take STARTTRACK\n\
                             as first track.\n\
  -b, --back                 previous track\n\
  -n, --next                 next track\n\
  -d, --device=CDROM-DEVICE  select cdrom device, from which to play\n\
  -e, --reset		     hard-reset the cdrom\n\
  -f, --file=FILE            play from list-FILE\n\
  -h, --help                 this output (usage)\n\
  -i, --listtracks           lists all tracks with length\n\
  -I, --playstatus           gives out the current track and time\n\
  -l, --loop                 it will loop --playtrack, --playcd and --random\n\
  -j, --eject                ejects cd\n\
  -m, --mcn                  reads 'Universal Product Code'\n\
  -o, --readvol              reads out the cdrom volumes\n\
  -O, --setvol               sets cdrom volumes\n\
  -p, --pause                pause playing (resume with -u)\n\
  -r, --random               plays the cd in shuffle modus\n\
  -s, --stop                 stops playing\n\
  -t, --playtrack=TRACK      plays a specified TRACK\n\
  -u, --resume               resumes playing from pause\n\
  -v, --verbose              verbose output\n\
  -V, --version              prints version of cdplay and exits\n\n\
For bugs, write to elmex@ta-sa.org.\n\
You can get cdplay on http://www.ta-sa.org/?entry=cdplay\n");
}

// wait for a track to finish
void 
wait_track()
{
  double nframes, oframes;
  struct cdrom_subchnl subc;

  subc.cdsc_format = CDROM_MSF;
  if (ioctl(fd, CDROMSUBCHNL, &subc) == -1) 
    {
        printf("cdplay: couldn't get info from Subchannel!\n");
        exit(1);
    }
  
  nframes = (subc.cdsc_absaddr.msf.minute * CD_SECS * CD_FRAMES) + 
            (subc.cdsc_absaddr.msf.second * CD_FRAMES) +
            subc.cdsc_absaddr.msf.frame;
  oframes = nframes - 1;

  while (oframes != nframes && oframes < nframes) 
    {
      oframes = nframes;
      usleep(666666);
      if (ioctl(fd, CDROMSUBCHNL, &subc) == -1) 
        {
          printf("cdplay: couldn't get info from subchannel!\n");
          exit(1);
        }
      nframes = (subc.cdsc_absaddr.msf.minute * CD_SECS * CD_FRAMES) + 
                (subc.cdsc_absaddr.msf.second * CD_FRAMES) +
                subc.cdsc_absaddr.msf.frame;
    }
}

// pause the cd
void 
pause_cd()
{
  if (ioctl(fd, CDROMPAUSE) == -1) 
    {
      printf("cdplay: couldn't pause CD!\n");
      exit(1);
    }
}

// read the UPC
void 
read_mcn()
{
  struct cdrom_mcn mcn;

  if (ioctl(fd, CDROM_GET_MCN, &mcn) == -1) 
    {
      printf
          ("cdplay: couldn't read UPC (thats no big problem, most CD's not have a UPC).\n");
      exit(1);
    }
  printf("UPC: %s\n", mcn.medium_catalog_number);
}

void resume_cd()
{
    if (ioctl(fd, CDROMRESUME) == -1) {
	printf("cdplay: couldn't resume CD!\n");
	exit(1);
    }
}
void eject_cd()
{
    if (ioctl(fd, CDROMEJECT) == -1) {
	printf("cdplay: couldn't eject CD!\n");
	exit(1);
    }
}
void stop_cd()
{
    if (ioctl(fd, CDROMSTOP) == -1) {
	printf("cdplay: couldn't stop CD!\n");
	exit(1);
    }
}
void reset_cd()
{
    if (ioctl(fd, CDROMRESET) == -1) {
	printf("cdplay: couldn't reset CD!\n");
	exit(1);
    }
}
void list_tracks()
{
    int st_track, lst_track, track, start_sec, start_min, end_sec, end_min;
    struct cdrom_tocentry toc;
    struct cdrom_tochdr toch;


    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD head!\n");
	exit(1);
    }
    st_track = toch.cdth_trk0;
    lst_track = toch.cdth_trk1;

    toc.cdte_format = CDROM_MSF;

    printf("CD Contents:\n");
    printf("------------\n");
    for (track = 1; track <= lst_track; track++) {

	toc.cdte_track = track;

	if (ioctl(fd, CDROMREADTOCENTRY, &toc) == -1) {
	    printf("cdplay: couldn't read track entry: %d!\n", track);
	} else {

	    start_sec = toc.cdte_addr.msf.second;
	    start_min = toc.cdte_addr.msf.minute;

	    if (toch.cdth_trk1 == track) {
		toc.cdte_track = CDROM_LEADOUT;
	    } else {
		toc.cdte_track = track + 1;
	    }

	    if (ioctl(fd, CDROMREADTOCENTRY, &toc) == -1) {
		printf("cdplay: couldn't read cd entry\n");
	    }
	    end_sec = toc.cdte_addr.msf.second;
	    end_min = toc.cdte_addr.msf.minute;

	    printf("%02d - [%02u:%02u]\n", track,
		   (((end_min * 60) + end_sec) -
		    ((start_min * 60) + start_sec)) / 60,
		   ((((end_min
		       * 60) + end_sec) - ((start_min * 60) +
					   start_sec)) % 60));
	}
    }
}
void play_status()
{
    struct cdrom_subchnl subc;

    subc.cdsc_format = CDROM_MSF;
    if (ioctl(fd, CDROMSUBCHNL, &subc) == -1) {
	printf("cdplay: couldn't get info from Subchannel!\n");
	exit(1);
    } else {
	printf("Current playposition:\n");
	printf("---------------------\n");
	printf("Track: %02d\nPosition: [%02d:%02d]\n", subc.cdsc_trk,
	       subc.cdsc_reladdr.msf.minute, subc.cdsc_reladdr.msf.second);
    }

}
void read_vol()
{
    struct cdrom_volctrl vol;

    if (ioctl(fd, CDROMVOLREAD, &vol) == -1) {
	printf("cdplay: couldn't read volume!\n");
	exit(1);
    }
    printf("Volumes:\n\n");
    printf("Channel 0: %03d\n", vol.channel0);
    printf("Channel 1: %03d\n", vol.channel1);
    printf("Channel 2: %03d\n", vol.channel2);
    printf("Channel 3: %03d\n", vol.channel3);
}

void set_vol()
{
    int volume, channel;
    struct cdrom_volctrl vol;

    if (ioctl(fd, CDROMVOLREAD, &vol) == -1) {
	printf("cdplay: couldn't read volume!\n");
	exit(1);
    }

    printf("Set voulme for channel(0-3):");
    scanf("%d", &channel);
    if (channel <= 3 && channel >= 0) {
	printf("Enter volume(0-255):");
	scanf("%d", &volume);
	if (volume <= 255 && volume >= 0) {
	    switch (channel) {
	    case 0:
		vol.channel0 = volume;
		break;
	    case 1:
		vol.channel1 = volume;
		break;
	    case 2:
		vol.channel2 = volume;
		break;
	    case 3:
		vol.channel3 = volume;
		break;
	    }
	    printf("Volumes:\n\n");
	    printf("Channel 0: %03d\n", vol.channel0);
	    printf("Channel 1: %03d\n", vol.channel1);
	    printf("Channel 2: %03d\n", vol.channel2);
	    printf("Channel 3: %03d\n", vol.channel3);
	    if (ioctl(fd, CDROMVOLCTRL, &vol) == -1) {
		printf("cdplay: couldn't set volume!\n");
		exit(1);
	    }
	} else {
	    printf("cdplay: wrong volume: %d", channel);
	    exit(1);
	}
    } else {
	printf("cdplay: wrong channel: %d", channel);
	exit(1);
    }
}

void play_track(int track, int verbose, int loop)
{
    signed int start_sec, start_min, end_min, end_sec;
    struct cdrom_msf msf;
    struct cdrom_tocentry toc;
    struct cdrom_tochdr toch;

    toc.cdte_track = track;
    toc.cdte_format = CDROM_MSF;

    if (ioctl(fd, CDROMREADTOCENTRY, &toc) == -1) {
	printf("cdplay: couldn't read track entry: %d!\n", track);
	exit(1);
    }

    start_sec = toc.cdte_addr.msf.second;
    start_min = toc.cdte_addr.msf.minute;

    msf.cdmsf_min0 = toc.cdte_addr.msf.minute;
    msf.cdmsf_sec0 = toc.cdte_addr.msf.second;
    msf.cdmsf_frame0 = toc.cdte_addr.msf.frame;

    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD header!\n");
	exit(1);
    }

    if (toch.cdth_trk1 == track) {
	toc.cdte_track = CDROM_LEADOUT;
    } else {
	toc.cdte_track = track + 1;
    }

    if (ioctl(fd, CDROMREADTOCENTRY, &toc) == -1) {
	printf("cdplay: couldn't read cd entry\n");
    }
    msf.cdmsf_min1 = toc.cdte_addr.msf.minute;
    msf.cdmsf_sec1 = toc.cdte_addr.msf.second;
    msf.cdmsf_frame1 = toc.cdte_addr.msf.frame;

    end_sec = toc.cdte_addr.msf.second;
    end_min = toc.cdte_addr.msf.minute;

    do {
	if (verbose) {
	    printf("Playing cd-track: %d [%02u:%02u]\n", track,
		   (((end_min * 60) + end_sec) -
		    ((start_min * 60) + start_sec)) / 60,
		   ((((end_min
		       * 60) + end_sec) - ((start_min * 60) +
					   start_sec)) % 60));
	}
	if (ioctl(fd, CDROMPLAYMSF, &msf) == -1) {
	    printf("cdplay: couldn't play track: %d!\n", track);
	    return;
	}
	if (loop) {
	    wait_track();
	}
    } while (loop);

}

// playing the cd and wait for the next track in a loop
// (only way to support -v)
void 
play_cd_blk(int track, int verbose, int loop)
{
    int st_track, lst_track, elements, i;
    struct cdrom_tochdr toch;
    int *array;

    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD head!\n");
	exit(1);
    }
    st_track = toch.cdth_trk0;
    lst_track = toch.cdth_trk1;
    elements = lst_track;

    if ((array = (int *)
	 malloc(sizeof(int) * (lst_track))) == NULL) {
	printf("cdplay:%s:%d: couldn't malloc\n", __FILE__, __LINE__);
	exit(1);
    }
    for (i = 0; i < elements; i++) {
	array[i] = i + 1;
    }

    if (verbose) {
	printf("Playing from CD-Track: %d\n", track);
    }

    do {
	for (i = (track - 1); i < elements; i++) {
	    play_track(array[i], verbose, 0);
	    wait_track();
	}
    } while (loop);

}
void play_list(char *list, int verbose, int loop)
{
    int track, d = 0, i = 0, st_track, lst_track, *array = NULL, size = 0;
    FILE *f = NULL;
    char *b;
    struct cdrom_tochdr toch;

    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD head!\n");
	exit(1);
    }
    st_track = toch.cdth_trk0;
    lst_track = toch.cdth_trk1;

    f = fopen(list, "r");
    if (f == NULL) {
	printf("cdplay: couldn't open listfile: %s!\n", list);
	exit(1);
    }
    if (verbose) {
	printf("Reading list from file: %s\n", list);
	printf("Playing tracks:\n");
	printf("---------------\n");
    }

    while (dyn_fgets(&b, f)) {
	i++;
	if (sscanf(b, "%d", &track) == 0) {
	    printf("cdplay: Error in listfile on line: %d!\n", i);
	    exit(1);
	}
	if (track <= lst_track && track >= st_track) {
	    size++;
	    if (array == NULL) {
		if ((array = (int *)
		     malloc(sizeof(int))) == NULL) {
		    printf("cdplay:%s:%d: couldn't malloc!\n", __FILE__,
			   __LINE__);
		    exit(1);
		}
	    } else {
		if ((array = (int *) realloc(array, sizeof(int) * size)) ==
		    NULL) {
		    printf("cdplay:%s:%d: couldn't malloc!\n", __FILE__,
			   __LINE__);
		    exit(1);
		}
	    }
	    if (verbose) {
		printf("%d\n", track);
	    }
	    array[d] = track;
	    d++;
	} else {
	    printf("cdplay: Error in list-file on line: %d!\n", i);
	    exit(1);
	}
    }
    if (verbose) {
	printf("---------------\n");
    }

    do {
	for (i = 0; i < d; i++) {
	    play_track(array[i], verbose, 0);
	    wait_track();
	}
    } while (loop);

}
void shuffle(int n, int *array)
{
    int i, randnr, tmp;
    srand(time(NULL));

    for (i = (n - 1); i >= 0; i--) {
	randnr = (int) (((float) n) * rand() / (RAND_MAX + 1.0));
	if (i == randnr) {
	    continue;
	}
	tmp = array[i];
	array[i] = array[randnr];
	array[randnr] = tmp;
    }
}
void play_random(int verbose, int loop)
{
    int st_track, lst_track, elements, i;
    int *array;
    struct cdrom_tochdr toch;

    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD head!\n");
	exit(1);
    }
    st_track = toch.cdth_trk0;
    lst_track = toch.cdth_trk1;
    elements = lst_track;

    if ((array = (int *)
	 malloc(sizeof(int) * (lst_track))) == NULL) {
	printf("cdplay:%s:%d: couldn't malloc!\n", __FILE__, __LINE__);
	exit(1);
    }
    for (i = 0; i < elements; i++) {
	array[i] = i + 1;
    }

    do {

	shuffle(elements, array);
	for (i = 0; i < elements; i++) {
	    play_track(array[i], verbose, 0);
	    wait_track();
	}
    } while (loop);
}
void prev_track()
{
    struct cdrom_tochdr toch;
    struct cdrom_subchnl subc;
    int act_track;

    subc.cdsc_format = CDROM_MSF;
    if (ioctl(fd, CDROMSUBCHNL, &subc) == -1) {
	printf("cdplay: couldn't get info from Subchannel!\n");
	exit(1);
    } else {
        act_track=subc.cdsc_trk-1;
    }
    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD head!\n");
	exit(1);
    }
    if (act_track < 1)
      act_track=toch.cdth_trk1;
    play_track(act_track, 0, 0);
}
void next_track()
{
    struct cdrom_tochdr toch;
    struct cdrom_subchnl subc;
    int act_track;

    subc.cdsc_format = CDROM_MSF;
    if (ioctl(fd, CDROMSUBCHNL, &subc) == -1) {
	printf("cdplay: couldn't get info from Subchannel!\n");
	exit(1);
    } else {
        act_track=subc.cdsc_trk+1;
    }
    if (ioctl(fd, CDROMREADTOCHDR, &toch) == -1) {
	printf("cdplay: couldn't read CD head!\n");
	exit(1);
    }
    if (act_track > toch.cdth_trk1)
      act_track=1;
    play_track(act_track, 0, 0);
}

int main(int argc, char *argv[])
{
    int chr, verbose = 0, do_play = 0, do_stop = 0, do_cd_play = 0;
    int do_rand_play = 0, do_list_play = 0, loop = 0, tracknr;
    char *track=NULL, *list=NULL;

    device = (char *) malloc(sizeof(char) * 1024);
    strncpy(device, "/dev/cdrom", strlen("/dev/cdrom") + 1);

    fd = open(device, O_RDONLY|O_NONBLOCK);
    if (fd == -1) {
	printf("cdplay: warning: could't open cdrom:%s!\n", device);
    }

    while (
	   (chr =
	    getopt_long(argc, argv, "ef:c::d:bnhiIjlmuoOprst:vV", options,
			NULL)) != -1) {

	switch (chr) {
	case 0:
	    break;
	case 'r':
	    do_rand_play = 1;
	    break;
	case 'v':
	    verbose = 1;
	    break;
	case 't':
	    track = optarg;
	    do_play = 1;
	    break;
	case 'l':
	    loop = 1;
	    break;
	case 'c':
	    if (optarg != NULL) {
		track = optarg;
	    } else {
		track = "1";
	    }
	    do_cd_play = 1;
	    break;
	case 's':
	    do_stop = 1;
	    break;
	case 'V':
	    printf("cdplay version %1.2f\n", version);
	    exit(0);
	    break;
	case 'h':
	    print_help();
	    exit(0);
	    break;
	case 'd':
	    if (strlen(device) > 1023) {
		printf("cdplay: Devicename too long!\n");
		exit(1);
	    }
	    strncpy(device, optarg, strlen(optarg) + 1);
	    close(fd);
	    fd = open(device, O_RDONLY|O_NONBLOCK);
	    if (fd == -1) {
		printf("cdplay: could't open cdrom:%s!\n", device);
		exit(1);
	    }
	    break;
	case 'e':
	    reset_cd();
	    exit(0);
	    break;
	case 'f':
	    do_list_play = 1;
	    list = optarg;
	    break;
	case 'p':
	    pause_cd();
	    exit(0);
	    break;
	case 'u':
	    resume_cd();
	    exit(0);
	    break;
	case 'j':
	    eject_cd();
	    exit(0);
	    break;
	case 'm':
	    read_mcn();
	    exit(0);
	    break;
	case 'o':
	    read_vol();
	    exit(0);
	    break;
	case 'O':
	    set_vol();
	    exit(0);
	    break;
	case 'i':
	    list_tracks();
	    exit(0);
	    break;
	case 'I':
	    play_status();
	    exit(0);
	    break;
	case 'b':
	    prev_track();
	    exit(0);
	    break;
	case 'n':
	    next_track();
	    exit(0);
	    break;
	default:
	    print_help();
	    exit(0);
	    break;
	}
    }


    if (do_play) {
	if (sscanf(track, "%u", &tracknr) == 0) {
	    printf("cdplay: -p <number>\n");
	    exit(1);
	}
	play_track(tracknr, verbose, loop);
	return 0;
    }
    if (do_list_play) {
	play_list(list, verbose, loop);
	return 0;
    }

    if (do_rand_play) {
	play_random(verbose, loop);
	return 0;
    }
    if (do_cd_play) {
	if (sscanf(track, "%d", &tracknr) == 0) {
	    printf("cdplay: -c <number>\n");
	    exit(1);
	}
	play_cd_blk(tracknr, verbose, loop);
	return 0;
    }
    if (do_stop) {
	stop_cd();
	return 0;
    }
    close(fd);
    return 0;
}
