// Programación en C++ para Ingenieros, Ed. Thomson Paraninfo, 2006
// Apéndice B: La Biblioteca CDAudioLib

// Fichero AplicacionCDAudio.cpp
// Implementación de la clase AplicacionCDAudio

#include "AplicacionCDAudio.h"

// Declaración de los atributos estáticos
HWND AplicacionCDAudio::hDlg;
std::vector<int> AplicacionCDAudio::vListaTracks;
int AplicacionCDAudio::iTrackActual;
bool AplicacionCDAudio::bPausa;
bool AplicacionCDAudio::bRandom;
bool AplicacionCDAudio::bPlaying;
bool AplicacionCDAudio::bRepetir;
bool AplicacionCDAudio::bResume;

// Método constructor, crea la ventana de diálogo, inicializa los
// atributos de estado y realiza un reset de la ventana de diálogo hDlg
// y de la lista de reproducción vListaTracks.
AplicacionCDAudio::AplicacionCDAudio(HINSTANCE hThisInstance) {
    hDlg = CreateDialog(hThisInstance,"diálogoCDAudio",NULL, DlgProc);
    bRandom = false;
    bRepetir = false;
    bResume = false;
    reset();
}

// Método de ejecución de la aplicación. Muestra la ventana de diálogo y
// procesa todos los mensajes mediante un bucle. Si no se puede abrir el
// dispositivo muestra un mensaje de error y finaliza la aplicación.
int AplicacionCDAudio::Ejecutar(void) {
    MSG messages;
    if(OpenCDAudio()) {
        MessageBox(hDlg, MensajeError(), NULL,MB_OK);
        return 1;
    }

    ShowWindow (hDlg,SW_SHOW);

    while(GetMessage (&messages, NULL, 0, 0))
    {
        if(!IsDialogMessage(hDlg,&messages)) {
            /* Traduce los mensajes de las teclas virtuales en mensajes de caracteres */
            TranslateMessage(&messages);
            /* Envia el mensaje a la función de atención de la ventana */
            DispatchMessage(&messages);
        }
    }
    CloseCDAudio();
    return messages.wParam;
}

// Función de atención a los mensajes de la ventana de diálogo hDlg.
// Comprueba que botón o casilla de verificación ha pulsado el usuario
// y llama al método correspondiente.
BOOL CALLBACK AplicacionCDAudio::DlgProc(HWND hDlg, UINT msg,
                                         WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
        case WM_INITDIALOG:
           return TRUE;
        case WM_COMMAND:
           switch(LOWORD(wParam)) {
              case IDC_BUTTON_ANTERIOR:
                 botonAnterior();
                 break;
              case IDC_BUTTON_PLAY_PAUSA:
                 botonPlayPausa();
                 break;
              case IDC_BUTTON_SIGUIENTE:
                 botonSiguiente();
                 break;
              case IDC_BUTTON_EXPULSAR_CD:
                 botonExpulsar();
                 break;
              case IDC_BUTTON_STOP:
                 botonStop();
                 break;
              case IDC_CHECK_RANDOM:
                 controlCheckRandom();
                 break;
              case IDC_CHECK_REPETIR:
                 controlCheckRepetir();
                 break;
              case WM_DESTROY:
                 PostQuitMessage (0);
                 EndDialog(hDlg, FALSE);
                 break;
           }
           return TRUE;
    }
    return FALSE;
}

