/*
*  ximp3  A simple mp3 player
*
*  Copyright (C) 2001 Mats Peterson
*
*  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; see the file COPYING.  If not, write to
*  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
*  Boston, MA 02111-1307, USA.
*
*  Please send any comments/bug reports to
*  [email protected]  (Mats Peterson)
*/

#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/soundcard.h>

#include "xingmp3.h"
#include "ximp3.h"
#include "init.h"
#include "audio.h"
#include "audiothread.h"
#include "info.h"
#include "id3.h"


VARS *v;


static int bs_fill(int fd)
{
   unsigned int nread;

   if (v->bs_bufbytes < 0)
       v->bs_bufbytes = 0;          // signed var could be negative

   if (v->bs_bufbytes < v->bs_trigger) {
       memmove(v->bs_buffer, v->bs_bufptr, v->bs_bufbytes);
       nread = read(fd, v->bs_buffer + v->bs_bufbytes,
               BS_BUFBYTES - v->bs_bufbytes);
       if ((nread + 1) == 0) {
/*-- test for -1 = error --*/
           v->bs_trigger = 0;
           errmsg("File read error");
           return 0;
       }
       v->bs_bufbytes += nread;
       v->bs_bufptr = v->bs_buffer;
   }

   return 1;
}


static void add_playbuf(char *pcm, int size, int in_bytes, int frames)
{
   PLAYBUF new;

   memcpy(new.pcm, pcm, size);
   new.size = size;
   new.in_bytes = in_bytes;
   new.frames = frames;
   if (write(v->pfds[1], &new, sizeof(PLAYBUF)) == -1) {
       perror("write");
       exit(1);
   }
}


static void play_file(char *filename)
{
   char         *pcm_buffer;
   unsigned int pcm_bufbytes;
   int          fd;
   int          framebytes;
   int          frames;
   MPEG_HEAD    head;
   MPEG         m;
   unsigned int skip;
   IN_OUT       x;
   int          in_bytes;
   DEC_INFO     decinfo;
   int          bitrate;
   int          r;

   if (! v->remote)
       printf("\nMPEG input file: %s\n", filename);

   mpeg_init(&m, 1);
   in_bytes = 0;

   pcm_buffer = NULL;
   pcm_bufbytes = 0;

   fd = -1;
   v->bs_buffer = NULL;
   v->bs_bufbytes = 0;
   v->bs_bufptr = v->bs_buffer;
   v->bs_trigger = 2500;

/*--- test for valid cvt_to_wave compile ---*/
   if (cvt_to_wave_test() != 0) {
       errmsg("Invalid cvt_to_wave compile");
       goto abort;
   }

/*----- open audio device ------*/
   if (! (v->audio_fd = open_audio(v->device)))
       exit(1);

/*------ open mpeg file --------*/
   if ((fd = open(filename, O_RDONLY)) < 0) {
       errmsg("Couldn't open input file");
       goto abort;
   }

/*----- get id3 info -----*/
   out_id3_info(fd, filename, v->remote);

/*--- allocate bs buffer ----*/
   v->bs_buffer = malloc(BS_BUFBYTES);
   if (v->bs_buffer == NULL) {
       errmsg("Couldn't allocate buffer");
       goto abort;
   }

/*--- fill bs buffer ----*/
   if (! bs_fill(fd))
       goto abort;

/*---- parse mpeg header  -------*/
   framebytes = head_info3(v->bs_buffer, v->bs_bufbytes, &head,
           &bitrate, &skip);
   if (framebytes == 0) {
       errmsg("Bad or unsupported MPEG file");
       goto abort;
   }
   v->bs_bufptr += skip;
   v->bs_bufbytes -= skip;

/*--- display mpeg info --*/
   out_mpeg_info(&head, bitrate);

/*---- allocate temporary pcm buffer -----*/
   pcm_buffer = malloc(PCM_BUFBYTES);
   if (pcm_buffer == NULL) {
       errmsg("Couldn't allocate PCM buffer");
       goto abort;
   }

/*------ init decoder -------*/
   if (! audio_decode_init(&m, &head, framebytes, 0, 0, 0, 24000)) {
       errmsg("Decoder init failed");
       goto abort;
   }

/*------ display decode info ------*/
   out_decode_info(&m, &decinfo);

/*------ configure audio device ------*/
   r =  config_audio((int)decinfo.bits, decinfo.channels, decinfo.samprate);
   if (r < 0)
       exit(1);
   if (! r)
       goto abort;

   v->bytes_sec = decinfo.samprate * (decinfo.bits / 8) * decinfo.channels;

/*--- init wave converter ---*/
   cvt_to_wave_init(decinfo.bits);

   if (! v->remote)
       printf("\n");

/*---- Create audio output thread ----*/
   if (pthread_create(&v->thr, NULL, audio_thread, NULL) < 0) {
       fprintf(stderr, "Couldn't create audio thread\n");
       exit(1);
   }

/*----- DECODE -----*/
   for (frames = 0;;) {
       if (! bs_fill(fd))
           break;
       if (v->bs_bufbytes < framebytes)
           break;                 /* end of file */

       x = audio_decode(&m, v->bs_bufptr,
               (short *) (pcm_buffer + pcm_bufbytes));

       if (x.in_bytes <= 0) {
           errmsg("Bad sync in MPEG file");
           break;
       }

       v->bs_bufptr += x.in_bytes;
       v->bs_bufbytes -= x.in_bytes;
       pcm_bufbytes += x.out_bytes;
       frames++;
       if (pcm_bufbytes == PCM_BUFBYTES) {
           pcm_bufbytes = cvt_to_wave(pcm_buffer, pcm_bufbytes);
           add_playbuf(pcm_buffer, pcm_bufbytes, in_bytes, frames);
           pcm_bufbytes = 0;
       }
       in_bytes += x.in_bytes;
   }

/*---------------------*/

   if (pcm_bufbytes > 0) {
       pcm_bufbytes = cvt_to_wave(pcm_buffer, pcm_bufbytes);
       add_playbuf(pcm_buffer, pcm_bufbytes, in_bytes, frames);
       pcm_bufbytes = 0;
   }

/*--- send terminating buffer and wait for thread to terminate ---*/
   add_playbuf(NULL, 0, in_bytes, frames);
   pthread_join(v->thr, NULL);

abort:
   mpeg_cleanup(&m);
   if (v->audio_fd)
       close(v->audio_fd);
   if (fd > 0)
       close(fd);
   if (v->bs_buffer)
       free(v->bs_buffer);
   if (pcm_buffer)
       free(pcm_buffer);
}


