#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include "cd.h"

#if defined(OS_LINUX)
 #include "cd_ll_linux.h"
#elif defined(OS_BSD)
 #include "cd_ll_bsd.h"
#else
 #error "You haven't defined your platform!"
#endif

// initialize
cd::cd(void)
    : m_mode(NORMAL), m_status(NO_CD_INSIDE), m_tracks(0), m_volume(255),
      m_looping(false), m_random(true), m_track(0), m_one_track_only(false),
      m_ind(0), m_programmed(0)
{
    srand( (unsigned int) time(NULL) );

#if defined(OS_LINUX)
    m_ll = new cd_ll_linux();
#elif defined(OS_BSD)
    m_ll = new cd_ll_bsd();
#endif
 
    // this is clever :)
    // 27.11.99: apparently too clever as I no longer have any idea
    // what the above comment and/or code means...
    m_tr[0]=0;
    open();
    
    read_volume();
    read_status();
}

// close the cd
cd::~cd()
{
    m_ll->close();
}

// new cd in drive, initialize all data
void cd::new_cd(void)
{
    if (m_status == NO_CD_INSIDE)
        return;

    read_info();
    read_status();
    init_tracks();
}

// read the number of tracks
void cd::read_info(void)
{
    if (m_status == NO_CD_INSIDE)
        return;

    cd_ll_toc_header toc_hdr;

    int x = m_ll->read_toc_header(&toc_hdr);
    
    if (x == -1)
        return;

    m_tracks = toc_hdr.tracks;
}
    
// open cd-rom device
void cd::open(void)
{
    if (m_ll->is_open())
        return;

    m_ll->open();
    if (m_ll->is_open() == false) {
        fprintf(stderr, "Cannot open CD-ROM device, exiting.\n");
        exit(1);
    }
}

// close cd-rom device
void cd::close(void)
{
    if (m_ll->is_open() == false)
        return;

    m_ll->close();
}

// read current status
// if track changed, play next track. meaning of "next track" varies
// with the mode.
// also detects cd insertions/ejections
void cd::read_status(void)
{
    cd_ll_status st;
        
    int x = m_ll->read_status(&st);

    // new cd inserted
    if ((m_status == NO_CD_INSIDE) && (x != -1)) {

        // the cd could be playing or paused, but it doesn't matter
        // since new_cd() calls read_status() again which then sets it
        // to the correct value
        m_status=STOPPED;
        new_cd();

        return;
    } else if (x == -1) {

        // cd ejected
        m_status=NO_CD_INSIDE;
        m_tracks=0;
        m_ind=0;
        m_track=0;
        m_programmed=0;
        clear_tracks();
        
        return;
    }

    switch (st.audio_status) {
    case CD_LL_PLAYING:
        m_status = PLAYING;
        m_track = st.track;
        break;
    case CD_LL_PAUSED:
        m_status = PAUSED;
        break;
    case CD_LL_NO_STATUS:
    case CD_LL_INVALID:
        // many drives report INVALID for stopped status
        m_status = STOPPED;
        break;
    case CD_LL_PLAY_COMPLETED:
        if (m_one_track_only) {
            m_one_track_only=false;

            // this is needed on some drives, for example on mine,
            // since for some reason they don't automatically stop
            // spinning after completing a track, resulting in a very
            // annoying humming noise requiring you to manually press
            // the 'S' key to stop the drive. this achieves the same
            // effect automatically.
            stop();
        }
        else
            next();
        break;
    }
}

// play a single track
void cd::play_one(int track)
{
    m_one_track_only=true;
    play(track);
}

// play the track and continue after it with the current mode
void cd::play(int track)
{
    if (m_status == NO_CD_INSIDE) {
        m_one_track_only=false;
        
        return;
    }

    if ( (track < 1) || (track > m_tracks) ) {
        m_one_track_only=false;
        
        return;
    }

    stop();

    // start spinning the drive.
    // necessary on some drives
    m_ll->start();

    cd_ll_tracks t;

    t.start_track = track;
    t.end_track = track;

    m_ll->play(&t);
    
}

// continue playing from where we stopped, or if we're at the start of
// the disc from the beginning. if we're at the start and in random
// mode, shuffle the right amount of tracks
void cd::play(void)
{
    if (m_status == NO_CD_INSIDE)
        return;

    if (m_ind < 1) {
        if ((m_mode == PROGRAMMED) && m_random)
            shuffle_tracks(m_programmed);
        else if ((m_mode == NORMAL) && m_random)
            shuffle_tracks(m_tracks);
        m_ind=1;
    }

    // it shouldn't be possible for m_ind to grow beyond its limits

    play(m_tr[m_ind]);
}
    
// stop
void cd::stop(void)
{
    if (m_status == NO_CD_INSIDE)
        return;

    m_ll->stop();
}

// pause
void cd::pause(void)
{
    if (m_status != PLAYING)
        return;
    
    m_ll->pause();
}

// resume
void cd::resume(void)
{
    if (m_status != PAUSED)
        return;

    m_ll->resume();
}

// eject
void cd::eject(void)
{
    stop();
    m_ll->eject();
}

// set volume
void cd::set_volume(int volume)
{
    cd_ll_volume vol;

    if (volume > 255)
        volume=255;
    else if (volume < 0)
        volume=0;
    
    m_volume = volume;

    vol.left = volume;
    vol.right = volume;

    m_ll->set_volume(&vol);
}

// read volume
void cd::read_volume(void)
{
    cd_ll_volume vol;

    int x = m_ll->read_volume(&vol);
    if (x == -1)
        return;

    m_volume = vol.left;
}

// return volume
int cd::get_volume(void)
{
    return m_volume;
}

// next track
void cd::next(void)
{
    m_ind++;
    
    // have we played through the whole array?
    if ( (m_ind > MAX_TRACKS) || (m_tr[m_ind] == -1) ) {
        tracks_played();
        return;
    } else
        play(m_tr[m_ind]);
}

// clear the track array
void cd::clear_tracks(void)
{
    for (int i=1; i <= MAX_TRACKS; i++)
        m_tr[i]=-1;
}

// set mode
void cd::set_mode(mode m)
{
    m_mode=m;
    init_tracks();
}

// init track array according to current mode
void cd::init_tracks(void)
{
    if (m_status == NO_CD_INSIDE)
        return;

    m_ind=0;
    m_programmed=0;
    clear_tracks();
    if (m_mode == NORMAL) {
        for (int i=1; i <= m_tracks; i++)
            m_tr[i]=i;
    }
}

// shuffle first n entries of m_tr
void cd::shuffle_tracks(int n)
{
    for (int i=1; i <= n; i++) {
        int temp=m_tr[i];
        int t=(rand() % n)+1;
        m_tr[i]=m_tr[t];
        m_tr[t]=temp;
    }
}

// called when all tracks in array are played
// if we're not looping, stop playing.
// if we're looping, start over at the beginning of the array.
void cd::tracks_played(void)
{
    if (!m_looping) {
        stop();

        return;
    }
    
    m_ind=0;
    play();
}

// if we're in PROGRAMMED mode, add a new track to be played
void cd::push_track(int track)
{
    if (m_mode != PROGRAMMED)
        return;

    if (m_programmed >= MAX_TRACKS)
        return;

    if ( (track < 1) || (track > m_tracks) )
        return;
    
    m_programmed++;
    m_tr[m_programmed]=track;
}