// Método que se ejecuta cuando se pulsa el boton Play/Pause. Cambia el
// estado de la aplicación de play a pause y viceversa. Actualiza los
// botones de la ventana de diálogo hDlg.
void AplicacionCDAudio::botonPlayPausa(void) {
    char wsPlayPause[30];
    if(!IsPlaying()) {
        // Si no está reproduciendo se comprueba que haya un disco
        // a reproducir y que la lista de reproducción sea correcta.
        if(NumeroDeTracks() < 1 || NumeroDeTracks() != vListaTracks.size())
            // Se actualiza la lista de reproducción vListaTracks y la
            // ventana de diálogo
            reset();
        if(vListaTracks.size() != 0) {
            // Si hay algún track a reproducir cambia el estado a play.
            bPlaying = true;
            // Cuando está en reprodución el mismo botón sirve para la pausa.
            SetDlgItemText(hDlg,IDC_BUTTON_PLAY_PAUSA,"Pausa");
            // Dependiendo de si estaba en pausa o no solicita al dispositivo
            // continuar la reproducción anterior o iniciar la reproducción
            // del track actual de la lista.
            if(bPausa) {
                bPausa = false;
                Resume(intProcedure);
                bResume = true;
            }
            else {
                PlayTrack(vListaTracks[iTrackActual],intProcedure);
            }
        }
    }
    else{
        // Si está reproduciendo cambia el estado a pausa y solicita
        // la pausa al dispositivo.
        SetDlgItemText(hDlg,IDC_BUTTON_PLAY_PAUSA,"Play");
        bPausa = true;
        bPlaying = false;
        Pause();
    }
}

// Método que se ejecuta cuando se pulsa el botón Siguiente.
// Actualiza el track actual al siguiente de la lista de
// Tracks vListaTracks y si está reproduciendo se incia la
// reproducción del nuevo track. Se comprueba que la lista de
// reproducción es correcta y si no lo es actualiza la lista de
// reproducción vListaTracks y la ventana de diálogo.
void AplicacionCDAudio::botonSiguiente(void) {
    if(NumeroDeTracks() != vListaTracks.size()) {
        reset();
    }
    if(vListaTracks.size() != 0) {
        iTrackActual = (iTrackActual+1) % vListaTracks.size();
        char wsTrack[5];
        // Se actualiza el valor del campo track actual de la ventana.
        wsprintf(wsTrack,"%i",vListaTracks[iTrackActual]);
        SetDlgItemText(hDlg,IDC_EDIT_TRACK_ACTUAL,wsTrack);
        if(bPlaying) {
            if(PlayTrack(vListaTracks[iTrackActual],intProcedure))
                reset();
        }
    }
}

// Método que se ejecuta cuando se pulsa el botón Anterior.
// Actualiza el track actual al anterior de la lista de
// tracks vListaTracks y si está reproduciendo se incia la
// reproducción del nuevo track. Se comprueba que la lista de
// reproducción es correcta y si no lo es actualiza la lista de
// reproducción vListaTracks y la ventana de diálogo.
void AplicacionCDAudio::botonAnterior(void) {
    if(NumeroDeTracks() != vListaTracks.size()) {
        reset();
    }
    if(vListaTracks.size() != 0) {
        if(iTrackActual == 0)
            iTrackActual = vListaTracks.size()-1;
        else iTrackActual--;
        char wsTrack[5];
        // Se actualiza el valor del campo track actual de la ventana.
        wsprintf(wsTrack,"%i",vListaTracks[iTrackActual]);
        SetDlgItemText(hDlg,IDC_EDIT_TRACK_ACTUAL,wsTrack);
        if(bPlaying) {
            if(PlayTrack(vListaTracks[iTrackActual],intProcedure))
                reset();
        }
    }
}

// Método que se ejecuta cuando se pulsa el botón Expulsar.
// Abre la puerta del dispositivo, expulsa el CD y actualiza
// la lista de reproducción y la ventana de diálogo.
void AplicacionCDAudio::botonExpulsar(void) {
    OpenDoorCD();
    reset();
}

// Método que se ejecuta cuando se pulsa el botón Stop.
// Si está reproduciendo detiene la reproducción. Si es
// necesario actualiza la lista de reproducción y la ventana
// de diálogo.
void AplicacionCDAudio::botonStop(void) {
    Stop();
    if(NumeroDeTracks() != vListaTracks.size()) {
        reset();
    }
    else {
        bPlaying = false;
        bPausa = false;
        SetDlgItemText(hDlg,IDC_BUTTON_PLAY_PAUSA,"Play");
    }
}

// Método que se ejecuta cuando se marca o desmarca la casilla
// de verificación Orden aleatorio. Actualiza la lista de
// reproducción vListaTracks según corresponda.
void AplicacionCDAudio::controlCheckRandom(void) {
    bRandom = !bRandom;
    if(bRandom)
        generarListaAleatoriaTracks();
    else
        generarListaTracks();
}