static void shuffle_files(void)
{
   int i, randnum;

   srand(time(NULL));
   v->shuffle_ord = (int *)malloc((v->flist_size + 1) * sizeof(int));
   if (! v->shuffle_ord) {
       perror("malloc");
       exit(1);
   }
   /* write songs in 'correct' order */
   for (i = 0; i < v->flist_size; i++) {
       v->shuffle_ord[i] = i;
   }
   /* now shuffle them */
   if (v->flist_size >= 2) {
       for (i = 0; i < v->flist_size; i++) {
           randnum = (rand() % (v->flist_size * 4 - 4)) / 4;
           randnum += (randnum >= i);
           v->shuffle_ord[i] ^= v->shuffle_ord[randnum];
           v->shuffle_ord[randnum] ^= v->shuffle_ord[i];
           v->shuffle_ord[i] ^= v->shuffle_ord[randnum];
       }
   }
}


static void add_file(char *fname)
{
   static int alloc_size = 0;

   if (v->flist_size + 2 > alloc_size) {
       alloc_size += 8;
       v->flist = (char **)realloc(v->flist, alloc_size * sizeof(char *));
       if (! v->flist) {
           perror("realloc");
           exit(1);
       }
   }
   if (! (v->flist[v->flist_size] = (char *)malloc(strlen(fname) + 1))) {
       perror("malloc");
       exit(1);
   }
   strcpy(v->flist[v->flist_size], fname);
   v->flist_size++;
}


static void play(int argc, char **argv)
{
   FILE *f;
   char fname[1026];
   char *p;
   int i;
   int newlist = 1;

   if (v->flist) {
       newlist = 0;
       goto playshuf;
   }

   for (i = optind; i < argc; i++) {
       p = strrchr(argv[i], '.');
       if (p && ((! strcmp(p, ".m3u") || (! strcmp(p, ".M3U"))))) {
           if (! (f = fopen(argv[i], "r"))) {
               errmsg("Couldn't open playlist");
               continue;
           }
           while (fgets(fname, 1024, f)) {
               fname[strlen(fname) - 1] = '\0';
               if (v->shuffle)
                   add_file(fname);
               else
                   play_file(fname);
           }
           fclose(f);
       } else {
           if (v->shuffle)
               add_file(argv[i]);
           else
               play_file(argv[i]);
       }
   }

   if (! v->shuffle)
       return;
playshuf:
   if (newlist)
       shuffle_files();
   for (i = 0; i < v->flist_size; i++)
       play_file(v->flist[v->shuffle_ord[i]]);
}


int main(int argc, char **argv)
{
   init(argc, argv);
   do {
       play(argc, argv);
   } while (v->loop);
   return 0;
}