// Método que se ejecuta cuando se marca o desmarca la casilla
// de verificación Repetir. Actualiza el atributo de estado bRepetir
// que indica si la casilla está marcada o no.
void AplicacionCDAudio::controlCheckRepetir(void) {
    bRepetir = !bRepetir;
}

// Método que genera la lista de tracks a reproducir en el mismo orden
// que se encuentran en el CD.
void AplicacionCDAudio::generarListaTracks(void) {
    int auxTrackActual;
    // Se ha de almacenar el track actual para actualizar su posición
    // en la nueva lista de reproducción.
    if(iTrackActual < vListaTracks.size())
        auxTrackActual = vListaTracks[iTrackActual];
    else auxTrackActual = 0;
    vListaTracks = std::vector<int>(NumeroDeTracks());
    for(int i = 0; i < vListaTracks.size() ; i++) {
        vListaTracks[i] = i+1;
        if (i+1 == auxTrackActual)
            iTrackActual = i; // Se actualiza la posición del track actual
    }
}

// Método que genera la lista de tracks a reproducir en un orden
// aleatorio.
void AplicacionCDAudio::generarListaAleatoriaTracks(void) {
    int auxTrackActual;
    // Se ha de almacenar el track actual para actualizar su posición
    // en la nueva lista de reproducción.
    if(iTrackActual < vListaTracks.size())
        auxTrackActual = vListaTracks[iTrackActual];
    else auxTrackActual = 0;
    vListaTracks = std::vector<int>(NumeroDeTracks());
    int i = 0;
    // Se introducen los tracks en la lista en orden aleatorio
    while(i < vListaTracks.size()) {
        int aux = rand() % vListaTracks.size() + 1;
        // Se ha de comprovar que el track escogido aleatoriamente
        // no este ya en la lista de reproducción antes de añadirlo.
        bool bYaEsta = false;
        int j = 0;
        while (j < i && !bYaEsta) {
            bYaEsta = vListaTracks[j] == aux;
            j++;
        }
        if(!bYaEsta) {
            vListaTracks[i] = aux;
            if (aux == auxTrackActual)
                iTrackActual = i;
            i++;
        }
    }
}

// Método que actualiza la lista de reproducción vListaTracks y la
// ventana de diálogo hDlg según el contenido del CD y las casillas
// de verificación.
void AplicacionCDAudio::reset(void) {
    bPausa = false;
    bPlaying = false;
    if(NumeroDeTracks() < 1) {
        iTrackActual = -1;
        SetDlgItemText(hDlg,IDC_EDIT_TRACK_ACTUAL,"");
        vListaTracks = std::vector<int>();
        SetDlgItemText(hDlg,IDC_BUTTON_PLAY_PAUSA,"Play");
    }
    else {
        if(bRandom)
            generarListaAleatoriaTracks();
        else
            generarListaTracks();
        iTrackActual = 0;
        char wsTrack[5];
        wsprintf(wsTrack,"%i",vListaTracks[iTrackActual]);
        SetDlgItemText(hDlg,IDC_EDIT_TRACK_ACTUAL,wsTrack);
        SetDlgItemText(hDlg,IDC_BUTTON_PLAY_PAUSA,"Play");
    }
}

// Función de atención a las interrupciones del dispositivo.
void AplicacionCDAudio::intProcedure(int wParam) {
    // Si la petición de reproducción ha finalizado correctamente
    // y hay que reproducir más canciones reproduce la siguiente
    // utilizando el método botonSiguiente().
    if(wParam == MCI_NOTIFY_SUCCESSFUL) {
        if(iTrackActual+1 < vListaTracks.size() || bRepetir) {
            if(bResume) bResume = false;
            else botonSiguiente();
        }
    }
    // Si la petición no ha finalizado correctamente se comprueba
    // que no se haya quitado el CD (o sustituido) y si es así se
    // actualiza la lista de reproducción y la ventana de diálogo.
    else if(NumeroDeTracks() != vListaTracks.size()) {
        reset();
    }
}